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

Project1

 找回密码
 注册会员
搜索
查看: 2708|回复: 6

[讨论] RGSS1的新bug

[复制链接]

Lv5.捕梦者 (版主)

遠航の猫咪

梦石
3
星屑
22393
在线时间
2335 小时
注册时间
2005-10-15
帖子
1160

开拓者

发表于 2021-10-5 14:03:48 | 显示全部楼层 |阅读模式

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

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

x
本帖最后由 SailCat 于 2021-10-5 14:14 编辑

bug捉不完啊捉不完

装备附带自动状态是XP的独有功能,后面所有的版本都没有恢复这个默认功能,VA加了那么多traits,装备可以直接弱化强化能力百分比,可惟独不能附加自动状态
结果整个把自动状态跟完一遍之后才发现,这里面的bug多到无法想象,难怪后续版本会取消这个功能
XP的装备自动状态是植入的,即装上装备附加一个解除回合是-1的状态,卸除装备会解除这个状态
因为解除回合是-1所以常规的解除方法如全回复、技能解除等,解除不掉这个状态

结果发现这种植入的模式,引入了许多bug
1. (SEP 1.0已修复)当A和B同时指向自动状态X时,装备A、装备B、卸除B,会导致自动状态X被错误卸除
2. (SEP 1.1已修复)当A和B分别指向MHP%/MSP%为x, y的两个状态时(x > y > 100),装备A、改装B,因为状态是先卸后装,会导致当前HP/SP下降,也就是变成100%(应该是从100+X下降到100+Y,而不是下降到100%)
3. (新发现的bug)当在战斗中附加了会一直持续到战斗外的状态X(不勾选“战斗结束时解除”)的装态,然后结束战斗之后,假设有一个装备A也指向状态X,简单通过装备A再卸除的方式就可以消掉这个状态
4. (新发现的bug,和自动状态无关)当中了MHP%/MSP%为x的状态(x<100)时,执行“完全回复”指令,不能完全回复,回复后HP和SP不满
5. (新发现的bug)当装备A指向的自动状态X,会同时附加Y状态(即对Y打+)时,装备A会自动附加自动状态X和非自动状态Y,卸除A却不会解除Y

第3条你可以认为是一个feature,但我认为是bug,至少对于插件开发来说:假如允许在战斗中更换装备,那……
而且过往的商业大作有此bug的不在少数,最著名的就是FF5,这一作(好像FF惟一的一作)允许在战斗中更换装备,但它的自动状态是每更换一次全身刷新的,结果就产生了无限分身的里技
我理解RGSS1将自动状态做成植入式是为了能利用默认的连锁附加、连锁解除、状态防御等逻辑(附加自动状态是走的add_state方法),但仅仅引入一个是否强制附加的bool值,这个处理方式过于粗暴,以致引入了许多bug……
最近在做战斗方面的插件,深感,作为一个特色系统,装备能附加状态确实很自由,但默认的植入式状态实现方法,为插件开发带了许多困难,单是在装备功能扩展的情况下静态维护自动状态就非常麻烦,更不用说如果允许战斗中换装,允许技能附加状态、给状态扩展各种功能之后导致的各种意外结果

讨论一下怎么修这个bug,目前我的思路如下:
1. 因为自动状态永远不会解除,所以Game_Actor#states_turn[id]设为-1这个标识,在这个语境中实质上是无用的。换个想法,假如states_turn里只有正常附加、且可以通过正常途径解除的状态,和装备(技能、职业、一应身外之物)附加的状态予以区分(也就是不植入,参考一般实现装备附带技能所用的逻辑),就可以解决这个问题。
2. 当自动状态不植入到角色身上(不写入@states_turn和@states),调取角色的状态(取能力值,做Game_Actor#state?判断)却要考虑这些状态,其中一个办法是就是重新定义reader方法的states,不再用attr_reader简单定义,而是让它返回@states与身外状态的并集,而“身外状态”并不是一个可维护集合,是根据角色的其他信息即时演算的。并且在取基本参数时,用self.states代替@states
3. (重修bug1)无需任何代码,因为这里没有add_state和remove_state逻辑
4. (重修bug2)因为这里没有add_state和remove_state逻辑,所以update_auto_state操作可以只包括对hp和sp进行修正,别的不用管
5. (修复bug3)无需任何代码,因为换装不触发add_state逻辑,所以装备上的状态可以和身上的状态并存,换装不影响,也因为取的是并集,不会导致状态的加成被计算两次
6. (修复bug4)因为这里面没有自动状态,完全回复根本不用走remove_state逻辑,只要将@states和@states_turn整个清空,再将hp和sp改成最大值就可以了
7. (修复bug5)装备返回的自动状态演算的集合时,除状态本身,还要包括该自动状态的+状态,并且在返回全身总有效states时,还要额外减去所有自动状态里包含的-状态(即暂时屏蔽掉,不是从@states里删掉)

上述思路会存在的问题:
1. 默认的@states每次更新附加状态时需要对整个数组进行排序(以便以正确的顺序显示状态名称、播放状态动画等),一旦变成外置的演算,这个排序的逻辑将无法保留,折中的方案是@states不再更新排序,改为在每次取值时排序,但这样程序复杂度爆炸(并集本身是n^2复杂度,排序是nlogn复杂度),而取states其实是一个非常频繁的操作,如果每次都要取一遍并集并排序,脚本执行会很慢。
2. 由于自动状态不附加到角色身上,因此两个互相抵消的自动状态会出问题,假设装备和A和B不同位置分别有状态X和Y(X对Y打-,Y对X打-),若同时装备A和B,则返回的总有效states里将同时不包含X和Y,这个逻辑是错误的,应同时包含X和Y,因为默认走add_state逻辑的时候,两个自动状态之间并不能实质上互相抵消(附加时有force=true,尝试解除时没有),而是共存。
3. 通过脚本执行add_state(id, true)来给角色施行永久的不可解除buff的办法不可行了,只要完全回复一次角色的buff就没了
4. 不执行add和remove操作,每次换装或其他可能触发角色(非附身状态变化)的操作时,就都要修正当前hp和sp,或者还可能有hp0之类的处理。总之默认系统中让自动状态走add和remove也并不是一无是处的,至少可以免除额外写hp/sp维护的逻辑(虽然内建的更新顺序是错的吧)

或者大家有什么其他的想法?总之Game_Battler 2基本上需要重写……(之前Game_Battler 3已经重写过了)

评分

参与人数 1+1 收起 理由
taeckle + 1 大神威武!

查看全部评分

SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。

Lv4.逐梦者

梦石
0
星屑
7926
在线时间
1181 小时
注册时间
2007-7-29
帖子
2055
发表于 2021-10-5 14:58:29 | 显示全部楼层
默认脚本忘了七七八八了,不过根据我懒人做法,我会给状态添加一个source的变量,装备的自动状态只会针对该source的情况,这个方式add和remove估计也要改。估计这样修改还是有bug
回复 支持 反对

使用道具 举报

Lv5.捕梦者

梦石
0
星屑
37641
在线时间
5311 小时
注册时间
2006-11-10
帖子
6541
发表于 2021-10-5 15:13:06 | 显示全部楼层
本帖最后由 灯笼菜刀王 于 2021-10-5 15:26 编辑

我的做法:  
1, 取消了 -1 回合的处理, 自动状态和默认状态一样没有特权
2, 自动状态全部到战斗里处理, 分为 开始战斗触发和每回合触发两种模式, 顺便把附加状态做个概率=.=


----------------------------
其实, 我觉得猫姐想太复杂了

@states 里保存的状态是id, 那add_state的时候, 把自动状态包括它+的都保存为负数, 让它们和正常状态分开, 各处理各的不就好了?  然后equip最后清除@states所有负数id, 遍历一遍全身装备塞回自动状态就可以解决所有问题了嘛

至于中毒不叠加, 状态描绘等, 这些连带出来的小问题, 解决起来也不会麻烦嘛,笑

点评

考虑扩展情况,附加-1状态的不只有装备,可能还有技能、职业、其他自动状态的+、剧情(事件脚本执行add_state(id, true)等等……,单修bug当然不算难  发表于 2021-10-5 17:21
回复 支持 反对

使用道具 举报

Lv4.逐梦者

梦石
0
星屑
6483
在线时间
119 小时
注册时间
2020-1-8
帖子
234
发表于 2021-10-5 17:04:50 | 显示全部楼层
本帖最后由 RPGzh500223 于 2021-10-5 18:20 编辑

我对bug的理解:弹窗报错或游戏无法进行,至于其他归为“写得不够好”
RMXP中目前我唯一遇到的bug——跳河
状态这块,没实际制作游戏,只了解些大概。
思路的话,给“状态添加个来源属性”(二进制数   位1表示是否装备获得的状态  位2是否技能获得……大概这意思)
Game_Battler状态好像都是ID索引数组,新建个状态的来源属性数组与之对应
来源属性值参与状态的操作

----------分割线------------------
感谢楼主在  【[原创发布] 地图截图工具v1.3(猫儿的RMXP工具包第九弹)更新!】
中的一段汇编代码,现已成功入坑汇编了

点评

rgba转argb这个需求不用汇编实在太慢了……  发表于 2021-10-5 19:48
回复 支持 反对

使用道具 举报

Lv5.捕梦者 (版主)

遠航の猫咪

梦石
3
星屑
22393
在线时间
2335 小时
注册时间
2005-10-15
帖子
1160

开拓者

 楼主| 发表于 2021-10-7 03:42:19 | 显示全部楼层
大概在SEP Core里实现了自己的思路,并且避免了一些前述讨论中发现的问题

装备附带(也包括以后扩展附带)的状态不再走add和remove逻辑,而是从装备中直接统计
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 获取额外状态(非附身但有效)
  3.   #--------------------------------------------------------------------------
  4.   def extra_states
  5.     set = armors.map {|a| a.auto_state_id} - [0]
  6.     i = 0
  7.     while i < set.size
  8.       set |= $data_states[set[i]].plus_state_set
  9.       i += 1
  10.     end
  11.     set.reject {|i| $data_states[i].zero_hp}
  12.   end

自动状态就是你设置的状态以及该状态所有+的状态,可以连锁+,反正都会并进来
注意XP在这里有一个编辑器约束,即(作为HP0的状态)是不可能被认定为防具自动状态的,在各种狂野模式备注,各种连锁附加的情况下,这里的最后一步还原了这个约束,保证游戏逻辑的正确。

如果要增加技能附带、职业附带状态,就可以更改这里的定义。
(在目前正在开发的基于SEP core的战斗类插件中,所有VA/MV的traits全部是定义在角色/敌人(固有)和状态(修正)上面的,这也是为了在设定的时候更符合习惯,但是技能和职业这种身外之物要附带traits,就只能通过类似自动状态的逻辑来附加,没法像VA那样直接设置)

获取身上的有效状态使用reader方法,该方法会覆盖原本的attr_reader默认方法
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 获取状态
  3.   #--------------------------------------------------------------------------
  4.   def states
  5.     return @states_cache if @states_cache
  6.     extras = extra_states
  7.     denies = extras.inject([]) {|a, i| a |= $data_states[i].minus_state_set}
  8.     @states_cache = (@states - denies | extras).sort_by do |i|
  9.       s = $data_states[i]
  10.       [-s.rating, -s.restriction, i]
  11.     end
  12.   end

由于状态调用委实过于频繁,采用了缓存的技术思路,防止反复取值时无意义地遍历装备、重新对状态排序等。当然代价就是缓存需要手工清除。

换装备时若触发自动状态更新,只是简单的清掉状态缓存,可以看到脱钩了人物本身状态后,update_auto_state现在变得非常简单(默认系统的update_auto_state调用在实际换装以前,这样改的话没有问题),换装后任何试图读取states的方法(比如取能力值)都会重建缓存,fix_hp里面会取到maxhp,即会重建缓存,如此bug1和2被自动修复。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 更新自动状态
  3.   #     old_equip : 卸下防具
  4.   #     new_equip : 装备防具
  5.   #--------------------------------------------------------------------------
  6.   def update_auto_state(old_equip, new_equip)
  7.     @states_cache = nil unless old_equip == new_equip
  8.   end
  9.   #--------------------------------------------------------------------------
  10.   # ● 变更装备
  11.   #     equip_type : 装备类型
  12.   #     id    : 武器 or 防具 ID  (0 为解除装备)
  13.   #--------------------------------------------------------------------------
  14.   def_after :equip do |equip_type, id|
  15.     fix_hp
  16.     fix_sp
  17.   end


状态的两个检定用方法:这里将本来取@states的地方取成了states,并且所有取能力值的地方也同样都这么改了,共11处(状态在XP里默认能加成11项能力),就不列了,因为现在@states仅仅只是身上通过事件、战斗等方式附加上的实体状态,并不等同于身上的有效状态。特别要说明的是state_full?这个方法,这是覆写同一状态时用到的检定,由于自动状态里可能包括连锁-的状态,原来的逻辑中当换装时,连锁-的状态就会被清除掉。现在换装不走add_state,连锁-的状态就不会被清除,但按照游戏原本的正常逻辑,它却不是身上的有效状态。即,附加的实体状态在state?里检定不出来,因此state_full?检定中除了原先的state?调用外还加上了@states.include?调用(这个就是原始的state?逻辑),为什么不直接只写@states.include?是因为自动状态也是true,至于为什么保留回合==-1的逻辑下面谈。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 检查状态
  3.   #     state_id : 状态 ID
  4.   #--------------------------------------------------------------------------
  5.   def state?(state_id)
  6.     states.include?(state_id)
  7.   end
  8.   #--------------------------------------------------------------------------
  9.   # ● 判断状态是否已满
  10.   #     state_id : 状态 ID
  11.   #--------------------------------------------------------------------------
  12.   def state_full?(state_id)
  13.     return false unless state?(state_id) or @states.include?(state_id)
  14.     return true if extra_states.include?(state_id)
  15.     return true if @states_turn[state_id] == -1
  16.     @states_turn[state_id] == $data_states[state_id].hold_turn
  17.   end


重头戏来了:状态的附加过程,基本上是原先的逻辑,简化了一些代码,例如不再进行movable?的判定而是直接写restriction == 4(movable?会判定身上所有状态,没必要,清行动肯定是因为附加的当前状态将restriction提到了4啊),这里的接口force没有改,force时回合设为-1的逻辑也没有改,但实际上系统内部update_auto_state改了之后,已经不可能再用force=true来调这个方法了。但用户可能会用事件脚本来调这个方法,接口必须留下。而且这是一个对于游戏非常有用的feature:剧情来源的自动状态(比如难度选简单后atk, pdef, mdef都*110%,困难则都*90%)。因为剧情来源的自动状态没法绑定到角色身上的东西,只能让它真实附加到角色身上。而为了防止它被意外解除掉(完全回复或事件解除),做成自动状态是最简单的办法。连锁附加和连锁解除都将force给递归传了进去,确保逻辑不出问题。这种状态虽然不能被彻底解除,但通过装备自动状态对其打-,可以在装了某装备的时候暂时屏蔽掉,这也是应有之意,如用特定装备抵消剧情带来的队伍debuff(原系统连锁解除时不传递force进去,便无法做到屏蔽,而且它也没法屏蔽啊,一屏蔽就真解除了),参考前面取states时候的逻辑。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 附加状态
  3.   #     state_id : 状态 ID
  4.   #     force    : 强制附加标志
  5.   #--------------------------------------------------------------------------
  6.   def add_state(state_id, force = false)
  7.     # 无效状态的情况下,过程结束
  8.     return unless state = $data_states[state_id]
  9.     # 不是强制附加,且状态被当前状态抵抗的情况下,过程结束
  10.     return if not force and states.any? {|i|
  11.       $data_states[i].minus_state_set.include?(state_id) and
  12.       not $data_states[state_id].minus_state_set.include?(i)}
  13.     # 并未附加本状态(任何来源)的情况下
  14.     unless state?(state_id) or @states.include?(state_id)
  15.       # 清除状态缓存
  16.       @states_cache = nil
  17.       # 附加状态
  18.       @states.push(state_id)
  19.       # HP 0的检查更改
  20.       @hp = 0 if state.zero_hp
  21.       # 限制不行动的检查更改
  22.       @current_action.clear if state.restriction == 4
  23.       # 状态变化 (+) (-) 处理
  24.       state.plus_state_set.each {|i| add_state(i, force)}
  25.       state.minus_state_set.each {|i| remove_state(i, force)}
  26.       # 修正当前 HP 及 SP
  27.       fix_hp
  28.       fix_sp
  29.     end
  30.     # 按强制附加与否设置解除回合数
  31.     if @states.include?(state_id)
  32.       @states_turn[state_id] = force ? -1 : state.hold_turn
  33.     end
  34.   end

这里有一个我并不打算修复的"bug":若状态A连锁+状态B和C,状态B和C彼此连锁-,则最后附加的状态(不论是不是强制附加)除了A以外,要看B和C的ID号哪个在前,在前的会被解除掉。默认脚本里就是这样,现在改了之后还是这样。我觉得这个是自己数据库设置的问题,状态B和C既然彼此连锁-,那本来逻辑上就不应该同时共存,都被A连锁+上你是想要闹哪样?
比如加速50%和减速50%,逻辑上互相抵销,但因为XP的状态能力乘算原理,如果真的都同时附加上,结果会是速度变成原来的(150%*50%)=75%,相当于半个减速。
不过,如果装备的自动状态A,满足上述条件的话,则确实目前会把A、B、C都处理成自动状态,也问问大家,虽然这个"bug"我是不打算修,但如果希望逻辑弄成一致,那倒确实是可以的。


状态的移除过程:基本直接copy代码,注意装备来的自动状态不在@states里面,是没法移除的,因装备自动状态被屏蔽掉的本体状态却在@states里面,是可以移除的,所以能不能移除只需要检查实体状态,有并能移除就移除。HP0的检查移到状态实际被删除之后,简化很多判断。最重要的是添加了连锁附加的状态连锁移除的过程。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 解除状态
  3.   #     state_id : 状态 ID
  4.   #     force    : 强制解除标志 (处理自动状态时使用)
  5.   #--------------------------------------------------------------------------
  6.   def remove_state(state_id, force = false)
  7.     # 无效状态的情况下,过程结束
  8.     return unless state = $data_states[state_id]
  9.     # 战斗者本体未附加本状态的情况下,过程结束
  10.     return unless @states.include?(state_id)
  11.     # 被强制附加的状态、并不是强制解除的情况下,过程结束
  12.     return if @states_turn[state_id] == -1 and not force
  13.     # 清除状态缓存
  14.     @states_cache = nil
  15.     # 删除本体状态
  16.     @states.delete(state_id)
  17.     @states_turn.delete(state_id)
  18.     # HP 0的检查更改
  19.     if @hp == 0 and state.zero_hp and states.all? {|i|
  20.       not $data_states[i].zero_hp}
  21.       @hp = 1
  22.     end
  23.     # 强制解除的情况下,同样解除连锁附加的状态
  24.     state.plus_state_set.each {|i| remove_state(i, true)} if force
  25.     # 修正当前 HP 及 SP
  26.     fix_hp
  27.     fix_sp
  28.   end


其他零星修改:除了取能力值都要改成states以外,以下一些方法也得改。记住@states是身上的实体状态,(self.)states是身上的有效状态,两者既不等同,也不是子集关系就行。完全回复的那个bug犯的真的非常之蠢,只要将hp/sp的复设挪到状态被清除之后马上就可以了。
RUBY 代码复制
  1. #--------------------------------------------------------------------------
  2.   # ● 获取状态的动画 ID
  3.   #--------------------------------------------------------------------------
  4.   def state_animation_id
  5.     states.empty? ? 0 : $data_states[states[0]].animation_id
  6.   end
  7.   #--------------------------------------------------------------------------
  8.   # ● 获取限制
  9.   #--------------------------------------------------------------------------
  10.   def restriction
  11.     states.empty? ? 0 : states.max_by {|i| $data_states[i].restriction}
  12.   end
  13.   #--------------------------------------------------------------------------
  14.   # ● 判断状态 [无法获得 EXP]、[无法回避攻击]、[连续伤害]
  15.   #--------------------------------------------------------------------------
  16.   def cant_get_exp?;  states.any? {|i| $data_states[i].cant_get_exp};     end
  17.   def cant_evade?;    states.any? {|i| $data_states[i].cant_evade};       end
  18.   def slip_damage?;   states.any? {|i| $data_states[i].slip_damage};      end
  19.   #--------------------------------------------------------------------------
  20.   # ● 状态变化 (+) 的适用
  21.   #     plus_state_set  : 状态变化 (+)
  22.   #--------------------------------------------------------------------------
  23.   def states_plus(plus_state_set)
  24.     # 清除有效标志
  25.     effective = false
  26.     # 循环 (附加状态)
  27.     for i in plus_state_set
  28.       # 防御本状态的情况下,跳过
  29.       next if state_guard?(i)
  30.       # 非满状态的情况下,设置有效标志
  31.       effective |= !state_full?(i)
  32.       # 状态为 [不能抵抗],或状态非满且概率判定通过的情况下
  33.       if $data_states[i].nonresistance or (not state_full?(i) and
  34.         rand(100) < state_table[state_ranks[i]])
  35.         # 设置状态变化标志
  36.         @state_changed = true
  37.         # 附加状态
  38.         add_state(i)
  39.       end
  40.     end
  41.     # 过程结束
  42.     return effective
  43.   end
  44.   #--------------------------------------------------------------------------
  45.   # ● 状态变化 (-) 的使用
  46.   #     minus_state_set : 状态变化 (-)
  47.   #--------------------------------------------------------------------------
  48.   def states_minus(minus_state_set)
  49.     # 清除有效标志
  50.     effective = false
  51.     # 循环 (解除状态)
  52.     for i in minus_state_set
  53.       # 状态被实际附加的情况下,设置有效标志
  54.       effective |= @states.include?(i)
  55.       # 设置状态变化标志
  56.       @state_changed = true
  57.       # 解除状态
  58.       remove_state(i)
  59.     end
  60.     # 过程结束
  61.     return effective
  62.   end
  63.   #--------------------------------------------------------------------------
  64.   # ● 全回复
  65.   #--------------------------------------------------------------------------
  66.   def recover_all
  67.     @states.reject! {|i| @states_turn[i] >= 0}
  68.     @states_turn.reject! {|k, v| v >= 0}
  69.     @states_cache = nil
  70.     @hp = maxhp
  71.     @sp = maxsp
  72.   end


以上,对Game_Battler 2的重写基本就是这样。

评分

参与人数 1+1 收起 理由
89444640 + 1 猫大威武

查看全部评分

SailCat (小猫子·要开心一点) 共上站 24 次,发表过 11 篇文章 上 次 在: [2006年01月28日11:41:18 星期六] 从 [162.105.120.91] 到本站一游。
回复 支持 反对

使用道具 举报

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

本版积分规则

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

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

GMT+8, 2024-3-29 23:13

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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