键,则直接向下进行强力攻击并形成冲击波; 此时,可以消耗连击点数发动[终结之击]。\n
"""
name = '螺旋穿刺'
+ cellPos = (3,8)
learnLv = 35
masterLv = 60
maxLv = 70
- position = 3 # TODO
+ position = 3
rangeLv = 2
cube = 1
cd = 15
@@ -848,7 +1344,7 @@ class Skill34(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 移动距离 : {value0}px
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -864,10 +1360,10 @@ class Skill34(ActiveSkill):
# [终结之击]攻击力 : {value5}%
data5 = get_data(f'{prefix}/{uuid}', 5)
hit5 = 1 # TODO
- group5 = 'end'
# [范围信息]
# 范围比率 : {value6}%
data6 = get_data(f'{prefix}/{uuid}', 6)
+ group5 = 'end'
def vp_1(self):
"""
@@ -899,23 +1395,23 @@ class Skill34(ActiveSkill):
"""
...
-
# 雷光刃影
# thief/rogue/e4c354a89c337310aeb7041d5e742828
# ddc49e9ad1ff72a00b53c6cff5b1e920/e4c354a89c337310aeb7041d5e742828
class Skill35(ActiveSkill):
"""
- 抓取敌人后, 对其进行连续地突刺攻击, 最后再向下猛踢敌人。\n
- 向下猛踢碰到地面时, 生成冲击波; 抓取成功时, 进入无敌状态, 并使敌人进入强制控制状态。\n
- 攻击无法抓取的敌人时, 将直接进行向下猛踢, 增加向下猛踢攻击力且不会控制敌人。 向下猛踢后, 可消耗连击点数发动[终结之击]。\n
- 在决斗场中[终结之击]无法使敌人浮空。
+ 抓取敌人后,对其进行连续地突刺攻击,最后再向下猛踢敌人。\n
+ 向下猛踢碰到地面时,生成冲击波; 抓取成功时,进入无敌状态,并使敌人进入强制控制状态。\n
+ 攻击无法抓取的敌人时,将直接进行向下猛踢,增加向下猛踢攻击力且不会控制敌人。 向下猛踢后,可消耗连击点数发动[终结之击]。\n
+ 在决斗场中[终结之击]无法使敌人浮空。\n
"""
name = '雷光刃影'
+ cellPos = (4,8)
learnLv = 35
masterLv = 60
maxLv = 70
- position = 4 # TODO
+ position = 4
rangeLv = 2
cube = 1
cd = 15
@@ -924,7 +1420,7 @@ class Skill35(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 突刺攻击力 : {value0}% X {value1}
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -942,12 +1438,12 @@ class Skill35(ActiveSkill):
# [终结之击]攻击力 : {value5}%
data5 = get_data(f'{prefix}/{uuid}', 5)
hit5 = 1
- group5 = 'end'
# [范围信息]
# 范围比率 (除踢击冲击波) : {value6}%
data6 = get_data(f'{prefix}/{uuid}', 6)
# 踢击冲击波范围比率 : {value7}%
data7 = get_data(f'{prefix}/{uuid}', 7)
+ group5 = 'end'
def vp_1(self):
"""
@@ -968,24 +1464,24 @@ class Skill35(ActiveSkill):
"""
...
-
# 疾风乱舞
# thief/rogue/1dad88963abdc96b091fcab185a8820d
# ddc49e9ad1ff72a00b53c6cff5b1e920/1dad88963abdc96b091fcab185a8820d
class Skill36(ActiveSkill):
"""
- 快速接近前方敌人, 并对该敌人进行狂风般的乱舞攻击。\n
- 突进过程中, 按上下方向键可以调整移动方向, 成功抓住后进入无敌状态并强控敌人。\n
- 乱舞攻击时, 若按跳跃键, 则会向周围的其他敌人瞬移, 并对其进行乱舞攻击。此时, 会在之前乱舞攻击过的敌人身上留下一个分身; 分身会在一定时间内持续攻击该敌人后消失。\n
- 后踢后, 可消耗连击点数发动[终结之击]。\n
- 在决斗场中, 强制中断基本攻击和技能动作发动时, 无法沿Y轴移动。
+ 快速接近前方敌人,并对该敌人进行狂风般的乱舞攻击。\n
+ 突进过程中,按上下方向键可以调整移动方向,成功抓住后进入无敌状态并强控敌人。\n
+ 乱舞攻击时,若按跳跃键,则会向周围的其他敌人瞬移,并对其进行乱舞攻击。此时,会在之前乱舞攻击过的敌人身上留下一个分身; 分身会在一定时间内持续攻击该敌人后消失。\n
+ 后踢后,可消耗连击点数发动[终结之击]。\n
+ 在决斗场中,强制中断基本攻击和技能动作发动时,无法沿Y轴移动。\n
"""
name = '疾风乱舞'
+ cellPos = (6,9)
learnLv = 40
masterLv = 60
maxLv = 70
- position = 6 # TODO
+ position = 6
rangeLv = 2
cube = 1
cd = 25
@@ -994,7 +1490,7 @@ class Skill36(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 乱舞攻击力 : {value0}% X {value1}
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1010,12 +1506,15 @@ class Skill36(ActiveSkill):
# [终结之击]攻击力 : {value5}%
data5 = get_data(f'{prefix}/{uuid}', 5)
hit5 = 1
- group5 = 'end'
# 瞬移距离上限 : {value6}px
data6 = get_data(f'{prefix}/{uuid}', 6)
- # [范围信息]
- # 范围比率 : {value7}%
+ # 压制力 (4人队伍) : {value7}秒 ({value8}秒)
data7 = get_data(f'{prefix}/{uuid}', 7)
+ data8 = get_data(f'{prefix}/{uuid}', 8)
+ # [范围信息]
+ # 范围比率 : {value9}%
+ data9 = get_data(f'{prefix}/{uuid}', 9)
+ group5 = 'end'
def vp_1(self):
"""
@@ -1036,27 +1535,27 @@ class Skill36(ActiveSkill):
"""
...
-
# 死亡风暴
# thief/rogue/c77a417c43de80c4ce32c1ed405d174a
# ddc49e9ad1ff72a00b53c6cff5b1e920/c77a417c43de80c4ce32c1ed405d174a
class Skill37(ActiveSkill):
"""
- 施放[剑刃风暴]时, 由风暴中心向外呈环形投掷匕首。
+ 施放[剑刃风暴]时,由风暴中心向外呈环形投掷匕首。\n
"""
name = '死亡风暴'
+ cellPos = (8,9)
learnLv = 40
masterLv = 60
maxLv = 70
- position = 8 # TODO
+ position = 8
type = 'passive'
rangeLv = 3
uuid = 'c77a417c43de80c4ce32c1ed405d174a'
hasVP = True
hasUP = False
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 匕首攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1094,23 +1593,23 @@ class Skill37(ActiveSkill):
"""
...
-
# 绝境瞬狱袭
# thief/rogue/128b9ddef2262f40723deae4407bdb42
# ddc49e9ad1ff72a00b53c6cff5b1e920/128b9ddef2262f40723deae4407bdb42
class Skill38(ActiveSkill):
"""
- 前冲突击使敌人浮空, 然后发动连续攻击。\n
- 前冲和连续攻击过程中, 自身进入无敌状态, 前冲攻击没有命中敌人时, 不会发动连续攻击。\n
- 可以在地上和空中发动, 第3击后可消耗连击点数发动[终结之击]。\n
- 在决斗场中, 无法在空中发动。
+ 前冲突击使敌人浮空,然后发动连续攻击。\n
+ 前冲和连续攻击过程中,自身进入无敌状态,前冲攻击没有命中敌人时,不会发动连续攻击。\n
+ 可以在地上和空中发动,第3击后可消耗连击点数发动[终结之击]。\n
+ 在决斗场中,无法在空中发动。\n
"""
name = '绝境瞬狱袭'
+ cellPos = (5,10)
learnLv = 45
masterLv = 60
maxLv = 70
- position = 5 # TODO
+ position = 5
rangeLv = 2
cube = 2
cd = 45
@@ -1119,7 +1618,7 @@ class Skill38(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 前冲突击攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1136,10 +1635,13 @@ class Skill38(ActiveSkill):
# [终结之击]攻击力 : {value4}%
data4 = get_data(f'{prefix}/{uuid}', 4)
hit4 = 1
- group4 = 'end'
- # [范围信息]
- # 范围比率 : {value5}%
+ # 压制力 (4人队伍) : {value5}秒 ({value6}秒)
data5 = get_data(f'{prefix}/{uuid}', 5)
+ data6 = get_data(f'{prefix}/{uuid}', 6)
+ # [范围信息]
+ # 范围比率 : {value7}%
+ data7 = get_data(f'{prefix}/{uuid}', 7)
+ group4 = 'end'
def vp_1(self):
"""
@@ -1164,20 +1666,20 @@ class Skill38(ActiveSkill):
"""
...
-
# 月弧
# thief/rogue/de3fea2d65c597f4d55c70a02b97fc79
# ddc49e9ad1ff72a00b53c6cff5b1e920/de3fea2d65c597f4d55c70a02b97fc79
class Skill39(PassiveSkill):
"""
- 获得连击点数时, 增加命中率、 回避率、 物理暴击率和暴击伤害, 效果持续一定时间。
+ 获得连击点数时,增加命中率、 回避率、 物理暴击率和暴击伤害,效果持续一定时间。\n
"""
name = '月弧'
+ cellPos = (6,11)
learnLv = 48
masterLv = 40
maxLv = 50
- position = 6 # TODO
+ position = 6
rangeLv = 3
uuid = 'de3fea2d65c597f4d55c70a02b97fc79'
hasVP = False
@@ -1197,22 +1699,22 @@ class Skill39(PassiveSkill):
associate = [{'data': data1}]
-
# 月轮舞
# thief/rogue/c61f5a010370101402b05b21916c2071
# ddc49e9ad1ff72a00b53c6cff5b1e920/c61f5a010370101402b05b21916c2071
class Skill40(ActiveSkill):
"""
- 施放[月轮舞]后, 会进入月舞模式并持续一定时间。 在月舞模式下, 增加攻击范围, 且基本攻击、 前冲攻击和跳跃攻击变成多段攻击; 获得连击点数时, 增加攻击速度和移动速度。\n
- 在月舞模式下, 基本攻击、 跳跃攻击和前冲攻击不受[基础精通]的影响。 增加攻击速度和移动速度的增益效果可以重叠一定次数。\n
- 施放技能动作中, 进入无敌状态; 可以在空中施放。
+ 施放[月轮舞]后,会进入月舞模式并持续一定时间。 在月舞模式下,增加攻击范围,且基本攻击、 前冲攻击和跳跃攻击变成多段攻击; 获得连击点数时,增加攻击速度和移动速度。\n
+ 在月舞模式下,基本攻击、 跳跃攻击和前冲攻击不受[基础精通]的影响。 增加攻击速度和移动速度的增益效果可以重叠一定次数。\n
+ 施放技能动作中,进入无敌状态; 可以在空中施放。\n
"""
name = '月轮舞'
+ cellPos = (5,11)
learnLv = 50
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 5
cube = 7
cd = 145
@@ -1235,23 +1737,23 @@ class Skill40(ActiveSkill):
# 每次重叠移动速度增加 : {value4}%
data4 = get_data(f'{prefix}/{uuid}', 4)
-
# 月舞终结
# thief/rogue/dec8961c485edb02036ba00c789010f0
# ddc49e9ad1ff72a00b53c6cff5b1e920/dec8961c485edb02036ba00c789010f0
class Skill41(ActiveSkill):
"""
- 可以在[月轮舞]增益状态下施放, 施放时发动强力连续斩击。\n
- 共发动4次斩击, 1~3次斩击命中时, 分身进行突进攻击后发动终结斩击。\n
- 被1~3段斩击命中的敌人会被强制控制并吸附过来, 并且可以在空中发动。\n
- 学习后, 技能等级补正为与[月轮舞]相同。
+ 可以在[月轮舞]增益状态下施放,施放时发动强力连续斩击。\n
+ 共发动4次斩击,1~3次斩击命中时,分身进行突进攻击后发动终结斩击。\n
+ 被1~3段斩击命中的敌人会被强制控制并吸附过来,并且可以在空中发动。\n
+ 学习后,技能等级补正为与[月轮舞]相同。\n
"""
name = '月舞终结'
+ cellPos = (5,12)
learnLv = 50
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 5
cd = 145
mp = [850, 7000]
@@ -1275,25 +1777,28 @@ class Skill41(ActiveSkill):
# 最后斩击攻击力 : {value4}%
data4 = get_data(f'{prefix}/{uuid}', 4)
hit4 = 1
-
+ # 压制力 (4人队伍) : {value5}秒 ({value6}秒)
+ data5 = get_data(f'{prefix}/{uuid}', 5)
+ data6 = get_data(f'{prefix}/{uuid}', 6)
# 旋刃冲击
# thief/rogue/6a1d1f08a6572be420bb3a256c44c015
# ddc49e9ad1ff72a00b53c6cff5b1e920/6a1d1f08a6572be420bb3a256c44c015
class Skill42(ActiveSkill):
"""
- 向前方投掷2个旋转刀刃。\n
- 旋转刀刃在一定时间内聚拢敌人, 造成多段伤害。\n
- 投掷出的旋转刀刃在触碰到敌人时停止移动, 若没有触碰到敌人, 则会移动至最大距离。\n
- 旋转刀刃持续期间, 可消耗连击点数发动[终结之击]。 发动[终结之击]时, 快速移动到第一次投掷的旋转刀刃的位置, 对敌人造成多段伤害。 多次施放技能后发动[终结之击]时, 移动到存在的旋转刀刃中最先投掷的刀锋位置。\n
- 在持续时间内, 即使发动[终结之击], 旋转刀刃也不会消失。
+ 向前方投掷2个旋转刀刃。\n
+ 旋转刀刃在一定时间内聚拢敌人,造成多段伤害。\n
+ 投掷出的旋转刀刃在触碰到敌人时停止移动,若没有触碰到敌人,则会移动至最大距离。\n
+ 旋转刀刃持续期间,可消耗连击点数发动[终结之击]。 发动[终结之击]时,快速移动到第一次投掷的旋转刀刃的位置,对敌人造成多段伤害。 多次施放技能后发动[终结之击]时,移动到存在的旋转刀刃中最先投掷的刀锋位置。\n
+ 在持续时间内,即使发动[终结之击],旋转刀刃也不会消失。\n
"""
name = '旋刃冲击'
+ cellPos = (7,12)
learnLv = 60
masterLv = 40
maxLv = 50
- position = 7 # TODO
+ position = 7
rangeLv = 2
cube = 2
cd = 30
@@ -1302,7 +1807,7 @@ class Skill42(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 旋转刀刃攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1316,11 +1821,11 @@ class Skill42(ActiveSkill):
# [终结之击]攻击力 : {value4}% X {value5}
data4 = get_data(f'{prefix}/{uuid}', 4)
hit4 = 5
- group4 = 'end'
data5 = get_data(f'{prefix}/{uuid}', 5)
# [范围信息]
# 范围比率 : {value6}%
data6 = get_data(f'{prefix}/{uuid}', 6)
+ group4 = 'end'
def vp_1(self):
"""
@@ -1351,22 +1856,22 @@ class Skill42(ActiveSkill):
"""
...
-
# 陨落螺旋刺
# thief/rogue/38612d8f2561edc2eb68d5057a837bfa
# ddc49e9ad1ff72a00b53c6cff5b1e920/38612d8f2561edc2eb68d5057a837bfa
class Skill43(ActiveSkill):
"""
- 跳跃后, 以极快的速度旋转并冲向地面, 落地时生成冲击波。\n
- 在地面发动时, 可以使用方向键调整落地位置; 在空中发动时, 立即向地面发动旋转攻击。\n
- 可消耗连击点数发动[终结之击]。
+ 跳跃后,以极快的速度旋转并冲向地面,落地时生成冲击波。\n
+ 在地面发动时,可以使用方向键调整落地位置; 在空中发动时,立即向地面发动旋转攻击。\n
+ 可消耗连击点数发动[终结之击]。\n
"""
name = '陨落螺旋刺'
+ cellPos = (3,13)
learnLv = 70
masterLv = 40
maxLv = 50
- position = 3 # TODO
+ position = 3
rangeLv = 2
cube = 3
cd = 50
@@ -1375,7 +1880,7 @@ class Skill43(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 旋转攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1416,21 +1921,21 @@ class Skill43(ActiveSkill):
"""
...
-
# 极限追击
# thief/rogue/0c3a468aee1f7ce06bf91eb3319518c1
# ddc49e9ad1ff72a00b53c6cff5b1e920/0c3a468aee1f7ce06bf91eb3319518c1
class Skill44(PassiveSkill):
"""
- 将身体能力强化到极致, 增加物理攻击力和连击点数上限, 并可以强制中断技能后施放。\n
- 强制中断技能的功能可以在已学的技能栏中, 通过鼠标右击进行开启/关闭切换。
+ 将身体能力强化到极致,增加物理攻击力和连击点数上限,并可以强制中断技能后施放。\n
+ 强制中断技能的功能可以在已学的技能栏中,通过鼠标右击进行开启/关闭切换。\n
"""
name = '极限追击'
+ cellPos = (5,13)
learnLv = 75
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 3
uuid = '0c3a468aee1f7ce06bf91eb3319518c1'
hasVP = False
@@ -1443,21 +1948,21 @@ class Skill44(PassiveSkill):
data1 = get_data(f'{prefix}/{uuid}', 1)
associate = [{'data': data0, 'type': '$*PAtkP'}]
-
# 乱空杀
# thief/rogue/5cac3411ccef1af333953e0ded5e942d
# ddc49e9ad1ff72a00b53c6cff5b1e920/5cac3411ccef1af333953e0ded5e942d
class Skill45(ActiveSkill):
"""
- 以极快的速度掠过敌人进行攻击。\n
- 下劈后, 可以通1过消耗连击点数使用[终结之击]。
+ 以极快的速度掠过敌人进行攻击。\n
+ 下劈后,可以通1过消耗连击点数使用[终结之击]。\n
"""
name = '乱空杀'
+ cellPos = (7,13)
learnLv = 75
masterLv = 40
maxLv = 50
- position = 7 # TODO
+ position = 7
rangeLv = 3
cube = 3
cd = 30
@@ -1466,7 +1971,7 @@ class Skill45(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 连续斩击攻击力 : {value0}% X {value1}
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1478,10 +1983,10 @@ class Skill45(ActiveSkill):
# [终结之击]攻击力 : {value3}%
data3 = get_data(f'{prefix}/{uuid}', 3)
hit3 = 1
- group3 = 'end'
# [范围信息]
# 范围比率 : {value4}%
data4 = get_data(f'{prefix}/{uuid}', 4)
+ group3 = 'end'
def vp_1(self):
"""
@@ -1507,21 +2012,21 @@ class Skill45(ActiveSkill):
"""
...
-
# 月影突袭
# thief/rogue/e1daab884dd07fc9e70d08b83d1790eb
# ddc49e9ad1ff72a00b53c6cff5b1e920/e1daab884dd07fc9e70d08b83d1790eb
class Skill46(ActiveSkill):
"""
- 向前方突进, 用肉眼看不见的速度多次斩击。\n
- 斩击可以将敌人聚集到中央。 多段斩击后, 可消耗连击点数发动[终结之击]。
+ 向前方突进,用肉眼看不见的速度多次斩击。\n
+ 斩击可以将敌人聚集到中央。 多段斩击后,可消耗连击点数发动[终结之击]。\n
"""
name = '月影突袭'
+ cellPos = (4,14)
learnLv = 80
masterLv = 40
maxLv = 50
- position = 4 # TODO
+ position = 4
rangeLv = 2
cube = 5
cd = 50
@@ -1530,7 +2035,7 @@ class Skill46(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 多段斩击攻击力 : {value0}% X {value1}
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1539,10 +2044,10 @@ class Skill46(ActiveSkill):
# [终结之击]攻击力 : {value2}%
data2 = get_data(f'{prefix}/{uuid}', 2)
hit2 = 1 # TODO
- group2 = 'end'
# [范围信息]
# 范围比率 : {value3}%
data3 = get_data(f'{prefix}/{uuid}', 3)
+ group2 = 'end'
def vp_1(self):
"""
@@ -1570,23 +2075,23 @@ class Skill46(ActiveSkill):
self.skillRation *= 1 - 0.5
...
-
# 天渊星狱
# thief/rogue/002cbdd9bfd0f0b970451ae8d48d029e
# ddc49e9ad1ff72a00b53c6cff5b1e920/002cbdd9bfd0f0b970451ae8d48d029e
class Skill47(ActiveSkill):
"""
- 化为一束星光快速造成多段攻击, 随后从空中坠下引起爆炸。\n
- 吸附多段攻击命中的敌人, 可以在空中施放。\n
- 对无法抓取的敌人不适用控制效果。\n
- 从空中坠下后, 可消耗连击点数发动[终结之击]。
+ 化为一束星光快速造成多段攻击,随后从空中坠下引起爆炸。\n
+ 吸附多段攻击命中的敌人,可以在空中施放。\n
+ 对无法抓取的敌人不适用控制效果。\n
+ 从空中坠下后,可消耗连击点数发动[终结之击]。\n
"""
name = '天渊星狱'
+ cellPos = (6,14)
learnLv = 85
masterLv = 40
maxLv = 50
- position = 6 # TODO
+ position = 6
rangeLv = 5
cube = 10
cd = 180
@@ -1607,22 +2112,22 @@ class Skill47(ActiveSkill):
hit2 = 1
group2 = 'end'
-
# 幻灭瞬杀
# thief/rogue/9376d04c476cd41d60ed1974ca69ab95
# ddc49e9ad1ff72a00b53c6cff5b1e920/9376d04c476cd41d60ed1974ca69ab95
class Skill48(ActiveSkill):
"""
- 瞬间释放超越身体极限的力量, 向前方突进。 突进时, 隐夜·暗星如残影攻击般快速攻击敌人。\n
- 前方范围内存在敌人时才能施放该技能, 可以在地上和空中发动。\n
- 突进后, 可以消耗连击点数发动[终结之击]。
+ 瞬间释放超越身体极限的力量,向前方突进。 突进时,隐夜·暗星如残影攻击般快速攻击敌人。\n
+ 前方范围内存在敌人时才能施放该技能,可以在地上和空中发动。\n
+ 突进后,可以消耗连击点数发动[终结之击]。\n
"""
name = '幻灭瞬杀'
+ cellPos = (7,15)
learnLv = 95
masterLv = 40
maxLv = 50
- position = 7 # TODO
+ position = 7
rangeLv = 2
cube = 7
cd = 60
@@ -1641,25 +2146,25 @@ class Skill48(ActiveSkill):
hit2 = 1
group2 = 'end'
-
# 绝命时刻
# thief/rogue/5536486eaf9b13c9a8283447cb5e77ab
# ddc49e9ad1ff72a00b53c6cff5b1e920/5536486eaf9b13c9a8283447cb5e77ab
class Skill49(PassiveSkill):
"""
- 激发超越身体极限的力量, 获得强大的攻击力和敌人无法感知的行动速度。\n
- 学习后, 增加基本攻击力和转职技能攻击力, [旋刃]和[剑刃风暴]技能附加特殊效果。\n
+ 激发超越身体极限的力量,获得强大的攻击力和敌人无法感知的行动速度。\n
+ 学习后,增加基本攻击力和转职技能攻击力,[旋刃]和[剑刃风暴]技能附加特殊效果。\n
[旋刃]\n
- 以极快的速度发动第1击、第2击和第3击。\n
+ 以极快的速度发动第1击、第2击和第3击。\n
[剑刃风暴]\n
- 增加移动速度和吸附力。
+ 增加移动速度和吸附力。\n
"""
name = '绝命时刻'
+ cellPos = (5,15)
learnLv = 95
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 3
uuid = '5536486eaf9b13c9a8283447cb5e77ab'
hasVP = False
@@ -1677,26 +2182,26 @@ class Skill49(PassiveSkill):
data3 = get_data(f'{prefix}/{uuid}', 3)
associate = [{'data': data0}]
-
# 影·万古星辰
# thief/rogue/ef9d26746effee9199b54541f01b8752
# ddc49e9ad1ff72a00b53c6cff5b1e920/ef9d26746effee9199b54541f01b8752
class Skill50(ActiveSkill):
"""
- 集中精神后, 暂时突破身体的极限, 将力量强化到极致。 然后, 凝聚星辰能量, 如光速般移动。\n
- 移动时, 隐夜·暗星以极快的速度对范围内最强的敌人进行多次旋转斩击。\n
- 可以在地上和空中施放, 施放时, 连击点数累积到上限。\n
- 可以在旋转斩击后, 消耗连击点数发动[终结之击]。\n
+ 集中精神后,暂时突破身体的极限,将力量强化到极致。 然后,凝聚星辰能量,如光速般移动。\n
+ 移动时,隐夜·暗星以极快的速度对范围内最强的敌人进行多次旋转斩击。\n
+ 可以在地上和空中施放,施放时,连击点数累积到上限。\n
+ 可以在旋转斩击后,消耗连击点数发动[终结之击]。\n
[三次觉醒技能]\n
- 使用三次觉醒技能时, 与关联的技能共享冷却时间。\n
- 若关联的技能还在冷却中, 则无法使用三次觉醒技能。
+ 使用三次觉醒技能时,与关联的技能共享冷却时间。\n
+ 若关联的技能还在冷却中,则无法使用三次觉醒技能。\n
"""
name = '影·万古星辰'
+ cellPos = (5,16)
learnLv = 100
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 5
cube = 15
cd = 290
@@ -1715,7 +2220,6 @@ class Skill50(ActiveSkill):
hit2 = 1
group2 = 'end'
-
class classChange(Character):
def __init__(self, equVersion):
diff --git a/backend/core/character/GF/thief/shadow_dancer.py b/backend/core/character/GF/thief/shadow_dancer.py
index d1813a34..8020092f 100644
--- a/backend/core/character/GF/thief/shadow_dancer.py
+++ b/backend/core/character/GF/thief/shadow_dancer.py
@@ -2,7 +2,100 @@
from core.basic.skill import PassiveSkill, ActiveSkill, get_data
from core.basic.character import Character
-prefix = 'thief/shadow_dancer/cn/skillDetail'
+prefix = "thief/shadow_dancer/cn/skillDetail"
+
+
+# 翔击
+# thief/shadow_dancer/3c5604bdbb0240b8f130f59ab40509c3
+# ddc49e9ad1ff72a00b53c6cff5b1e920/3c5604bdbb0240b8f130f59ab40509c3
+# MIGRATION: character 中没有同名技能“翔击”,暂不启用
+# class Skill0(ActiveSkill):
+# """
+# 向敌人发出向上的切割攻击,并使敌人浮空。\n
+# 在空中也可以使用[翔击],且按住技能键的时间越长,[翔击]的跳跃高度就越高。\n
+# 可消耗连击点数发动[终结之击]。\n
+# """
+# name = "翔击"
+# learnLv = 1
+# masterLv = 10
+# maxLv = 20
+# cellPos = (5,1)
+# position = 5
+# rangeLv = 3
+# cd = 2
+# mp = [0, 0]
+# uuid = "3c5604bdbb0240b8f130f59ab40509c3"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 浮空力比率 : {value1}%
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # [终结之击]攻击力 : {value2}% X {value3}
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+# # [终结之击]浮空力比率 : {value4}%
+# data4 = get_data(f'{prefix}/{uuid}', 4)
+
+# 投掷苦无
+# thief/shadow_dancer/506e7ed77d517419a6e1c437a2cedb17
+# ddc49e9ad1ff72a00b53c6cff5b1e920/506e7ed77d517419a6e1c437a2cedb17
+# MIGRATION: character 中没有同名技能“投掷苦无”,暂不启用
+# class Skill1(ActiveSkill):
+# """
+# 向敌人投掷苦无,使远距离的敌人受到无属性物理伤害。\n
+# 苦无具有略微的诱导性和穿透力,可以在跳跃状态中投掷。\n
+# 技能等级越高,被苦无命中的敌人的僵直时间越长。\n
+# """
+# name = "投掷苦无"
+# learnLv = 1
+# masterLv = 60
+# maxLv = 70
+# cellPos = (3,1)
+# position = 3
+# rangeLv = 2
+# cd = 2.5
+# mp = [8, 91]
+# uuid = "506e7ed77d517419a6e1c437a2cedb17"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 苦无投掷个数 : {value1}个
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 僵直时间增加率 : {value2}%
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 最大穿透次数 : {value3}次
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+# # [范围信息]
+# # 苦无大小 : {value4}%
+# data4 = get_data(f'{prefix}/{uuid}', 4)
+
+# 后跳
+# thief/shadow_dancer/7822d6d52e10964a6755f142c666b494
+# ddc49e9ad1ff72a00b53c6cff5b1e920/7822d6d52e10964a6755f142c666b494
+# MIGRATION: character 中没有同名技能“后跳”,暂不启用
+# class Skill2(ActiveSkill):
+# """
+# 使自身向后方小跳并避开敌人的攻击。\n
+# """
+# name = "后跳"
+# learnLv = 1
+# masterLv = 1
+# maxLv = 1
+# cellPos = (3,0)
+# position = 3
+# rangeLv = None
+# cd = []
+# uuid = "7822d6d52e10964a6755f142c666b494"
+# icon = "$common/$uuid"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
# 基础精通
@@ -10,15 +103,16 @@ prefix = 'thief/shadow_dancer/cn/skillDetail'
# ddc49e9ad1ff72a00b53c6cff5b1e920/5a56514f35cf0270ae8d6c65f8fefd78
class Skill3(PassiveSkill):
"""
- 增加基本攻击、 前冲攻击、 跳跃攻击、 [翔击]的攻击力。\n
- 在决斗场中, 增益/减益技能、 被动技能的技能攻击力增加效果对[基础精通]无影响。
+ 增加基本攻击、 前冲攻击、 跳跃攻击、 [翔击]的攻击力。\n
+ 在决斗场中,增益/减益技能、 被动技能的技能攻击力增加效果对[基础精通]无影响。\n
"""
name = '基础精通'
+ cellPos = (1,0)
learnLv = 1
masterLv = 115
maxLv = 200
- position = 1 # TODO
+ position = 1
rangeLv = 1
uuid = '5a56514f35cf0270ae8d6c65f8fefd78'
icon = '$common/$uuid'
@@ -33,21 +127,109 @@ class Skill3(PassiveSkill):
# 跳跃攻击力变化率 : {value2}%
data2 = get_data(f'{prefix}/{uuid}', 2)
+# 受身蹲伏
+# thief/shadow_dancer/ce26c6b69d02a440a81b552bec94f03b
+# ddc49e9ad1ff72a00b53c6cff5b1e920/ce26c6b69d02a440a81b552bec94f03b
+# MIGRATION: character 中没有同名技能“受身蹲伏”,暂不启用
+# class Skill4(ActiveSkill):
+# """
+# 使自身在倒地状态下迅速起身并采取蹲伏姿势; 蹲伏状态下,自身会进入无敌状态,效果持续一定时间。\n
+# """
+# name = "受身蹲伏"
+# learnLv = 1
+# masterLv = 10
+# maxLv = 20
+# cellPos = (2,0)
+# position = 2
+# rangeLv = None
+# cd = 5
+# mp = [1, 1]
+# uuid = "ce26c6b69d02a440a81b552bec94f03b"
+# icon = "$common/$uuid"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 蹲伏姿势最短无敌时间 : {value0}秒
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 蹲伏姿势最长无敌时间 : {value1}秒
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 起身时霸体时间 : {value2}秒
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+
+# 技能连招
+# thief/shadow_dancer/10b31c7e8cd2d60b9bcf3b1d3ada4b0a
+# ddc49e9ad1ff72a00b53c6cff5b1e920/10b31c7e8cd2d60b9bcf3b1d3ada4b0a
+# MIGRATION: character 中没有同名技能“技能连招”,暂不启用
+# class Skill5(ActiveSkill):
+# """
+# 可以最多指定4个角色技能进行排列组合,并通过一个技能栏快捷键施放。\n
+# 点击技能连招下方的图标,可以设置技能顺序和调整初始化时间。\n
+# - 可将技能连招图标设置到技能栏快捷键中使用。\n
+# - 仅限在城镇及修炼场中进行设置。\n
+# 按顺序施放添加在连招栏位中的技能。\n
+# - 具有再输入功能的技能,需结束所有再输入后,再切换至下一个技能。\n
+# - 若所有技能均在冷却中,则显示冷却时间最短的技能。\n
+# 当前序部署的技能冷却结束后,经过一定时间,将切换至该前序技能。\n
+# - 切换至前序技能的时间可自行调整。 (0~5秒)\n
+# - 设置为0秒时,技能冷却结束后将立即切换至前序部署的技能。\n
+# - 设置为0秒时,可重复使用填充型技能及无冷却时间的技能。\n
+# 包括觉醒技能在内的部分特殊技能将被排除在目标技能列表外,无法添加到技能连招中。\n
+# 决斗场中无法使用技能连招。\n
+# """
+# name = "技能连招"
+# learnLv = 1
+# masterLv = 1
+# maxLv = 1
+# cellPos = (8,0)
+# position = 8
+# rangeLv = None
+# cd = []
+# uuid = "10b31c7e8cd2d60b9bcf3b1d3ada4b0a"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+
+
+# 防具精通
+# thief/shadow_dancer/892ef624d8bf3d7fc045f84825fd6104
+# ddc49e9ad1ff72a00b53c6cff5b1e920/892ef624d8bf3d7fc045f84825fd6104
+# MIGRATION: character 中没有同名技能“防具精通”,暂不启用
+# class Skill6(PassiveSkill):
+# """
+# 穿戴防具时,可以增加各种属性。\n
+# 穿戴的防具越多,效果越强; 可根据转职,增加不同的属性种类及数值。\n
+# """
+# name = "防具精通"
+# learnLv = 1
+# masterLv = 1
+# maxLv = 1
+# cellPos = (0,1)
+# position = 0
+# rangeLv = None
+# cd = []
+# uuid = "892ef624d8bf3d7fc045f84825fd6104"
+# icon = "$common/$uuid"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+
# 切割
# thief/shadow_dancer/9dda3f4a849dba1a288dd65e116860f2
# ddc49e9ad1ff72a00b53c6cff5b1e920/9dda3f4a849dba1a288dd65e116860f2
class Skill6(ActiveSkill):
"""
- 前冲攻击中输入攻击键时, 施放追加斩击。\n
- 攻击力、 攻击范围、 前进速度和前进距离随技能等级增加而增大。
+ 前冲攻击中输入攻击键时,施放追加斩击。\n
+ 攻击力、 攻击范围、 前进速度和前进距离随技能等级增加而增大。\n
"""
name = '切割'
+ cellPos = (3,2)
learnLv = 5
masterLv = 1
maxLv = 6
- position = 3 # TODO
+ position = 3
rangeLv = 2
cd = 2.5
mp = [6, 56]
@@ -65,22 +247,55 @@ class Skill6(ActiveSkill):
# 攻击范围比率 : {value2}%
data2 = get_data(f'{prefix}/{uuid}', 2)
+# 焰刃
+# thief/shadow_dancer/eb71e1d82d92c7e1d40500a0dcd77aa6
+# ddc49e9ad1ff72a00b53c6cff5b1e920/eb71e1d82d92c7e1d40500a0dcd77aa6
+# MIGRATION: character 中没有同名技能“焰刃”,暂不启用
+# class Skill8(ActiveSkill):
+# """
+# 火属性近身魔法技能; 聚集火焰的力量对敌人进行强力攻击并把敌人击飞。\n
+# 攻击力、 击飞力、 发动速度和攻击范围随技能等级增加而增大。\n
+# """
+# name = "焰刃"
+# learnLv = 5
+# masterLv = 60
+# maxLv = 70
+# cellPos = (1,2)
+# position = 1
+# rangeLv = 2
+# cd = 5
+# mp = [15, 168]
+# uuid = "eb71e1d82d92c7e1d40500a0dcd77aa6"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 击飞力比率 : {value1}%
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 发动速度增加率 : {value2}%
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # [范围信息]
+# # 范围 : {value3}%
+# data3 = get_data(f'{prefix}/{uuid}', 3)
# 影袭
# thief/shadow_dancer/c9664191611af31142e052dfaef84530
# ddc49e9ad1ff72a00b53c6cff5b1e920/c9664191611af31142e052dfaef84530
class Skill8(ActiveSkill):
"""
- 以无视敌人硬直的斩击攻击敌人, 使敌人受到无属性物理伤害的同时进入僵直状态; 之后移动到敌人身后, 伺机进行下一次攻击。\n
- 转职为影舞者时, 可以强制控制敌人。\n
- 在决斗场中, 无法强制控制敌人, 但是相比其他暗夜使者, 可以使敌人进入更长时间的僵直状态。
+ 以无视敌人硬直的斩击攻击敌人,使敌人受到无属性物理伤害的同时进入僵直状态; 之后移动到敌人身后,伺机进行下一次攻击。\n
+ 转职为影舞者时,可以强制控制敌人。\n
+ 在决斗场中,无法强制控制敌人,但是相比其他暗夜使者,可以使敌人进入更长时间的僵直状态。\n
"""
name = '影袭'
+ cellPos = (5,2)
learnLv = 5
masterLv = 60
maxLv = 70
- position = 5 # TODO
+ position = 5
rangeLv = 2
cd = 6
mp = [15, 168]
@@ -97,28 +312,110 @@ class Skill8(ActiveSkill):
# - [转职为影舞者时] -
# 僵直时间比率 : {value2}%
data2 = get_data(f'{prefix}/{uuid}', 2)
+ # 压制力 (4人队伍) : {value3}秒 ({value4}秒)
+ data3 = get_data(f'{prefix}/{uuid}', 3)
+ data4 = get_data(f'{prefix}/{uuid}', 4)
+# 暗魂波
+# thief/shadow_dancer/ade01c1d6afc8a05055225045e89fe49
+# ddc49e9ad1ff72a00b53c6cff5b1e920/ade01c1d6afc8a05055225045e89fe49
+# MIGRATION: character 中没有同名技能“暗魂波”,暂不启用
+# class Skill10(ActiveSkill):
+# """
+# 向敌人发射暗属性球体。\n
+# 球体会自动寻找敌人,发现敌人后对敌人造成伤害,最后球体爆炸。\n
+# - [转职为黑夜术士后附加效果] -\n
+# 施放[降临 : 暴君巴拉克]的状态下,可以蓄气施放[暗魂波]。\n
+# 蓄气施放[暗魂波]时,球体变大且增加攻击力。\n
+# 在决斗场中,地图内球体的存在时间减少。\n
+# """
+# name = "暗魂波"
+# learnLv = 5
+# masterLv = 60
+# maxLv = 70
+# cellPos = (7,2)
+# position = 7
+# rangeLv = 2
+# cd = 3.5
+# mp = [15, 161]
+# uuid = "ade01c1d6afc8a05055225045e89fe49"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 多段攻击次数上限 : {value0}次
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 多段攻击攻击力 : {value1}%
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 爆炸攻击力 : {value2}%
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 诱导力比率 : {value3}%
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+# # [转职为黑夜术士后]
+# # 蓄气时攻击力增加率 : {value4}%
+# data4 = get_data(f'{prefix}/{uuid}', 4)
+# # 地图内球体存在时间 : {value5}秒
+# data5 = get_data(f'{prefix}/{uuid}', 5)
+# # [冥河之钥]
+# # 多段攻击力 : {value6}%
+# data6 = get_data(f'{prefix}/{uuid}', 6)
+
+# 弧光闪
+# thief/shadow_dancer/0969cd4054d93da07708108c0cc1c4b5
+# ddc49e9ad1ff72a00b53c6cff5b1e920/0969cd4054d93da07708108c0cc1c4b5
+# MIGRATION: character 中没有同名技能“弧光闪”,暂不启用
+# class Skill11(ActiveSkill):
+# """
+# 急速冲向前方的敌人并对其进行闪电般的攻击。\n
+# 按上下方向键可向Y轴移动; 若急速前冲过程中按跳跃键,则可停止移动。\n
+# 暗星可以连续发动两次[弧光闪]; 可在空中施放[弧光闪]; 可强制中断转职技能,并施放[弧光闪]; 可消耗连击点数发动可控制左右方向的[终结之击]; 学习[弧光连闪]后,增加追加攻击的可发动次数。\n
+# 转职为影舞者后,可以强制中断技能并施放[追影步]; 学习[潜行教义]后,可以再次按技能键发动追加攻击。\n
+# 可以利用方向键调整移动方向。\n
+# """
+# name = "弧光闪"
+# learnLv = 10
+# masterLv = 60
+# maxLv = 70
+# cellPos = (3,3)
+# position = 3
+# rangeLv = 2
+# cd = 6
+# mp = [18, 190]
+# uuid = "0969cd4054d93da07708108c0cc1c4b5"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # [终结之击]攻击力 : {value1}%
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 发动速度比率 : {value2}%
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 僵直时间比率 : {value3}%
+# data3 = get_data(f'{prefix}/{uuid}', 3)
# 骨盾
# thief/shadow_dancer/ff171dc487807bb9aa28900ca9a46b41
# ddc49e9ad1ff72a00b53c6cff5b1e920/ff171dc487807bb9aa28900ca9a46b41
class Skill11(ActiveSkill):
"""
- 在自身或队员周围召唤由骨头碎片形成的骨盾, 可以增加被骨盾守护的角色的物理防御力和魔法防御力。\n
- 该角色被攻击时, 骨头碎片会自动飞向敌人, 并使敌人受到无属性魔法伤害。\n
- 每次被攻击时, 骨头碎片会逐块消失。 当骨头碎片全部消失时, 增加的物理防御力和魔法防御力效果就会消失。\n
+ 在自身或队员周围召唤由骨头碎片形成的骨盾,可以增加被骨盾守护的角色的物理防御力和魔法防御力。\n
+ 该角色被攻击时,骨头碎片会自动飞向敌人,并使敌人受到无属性魔法伤害。\n
+ 每次被攻击时,骨头碎片会逐块消失。 当骨头碎片全部消失时,增加的物理防御力和魔法防御力效果就会消失。\n
[转职为黑夜术士后功能变更]\n
- 减少骨盾的持续时间。\n
- 学习后减少所受伤害, 施放技能后被攻击一定次数内附加霸体护甲。\n
- 可以在其他动作中施放, 不受冷却时间减少效果的影响。\n
- 在决斗场中, 不适用持续时间减少和霸体护甲效果, 施放技能时减少所受伤害。
+ 减少骨盾的持续时间。\n
+ 学习后减少所受伤害,施放技能后被攻击一定次数内附加霸体护甲。\n
+ 可以在其他动作中施放,不受冷却时间减少效果的影响。\n
+ 在决斗场中,不适用持续时间减少和霸体护甲效果,施放技能时减少所受伤害。\n
"""
name = '骨盾'
+ cellPos = (1,3)
learnLv = 10
masterLv = 1
maxLv = 7
- position = 1 # TODO
+ position = 1
rangeLv = 3
cd = 15
mp = [20, 400]
@@ -147,21 +444,228 @@ class Skill11(ActiveSkill):
# 霸体护甲生效次数 : {value7}次
data7 = get_data(f'{prefix}/{uuid}', 7)
+# 忍法 : 影分身
+# thief/shadow_dancer/a5fa08f5d509e6ff2ebc68856a470b5a
+# ddc49e9ad1ff72a00b53c6cff5b1e920/a5fa08f5d509e6ff2ebc68856a470b5a
+# MIGRATION: character 中没有同名技能“忍法 : 影分身”,暂不启用
+# class Skill13(ActiveSkill):
+# """
+# 把自己的影分身留在原地的同时,向空中瞬移,可用左右方向键控制瞬移地点。\n
+# 被攻击时,也可以使用此技能。\n
+# 留在地上的影分身会挑衅一定领域内的敌人; 影分身经过一段时间或受到一定伤害后,会爆炸并使敌人受到无属性魔法伤害。\n
+# """
+# name = "忍法 : 影分身"
+# learnLv = 10
+# masterLv = 40
+# maxLv = 50
+# cellPos = (9,4)
+# position = 9
+# rangeLv = 3
+# cd = [30, 29.387755, 28.77551, 28.163265, 27.55102, 26.938776, 26.326531, 25.714286, 25.102041, 24.489796, 23.877551, 23.265306, 22.653061, 22.040816, 21.428571, 20.816327, 20.204082, 19.591837, 18.979592, 18.367347, 17.755102, 17.142857, 16.530612, 15.918367, 15.306122, 14.693878, 14.081633, 13.469388, 12.857143, 12.244898, 11.632653, 11.020408, 10.408163, 9.795918, 9.183673, 8.571429, 7.959184, 7.346939, 6.734694, 6.122449, 5.510204, 4.897959, 4.285714, 3.673469, 3.061224, 2.44898, 1.836735, 1.22449, 0.612245, 0]
+# mp = [20, 231]
+# uuid = "a5fa08f5d509e6ff2ebc68856a470b5a"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 分身生命值 : {value1}
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 分身持续时间 : {value2}秒
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 挑衅范围 : {value3}px
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+
+# 碎踝
+# thief/shadow_dancer/5dc7008b12a459325b548b0715c6b73c
+# ddc49e9ad1ff72a00b53c6cff5b1e920/5dc7008b12a459325b548b0715c6b73c
+# MIGRATION: character 中没有同名技能“碎踝”,暂不启用
+# class Skill14(ActiveSkill):
+# """
+# 用强力的扫腿踢攻击敌人的脚踝。\n
+# 被扫腿踢命中的敌人将受到无属性物理伤害,且有一定几率进入减速状态。\n
+# """
+# name = "碎踝"
+# learnLv = 10
+# masterLv = 60
+# maxLv = 70
+# cellPos = (7,3)
+# position = 7
+# rangeLv = 2
+# cd = 7
+# mp = [15, 168]
+# uuid = "5dc7008b12a459325b548b0715c6b73c"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 减速几率 : {value1}%
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 减速持续时间 : {value2}秒
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 移动速度减少率 : {value3}%
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+# # 攻击速度减少率 : {value4}%
+# data4 = get_data(f'{prefix}/{uuid}', 4)
+
+# 强化 - 后跳
+# thief/shadow_dancer/2b340542e776818b78f3212af184bd6b
+# ddc49e9ad1ff72a00b53c6cff5b1e920/2b340542e776818b78f3212af184bd6b
+# MIGRATION: character 中没有同名技能“强化 - 后跳”,暂不启用
+# class Skill15(PassiveSkill):
+# """
+# 施放技能期间、 被击或倒地的状态下,可以施放无敌状态的[后跳]。\n
+# 该能力适用与[后跳]不同的冷却时间,并且不受冷却时间减少效果的影响。\n
+# 根据施放情况的不同(强制中断技能和被敌人攻击),进入不同冷却时间。\n
+# 无法强制中断觉醒技能和跳跃超过一定高度的技能。\n
+# """
+# name = "强化 - 后跳"
+# learnLv = 10
+# masterLv = 1
+# maxLv = 1
+# cellPos = (4,0)
+# position = 4
+# rangeLv = None
+# cd = 30
+# uuid = "2b340542e776818b78f3212af184bd6b"
+# icon = "$common/$uuid"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 强制中断技能时的冷却时间 : {value0}秒
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 被击或倒地期间施放时的冷却时间 : {value1}秒
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+
+# 跃翔
+# thief/shadow_dancer/1fea5a626f15230237946a11a9d11582
+# ddc49e9ad1ff72a00b53c6cff5b1e920/1fea5a626f15230237946a11a9d11582
+# MIGRATION: character 中没有同名技能“跃翔”,暂不启用
+# class Skill16(ActiveSkill):
+# """
+# 增加自身20%的跳跃力,效果持续一定时间。\n
+# 效果持续期间内,再次按技能键可以结束。\n
+# """
+# name = "跃翔"
+# learnLv = 10
+# masterLv = 1
+# maxLv = 1
+# cellPos = (6,0)
+# position = 6
+# rangeLv = 3
+# cd = 5
+# mp = [13, 13]
+# uuid = "1fea5a626f15230237946a11a9d11582"
+# icon = "$common/$uuid"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 持续时间 : {value0}秒
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+
+# 疾空踏
+# thief/shadow_dancer/717f1e2104fe4b796f800352fa143ecc
+# ddc49e9ad1ff72a00b53c6cff5b1e920/717f1e2104fe4b796f800352fa143ecc
+# MIGRATION: character 中没有同名技能“疾空踏”,暂不启用
+# class Skill17(ActiveSkill):
+# """
+# 从空中快速下降并向敌人发出连续踢击。\n
+# 连续踢击结束前,输入Z键可以发动最后一击。\n
+# 只能在空中使用,且在下落过程中有敌人时才能施放连续踢击。\n
+# 终结攻击会引发冲击波,被命中的敌人会被击飞。\n
+# 连续踢击过程中连续按下攻击键时,会增加连续踢击的速度和命中次数。\n
+# """
+# name = "疾空踏"
+# learnLv = 15
+# masterLv = 60
+# maxLv = 70
+# cellPos = (1,4)
+# position = 1
+# rangeLv = 2
+# cd = 6.5
+# mp = [22, 235]
+# uuid = "717f1e2104fe4b796f800352fa143ecc"
+# hasVP = False
+# hasUP = True
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 连续踢击攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 连续踢击次数 : {value1}次~{value2}次
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 最后一击攻击力 : {value3}%
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+# # 冲击波攻击力 : {value4}%
+# data4 = get_data(f'{prefix}/{uuid}', 4)
+# # 最后一击浮空力比率 : {value5}%
+# data5 = get_data(f'{prefix}/{uuid}', 5)
+# # [范围信息]
+# # 冲击波范围比率 : {value6}%
+# data6 = get_data(f'{prefix}/{uuid}', 6)
+
+# 忍法 : 飞鼠
+# thief/shadow_dancer/4655101518604f874721b3cc249aae10
+# ddc49e9ad1ff72a00b53c6cff5b1e920/4655101518604f874721b3cc249aae10
+# MIGRATION: character 中没有同名技能“忍法 : 飞鼠”,暂不启用
+# class Skill18(ActiveSkill):
+# """
+# 快速跳向敌人并引爆炸弹,给予敌人火属性魔法伤害的同时使敌人浮空。\n
+# 该技能可以在地上和空中使用,且命中敌人后可以进行追击。\n
+# 攻击力和爆炸范围随攻击次数递增,最后一击时增幅更大。\n
+# """
+# name = "忍法 : 飞鼠"
+# learnLv = 15
+# masterLv = 60
+# maxLv = 70
+# cellPos = (0,4)
+# position = 0
+# rangeLv = 2
+# cd = 10
+# mp = [10, 84]
+# uuid = "4655101518604f874721b3cc249aae10"
+# hasVP = False
+# hasUP = True
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 使用次数上限 : {value1}次
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 最后一击浮空力比率 : {value2}%
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 每次追击时攻击力增加率 : {value3}%
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+# # 最后一击时攻击力增加率 : {value4}%
+# data4 = get_data(f'{prefix}/{uuid}', 4)
+# # 每次追击时爆炸范围增加率 : {value5}%
+# data5 = get_data(f'{prefix}/{uuid}', 5)
+# # 最后一击爆炸范围增加率 : {value6}%
+# data6 = get_data(f'{prefix}/{uuid}', 6)
+# # [范围信息]
+# # 爆炸范围比率 : {value7}%
+# data7 = get_data(f'{prefix}/{uuid}', 7)
# 天诛
# thief/shadow_dancer/f2fb27162beb0b87a7cb9af7900e95f2
# ddc49e9ad1ff72a00b53c6cff5b1e920/f2fb27162beb0b87a7cb9af7900e95f2
class Skill18(ActiveSkill):
"""
- 抓取一名敌人后向其头部进行刺击, 被刺击命中的敌人将受到无属性伤害。\n
- 若[天诛]背击敌人时, 则会追加一次刺击, 且最后还会追加一次高伤害的切割攻击。
+ 抓取一名敌人后向其头部进行刺击,被刺击命中的敌人将受到无属性伤害。\n
+ 若[天诛]背击敌人时,则会追加一次刺击,且最后还会追加一次高伤害的切割攻击。\n
"""
name = '天诛'
+ cellPos = (5,3)
learnLv = 15
masterLv = 60
maxLv = 70
- position = 5 # TODO
+ position = 5
line = 10
rangeLv = 2
cd = 7
@@ -179,25 +683,61 @@ class Skill18(ActiveSkill):
# 切割攻击攻击力 : {value2}%
data2 = get_data(f'{prefix}/{uuid}', 2)
hit2 = 1
+ # 压制力 (4人队伍) : {value3}秒 ({value4}秒)
+ data3 = get_data(f'{prefix}/{uuid}', 3)
+ data4 = get_data(f'{prefix}/{uuid}', 4)
+# 诅咒之箭
+# thief/shadow_dancer/547ab2b2bd860d3e37355a9cfbc1077c
+# ddc49e9ad1ff72a00b53c6cff5b1e920/547ab2b2bd860d3e37355a9cfbc1077c
+# MIGRATION: character 中没有同名技能“诅咒之箭”,暂不启用
+# class Skill20(ActiveSkill):
+# """
+# 召唤暗属性的诅咒之箭后,向前方敌人发射; 被诅咒之箭命中的敌人会受到暗属性多段魔法伤害,并有一定几率进入诅咒状态。\n
+# """
+# name = "诅咒之箭"
+# learnLv = 15
+# masterLv = 60
+# maxLv = 70
+# cellPos = (10,4)
+# position = 10
+# rangeLv = 2
+# cd = 10
+# mp = [22, 235]
+# uuid = "547ab2b2bd860d3e37355a9cfbc1077c"
+# hasVP = False
+# hasUP = True
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 攻击力 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
+# # 发射数量 : {value1}发
+# data1 = get_data(f'{prefix}/{uuid}', 1)
+# # 诅咒几率 : {value2}%
+# data2 = get_data(f'{prefix}/{uuid}', 2)
+# # 诅咒持续时间 : {value3}秒
+# data3 = get_data(f'{prefix}/{uuid}', 3)
+# # 多段攻击次数上限 : {value4}
+# data4 = get_data(f'{prefix}/{uuid}', 4)
# 潜行教义
# thief/shadow_dancer/e0daa922b19cdc35de879e938361464e
# ddc49e9ad1ff72a00b53c6cff5b1e920/e0daa922b19cdc35de879e938361464e
class Skill20(PassiveSkill):
"""
- 影舞者特有的潜行术。 进行背后攻击时, 敌人的被击方向不会改变, 强控敌人时, 敌人的方向也不会改变。 若技能的首次攻击判定为从背后命中敌人, 则该技能施放结束为止都维持背击判定。 另外, 基本攻击、 [弧光闪]、 [天诛]、 [碎踝]的攻击效果会产生变化, 且增加暴击伤害。\n
- [弧光闪] : 移动速度不变, 通过追加操作可以进行一次追加攻击。 使用[弧光闪]进行背后攻击时, 敌人会转向攻击的方向。\n
- [天诛] : 变更最后一击的特效, 并且对无法抓取的敌人进行踢腿攻击。\n
+ 影舞者特有的潜行术。 进行背后攻击时,敌人的被击方向不会改变,强控敌人时,敌人的方向也不会改变。 若技能的首次攻击判定为从背后命中敌人,则该技能施放结束为止都维持背击判定。 另外,基本攻击、 [弧光闪]、 [天诛]、 [碎踝]的攻击效果会产生变化,且增加暴击伤害。\n
+ [弧光闪] : 移动速度不变,通过追加操作可以进行一次追加攻击。 使用[弧光闪]进行背后攻击时,敌人会转向攻击的方向。\n
+ [天诛] : 变更最后一击的特效,并且对无法抓取的敌人进行踢腿攻击。\n
[碎踝] : 使被击中的敌人向后回头。\n
- 影舞者基本攻击第三击 : 使被击中的敌人向后回头。
+ 影舞者基本攻击第三击 : 使被击中的敌人向后回头。\n
"""
name = '潜行教义'
+ cellPos = (3,4)
learnLv = 15
masterLv = 1
maxLv = 11
- position = 3 # TODO
+ position = 3
rangeLv = 3
uuid = 'e0daa922b19cdc35de879e938361464e'
hasVP = False
@@ -208,20 +748,20 @@ class Skill20(PassiveSkill):
data0 = get_data(f'{prefix}/{uuid}', 0)
associate = [{'data': data0}]
-
# 追影步
# thief/shadow_dancer/0fbb8de70002ad34f046c94c2cb3e863
# ddc49e9ad1ff72a00b53c6cff5b1e920/0fbb8de70002ad34f046c94c2cb3e863
class Skill21(ActiveSkill):
"""
- 向前快速移动, 若移动途中碰到敌人, 将会移动到敌人的背后。 施放该技能时, 可以强制中断某些指定技能的施放, 某些技能被强制中断时, 会制造暗袭分身完成未完成的动作。
+ 向前快速移动,若移动途中碰到敌人,将会移动到敌人的背后。 施放该技能时,可以强制中断某些指定技能的施放,某些技能被强制中断时,会制造暗袭分身完成未完成的动作。\n
"""
name = '追影步'
+ cellPos = (7,4)
learnLv = 15
masterLv = 1
maxLv = 1
- position = 7 # TODO
+ position = 7
rangeLv = 1
cd = 3
mp = [50, 50]
@@ -230,20 +770,22 @@ class Skill21(ActiveSkill):
hasUP = False
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
+ # 1 99
# 撕裂
# thief/shadow_dancer/c5a2956d8ed3af1746ed2f76ca971a09
# ddc49e9ad1ff72a00b53c6cff5b1e920/c5a2956d8ed3af1746ed2f76ca971a09
class Skill22(ActiveSkill):
"""
- 使用武器向下攻击敌人。 如果命中敌人背部, 将会进行追加攻击。 此技能可在空中使用, 在空中使用时, 会垂直下落并向下攻击敌人。 技能命中敌人后, 使用[挑空]可以与其他技能形成连招。
+ 使用武器向下攻击敌人。 如果命中敌人背部,将会进行追加攻击。 此技能可在空中使用,在空中使用时,会垂直下落并向下攻击敌人。 技能命中敌人后,使用[挑空]可以与其他技能形成连招。\n
"""
name = '撕裂'
+ cellPos = (5,4)
learnLv = 15
masterLv = 60
maxLv = 70
- position = 5 # TODO
+ position = 5
rangeLv = 2
cd = 6
mp = [30, 252]
@@ -259,49 +801,50 @@ class Skill22(ActiveSkill):
data1 = get_data(f'{prefix}/{uuid}', 1)
hit1 = 1
-
# 潜行之匕首精通
# thief/shadow_dancer/e5c09f9132a48dc1d695968592cc5878
# ddc49e9ad1ff72a00b53c6cff5b1e920/e5c09f9132a48dc1d695968592cc5878
class Skill23(PassiveSkill):
"""
- 增加武器物理攻击力, 使用匕首系列武器时, 增加命中率。 攻击时对敌人造成的僵直时间提高至双剑水准。\n
- 在决斗场中, 不会增加僵直时间。
+ 增加物理攻击力,使用匕首系列武器时,增加命中率。 攻击时对敌人造成的僵直时间提高至双剑水准。\n
+ 在决斗场中,不会增加僵直时间。\n
"""
name = '潜行之匕首精通'
+ cellPos = (3,5)
learnLv = 20
masterLv = 20
maxLv = 30
- position = 3 # TODO
+ position = 3
rangeLv = 3
uuid = 'e5c09f9132a48dc1d695968592cc5878'
hasVP = False
hasUP = False
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- # 武器物理攻击力增加率 : {value0}%
+ # 物理攻击力增加率 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
+ # [装备匕首时]
# 命中率增加 : {value1}%
data1 = get_data(f'{prefix}/{uuid}', 1)
associate = [{'data': data0, 'type': '$*PAtkP'}]
-
# 挑空
# thief/shadow_dancer/e0a072e8cef2d77893aad5f68aeed56a
# ddc49e9ad1ff72a00b53c6cff5b1e920/e0a072e8cef2d77893aad5f68aeed56a
class Skill24(ActiveSkill):
"""
- 从地面垂直对敌人发出上挑攻击。 击中倒地或者浮在低空中的敌人可以挑起对方。 被击中的敌人会进入无视硬直的僵直状态。\n
- 在决斗场对倒地的敌人使用时, 不会挑起对方, 而是让敌人浮在空中。
+ 从地面垂直对敌人发出上挑攻击。 击中倒地或者浮在低空中的敌人可以挑起对方。 被击中的敌人会进入无视硬直的僵直状态。\n
+ 在决斗场对倒地的敌人使用时,不会挑起对方,而是让敌人浮在空中。\n
"""
name = '挑空'
+ cellPos = (5,5)
learnLv = 20
masterLv = 60
maxLv = 70
- position = 5 # TODO
+ position = 5
rangeLv = 2
cd = 3
mp = [40, 292]
@@ -318,15 +861,14 @@ class Skill24(ActiveSkill):
# 僵直时间 : {value2}秒
data2 = get_data(f'{prefix}/{uuid}', 2)
-
# 潜行领域
# thief/shadow_dancer/7ec521d063d2190e1fcc5bd229af9bcf
# ddc49e9ad1ff72a00b53c6cff5b1e920/7ec521d063d2190e1fcc5bd229af9bcf
class Skill25(ActiveSkill):
"""
- 影舞者先贤为了弥补影舞者攻击范围不足而创造的领域技能。 指定的技能命中敌人背部时, 会召唤暗袭分身攻击附近的敌人。\n
- 暗袭分身的攻击力等同于施放的技能攻击力。 再次按技能键时, 取消技能。\n
- 随着技能等级的增加, 背击时可以产生暗袭分身的技能的种类也会增加。\n
+ 影舞者先贤为了弥补影舞者攻击范围不足而创造的领域技能。 指定的技能命中敌人背部时,会召唤暗袭分身攻击附近的敌人。\n
+ 暗袭分身的攻击力等同于施放的技能攻击力。 再次按技能键时,取消技能。\n
+ 随着技能等级的增加,背击时可以产生暗袭分身的技能的种类也会增加。\n
1级 - 基本攻击第4击、 [天诛]、 [撕裂]、 [挑空]\n
2级 - [穿心]\n
3级 - [追袭]\n
@@ -336,14 +878,15 @@ class Skill25(ActiveSkill):
7级 - [绝境飞刃]\n
8级 - [八荒影杀]\n
9级 - [死亡连舞]\n
- 10级 - [摧心]、 [幽冥链狱 : 瞬逝]、 [影缚追魂锁]
+ 10级 - [摧心]、 [幽冥链狱 : 瞬逝]、 [影缚追魂锁]\n
"""
name = '潜行领域'
+ cellPos = (7,5)
learnLv = 20
masterLv = 1
maxLv = 10
- position = 7 # TODO
+ position = 7
rangeLv = 5
cd = 5
mp = [128, 128]
@@ -375,22 +918,45 @@ class Skill25(ActiveSkill):
# [影缚追魂锁]暗影爆炸攻击力 : {value10}%
data10 = get_data(f'{prefix}/{uuid}', 10)
+# 暴击
+# thief/shadow_dancer/fc1262c19f3d0477ee8eda47b8db8696
+# ddc49e9ad1ff72a00b53c6cff5b1e920/fc1262c19f3d0477ee8eda47b8db8696
+# MIGRATION: character 中没有同名技能“暴击”,暂不启用
+# class Skill27(PassiveSkill):
+# """
+# 集中精神,提升物理/魔法暴击率。\n
+# """
+# name = "暴击"
+# learnLv = 20
+# masterLv = 10
+# maxLv = 20
+# cellPos = (5,0)
+# position = 5
+# rangeLv = 3
+# uuid = "fc1262c19f3d0477ee8eda47b8db8696"
+# hasVP = False
+# hasUP = False
+# custom = get_data(f'{prefix}/{uuid}', "custom") # noqa: E501
+#
+# # 物理/魔法暴击率增加 : {value0}%
+# data0 = get_data(f'{prefix}/{uuid}', 0)
# 穿心
# thief/shadow_dancer/2a0a39184de92acf1c1375e00b77404c
# ddc49e9ad1ff72a00b53c6cff5b1e920/2a0a39184de92acf1c1375e00b77404c
class Skill27(ActiveSkill):
"""
- 以锐利的角度刺向敌人的要害。 技能命中敌人背部时, 会发动剜出敌人心脏的要害攻击, 同时让敌人进入僵直状态。\n
- 被要害攻击命中的敌人将会减少硬直最大值, 并使敌人背面朝向影舞者。\n
- 对无法抓取的敌人不适用控制效果。
+ 以锐利的角度刺向敌人的要害。 技能命中敌人背部时,会发动剜出敌人心脏的要害攻击,同时让敌人进入僵直状态。\n
+ 被要害攻击命中的敌人将会减少硬直最大值,并使敌人背面朝向影舞者。\n
+ 对无法抓取的敌人不适用控制效果。\n
"""
name = '穿心'
+ cellPos = (5,6)
learnLv = 25
masterLv = 60
maxLv = 70
- position = 5 # TODO
+ position = 5
rangeLv = 2
cd = 8
mp = [30, 500]
@@ -410,20 +976,20 @@ class Skill27(ActiveSkill):
# 硬直持续时间 : {value3}秒
data3 = get_data(f'{prefix}/{uuid}', 3)
-
# 影遁
# thief/shadow_dancer/9dc8438e4572d39243c97da31c113acc
# ddc49e9ad1ff72a00b53c6cff5b1e920/9dc8438e4572d39243c97da31c113acc
class Skill28(ActiveSkill):
"""
- 召唤出挑衅敌人的暗袭分身, 并且在一段时间内变为隐身状态进行突进。
+ 召唤出挑衅敌人的暗袭分身,并且在一段时间内变为隐身状态进行突进。\n
"""
name = '影遁'
+ cellPos = (3,6)
learnLv = 25
masterLv = 1
maxLv = 1
- position = 3 # TODO
+ position = 3
rangeLv = 5
cd = 10
mp = [150, 150]
@@ -443,22 +1009,22 @@ class Skill28(ActiveSkill):
# 隐身状态下物理暴击率增加量 : {value4}%
data4 = get_data(f'{prefix}/{uuid}', 4)
-
# 潜行信条
# thief/shadow_dancer/7f80b887a09e88e2c4728c898bd73654
# ddc49e9ad1ff72a00b53c6cff5b1e920/7f80b887a09e88e2c4728c898bd73654
class Skill29(ActiveSkill):
"""
- 增加暴击率和暴击伤害。\n
+ 增加暴击率和暴击伤害。\n
追踪者的五种美德。\n
- 忍耐、 集中、 冷静、 沉着、 任务至上。 (节选自教科书《潜伏的定式》)
+ 忍耐、 集中、 冷静、 沉着、 任务至上。 (节选自教科书《潜伏的定式》)\n
"""
name = '潜行信条'
+ cellPos = (3,7)
learnLv = 30
masterLv = 10
maxLv = 20
- position = 3 # TODO
+ position = 3
rangeLv = 3
cd = 5
uuid = '7f80b887a09e88e2c4728c898bd73654'
@@ -473,22 +1039,22 @@ class Skill29(ActiveSkill):
# 暴击伤害增加率 : {value2}%
data2 = get_data(f'{prefix}/{uuid}', 2)
-
# 追袭
# thief/shadow_dancer/3fb8395ae3b81bd608e0c4223a8eb534
# ddc49e9ad1ff72a00b53c6cff5b1e920/3fb8395ae3b81bd608e0c4223a8eb534
class Skill30(ActiveSkill):
"""
- 利用离心力将抓取到的敌人用力按在地面上, 同时引发冲击波。 可以在空中使用该技能。 如果技能命中敌人背部, 冲击波的攻击力和范围会增加。\n
- 在地面使用技能时, 跳跃较长距离进行攻击。 通过方向键可以调整中/远距离以及Y轴的移动方向。\n
- 在空中使用技能时, 会把敌人按在正前方的地面上。 [后跳]过程中也可以使用空中[追袭]。
+ 利用离心力将抓取到的敌人用力按在地面上,同时引发冲击波。 可以在空中使用该技能。 如果技能命中敌人背部,冲击波的攻击力和范围会增加。\n
+ 在地面使用技能时,跳跃较长距离进行攻击。 通过方向键可以调整中/远距离以及Y轴的移动方向。\n
+ 在空中使用技能时,会把敌人按在正前方的地面上。 [后跳]过程中也可以使用空中[追袭]。\n
"""
name = '追袭'
+ cellPos = (7,7)
learnLv = 30
masterLv = 60
maxLv = 70
- position = 7 # TODO
+ position = 7
rangeLv = 2
cd = 10
mp = [50, 420]
@@ -500,27 +1066,30 @@ class Skill30(ActiveSkill):
# 攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
hit0 = 1
- power0 = 1.5
# 背击时攻击力增加率 : {value1}%
data1 = get_data(f'{prefix}/{uuid}', 1)
- # [范围信息]
- # 冲击波范围比率 : {value2}%
+ # 压制力 (4人队伍) : {value2}秒 ({value3}秒)
data2 = get_data(f'{prefix}/{uuid}', 2)
-
+ data3 = get_data(f'{prefix}/{uuid}', 3)
+ # [范围信息]
+ # 冲击波范围比率 : {value4}%
+ data4 = get_data(f'{prefix}/{uuid}', 4)
+ power0 = 1.5
# 刃舞
# thief/shadow_dancer/5806440d21e7546d50007a5ba11f8024
# ddc49e9ad1ff72a00b53c6cff5b1e920/5806440d21e7546d50007a5ba11f8024
class Skill31(ActiveSkill):
"""
- 猛烈挥舞手中的剑刃, 对周围的敌人进行攻击。 施放过程中, 按下Z键或跳跃键, 可以快速施放最后的斩击。
+ 猛烈挥舞手中的剑刃,对周围的敌人进行攻击。 施放过程中,按下Z键或跳跃键,可以快速施放最后的斩击。\n
"""
name = '刃舞'
+ cellPos = (7,8)
learnLv = 35
masterLv = 60
maxLv = 70
- position = 7 # TODO
+ position = 7
rangeLv = 2
cube = 1
cd = 15
@@ -529,7 +1098,7 @@ class Skill31(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 回旋斩击攻击力 : {value0}% X {value1}
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -563,20 +1132,20 @@ class Skill31(ActiveSkill):
"""
...
-
# 潜影刺击
# thief/shadow_dancer/c524c4f378d1cd0ff99e4580750a4567
# ddc49e9ad1ff72a00b53c6cff5b1e920/c524c4f378d1cd0ff99e4580750a4567
class Skill32(ActiveSkill):
"""
- 快速挥斩两次后, 移动到敌人背后, 最后强力刺击敌人要害。 从背后命中敌人时, 可以造成更深的伤口。
+ 快速挥斩两次后,移动到敌人背后,最后强力刺击敌人要害。 从背后命中敌人时,可以造成更深的伤口。\n
"""
name = '潜影刺击'
+ cellPos = (6,8)
learnLv = 35
masterLv = 60
maxLv = 70
- position = 6 # TODO
+ position = 6
rangeLv = 2
cube = 1
cd = 15
@@ -585,7 +1154,7 @@ class Skill32(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 连续挥斩攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -613,23 +1182,23 @@ class Skill32(ActiveSkill):
"""
...
-
# 影戮
# thief/shadow_dancer/d429147c372b549c3dadcabcba50787f
# ddc49e9ad1ff72a00b53c6cff5b1e920/d429147c372b549c3dadcabcba50787f
class Skill33(ActiveSkill):
"""
- 通过华丽而致命的乱舞斩击毁灭敌人。\n
- 命中敌人背部时, 会施放强力的斩击将敌人斩为两半。\n
- 命中敌人时, 使用[追影步]可以让暗袭分身继续进行该技能的剩余动作。\n
- 在决斗场中, 只有当影戮攻击命中目标时, 可以用追影步强行停止分身。
+ 通过华丽而致命的乱舞斩击毁灭敌人。\n
+ 命中敌人背部时,会施放强力的斩击将敌人斩为两半。\n
+ 命中敌人时,使用[追影步]可以让暗袭分身继续进行该技能的剩余动作。\n
+ 在决斗场中,只有当影戮攻击命中目标时,可以用追影步强行停止分身。\n
"""
name = '影戮'
+ cellPos = (7,9)
learnLv = 40
masterLv = 60
maxLv = 70
- position = 7 # TODO
+ position = 7
rangeLv = 2
cube = 1
cd = 25
@@ -638,7 +1207,7 @@ class Skill33(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 乱舞攻击力 : {value0}% X {value1}
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -651,6 +1220,9 @@ class Skill33(ActiveSkill):
# 背击时最终一击攻击力 : {value4}%
data4 = get_data(f'{prefix}/{uuid}', 4)
hit4 = 1
+ # 压制力 (4人队伍) : {value5}秒 ({value6}秒)
+ data5 = get_data(f'{prefix}/{uuid}', 5)
+ data6 = get_data(f'{prefix}/{uuid}', 6)
def vp_1(self):
"""
@@ -681,20 +1253,20 @@ class Skill33(ActiveSkill):
self.skillRation *= 1.25
...
-
# 瞬袭
# thief/shadow_dancer/0113c8b1306ca76d208f83f2d093dd62
# ddc49e9ad1ff72a00b53c6cff5b1e920/0113c8b1306ca76d208f83f2d093dd62
class Skill34(ActiveSkill):
"""
- 悄无声息地接近敌人进行攻击。 被攻击的对象的生命值越少, 受到的伤害越高。
+ 悄无声息地接近敌人进行攻击。 被攻击的对象的生命值越少,受到的伤害越高。\n
"""
name = '瞬袭'
+ cellPos = (5,10)
learnLv = 45
masterLv = 60
maxLv = 70
- position = 5 # TODO
+ position = 5
rangeLv = 2
cube = 2
cd = 45
@@ -703,7 +1275,7 @@ class Skill34(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 背后偷袭攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -718,6 +1290,9 @@ class Skill34(ActiveSkill):
data4 = get_data(f'{prefix}/{uuid}', 4)
# 领主怪物被击毙的生命值比率 : 低于{value5}%
data5 = get_data(f'{prefix}/{uuid}', 5)
+ # 压制力 (4人队伍) : {value6}秒 ({value7}秒)
+ data6 = get_data(f'{prefix}/{uuid}', 6)
+ data7 = get_data(f'{prefix}/{uuid}', 7)
def vp_1(self):
"""
@@ -738,21 +1313,21 @@ class Skill34(ActiveSkill):
"""
...
-
# 潜行之心
# thief/shadow_dancer/c91a62dc0a18360acf5031ac0ebf09f5
# ddc49e9ad1ff72a00b53c6cff5b1e920/c91a62dc0a18360acf5031ac0ebf09f5
class Skill35(PassiveSkill):
"""
- 死亡舞会所属要员必须掌握的潜行术, 可以准确的从背后命中目标的要害。\n
- 技能命中敌人背部时, 会增加攻击力和暴击率。
+ 死亡舞会所属要员必须掌握的潜行术,可以准确的从背后命中目标的要害。\n
+ 技能命中敌人背部时,会增加攻击力和暴击率。\n
"""
name = '潜行之心'
+ cellPos = (3,11)
learnLv = 48
masterLv = 40
maxLv = 50
- position = 3 # TODO
+ position = 3
rangeLv = 3
uuid = 'c91a62dc0a18360acf5031ac0ebf09f5'
hasVP = False
@@ -766,20 +1341,20 @@ class Skill35(PassiveSkill):
associate = [{'data': data1}]
-
# 影斩·乱舞
# thief/shadow_dancer/8b08f9504167a9c0f3a1d29d71b7943e
# ddc49e9ad1ff72a00b53c6cff5b1e920/8b08f9504167a9c0f3a1d29d71b7943e
class Skill36(ActiveSkill):
"""
- 梦魇会在大范围内进行快速移动并斩击敌人。 每次斩击都会制造一个暗袭分身进行攻击, 最后在暗袭分身和梦魇的夹击下颠覆空间。 连续按攻击键, 可以加快施放速度。 按前或下方向键, 可以在原地发动最终一击。
+ 梦魇会在大范围内进行快速移动并斩击敌人。 每次斩击都会制造一个暗袭分身进行攻击,最后在暗袭分身和梦魇的夹击下颠覆空间。 连续按攻击键,可以加快施放速度。 按前或下方向键,可以在原地发动最终一击。\n
"""
name = '影斩·乱舞'
+ cellPos = (7,11)
learnLv = 50
masterLv = 40
maxLv = 50
- position = 7 # TODO
+ position = 7
rangeLv = 5
cube = 7
cd = 145
@@ -797,20 +1372,20 @@ class Skill36(ActiveSkill):
data2 = get_data(f'{prefix}/{uuid}', 2)
hit2 = 1
-
# 绝境飞刃
# thief/shadow_dancer/ac21c02567f04a92b54dd85c091d1e5a
# ddc49e9ad1ff72a00b53c6cff5b1e920/ac21c02567f04a92b54dd85c091d1e5a
class Skill37(ActiveSkill):
"""
- 投掷匕首, 将匕首掷入敌人的身体里, 可在空中进行投掷。 匕首会在敌人身体内滞留一段时间, 此时再次输入技能键, 影舞者会接近敌人背后, 随后拔出匕首进行一次攻击。
+ 投掷匕首,将匕首掷入敌人的身体里,可在空中进行投掷。 匕首会在敌人身体内滞留一段时间,此时再次输入技能键,影舞者会接近敌人背后,随后拔出匕首进行一次攻击。\n
"""
name = '绝境飞刃'
+ cellPos = (7,12)
learnLv = 60
masterLv = 40
maxLv = 50
- position = 7 # TODO
+ position = 7
rangeLv = 2
cube = 1
cd = 30
@@ -819,7 +1394,7 @@ class Skill37(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 匕首滞留敌人身体的时间 : {value0}秒
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -855,20 +1430,20 @@ class Skill37(ActiveSkill):
"""
...
-
# 八荒影杀
# thief/shadow_dancer/ab6fc3303df03b58911967dfca2b5d07
# ddc49e9ad1ff72a00b53c6cff5b1e920/ab6fc3303df03b58911967dfca2b5d07
class Skill38(ActiveSkill):
"""
- 对敌人进行斩击, 随后制造暗袭分身, 从八个方向同时斩击敌人。
+ 对敌人进行斩击,随后制造暗袭分身,从八个方向同时斩击敌人。\n
"""
name = '八荒影杀'
+ cellPos = (7,13)
learnLv = 70
masterLv = 40
maxLv = 50
- position = 7 # TODO
+ position = 7
rangeLv = 2
cube = 2
cd = 50
@@ -877,7 +1452,7 @@ class Skill38(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 第1击攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -919,22 +1494,22 @@ class Skill38(ActiveSkill):
"""
...
-
# 死亡连舞
# thief/shadow_dancer/6e33d47e6622ce03b6defdd912140270
# ddc49e9ad1ff72a00b53c6cff5b1e920/6e33d47e6622ce03b6defdd912140270
class Skill39(ActiveSkill):
"""
- 抓住敌人后, 使用迅速、 干净利落的动作对要害进行连续攻击。\n
- 从背后进行攻击时, 会施放强力的追加攻击。\n
- 对不可抓取敌人省略部分中间动作,增加最后一击攻击力。
+ 抓住敌人后,使用迅速、 干净利落的动作对要害进行连续攻击。\n
+ 从背后进行攻击时,会施放强力的追加攻击。\n
+ 对不可抓取敌人省略部分中间动作,增加最后一击攻击力。\n
"""
name = '死亡连舞'
+ cellPos = (5,13)
learnLv = 75
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 2
cube = 3
cd = 40
@@ -943,7 +1518,7 @@ class Skill39(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 连续攻击攻击力 : {value0}% X {value1}
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -957,6 +1532,9 @@ class Skill39(ActiveSkill):
hit3 = 1
# 对无法抓取的敌人最后一击攻击力 : {value4}%
data4 = get_data(f'{prefix}/{uuid}', 4)
+ # 压制力 (4人队伍) : {value5}秒 ({value6}秒)
+ data5 = get_data(f'{prefix}/{uuid}', 5)
+ data6 = get_data(f'{prefix}/{uuid}', 6)
mode = ['抓取', '不可抓取'] # noqa: E501
@@ -989,44 +1567,44 @@ class Skill39(ActiveSkill):
self.setMode('不可抓取') # noqa: E501
...
-
# 潜行之袭
# thief/shadow_dancer/51a08fd0c90f0a5276cd552047fac93d
# ddc49e9ad1ff72a00b53c6cff5b1e920/51a08fd0c90f0a5276cd552047fac93d
class Skill40(PassiveSkill):
"""
- 增加自身基本攻击力和技能攻击力, 技能施放过程中可以使用[影遁]。
+ 增加自身基本攻击力和技能攻击力,技能施放过程中可以使用[影遁]。\n
"""
name = '潜行之袭'
+ cellPos = (3,13)
learnLv = 75
masterLv = 40
maxLv = 50
- position = 3 # TODO
+ position = 3
rangeLv = 3
uuid = '51a08fd0c90f0a5276cd552047fac93d'
hasVP = False
hasUP = False
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- # 基本攻击力和技能攻击力增加率 : {value0}%
+ # 基本攻击力和技能攻击力增加量 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
associate = [{'data': data0}]
-
# 摧心
# thief/shadow_dancer/b163d099c8cc27fdb6fd3639c2ee6df9
# ddc49e9ad1ff72a00b53c6cff5b1e920/b163d099c8cc27fdb6fd3639c2ee6df9
class Skill41(ActiveSkill):
"""
- 用绚丽旋转的步伐对前方敌人进行攻击。 如果命中敌人的正面, 用强烈的旋转力施加重击。 如果命中敌人的背部, 则会将敌人的要害握在手里, 随后引起爆炸。
+ 用绚丽旋转的步伐对前方敌人进行攻击。 如果命中敌人的正面,用强烈的旋转力施加重击。 如果命中敌人的背部,则会将敌人的要害握在手里,随后引起爆炸。\n
"""
name = '摧心'
+ cellPos = (3,14)
learnLv = 80
masterLv = 40
maxLv = 50
- position = 3 # TODO
+ position = 3
rangeLv = 2
cube = 5
cd = 45
@@ -1035,7 +1613,7 @@ class Skill41(ActiveSkill):
hasVP = True
hasUP = True
custom = get_data(f'{prefix}/{uuid}', 'custom') # noqa: E501
- vps = get_data(f'{prefix}/{uuid}', 'vps') # noqa: E501
+ vps = get_data(f'{prefix}/{uuid}', "vps") # noqa: E501
# 正面旋转重击的攻击力 : {value0}%
data0 = get_data(f'{prefix}/{uuid}', 0)
@@ -1060,31 +1638,31 @@ class Skill41(ActiveSkill):
"""
...
-
# 幽冥链狱 : 瞬逝
# thief/shadow_dancer/28b583c75a49103a1d8aabf799c000a4
# ddc49e9ad1ff72a00b53c6cff5b1e920/28b583c75a49103a1d8aabf799c000a4
class Skill42(ActiveSkill):
"""
- 使用末端连接锁链的匕首, 对敌人进行攻击。\n
- 技能的过程中按下技能键时, 可以射出锁链。 可以在空中射出锁链, 也可以强制中断特定技能后射出锁链。 如果锁链插中敌人, 可以通过追加操作进行其他的动作。\n
- 未按键 : 把敌人拉到自己面前。\n
- X键 : 靠近敌人并进行攻击。\n
- X键 + 后方向键 : 靠近敌人后转换位置, 并进行强力一击。\n
- 空格键 : 把锁链插到地面上, 束缚敌人一段时间。 此动作有另外的冷却时间。\n
- 空格 + ↓键 : 用锁链和影分身将目标周围的敌人聚拢, 并攻击造成伤害。 该动作与按空格键的动作共享冷却时间。\n
- 再次按下技能键 : 施放强力终结攻击。\n
- 可以强制中断的技能 : [翔击]、 [切割]、 [弧光闪]、 [影袭]、 [碎踝]、 [追影步]、 [挑空]、 [穿心]、 [刃舞]、 [潜影刺击]、 [影斩·乱舞]\n
- 可以强制中断特定技能的后僵直并发射锁链的技能 :\n
+ 使用末端连接锁链的匕首,对敌人进行攻击。\n
+ 技能的过程中按下技能键时,可以射出锁链。 可以在空中射出锁链,也可以强制中断特定技能后射出锁链。 如果锁链插中敌人,可以通过追加操作进行其他的动作。\n
+ 未按键 : 把敌人拉到自己面前。\n
+ X键 : 靠近敌人并进行攻击。\n
+ X键 + 后方向键 : 靠近敌人后转换位置,并进行强力一击。\n
+ 空格键 : 把锁链插到地面上,束缚敌人一段时间。 此动作有另外的冷却时间。\n
+ 空格 + ↓键 : 用锁链和影分身将目标周围的敌人聚拢,并攻击造成伤害。 该动作与按空格键的动作共享冷却时间。\n
+ 再次按下技能键 : 施放强力终结攻击。\n
+ 可以强制中断的技能 : [翔击]、 [切割]、 [弧光闪]、 [影袭]、 [碎踝]、 [追影步]、 [挑空]、 [穿心]、 [刃舞]、 [潜影刺击]、 [影斩·乱舞]\n
+ 可以强制中断特定技能的后僵直并发射锁链的技能 :\n
[绝境飞刃] (最后二击)、 [摧心] (要害袭击动作)、 [瞬袭] (转刀动作)、 [影缚追魂锁] (背后暗影爆炸动作)。\n
- 射出匕首命中敌人时, 增加[幽冥链狱 : 瞬逝]的持续时间。
+ 射出匕首命中敌人时,增加[幽冥链狱 : 瞬逝]的持续时间。\n
"""
name = '幽冥链狱 : 瞬逝'
+ cellPos = (5,14)
learnLv = 85
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 5
cube = 10
cd = 180
@@ -1125,6 +1703,9 @@ class Skill42(ActiveSkill):
data14 = get_data(f'{prefix}/{uuid}', 14)
# 射出匕首命中敌人后技能持续时间最大增加值 : {value15}秒
data15 = get_data(f'{prefix}/{uuid}', 15)
+ # 压制力 (4人队伍) : {value16}秒 ({value17}秒)
+ data16 = get_data(f'{prefix}/{uuid}', 16)
+ data17 = get_data(f'{prefix}/{uuid}', 17)
mode = ['终结抓取', '终结非抓', '锁链聚集', '锁链拉拽'] # noqa: E501
@@ -1151,23 +1732,23 @@ class Skill42(ActiveSkill):
return 1.0
return super().getSkillCD(mode)
-
# 暗影禁忌
# thief/shadow_dancer/33ad4930f4724a7d025c3046c6f6074b
# ddc49e9ad1ff72a00b53c6cff5b1e920/33ad4930f4724a7d025c3046c6f6074b
class Skill43(PassiveSkill):
"""
- 隐夜·影舞者利用他人影子施展禁忌潜行术, 在任何人都无法察觉的情况下, 除掉敌人。\n
- 学习后, 增加基本攻击力和转职技能攻击力, 部分技能附加特殊效果。\n
- [潜行领域] : 增加暗影领域范围。 背击成功时若存在多个敌人, 则增加每次召唤的暗袭分身的数量。\n
- [穿心] : 影舞者的所有转职技能的终结动作时可以预输入该技能键。 预输入后发动技能的状态下, 技能完全结束或施放[追影步]时, 出现在该技能命中的敌人中最强的敌人背后, 穿刺敌人的心脏。 此效果发动的[穿心]必定为背击。
+ 隐夜·影舞者利用他人影子施展禁忌潜行术,在任何人都无法察觉的情况下,除掉敌人。\n
+ 学习后,增加基本攻击力和转职技能攻击力,部分技能附加特殊效果。\n
+ [潜行领域] : 增加暗影领域范围。 背击成功时若存在多个敌人,则增加每次召唤的暗袭分身的数量。\n
+ [穿心] : 影舞者的所有转职技能的终结动作时可以预输入该技能键。 预输入后发动技能的状态下,技能完全结束或施放[追影步]时,出现在该技能命中的敌人中最强的敌人背后,穿刺敌人的心脏。 此效果发动的[穿心]必定为背击。\n
"""
name = '暗影禁忌'
+ cellPos = (5,15)
learnLv = 95
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 3
uuid = '33ad4930f4724a7d025c3046c6f6074b'
hasVP = False
@@ -1182,22 +1763,22 @@ class Skill43(PassiveSkill):
data2 = get_data(f'{prefix}/{uuid}', 2)
associate = [{'data': data0}]
-
# 影缚追魂锁
# thief/shadow_dancer/9ceb0c55f40f1fc0fe0fcc65c8fee3a0
# ddc49e9ad1ff72a00b53c6cff5b1e920/9ceb0c55f40f1fc0fe0fcc65c8fee3a0
class Skill44(ActiveSkill):
"""
- 投掷暗影锁链, 抽取敌人的影子, 对其进行追魂攻击。\n
- 暗影锁链命中敌人背后时, 抽取影子后快速移动, 切割影子并使其爆炸。 影子爆炸瞬间, 所有伤害都会瞬间转移到敌人本体, 造成致命伤害。\n
- 背后攻击时, 若敌人的影子贴近隐夜·影舞者, 则后空翻拉开距离后再开始攻击。
+ 投掷暗影锁链,抽取敌人的影子,对其进行追魂攻击。\n
+ 暗影锁链命中敌人背后时,抽取影子后快速移动,切割影子并使其爆炸。 影子爆炸瞬间,所有伤害都会瞬间转移到敌人本体,造成致命伤害。\n
+ 背后攻击时,若敌人的影子贴近隐夜·影舞者,则后空翻拉开距离后再开始攻击。\n
"""
name = '影缚追魂锁'
+ cellPos = (7,15)
learnLv = 95
masterLv = 40
maxLv = 50
- position = 7 # TODO
+ position = 7
rangeLv = 2
cube = 7
cd = 60
@@ -1217,25 +1798,25 @@ class Skill44(ActiveSkill):
data3 = get_data(f'{prefix}/{uuid}', 3)
hit3 = 1
-
# 无间影狱·噬灭
# thief/shadow_dancer/aa6dd52e1c925d87cdc0ca340056c543
# ddc49e9ad1ff72a00b53c6cff5b1e920/aa6dd52e1c925d87cdc0ca340056c543
class Skill45(ActiveSkill):
"""
- 隐夜·影舞者觉醒自身暗影领域, 融入蔓延到周围的暗影地带, 隐藏身影。 用锁链环绕周围一带后, 用暗影匕首斩击敌人。 最后, 挥动暗影匕首施展3次致命攻击, 最后投掷匕首。\n
- 暗影地带内的敌人都是隐夜·影舞者的攻击对象, 所有攻击均适用背后攻击。\n
- 施放技能时, 无法使用[影遁], 无法强制中断并施放影舞者转职技能。\n
+ 隐夜·影舞者觉醒自身暗影领域,融入蔓延到周围的暗影地带,隐藏身影。 用锁链环绕周围一带后,用暗影匕首斩击敌人。 最后,挥动暗影匕首施展3次致命攻击,最后投掷匕首。\n
+ 暗影地带内的敌人都是隐夜·影舞者的攻击对象,所有攻击均适用背后攻击。\n
+ 施放技能时,无法使用[影遁],无法强制中断并施放影舞者转职技能。\n
[三次觉醒技能]\n
- 选择[影斩·乱舞]使用三次觉醒技能时, 与[影斩·乱舞]共享冷却时间。 \n
- 选择[幽冥链狱 : 瞬逝]使用三次觉醒技能时, 可以使用三次觉醒技能代替[幽冥链狱 : 瞬逝]的终结攻击。 三次觉醒技能在冷却中、 [幽冥链狱 : 瞬逝]终结攻击已发动或[幽冥链狱 : 瞬逝]未激活的状态下, 无法使用三次觉醒技能。
+ 选择[影斩·乱舞]使用三次觉醒技能时,与[影斩·乱舞]共享冷却时间。\n
+ 选择[幽冥链狱 : 瞬逝]使用三次觉醒技能时,可以使用三次觉醒技能代替[幽冥链狱 : 瞬逝]的终结攻击。 三次觉醒技能在冷却中、 [幽冥链狱 : 瞬逝]终结攻击已发动或[幽冥链狱 : 瞬逝]未激活的状态下,无法使用三次觉醒技能。\n
"""
name = '无间影狱·噬灭'
+ cellPos = (5,16)
learnLv = 100
masterLv = 40
maxLv = 50
- position = 5 # TODO
+ position = 5
rangeLv = 5
cube = 15
cd = 290
@@ -1259,7 +1840,6 @@ class Skill45(ActiveSkill):
data4 = get_data(f'{prefix}/{uuid}', 4)
hit4 = 1
-
class classChange(Character):
def __init__(self, equVersion):
diff --git a/backend/openapi b/backend/openapi
index 43f67e00..c7b0736c 160000
--- a/backend/openapi
+++ b/backend/openapi
@@ -1 +1 @@
-Subproject commit 43f67e007d72165e48deb4372363c3c26398a094
+Subproject commit c7b0736cdbfc3456fd4c3d13c17594337dd1b65c
diff --git a/frontend/src/pages/Character/Skill/components/SkillDetail/index.tsx b/frontend/src/pages/Character/Skill/components/SkillDetail/index.tsx
index d09bd817..79603358 100644
--- a/frontend/src/pages/Character/Skill/components/SkillDetail/index.tsx
+++ b/frontend/src/pages/Character/Skill/components/SkillDetail/index.tsx
@@ -32,22 +32,22 @@ export default defineComponent({
!skillInfo.value
? []
: [
- { label: '技能等级', value: skillInfo.value?.attribute.level },
+ { label: '技能等级', value: skillInfo.value?.attribute?.level ?? props.level },
{ label: '学习等级', value: skillInfo.value?.requiredLevel },
{ label: '等级上限', value: skillInfo.value?.maxLevel },
{
label: '冷却时间',
- value: skillInfo.value?.attribute.coolTime
- ? `${skillInfo.value?.attribute.coolTime.toFixed(1)}秒`
+ value: skillInfo.value?.attribute?.coolTime
+ ? `${skillInfo.value?.attribute?.coolTime.toFixed(1)}秒`
: undefined,
},
{
label: '施放时间',
- value: !skillInfo.value?.attribute.castingTime
+ value: !skillInfo.value?.attribute?.castingTime
? '瞬发'
- : `${skillInfo.value?.attribute.castingTime.toFixed(1)}秒`,
+ : `${skillInfo.value?.attribute?.castingTime.toFixed(1)}秒`,
},
- { label: '魔法值', value: skillInfo.value?.attribute.consumeMp },
+ { label: '魔法值', value: skillInfo.value?.attribute?.consumeMp },
{
label: '消耗无色小晶块',
value: skillInfo.value?.consumeItem?.value
@@ -61,9 +61,9 @@ export default defineComponent({
const detail = computed(() => {
const items =
- skillInfo.value?.attribute.optionDesc?.split('\n').filter((item) => !!item) ?? []
+ skillInfo.value?.attribute?.optionDesc?.split('\n').filter((item) => !!item) ?? []
const details = items?.map((a) => {
- const beforeReplace = a.match(/^(.+?)\s*(\{value\d+\}.*)$/);
+ const beforeReplace = a.match(/^(.+?)\s*(\{value\d+\}.*)$/)
if (beforeReplace) {
a = `${beforeReplace[1]}${beforeReplace[2]}`
}
@@ -73,7 +73,7 @@ export default defineComponent({
const key = match.replace(/[{}]/g, '')
// Only access if key matches the expected pattern
if (/^value.+/.test(key)) {
- const value = skillInfo.value?.attribute.optionValue[key as `value${string}`] ?? '-'
+ const value = skillInfo.value?.attribute?.optionValue[key as `value${string}`] ?? '-'
if (value) {
a = a.replace(match, `${value}`)
}
@@ -102,19 +102,17 @@ export default defineComponent({
技能描述
- {desc.value?.map((item) =>
+ {desc.value?.map((item) => (
// item.startsWith('[') || item.startsWith('-') || item.startsWith('<') ? (
//
{item}
// ) :
- (
-
- {item}
-
- ),
- )}
+
+ {item}
+
+ ))}
- {(detail.value.length > 0 ) && (
+ {detail.value.length > 0 && (
<>
技能属性
diff --git a/scripts/migrate_generated_characters.py b/scripts/migrate_generated_characters.py
new file mode 100644
index 00000000..c8215ab7
--- /dev/null
+++ b/scripts/migrate_generated_characters.py
@@ -0,0 +1,503 @@
+"""Merge OpenAPI-generated layouts into runtime character modules.
+
+Skills are matched strictly by their constant ``name``. Runtime Skill class
+identifiers and implementations are preserved; ``position`` and ``cellPos``
+come from the generated class. Generated-only skills are retained as commented blocks.
+The runtime header and profession-specific footer are preserved unchanged.
+"""
+
+from __future__ import annotations
+
+import argparse
+import ast
+from dataclasses import dataclass
+import os
+from pathlib import Path
+import re
+import time
+
+
+ROOT = Path(__file__).resolve().parents[1]
+GENERATED_ROOT = ROOT / "backend" / "openapi" / "generate"
+CHARACTER_ROOT = ROOT / "backend" / "core" / "character" / "GF"
+
+
+@dataclass(frozen=True)
+class SkillBlock:
+ class_name: str
+ skill_name: str
+ start: int
+ end: int
+ position: str | None
+ cell_pos: str | None
+
+
+def _offsets(source: str) -> list[int]:
+ result = [0]
+ for line in source.splitlines(keepends=True):
+ result.append(result[-1] + len(line))
+ return result
+
+
+def _skills(source: str, path: Path) -> list[SkillBlock]:
+ tree = ast.parse(source, filename=str(path))
+ offsets = _offsets(source)
+ result: list[SkillBlock] = []
+ for node in tree.body:
+ if not isinstance(node, ast.ClassDef) or not re.fullmatch(r"Skill\d+", node.name):
+ continue
+ name = None
+ position = None
+ cell_pos = None
+ for statement in node.body:
+ if not isinstance(statement, (ast.Assign, ast.AnnAssign)):
+ continue
+ targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target]
+ if len(targets) != 1 or not isinstance(targets[0], ast.Name):
+ continue
+ if targets[0].id == "name" and isinstance(statement.value, ast.Constant):
+ name = statement.value.value
+ elif targets[0].id == "position":
+ position = ast.get_source_segment(source, statement.value)
+ elif targets[0].id == "cellPos":
+ cell_pos = ast.get_source_segment(source, statement.value)
+ if not isinstance(name, str) or node.end_lineno is None:
+ raise ValueError(f"{path}:{node.lineno}: invalid {node.name}")
+ result.append(
+ SkillBlock(
+ node.name,
+ name,
+ offsets[node.lineno - 1],
+ offsets[node.end_lineno],
+ position,
+ cell_pos,
+ )
+ )
+ return result
+
+
+def _module_skill_bounds(source: str, path: Path) -> tuple[int, int]:
+ """Return the code-only header end and profession-footer start offsets."""
+ tree = ast.parse(source, filename=str(path))
+ offsets = _offsets(source)
+ indexes = [
+ index
+ for index, node in enumerate(tree.body)
+ if isinstance(node, ast.ClassDef) and re.fullmatch(r"Skill\d+", node.name)
+ ]
+ if not indexes:
+ raise ValueError(f"missing skills: {path}")
+ first_index, last_index = indexes[0], indexes[-1]
+ header_end = offsets[tree.body[first_index - 1].end_lineno] if first_index else 0
+ footer_start = (
+ offsets[tree.body[last_index + 1].lineno - 1]
+ if last_index + 1 < len(tree.body)
+ else len(source)
+ )
+ return header_end, footer_start
+
+
+def _set_generated_field(block: str, field: str, value: str | None) -> str:
+ if value is None:
+ return block
+ assignment = re.compile(rf"(?m)^(\s*{re.escape(field)}\s*(?::[^=\n]+)?=\s*).*$")
+ if assignment.search(block):
+ return assignment.sub(rf"\g<1>{value}", block, count=1)
+ return re.sub(r"(?m)^(\s*name\s*=.*)$", rf"\g<1>\n {field} = {value}", block, count=1)
+
+
+def _statement_key(statement: ast.stmt) -> str | None:
+ if isinstance(statement, (ast.Assign, ast.AnnAssign)):
+ targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target]
+ if len(targets) == 1 and isinstance(targets[0], ast.Name):
+ return targets[0].id
+ return None
+
+
+def _is_data_key(key: str | None) -> bool:
+ return bool(key and re.fullmatch(r"data\d+", key))
+
+
+def _sync_generated_assignment(
+ runtime_block: str, generated_block: str, field: str
+) -> str:
+ """Replace all runtime definitions of one field with the generated one."""
+ runtime_class = ast.parse(runtime_block).body[0]
+ generated_class = ast.parse(generated_block).body[0]
+ if not isinstance(runtime_class, ast.ClassDef) or not isinstance(generated_class, ast.ClassDef):
+ return runtime_block
+ generated_statement = next(
+ (statement for statement in generated_class.body if _statement_key(statement) == field),
+ None,
+ )
+ if generated_statement is None:
+ return runtime_block
+
+ runtime_statements = [
+ statement for statement in runtime_class.body if _statement_key(statement) == field
+ ]
+ runtime_lines = runtime_block.splitlines(keepends=True)
+ generated_lines = generated_block.splitlines(keepends=True)
+ generated_source = generated_lines[
+ generated_statement.lineno - 1:generated_statement.end_lineno
+ ]
+ if runtime_statements:
+ insertion_line = runtime_statements[0].lineno - 1
+ for statement in sorted(runtime_statements, key=lambda item: item.lineno, reverse=True):
+ runtime_lines[statement.lineno - 1:statement.end_lineno] = []
+ else:
+ anchor = next(
+ (
+ statement
+ for key in ("custom", "hasUP", "hasVP", "uuid")
+ for statement in reversed(runtime_class.body)
+ if _statement_key(statement) == key
+ ),
+ None,
+ )
+ insertion_line = anchor.end_lineno if anchor is not None else 1
+ generated_source = ["\n", *generated_source]
+ runtime_lines[insertion_line:insertion_line] = generated_source
+ return "".join(runtime_lines)
+
+
+def _sync_generated_data(runtime_block: str, generated_block: str) -> str:
+ """Rebuild dataN/hitN in numeric order using generated data definitions."""
+ runtime_class = ast.parse(runtime_block).body[0]
+ generated_class = ast.parse(generated_block).body[0]
+ if not isinstance(runtime_class, ast.ClassDef) or not isinstance(generated_class, ast.ClassDef):
+ return runtime_block
+ runtime_lines = runtime_block.splitlines(keepends=True)
+ generated_lines = generated_block.splitlines(keepends=True)
+ runtime_items = {
+ _statement_key(statement): (index, statement)
+ for index, statement in enumerate(runtime_class.body)
+ if _statement_key(statement)
+ }
+ generated_items = {
+ _statement_key(statement): (index, statement)
+ for index, statement in enumerate(generated_class.body)
+ if _statement_key(statement)
+ }
+ selected_statements = [
+ (index, statement, _statement_key(statement))
+ for index, statement in enumerate(runtime_class.body)
+ if _is_data_key(_statement_key(statement))
+ or re.fullmatch(r"hit\d+", _statement_key(statement) or "")
+ ]
+ generated_data = {key: value for key, value in generated_items.items() if _is_data_key(key)}
+ if not selected_statements and not generated_data:
+ return runtime_block
+
+ def numeric_key(key: str) -> int:
+ return int(re.search(r"\d+$", key).group())
+
+ def segment(
+ lines: list[str], class_node: ast.ClassDef, index: int, statement: ast.stmt
+ ) -> list[str]:
+ previous_end = class_node.body[index - 1].end_lineno if index else class_node.lineno
+ return lines[previous_end:statement.end_lineno]
+
+ # Remove every old dataN/hitN statement together with its leading comment
+ # trivia. They are re-emitted below in deterministic numeric order.
+ removals: list[tuple[int, int]] = []
+ for index, statement, _ in selected_statements:
+ previous_end = runtime_class.body[index - 1].end_lineno if index else runtime_class.lineno
+ removals.append((previous_end, statement.end_lineno))
+ if removals:
+ insertion_line = min(start for start, _ in removals)
+ else:
+ insertion_anchor = next(
+ (
+ statement
+ for statement in runtime_class.body
+ if _statement_key(statement) == "associate"
+ or isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef))
+ ),
+ None,
+ )
+ insertion_line = (
+ insertion_anchor.lineno - 1
+ if insertion_anchor is not None
+ else runtime_class.end_lineno
+ )
+ for start, end in sorted(removals, reverse=True):
+ runtime_lines[start:end] = []
+
+ data_keys = sorted(
+ set(generated_data) | {key for key in runtime_items if _is_data_key(key)},
+ key=numeric_key,
+ )
+ rebuilt: list[str] = []
+ emitted_hits: set[str] = set()
+ for data_key in data_keys:
+ source_items = generated_items if data_key in generated_data else runtime_items
+ source_lines = generated_lines if data_key in generated_data else runtime_block.splitlines(keepends=True)
+ source_class = generated_class if data_key in generated_data else runtime_class
+ index, statement = source_items[data_key]
+ rebuilt.extend(segment(source_lines, source_class, index, statement))
+
+ hit_key = f"hit{numeric_key(data_key)}"
+ if hit_key in runtime_items:
+ _, hit_statement = runtime_items[hit_key]
+ rebuilt.extend(runtime_block.splitlines(keepends=True)[hit_statement.lineno - 1:hit_statement.end_lineno])
+ emitted_hits.add(hit_key)
+
+ # Retain unusual hitN fields even when no matching dataN exists.
+ for hit_key in sorted(
+ (
+ key
+ for key in runtime_items
+ if re.fullmatch(r"hit\d+", key) and key not in emitted_hits
+ ),
+ key=numeric_key,
+ ):
+ _, hit_statement = runtime_items[hit_key]
+ rebuilt.extend(runtime_block.splitlines(keepends=True)[hit_statement.lineno - 1:hit_statement.end_lineno])
+
+ runtime_lines[insertion_line:insertion_line] = rebuilt
+ return "".join(runtime_lines)
+
+
+def _sync_generated_comments(runtime_block: str, generated_block: str) -> str:
+ """Sync generated docstrings and leading field comments into runtime code."""
+ runtime_class = ast.parse(runtime_block).body[0]
+ generated_class = ast.parse(generated_block).body[0]
+ if not isinstance(runtime_class, ast.ClassDef) or not isinstance(generated_class, ast.ClassDef):
+ return runtime_block
+
+ runtime_lines = runtime_block.splitlines(keepends=True)
+ generated_lines = generated_block.splitlines(keepends=True)
+ replacements: list[tuple[int, int, list[str]]] = []
+
+ # The generated API description is authoritative, but executable methods
+ # and assignments continue to come from the runtime implementation.
+ runtime_doc = runtime_class.body[0] if runtime_class.body and isinstance(runtime_class.body[0], ast.Expr) and isinstance(runtime_class.body[0].value, ast.Constant) and isinstance(runtime_class.body[0].value.value, str) else None
+ generated_doc = generated_class.body[0] if generated_class.body and isinstance(generated_class.body[0], ast.Expr) and isinstance(generated_class.body[0].value, ast.Constant) and isinstance(generated_class.body[0].value.value, str) else None
+ if runtime_doc is not None and generated_doc is not None:
+ replacements.append(
+ (runtime_doc.lineno - 1, runtime_doc.end_lineno, generated_lines[generated_doc.lineno - 1:generated_doc.end_lineno])
+ )
+ elif runtime_doc is None and generated_doc is not None:
+ # Insert the generated skill description immediately below the class
+ # declaration when the old runtime class had no docstring.
+ replacements.append(
+ (
+ runtime_class.lineno,
+ runtime_class.lineno,
+ generated_lines[generated_doc.lineno - 1:generated_doc.end_lineno],
+ )
+ )
+
+ generated_by_key = {_statement_key(statement): (index, statement) for index, statement in enumerate(generated_class.body) if _statement_key(statement)}
+ for runtime_index, runtime_statement in enumerate(runtime_class.body):
+ key = _statement_key(runtime_statement)
+ if key not in generated_by_key:
+ continue
+ generated_index, generated_statement = generated_by_key[key]
+ runtime_previous_end = runtime_class.body[runtime_index - 1].end_lineno if runtime_index else runtime_class.lineno
+ generated_previous_end = generated_class.body[generated_index - 1].end_lineno if generated_index else generated_class.lineno
+ generated_trivia = generated_lines[generated_previous_end:generated_statement.lineno - 1]
+ # Only replace trivia when it carries actual generated comments. Blank
+ # spacing alone should not disturb hand-formatted runtime code.
+ if any(line.lstrip().startswith("#") for line in generated_trivia):
+ replacements.append(
+ (runtime_previous_end, runtime_statement.lineno - 1, generated_trivia)
+ )
+
+ for start, end, replacement in sorted(replacements, reverse=True):
+ runtime_lines[start:end] = replacement
+ return "".join(runtime_lines)
+
+
+def _append_missing_methods(
+ runtime_block: str, generated_block: str, method_names: set[str]
+) -> str:
+ """Append generated class methods that the runtime skill does not define."""
+ runtime_class = ast.parse(runtime_block).body[0]
+ generated_class = ast.parse(generated_block).body[0]
+ if not isinstance(runtime_class, ast.ClassDef) or not isinstance(generated_class, ast.ClassDef):
+ return runtime_block
+ existing = {
+ statement.name
+ for statement in runtime_class.body
+ if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef))
+ }
+ generated_lines = generated_block.splitlines(keepends=True)
+ additions: list[str] = []
+ for statement in generated_class.body:
+ if not isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef)):
+ continue
+ if statement.name not in method_names or statement.name in existing:
+ continue
+ additions.extend(generated_lines[statement.lineno - 1:statement.end_lineno])
+ existing.add(statement.name)
+ if not additions:
+ return runtime_block
+ return runtime_block.rstrip() + "\n\n" + "".join(additions).rstrip() + "\n"
+
+
+def _ensure_get_data_import(source: str) -> str:
+ if not re.search(r"(?m)^\s*data\d+\s*=\s*get_data\(", source):
+ return source
+ pattern = re.compile(r"(?m)^(from\s+core\.basic\.skill\s+import\s+)([^\n]+)$")
+ match = pattern.search(source)
+ if match is None:
+ raise ValueError("generated data requires a core.basic.skill import")
+ imports = [item.strip() for item in match.group(2).split(",")]
+ if "get_data" in imports:
+ return source
+ imports.append("get_data")
+ return source[:match.start()] + match.group(1) + ", ".join(imports) + source[match.end():]
+
+
+def _sync_module_assignment(
+ runtime_source: str, generated_source: str, field: str
+) -> str:
+ """Make a module-level assignment match the generated module."""
+ runtime_tree = ast.parse(runtime_source)
+ generated_tree = ast.parse(generated_source)
+ generated_statement = next(
+ (
+ statement
+ for statement in generated_tree.body
+ if _statement_key(statement) == field
+ ),
+ None,
+ )
+ if generated_statement is None:
+ return runtime_source
+ runtime_statements = [
+ statement
+ for statement in runtime_tree.body
+ if _statement_key(statement) == field
+ ]
+ runtime_lines = runtime_source.splitlines(keepends=True)
+ generated_lines = generated_source.splitlines(keepends=True)
+ assignment = generated_lines[
+ generated_statement.lineno - 1:generated_statement.end_lineno
+ ]
+ if runtime_statements:
+ insertion_line = runtime_statements[0].lineno - 1
+ for statement in sorted(runtime_statements, key=lambda item: item.lineno, reverse=True):
+ runtime_lines[statement.lineno - 1:statement.end_lineno] = []
+ replacement = assignment
+ else:
+ imports = [
+ statement
+ for statement in runtime_tree.body
+ if isinstance(statement, (ast.Import, ast.ImportFrom))
+ ]
+ insertion_line = imports[-1].end_lineno if imports else 0
+ replacement = [*assignment, "\n"]
+ runtime_lines[insertion_line:insertion_line] = replacement
+ return "".join(runtime_lines)
+
+
+def _comment(block: str, name: str) -> str:
+ lines = [f"# MIGRATION: character 中没有同名技能“{name}”,暂不启用\n"]
+ lines.extend("# " + line.rstrip() + "\n" if line.strip() else "#\n" for line in block.splitlines())
+ return "".join(lines)
+
+
+def merge(generated_path: Path, character_path: Path) -> tuple[str, dict[str, int]]:
+ generated = generated_path.read_text(encoding="utf-8")
+ character = character_path.read_text(encoding="utf-8")
+ generated_skills = _skills(generated, generated_path)
+ character_skills = _skills(character, character_path)
+ if not generated_skills or not character_skills:
+ raise ValueError(f"missing skills: {generated_path} or {character_path}")
+ runtime = {skill.skill_name: skill for skill in character_skills}
+ if len(runtime) != len(character_skills):
+ raise ValueError(f"duplicate skill name: {character_path}")
+
+ character_header_end, character_footer_start = _module_skill_bounds(character, character_path)
+ generated_header_end, _ = _module_skill_bounds(generated, generated_path)
+ chunks = [character[:character_header_end].rstrip() + "\n\n"]
+ chunks.append(generated[generated_header_end:generated_skills[0].start])
+ cursor = generated_skills[0].start
+ matched = commented = 0
+ for index, generated_skill in enumerate(generated_skills):
+ if index:
+ chunks.append(generated[cursor:generated_skill.start])
+ runtime_skill = runtime.get(generated_skill.skill_name)
+ if runtime_skill is None:
+ chunks.append(_comment(generated[generated_skill.start:generated_skill.end], generated_skill.skill_name))
+ commented += 1
+ else:
+ block = character[runtime_skill.start:runtime_skill.end]
+ generated_block = generated[generated_skill.start:generated_skill.end]
+ block = _sync_generated_assignment(block, generated_block, "vps")
+ block = _sync_generated_data(block, generated_block)
+ block = _sync_generated_comments(block, generated_block)
+ block = _set_generated_field(block, "position", generated_skill.position)
+ block = _set_generated_field(block, "cellPos", generated_skill.cell_pos)
+ block = _append_missing_methods(block, generated_block, {"vp_1", "vp_2"})
+ chunks.append(block.rstrip() + "\n")
+ matched += 1
+ cursor = generated_skill.end
+ chunks.append("\n" + character[character_footer_start:].lstrip("\r\n"))
+ merged = "".join(chunks)
+ merged = "\n".join(line.rstrip() for line in merged.splitlines()) + "\n"
+ merged = _sync_module_assignment(merged, generated, "prefix")
+ merged = _ensure_get_data_import(merged)
+ return merged, {
+ "generated": len(generated_skills), "runtime": len(character_skills),
+ "matched": matched, "commented": commented,
+ "runtime_only": len(character_skills) - matched,
+ }
+
+
+def _atomic_write(path: Path, content: str) -> None:
+ """Write beside the target and atomically replace it on Windows.
+
+ Writing an already-open Python source file directly can intermittently
+ produce ``OSError: [Errno 22]`` on Windows. A sibling temporary file also
+ prevents a failed write from leaving a partially truncated module.
+ """
+ temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
+ try:
+ temporary.write_bytes(content.encode("utf-8"))
+ for attempt in range(3):
+ try:
+ os.replace(temporary, path)
+ return
+ except OSError:
+ if attempt == 2:
+ raise
+ time.sleep(0.1 * (attempt + 1))
+ except OSError as error:
+ raise OSError(
+ error.errno,
+ f"failed to replace {path} using temporary file {temporary}: {error.strerror}",
+ str(path),
+ ) from error
+ finally:
+ temporary.unlink(missing_ok=True)
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--check", action="store_true", help="validate without writing")
+ args = parser.parse_args()
+ totals = {key: 0 for key in ("files", "generated", "runtime", "matched", "commented", "runtime_only")}
+ for generated_path in sorted(GENERATED_ROOT.rglob("*.py")):
+ character_path = CHARACTER_ROOT / generated_path.relative_to(GENERATED_ROOT)
+ if not character_path.exists():
+ print(f"SKIP {generated_path.relative_to(GENERATED_ROOT)}: no runtime profession")
+ continue
+ output, stats = merge(generated_path, character_path)
+ ast.parse(output, filename=str(character_path))
+ if not args.check:
+ _atomic_write(character_path, output)
+ totals["files"] += 1
+ for key, value in stats.items():
+ totals[key] += value
+ print(f"{'CHECK' if args.check else 'WRITE'} {generated_path.relative_to(GENERATED_ROOT)}: "
+ f"matched={stats['matched']} commented={stats['commented']} runtime_only={stats['runtime_only']}")
+ print("TOTAL " + " ".join(f"{key}={value}" for key, value in totals.items()))
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/remove_character_remove_lines.py b/scripts/remove_character_remove_lines.py
new file mode 100644
index 00000000..f12c0c93
--- /dev/null
+++ b/scripts/remove_character_remove_lines.py
@@ -0,0 +1,38 @@
+"""Batch-remove every line containing ``# REMOVE`` from character modules."""
+
+from __future__ import annotations
+
+import argparse
+import ast
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+CHARACTER_ROOT = ROOT / "backend" / "core" / "character"
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--check", action="store_true", help="report only; do not write")
+ args = parser.parse_args()
+ files = removed_total = 0
+ for path in sorted(CHARACTER_ROOT.rglob("*.py")):
+ source = path.read_text(encoding="utf-8")
+ lines = source.splitlines(keepends=True)
+ kept = [line for line in lines if "# REMOVE" not in line]
+ removed = len(lines) - len(kept)
+ if not removed:
+ continue
+ output = "".join(kept)
+ ast.parse(output, filename=str(path))
+ files += 1
+ removed_total += removed
+ print(f"{'CHECK' if args.check else 'WRITE'} {path.relative_to(ROOT)}: {removed}")
+ if not args.check:
+ path.write_text(output, encoding="utf-8", newline="\n")
+ print(f"TOTAL files={files} lines={removed_total}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())