You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
737 lines
30 KiB
737 lines
30 KiB
import { _decorator, Component, director, ERigidBody2DType, instantiate, isValid, Node, Prefab, RigidBody2D, sp, Tween, v2, v3, Vec3 } from "cc";
|
|
import { LaserContinueSchedule } from "./LaserContinueSchedule";
|
|
import { GBundle } from "../../../game/ConfigRes";
|
|
import { WeaponType } from "../../../manager/WeaponDataManager";
|
|
import { BattleDataManager, SpecialSkillType } from "../BattleDataManager";
|
|
import { SkillType, Skill } from "./Skill";
|
|
import { TornadoContinueSchedule } from "./TornadoContinueSchedule";
|
|
import { TreasureSkill } from "./TreasureSkill";
|
|
import { iceWeaponSchedule } from "./iceWeaponSchedule";
|
|
import { FlashlightSchedule } from "./FlashlightSchedule";
|
|
import { GEvent } from "db://assets/mx/module/event/GEvent";
|
|
import { BubbleSchedule } from "./BubbleSchedule";
|
|
import { DroneBullet } from "./DroneBullet";
|
|
import { MapAgent } from "../MapAgent";
|
|
import { LaserLine } from "./LaserLine";
|
|
import { ThunderboltSchedule } from "./ThunderboltSchedule";
|
|
import { TetriStackDiagnostics } from "../../../tetriMap/TetriStackDiagnostics";
|
|
import { BattlePerformance } from "../BattlePerformance";
|
|
import MTools from "db://assets/mx/tools/MTools";
|
|
const { ccclass, property } = _decorator;
|
|
|
|
export class WeaponState {
|
|
public id: number;
|
|
public config: ITableWeapon2;
|
|
public isReady: boolean = true; // 是否可用
|
|
public remainingCD: number = 0; // 剩余冷却时间
|
|
public cd: number = 0; // 冷却总时长(用于 UI 展示)
|
|
public timer: number = 0; // 定时器ID
|
|
|
|
constructor(id: number, config: ITableWeapon2) {
|
|
this.id = id;
|
|
this.config = config;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 技能控制器
|
|
*/
|
|
@ccclass('SkillController')
|
|
export class SkillController extends Component {
|
|
|
|
// 可用武器ID数组
|
|
private availableWeapons: number[] = [];
|
|
// 武器状态表
|
|
public weaponStates: Map<number, WeaponState> = new Map();
|
|
|
|
/**特殊武器发射塔数量 */
|
|
private specialWeaponLaunchCount = {};
|
|
/** 各武器第一次触发狂暴射击(removeCD)时必定生效 */
|
|
private furiousShootFirstTriggeredWeapons = new Set<number>();
|
|
|
|
private _lasers1: Node[] = []
|
|
private _lasers2: Node[] = []
|
|
|
|
dt: number = 0.05
|
|
//冰棍转转转
|
|
|
|
|
|
initAll() {
|
|
this.specialWeaponLaunchCount = {};
|
|
this.furiousShootFirstTriggeredWeapons.clear()
|
|
}
|
|
|
|
/** 结算/退出战斗时回收 ui_skilllayer 下仍挂着的直线弹等(避免 tween/碰撞漏删导致节点泄漏) */
|
|
public recycleSkillLayerBullets(): void {
|
|
const layer = this.node.getChildByName('ui_skilllayer');
|
|
if (!layer?.isValid) return;
|
|
const list = layer.children.slice();
|
|
for (let i = 0; i < list.length; i++) {
|
|
const c = list[i];
|
|
if (!c?.isValid) continue;
|
|
const sk = c.getComponent(Skill);
|
|
if (!sk || sk.getIsDead()) continue;
|
|
Tween.stopAllByTarget(c);
|
|
sk.removeBullet();
|
|
}
|
|
}
|
|
/**刷新武器状态 */
|
|
freshDamageStaWeaponList() {
|
|
this.availableWeapons = []
|
|
let curUseWeaponArr = gg.game.CurentBattle.CurUseWeaponArr.map(item => item.weaponid);
|
|
//设置可用武器数组
|
|
this.availableWeapons = [...curUseWeaponArr];
|
|
|
|
|
|
}
|
|
onDestroy() {
|
|
this.unscheduleAllCallbacks(); // 使用组件自己的方法取消定时器
|
|
}
|
|
|
|
/**
|
|
* 初始化武器状态
|
|
*/
|
|
public initWeaponStates(): void {
|
|
this.weaponStates.clear();
|
|
this.freshDamageStaWeaponList();
|
|
|
|
this.availableWeapons.forEach((weaponId, index) => {
|
|
const config = gg.data.project.getWeaponData(weaponId);
|
|
if (config) {
|
|
let weapon = BattleDataManager.getWeaponAddProperty(weaponId)
|
|
let statusData = new WeaponState(weaponId, config);
|
|
statusData.remainingCD = weapon.cd * BattlePerformance.weaponCdMul();
|
|
statusData.cd = weapon.cd * BattlePerformance.weaponCdMul();
|
|
this.weaponStates.set(weaponId, statusData);
|
|
if (index == 0) {
|
|
statusData.isReady = true;
|
|
}
|
|
}
|
|
});
|
|
// // 激光/无人机发射塔在开局就应与“已拥有武器”状态保持一致,避免必须先发射一次才显示
|
|
this.refreshDroneOrLaserLaunch();
|
|
}
|
|
|
|
/**
|
|
* 释放武器技能
|
|
* @param weaponId 武器ID
|
|
* @returns 是否释放成功
|
|
*/
|
|
public useWeaponSkill(weaponId: number): boolean {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
|
|
if (!weaponState) {
|
|
console.warn(`武器ID ${weaponId} 不存在或不可用`);
|
|
return false;
|
|
}
|
|
|
|
if (!weaponState.isReady) {
|
|
// console.warn(`武器ID ${weaponId} 技能冷却中,剩余时间: ${weaponState.remainingCD}秒`);
|
|
return false;
|
|
}
|
|
// 执行技能释放逻辑
|
|
this.executeWeaponSkill(weaponId);
|
|
|
|
// 开始冷却
|
|
this.startCooldown(weaponId);
|
|
|
|
return true;
|
|
}
|
|
/**
|
|
* 执行武器技能逻辑(宝箱武器)
|
|
* @param weaponId 武器ID
|
|
* @param startPos 起始位置
|
|
* @param isFuel 是否是燃油弹
|
|
*/
|
|
executeTreasureWeaponSkill(weaponId: number) {
|
|
let posY = 25//间距
|
|
let xTime = 0.1//间隔
|
|
//单次发射数量
|
|
let launchCount = 5
|
|
//连发数量
|
|
let burstCount = 1
|
|
for (let i = 0; i < launchCount; i++) {
|
|
let addPos = v3(0, i * posY - (launchCount - 1) * posY / 2, 0)
|
|
for (let j = 0; j < burstCount; j++) {
|
|
this.scheduleOnce(() => {
|
|
void (async () => {
|
|
let weaponNode = await this.instantiateSingleWeapon(weaponId, 1, addPos)
|
|
if (!weaponNode?.isValid) return;
|
|
let skillComp = weaponNode.getComponent(TreasureSkill);
|
|
skillComp.startAtk();
|
|
})();
|
|
}, xTime * j)
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* 执行武器技能逻辑(无人机)
|
|
* @param weaponId 武器ID
|
|
* @param startPos 起始位置
|
|
* @param isFuel 是否是燃油弹
|
|
*/
|
|
async executeDroneWeaponSkill(weaponId: number, startPos: Vec3, targetPos: Vec3, isFuel: boolean) {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) return;
|
|
|
|
let weaponProperty = BattleDataManager.getWeaponAddProperty(weaponId)
|
|
let weaponNode = await this.instantiateSingleWeapon(weaponId, weaponProperty.volume)
|
|
if (!weaponNode || !weaponNode.isValid) return;
|
|
weaponNode.worldPosition = startPos
|
|
let skillComp = weaponNode.getComponent(Skill);
|
|
skillComp.setData(weaponState.config, targetPos, false, isFuel);
|
|
}
|
|
/**
|
|
* 执行武器技能逻辑
|
|
* @param weaponId 武器ID
|
|
*/
|
|
executeWeaponSkill(weaponId: number) {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) return;
|
|
// console.log(`释放武器 ${weaponId} 技能,伤害: ${weaponState.config.hp}`);
|
|
|
|
// 这里添加实际的技能效果逻辑
|
|
// 例如:播放特效、造成伤害等
|
|
let weaponProperty = BattleDataManager.getWeaponAddProperty(weaponId)
|
|
|
|
if (weaponState.config.flight == SkillType.直线弹幕) {
|
|
//解锁了特殊技能移除cd
|
|
let isRemoveCD = false
|
|
let removeCDTime = 0
|
|
for (let i = 0; i < weaponProperty.specialSkillInfo.length; i++) {
|
|
let buffInfo = weaponProperty.specialSkillInfo[i]
|
|
let arr = buffInfo.split(',')
|
|
let buffType = Number(arr[0])
|
|
if (buffType == SpecialSkillType.removeCD) {
|
|
//移除cd
|
|
let rateNum = Number(arr[1]) / 10000//移除cd的概率
|
|
removeCDTime = Number(arr[2])//持续时间
|
|
isRemoveCD = Math.random() < rateNum
|
|
if (!this.furiousShootFirstTriggeredWeapons.has(weaponId)) {
|
|
this.furiousShootFirstTriggeredWeapons.add(weaponId)
|
|
isRemoveCD = true
|
|
}
|
|
this.forceChangeWeaponCD(weaponId, removeCDTime)
|
|
}
|
|
}
|
|
let posX = weaponState.config.bullet_distance //y轴间距
|
|
let xTime = weaponState.config.bullet_column//间隔
|
|
//单次发射数量
|
|
let launchCount = weaponProperty.launchCount
|
|
//连发数量
|
|
let burstCount = isRemoveCD ? Math.floor(removeCDTime / xTime) : weaponProperty.burstCount;
|
|
GEvent.Ins.emit(GEvent.StraightWeaponShoot, weaponProperty, posX, xTime, launchCount, burstCount)
|
|
} else if (weaponState.config.flight == SkillType.直线弹射) {
|
|
//击中目标,再次弹射一下,重新生成一个武器再次寻找一个目标,这个武器不能继续分裂
|
|
let isRemoveCD = false
|
|
let removeCDTime = 0
|
|
for (let i = 0; i < weaponProperty.specialSkillInfo.length; i++) {
|
|
let buffInfo = weaponProperty.specialSkillInfo[i]
|
|
let arr = buffInfo.split(',')
|
|
let buffType = Number(arr[0])
|
|
if (buffType == SpecialSkillType.removeCD) {
|
|
//移除cd
|
|
let rateNum = Number(arr[1]) / 10000//移除cd的概率
|
|
removeCDTime = Number(arr[2])//持续时间
|
|
isRemoveCD = Math.random() < rateNum
|
|
if (!this.furiousShootFirstTriggeredWeapons.has(weaponId)) {
|
|
this.furiousShootFirstTriggeredWeapons.add(weaponId)
|
|
isRemoveCD = true
|
|
}
|
|
this.forceChangeWeaponCD(weaponId, removeCDTime)
|
|
}
|
|
}
|
|
let posX = weaponState.config.bullet_distance //y轴间距
|
|
let xTime = weaponState.config.bullet_column//间隔
|
|
//单次发射数量
|
|
let launchCount = weaponProperty.launchCount
|
|
//连发数量
|
|
let burstCount = isRemoveCD ? Math.floor(removeCDTime / xTime) : weaponProperty.burstCount;
|
|
|
|
GEvent.Ins.emit(GEvent.StraightWeaponLaunch, weaponProperty, posX, xTime, launchCount, burstCount)
|
|
|
|
} else if (weaponState.config.flight == SkillType.直线数量 || weaponState.config.flight == SkillType.直线分裂
|
|
|| weaponState.config.flight == SkillType.直线穿透) {
|
|
//默认一发子弹加上配置的数量
|
|
let isRemoveCD = false
|
|
let removeCDTime = 0
|
|
for (let i = 0; i < weaponProperty.specialSkillInfo.length; i++) {
|
|
let buffInfo = weaponProperty.specialSkillInfo[i]
|
|
let arr = buffInfo.split(',')
|
|
let buffType = Number(arr[0])
|
|
if (buffType == SpecialSkillType.removeCD) {
|
|
//移除cd
|
|
let rateNum = Number(arr[1]) / 10000//移除cd的概率
|
|
removeCDTime = Number(arr[2])//持续时间
|
|
isRemoveCD = Math.random() < rateNum
|
|
if (!this.furiousShootFirstTriggeredWeapons.has(weaponId)) {
|
|
this.furiousShootFirstTriggeredWeapons.add(weaponId)
|
|
isRemoveCD = true
|
|
}
|
|
this.forceChangeWeaponCD(weaponId, removeCDTime)
|
|
}
|
|
}
|
|
let posX = weaponState.config.bullet_distance //y轴间距
|
|
let xTime = weaponState.config.bullet_column//间隔
|
|
//单次发射数量
|
|
let launchCount = weaponProperty.launchCount
|
|
//连发数量
|
|
let burstCount = isRemoveCD ? Math.floor(removeCDTime / xTime) : weaponProperty.burstCount;
|
|
|
|
if(weaponState.config.flight == SkillType.直线数量){
|
|
GEvent.Ins.emit(GEvent.StraightWeaponShootNum, weaponProperty, burstCount, xTime, launchCount, posX)
|
|
}
|
|
else if(weaponState.config.flight == SkillType.直线分裂){
|
|
GEvent.Ins.emit(GEvent.StraightWeaponSplit, weaponProperty, burstCount, xTime, launchCount, posX)
|
|
}
|
|
else if(weaponState.config.flight == SkillType.直线穿透){
|
|
GEvent.Ins.emit(GEvent.StraightWeaponPierce, weaponProperty, burstCount, xTime, launchCount, posX)
|
|
}
|
|
}else if (weaponState.config.flight == SkillType.抛物线碰撞) {
|
|
let xTime = weaponState.config.bullet_column//间隔
|
|
let launchCount = weaponProperty.launchCount
|
|
GEvent.Ins.emit(GEvent.ParabolaCollide, weaponProperty, xTime, launchCount)
|
|
|
|
} else if (weaponState.config.flight == SkillType.抛物线没有碰撞) {
|
|
let xTime = weaponState.config.bullet_column//间隔
|
|
let launchCount = weaponProperty.launchCount
|
|
GEvent.Ins.emit(GEvent.ParabolaNoCollide, weaponProperty, xTime, launchCount)
|
|
|
|
} else if (weaponState.config.flight == SkillType.单点激光) {
|
|
|
|
//连发数量
|
|
let burstCount = weaponProperty.burstCount
|
|
//单次发射数量
|
|
let launchCount = weaponProperty.launchCount
|
|
|
|
GEvent.Ins.emit(GEvent.SinglePointLaser, weaponProperty, burstCount, launchCount)
|
|
}
|
|
else if (weaponState.config.flight == SkillType.直线目标持续伤害) {
|
|
|
|
let posY = weaponState.config.bullet_distance //y轴间距
|
|
let xTime = weaponState.config.bullet_column//间隔
|
|
//单次发射数量
|
|
let launchCount = weaponProperty.launchCount
|
|
GEvent.Ins.emit(GEvent.StraightTargetDamage, weaponProperty, launchCount, xTime, posY, 1)
|
|
}else if (weaponState.config.flight == SkillType.无弹道单体) {
|
|
//雷电电击攻击单体
|
|
let posX = weaponState.config.bullet_distance //y轴间距
|
|
let xTime = weaponState.config.bullet_column//间隔
|
|
//单次发射数量
|
|
let launchCount = weaponProperty.launchCount
|
|
//连发数量
|
|
let burstCount = weaponProperty.burstCount;
|
|
|
|
GEvent.Ins.emit(GEvent.LightningElectricity, weaponProperty, posX, xTime, launchCount, burstCount)
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 实例化单个武器
|
|
* @param weaponId 武器ID
|
|
* @param volume 武器缩放比例
|
|
* @param addPos 武器位置偏移
|
|
* @param index 第几个位置
|
|
*/
|
|
async instantiateSingleWeapon(weaponId: number, volume: number, addPos?: Vec3, index?: number, launchNodeSnap?: Node): Promise<Node | null> {
|
|
const curMap = gg.game.CurentBattle?.curMap;
|
|
if (!curMap) {
|
|
console.log('curMap 不存在')
|
|
return null
|
|
};
|
|
|
|
const add = addPos ?? v3(0, 0, 0);
|
|
|
|
let worldPos: Vec3;
|
|
// 优先使用外部已捕获的发射点,避免重复查 carrier/idx
|
|
if (launchNodeSnap && isValid(launchNodeSnap)) {
|
|
worldPos = launchNodeSnap.worldPosition.clone().add(add);
|
|
} else {
|
|
|
|
const carriers = gg.game.CurentBattle?.curMap.getTetriWeaponCarrier(weaponId) ?? [];
|
|
console.log('launchNodeSnap 不存在',carriers.length)
|
|
if (carriers.length > 0) {
|
|
const ci = ((index ?? 0) % carriers.length + carriers.length) % carriers.length;
|
|
const launchNode = carriers[ci].getWeaponLaunchNode();
|
|
worldPos = launchNode.worldPosition.clone().add(add);
|
|
} else {
|
|
console.log('carriers 不存在', carriers.length)
|
|
TetriStackDiagnostics.onLaunchOriginFallback(weaponId, carriers.length);
|
|
worldPos = v3(0, 0, 0)
|
|
// const launch = gg.game.CurentBattle.PlayerController.playerNode.getChildByName('weaponLaunchNode');
|
|
// if (!launch) {
|
|
// console.log('weaponLaunchNode 不存在');
|
|
// return null;
|
|
// }
|
|
// worldPos = launch.worldPosition.clone().add(add);
|
|
}
|
|
}
|
|
|
|
let weaponNode = await gg.res.getNodeAsync(`prefab/武器/`, `weapon${weaponId}`, GBundle.BattleGameWeapon);
|
|
if (!weaponNode || !weaponNode.isValid) return null;
|
|
|
|
// 节点池复用:可能残留 tween/物理速度/透明度/active,导致“从屏幕外飞入”等异常
|
|
// 注意:不要递归停止子节点 tween(很多武器预制体内部用子节点 tween 做表现/淡入)
|
|
Tween.stopAllByTarget(weaponNode);
|
|
weaponNode.active = true;
|
|
weaponNode.opacity = 255;
|
|
|
|
const rb2d = weaponNode.getComponent(RigidBody2D);
|
|
if (rb2d) {
|
|
rb2d.linearVelocity = v2(0, 0);
|
|
rb2d.angularVelocity = 0;
|
|
rb2d.fixedRotation = true;
|
|
rb2d.type = ERigidBody2DType.Kinematic;
|
|
}
|
|
|
|
weaponNode.setParent(this.node, true);
|
|
weaponNode.scale = v3(volume, volume, 1);
|
|
weaponNode.worldPosition = worldPos;
|
|
if (this.node?.isValid) {
|
|
gg.game.CurentBattle?.setLayerMask(weaponNode, this.node.layer);
|
|
}
|
|
return weaponNode;
|
|
}
|
|
|
|
/**
|
|
* 多块落稳且带同武器时多个发射点:从第 2 个起每帧创建一个,全部创建完成后 resolve 所有武器节点。
|
|
* 武器挂在 this.node 下;仅 0/1 个发射点时与 {@link instantiateSingleWeapon} 等价(立即完成)。
|
|
*/
|
|
instantiateWeaponsFromTetriCarriersStaggered(weaponId: number, volume: number, addPos?: Vec3): Promise<Node[]> {
|
|
const curMap = gg.game.CurentBattle?.curMap;
|
|
if (!curMap) {
|
|
console.log('curMap 不存在');
|
|
return Promise.resolve([]);
|
|
}
|
|
const carriers = gg.game.CurentBattle?.curMap.getTetriWeaponCarrier(weaponId) ?? [];
|
|
if (carriers.length <= 1) {
|
|
return this.instantiateSingleWeapon(weaponId, volume, addPos, 0).then((one) => (one ? [one] : []));
|
|
}
|
|
return new Promise((resolve) => {
|
|
const out: Node[] = [];
|
|
void (async () => {
|
|
const first = await this.instantiateSingleWeapon(weaponId, volume, addPos, 0);
|
|
if (first) {
|
|
out.push(first);
|
|
}
|
|
let i = 1;
|
|
const tick = async () => {
|
|
if (i >= carriers.length) {
|
|
this.unschedule(tick);
|
|
resolve(out);
|
|
return;
|
|
}
|
|
const n = await this.instantiateSingleWeapon(weaponId, volume, addPos, i);
|
|
if (n) {
|
|
out.push(n);
|
|
}
|
|
i++;
|
|
};
|
|
this.schedule(tick);
|
|
})();
|
|
});
|
|
}
|
|
|
|
|
|
/**直线弹幕/窜天猴类武器分裂为3个 */
|
|
splitBullet(
|
|
weaponId: number,
|
|
originPos: Vec3,
|
|
splitCount: number = 3,
|
|
stayTime: number = 0,
|
|
damageRate: number = 0,
|
|
damageRadius: number = 0,
|
|
tetriLevel: number = 1,
|
|
) {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) return;
|
|
|
|
let weaponData = weaponState.config
|
|
let weaponProperty = BattleDataManager.getWeaponAddProperty(weaponId, undefined, tetriLevel)
|
|
|
|
if (weaponId == WeaponType.FeiJian || weaponId == WeaponType.FaZhen || weaponId == WeaponType.LiSanDaoDan) {
|
|
let targetPos = v3(0, 0, 0)
|
|
|
|
// console.log('分裂子弹分裂 splitBullet ++++',splitCount)
|
|
// splitBullet 通常在物理碰撞回调栈内触发;在该栈内 setParent/active 可能导致 RigidBody2D m_world=null。
|
|
// 处理方式:把“创建节点 + setParent + 启用物理组件”整体延后一帧执行。
|
|
const spawnWp = originPos.clone();
|
|
this.scheduleOnce(() => {
|
|
void (async () => {
|
|
for (let i = 0; i < splitCount; i++) {
|
|
const weaponNode = await gg.res.getNodeAsync(`prefab/武器/`, `weapon${weaponId}`, GBundle.BattleGameWeapon);
|
|
if (!weaponNode || !weaponNode.isValid) continue;
|
|
|
|
weaponNode.active = true;
|
|
weaponNode.opacity = 255;
|
|
// 先禁用物理组件,等节点稳定后再启用
|
|
const rb2d = weaponNode.getComponent('RigidBody2D' as any) as any;
|
|
if (rb2d) rb2d.enabled = false;
|
|
const col2d = weaponNode.getComponent('Collider2D' as any) as any;
|
|
if (col2d) col2d.enabled = false;
|
|
|
|
weaponNode.setParent(this.node, true);
|
|
weaponNode.worldPosition = spawnWp.clone().add(v3(0, 35, 0));
|
|
weaponNode.scale = v3(weaponProperty.volume, weaponProperty.volume, 1);
|
|
|
|
const skillComp = weaponNode.getComponent(Skill);
|
|
skillComp?.setData(weaponData, targetPos, true, false, i, 0, 0);
|
|
|
|
this.scheduleOnce(() => {
|
|
if (!weaponNode || !weaponNode.isValid) return;
|
|
const rb2d2 = weaponNode.getComponent('RigidBody2D' as any) as any;
|
|
if (rb2d2) rb2d2.enabled = true;
|
|
const col2d2 = weaponNode.getComponent('Collider2D' as any) as any;
|
|
if (col2d2) col2d2.enabled = true;
|
|
}, 0);
|
|
}
|
|
})();
|
|
}, 0);
|
|
} else if (weaponId == WeaponType.LeiShenZhiChui) {
|
|
const spawnWp = originPos.clone();
|
|
const wpProp = BattleDataManager.getWeaponAddProperty(weaponId, undefined, tetriLevel);
|
|
this.scheduleOnce(() => {
|
|
void (async () => {
|
|
const mapRoot = gg.game.CurentBattle?.curMap?.node;
|
|
if (!mapRoot?.isValid) return;
|
|
for (let i = 0; i < splitCount; i++) {
|
|
const ball = await gg.res.getNodeAsync(`prefab/爆炸/`, '雷电球', GBundle.BattleGameWeapon);
|
|
if (!ball?.isValid) continue;
|
|
ball.active = true;
|
|
ball.opacity = 255;
|
|
ball.setParent(mapRoot, true);
|
|
ball.worldPosition = spawnWp.clone();
|
|
const vol = wpProp.volume > 0 ? wpProp.volume : 1;
|
|
ball.scale = v3(vol, vol, 1);
|
|
|
|
let thunder = ball.getComponent(ThunderboltSchedule);
|
|
if (!thunder) thunder = ball.addComponent(ThunderboltSchedule);
|
|
|
|
const speed = Math.max(1, wpProp.speed > 0 ? wpProp.speed : 400);
|
|
const radius = damageRadius > 0 ? damageRadius : Math.max(1, wpProp.explodeRadius || 80);
|
|
const interval = Math.max(0.05, wpProp.damageInterval > 0 ? wpProp.damageInterval : 0.2);
|
|
const continueT = stayTime > 0 ? stayTime : Math.max(interval, wpProp.weaponContinueTime > 0 ? wpProp.weaponContinueTime : 2);
|
|
const atkScale = damageRate > 0 ? damageRate / 10000 : 1;
|
|
|
|
thunder.resetThunderboltSchedule(
|
|
spawnWp.clone(),
|
|
radius,
|
|
atkScale,
|
|
weaponId,
|
|
interval,
|
|
Math.max(1, Math.floor(continueT / interval)),
|
|
continueT,
|
|
tetriLevel,
|
|
i,
|
|
speed,
|
|
);
|
|
}
|
|
})();
|
|
}, 0);
|
|
}
|
|
}
|
|
/**
|
|
* 获得攻击点位置(抛物线)
|
|
* @param weaponData
|
|
* @param index 第几个位置
|
|
* @returns
|
|
*/
|
|
getAtkPos(weaponData: ITableWeapon2, index: number = 0) {
|
|
if (weaponData.flight == SkillType.直线弹幕) {
|
|
return v3(0, 0, 0)
|
|
} else if (weaponData.flight == SkillType.抛物线碰撞 || weaponData.flight == SkillType.抛物线没有碰撞) {
|
|
let targetPos = gg.game.CurentBattle.MonsterSpawner.getMonsterParabolaPos(Math.max(1, index), this.node)
|
|
return targetPos
|
|
}
|
|
}
|
|
/**强制改变武器冷却时间 */
|
|
forceChangeWeaponCD(weaponId: number, cd: number) {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) return;
|
|
weaponState.remainingCD = cd
|
|
weaponState.cd = cd
|
|
}
|
|
/**
|
|
* 开始武器冷却
|
|
* @param weaponId 武器ID
|
|
*/
|
|
private startCooldown(weaponId: number): void {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) return;
|
|
|
|
// 设置为冷却状态
|
|
weaponState.isReady = false;
|
|
let weaponProperty = BattleDataManager.getWeaponAddProperty(weaponId)
|
|
const cdMul = BattlePerformance.weaponCdMul();
|
|
weaponState.remainingCD = weaponProperty.cd * cdMul;
|
|
weaponState.cd = weaponProperty.cd * cdMul;
|
|
|
|
// 清理之前的定时器(如果有)
|
|
//this.clearWeaponTimer(weaponId);
|
|
|
|
// // 启动新的冷却定时器
|
|
// const timerId = setInterval(() => {
|
|
// // BattleManager.Instance.audio_timeStampADD += this.dt*1000
|
|
// // BattleManager.Instance._beAtkTimeStampTimeAdd += this.dt*1000
|
|
// // BattleManager.Instance._hurtScoreTimeStampTimeAdd += this.dt*1000
|
|
// // BattleManager.Instance._hurtColorTimeStamp += this.dt*1000
|
|
|
|
// this.updateCooldown(weaponId, weaponProperty.cd);
|
|
// }, this.dt * 1000); // 每0.1秒更新一次
|
|
|
|
// this.timers.set(weaponId, timerId);
|
|
// weaponState.timer = timerId;
|
|
|
|
// console.log(`武器 ${weaponId} 开始冷却,CD: ${weaponState.config.cd}秒`);
|
|
}
|
|
|
|
protected update(dt: number): void {
|
|
if (!gg.game.isBattleContextActive()) return;
|
|
if (gg.game.IsPlayingGuideStory) {
|
|
//开启攻击
|
|
const curUseArr = gg.game.CurentBattle.CurUseWeaponArr;
|
|
for (let i = 0; i < curUseArr.length; i++) {
|
|
const weaponId = curUseArr[i].weaponid;
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) continue;
|
|
|
|
weaponState.remainingCD -= dt * gg.game.CurentBattle.TimeScale;
|
|
|
|
if (weaponState.remainingCD <= 0) {
|
|
// 冷却完成
|
|
this.finishCooldown(weaponId);
|
|
} else {
|
|
|
|
}
|
|
}
|
|
return
|
|
}
|
|
if (!gg.game.CurentBattle.canUpdateFrame()) {
|
|
return
|
|
}
|
|
|
|
const curUseArr = gg.game.CurentBattle.CurUseWeaponArr;
|
|
for (let i = 0; i < curUseArr.length; i++) {
|
|
const weaponId = curUseArr[i].weaponid;
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) continue;
|
|
|
|
weaponState.remainingCD -= dt * gg.game.CurentBattle.TimeScale;
|
|
|
|
if (weaponState.remainingCD <= 0) {
|
|
// 冷却完成
|
|
this.finishCooldown(weaponId);
|
|
} else {
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 完成冷却
|
|
* @param weaponId 武器ID
|
|
*/
|
|
private finishCooldown(weaponId: number): void {
|
|
//没有怪物不在发射武器
|
|
if (gg.game.CurentBattle.MonsterSpawner.node.children.length == 0) {
|
|
//console.log('没有怪物不在发射武器')
|
|
return
|
|
};
|
|
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (!weaponState) return;
|
|
|
|
weaponState.isReady = true;
|
|
weaponState.remainingCD = 0;
|
|
|
|
// 触发冷却完成事件
|
|
this.onWeaponCooldownComplete(weaponId);
|
|
}
|
|
|
|
/**
|
|
* 获取可用武器列表
|
|
* @returns 可用武器ID数组
|
|
*/
|
|
public getAvailableWeapons(): number[] {
|
|
return this.availableWeapons.filter(weaponId => {
|
|
const state = this.weaponStates.get(weaponId);
|
|
return state && state.isReady;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取武器状态
|
|
* @param weaponId 武器ID
|
|
* @returns 武器状态
|
|
*/
|
|
public getWeaponState(weaponId: number): WeaponState | undefined {
|
|
return this.weaponStates.get(weaponId);
|
|
}
|
|
|
|
// === 以下方法需要根据实际游戏逻辑实现 ===
|
|
|
|
// /**
|
|
// * 播放武器特效
|
|
// * @param weaponId 武器ID
|
|
// */
|
|
// private playWeaponEffect(weaponId: number): void {
|
|
// // 实现武器特效播放逻辑
|
|
// // 例如:播放动画、音效等
|
|
// }
|
|
|
|
/**
|
|
* 更新冷却时间UI显示
|
|
* @param weaponId 武器ID
|
|
* @param remainingTime 剩余时间
|
|
*/
|
|
private updateCooldownUI(weaponId: number, cd: number, remainingTime: number): void {
|
|
// 更新UI显示,比如技能按钮上的倒计时
|
|
gg.game.CurentBattle.DamageStatistics.freshWeaponCD(weaponId, cd, remainingTime)
|
|
}
|
|
|
|
/**
|
|
* 武器冷却完成回调
|
|
* @param weaponId 武器ID
|
|
*/
|
|
private onWeaponCooldownComplete(weaponId: number): void {
|
|
//如果没有怪物不需要发射武器
|
|
if (gg.game.CurentBattle.MonsterSpawner.node.children.length == 0) {
|
|
return
|
|
};
|
|
const minMs = BattlePerformance.weaponFireMinIntervalMs();
|
|
// beforeTimes:冷却窗口内返回 true(应推迟发射);勿取反
|
|
if (minMs > 0 && MTools.beforeTimes(minMs, `weapon_fire_${weaponId}`)) {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (weaponState) {
|
|
weaponState.remainingCD = Math.max(weaponState.remainingCD, minMs / 1000);
|
|
weaponState.isReady = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
this.useWeaponSkill(weaponId)
|
|
}
|
|
|
|
/**刷新无人机和激光武器发射塔 */
|
|
public refreshDroneOrLaserLaunch() {
|
|
this.availableWeapons.forEach(weaponId => {
|
|
const weaponState = this.weaponStates.get(weaponId);
|
|
if (weaponState && weaponState.config.flight == SkillType.单点激光) {
|
|
let weaponProperty = BattleDataManager.getWeaponAddProperty(weaponId)
|
|
//连发数量(激光塔数量)
|
|
let burstCount = weaponProperty.burstCount
|
|
let curCount = this.specialWeaponLaunchCount['weapon' + weaponId] || 0
|
|
if (curCount < burstCount) {
|
|
|
|
this.specialWeaponLaunchCount['weapon' + weaponId] = burstCount
|
|
}
|
|
}
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|