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/Statemachine/BulletBase.gd
T

267 lines
8.6 KiB
GDScript
Raw Normal View History

2025-08-26 11:39:47 +08:00
extends Area2D
class_name BulletBase
@export var displayName: String = "未知子弹"
@export var speed: float = 10.0
@export var baseDamage: float = 10.0
@export var damageMultipliers: Array[float] = [1.0]
@export var usingDamageMultiplier: int = 0
@export var penerate: float = 0.0
@export var penerateDamageReduction: float = 0.0
@export var lifeDistance: float = -1 # -1表示无限距离
@export var lifeTime: float = -1 # -1表示无限时间
@export var allowFriendlyDamage: bool = false # 是否无差别伤害(不区分敌我)
@export var canDamageSelf: bool = false # 是否可以伤害发射者
@export var autoSpawnAnimation: bool = false
@export var autoLoopAnimation: bool = false
@export var autoDestroyAnimation: bool = false
@export var autoDestroyOnHitMap: bool = true
@export var autoPlayTexture: bool = false
@export var freeAfterSpawn: bool = false
@export var knockback: float = 0 # 击退力,物理引擎单位
@export var recoil: float = 0 # 后坐力,物理引擎单位
@export var canDoDuplicate: bool = true # 是否可以分裂、折射
2025-08-26 11:39:47 +08:00
@onready var animator: AnimationPlayer = $"%animator"
@onready var hitbox: CollisionShape2D = $"%hitbox"
@onready var texture: AnimatedSprite2D = $"%texture"
2025-08-26 11:39:47 +08:00
var launcher: EntityBase = null
var launcherSummoned: EntityBase = null
var parent: BulletBase = null
var spawnInWhen: float = 0
var spawnInWhere: Vector2 = Vector2.ZERO
var destroying: bool = false
var canDuplicateSelf: bool = true
var initialSpeed: float = 0
var initialDamage: float = 0
var speedScale: float = 1
var isFirstFrame: bool = true
var cycleStateAngle: float = 0
var lastDelta: float = 0
2025-08-26 11:39:47 +08:00
func _ready():
initialSpeed = speed
initialDamage = baseDamage
if launcher.isSummon():
launcherSummoned = launcher
launcher = launcher.myMaster
register()
area_entered.connect(hitEntity)
body_entered.connect(hitObstacle)
spawnInWhen = WorldManager.getTime()
spawnInWhere = position
spawn()
2025-08-28 21:14:36 +08:00
dotLoop()
if autoLoopAnimation:
animator.play("loop")
if autoPlayTexture:
texture.play("default")
body_entered.connect(
func(body):
if body.is_in_group("map"):
if autoDestroyOnHitMap:
tryDestroy(true)
)
area_entered.connect(
func(body):
var bullet = BulletTool.fromArea(body)
if is_instance_valid(bullet):
hitBullet(bullet)
)
ai()
if autoSpawnAnimation:
animator.play("spawn")
await animator.animation_finished
afterSpawn()
if freeAfterSpawn:
tryDestroy()
func _process(_delta: float) -> void:
if destroying: return
if lifeTime > 0:
if WorldManager.getTime() - spawnInWhen >= lifeTime:
tryDestroy()
if lifeDistance > 0:
if position.distance_to(spawnInWhere) >= lifeDistance:
tryDestroy()
func _physics_process(delta: float) -> void:
lastDelta = delta
if destroying: return
if is_instance_valid(launcher) and (launcher.isPlayer() or is_instance_valid(launcher.currentFocusedBoss)):
launcher.position -= Vector2.from_angle(rotation) * recoil
var targetEntity = EntityTool.findClosetEntity(position, get_tree(),
!launcher.isPlayer(),
launcher.isPlayer(),
[launcher])
if is_instance_valid(targetEntity):
PresetBulletAI.trace(
self ,
targetEntity.getTrackingAnchor(),
launcher.fields.get(FieldStore.Entity.BULLET_TRACE) / 10
)
if isFirstFrame:
firstFrame()
isFirstFrame = false
ai()
else:
tryDestroy()
2025-08-26 11:39:47 +08:00
func findAnchor(anch: String):
var node = get_node_or_null("%anchor" + anch)
if node is Node2D:
return node.global_position
else:
return Vector2.ZERO
func setupCuttable(cutSpeed: float):
area_entered.connect(
func(body):
var entity = EntityTool.fromHurtbox(body)
if entity:
speedScale = cutSpeed
)
area_exited.connect(
func(body):
var entity = EntityTool.fromHurtbox(body)
if entity:
speedScale = 1
)
func getDamage():
return baseDamage * damageMultipliers[usingDamageMultiplier]
func calculateDamage(crit: bool):
var baseDmg = getDamage() * launcher.fields.get(FieldStore.Entity.DAMAGE_MULTIPILER) * randf_range(1 - GameRule.damageOffset, 1 + GameRule.damageOffset)
var damage = baseDmg + baseDmg * int(crit) * launcher.fields.get(FieldStore.Entity.CRIT_DAMAGE)
return damage
func determineCrit():
return MathTool.rate(launcher.fields.get(FieldStore.Entity.CRIT_RATE) + GameRule.critRateInfluenceByLuckValue * launcher.fields[FieldStore.Entity.LUCK_VALUE])
func hitEntity(target: Node):
2025-08-26 11:39:47 +08:00
var entity: EntityBase = EntityTool.fromHurtbox(target)
if !BulletTool.canDamage(self , entity): return
var resultDamage = entity.bulletHit(self , determineCrit())
succeedToHit(resultDamage, entity)
if MathTool.rate(fullPenerate() - entity.fields[FieldStore.Entity.PENARATION_RESISTANCE]):
baseDamage *= 1.0 - penerateDamageReduction
else:
tryDestroy()
func hitObstacle(target: Node):
if target is ObstacleBase:
var obstacle = target as ObstacleBase
if is_instance_valid(obstacle.launcher):
if not BulletTool.canDamage(self , obstacle.launcher): return
obstacle.takeDamage(calculateDamage(determineCrit()))
if MathTool.rate(fullPenerate() - obstacle.penerateResistance):
baseDamage *= 1.0 - penerateDamageReduction
else:
tryDestroy()
func forward(direction: Vector2):
position += direction.normalized() * speed * GameRule.bulletSpeedMultiplier
func fullPenerate():
return penerate + launcher.fields.get(FieldStore.Entity.PENERATE) + GameRule.penerateRateInfluenceByLuckValue * launcher.fields[FieldStore.Entity.LUCK_VALUE]
func timeLived():
return WorldManager.getTime() - spawnInWhen
func distanceLived():
return position.distance_to(spawnInWhere)
func lifeTimePercent():
return timeLived() / lifeTime
func lifeDistancePercent():
return distanceLived() / lifeDistance
func dotLoop():
if await applyDot():
await TickTool.until(func(): return !UIState.currentPanel)
await dotLoop()
func tryDestroy(becauseMap: bool = false):
if destroying: return
destroying = true
if canDoDuplicate:
trySplit()
tryRefract()
await destroy(becauseMap)
if autoDestroyAnimation:
animator.play("destroy")
await animator.animation_finished
queue_free()
func trySplit():
if is_instance_valid(launcher) and canDuplicateSelf:
var value = launcher.fields.get(FieldStore.Entity.BULLET_SPLIT)
var total = MathTool.shrimpRate(value)
var last = value - floor(value)
for i in total:
var cloned = duplicate() as BulletBase
cloned.rotation += deg_to_rad(360.0 / total * i + 180)
cloned.canDuplicateSelf = false
cloned.launcher = launcher
if is_instance_valid(parent):
cloned.parent = parent
var splited = split(cloned, i, total, last)
if !is_instance_valid(splited): continue
get_parent().add_child.call_deferred(splited)
func tryRefract():
if is_instance_valid(launcher) and canDuplicateSelf:
var value = launcher.fields.get(FieldStore.Entity.BULLET_REFRACTION)
var total = MathTool.shrimpRate(value)
var last = value - floor(value)
var aimed: Array[EntityBase] = []
for i in total:
var entity = EntityTool.findClosetEntity(
position, get_tree(),
!launcher.isPlayer(),
launcher.isPlayer(),
[launcher] + aimed
)
if is_instance_valid(entity):
aimed.append(entity)
var cloned = duplicate() as BulletBase
cloned.look_at(entity.position)
cloned.canDuplicateSelf = false
cloned.launcher = launcher
if is_instance_valid(parent):
cloned.parent = parent
var refracted = refract(cloned, entity, i, total, last)
if !is_instance_valid(refracted): continue
get_parent().add_child.call_deferred(refracted)
# 抽象方法
func firstFrame():
pass
func ai():
pass
func destroy(_beacuseMap: bool):
pass
func spawn():
pass
func afterSpawn():
pass
func applyDot():
pass
func succeedToHit(_dmg: float, _entity: EntityBase):
pass
func register():
pass
func split(newBullet: BulletBase, _index: int, _total: int, _lastBullet: float):
return newBullet
func refract(newBullet: BulletBase, _entity: EntityBase, _index: int, _total: int, _lastBullet: float):
return newBullet
func hitBullet(_bullet: BulletBase):
pass
2025-08-26 11:39:47 +08:00
static func generate(
bullet: PackedScene,
launchBy: EntityBase,
spawnPosition: Vector2,
spawnRotation: float,
addToWorld: bool = true,
ignoreOffset: bool = false
2025-08-26 11:39:47 +08:00
):
var extraCount = launchBy.fields.get(FieldStore.Entity.EXTRA_BULLET_COUNT)
var count = 1 + MathTool.shrimpRate(extraCount)
var instances = []
for i in range(count):
var instance: BulletBase = bullet.instantiate()
instance.launcher = launchBy
instance.position = spawnPosition
instance.rotation = spawnRotation + deg_to_rad(launchBy.fields.get(FieldStore.Entity.OFFSET_SHOOT) * randf_range(-1, 1) * int(!ignoreOffset))
if addToWorld:
WorldManager.rootNode.call_deferred("add_child", instance)
instance.add_to_group("bullets")
instances.append(instance)
return instances