import { _decorator, Component, find, isValid, Layers, Node, sp, UITransform, v3, Vec3 } from 'cc'; import { GBundle, GPath } from '../game/ConfigRes'; import { shootPoint } from './shootPoint'; import { GEvent } from '../../mx/module/event/GEvent'; import { SkillType } from '../ui/BattleGame/WeaponSkill/SkillTypes'; import { IWeaponProperty, SpecialSkillType } from '../ui/BattleGame/BattleDataManager'; import { Skill } from '../ui/BattleGame/WeaponSkill/Skill'; import { LaserLine } from '../ui/BattleGame/WeaponSkill/LaserLine'; import { tetriNode } from './tetriNode'; import { applyRoleHand } from './roleHand'; import MTools from '../../mx/tools/MTools'; import { WeaponType } from '../manager/WeaponDataManager'; import { BattleType } from '../manager/ChapterDataManager'; import { UILayer } from '../../mx/module/ui/UIManager'; /** 救救我鸭:场上角色初始武器 id */ export const JIUJIUWOYA_INIT_WEAPON_ID = 1; const { ccclass, property } = _decorator; /**方块武器载体脚本,用于绑定武器和技能 * TetriType == 1 基础武器方块,只有武器和技能 * TetriType == 5 藤蔓方块,既有武器还有其他特殊功能 */ @ccclass('tetriWeaponCarrier') export class tetriWeaponCarrier extends Component { /** 攻击动画相对首个弹体/技能生成提前的秒数(先播「攻击」再发射) */ private static readonly ATK_ANIM_LEAD_SEC = 0.4; private static readonly ATK_ANIM_LEAD_SEC2 = 0.1; /** 方块 prefab 名 → 武器/hand 缩放与 hand 贴图(与原「角色」节点 scale 一致) */ roleConfig = [ { id: 1, name: "Tetris_att_I", mainPosi: { x: 0, y: 0 }, handName: '3格横手', scalex: -0.25, scaley: 0.25 }, { id: 2, name: "Tetris_att_1_1", mainPosi: { x: 0, y: 0 }, handName: '3格横手', scalex: -0.25, scaley: 0.25 }, { id: 3, name: "Tetris_att_J1", mainPosi: { x: -12.214, y: -17.434 }, handName: '左L手', scalex: -0.25, scaley: 0.25 }, { id: 4, name: "Tetris_att_J2", mainPosi: { x: -18.862, y: 10.235 }, handName: '左L手', scalex: -0.25, scaley: 0.25 }, { id: 5, name: "Tetris_att_J3", mainPosi: { x: 11.196, y: 18.98 }, handName: '左L手', scalex: -0.25, scaley: 0.25 }, { id: 6, name: "Tetris_att_J", mainPosi: { x: 18.079, y: -8.477 }, handName: '左L手', scalex: -0.25, scaley: 0.25 }, { id: 7, name: "Tetris_att_L1", mainPosi: { x: -19, y: 15 }, handName: '右L手', scalex: -0.25, scaley: 0.25 }, { id: 8, name: "Tetris_att_L2", mainPosi: { x: 17, y: 18 }, handName: '右L手', scalex: -0.25, scaley: 0.25 }, { id: 9, name: "Tetris_att_L3", mainPosi: { x: 19, y: -15 }, handName: '右L手', scalex: -0.25, scaley: 0.25 }, { id: 10, name: "Tetris_att_L", mainPosi: { x: -25, y: -22 }, handName: '右L手', scalex: -0.25, scaley: 0.25 }, { id: 11, name: "Tetris_att_Z1_1", mainPosi: { x: 4.018, y: -2.712 }, handName: '右z手', scalex: -0.25, scaley: 0.25 }, { id: 12, name: "Tetris_att_Z1", mainPosi: { x: -6.197, y: -4.563 }, handName: '右z手', scalex: -0.2, scaley: 0.2 }, { id: 13, name: "Tetris_att_Z2_1", mainPosi: { x: 2.81, y: 1.751 }, handName: '左z手', scalex: -0.25, scaley: 0.25 }, { id: 14, name: "Tetris_att_Z2", mainPosi: { x: 4.65, y: -1.475 }, handName: '左z手', scalex: -0.25, scaley: 0.25 }, { id: 15, name: "Tetris_att_凸_1", mainPosi: { x: 20.727, y: 0 }, handName: 'T手', scalex: -0.25, scaley: 0.25 }, { id: 16, name: "Tetris_att_凸_2", mainPosi: { x: 0, y: -19.624 }, handName: 'T手', scalex: -0.25, scaley: 0.25 }, { id: 17, name: "Tetris_att_凸_3", mainPosi: { x: -16.394, y: 0 }, handName: 'T手', scalex: -0.25, scaley: 0.25 }, { id: 18, name: "Tetris_att_凸", mainPosi: { x: 0, y: 17.555 }, handName: 'T手', scalex: -0.25, scaley: 0.25 }, ] private _config: ITableBattleCube | null = null; _curentWeaponId: number = 0 weaponNode: Node = null; timeoutIndex: number = 0 animState: string; /** 武器挂点初始朝向(scale.x 符号) */ initRoleFacing: number = 0; initRoleFacingFix: number = 0; private roleBaseAbsScaleX: number = 1; _isBattleScene = false //是否是战斗场景相机 _isCanShoot = true //是否可以射击 setConfig(config: ITableBattleCube, isCanShoot = true, isBattleScene = false) { this._config = config this._isBattleScene = isBattleScene this._isCanShoot = isCanShoot const cubeName = config.cubename; if (gg.game.CurentBattle.BattleType === BattleType.OrangeWeaponMode_Ya) { this._curentWeaponId = JIUJIUWOYA_INIT_WEAPON_ID; if (cubeName) { gg.game.CurentBattle.TetriWeaponMap.set(cubeName, JIUJIUWOYA_INIT_WEAPON_ID); } } else { const weaponId = gg.game.CurentBattle.TetriWeaponMap.get(cubeName); this._curentWeaponId = weaponId ?? config.weaponid ?? 0; } } getWeapontype(): number { return this._config?.weapontype ?? 0; } /** 救救我鸭:直接换武器(不走路径方块 / weapontype) */ applyWeaponId(weaponId: number, playReplaceEffect = false) { if (!Number.isFinite(weaponId) || weaponId <= 0) return; this._curentWeaponId = weaponId; const cubeName = this._config?.cubename; if (cubeName && gg.game?.CurentBattle) { gg.game.CurentBattle.TetriWeaponMap.set(cubeName, weaponId); } this.changeWeaponConf(); if (playReplaceEffect && gg.game?.CurentBattle?.BattleType === BattleType.OrangeWeaponMode_Ya) { this.showSkillSelectCompleteEffect(false, 0.3); } } protected onDestroy(): void { const block = this.findCarrierTetriNode()?.node ?? this.node; gg.game?.CurentBattle?.pruneDetachedBattleLayers?.(block); const weapon = this.weaponNode; this.weaponNode = null; if (this.timeoutIndex) clearTimeout(this.timeoutIndex as unknown as number); GEvent.Ins.off(GEvent.ChangeCubeWeaponConfig, this.ChangeCubeWeapon, this) GEvent.Ins.off(GEvent.StraightWeaponShoot, this.StraightWeaponShoot, this) GEvent.Ins.off(GEvent.StraightWeaponLaunch, this.StraightWeaponLaunch, this) GEvent.Ins.off(GEvent.StraightWeaponShootNum, this.StraightWeaponShootNum, this) GEvent.Ins.off(GEvent.StraightWeaponSplit, this.StraightWeaponSplit, this) GEvent.Ins.off(GEvent.StraightWeaponPierce, this.StraightWeaponPierce, this) GEvent.Ins.off(GEvent.ParabolaCollide, this.ParabolaCollide, this) GEvent.Ins.off(GEvent.ParabolaNoCollide, this.ParabolaNoCollide, this) GEvent.Ins.off(GEvent.SinglePointLaser, this.SinglePointLaser, this) GEvent.Ins.off(GEvent.StraightTargetDamage, this.StraightTargetDamage, this) GEvent.Ins.off(GEvent.LightningElectricity, this.LightningElectricity, this) this.safePutPoolNode(weapon); } start() { //ChangeCubeWeaponConfig GEvent.Ins.on(GEvent.ChangeCubeWeaponConfig, this.ChangeCubeWeapon, this) //武器发射事件 GEvent.Ins.on(GEvent.StraightWeaponShoot, this.StraightWeaponShoot, this)//直线弹幕 GEvent.Ins.on(GEvent.StraightWeaponLaunch, this.StraightWeaponLaunch, this)//直线弹射 GEvent.Ins.on(GEvent.StraightWeaponShootNum, this.StraightWeaponShootNum, this)//直线数量 GEvent.Ins.on(GEvent.StraightWeaponSplit, this.StraightWeaponSplit, this) //直线分裂 GEvent.Ins.on(GEvent.StraightWeaponPierce, this.StraightWeaponPierce, this)//直线穿透 GEvent.Ins.on(GEvent.ParabolaCollide, this.ParabolaCollide, this)//抛物线碰撞 GEvent.Ins.on(GEvent.ParabolaNoCollide, this.ParabolaNoCollide, this)//抛物线没有碰撞 GEvent.Ins.on(GEvent.SinglePointLaser, this.SinglePointLaser, this)//单点激光 GEvent.Ins.on(GEvent.StraightTargetDamage, this.StraightTargetDamage, this)//直线目标持续伤害 GEvent.Ins.on(GEvent.LightningElectricity, this.LightningElectricity, this)//雷电电击攻击单体 this.scheduleOnce(() => this.initCarrier(), 0.02); } /** 方块 prefab 上的武器挂点 */ private getWeaponMountNode(): Node | null { return find('武器', this.node); } /** 武器预制体 Spine(动画在子节点 spine 上) */ private getWeaponSkeleton(weaponNode: Node | null): sp.Skeleton | null { if (!weaponNode?.isValid) return null; const spineNode = weaponNode.getChildByName('spine'); if (spineNode?.isValid) { return spineNode.getComponent(sp.Skeleton); } return weaponNode.getComponent(sp.Skeleton); } private playWeaponAnim(weaponNode: Node | null, animName: string, loop: boolean): void { this.getWeaponSkeleton(weaponNode)?.setAnimation(0, animName, loop); } /** 清理旧版动态挂上的「角色」子节点 */ private removeLegacyRoleNodes(): void { const dupDestroy: Node[] = []; for (let i = 0; i < this.node.children.length; i++) { const c = this.node.children[i]; if (c?.name === '角色') dupDestroy.push(c); } for (const d of dupDestroy) { if (d?.isValid) d.destroy(); } } /** 按 roleConfig 设置武器挂点 / hand 的缩放与朝向基准(等同原角色节点 setScale) */ private applyBlockWeaponVisual(conf: { scalex: number; scaley: number }): void { const mount = this.getWeaponMountNode(); if (mount?.isValid) { mount.setScale(conf.scalex, conf.scaley); } const hand = this.node.getChildByName('hand'); if (hand?.isValid) { hand.setScale(conf.scalex, conf.scaley); } this.initRoleFacing = conf.scalex; this.initRoleFacingFix = conf.scalex; const abs = Math.abs(conf.scalex); this.roleBaseAbsScaleX = abs > 1e-6 ? abs : 1; } /** 初始化方块载体:hand 贴图 + 武器(挂方块「武器」节点,local 0,0) */ private initCarrier(): void { if (!this._config) { console.error('[tetriWeaponCarrier] initCarrier: _config 为空,请保证 setConfig 在 start 之前已调用'); return; } this.removeLegacyRoleNodes(); const conf = this.roleConfig.find(item => item.name === this._config.cubename); if (conf) { if (conf.handName) { applyRoleHand(this.node, conf.handName); } this.applyBlockWeaponVisual(conf); } this.scheduleOnce(() => { if (!isValid(this.node)) return; const conf2 = this.roleConfig.find(item => item.name === this._config?.cubename); if (conf2) { if (conf2.handName) applyRoleHand(this.node, conf2.handName); this.applyBlockWeaponVisual(conf2); } this.setView(); }, 0.1); } /** 翻转武器挂点 / hand 面向目标 x(只改 scale.x) */ private faceRoleToWorldX(targetWorldX: number) { const mount = this.getWeaponMountNode(); if (!mount?.isValid) return; const selfX = this.node.worldPosition.x; const dx = targetWorldX - selfX; if (Math.abs(dx) < 0.01) return; const rightSign = this.initRoleFacingFix >= 0 ? 1 : -1; const dir = dx >= 0 ? rightSign : -rightSign; const newX = this.roleBaseAbsScaleX * dir; const applyFlip = (n: Node) => { const s = n.scale; if (Math.abs(s.x - newX) > 1e-6) { n.setScale(newX, s.y, s.z); } }; applyFlip(mount); const hand = this.node.getChildByName('hand'); if (hand?.isValid) applyFlip(hand); } private faceRoleToMonster(monsterNode: Node | null) { if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Ya){ return; } if (!monsterNode || !isValid(monsterNode)) return; this.faceRoleToWorldX(monsterNode.worldPosition.x); } /** * 回收到 gg.res 节点池。父节点被 destroy 时,子节点可能已在销毁流程中, * 必须先脱父且 isValid 再 putNode,否则 putNode 里 getComponentsInChildren 会空引用。 */ private safePutPoolNode(n: Node | null): void { if (!n) return; if (!isValid(n)) return; try { if (n.parent) n.destroy(); //gg.res.putNode(n); } catch (e) { console.warn('[tetriWeaponCarrier] safePutPoolNode failed', e); } } setView() { const conf = gg.data.project.getWeaponData(this._curentWeaponId); if (!conf) { console.error('[tetriWeaponCarrier] setView: 武器表无数据 weaponId=', this._curentWeaponId, this._config?.cubename); return; } this.addWeapon(conf.weaponani); //this.changeIcon(conf.typeicon); } ChangeCubeWeapon(cubename: string, weaponid: number) { if (gg.game?.CurentBattle?.BattleType === BattleType.OrangeWeaponMode_Ya) { return; } if (cubename == this._config?.cubename) { this._curentWeaponId = weaponid; this.changeWeaponConf(); } } /** * 三选一完成格子效果(救救我鸭无方块时挂在角色载体上;与 tetriNode.showUpLevelEffect 一致) * @param isUpLevel true 技能升级,false 武器替换 */ showSkillSelectCompleteEffect(isUpLevel = true, delay = 0) { const host = this.node; if (!host?.isValid) return; this.scheduleOnce(() => { if (!host?.isValid) return; void gg.res.getNodeAsync(GPath.Prefab, '三选一完成格子效果', GBundle.tetriGame).then((eff) => { if (!eff?.isValid) return; const battle = gg.game?.CurentBattle; const ya = battle?.BattleType === BattleType.OrangeWeaponMode_Ya; const spineLayer = ya && battle?.TopSpineLayer?.isValid ? battle.TopSpineLayer : null; if (spineLayer) { eff.parent = spineLayer; eff.setWorldPosition(host.worldPosition); battle.setLayerMask(eff, spineLayer.layer); eff.setSiblingIndex(spineLayer.children.length - 1); } else { gg.ui.showNode(eff, UILayer.Anim); eff.parent = host; eff.setPosition(Vec3.ZERO); } eff.active = true; const nReplace = eff.find('武器替换'); const nUp = eff.find('升级'); if (nReplace?.isValid) nReplace.active = !isUpLevel; if (nUp?.isValid) nUp.active = isUpLevel; const playSpine = (node: Node | null, animName: string) => { const sk = node?.getComponent(sp.Skeleton); if (!sk) return; sk.clearTracks(); sk.setAnimation(0, animName, false); }; if (nReplace?.active) playSpine(nReplace, 'animation'); if (nUp?.active) playSpine(nUp, '升级'); this.scheduleOnce(() => { if (eff?.isValid) gg.res.putNode(eff); }, 1.5); }); }, delay); } changeWeaponConf() { console.log('我改变武器啦 ++= changeWeaponConf++++++++===', this._curentWeaponId) let conf2 = gg.data.project.getWeaponData(this._curentWeaponId) //this.changeIcon(conf2.typeicon) this.changeWeapon(conf2.weaponani) } changeWeapon(weaponani) { const old = this.weaponNode; this.weaponNode = null; this.safePutPoolNode(old); this.addWeapon(weaponani) } // 武器挂到方块 prefab 的「武器」节点(local 0,0,偏移在 prefab 挂点配置) addWeapon(weaponani) { const wuqiNode = this.getWeaponMountNode(); if (!wuqiNode) { console.error('[tetriWeaponCarrier] addWeapon: 未找到方块/武器', this._config?.cubename); return; } void gg.res.getNodeAsync('prefab/', weaponani, GBundle.tetriWeapon).then((weaponNode) => { if (!weaponNode?.isValid || !wuqiNode?.isValid) { if (!weaponNode) { console.error('[tetriWeaponCarrier] addWeapon: 武器预制未取到', weaponani, this._config?.cubename); } return; } this.setLayerMask(weaponNode, wuqiNode.layer); this.weaponNode = weaponNode; weaponNode.setParent(wuqiNode); weaponNode.setPosition(0, 0, 0); this.playWeaponAnim(weaponNode, '待机', true); }); } setLayerMask(n: Node, layerMask: number) { if (n.name === 'hand') return; n.layer = layerMask; for (let i = 0; i < n.children.length; i++) { if (n.children[i].isValid) { this.setLayerMask(n.children[i], layerMask); } } } changeIcon(typeicon) { } //播放攻击动画 playAtkAnim() { clearTimeout(this.timeoutIndex as unknown as number); const weaponSk = this.getWeaponSkeleton(this.weaponNode); if (!weaponSk) { return; } weaponSk.setAnimation(0, '攻击', false); this.timeoutIndex = setTimeout(() => { if (weaponSk?.isValid) { weaponSk.setAnimation(0, '待机', true); } this.animState = '待机'; this.timeoutIndex = null; }, 1000) as unknown as number; } //获取武器的发射节点和原点 getWeaponLaunchNode(): Node { const wuqiNode = this.getWeaponMountNode(); if (!wuqiNode?.isValid) { return this.node; } const shoot = this.weaponNode?.getComponentInChildren(shootPoint)?.node ?? wuqiNode.getComponentInChildren(shootPoint)?.node; if (shoot?.isValid) { return shoot; } // 部分武器预制未挂 shootPoint:用武器模型节点(只要在「武器」子树下)或「武器」挂点作为发射参考 if (this.weaponNode?.isValid) { let p: Node | null = this.weaponNode; while (p) { if (p === wuqiNode) { return this.weaponNode; } p = p.parent; } } return wuqiNode; } /** 载体关联方块:自身或父链上的 tetriNode(救救我鸭等直挂角色时为 null) */ findCarrierTetriNode(): tetriNode | null { const onSelf = this.node.getComponent(tetriNode); if (onSelf) return onSelf; let cur: Node | null = this.node.parent; while (cur) { const t = cur.getComponent(tetriNode); if (t) return t; cur = cur.parent; } return null; } /** 方块未落稳不发射;无方块载体(副本直挂等)视为可随时发射 */ isSettledForWeaponShoot(): boolean { const tetri = this.findCarrierTetriNode(); return !tetri || tetri.isSettled; } getCarrierTetriLevel(): number { return this.findCarrierTetriNode()?.getNodeLevel?.() ?? 1; } /** 战斗暂停/弹窗叠层时不发射已排队的子弹 */ private scheduleWeaponFire(delaySec: number, fn: () => void): void { this.scheduleOnce(() => { if (!gg.game?.CurentBattle?.canUpdateFrame()) return; fn(); }, delaySec); } /** GEvent 武器发射为全局广播,仅处理与当前发射塔绑定的武器 */ private shouldHandleWeaponLaunch(emitWeaponId: number): boolean { return emitWeaponId > 0 && emitWeaponId === this._curentWeaponId; } /**直线弹幕 */ StraightWeaponShoot(weaponProperty: IWeaponProperty, posX, xTime, launchCount, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } //没有落稳的方块不在发射武器 //单次发射数量launchCount // console.log('StraightWeaponShoot 单次发射数量launchCount',launchCount) // console.log('StraightWeaponShoot burstCount',burstCount) if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线弹幕) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponShoot, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); if (!MTools.beforeTimes(100, "audio_timeStamp")) { this.playEffectStraight(weaponId); } for (let j = 0; j < launchCount; j++) { let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线弹射 */ StraightWeaponLaunch(weaponProperty: IWeaponProperty, posX, xTime, launchCount, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线弹射) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponLaunch, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); if (!MTools.beforeTimes(100, "audio_timeStamp2")) { this.playEffectStraightLaunch(weaponId); } for (let j = 0; j < launchCount; j++) { let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线数量 */ StraightWeaponShootNum(weaponProperty: IWeaponProperty, burstCount, xTime, launchCount, posX) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线数量) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponShootNum, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); if (!MTools.beforeTimes(100, "audio_timeStamp3")) { this.playEffectStraightNum(weaponId); } for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线分裂 */ StraightWeaponSplit(weaponProperty: IWeaponProperty, burstCount, xTime, launchCount, posX) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线分裂) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponSplit, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); if (!MTools.beforeTimes(100, "audio_timeStamp3")) { this.playEffectStraight(weaponId); } for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线穿透*/ StraightWeaponPierce(weaponProperty: IWeaponProperty, burstCount, xTime, launchCount, posX) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线穿透) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightWeaponPierce, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC2 / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); if (!MTools.beforeTimes(100, "audio_timeStamp3")) { this.playEffectStraight(weaponId); } for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**抛物线碰撞 */ ParabolaCollide(weaponProperty: IWeaponProperty, bullet_column, launchCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let throwCount = weaponProperty.throwCount let specialSkillInfo = weaponProperty.specialSkillInfo let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.抛物线碰撞) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.ParabolaCollide, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; let createSkill = async (mNode: Node) => { if (mNode && isValid(mNode)) { // weaponNode 挂在 SkillController.node 下,setData 的 targetPos 需要与 weaponNode 同坐标系 const parentUi = battle.SkillController.node.getComponent(UITransform); let pos = parentUi ? parentUi.convertToNodeSpaceAR(mNode.worldPosition) : mNode.worldPosition.clone(); void battle.SkillController.instantiateSingleWeapon(weaponId, volume, v3(0, 0, 0), 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), pos, false, false, 0, 0, 0, 0, tetriLevel); }); } } let singleLaunchCall = () => { //单次发射数量 if (!MTools.beforeTimes(100, "audio_timeStamp4")) { this.playEffectParabolaCollide(weaponId); } let selectMonster = []; selectMonster = gg.game.CurentBattle.MonsterSpawner.getMonsterNearestCountPar(this.node, throwCount); if (selectMonster.length == 0) { return } // 面向第一个目标怪物 this.faceRoleToMonster(selectMonster[0]); for (let i = 0; i < throwCount; i++) { if (i >= throwCount) break; if (i >= selectMonster.length) break; let m = selectMonster[i]; if (!isValid(m)) continue; createSkill(m); } } let isBurst = false let burstCount = 1 let xTime = bullet_column//间隔 for (let i = 0; i < specialSkillInfo.length; i++) { let buffInfo = specialSkillInfo[i] let arr = buffInfo.split(',') let buffType = Number(arr[0]) if (buffType == SpecialSkillType.rateBurst) { //概率连发 let rateNum = Number(arr[1]) / 10000//连发的概率 burstCount += Number(arr[2])//增加连发数量 isBurst = Math.random() < rateNum } } //连发 if (isBurst) { for (let j = 0; j < burstCount; j++) { this.scheduleWeaponFire(lead + xTime * j, () => { singleLaunchCall() }) } //单发 } else { this.scheduleWeaponFire(lead, () => { // 多发:一次性取足目标,避免每次 random 到同一个怪 const totalNeed = Math.max(1, Math.floor(throwCount)) * Math.max(1, Math.floor(launchCount)); const selectMonsterAll = battle.MonsterSpawner.getMonsterParabolaPos(totalNeed, carrierNode); if (selectMonsterAll.length == 0) return; // 面向第一个目标怪物 const first = selectMonsterAll[0]?.node as Node; if (first && isValid(first)) { this.faceRoleToMonster(first); } for (let k = 0; k < totalNeed; k++) { if (k >= selectMonsterAll.length) break; const m = selectMonsterAll[k]; if (!m?.node || !isValid(m.node)) continue; createSkill(m.node); } }); } } }); } } /**抛物线没有碰撞 */ ParabolaNoCollide(weaponProperty: IWeaponProperty, bullet_column, launchCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let throwCount = weaponProperty.throwCount let specialSkillInfo = weaponProperty.specialSkillInfo let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.抛物线没有碰撞) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.ParabolaNoCollide, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; let createSkill = async (mNode: Node) => { if (mNode && isValid(mNode)) { // weaponNode 挂在 SkillController.node 下,setData 的 targetPos 需要与 weaponNode 同坐标系 const parentUi = battle.SkillController.node.getComponent(UITransform); let pos = parentUi ? parentUi.convertToNodeSpaceAR(mNode.worldPosition) : mNode.worldPosition.clone(); void battle.SkillController.instantiateSingleWeapon(weaponId, volume, v3(0, 0, 0), 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), pos, false, false, 0, 0, 0, 0, tetriLevel); }); } } let singleLaunchCall = () => { //单次发射数量 if (!MTools.beforeTimes(100, "audio_timeStamp5")) { this.playEffectParabolaNoCollide(weaponId); } let selectMonster = []; selectMonster = gg.game.CurentBattle.MonsterSpawner.getMonsterParabolaPos(throwCount, this.node); if (selectMonster.length == 0) { return } // 大炮等“抛物线没有碰撞”类型也需要左右转向(与其它发射类型保持一致) const first = selectMonster[0]?.node as Node; if (first && isValid(first)) { this.faceRoleToMonster(first); } for (let i = 0; i < throwCount; i++) { if (i >= throwCount) break; if (i >= selectMonster.length) break; let m = selectMonster[i]; if (!isValid(m.node)) continue; createSkill(m.node); } } let isBurst = false let burstCount = 1 let xTime = bullet_column//间隔 for (let i = 0; i < specialSkillInfo.length; i++) { let buffInfo = specialSkillInfo[i] let arr = buffInfo.split(',') let buffType = Number(arr[0]) if (buffType == SpecialSkillType.rateBurst) { //概率连发 let rateNum = Number(arr[1]) / 10000//连发的概率 burstCount += Number(arr[2])//增加连发数量 isBurst = Math.random() < rateNum } } //连发 if (isBurst) { for (let j = 0; j < burstCount; j++) { this.scheduleWeaponFire(lead + xTime * j, () => { singleLaunchCall() }) } //单发 } else { this.scheduleWeaponFire(lead, () => { // 多发:一次性取足目标,避免每次 random 到同一个怪 const totalNeed = Math.max(1, Math.floor(throwCount)) * Math.max(1, Math.floor(launchCount)); const selectMonsterAll = battle.MonsterSpawner.getMonsterParabolaPos(totalNeed, carrierNode); if (selectMonsterAll.length == 0) return; // 面向第一个目标怪物 const first = selectMonsterAll[0]?.node as Node; if (first && isValid(first)) { this.faceRoleToMonster(first); } for (let k = 0; k < totalNeed; k++) { if (k >= selectMonsterAll.length) break; const m = selectMonsterAll[k]; if (!m?.node || !isValid(m.node)) continue; createSkill(m.node); } }); } } }); } } /**单点激光 */ SinglePointLaser(weaponProperty: IWeaponProperty, burstCount, launchCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.单点激光) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.SinglePointLaser, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC2 / battle.TimeScale; this.scheduleWeaponFire(lead, () => { const b = gg.game.CurentBattle; if (b?.IsWarnTimeActive && b?.IsGamePause) { return; } for (let i = 0; i < burstCount; i++) { for (let j = 0; j < launchCount; j++) { const laserIndex = i * launchCount + j; if (i == 0 && j == 0) { gg.audio.playEffect({ name: '激光', path: 'sound/武器音效2/' }); } // void battle.SkillController.instantiateSingleWeapon(weaponId, volume, v3(0, 0, 0), 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; const skillComp = weaponNode.getComponent(Skill); skillComp.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, laserIndex, 0, 0, 0, tetriLevel); weaponNode.getComponent(LaserLine).setData(weaponId, weaponProperty, laserIndex, tetriLevel); }); } } }); } }); } } /**直线目标持续伤害 */ StraightTargetDamage(weaponProperty: IWeaponProperty, launchCount, xTime, posX, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.直线目标持续伤害) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.StraightTargetDamage, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); if (!MTools.beforeTimes(100, "audio_timeStamp")) { this.playEffectStraight(weaponId); } for (let j = 0; j < launchCount; j++) { let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } LightningElectricity(weaponProperty: IWeaponProperty, posX, xTime, launchCount, burstCount) { if (!this.shouldHandleWeaponLaunch(weaponProperty?.weaponId)) { return } //雷电武器 if (!this._isCanShoot) { return } if (!this.isSettledForWeaponShoot()) { //console.log('没有落稳的方块不在发射武器') return } const weaponId = this._curentWeaponId; const volume = weaponProperty.volume; const carrierNode = this.node; const launchNodeSnap = this.getWeaponLaunchNode(); const tetriLevel = this.getCarrierTetriLevel(); let weaponConf = gg.data.project.getWeaponData(weaponId) if (weaponConf.flight == SkillType.无弹道单体) { gg.game.CurentBattle?.enqueueWeaponLaunchEvent?.({ type: GEvent.LightningElectricity, run: () => { const battle = gg.game.CurentBattle; if (!battle?.SkillController?.node?.isValid) return; if (!isValid(carrierNode)) return; const carrier = carrierNode.getComponent(tetriWeaponCarrier); if (!carrier) return; carrier.playAtkAnim(); const lead = tetriWeaponCarrier.ATK_ANIM_LEAD_SEC / battle.TimeScale; for (let i = 0; i < burstCount; i++) { let t1 = lead + xTime * i; this.scheduleWeaponFire(t1, () => { // 面向最近怪物(角色不移动也需要左右转向) this.faceRoleToMonster(battle.MonsterSpawner?.getAllPathDisMinMonster?.(carrierNode)); if (!MTools.beforeTimes(100, "audio_timeStamp3")) { this.playEffectStraightNum(weaponId); } for (let j = 0; j < launchCount; j++) { //多个子弹并排开 let addPos = v3(j * posX - (launchCount - 1) * posX / 2, 0, 0) void battle.SkillController.instantiateSingleWeapon(weaponId, volume, addPos, 0, launchNodeSnap).then((weaponNode) => { if (!weaponNode?.isValid) return; weaponNode.getComponent(Skill)?.setData(gg.data.project.getWeaponData(weaponId), v3(0, 0, 0), false, false, j, 0, 0, 0, tetriLevel); }); } }); } } }); } } /**直线播放武器音效 */ playEffectStraight(weaponId: number) { //1,9,14,16 if (weaponId == WeaponType.DanGong) { gg.audio.playEffect({ name: '弹弓', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.TuJiBuBuQiang) { gg.audio.playEffect({ name: '突击步枪', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.DianCiPao) { gg.audio.playEffect({ name: '电磁炮', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ZhanShuZhuaDao) { gg.audio.playEffect({ name: '战术爪刀', path: 'sound/武器音效2/' }); }else if (weaponId == WeaponType.LiSanDaoDan) { gg.audio.playEffect({ name: '导弹', path: 'sound/武器音效2/' }); }else if (weaponId == WeaponType.LeiShenZhiChui) { gg.audio.playEffect({ name: '雷击', path: 'sound/武器音效2/' }); } } /**直线数量播放武器音效 */ playEffectStraightNum(weaponId: number) { //3 if (weaponId == WeaponType.ChangGong) { gg.audio.playEffect({ name: '射箭', path: 'sound/武器音效2/' }); }else if (weaponId == WeaponType.LeiShenZhiChui) { gg.audio.playEffect({ name: '雷击', path: 'sound/武器音效2/' }); } } /**直线弹射播放武器音效 */ playEffectStraightLaunch(weaponId: number) { //5,7 if (weaponId == WeaponType.HuiXuanBiao) { gg.audio.playEffect({ name: '回旋镖', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.FaZhen) { gg.audio.playEffect({ name: '法杖', path: 'sound/武器音效2/' }); } } /**抛物线碰撞播放武器音效 */ playEffectParabolaCollide(weaponId: number) { //2,4,6 if (weaponId == WeaponType.BiaoQiang) { gg.audio.playEffect({ name: '长矛', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ShiFu) { gg.audio.playEffect({ name: '斧头', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.FeiJian) { gg.audio.playEffect({ name: '飞剑', path: 'sound/武器音效2/' }); } } /**抛物线没有碰撞播放武器音效 */ playEffectParabolaNoCollide(weaponId: number) { //8,10,11,13,15 if (weaponId == WeaponType.FuWenTa) { gg.audio.playEffect({ name: '符文塔', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.DaPao) { gg.audio.playEffect({ name: '大炮', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ShouLiuDan) { gg.audio.playEffect({ name: '手榴弹', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.ZhuiZongDaoDan) { gg.audio.playEffect({ name: '追踪导弹', path: 'sound/武器音效2/' }); } else if (weaponId == WeaponType.BingDongShouLei) { gg.audio.playEffect({ name: '冰冻手雷', path: 'sound/武器音效2/' }); } } getCurWeaponId() { return this._curentWeaponId } setIsShootCan(can: boolean) { this._isCanShoot = can } }