1
1
mirror of https://github.com/Rundll86/Dog-Lynx-And-HCN.git synced 2026-05-28 06:51:54 +08:00
Files
Dog-Lynx-And-HCN/scripts/Structs/Weapon.gd
T

324 lines
10 KiB
GDScript
Raw Normal View History

@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
2025-09-06 15:24:50 +08:00
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():
2025-09-06 15:24:50 +08:00
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