设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 76|回复: 0
打印 上一主题 下一主题

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

[复制链接]

Lv1.梦旅人

梦石
0
星屑
180
在线时间
18 小时
注册时间
2025-2-4
帖子
17
跳转到指定楼层
1
发表于 昨天 08:35 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
本帖最后由 雪狼的天空 于 2025-9-7 13:25 编辑

这是一份简单的物品合成插件。
实现的功能为:根据已有的配方文件和对应的操作台,如果物品栏中已有合成材料的种类和数量满足条件,则合成得到产品。例如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. [
  2.         { "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" },
  3.         { "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" },
  4.         { "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" },
  5.         { "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" },

  6.         { "id": "Default_End", "name": "本条末尾不能有逗号", "result": { "type": "item", "id": 0, "count": 0 }, "materials": [ { "type": "item", "id": 0, "count": 0 } ], "workstation": "default1" }

  7. ]
复制代码


其次是V0.9 - 原型的代码:
"ItemSynthesis_prototype.js"
  1. /*:
  2. * @target MZ
  3. * @plugindesc 物品合成系统原型 v0.9 - 支持多种配方和操作台的合成系统
  4. * @author Snowwolf
  5. *
  6. * @param RecipeFile
  7. * @type text
  8. * @text 配方文件
  9. * @desc 合成配方JSON文件的路径
  10. * @default data/SynthesisRecipe.json
  11. *
  12. * @command OpenSynthesis
  13. * @text 打开合成界面
  14. * @desc 打开指定类型的合成界面
  15. *
  16. * @arg workstationType
  17. * @type text
  18. * @text 操作台类型
  19. * @default default1
  20. *
  21. * @help
  22. * 使用说明:
  23. * 1. 准备配方文件:在指定路径创建SynthesisRecipe.json文件
  24. * 2. 在事件中使用插件命令调用合成界面
  25. * 3. 或在脚本中使用Window_SynthesisSystem.open("default1")调用打开合成界面
  26. *
  27. * 插件命令:
  28. *   OpenSynthesis workstationType - 打开指定类型的合成界面
  29. */

  30. // ==============================
  31. // 主插件代码
  32. // ==============================
  33. (() => {
  34.     // 插件参数
  35.     const parameters = PluginManager.parameters('ItemSynthesisSystem');
  36.     const recipeFile = parameters.RecipeFile || 'data/SynthesisRecipe.json';
  37.    
  38.     // 内部定义的操作台类型
  39.     const WORKSTATIONS = {
  40.         default1: { name: "缺省操作台1", icon: 224 },
  41.         default2: { name: "缺省操作台2", icon: 224 },
  42.         herbalist_table: { name: "炼金台", icon: 224 },
  43.         anvil: { name: "铁砧", icon: 225 },
  44.         stove: { name: "灶台", icon: 226 },
  45.         sewing: { name: "缝纫台", icon: 227 }
  46.     };

  47.    // 内部定义的缺省配方
  48.     const SAMPLE_RECIPES = [
  49.         { "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" },
  50.         { "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" },
  51.      ];
  52.    
  53.     // 合成系统核心类
  54.     class SynthesisSystem {
  55.         constructor() {
  56.             this._recipes = SAMPLE_RECIPES; //变量置空 =[];
  57.             this._currentWorkstation = null;
  58.             this.loadRecipes(); //加载外置配方
  59.         }
  60.         
  61.         // 加载配方文件
  62.         loadRecipes() {
  63.             const xhr = new XMLHttpRequest(); // 注:或许是异步加载,有可能出现窗口时配方未加载出来,但此状况没有遇到过。
  64.             xhr.open('GET', recipeFile);
  65.             xhr.overrideMimeType('application/json');
  66.             xhr.onload = () => {
  67.                 if (xhr.status < 400) {
  68.                     this._recipes = JSON.parse(xhr.responseText);
  69.                     console.log("配方加载成功:", this._recipes.length, "个配方");
  70.                 }
  71.             };
  72.             xhr.onerror = () => {
  73.                 console.error("无法加载配方文件:", recipeFile);
  74.             };
  75.             xhr.send();
  76.         }
  77.         
  78.         // 获取指定操作台的配方
  79.         getRecipesByWorkstation(workstationType) {
  80.             return this._recipes.filter(recipe =>
  81.                 recipe.workstation === workstationType &&
  82.                 (!recipe.requiredSwitch || $gameSwitches.value(recipe.requiredSwitch))
  83.             );
  84.         }
  85.         
  86.         // 检查材料是否足够
  87.         hasEnoughMaterials(recipe) {
  88.             return recipe.materials.every(material => {
  89.                 let count = 0;
  90.                 switch (material.type) {
  91.                     case 'item': count = $gameParty.numItems($dataItems[material.id]); break;
  92.                     case 'weapon': count = $gameParty.numItems($dataWeapons[material.id]); break;
  93.                     case 'armor': count = $gameParty.numItems($dataArmors[material.id]); break;
  94.                 }
  95.                 return count >= material.count;
  96.             });
  97.         }
  98.         
  99.         // 执行合成
  100.         performSynthesis(recipe) {
  101.             if (!this.hasEnoughMaterials(recipe)) return false;
  102.             
  103.             // 消耗材料
  104.             recipe.materials.forEach(material => {
  105.                 let item = null;
  106.                 switch (material.type) {
  107.                     case 'item': item = $dataItems[material.id]; break;
  108.                     case 'weapon': item = $dataWeapons[material.id]; break;
  109.                     case 'armor': item = $dataArmors[material.id]; break;
  110.                 }
  111.                 $gameParty.loseItem(item, material.count);
  112.             });
  113.             
  114.             // 获得结果
  115.             let resultItem = null;
  116.             switch (recipe.result.type) {
  117.                 case 'item':
  118.                     resultItem = $dataItems[recipe.result.id];
  119.                     $gameParty.gainItem(resultItem, recipe.result.count);
  120.                     break;
  121.                 case 'weapon':
  122.                     resultItem = $dataWeapons[recipe.result.id];
  123.                     $gameParty.gainItem(resultItem, recipe.result.count);
  124.                     break;
  125.                 case 'armor':
  126.                     resultItem = $dataArmors[recipe.result.id];
  127.                     $gameParty.gainItem(resultItem, recipe.result.count);
  128.                     break;
  129.             }
  130.             
  131.             return true;
  132.         }
  133.         
  134.         // 设置当前操作台
  135.         setCurrentWorkstation(workstationType) {
  136.             this._currentWorkstation = workstationType;
  137.         }

  138.         // 获取当前操作台
  139.         getCurrentWorkstation() {
  140.             return this._currentWorkstation;
  141.         }
  142.         
  143.         // 获取操作台信息
  144.         getWorkstationInfo(type) {
  145.             return WORKSTATIONS[type] || { name: "未知操作台", icon: 0 };
  146.         }
  147.     }
  148.         
  149.     // 全局合成系统实例
  150.     window.SynthesisSystem = new SynthesisSystem();
  151.    
  152.     // 全局访问函数
  153.     window.Window_SynthesisSystem = {
  154.         open: function(workstationType) {
  155.             window.SynthesisSystem.setCurrentWorkstation(workstationType);
  156.             SceneManager.push(Scene_Synthesis);
  157.         }
  158.     };
  159.    
  160.     // 合成场景
  161.     class Scene_Synthesis extends Scene_MenuBase {
  162.         create() {
  163.             super.create();
  164.             this.createHelpWindow();
  165.             this.createRecipeListWindow();
  166.             this._confirmationWindow = null; // 状态窗口将在需要时创建
  167.         }
  168.         
  169.         createHelpWindow() {
  170.             const workstation = window.SynthesisSystem.getWorkstationInfo(
  171.                 window.SynthesisSystem.getCurrentWorkstation()
  172.             );
  173.             const rect = new Rectangle(0, 0, Graphics.boxWidth/2, this.calcWindowHeight(1, true));
  174.             this._helpWindow = new Window_Help(rect);
  175.             this._helpWindow.setText(workstation.name);
  176.             this.addWindow(this._helpWindow);
  177.         }
  178.         
  179.         createRecipeListWindow() {
  180.             const wy = this._helpWindow.height;
  181.             const wh = Graphics.boxHeight - wy;
  182.             const rect = new Rectangle(0, wy, Graphics.boxWidth/2, wh);
  183.             
  184.             this._recipeListWindow = new Window_RecipeList(rect);
  185.             this._recipeListWindow.setHandler('ok', this.onRecipeOk.bind(this));
  186.             this._recipeListWindow.setHandler('cancel', this.popScene.bind(this));
  187.             
  188.             this.addWindow(this._recipeListWindow);
  189.             this._recipeListWindow.activate();
  190.         }
  191.         
  192.         onRecipeOk() {
  193.             const recipe = this._recipeListWindow.currentRecipe();
  194.             this.showConfirmationDialog(recipe);
  195.         }
  196.         
  197.         showConfirmationDialog(recipe) {
  198.             // 创建确认对话框
  199.             const x = Graphics.boxWidth/2;
  200.             const y = this._helpWindow.height;
  201.             const width = Graphics.boxWidth/2;
  202.             const height = Graphics.boxHeight - y;
  203.             const rect = new Rectangle(x, y, width, height);
  204.             
  205.             this._confirmationWindow = new Window_Confirmation(rect, recipe);
  206.             this._confirmationWindow.setHandler('yes', this.onConfirmYes.bind(this, recipe));
  207.             this._confirmationWindow.setHandler('no', this.onConfirmNo.bind(this));
  208.             
  209.             this.addWindow(this._confirmationWindow);
  210.             this._confirmationWindow.activate();
  211.             this._recipeListWindow.deactivate();
  212.         }
  213.         
  214.         onConfirmYes(recipe) {
  215.             if (window.SynthesisSystem.hasEnoughMaterials(recipe)) {
  216.                 const success = window.SynthesisSystem.performSynthesis(recipe);
  217.                 if (success) {
  218.                     $gameMessage.add("合成成功!");
  219.                 }
  220.             } else {
  221.                 $gameMessage.add("材料不足!");
  222.             }
  223.             
  224.             // this.removeWindow(this._confirmationWindow); 没有 removeWindow func 用close()代替
  225.             this._confirmationWindow.close();
  226.             this._recipeListWindow.activate();
  227.             this._recipeListWindow.refresh();
  228.         }
  229.         
  230.         onConfirmNo() {
  231.             // this.removeWindow(this._confirmationWindow); 没有 removeWindow func 用close()代替
  232.             this._confirmationWindow.close();
  233.             this._recipeListWindow.activate();
  234.         }
  235.     }
  236.    
  237.     // 配方列表窗口
  238.     class Window_RecipeList extends Window_Selectable {
  239.         initialize(rect) {
  240.             super.initialize(rect);
  241.             this.refresh();
  242.         }
  243.         
  244.         maxItems() {
  245.             if (!this.recipes()) return 0;
  246.             return this.recipes().length;
  247.         }
  248.         
  249.         recipes() {
  250.             return window.SynthesisSystem.getRecipesByWorkstation(
  251.                 window.SynthesisSystem.getCurrentWorkstation()
  252.             );
  253.         }
  254.         
  255.         currentRecipe() {
  256.             return this.recipes()[this.index()];
  257.         }
  258.         
  259.         refresh() {
  260.             this.contents.clear();

  261.             // 检查是否有配方
  262.             const recipes = this.recipes();
  263.             if (!recipes) {
  264.                 this.drawText("没有可用配方", 0, 0, this.contentsWidth, 'center');
  265.                 return;
  266.             }

  267.             // 绘制所有配方
  268.             for (let i = 0; i < this.maxItems(); i++) {
  269.                 this.drawItem(i);
  270.             }
  271.         }
  272.         
  273.         drawItem(index) {
  274.             const recipe = this.recipes()[index];
  275.             if (!recipe) return;
  276.             
  277.             const rect = this.itemRect(index);
  278.             const canCraft = window.SynthesisSystem.hasEnoughMaterials(recipe);
  279.             
  280.             this.changePaintOpacity(canCraft);
  281.             this.drawText(recipe.name, rect.x, rect.y, rect.width);
  282.             this.changePaintOpacity(true);
  283.         }
  284.         
  285.         isCurrentItemEnabled() {
  286.             const recipe = this.currentRecipe();
  287.             return recipe && window.SynthesisSystem.hasEnoughMaterials(recipe);
  288.         }
  289.     }
  290.    
  291.     // 确认对话框窗口
  292.     class Window_Confirmation extends Window_Command {
  293.         constructor(rect, recipe) {
  294.             super(rect);
  295.             this._recipe = recipe;
  296.             this.refresh();
  297.         }

  298.         makeCommandList() {
  299.             this.addCommand("是", 'yes');
  300.             this.addCommand("否", 'no');
  301.         }
  302.         
  303.         refresh() {
  304.             super.refresh();
  305.             this.drawRecipeInfo();
  306.         }

  307.         // 如果出现透明度设置问题报错,不是drawBackgroundRect(rect)的问题,虽然报错在它画背景的函数里

  308.         drawRecipeInfo() {
  309.             if (!this._recipe) return;
  310.             
  311.             // 绘制配方名称
  312.             let y = this.lineHeight() * 3;
  313.             this.drawText(`合成 ${this._recipe.name} 需要:`, 0, y, this.contentsWidth(), 'center');
  314.             y += this.lineHeight() * 1;
  315.             
  316.             // 绘制材料需求
  317.             this._recipe.materials.forEach(material => {
  318.                 let itemName = "";
  319.                 let currentCount = 0;
  320.                 // 获取材料信息
  321.                 switch (material.type) {
  322.                     case 'item':
  323.                         itemName = $dataItems[material.id].name;
  324.                         currentCount = $gameParty.numItems($dataItems[material.id]);
  325.                         break;
  326.                     case 'weapon':
  327.                         itemName = $dataWeapons[material.id].name;
  328.                         currentCount = $gameParty.numItems($dataWeapons[material.id]);
  329.                         break;
  330.                     case 'armor':
  331.                         itemName = $dataArmors[material.id].name;
  332.                         currentCount = $gameParty.numItems($dataArmors[material.id]);
  333.                         break;
  334.                 }
  335.                
  336.                 // 根据材料是否足够选择颜色(颜色暂时不使用)
  337.                 const hasEnough = currentCount >= material.count;
  338.                 //this.changeTextColor(hasEnough ? this.normalColor() : this.crisisColor()); // normalColor=grey, crisisColor=red
  339.                
  340.                 // 绘制材料信息
  341.                 this.drawText(`${itemName} x${material.count} (现有:${currentCount})`, 0, y, this.contentsWidth(), 'center');
  342.                 y += this.lineHeight();
  343.             });
  344.             
  345.             // 绘制提示文本
  346.             y += this.lineHeight();
  347.             // this.changeTextColor(this.normalColor()); // normalColor=grey, crisisColor=red
  348.             this.drawText("要合成该物品吗?", 0, y, this.contentsWidth(), 'center');
  349.         }

  350.         // 将命令按钮放在窗口底部
  351.         itemRect(index) { //此处最易出问题。index 应只和是否按钮有关。
  352.            const rect = super.itemRect(index);
  353.            rect.y = this.contentsHeight() - rect.height * 2 - this.lineHeight() * (1 - index) ; // contentsHeight()而不是contentsHeight确保值类型不出问题。//index yes=1,no=0
  354.            return rect;
  355.         }
  356.     }
  357.    
  358.     // 插件命令处理
  359.     // 旧写法 (兼容mv?)
  360.     const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  361.     Game_Interpreter.prototype.pluginCommand = function(command, args) {
  362.         _Game_Interpreter_pluginCommand.call(this, command, args);
  363.         
  364.         if (command === 'OpenSynthesis') {
  365.             const workstationType = args[0];
  366.             Window_SynthesisSystem.open(workstationType);
  367.         }
  368.     };

  369.     //新写法 - mz标准
  370.     (function() {
  371.         // 获取插件名称
  372.         const pluginName = 'ItemSynthesis_prototype';

  373.         // 使用 PluginManager.registerCommand 注册命令
  374.         PluginManager.registerCommand(pluginName, "OpenSynthesis", function(args) {
  375.             // 参数已经被自动解析成对象 args
  376.             // 解析成的arg形式为{workstationType: "stove"}, 需要由键值对转换成字符串。
  377.             // console.log(args["workstationType"]);
  378.             const workstationType = args["workstationType"];

  379.          // 命令逻辑
  380.          window.Window_SynthesisSystem.open(workstationType);
  381.       });
  382.   })();


  383. })();
复制代码


----------------------
V1.0的plugin command和脚本调用,调试全部完成。目前没有发现问题。存档功能也正常,即存储后的存档下一次打开时,其学会的配方完全被存储,根据其合成物品功能也正常;存储的配方,也可以被forgetRecipe函数操作遗忘。
下面为V1.0的代码。
"ItemSynthesis_v1.0.js"
  1. /*:
  2. * @target MZ
  3. * @plugindesc 物品合成系统 v1.0 - 支持多种配方和操作台的合成系统
  4. * @author Snowwolf
  5. *
  6. * @param RecipeFile
  7. * @type text
  8. * @text 配方文件
  9. * @desc 合成配方JSON文件的路径
  10. * @default data/SynthesisRecipe.json
  11. *
  12. * @command OpenSynthesis
  13. * @text 打开合成界面
  14. * @desc 打开指定类型的合成界面
  15. *
  16. * @arg workstationType
  17. * @type text
  18. * @text 操作台类型
  19. * @default default1
  20. *
  21. * @command LearnRecipe
  22. * @text 学习配方
  23. * @desc 学习指定ID的配方
  24. *
  25. * @arg recipeId
  26. * @type text
  27. * @text 配方ID
  28. * @default healing_potion_1
  29. *
  30. * @command ForgetRecipe
  31. * @text 忘记配方
  32. * @desc 忘记指定ID的配方
  33. *
  34. * @arg recipeId
  35. * @type text
  36. * @text 配方ID
  37. * @default healing_potion_1
  38. *
  39. * @command CheckKnowsRecipe
  40. * @text 检查是否知道配方
  41. * @desc 检查是否知道指定ID的配方,结果存储在变量中
  42. *
  43. * @arg recipeId
  44. * @type text
  45. * @text 配方ID
  46. * @default Default_1
  47. *
  48. * @arg variableId
  49. * @type number
  50. * @text 变量ID
  51. * @default 1
  52. *
  53. * @help
  54. * 使用说明:
  55. * 1. 准备配方文件:在指定路径创建SynthesisRecipe.json文件
  56. * 2. 在事件中使用插件命令调用合成界面
  57. * 3. 或在脚本中使用Window_SynthesisSystem.open("stove")调用合成界面
  58. * 4. 脚本函数:
  59. * 5. 学习指定配方:$gameSystem.learnRecipe(recipeId);
  60. * 6. 忘记指定配方:$gameSystem.forgetRecipe(recipeId);
  61. * 7. 检查是否知道某个配方:$gameSystem.knowsRecipe(recipeId);
  62. *
  63. * 插件命令:
  64. *   OpenSynthesis workstationType - 打开指定类型的合成界面
  65. *   LearnRecipe recipeId - 学习指定配方
  66. *   ForgetRecipe recipeId - 忘记指定配方
  67. *   CheckKnowsRecipe recipeId variableId - 检查是否知道配方,结果存储在变量中
  68. */

  69. // ==============================
  70. // Game_System 扩展 - 存储已知配方
  71. // ==============================
  72. (function() {
  73.     // 存储已知配方
  74.     const _Game_System_initialize = Game_System.prototype.initialize;
  75.     Game_System.prototype.initialize = function() {
  76.         _Game_System_initialize.call(this);
  77.         this.initKnownRecipes();
  78.     };
  79.    
  80.     Game_System.prototype.initKnownRecipes = function() {
  81.         this._knownRecipes = this._knownRecipes || [];
  82.     };
  83.    
  84.     Game_System.prototype.knownRecipes = function() {
  85.         this.initKnownRecipes();
  86.         return this._knownRecipes;
  87.     };
  88.    
  89.     Game_System.prototype.learnRecipe = function(recipeId) {
  90.         this.initKnownRecipes();
  91.         if (!this._knownRecipes.includes(recipeId)) {
  92.             this._knownRecipes.push(recipeId);
  93.         }
  94.     };
  95.    
  96.     Game_System.prototype.forgetRecipe = function(recipeId) {
  97.         this.initKnownRecipes();
  98.         this._knownRecipes = this._knownRecipes.filter(id => id !== recipeId);
  99.     };
  100.    
  101.     Game_System.prototype.knowsRecipe = function(recipeId) {
  102.         this.initKnownRecipes();
  103.         return this._knownRecipes.includes(recipeId);
  104.     };
  105.    
  106.     Game_System.prototype.clearKnownRecipes = function() {
  107.         this._knownRecipes = [];
  108.     };
  109. })();

  110. // ==============================
  111. // 主插件代码
  112. // ==============================
  113. (() => {
  114.     // 插件参数
  115.     const parameters = PluginManager.parameters('ItemSynthesisSystem');
  116.     const recipeFile = parameters.RecipeFile || 'data/SynthesisRecipe.json';
  117.    
  118.     // 内部定义的操作台类型
  119.     const WORKSTATIONS = {
  120.         default1: { name: "缺省操作台1", icon: 224 },
  121.         default2: { name: "缺省操作台2", icon: 224 },
  122.         herbalist_table: { name: "炼金台", icon: 224 },
  123.         anvil: { name: "铁砧", icon: 225 },
  124.         stove: { name: "灶台", icon: 226 },
  125.         sewing: { name: "缝纫台", icon: 227 }
  126.     };

  127. // 内部定义的缺省配方
  128.     const SAMPLE_RECIPES = [
  129.         { "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" },
  130.         { "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" },
  131.      ];
  132.    
  133.     // 合成系统核心类
  134.     class SynthesisSystem {
  135.         constructor() {
  136.             this._recipes = SAMPLE_RECIPES; //变量置空 =[];
  137.             this._currentWorkstation = null;
  138.             this.loadRecipes(); //加载外置配方
  139.         }
  140.         
  141.         // 加载配方文件
  142.         loadRecipes() {
  143.             const xhr = new XMLHttpRequest(); // 注:deepseek说是异步加载,有可能出现窗口时配方未加载出来,但此状况没有遇到过。
  144.             xhr.open('GET', recipeFile);
  145.             xhr.overrideMimeType('application/json');
  146.             xhr.onload = () => {
  147.                 if (xhr.status < 400) {
  148.                     this._recipes = JSON.parse(xhr.responseText);
  149.                     console.log("配方加载成功:", this._recipes.length, "个配方");
  150.                 }
  151.             };
  152.             xhr.onerror = () => {
  153.                 console.error("无法加载配方文件:", recipeFile);
  154.             };
  155.             xhr.send();
  156.         }
  157.         
  158.        // // 获取指定操作台的已知配方
  159.        getKnownRecipesByWorkstation(workstationType) {
  160.         const allRecipes = this.getRecipesByWorkstation(workstationType);
  161.         return allRecipes.filter(recipe =>
  162.             $gameSystem.knowsRecipe(recipe.id) &&
  163.             (!recipe.requiredSwitch || $gameSwitches.value(recipe.requiredSwitch))
  164.         );
  165.     }

  166.     // 检查玩家是否知道某个配方
  167.     knowsRecipe(recipeId) {
  168.         return $gameSystem.knowsRecipe(recipeId);
  169.     }
  170.    
  171.     // 学习新配方
  172.     learnRecipe(recipeId) {
  173.         $gameSystem.learnRecipe(recipeId);
  174.     }
  175.    
  176.     // 忘记配方
  177.     forgetRecipe(recipeId) {
  178.         $gameSystem.forgetRecipe(recipeId);
  179.     }

  180.     // 根据ID获取配方
  181.     getRecipeById(recipeId) {
  182.         return this._recipes.find(recipe => recipe.id === recipeId);
  183.     }
  184.         
  185.         // 获取指定操作台的配方(准备弃用)
  186.         getRecipesByWorkstation(workstationType) {
  187.             return this._recipes.filter(recipe =>
  188.                 recipe.workstation === workstationType &&
  189.                 (!recipe.requiredSwitch || $gameSwitches.value(recipe.requiredSwitch))
  190.             );
  191.         }
  192.         
  193.         // 检查材料是否足够
  194.         hasEnoughMaterials(recipe) {
  195.             return recipe.materials.every(material => {
  196.                 let count = 0;
  197.                 switch (material.type) {
  198.                     case 'item': count = $gameParty.numItems($dataItems[material.id]); break;
  199.                     case 'weapon': count = $gameParty.numItems($dataWeapons[material.id]); break;
  200.                     case 'armor': count = $gameParty.numItems($dataArmors[material.id]); break;
  201.                 }
  202.                 return count >= material.count;
  203.             });
  204.         }
  205.         
  206.         // 执行合成
  207.         performSynthesis(recipe) {
  208.             if (!this.hasEnoughMaterials(recipe)) return false;
  209.             
  210.             // 消耗材料
  211.             recipe.materials.forEach(material => {
  212.                 let item = null;
  213.                 switch (material.type) {
  214.                     case 'item': item = $dataItems[material.id]; break;
  215.                     case 'weapon': item = $dataWeapons[material.id]; break;
  216.                     case 'armor': item = $dataArmors[material.id]; break;
  217.                 }
  218.                 $gameParty.loseItem(item, material.count);
  219.             });
  220.             
  221.             // 获得结果
  222.             let resultItem = null;
  223.             switch (recipe.result.type) {
  224.                 case 'item':
  225.                     resultItem = $dataItems[recipe.result.id];
  226.                     $gameParty.gainItem(resultItem, recipe.result.count);
  227.                     break;
  228.                 case 'weapon':
  229.                     resultItem = $dataWeapons[recipe.result.id];
  230.                     $gameParty.gainItem(resultItem, recipe.result.count);
  231.                     break;
  232.                 case 'armor':
  233.                     resultItem = $dataArmors[recipe.result.id];
  234.                     $gameParty.gainItem(resultItem, recipe.result.count);
  235.                     break;
  236.             }
  237.             
  238.             return true;
  239.         }
  240.         
  241.         // 设置当前操作台
  242.         setCurrentWorkstation(workstationType) {
  243.             this._currentWorkstation = workstationType;
  244.         }

  245.         // 获取当前操作台
  246.         getCurrentWorkstation() {
  247.             return this._currentWorkstation;
  248.         }
  249.         
  250.         // 获取操作台信息
  251.         getWorkstationInfo(type) {
  252.             return WORKSTATIONS[type] || { name: "未知工作台", icon: 0 };
  253.         }
  254.     }
  255.         
  256.     // 全局合成系统实例
  257.     window.SynthesisSystem = new SynthesisSystem();
  258.    
  259.     // 全局访问函数
  260.     window.Window_SynthesisSystem = {
  261.         open: function(workstationType) {
  262.             window.SynthesisSystem.setCurrentWorkstation(workstationType);
  263.             SceneManager.push(Scene_Synthesis);
  264.         }
  265.     };
  266.    
  267.     // 合成场景
  268.     class Scene_Synthesis extends Scene_MenuBase {
  269.         create() {
  270.             super.create();
  271.             this.createHelpWindow();
  272.             this.createRecipeListWindow();
  273.             this._confirmationWindow = null; // 状态窗口将在需要时创建
  274.         }
  275.         
  276.         createHelpWindow() {
  277.             const workstation = window.SynthesisSystem.getWorkstationInfo(
  278.                 window.SynthesisSystem.getCurrentWorkstation()
  279.             );
  280.             const rect = new Rectangle(0, 0, Graphics.boxWidth/2, this.calcWindowHeight(1, true));
  281.             this._helpWindow = new Window_Help(rect);
  282.             this._helpWindow.setText(workstation.name);
  283.             this.addWindow(this._helpWindow);
  284.         }
  285.         
  286.         createRecipeListWindow() {
  287.             const wy = this._helpWindow.height;
  288.             const wh = Graphics.boxHeight - wy;
  289.             const rect = new Rectangle(0, wy, Graphics.boxWidth/2, wh);
  290.             
  291.             this._recipeListWindow = new Window_RecipeList(rect);
  292.             this._recipeListWindow.setHandler('ok', this.onRecipeOk.bind(this));
  293.             this._recipeListWindow.setHandler('cancel', this.popScene.bind(this));
  294.             
  295.             this.addWindow(this._recipeListWindow);
  296.             this._recipeListWindow.activate();
  297.         }
  298.         
  299.         onRecipeOk() {
  300.             const recipe = this._recipeListWindow.currentRecipe();
  301.             this.showConfirmationDialog(recipe);
  302.         }
  303.         
  304.         showConfirmationDialog(recipe) {
  305.             // 创建确认对话框
  306.             const x = Graphics.boxWidth/2;
  307.             const y = this._helpWindow.height;
  308.             const width = Graphics.boxWidth/2;
  309.             const height = Graphics.boxHeight - y;
  310.             const rect = new Rectangle(x, y, width, height);
  311.             
  312.             this._confirmationWindow = new Window_Confirmation(rect, recipe);
  313.             this._confirmationWindow.setHandler('yes', this.onConfirmYes.bind(this, recipe));
  314.             this._confirmationWindow.setHandler('no', this.onConfirmNo.bind(this));
  315.             
  316.             this.addWindow(this._confirmationWindow);
  317.             this._confirmationWindow.activate();
  318.             this._recipeListWindow.deactivate();
  319.         }
  320.         
  321.         onConfirmYes(recipe) {
  322.             if (window.SynthesisSystem.hasEnoughMaterials(recipe)) {
  323.                 const success = window.SynthesisSystem.performSynthesis(recipe);
  324.                 if (success) {
  325.                     $gameMessage.add("合成成功!");
  326.                 }
  327.             } else {
  328.                 $gameMessage.add("材料不足!");
  329.             }
  330.             
  331.             // this.removeWindow(this._confirmationWindow); 没有 removeWindow 函数,用close()
  332.             this._confirmationWindow.close();
  333.             this._recipeListWindow.activate();
  334.             this._recipeListWindow.refresh();
  335.         }
  336.         
  337.         onConfirmNo() {
  338.             // this.removeWindow(this._confirmationWindow); 没有 removeWindow 函数,用close()
  339.             this._confirmationWindow.close();
  340.             this._recipeListWindow.activate();
  341.         }
  342.     }
  343.    
  344.     // 配方列表窗口
  345.     class Window_RecipeList extends Window_Selectable {
  346.         initialize(rect) {
  347.             super.initialize(rect);
  348.             this.refresh();
  349.         }
  350.         
  351.         maxItems() {
  352.             if (!this.recipes()) return 0;
  353.             return this.recipes().length;
  354.         }
  355.         
  356.         recipes() {
  357.             // return window.SynthesisSystem.getRecipesByWorkstation(
  358.             return window.SynthesisSystem.getKnownRecipesByWorkstation(
  359.                 window.SynthesisSystem.getCurrentWorkstation()
  360.             );
  361.         }
  362.         
  363.         currentRecipe() {
  364.             return this.recipes()[this.index()];
  365.         }
  366.         
  367.         refresh() {
  368.             this.contents.clear();

  369.             // 检查是否有配方
  370.             const recipes = this.recipes();
  371.             if (!recipes) {
  372.                 this.drawText("没有可用配方", 0, 0, this.contentsWidth, 'center');
  373.                 return;
  374.             }

  375.             // 绘制所有配方
  376.             for (let i = 0; i < this.maxItems(); i++) {
  377.                 this.drawItem(i);
  378.             }
  379.         }
  380.         
  381.         drawItem(index) {
  382.             const recipe = this.recipes()[index];
  383.             if (!recipe) return;
  384.             
  385.             const rect = this.itemRect(index);
  386.             const canCraft = window.SynthesisSystem.hasEnoughMaterials(recipe);
  387.             
  388.             this.changePaintOpacity(canCraft);
  389.             this.drawText(recipe.name, rect.x, rect.y, rect.width);
  390.             this.changePaintOpacity(true);
  391.             console.log("DrawItemIndex是", index, "\n");
  392.         }
  393.         
  394.         isCurrentItemEnabled() {
  395.             const recipe = this.currentRecipe();
  396.             return recipe && window.SynthesisSystem.hasEnoughMaterials(recipe);
  397.         }
  398.     }
  399.    
  400.     // 确认对话框窗口
  401.     class Window_Confirmation extends Window_Command {
  402.         constructor(rect, recipe) {
  403.             console.log(rect);
  404.             super(rect);
  405.             this._recipe = recipe;
  406.             this.refresh();
  407.         }

  408.         makeCommandList() {
  409.             this.addCommand("是", 'yes');
  410.             this.addCommand("否", 'no');
  411.         }
  412.         
  413.         refresh() {
  414.             super.refresh();
  415.             this.drawRecipeInfo();
  416.         }

  417.         //不是drawBackgroundRect(rect)的问题,虽然报错在它画背景的函数里

  418.         drawRecipeInfo() {
  419.             if (!this._recipe) return;
  420.             
  421.             // 绘制配方名称
  422.             let y = this.lineHeight() * 3;
  423.             this.drawText(`合成 ${this._recipe.name} 需要:`, 0, y, this.contentsWidth(), 'center');
  424.             y += this.lineHeight() * 1;
  425.             
  426.             // 绘制材料需求
  427.             this._recipe.materials.forEach(material => {
  428.                 let itemName = "";
  429.                 let currentCount = 0;
  430.                 console.log(material);
  431.                 // 获取材料信息
  432.                 switch (material.type) {
  433.                     case 'item':
  434.                         itemName = $dataItems[material.id].name;
  435.                         currentCount = $gameParty.numItems($dataItems[material.id]);
  436.                         break;
  437.                     case 'weapon':
  438.                         itemName = $dataWeapons[material.id].name;
  439.                         currentCount = $gameParty.numItems($dataWeapons[material.id]);
  440.                         break;
  441.                     case 'armor':
  442.                         itemName = $dataArmors[material.id].name;
  443.                         currentCount = $gameParty.numItems($dataArmors[material.id]);
  444.                         break;
  445.                 }
  446.                
  447.                 // 根据材料是否足够选择颜色(此处弃用)
  448.                 const hasEnough = currentCount >= material.count;
  449.                 //this.changeTextColor(hasEnough ? this.normalColor() : this.crisisColor()); 把normalColor换成grey,crisisColor换成red
  450.                
  451.                 // 绘制材料信息
  452.                 this.drawText(`${itemName} x${material.count} (现有:${currentCount})`, 0, y, this.contentsWidth(), 'center');
  453.                 y += this.lineHeight();
  454.             });
  455.             
  456.             // 绘制提示文本
  457.             y += this.lineHeight();
  458.             // this.changeTextColor(this.normalColor()); 把normalColor换成grey
  459.             this.drawText("要合成该物品吗?", 0, y, this.contentsWidth(), 'center');
  460.         }
  461.         // 将命令按钮放在窗口底部
  462.         itemRect(index) { //此处容易出错 //index应该只有是否按钮的0和1,如有其他值会使ui绘制混乱.
  463.            const rect = super.itemRect(index);
  464.            rect.y = this.contentsHeight() - rect.height * 2 - this.lineHeight() * (1 - index) ; // contentsHeight()而不是contentsHeight确保值类型不出问题。//index yes=1,no=0
  465.            return rect;
  466.         }

  467.     }
  468.    
  469.     // 插件命令处理
  470.     // 旧写法 (兼容mv?)
  471.     const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  472.     Game_Interpreter.prototype.pluginCommand = function(command, args) {
  473.         _Game_Interpreter_pluginCommand.call(this, command, args);
  474.         
  475.         if (command === 'OpenSynthesis') {
  476.             const workstationType = args[0];
  477.             Window_SynthesisSystem.open(workstationType);
  478.         } else if (command === 'LearnRecipe') {
  479.             const recipeId = args[0];
  480.             window.SynthesisSystem.learnRecipe(recipeId);
  481.             $gameMessage.add(`学会了新的配方: ${this.getRecipeName(recipeId)}`);
  482.         } else if (command === 'ForgetRecipe') {
  483.             const recipeId = args[0];
  484.             window.SynthesisSystem.forgetRecipe(recipeId);
  485.             $gameMessage.add(`忘记了配方: ${this.getRecipeName(recipeId)}`);
  486.         } else if (command === 'CheckKnowsRecipe') {
  487.             const recipeId = args[0];
  488.             const knows = window.SynthesisSystem.knowsRecipe(recipeId);
  489.             // 可以将结果存储在变量中供事件使用
  490.             if (args[1]) {
  491.                 $gameVariables.setValue(parseInt(args[1]), knows ? 1 : 0);
  492.             }
  493.         }
  494.     };
  495.    
  496.     // 辅助方法:获取配方名称
  497.     Game_Interpreter.prototype.getRecipeName = function(recipeId) {
  498.         const recipe = window.SynthesisSystem.getRecipeById(recipeId);
  499.         return recipe ? recipe.name : "未知配方";
  500.     };

  501.     //新写法 - mz标准
  502.     (function() {
  503.         // 获取插件名称
  504.         const pluginName = 'ItemSynthesis_v1.0';

  505.         // 使用 PluginManager.registerCommand 注册命令
  506.         PluginManager.registerCommand(pluginName, "OpenSynthesis", function(args) {
  507.             // 参数已经被自动解析成对象 args
  508.             // 解析成的arg形式为{workstationType: "stove"}, 需要由键值对转换成字符串。
  509.             // console.log(args["workstationType"]);
  510.             const workstationType = args["workstationType"];
  511.            // 命令逻辑
  512.            window.Window_SynthesisSystem.open(workstationType);
  513.       });

  514.       PluginManager.registerCommand(pluginName, "LearnRecipe", function(args) {
  515.             // 参数已经被自动解析成对象 args
  516.             // 解析成的arg形式为{recipeId: "ID"}, 需要由键值对转换成字符串。
  517.             // console.log(args["recipeId"]);
  518.             const recipeId = args["recipeId"];
  519.            // 命令逻辑
  520.            window.SynthesisSystem.learnRecipe(recipeId);
  521.            $gameMessage.add(`学会了新的配方: ${this.getRecipeName(recipeId)}`);
  522.       });

  523.       PluginManager.registerCommand(pluginName, "ForgetRecipe", function(args) {
  524.             // 参数已经被自动解析成对象 args
  525.             // 解析成的arg形式为{recipeId: "ID"}, 需要由键值对转换成字符串。
  526.             // console.log(args["recipeId"]);
  527.             const recipeId = args["recipeId"];
  528.            // 命令逻辑
  529.            window.SynthesisSystem.forgetRecipe(recipeId);
  530.            $gameMessage.add(`忘记了配方: ${this.getRecipeName(recipeId)}`);
  531.       });

  532.       PluginManager.registerCommand(pluginName, "CheckKnowsRecipe", function(args) {
  533.             // 参数已经被自动解析成对象 args
  534.             // 解析成的arg形式为{recipeId: "ID"}, 需要由键值对转换成字符串。
  535.             // console.log(args["recipeId"]);
  536.             const recipeId = args["recipeId"];
  537.             const variableId = args["variableId"];
  538.            // 命令逻辑
  539.            const knows = window.SynthesisSystem.knowsRecipe(recipeId);
  540.            // 可以将结果存储在变量中供事件使用
  541.             if (variableId) {
  542.                 $gameVariables.setValue(parseInt(variableId), knows ? 1 : 0);
  543.             }
  544.       });

  545.   })();

  546. })();

复制代码
您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2025-9-8 21:41

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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