mirror of
https://github.com/Rundll86/Dog-Lynx-And-HCN.git
synced 2026-05-27 22:41:56 +08:00
1b3df9727a
- 将sublimateToggled信号拆分为sublimateOpened和sublimateClosed - 添加武器卡片的hide/show动画效果 - 移除MuyangDog角色中的Volcano武器 - 重置EntityBase中钻石的初始数量为0 - 为CategoryStore添加@tool注解
324 lines
10 KiB
GDScript
324 lines
10 KiB
GDScript
@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 store.get(key, 0) + readStoreExtra(key)
|
||
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
|
||
storeExtra[key] = clamp(storeExtra[key], 0, INF)
|
||
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
|