1
1
mirror of https://github.com/Rundll86/Dog-Lynx-And-HCN.git synced 2026-05-27 22:41:56 +08:00
Files
Dog-Lynx-And-HCN/scripts/Structs/Weapon.gd
T
fallingshrimp 6b7801e1ce feat(武器系统): 为BigLaser武器添加执行伤害和升华选项
- 在ObstacleBase和EntityBase中添加getHealthPercent方法用于获取生命值百分比
- 修改bulletHit方法支持伤害覆盖参数
- 为BigLaser武器添加5个升华选项,包括临界斩杀效果
- 实现damageOverride方法根据目标生命值动态调整伤害
- 修复store数值可能为负数的问题
2026-05-10 14:58:05 +08:00

323 lines
10 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@tool
extends PanelContainer
class_name Weapon
enum EmitType {
HOLD_SHOOT,
CLICK_SHOOT,
CHARGE,
HOLD_LOOP
}
signal sublimateOpened()
signal sublimateClosed()
@export var avatarTexture: Texture2D = null
@export var displayName: String = "未命名武器"
@export var quality: WeaponName.Quality = WeaponName.Quality.COMMON
@export var typeTopic: WeaponName.TypeTopic = WeaponName.TypeTopic.IMPACT
@export var soulLevel: int = 1
@export var costBeachball: int = 500
@export var emitType: EmitType = EmitType.HOLD_SHOOT
@export var store: Dictionary = {
"atk": 10
}
@export var storeType: Dictionary = {
"atk": FieldStore.DataType.INTEGER
}
@export_multiline var descriptionTemplate: String = "造成$atk点伤害。"
@export var sources: Array[String] = []
@export var tease: String = ""
@export var needEnergy: float = 0
@export var cooldown: float = 100
@export var debugRebuild: bool = false
@export var level: int = 0
@onready var avatarRect: TextureRect = $%avatar
@onready var nameLabel: WeaponName = $%name
@onready var sourceLabel: Label = $%source
@onready var teaseLabel: Label = $%tease
@onready var energyLabel: Label = $%energy
@onready var beachball: ItemShow = $%beachball
@onready var soul: ItemShow = $%soul
@onready var descriptionLabel: RichTextLabel = $%description
@onready var sounds: Node2D = $%sounds
@onready var moveLeftBtn: Button = $%moveleft
@onready var moveRightBtn: Button = $%moveright
@onready var animator: AnimationPlayer = $%animator
@onready var autoUpdateBtn: Button = $%autoUpdateBtn
@onready var onceUpdateBtn: Button = $%onceUpdateBtn
@onready var updateBtn: Button = $%updateBtn
@onready var extractBtn: Button = $%extractBtn
@onready var inlayBtn: Button = $%inlayBtn
@onready var sublimateBtn: Button = $%sublimateBtn
@onready var sublimateOptionsBox: Control = $%sublimateOptions
var cooldownTimer: CooldownTimer = null
var originalStore: Dictionary = {}
var chargedTime: float = 0
var attackSpeed: float = 1
var looping: bool = false
var autoUpdate: bool = false
var storeExtra: Dictionary = {}
var sublimateOptionsStored: Array[SublimateOption] = []
func _ready():
cooldownTimer = CooldownTimer.new()
cooldownTimer.cooldown = cooldown
originalStore = store
sublimateBtn.toggled.connect(
func(on: bool):
if on:
animator.play("openSub")
sublimateOpened.emit()
else:
animator.play("closeSub")
sublimateClosed.emit()
)
autoUpdateBtn.toggled.connect(
func(on: bool):
autoUpdate = on
)
onceUpdateBtn.pressed.connect(
func():
var count = 0
while canUpdate(UIState.player):
updateApply(UIState.player)
count += 1
if count > 0:
UIState.showTip("一键强化提升了[b]%d[/b]级!" % count)
else:
UIState.showTip("一键强化没有提升等级......", TipBox.MessageType.ERROR)
)
updateBtn.pressed.connect(
func():
updateApply(UIState.player)
)
extractBtn.pressed.connect(
func():
if soulLevel > WeaponName.SoulLevel.NORMALIZE:
UIState.player.getItem({
ItemStore.ItemType.SOUL: soulLevel - 1
})
soulLevel -= 1
updateStore(level, UIState.player)
rebuildInfo()
else:
UIState.showTip("[b]%s[/b]还未镶嵌任何灵魂!" % displayName, TipBox.MessageType.ERROR)
)
inlayBtn.pressed.connect(
func():
if soulLevel < WeaponName.SoulLevel.INFINITY:
if UIState.player.useItem({
ItemStore.ItemType.SOUL: soulLevel
}):
soulLevel += 1
updateStore(level, UIState.player)
rebuildInfo()
else:
UIState.showTip("持有的灵魂数量不足!", TipBox.MessageType.ERROR)
else:
UIState.showTip("[b]%s[/b]的灵魂槽位已满!" % displayName, TipBox.MessageType.ERROR)
)
moveLeftBtn.pressed.connect(
func():
if get_parent():
var myIndex = get_index()
var leftIndex = max(myIndex - 1, 0)
get_parent().move_child(self , leftIndex)
ArrayTool.swap(UIState.player.weapons, myIndex, leftIndex)
UIState.player.rebuildWeaponIcons()
)
moveRightBtn.pressed.connect(
func():
if get_parent():
var myIndex = get_index()
var rightIndex = min(myIndex + 1, get_parent().get_child_count() - 1)
get_parent().move_child(self , rightIndex)
ArrayTool.swap(UIState.player.weapons, myIndex, rightIndex)
UIState.player.rebuildWeaponIcons()
)
for i in sounds.get_children():
i.process_mode = ProcessMode.PROCESS_MODE_ALWAYS
debugRebuild = false # 只能在编辑器里打开
rebuildInfo()
func _physics_process(_delta):
if debugRebuild:
rebuildInfo()
func canUpdate(entity: EntityBase):
return entity.hasItem({ItemStore.ItemType.BEACHBALL: costBeachball})
func canInlay():
return UIState.player.hasItem({ItemStore.ItemType.SOUL: soulLevel})
func updateApply(entity: EntityBase) -> bool:
if canUpdate(entity):
level += 1
entity.inventory[ItemStore.ItemType.BEACHBALL] -= costBeachball
updateStore(level, entity)
costBeachball = floor(GameRule.weaponUpdateCost * costBeachball)
rebuildInfo(true)
return true
else:
UIState.showTip("沙滩球不足!", TipBox.MessageType.ERROR)
return false
func updateStore(to: int, entity: EntityBase):
store = update(to, originalStore.duplicate(), entity)
func multipiler() -> float:
if is_instance_valid(UIState.player):
return 1 - UIState.player.fields.get(FieldStore.Entity.PRICE_REDUCTION)
else:
return 1
func rebuildInfo(showNext: bool = false):
rebuildBaseInfo()
rebuildDescription(showNext)
rebuildSublimateOptions(showNext)
func rebuildBaseInfo():
avatarRect.texture = avatarTexture
nameLabel.displayName = displayName
nameLabel.quality = quality
nameLabel.typeTopic = typeTopic
nameLabel.soulLevel = soulLevel
nameLabel.level = level
sourceLabel.text = " × ".join(sources)
if len(tease) > 0:
teaseLabel.text = "%s" % tease
teaseLabel.show()
else:
teaseLabel.hide()
energyLabel.text = "%.1f" % needEnergy
beachball.count = costBeachball
soul.count = soulLevel
if is_instance_valid(UIState.player):
beachball.enough = canUpdate(UIState.player)
soul.enough = canInlay()
func formatValue(value: Variant, type: FieldStore.DataType) -> String:
if type == FieldStore.DataType.VALUE:
return "%.2f" % value
elif type == FieldStore.DataType.INTEGER:
return "%d" % value
elif type == FieldStore.DataType.PERCENT:
return ("%.1f" % (value * 100)) + "%"
elif type == FieldStore.DataType.ANGLE:
return "%.1f°" % value
elif type == FieldStore.DataType.FREQUENCY:
return "%.1fHz" % value
else:
return str(value)
func buildDescription(showNext: bool = false) -> String:
var next = update(level + 1, originalStore.duplicate(), UIState.player)
var result = descriptionTemplate
for key in store.keys():
var data = readStore(key)
var nextData = next[key]
var type = storeType.get(key, FieldStore.DataType.VALUE)
data = formatValue(data, type)
nextData = formatValue(nextData, type)
var text
if showNext:
text = "[color=cyan]%s[/color]→[color=yellow]%s[/color]" % [data, nextData]
else:
text = "[color=cyan]%s[/color]" % data
result = result.replace("$" + key, text)
return result
func rebuildDescription(showNext: bool):
descriptionLabel.text = buildDescription(showNext && (canUpdate(UIState.player) || canInlay()))
func rebuildSublimateOptions(showNext: bool):
for i in sublimateOptionsBox.get_children():
sublimateOptionsBox.remove_child(i)
for sublimate in getSublimateOptions():
var instance = ComponentManager.getUIComponent("SublimateOption").instantiate() as SublimateOptionHandler
instance.use = sublimate
instance.apply.connect(
func():
sublimate.apply(UIState.player, self )
rebuildBaseInfo()
rebuildDescription(showNext)
instance.rebuildInfo()
disruptSublimateOptions()
)
sublimateOptionsBox.add_child(instance)
disruptSublimateOptions()
func readStore(key: String):
return clamp(store.get(key, 0) + readStoreExtra(key), 0, INF)
func playSound(sound: String):
var body = sounds.get_node_or_null(sound)
if body is AudioStreamPlayer2D:
var cloned = body.duplicate() as AudioStreamPlayer2D
add_child(cloned)
cloned.play()
await cloned.finished
cloned.queue_free()
func canAttackBy(entity: EntityBase):
cooldownTimer.speedScale = entity.fields.get(FieldStore.Entity.ATTACK_SPEED) * attackSpeed
return cooldownTimer.isCooldowned() and entity.isEnergyEnough(needEnergy) and checkAttack(entity)
func tryAttack(entity: EntityBase):
if looping:
if checkAttack(entity):
return await attack(entity)
else:
exitLoop(entity)
else:
if canAttackBy(entity):
if emitType == EmitType.HOLD_LOOP:
var result = await loopStart(entity)
if result:
looping = true
cooldownTimer.start()
entity.useEnergy(needEnergy)
return result
else:
var result = await attack(entity)
if result:
cooldownTimer.start()
entity.useEnergy(needEnergy)
return result
func charged(base: float, percent: float):
return base * sqrt(1 + chargedTime / 15 * percent)
func exitLoop(entity: EntityBase):
if !looping: return
looping = false
loopExit(entity)
func addStoreExtra(key: String, value: float):
if !storeExtra.has(key):
storeExtra[key] = 0
storeExtra[key] += value
func readStoreExtra(key: String):
return storeExtra.get(key, 0)
func getSublimateOptions() -> Array[SublimateOption]:
if len(sublimateOptionsStored) == 0:
sublimateOptionsStored = sublimateOptions()
return sublimateOptionsStored
func disruptSublimateOptions():
var children = sublimateOptionsBox.get_children()
children.shuffle()
for index in len(children):
sublimateOptionsBox.remove_child(children[index])
for index in len(children):
var child = children[index]
if child is SublimateOptionHandler:
child.visible = index < 3
sublimateOptionsBox.add_child(child)
# 抽象
func sublimateOptions() -> Array[SublimateOption]:
return [
SublimateOption.new("强化攻击", "伤害+1",
func(w: Weapon, _e): w.addStoreExtra("atk", 1),
1,
CategoryStore.Quality.COMMON
),
]
func update(_to: int, origin: Dictionary, _entity: EntityBase):
return origin
func loopStart(_entity: EntityBase):
pass
func checkAttack(_entity: EntityBase) -> bool:
return true
func attack(_entity: EntityBase):
pass
func loopExit(_entity: EntityBase):
pass