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.
1350 lines
60 KiB
1350 lines
60 KiB
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;
|
|
/** 武器异步加载序号,丢弃过期回调 */
|
|
private _weaponLoadSeq = 0;
|
|
/** 武器异步加载中,避免 initCarrier / ensureBattleReady 并发重复挂载 */
|
|
private _weaponLoading = false;
|
|
_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;
|
|
}
|
|
|
|
/** 松手放置前:清卡牌模板拷贝上的武器引用,避免 instantiate 后重复挂载 */
|
|
public resetWeaponForBattlePlace(): void {
|
|
this.weaponNode = null;
|
|
this._weaponLoading = false;
|
|
this._weaponLoadSeq++;
|
|
}
|
|
|
|
/** 放置到战场:若 start 早于 setConfig 或武器未挂上,在此补初始化(武器仍挂方块「武器」节点) */
|
|
ensureBattleReady(): void {
|
|
if (!this._config || !this._isBattleScene) 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._isCanShoot = true;
|
|
this._syncWeaponNodeFromMount();
|
|
if (!this.weaponNode?.isValid && !this._weaponLoading) {
|
|
this.setView();
|
|
}
|
|
}
|
|
|
|
/** 救救我鸭:直接换武器(不走路径方块 / 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;
|
|
this._weaponLoadSeq++;
|
|
this._weaponLoading = false;
|
|
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)//雷电电击攻击单体
|
|
|
|
const table = gg.game?.CurentBattle?.TetraMap?._tetrisTable;
|
|
const onSceneTable = table?.isValid && this.node.parent === table;
|
|
if (onSceneTable) {
|
|
this.scheduleOnce(() => this.ensureBattleReady(), 0.02);
|
|
return;
|
|
}
|
|
if (this._isBattleScene) return;
|
|
this.scheduleOnce(() => this.initCarrier(), 0.02);
|
|
}
|
|
|
|
/** 方块 prefab 上的武器挂点 */
|
|
private getWeaponMountNode(): Node | null {
|
|
return find('武器', this.node);
|
|
}
|
|
|
|
/** 挂点上已有武器但 weaponNode 未同步(instantiate 克隆常见)时对齐引用并去掉多余实例 */
|
|
private _syncWeaponNodeFromMount(): void {
|
|
const mount = this.getWeaponMountNode();
|
|
if (!mount?.isValid) return;
|
|
const kids = mount.children.filter((c) => c?.isValid);
|
|
if (kids.length === 0) {
|
|
if (!this.weaponNode?.isValid) this.weaponNode = null;
|
|
return;
|
|
}
|
|
if (this.weaponNode?.isValid && kids.indexOf(this.weaponNode) >= 0) {
|
|
for (let i = 0; i < kids.length; i++) {
|
|
if (kids[i] !== this.weaponNode) this.safePutPoolNode(kids[i]);
|
|
}
|
|
return;
|
|
}
|
|
this.weaponNode = kids[0];
|
|
for (let i = 1; i < kids.length; i++) {
|
|
this.safePutPoolNode(kids[i]);
|
|
}
|
|
}
|
|
|
|
/** 清空挂点上动态加载的武器(保留挂点节点本身) */
|
|
private _clearWeaponMountChildren(): void {
|
|
const mount = this.getWeaponMountNode();
|
|
if (!mount?.isValid) return;
|
|
const kids = mount.children.slice();
|
|
for (let i = 0; i < kids.length; i++) {
|
|
this.safePutPoolNode(kids[i]);
|
|
}
|
|
this.weaponNode = null;
|
|
}
|
|
|
|
/** 武器预制体 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._syncWeaponNodeFromMount();
|
|
if (this.weaponNode?.isValid || this._weaponLoading) return;
|
|
// 战场方块由 ensureBattleReady 负责补武器,避免与 initCarrier 重复加载
|
|
if (this._isBattleScene) return;
|
|
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._syncWeaponNodeFromMount();
|
|
if (this.weaponNode?.isValid) {
|
|
if (this.weaponNode.name === conf.weaponani) return;
|
|
}
|
|
if (this._weaponLoading) 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) {
|
|
this._weaponLoadSeq++;
|
|
this._weaponLoading = false;
|
|
this._clearWeaponMountChildren();
|
|
this.addWeapon(weaponani)
|
|
}
|
|
|
|
// 武器挂到方块 prefab 的「武器」节点(local 0,0,偏移在 prefab 挂点配置)
|
|
addWeapon(weaponani) {
|
|
const wuqiNode = this.getWeaponMountNode();
|
|
if (!wuqiNode) {
|
|
console.error('[tetriWeaponCarrier] addWeapon: 未找到方块/武器', this._config?.cubename);
|
|
return;
|
|
}
|
|
if (this._weaponLoading) return;
|
|
this._weaponLoading = true;
|
|
const loadSeq = ++this._weaponLoadSeq;
|
|
void gg.res.getNodeAsync('prefab/', weaponani, GBundle.tetriWeapon).then((weaponNode) => {
|
|
this._weaponLoading = false;
|
|
if (loadSeq !== this._weaponLoadSeq) {
|
|
this.safePutPoolNode(weaponNode);
|
|
return;
|
|
}
|
|
if (!weaponNode?.isValid || !wuqiNode?.isValid) {
|
|
if (!weaponNode) {
|
|
console.error('[tetriWeaponCarrier] addWeapon: 武器预制未取到', weaponani, this._config?.cubename);
|
|
}
|
|
return;
|
|
}
|
|
this._clearWeaponMountChildren();
|
|
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;
|
|
}
|
|
|
|
/** 方块未落稳不发射;无 tetriNode 时仅救救我鸭直挂角色可发射(预览/卡牌模板有 carrier 无 node 不算) */
|
|
isSettledForWeaponShoot(): boolean {
|
|
const tetri = this.findCarrierTetriNode();
|
|
if (!tetri) {
|
|
return gg.game?.CurentBattle?.BattleType === BattleType.OrangeWeaponMode_Ya;
|
|
}
|
|
return 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
|
|
}
|
|
|
|
getIsShootCan(): boolean {
|
|
return this._isCanShoot;
|
|
}
|
|
}
|
|
|
|
|
|
|