Potion Craft: Alchemist Simulator

Potion Craft: Alchemist Simulator

32 ratings
零误差数控炼药、配方数据库 Zero-error Numerical Control & Recipe Database
By oubeidong
A comprehensive guide to Numerical Control Recipes is currently in progress. We welcome interested in-depth players to participate. Thank you!

数字控制配方大全,项目进行中,欢迎感兴趣的深度玩家参与,谢谢!
2
2
   
Award
Favorite
Favorited
Unfavorite
项目概述与目标
立项原因
本游戏的一大痛点,配方的研发困难,部分极限配方的复现也极为困难,且沟通交流十分不便,游戏截图很大程度上无法说明一个配方的步骤,因为少量的搅拌、倒液、加热等步骤并不会有所记录(且可能乱序),大多数配方的交流依赖官方的 Plotter 网站或视频。

Plotter有个致命的底层问题,即其本身是模拟游戏运行环境的一个二次模拟,很多极限配方只存在于网站上,实操无法复现。反之亦然,许多极限手操配方,网站上却显示无法通过。下一章我会给出Plotter不精准的直接证据,因此请勿用Plotter的数据来碰瓷本项目使用的绝对零误差工具。

对于玩家实操,游戏也存在巨大的不便,即使使用了某些信息显示MOD,依然存在大量不确定性。根据本人严格审查,游戏开发者对于所有的运动模拟(漩涡旋转、沼泽衰减、血量扣除等)均属于非精确过程,这是游戏源代码所决定的,在本人开发的项目工具之前,无法通过任何已有手段消除。举个通俗的例子,假设游戏运行环境的一切人设参数完全相同,也绝不可能有两次漩涡旋转的结果100%相同,这个误差来自于机器运行的底层波动和游戏引擎的渲染机制。

为解决以上痛点,本项目启动。

参与条件与方式
由于涉及到一些对游戏的深入理解,本项目仅限已全成就或已通关游戏的玩家,且研发配方的过程相对比较枯燥,因此需要参与人员对游戏有较高的喜爱程度,以及一定的钻研精神。伸手党、轻度玩家、享乐型玩家请勿参与。

满足要求且对项目感兴趣的玩家,请加 QQ 群 775264935 进行报名,后续审核通过后会告知参与入口。对于重大贡献者,将添加为本指南的共同作者,其余所有参与人员均会有名单进行致谢。

如果你发明了一些很有价值的配方,但是不想参与数字化项目,依然可以进群联系并提供思路,对于配方的设计者,我们依然会将你的 ID 作为核心信息保存在数据库内。另外由于一些配方的原始设计者已不可考,如果你是原始设计者且能提供证据,那么请告知,本指南将立刻录入作者。

基本内容
本项目将完成一切有价值、且互斥配方的标准化制定,所有配方均会转化为数控配方。所谓数控配方,即消除了一切运行设备、操作、时间等方面的差异,所有步骤完全由数字化控制的配方。且该类配方具备高度的完整性、低冗余性、可恢复性,无需任何额外图片或视频信息,仅靠标准化、序列化之后的文本,即可与任何其他玩家完整共享整个过程,且能100%复现。由于是标准化格式,数控配方可方便录入数据库,或进行常见的数据结构转化与语义解析。

由于数控配方的还原需要使用本人研发的工具,在项目大致完成之前,该工具并不会公开共享。但这并不意味着本指南对于普通玩家就毫无价值,因为配方本身的流程是完全基于游戏运行环境研发的,所有步骤只是无法手动控制精度而已,其提供的大致思路,以及对应的操作步骤也是极具价值的信息。

项目完成后,还会提供在玩家存档中置入数控配方的功能,真正实现让任何人都享受成果。且可制作初始全配方存档供新玩家使用(仅置入配方而不修改其他任何数据),提供更多的可选性。
Project Overview & Goals
Reasons for the project
One of the major pain points of this game, the difficulty of recipe development, the reproduction of some extreme recipe is also extremely difficult, and the communication is very inconvenient, the game screenshots are largely unable to explain the steps of a recipe, because a small number of stirring, pouring, heating and other steps will not be recorded (and may be out of order), and most of the recipe communication relies on the official Plotter website or video.

Plotter has a fatal underlying problem, that is, it is a secondary simulation of the game operating environment, and many extreme recipes only exist on the website, and cannot be reproduced in practice. And vice versa, many extreme manual recipes are shown on the website to fail. In the next chapter, I'll give direct evidence that Plotter is inaccurate, so don't use Plotter's data to contradict the absolute zero error tool used in this project.

There is also a huge inconvenience for the player, and even if some information display mods are used, there is still a lot of uncertainty. According to my strict review, all motion simulations (swirl rotation, swamp decay, health deduction, etc.) by the game developers are non-accurate processes, which are determined by the source code of the game and cannot be eliminated by any existing means until the project tools I have developed. As a layman's example, assuming that all the parameters of the game runtime are exactly the same, it is impossible for two vortex rotations to be 100% identical, and this error comes from the underlying fluctuations of the machine and the rendering mechanism of the game engine.

In order to solve the above pain points, this project was launched.

Conditions and Methods of Participation
Due to the in-depth understanding of the game, this project is limited to players who have completed all achievements or cleared the game, and the process of developing the recipe is relatively boring, so it requires participants to have a high level of love for the game and a certain spirit of study. Reach out parties, light players, and hedonistic players are not allowed to participate.

Players who meet the requirements and are interested in the project should leave a
comment in this guide or connect me at official Discord @oubeidong. Significant contributors will be added as co-authors of this guide, and all other contributors will be acknowledged.

If you have invented some valuable recipes, but don't want to participate in the digitization project, you can still contact the group and provide ideas, and for the recipe designer, we will still store your ID as the core information in the database. In addition, since the original designer of some recipes are no longer available, if you are the original designer and can provide evidence, please let us know and this guide will be entered into the author immediately.

Basics
This project will complete the standardization of all valuable and mutually exclusive recipes, and all recipes will be converted into CNC recipes. The so-called numerical control recipe is a recipe that eliminates all differences in operating equipment, operation, time, etc., and all steps are completely digitally controlled. This type of recipe has a high degree of integrity, low redundancy, and recoverability, without any additional image or video information, and can be fully shared with any other player with 100% reproducibility by relying only on standardized and serialized text. Because it is a standardized format, NC recipes can be easily entered into the database, or common data structure transformation and semantic analysis can be performed.

Since the restoration of the NC recipe requires the use of a tool developed by me, the tool will not be shared publicly until the project is roughly completed. However, this does not mean that this guide is worthless for the average player, as the process of the recipe itself is completely based on the game's runtime environment, and all the steps are just not manual to control the precision, and the general idea and corresponding operation steps are also valuable information.

Once the project is complete, it will also provide the ability to insert NC recipes into the player's save, making it truly possible for anyone to enjoy the results. It is also possible to create an initial full recipe save for new players to use (just insert the recipe without modifying any other data), providing more options.
关于Plotter误差的说明 Explanation regarding Plotter errors

[CN]
如图,打开Plotter,切换至油基地图,添加泰拉瑞亚(研磨100%),添加泥浆菇(研磨100%),此时终点应该会显示可以获取石肤效果,但是当你添加了一个全路径(大于20)的搅拌数值后,Plotter却显示无法获取石肤效果。至于游戏内到底能否获取,以及最终位置到底在哪,请自行动手验证。

注意这仅仅是一个最容易验证的例子,因为所有的研磨和搅拌都是100%,你可以轻松在游戏里手动验证。事实上,Plotter的误差远大于此,尤其是血量计算的部分,一旦涉及到接触骷髅区的路径,Plotter几乎不存在准确性,且Plotter对于漩涡采用的是路径长度值,而游戏内的加热牵扯到的东西更多。旋转位移长度本身就是一个计算结果,而非原始参照值,在计算这个旋转位移的过程中,游戏内引用了相当多的随机变量,因此其结果无法保证一致。

提出本例子并非否认Plotter的作用,其在配方思路共享方面,依然为玩家发挥了巨大作用。Plotter与本项目的工具可以相辅相成,通过Plotter进行配方的大概思路设计,再使用本项目工具在游戏内实际验证并将配方标准化,但请记住,最终所有数据,请以本项目工具为准。

[EN]
As shown in the figure, open the Plotter, switch to the oil base map, add Terraria (grind 100%), add Mudshroom (grind 100%), at this point the endpoint should indicate that the Skin effect can be obtained. However, after you add a stirring value with a full path (greater than 20), the Plotter shows that the Skin effect cannot be obtained. As for whether it can actually be obtained in the game and where the final position is, please verify it yourself.

Please note that this is merely the easiest example to verify, as all grinding and stirring are at 100%, allowing you to easily verify it manually in the game. In fact, the error of the Plotter is much greater than this, especially in the part of health calculation. Once it involves paths that come into contact with the skeleton area, the Plotter has almost no accuracy, and the Plotter uses path length values for whirlpools, while the heating involved in the game encompasses much more. The length of rotational displacement itself is a calculated result, not an original reference value. During the calculation of this rotational displacement, the game references a considerable number of random variables, thus its results cannot be guaranteed to be consistent.

The presentation of this example does not deny the role of the Plotter; it still plays a significant role in sharing recipe ideas among players. The Plotter and the tools of this project can complement each other. You can design the general idea of the recipe using the Plotter, then use the tools of this project to verify it in the game and standardize the recipe. However, please remember that all final data should be based on the tools of this project.
游戏源码缺陷排查与数控工具开发思路
本节涉及到底层原理讲解,需要一定的计算机基础,如果不感兴趣可跳过。

整体设计与核心功能规划
要做到将游戏内炼药的操作完全数控化,需要枚举出所有相关的控制变量。即:
  • 素材研磨——药瓶的移动路径基准参照
  • 汤勺搅拌——药瓶的实际移动,关联着沼泽衰减和骷髅扣血
  • 沼泽衰减——接触到沼泽区时,剩余路径和搅拌速度会以恒定比例衰减
  • 骷髅扣血——接触到骷髅区时,药瓶血量会以恒定速率下降
  • 倾倒基底——药瓶向原点靠近,角度向0度靠近,不受沼泽影响
  • 加热大锅——影响搅拌速度,并使漩涡旋转并带动药瓶移动
  • 加虚无盐——从路径尾部向药瓶抹除
  • 加日月盐——使药瓶顺时针或逆时针旋转
  • 加生命盐——回复药瓶血量
  • 加贤者盐——使药瓶向最近效果移动,且角度向最近效果旋转,受沼泽影响

素材研磨的控制原理
游戏程序通过引用一个内部浮点值(范围0~1)代表当前研磨程度,其控制着路径的终点坐标。由于单一素材的路径模板是完全相同的,因此研磨程度可准确定位终点坐标,这一步使用开发者提供的Mod接口也可实现。

药瓶移动的控制原理
关于精确控制药瓶最终坐标,一个朴素的想法是通过设置整体需要移动的长度,然后直接将药瓶传送至计算出的最终坐标。这是错误的,因为实际游戏中药瓶的搅拌过程会经历沼泽的衰减,此时的路径计算会变复杂,通过整体长度很难计算出精确坐标。虽然并非不可计算,但是还有一个更重要的原因导致直接传送思路的错误,即骷髅区的血量衰减计算是通过碰撞检测实现的,想通过整体移动长度计算出精确的血量消耗是不可能的。

这个不可能并非数学上的不可能,而是游戏引擎的运行机制所限。这也是Plotter或者其他任何二次模拟环境均不可能完美再现结果,且保证一致性的根本原因所在。同时这也是很多游戏内在Bug的根本原因所在,比如说某些骷髅区,当玩家以较低速度搅拌无法通过时,却可通过加热使用最高搅拌速度通过。

略述程序底层运行机制
一个基本事实是,所有交互式计算机程序本质上都是一个无限循环程序。这也许和初学者接触的内容不同,初学者编写的大部分程序都是单次执行随后产生一个结果即结束,比如打印“Hello World”,或者计算3+2这样的简单程序,即使包含了循环,大多也是有限次数的简单循环。

而交互式程序,更具体来说,游戏程序,本质上就是一个巨大的无限循环内包含了无数函数和控制代码的程序。理论上来说,如果用户不退出程序,或者执行到终止控制代码,那么游戏程序会永远执行下去。现在以Unity为例(其他所有现代游戏引擎均类似),简要讲解引擎的工作机制。

引擎代码的执行总是先于游戏逻辑代码的执行(栈帧返回地址的大端总是引擎地址在前),其基本工作之一就是渲染游戏画面。渲染的机制就是通过一个无限循环,不断引用当前的游戏状态变量,执行所有逻辑代码后,将结果渲染并发送给显存最终呈现出画面。而渲染是周期性的,对于古老的引擎来说,一个周期(即一帧)内就包含了所有的逻辑代码执行,所有计算都在一个周期内按顺序依次执行。但现代引擎并非如此。

简单来说,Unity在顶层的大循环内,又内嵌了多个异步循环,其中有两类循环内是直接包含游戏逻辑代码的。一类循环被命名为“FixedUpdate()”,这类循环以固定周期执行游戏作者注册的每一个同名函数,但并未同步发送渲染数据,其一个周期称为“非渲染帧”(有些人给这个起了个奇怪的名字叫逻辑帧,问题在于渲染帧里面也包含了逻辑,他们的本质区别是“FixedUpdate()”不会同步发送渲染请求)。

另一类循环被命名为“Update()”和“LateUpdate()”,由于“LateUpdate()”总是与“Update()”同周期,且总是在“Update()”执行后执行,因此这里只讨论“Update()”的影响。“Update()”的一个周期被成为“渲染帧”,而渲染帧的周期受到的影响和波动远大于“FixedUpdate()”。首先是垂直同步或者帧数限制等类似的游戏设置会直接影响渲染帧的周期长短(“FixedUpdate()”不受影响),引擎的“Update()”更新周期下限就是这个设置的数值。比如说设置了每秒100帧的帧数限制,那么此时“Update()”的执行周期下限为1/100=0.01秒。

“Update()”与“FixedUpdate()”最大的区别在于,前者在执行完毕逻辑代码以后,会立刻发送渲染请求,在GPU渲染完成之前,“Update()”会一直处于挂起状态阻塞下一个周期循环。因此前面所说的帧数限制等设置,只是限定了一个最大帧率,实际上由于环境波动、硬件性能等原因,实际的帧率并非总是可以达到设定的最大帧率,这在一些大型游戏当中相当常见。总结一个不十分严谨的结论,“FixedUpdate()”的实际周期主要受CPU性能影响,而“Update()” 的实际周期同时受CPU和GPU的性能影响。

这也是现代引擎拆分两类更新函数的设计初衷,因为CPU的性能大多数时候是处于过剩的状态,因此“FixedUpdate()”几乎总是能以恒定的设定周期执行。而根据一般开发规范,游戏开发者应当将所有的物理计算、运动模拟等需要高精度的逻辑代码,封装入“FixedUpdate()”当中,而其他过程的逻辑代码应当封装入“Update()”中。很显然Potion Craft的开发人员忘记了这件事,各类速度、路径、血量、旋转等物理过程,均被分拆到了“Update()”与“FixedUpdate()”当中,位于两处的代码天然就是异步的,因此无论怎么调用开发者提供的接口,其底层的计算本身就不可能一致,这也是数控项目最大的障碍。

关于不一致性,举个简单的例子也许能很好理解。假设“FixedUpdate()”的执行频率是每秒100次,“Update()” 的执行频率是每秒50次,其中“FixedUpdate()”内有某个计算当前速度的代码,而“Update()”内有一个相关的计算速度衰减的代码。很明显,每计算两次当前速度,才会计算一次速度衰减。假设计算机是理想中的完美环境,一点波动都没有,那么即使分拆后的代码也能完美保持一致。但很遗憾,现实是不完美的,事实上几乎没有任何两次相邻的周期能完全相同,计算机总是处于不可预测的波动环境当中,因此两类更新函数不可能找到一个固定的周期比。

玩家也许会问,那为什么实际体验来说,这类波动几乎不可察觉,结果的误差也几乎肉眼不可见呢?因为引擎使用了两个变量来强行修正时间的不一致性,其中“delta_time”对应着“Update()”相邻两次执行的时间间隔,“fixed_delta_time”对应着“FixedUpdate()” 相邻两次执行的时间间隔,然后使用微积分的思维,将每一帧看作时间上的微分,通过对应的“Delta time”在位移上积分起来,这样就大致保持了程序的一致性。但是请记住,这样的处理方式,永远只能是“大致”,而数控要求的是无论小数点后多少位,无论重复执行多少次,结果都要绝对完全一致。应当知道的是,误差是有积累效应的,不能仅考察单次计算的误差,即使只有0.001的差距,那么执行成百上千次以后,误差的累计也相当可观。事实上一次看似不长的普通搅拌,其对应的“Update()”函数就执行了上千次。

具体分析游戏逻辑代码
需要清楚一个基本事实是,所有的程序模拟运动,本质上都是一个微分值极小的传送积分过程。没有现实物理的那套运动理论,所有看上去平滑的运动,其实都是在以一个极高的速率不断传送实体。

对于搅拌和倒液操作,当不考虑沼泽和骷髅的影响时,这两个操作有较为类似的实现逻辑。搅拌引用了玩家每一帧的勺子运动偏移量,通过这个量发送一个路径删除请求,随后以一个平滑的坐标间隔每帧传送药瓶至新位置,新位置都是位于路径模板之上的。倒液也是类似的逻辑,只不过引用的是每帧液体倾倒总量,同时新坐标总是位于当前坐标与原点坐标的连线之上。

再考察沼泽和血量的计算,将这两个过程放在一起看似不相关,实际上游戏作者对于实现这两个过程采用的逻辑是几乎一致的。本质上,玩家看见的沼泽以及骷髅区域,都是由大量的基本区域单元构成的,从引擎角度来看待,一片沼泽区域无非就是大量刚体碰撞器的聚簇集合。游戏程序如何判断是否进入沼泽和骷髅区域呢?开发者选择了最容易但是最不精确的实现方式——刚体碰撞检测,即检测瓶子是否与区域单元发生了刚体碰撞。这是通过“OnTriggerEnter2D()”或“OnTriggerStay2D()”实现的,其周期天然与“FixedUpdate()”同步。如果你足够聪明,那么现在已经可以完美解释:为什么有些骷髅区域的通过与否,会因搅拌速度不同而改变——答案就是高速搅拌意味着传送间隔更大,导致两次相邻的坐标传送正好跳过了部分区域单元的碰撞箱,从而导致后续的扣血逻辑代码未能得到执行。需要注意的是并不一定是高速才能跳过,因为根据区域单元的分布不同,能否跳过碰撞箱属于无法预测的范畴。

对于加热以及漩涡旋转来说,其实情形与搅拌和倒液类似,只不过这里引用的是热力值,也就是玩家的加热时长总量,随后有一个缓慢的热力衰减。运动方面则是当热力值恒定不变时,则瓶子产生会以一个恒定的角速度作圆周运动,同时产生一个恒定的指向漩涡中心的线速度,其路径也就是等速螺线(阿基米德螺线)。实际上热力值是递减的,其运动表现为一个沿等速螺线速度递减的运动。

当同步了“Update()”与“FixedUpdate()”的周期后,以上所有的模拟不一致性均得到了消除。那么数控到这里是否就完美了呢?当考察加盐操作时,新的严重问题即将显现。加盐,尤其是日月和贤者盐,均会有两个旋转过程——一个是瓶子本身的旋转,一个是路径的旋转。实际情况就是,如果同步了引擎,瓶子本身的旋转就变得绝对一致,但是路径旋转每次的结果却不同,这说明其内在的误差机制完全不同。

加盐、倒液的路径旋转均会产生类似的误差机制,首先需要理解游戏内的路径本质,事实上开发者使用的是序列化点阵来实现路径,而非贝塞尔曲线动态计算。如果你有图形处理方面的相关知识,这就是位图和矢量图的区别,开发者选择了位图类似的思路,所有路径无非是一个点阵的序列集合,只不过间隔非常小玩家看不出来是离散的,而误以为是连续曲线。路径旋转就是所有点阵以相同角速度绕瓶子中心旋转,具体的实现涉及到大量的矩阵运算,数值采用的是浮点值。而开发者在处理这类逻辑的时候犯了一个错误——路径旋转和瓶子旋转不同步。

这里需要理解另一个编程方面的知识,即事件处理机制。简而言之当盐溶解时,其注册的事件会发送瓶子的旋转请求,按照正常的设计思路,这个请求应当同步发送路径的旋转请求。很遗憾,开发者采用了不恰当的异步思路——加盐并不会发送路径旋转请求,而是在瓶子上又注册了一个检查瓶子是否旋转的事件监听器,这个监听器的回调才会发送路径的旋转请求。而且瓶子旋转本身并非即时的,有一个不易察觉的旋转动画时间,而在这个时间内,瓶子旋转的事件监听器会持续运行,且具体的执行次数不一致且无法预测。

所有的事件回调函数周期与“Update()”是同步的,现考察加盐的溶解判断逻辑——当盐的碰撞器与锅内的碰撞器接触时,则判定为溶解,随后发送一个旋转请求,其角度是一个常数。但是,在一帧之内,并不是一定只有一颗盐被溶解。现在已知一颗盐旋转角度是常数,请思考一下——每帧加1颗盐,加3帧,对比每帧加3颗盐,加1帧,是否有区别?从总量来说,都是加了3颗盐,仅仅是旋转3次小角度和旋转1次大角度的区别,两者理论上应该是相同的。很遗憾,计算机在进行浮点运算的时候精度会丢失,特别是对于这种通过角度计算旋转后的坐标,通常会进行大量浮点运算。要解释清楚每一个运算细节会占据大量篇幅(并非0.1+0.2≠0.3这样简单的逻辑),这里略过,结论就是即使两个最终旋转角度总和相同的旋转,只要计算次数不同,其最终坐标就不会相同。注意这个坐标计算过程是托管给引擎实现的,因此游戏开发者不可能以现有计算逻辑修复。

本项目工具实现原理
核心是修改了引擎的工作机制,强制让所有更新、触发器、事件回调等同步执行,并且消除了与时间相关变量的不一致性,由于并未修改游戏作者对于游戏功能的处理逻辑,因此本工具的结果是绝对可信、绝对一致的。正因为对引擎的逆向修改,因此本工具无法通过一般的Mod接口实现,开发难度(并非复杂度)也远超大部分程序,如果你有自信能解决上述问题却不通过修改引擎,那么需要从头到尾重新编写一切相关代码,使游戏采用数学上的严格运算而非依靠引擎的物理框架执行。如果你打算尝试从引擎入手,这里稍微提醒一下,引擎内核没有任何源代码可供参考,且机器码是经过编译器高度优化的乱序动态跳转表,你可能会遇到前所未有的困难局面和挑战。
Investigation of game source code defects and development ideas for Numerical Control tool
This section involves an explanation of the underlying principles and requires a certain level of computer knowledge. If you are not interested, you may skip it.

Overall design and core functionality planning
To achieve complete numerical control of the alchemy operations within the game, it is necessary to enumerate all relevant control variables:
  • Ingredient Grinding - The reference path for the movement of the bottle
  • Spoon Stirring - The actual movement of the bottle, associated with swamp decay and skeleton blood loss
  • Swamp Decay - When entering the swamp area, the remaining path and stirring speed will decay at a constant ratio
  • Skeleton Blood Loss - When entering the skeleton area, the health of the bottle will decrease at a constant rate
  • Pouring Base - The bottle approaches the origin point, with the angle approaching 0 degrees, unaffected by the swamp
  • Heating Cauldron - Affects the stirring speed, causing the vortex to rotate and drive the movement of the bottle
  • Adding Void Salt - Erases from the tail end of the path towards the bottle
  • Adding Sun or Moon Salt - Causes the bottle to rotate clockwise or counterclockwise
  • Adding Life Salt - Restores the health of the bottle
  • Adding Philosopher's Salt - Moves the bottle towards the nearest effect and rotates the angle towards the nearest effect, influenced by the swamp

The control principle of ingredient grinding
The game program represents the current grinding degree by referencing an internal floating-point value (ranging from 0 to 1), which controls the endpoint coordinates of the path. Since the path template for a single ingredient is completely identical, the grinding degree can accurately locate the endpoint coordinates, this step can also be achieved using the Mod interface provided by the developer.

The control principle of moving the bottle
Regarding the precise control of the final coordinates of the bottle, a simple idea is to set the overall distance that needs to be moved and then directly teleport the bottle to the calculated final coordinates. This is incorrect because, in actual gameplay, the stirring process of the bottle experiences swamp decay, making the path calculation more complex, it is difficult to calculate precise coordinates based on the overall length. Although it is not impossible to calculate, there is a more significant reason that leads to the error in the direct teleport approach, which is that the health calculation in the skeleton area is implemented through collision detection, making it impossible to determine precise health consumption based on the overall movement length.

This impossibility is not a mathematical one but is limited by the operational mechanisms of the game engine. This is also the fundamental reason why Plotter or any other secondary simulation environment cannot perfectly reproduce results while ensuring consistency. At the same time, this is also the root cause of many inherent bugs in games. For instance, in certain skeleton areas, when players stir at a lower speed and cannot pass through, but they can pass through by using the maximum stirring speed while heating.

Briefly describe the underlying operating mechanism of the program
A fundamental fact is that all interactive computer programs are essentially infinite loop programs. This may differ from the content encountered by beginners, as most programs written by beginners are executed once and then produce a result before ending, such as printing "Hello World" or calculating a simple operation like 3+2. Even when loops are included, they are mostly simple loops with a finite number of iterations.

Interactive programs, more specifically, game programs, are essentially large infinite loops that contain countless functions and control codes. Theoretically, if the user does not exit the program or execute the termination control code, the game program will continue to run indefinitely. Taking Unity as an example (all other modern game engines are similar), I will briefly explain the working mechanism of the engine.

The execution of engine code always precedes the execution of game logic code (the stack frame return address is always in the order of engine address first), one of its basic functions is to render the game screen. The rendering mechanism operates through an infinite loop, continuously referencing the current game state variables, executing all logic codes, and then rendering the results to be sent to the video memory, ultimately presenting the screen. Rendering is periodic, for older engines, one cycle (i.e., one frame) includes the execution of all logic codes, with all calculations executed sequentially within a single cycle. However, modern engines do not operate in this manner.

In simple terms, Unity incorporates multiple asynchronous loops within the top-level main loop, two of which directly contain game logic code. One type of loop is named "FixedUpdate()", which executes every registered function with the same name at fixed intervals, but does not synchronize the sending of rendering data; one cycle of this is referred to as a "non-rendering frame" (some people have oddly named this a "logic frame", the issue being that rendering frames also include logic, and their essential difference is that "FixedUpdate()" does not synchronize rendering requests).

The other type of loop is named "Update()" and "LateUpdate()". Since "LateUpdate()" always runs in the same cycle as "Update()" and always executes after "Update()", we will only discuss the impact of "Update()". One cycle of "Update()" is referred to as a "rendering frame", and the duration of the rendering frame is influenced and fluctuated far more than that of "FixedUpdate()". Firstly, settings such as vertical synchronization or frame rate limits directly affect the duration of the rendering frame (while "FixedUpdate()" remains unaffected); the lower limit of the engine's "Update()" update cycle is determined by this setting. For instance, if a frame rate limit of 100 frames per second is set, then the lower limit of the execution cycle for "Update()" would be 1/100=0.01 seconds.

The main difference between “Update()” and “FixedUpdate()” is that the former immediately sends a rendering request after executing the logic code, and remains in a suspended state blocking the next cycle until the GPU completes rendering. Therefore, the frame rate limit and other settings mentioned earlier only define a maximum frame rate; in reality, due to environmental fluctuations and hardware performance, the actual frame rate may not always reach the set maximum frame rate, which is quite common in some large games. To summarize a somewhat imprecise conclusion, the actual cycle of “FixedUpdate()” is primarily influenced by CPU performance, while the actual cycle of “Update()” is influenced by both CPU and GPU performance.

This is also the original intention behind modern engines splitting the two types of update functions, as CPU performance is often in a surplus state, allowing “FixedUpdate()” to almost always execute at a constant set cycle. According to general development standards, game developers should encapsulate all high-precision logic code, such as physical calculations and motion simulations, within “FixedUpdate()”, while other procedural logic code should be encapsulated within “Update()”. It is evident that the developers of Potion Craft overlooked this matter, as various physical processes such as speed, path, health, and rotation have been split into “Update()” and “FixedUpdate()”, resulting in code located in two places being inherently asynchronous. Therefore, regardless of how the interfaces provided by the developers are called, the underlying calculations themselves cannot be consistent, which is the greatest obstacle in numerical control projects.
Continued from above (word limit)
Regarding inconsistencies, a simple example may help in understanding. Suppose the execution frequency of "FixedUpdate()" is 100 times per second, while that of "Update()" is 50 times per second. Within "FixedUpdate()", there is a piece of code that calculates the current speed, and within "Update()", there is a related piece of code that calculates speed decay. It is evident that for every two calculations of the current speed, there will be one calculation of speed decay. Assuming the computer operates in an ideal environment without any fluctuations, even the split code could maintain perfect consistency. However, the unfortunate reality is that it is imperfect; in fact, almost no two adjacent cycles can be exactly the same, as the computer is always in an unpredictable fluctuating environment. Therefore, it is impossible for the two types of update functions to find a fixed periodic ratio.

Players may wonder why, in practical experience, these fluctuations are almost imperceptible, and the resulting errors are nearly invisible to the naked eye. This is because the engine uses two variables to forcibly correct the inconsistency of time. The variable "delta_time" corresponds to the time interval between two consecutive executions of "Update()", while "fixed_delta_time" corresponds to the time interval between two consecutive executions of "FixedUpdate()". By employing calculus thinking, each frame is viewed as a differential in time, and through the corresponding "Delta time", integration is performed on displacement, thus roughly maintaining the consistency of the program. However, it should be noted that this approach can only ever be "roughly" consistent, while numerical control requires absolute consistency regardless of how many decimal places there are or how many times it is executed. It is important to understand that errors have a cumulative effect; one cannot only consider the error of a single calculation. Even a difference of 0.001, after being executed hundreds or thousands of times, can lead to a considerable accumulation of error. In fact, a seemingly short ordinary stirring process corresponds to the "Update()" function being executed thousands of times.

Analyze the game logic code specifically
The basic fact to be clear is that all motion simulation programs are essentially a teleportion integral process with very small differential values. Without the theory of motion in real physics, all seemingly smooth motion is actually constantly teleporting entities at an extremely high rate.

For stirring and pouring operations, when the effects of swamps and skeletons are not considered, the two operations have a relatively similar implementation logic. Stirring references the player's spoon motion offset for each frame, through which a path deletion request is sent, and then the bottom is teleported to a new location at a smooth coordinate interval each frame, all on top of the path template. The same logic is used for pouring liquid, except that the total amount of liquid poured per frame is referenced, and the new coordinates are always on the line between the current coordinates and the origin coordinates.

Looking at the swamp and health calculations, the two processes seem to be unrelated when put together, but in fact the logic used by the game authors to implement these two processes is almost identical. Essentially, the swamp and skeleton areas that the player sees are made up of a large number of basic area cells, and from an engine perspective, a swamp area is nothing more than a cluster of rigid body colliders. How does the game program know if to enter the swamp and skeleton area? The developers chose the easiest, but least accurate, implementation method – rigid body collision detection, which detects if a bottle has a rigid body collision with a regional element. This is achieved via "OnTriggerEnter2D()" or "OnTriggerStay2D()", whose period is naturally synchronized with "FixedUpdate()". If you're smart enough, you can now perfectly explain why some skeletal areas can be passed or not depending on the speed of the stirring - the answer is that high-speed stirring means that the teleportation interval is larger, causing two adjacent coordinate teleports to skip the hitbox of some of the area cells, resulting in the subsequent health calculate logic code not being executed. It is important to note that it is not necessary to skip at high speeds, as it is unpredictable whether or not the hitbox can be skipped depending on the distribution of area cells.

For heating and vortex rotations, the situation is similar to stirring and pouring, except that the heat value is referenced, which is the total amount of time the player heats up, followed by a slow heat decay. In terms of motion, when the heat value is constant, the bottle will move in a circle with a constant angular velocity, and at the same time produce a constant linear velocity towards the center of the vortex, its path is the isokinetic spiral (Archimedes spiral). In fact, the heat value is decreasing, and its motion is represented by a decreasing velocity along a isokinetic spiral.

When the cycles of "Update()" and "FixedUpdate()" are synchronized, all the above simulation inconsistencies are eliminated. So is the Numerical Control perfect here? When it comes time to examine salting operations, new serious problems are about to emerge. Adding salt, especially the sun and moon and the Philosopher's salt, has two rotational processes - one is the rotation of the bottle itself, and the other is the rotation of the path. The reality is that if the engine is synchronized, the rotation of the bottle itself becomes absolutely consistent, but the result of the path rotation is different each time, which indicates that the inherent error mechanism is completely different.

The rotation of the path with salt and liquid pouring will produce a similar error mechanism, and it is necessary to understand the essence of the path in the game, in fact, the developers use a serialized point matrix to implement the path, rather than the Bezier curve dynamic calculation. If you have any knowledge of graphics processing, this is the difference between bitmaps and vectors, the developers chose a similar idea to bitmaps, all paths are nothing more than a set of point matrix sequences, but the intervals are very small, so the player can't see that they are discrete, and mistakenly think that they are continuous curves. Path rotation is the rotation of all point matrix sequences around the center of the bottle at the same angular velocity, the specific implementation involves a lot of matrix operations, and the values are floating-point values. The developers made a mistake when dealing with this kind of logic - the path rotation and the bottle rotation were out of sync.

There is another aspect of programming that needs to be understood here, which is the event handling mechanism. In short, when the salt is dissolved, its registered event sends a rotation request for the bottle, which should be synchronized with the rotation request of the path, according to normal design thinking. Unfortunately, the developers have adopted an inappropriate asynchronous approach - instead of sending a path rotation request, salting registers an event listener on the bottle to check whether the bottle is rotating, and the listener's callback will send the path rotation request. Moreover, the bottle rotation itself is not instantaneous, there is a subtle rotation animation time, during which the bottle rotation event listener runs continuously and the exact number of executions is inconsistent and unpredictable.
Continued from above (word limit)
All event callback function periods are synchronized with "Update()", and now we look at the logic of dissolving with salt - when the collider of salt comes into contact with the collider in the cauldron, it is judged to be dissolved, then a rotation request is sent, the angle of which is a constant. However, it is not necessary that only one salt is dissolved in a frame. Now that we know that the rotation angle of one salt is constant, think about it - is there a difference between adding 1 salt per frame for 3 frames and adding 3 salts per frame for 1 frame? In terms of the total amount, 3 salts are added both, which is only the difference between 3 small angle rotations and 1 large angle rotation, the two should theoretically be the same. Unfortunately, computers lose precision when performing floating-point arithmetic, especially for this kind of calculated coordinates after rotation by angles, which usually do a lot of floating-point arithmetic. To explain that each of these details will take up a lot of space (not as simple as 0.1 + 0.2 ≠ 0.3), the conclusion here is that even if the sum of the two final rotation angles is the same rotation, as long as the times of calculations is different, the final coordinates will not be the same. Note that this coordinate calculation process is managed to the engine, so it is not possible for game developers to fix it with existing calculation logic.

The implementation principle of the project tool
The core is to modify the working mechanism of the engine, force all updates, triggers, event callbacks, etc. to be executed synchronously, and eliminate the inconsistency of time-related variables, because has not modified the game author's processing logic of the game function, so the result of this tool is absolutely credible and consistent. If you are confident that you can solve the above problems without modifying the engine, you will need to rewrite all the relevant code from start to finish so that the game is mathematically rigorous rather than relying on the physical framework of the engine. If you're going to try to start with the engine, a little bit of a reminder is that the engine kernel doesn't have any source code to refer to, and the machine code is a compiler-optimized out-of-order dynamic jump table, you may encounter unprecedented difficulties and challenges.
项目进度一览 Overview of Project Progress
  • 已完成——核心工具开发
  • 已完成——官方源代码模拟逻辑缺陷修复
  • 已完成——标准化草案
  • 已完成——标准化敲定(标准化对于后续有深远影响,格式一旦确认将无法更改)
  • 需修改——数控配方图片说明(见后续章节)
  • 进行中——章节Ⅰ药剂数控配方研发
  • 进行中——章节Ⅱ药剂数控配方研发
  • 进行中——章节Ⅲ药剂数控配方研发
  • 进行中——章节Ⅳ药剂数控配方研发
  • 进行中——章节Ⅴ药剂数控配方研发
  • 进行中——章节Ⅵ药剂数控配方研发
  • 进行中——章节Ⅶ药剂数控配方研发
  • 进行中——章节Ⅷ药剂数控配方研发
  • 进行中——章节Ⅸ药剂数控配方研发
  • 未开始——本地化支持(主要语种)
主要贡献者感谢名单 List of Main Contributors
注意:配方的标准化人员和设计者会编码在配方本身内,这里仅列出做出重大贡献的人员

立项——oubeidong
数控工具开发——oubeidong
存档解码与编码——oubeidong
说明文档制作——oubeidong
翻译——oubeidong
数据库录入与管理——(暂无)
配方标准化主力——Wood
配方标准化主力——Racter
配方标准化主力——ByakuyX
配方标准化主力——Neno
数控配方格式详解 Detailed of Numerical Control Recipe Format
章节Ⅰ配方 Chapter Ⅰ potion recipes
治疗 Healing
Water-Tp1-Cpt4-Igd2 Std<oubeidong> HLG3=trr48cdl63&&##
Water-Tp2-Cpt5-Igd1 Std<oubeidong> HLG3=hlh87.5&&$2.3##
Water-Tp2-Cpt1-Igd2 Std<Racter>Dsn<轩辕Jun> HLG3=lfl100lfl11.5&&$0.1##
Water-Tp3-Cpt2-Igd1 Std<ByakuyX> HLG3=gdt34&10.25#15.5&&##
中毒 Poisoning
Water-Tp1-Cpt2-Igd2 Std<oubeidong> PSN3=trr28hbn76&&##
Water-Tp2-Cpt6-Igd1 Std<Racter>Dsn<轩辕Jun> PSN3=gtf30.5&&$2.82##
Oil-Tp2-Cpt4-Igd1-STm9 Std<oubeidong> PSN3=pps96.72&3.128$$STm9&&##
Water-Tp2-Cpt1-Igd2 Std<Racter>Dsn<轩辕Jun> PSN3=stm100stm55&&$0.9##
Water-Tp3-Cpt2-Igd1 Std<ByakuyX> PSN3=gdt66&13.15#19&2.9#20.9&&##
火 Fire
Water-Tp1-Cpt4-Igd2 Std<oubeidong> FRE3=fmw80lvr100&&##
Water-Tp2-Cpt1-Igd2 Std<Racter> FRE3=sps100sps50.3&0.37$$&&##
Water-Tp2-Cpt4-Igd2 Std<Racter> FRE3=pps100lvr83.6&12.6$$&&##
冰霜 Frost
Water-Tp1-Cpt2-Igd2 Std<oubeidong> FRS3=ift86tgw26&&##
Water-Tp2-Cpt4-Igd1 Std<ByakuyX> FRS3=wtc78&3.7$$&&##
Water-Tp2-Cpt1-Igd2 Std<Racter> FRS3=wtb100tgw39.3&&$0.3##
章节Ⅱ配方 Chapter Ⅱ potion recipes
爆炸 Explosion
Water-Tp1-Cpt5-Igd2 Std<oubeidong> EPS3=bmb99.8gpr46.4&&##
Water-Tp3-Cpt5-Igd1-STs117 Std<ByakuyX>Dsn<Alchemist's Tome V2> EPS3=bmb89.2&3.85STs117&4.65#15$1.1#10$1.1#10$1.1#19&3.85#24$3.8#23$3.8#23$3.8#23$3.8#21.1&&##
Water-Tp3-Cpt5-Igd2 Std<Racter> EPS3=fbl50bmb94.5&8#25&10#18.1&&##
Water-Tp3-Cpt5-Igd2 Std<Racter><oubeidong> EPS3=lsm90lsm58&6.7#19&&$0.8##
疯长 Growth
Water-Tp1-Cpt6-Igd3 Std<oubeidong> GRT3=hlh100egf75tgw49.3&&##
Water-Tp3-Cpt2-Igd1-STs63 Std<ByakuyX><Neno><oubeidong><Racter>Dsn<ByakuyX> GRT3=gdt91.5&1.65$$&7.69##STs63&4.4#13$0.8#13&4.4##&5.8##&1.3$6.3##$6.5##$6.5##$6.5#16$0.44#16&&##
Water-Tp3-Cpt5-Igd2 Std<Racter>Dsn<这个是闹哪样> GRT3=hlh100mss95&18.5#22.65&&##
Water-Tp3-Cpt3-Igd3 Std<Racter> GRT3=drm100drm100wtb47&16#18.25&&##
Water-Tp3-Cpt6-Igd2 Std<Racter>Dsn<雪亲王雪风> GRT3=egf40egf88.3&25#25&3#25&4#26&3.5#11.5&&##
力量 Strength
Water-Tp1-Cpt2-Igd3 Std<oubeidong> STT3=mds25.6trr100wds52.2&&##
Water-Tp2-Cpt1-Igd1 Std<Racter>Dsn<仁十六> STT3=mds98.5&7.8##&&##
Water-Tp3-Cpt2-Igd1 Std<ByakuyX>Dsn<庚子菌> STT3=gdt85.2&13#19&2.5#19&2.8#19&2.8#19&0.7##&4#7.5&&##
Water-Tp3-Cpt4-Igd2 Std<Racter> STT3=trr100pps96.5&13##&8#20.5&&##
灵巧 Dexterity
Water-Tp1-Cpt6-Igd3 Std<oubeidong> DTR3=cdl100mgb20.2ift100&&##
Water-Tp2-Cpt4-Igd3 Std<Racter> DTR3=wtc87.5wtm100wtb90.5&3.08$$&&##
Water-Tp3-Cpt4-Igd1-Sts253 Std<失去梦想的玩家><ByakuyX>Dsn<ByakuyX> DTR3=pts99.5STs21&4.83STs27&0.05STs6&0.05STs8&0.05STs11&0.05STs16&0.05STs20&0.05STs35&0.05STs41&0.05STs20&0.05(STs9&0.05)*2STs12&0.05STs14&0.05STs4&12.27#22$3#22$3.5(#23.5$3.8)*12#21&3.8#23.5&3.8#15&0.2#25.3&&##
Water-Tp3-Cpt4-Igd2 Std<Racter>Dsn<幻桜落> DTR3=wtc83pts99&3$$&27#19$0.5&7#17&3#5$0.7#15&1.5#24.75&&##
Water-Tp3-Cpt6-Igd2 Std<Racter>Dsn<显示错误---> DTR3=mgb33.3wtc87.75&21.5#26.6&&##
Water-Tp3-Cpt5-Igd3 Std<Racter>Dsn<黑喵水电费> DTR3=kkm55kkm38.75kkm51&6.75$$&30#32.15&&##
加速 Swiftness
Water-Tp1-Cpt4-Igd2 Std<oubeidong> SWF3=wlw99.6wlw78.6&&##
Water-Tp1-Cpt3-Igd3 Std<Racter> SWF3=ftb100ftb100ftb25&&##
Wine-Tp2-Cpt6-Igd1-STm92 Std<ByakuyX>Dsn<wood> SWF3=gtf73.661&4.45$$&7$$&0.8STm75&10STm10&&STm7##
Water-Tp2-Cpt1-Igd3 Std<Racter> SWF3=mdm100wdb100wtm90&10.85##&&##
Water-Tp3-Cpt4-Igd2 Std<Racter>Dsn<110MT011> SWF3=gpr57.5fgp28.5&7.24#12.5$1.1(#10$1.1)*3##&7.5$1.7&&##
Water-Tp3-Cpt6-Igd2 Std<Racter>Dsn<bill_20617117476> SWF3=trb75.5tdt87.5&2.5$$&7.7#16$0.8(#10$1.1)*3##&&$2.15##
章节Ⅲ配方 Chapter Ⅲ potion recipes
闪电 Lightning
Water-Tp1-Cpt5-Igd3 Std<oubeidong> LTN3=slb100pts24.1tdt26&&##
法力 Mana
Water-Tp1-Cpt5-Igd2 Std<oubeidong> MAN3=slb94sdc95.5&&##
Wine-Tp2-Cpt4-Idg1-STs119 Std<ByakuyX> MAN3=pts16&4.3STs119&&##
石肤 Skin
Water-Tp1-Cpt6-Igd3 Std<oubeidong> SKN3=hlh100mds84.3gtf46.1&&##
睡眠 Sleep
Water-Tp1-Cpt6-Igd3 Std<oubeidong> SLP3=cdl100tgw30.3egf30.1&&##
光 Light
Water-Tp1-Cpt5-Igd3 Std<oubeidong> LIT3=mmm100mmm99mdm63&&##
章节Ⅳ配方 Chapter Ⅳ potion recipes
魅力 Charm
Water-Tp1-Cpt5-Igd4 Std<oubeidong> CHM3=bmb100bmb100slb89bth15.5&&##
减速 Slowness
Water-Tp1-Cpt6-Igd4 Std<oubeidong> SLW3=hlh100mds80trr85egf53.1&&##
Water-Tp3-Cpt2-Igd2 Std<oubeidong> SLW3=gdt89mds100&13.3#20&3#18&3.1#18&3.1#18&1.8##&4.6##&13.95#20$0.47#12$0.42#7&&$2.47##
狂怒 Rage
Water-Tp1-Cpt5-Igd4 Std<oubeidong> RAG3=bmb100bmb100lvr72sdc63&&##
透视 Magical Vision
Water-Tp1-Cpt6-Igd3 Std<oubeidong> MGV3=slb100egf11.5mgb22.5&&##
Water-Tp3-Cpt5-Igd2 Std<ByakuyX><oubeidong> MGV3=wtc82slb94&2.75$$&18.85#24.5$1.35#11.5$0.75#20.5&&##
章节Ⅴ配方 Chapter Ⅴ potion recipes
酸 Acid
Water-Tp1-Cpt6-Igd4 Std<oubeidong> ACD3=gtf35hbn50mmm72.5gtf30.5&&##
Wine-Tp2-Cpt6-Igd1-STm287 Std<ByakuyX>Dsn<显示错误---> ACD3=gtf60.32STm32&12.25STm250&&STm5##
性欲 Libido
Water-Tp1-Cpt5-Igd5 Std<oubeidong> LBD3=mmm100bmb100fmw100ffb2wdb23&&##
隐形 Invisibility
Water-Tp1-Cpt5-Igd5 Std<oubeidong> IVS3=slb100wlw100slb100pts39.6fmw45&&##
Oil-Tp3-Cpt4-Igd1-STm353-STs6 Std<oubeidong> IVS3=STs6pts100$$&4.75$0.673&3.07#18$0.96#9$0.48#6$0.27#4$0.12#2.5$0.05#14&0.16#8&0.06#7.94&6.52##&2.54STm353&&##
悬浮 Levitation
Water-Tp1-Cpt6-Igd5 Std<oubeidong> LVT3=gpr50&&##fgp70wlw100mgb7bth26&&##
死灵术 Necromancy
Water-Tp1-Cpt6-Igd5 Std<oubeidong> NCM3=gdt20&&##gtf30tsk68tsk63fct90.5&&##
章节Ⅵ配方 Chapter Ⅵ potion recipes
防毒 Poison Protection
Oil-Tp1-Cpt3-Igd4 Std<oubeidong> PSP3=drm100mds100gdb100tgw34&&##
防电 Lightning Protection
Oil-Tp1-Cpt4-Igd5 Std<oubeidong> LNP3=drm100mds100gdb100pps65tsk63&&##
防火 Fire Protection
Oil-Tp1-Cpt5-Igd4 Std<oubeidong> FRP3=wtm100&&##hlh68tgw100kkm31.8&&##
防冻 Frost Protection
Oil-Tp1-Cpt6-Igd4 Std<oubeidong> FSP3=hbn55fmw100gtf34.2lsm88.5&&##
胶着 Gluing
Oil-Tp1-Cpt4-Igd4 Std<oubeidong> GLU3=pps35mds98gbs66.5msr68&&##
滑润 Slipperiness
Oil-Tp1-Cpt6-Igd4 Std<oubeidong> SLR3=rbc100&&##hlh68wtb100drm67&&##
臭味 Stench
Oil-Tp1-Cpt5-Igd4 Std<oubeidong> STC3=msr100mds76gdt12mmm76&&##
章节Ⅶ配方 Chapter Ⅶ potion recipes
防酸 Acid Protection
Oil-Tp1-Cpt6-Igd6 Std<oubeidong> ACP3=drm100mds100gdb100mss100wtb88egf35&&##
反魔法 Anti-Magic
Oil-Tp1-Cpt6-Igd6 Std<oubeidong> ATM3=dmb100&&##pts15mgb33tdt100wtc81wdb81&&##
缩小 Shrinking
Oil-Tp1-Cpt6-Igd6 Std<oubeidong> SRK3=sdc100&&##wtc83lfl100tgw100gdb100egf15.6&&##
巨化 Enlargement
Oil-Tp1-Cpt5-Igd7 Std<oubeidong> ELG3=fbl100pts32gpr33fmw100sps95&&##dgp45dsd56.5&&##
回春 Rejuvenation
Oil-Tp1-Cpt6-Igd7 Std<oubeidong> RJV3=drm100mds100gdb100cdl100egf30kkm43hlh93.5&&##
章节Ⅷ配方 Chapter Ⅷ potion recipes
灵感 Inspiration
Wine-Tp1-Cpt5-Igd6 Std<oubeidong> IPR3=bth45wtm100slb100tdt90wdb53mmm49&&##
香气 Fragrance
Wine-Tp1-Cpt6-Igd4 Std<oubeidong> FRG3=hlh80egf20wtc69rbc13.4&&##
恐忧 Fear
Wine-Tp1-Cpt6-Igd4 Std<oubeidong> FER3=dgp40trb54lsm79ftb100&&##
章节Ⅸ配方 Chapter Ⅸ potion recipes
幻象 Hallucinations
Wine-Tp1-Cpt6-Igd3 Std<oubeidong> HLC3=mgb43slb99mgb10&&##
幸运 Luck
Wine-Tp1-Cpt5-Igd4 Std<oubeidong> LUK3=wtc75cdl55slb71acc100&&##
诅咒 Curse
Wine-Tp1-Cpt6-Igd4 Std<oubeidong> CUS3=dgp39trb45mds78.5fmw77&&##
贤者之石 Philosopher's Stone
虚无之盐 Void Salt
月之盐 Moon Salt
日之盐 Sun Salt
生命之盐 Life Salt
贤者之盐 Philosopher's Salt
2 Comments
Haira 23 Dec, 2024 @ 9:44pm 
很有意思的项目
九尔Sarah 22 Dec, 2024 @ 7:36pm 
哇塞哇塞,太强了!大佬加油!