消消方块阵换皮表情
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.

1592 lines
62 KiB

1 week ago
import { _decorator, BoxCollider2D, clamp01, Collider2D, Color, Component, Contact2DType, easing, IPhysics2DContact, isValid, Node, RigidBody2D, sp, Sprite, tween, Tween, UITransform, v3, Vec3 } from 'cc';
import MTools from 'db://assets/mx/tools/MTools';
import { BattlePerformance } from './BattlePerformance';
import { GBundle, UIID } from '../../game/ConfigRes';
import BombSpine from './WeaponSkill/BombSpine';
import { MonsterType } from '../../manager/MonsterDataManager';
import { MonsterBlood } from './MonsterBlood';
import { MapAgent } from './MapAgent';
import { SkillType } from './WeaponSkill/SkillTypes';
import { WeaponType } from '../../manager/WeaponDataManager';
import { BattleDataManager, BezierFallEffectType, SpecialSkillAddBuffType, SpecialSkillType } from './BattleDataManager';
import { SnowStormContinueSchedule } from './WeaponSkill/SnowStormContinueSchedule';
import { BubbleSchedule } from './WeaponSkill/BubbleSchedule';
import { acquireWeChatIosHurtFloatSpawn, HurtScore } from './HurtScore';
import { BuffControlSchedule } from './WeaponSkill/BuffControlSchedule';
import { BuffBurnSchedule } from './WeaponSkill/BuffBurnSchedule';
import { BuffIceSchedule } from './WeaponSkill/BuffIceSchedule';
import { BuffAccelerateSchedule } from './WeaponSkill/BuffAccelerateSchedule';
import { BuffElecSchedule } from './WeaponSkill/BuffElecSchedule';
import { BattleType } from '../../manager/ChapterDataManager';
import { DiamondTreasure } from './DiamondTreasure';
import { UILayer } from 'db://assets/mx/module/ui/UIManager';
import { GEvent } from 'db://assets/mx/module/event/GEvent';
import { CoinFly } from './CoinFly';
import { SingleWitch } from './NewMechanism/SingleWitch';
import { MonsterState } from './MonsterState';
import { Skill } from './WeaponSkill/Skill';
import { ConstNumType } from '../../game/ConfigProjectData';
import { SkillCostType } from '../../game/ConfigTableData';
import { Boss } from './Boss';
const { ccclass, property } = _decorator;
export { MonsterState } from './MonsterState';
/**怪物标签 */
export enum MonsterTag {
/**是否被控制 */
IS_BE_CONTROL = "IS_BE_CONTROL",
/**是否正在被激光武器攻击 */
IS_BE_ATTACKED_BY_LASER = "IS_BE_ATTACKED_BY_LASER",
}
@ccclass('Monster')
export class Monster extends Component {
/**下击特效父节点 */
downEff: Node = null;
/**上击特效父节点 */
upEff: Node = null;
/**怪物皮肤父节点 */
skin: Node = null;
/**血条父节点 */
bloodPos: Node = null;
/**怪物主动画组件 */
anim: sp.Skeleton = null;
/**怪物血条组件 */
boold: MonsterBlood = null;
/**宝箱特效 */
boxEff: Node = null;
/**box怪物引导特效 */
boxGuideEff: Node = null;
/**移动代理 */
private _mapAgent: MapAgent = null;
/**移动速度 */
private _speed: number = 0;
/**特殊模式的速度倍数 */
private _specialModelSpeedMultiple: number = 1;
/**速度倍数(减速buff影响) */
private _reduceSpeedMultiple: number = 1;
/**速度倍数(加速buff影响) */
private _accelerateSpeedMultiple: number = 0;
/**红月加速倍数 */
private _redMoonSpeedMultiple: number = 0;
/**当前状态 */
public state: MonsterState = MonsterState.MOVING_FORWARD;
/**上次攻击时间 */
private _lastAttackTime: number = 0;
/**攻击目标 */
private _atkTarget: Node = null;
/**出生点(世界坐标) */
private _bornPos: Vec3 = null;
/**设置障碍物前的位置 */
private _setObstacleBeforePos: Vec3 = null;
/**伤害倍数(增幅buff影响) */
private _damageMultiple: number = 1;
/**冰棒武器对怪物的最后攻击时间记录,键是武器实例ID,值是最后攻击时间*/
private _iceWeaponLastAttackTime = new Map<number, number>();
/**当前怪物身上的所有buff */
private _allBuff: SpecialSkillAddBuffType[] = [];
/**被同种武器攻击次数 */
private _sameWeaponAtkCount: Map<number, number> = new Map();
/**被同种武器攻击的累积倍数 */
private _sameWeaponAtkMultiple: number = 1;
/**特殊伤害倍数(老婆飞饼每0.5s伤害增幅) */
private _damageSpecialMultiple: number = 1;
private _canHideGuideEff: boolean = false;
/**怪物标签状态 */
private _tagStatus: Map<string, boolean> = new Map();
/**是不是保护期间, 保护期间怪物不会受到伤害*/
private _isProtecting: boolean = true;
5 days ago
/**保护时间 */
private _protectTime: number = 0;
1 week ago
isMonsterPlayGude = false
checkTag(tag: MonsterTag | string): boolean {
return this._tagStatus.get(tag) || false;
}
setTag(tag: MonsterTag | string, value: boolean) {
this._tagStatus.set(tag, value);
}
playGuide() {
this.isMonsterPlayGude = true
}
/** 怪物上层特效:对象池优先,无则异步加载 */
private async loadUpEffPrefab(path: string, name: string, bundle: string = GBundle.BattleGameMonster): Promise<Node | null> {
const node = await gg.res.getNodeAsync(path, name, bundle);
if (!node?.isValid || !this.upEff?.isValid) return null;
node.parent = this.upEff;
return node;
}
protected onLoad(): void {
this.downEff = this.node.find("downEff");
this.upEff = this.node.find("upEff");
this.skin = this.node.find("skin");
this.bloodPos = this.node.find("bloodPos");
this._mapAgent = this.node.getOrAddComponent(MapAgent);
this._mapAgent.curMap = gg.game.CurentBattle.curMap;
this._mapAgent.CanMove = false;
}
protected onEnable(): void {
let collider = this.getComponent(Collider2D);
if (collider) {
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
if (gg.game.CurentBattle.ChapterId == 1) {
GEvent.Ins.on(GEvent.onMovePlayer, this.onMovePlayer, this);
}
//GEvent.Ins.on(GEvent.BattleWarnTimeStart, this._onWarnTimeStart, this)
}
protected onDisable(): void {
let collider = this.getComponent(Collider2D);
if (collider) {
collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
if (gg.game.CurentBattle.ChapterId == 1) {
GEvent.Ins.off(GEvent.onMovePlayer, this.onMovePlayer, this);
}
//GEvent.Ins.off(GEvent.BattleWarnTimeStart, this._onWarnTimeStart, this);
}
// _onWarnTimeStart(){
// console.log('this.hp', this.hp)
// }
protected onDestroy(): void {
}
Data: ITableMonster = null;
/**怪物当前血量 */
hp: number = 0;
/**怪物最大血量 */
maxHp: number = 0;
/**护盾值 */
shield: number = 0;
/**最大护盾值 */
maxShield: number = 0;
setData(data: ITableMonster, hp: number) {
this.Data = data;
this._speed = this.Data.speed;
this.IsDead = false;
this._canHideGuideEff = false;
this.hp = hp * gg.game.monsterHpPercent;
this.maxHp = hp * gg.game.monsterHpPercent;
this.shield = 0;
this.maxShield = 0;
this._isProtecting = true
let time = gg.data.table.getConst(ConstNumType.MonsterRefreshUnselectableTime);
5 days ago
this._protectTime = time;
1 week ago
/**保护期间,时间内变化*/
this.init();
}
init() {
this.state = MonsterState.PATHFINDING;
if (this.Data.type == MonsterType.BigBoss) {
this.node.worldPositionX -= 40;
this.node.setPosition(this.node.x, -350, 0);
}
this._bornPos = this.node.worldPosition.clone();
this._isShowWenHaoEff = false;
// 兼容不同怪物 prefab 结构:优先 skin/anim,其次 skin 下任意 Spine 组件
let animNode = this.skin.find("anim");
this.anim = animNode?.getComponent(sp.Skeleton) || this.skin.getComponentInChildren(sp.Skeleton);
this.syncBloodPosToSkin();
//初始化血条
if (!this.boold && this.Data && this.Data.type != MonsterType.TreasureBox) {
let name = "";
if (this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss) {
name = "BOSS血条底框"
void gg.res.getNodeAsync('prefab/blood/', name, GBundle.BattleGameMonster).then((booldNode) => {
if (!booldNode?.isValid || !this.bloodPos?.isValid) return;
booldNode.setParent(this.bloodPos);
booldNode.active = true;
const bloodLayer = this.bloodPos?.layer ?? this.node.layer;
gg.game.CurentBattle?.setLayerMask(booldNode, bloodLayer);
this.boold = booldNode.getComponent(MonsterBlood);
this.boold.Data = this.Data;
this.boold.node.x = this.Data.type == MonsterType.BigBoss ? -100 : 0;
this.boold.node.y = this.Data.type == MonsterType.BigBoss ? 40 : 0;
this.updateBloodBar();
});
};
// if (this.Data.type == MonsterType.SmallMonster) name = "小怪血条底";
}
this.updateBloodBar();
// if (this.Data.type == MonsterType.MidBoss || this.Data.type == MonsterType.GoldenBoss || this.Data.type == MonsterType.OrangeBoss || this.Data.type == MonsterType.SilverBoss) {
// if (!this.boxEff) {
// this.boxEff = gg.res.getNode('prefab/', '宝箱光', GBundle.BattleGameMonster);
// if (this.boxEff)
// this.boxEff.setParent(this.downEff);
// }
// }
this._lastAttackTime = 0
this.IsDead = false;
this._mapAgent.CanMove = this.isCanMove();
this._specialModelSpeedMultiple = 1;//特殊模式的速度倍数,目前都是1倍
this._reduceSpeedMultiple = 1
this._accelerateSpeedMultiple = 0
if (this.Data.id == 90004 || this.Data.id == 90005) {
void this.loadUpEffPrefab('prefab/', '技能宝箱怪').then((boxGuideEff) => {
if (!boxGuideEff) return;
this.boxGuideEff = boxGuideEff;
boxGuideEff.setPosition(0, 150, 0);
boxGuideEff.active = this._canHideGuideEff;
});
// this.scheduleOnce(() => {
// this._canHideGuideEff = true;
// }, 5);
}
this._showShield = false;
this._totalShieldHP = 1;
this._curShieldHP = 0;
this.isKill = false;
this.isKillByHp = false;
this.playWalk()
if (gg.game.CurentBattle?.curMap && isValid(gg.game.CurentBattle.curMap)) {
this._mapAgent.curMap = gg.game.CurentBattle.curMap;
}
this.getAtkTarget();
if (this.Data.type == MonsterType.BigBoss) {
let boss = this.node.getOrAddComponent(Boss);
boss.setMonster(this);
}
}
/**按 skin 实际显示大小重定位血条锚点 */
private syncBloodPosToSkin() {
if (!this.bloodPos || !this.skin) return;
const collider = this.node.getComponent(BoxCollider2D);
if (!collider) return;
const topPadding = this.Data?.type == MonsterType.SmallMonster ? 10 : 20;
let y = collider.offset.y + collider.size.y * 0.5 + topPadding;
if (this.Data.model == "空投怪物箱子" || this.Data.model == "伞兵" || this.Data.model == "女巫丧尸(加血)")
y = y - 120;
this.bloodPos.setPosition(0, y, 0);
}
protected update(dt: number): void {
5 days ago
this._protectTime -= dt;
if (this._protectTime <= 0) {
this._isProtecting = false;
}
1 week ago
if (!gg.game.isBattleContextActive()) return;
if (this.isMonsterPlayGude) {
// 引导怪只做演出攻击,不参与寻路移动,避免出现“乱跳/抖动”
if (this._mapAgent) {
this._mapAgent.CanMove = false;
}
this.tryAttack(dt);
return
}
if (gg.game.CurentBattle.IsGameOver) return;
if (gg.game.CurentBattle.IsGamePause) return;
if (!this.Data || this.Data.type == MonsterType.TreasureBox) {
return
}
if (this.Data && this.Data.id == 40041) return
const mapAgent = this._mapAgent;
switch (this.state) {
case MonsterState.MOVING_FORWARD:
if (mapAgent) {
mapAgent.MoveSpeed = this.getMoveSpeed();
// 控制(眩晕/粘液等)期间禁止移动;不可每帧 true 覆盖 resetControlBuff 的 false
mapAgent.CanMove = this.isCanMove();
}
break;
case MonsterState.PATHFINDING:
if (mapAgent) {
mapAgent.MoveSpeed = this.getMoveSpeed();
mapAgent.CanMove = this.isCanMove();
}
break;
case MonsterState.ATTACKING:
if (mapAgent) mapAgent.CanMove = false;
this.tryAttack(dt);
break;
case MonsterState.STOP:
if (mapAgent) mapAgent.CanMove = false;
break;
}
}
// 攻击玩家
private attack() {
if (!this._atkTarget) return;
if (gg.game.CurentBattle.IsGameOver) return;
this.playStack();
if (!MTools.beforeTimes(100, "monster_atk_wall_sfx")) {
gg.audio.playEffect({ name: "怪物攻击城墙" });
}
let atk_multiple = gg.game.CurentBattle.CurentConfigChapter.atk_multiple;
let atk = this.Data.attack * atk_multiple;
if (gg.game.CurentBattle.BattleType == BattleType.InfiniteMode && gg.game.CurentBattle.InfiniteModeCurentWaveNum > 20) {
let num = gg.data.table.getConst(ConstNumType.InfiniteModeAtkBaseMultiple)
atk = atk * Math.pow(num, gg.game.CurentBattle.InfiniteModeCurentWaveNum - 20);
}
//取整数
atk = atk * gg.game.monsterAttackPercent;
atk = Math.floor(atk);
if (this.isMonsterPlayGude) {
GEvent.Ins.emit(GEvent.playerHit, this._atkTarget)
return
}
gg.game.CurentBattle.subWallHp(atk);
GEvent.Ins.emit(GEvent.playerHit, this._atkTarget)
}
/**显示血条 */
showBloodBar() {
if (this.boold) this.boold.node.active = true;
}
/**隐藏血条 */
hideBloodBar() {
if (this.boold) this.boold.node.active = false;
}
/**更新血条 */
updateBloodBar() {
if (this.boold) this.boold.updateBloodBar(this.hp, this.maxHp, this.shield, this.maxShield);
}
/**展示受击特效(受击特效) */
showBeAtkAni(weaponid?) {
if (MTools.beforeTimes(BattlePerformance.hitVfxCooldownMs(), this.uuid)) return;
if (gg.game.CurentBattle.BattleType == BattleType.BossMode && this.Data.type == MonsterType.BigBoss) {
return;
}
if (!BattlePerformance.rollShowCombatVfx(0.48)) return;
let conf = gg.data.project.getWeaponData(weaponid);
let atkPosIndex = conf.atkPos;
let [spineName, animName] = conf.hitEffect.split('|');
void gg.res.getNodeAsync('prefab/爆炸/', animName, GBundle.BattleGameWeapon).then((spineNode) => {
if (!spineNode?.isValid || !this.upEff?.isValid) return;
spineNode.setParent(this.upEff);
const layer = this.upEff?.layer ?? this.node.layer;
gg.game.CurentBattle?.setLayerMask(spineNode, layer);
let pos = this.getPosByAtkPosIndex(atkPosIndex);
spineNode.worldPosition = pos;
const vfxMul = BattlePerformance.vfxScaleMul();
spineNode.setScale(vfxMul, vfxMul, vfxMul);
let bombComp = spineNode.getComponent(BombSpine);
bombComp.playSpine(spineName, () => {
bombComp.destoryNode();
}, 1, false);
});
}
/** 直线武器瞄准用:碰撞盒中心世界坐标(缩小 Boss 包围盒后仍与可命中区域一致) */
getColliderAimWorldPos(): Vec3 {
const ui = this.node.getComponent(UITransform);
const collider = this.node.getComponent(BoxCollider2D);
if (ui && collider) {
return ui.convertToWorldSpaceAR(v3(collider.offset.x, collider.offset.y, 0));
}
return this.node.worldPosition.clone();
}
/**根据攻击点索引获取世界坐标 */
getPosByAtkPosIndex(atkPosIndex: number) {
const ui = this.node.getComponent(UITransform);
const collider = this.node.getComponent(BoxCollider2D);
if (!ui || !collider) {
return this.node.worldPosition.clone();
}
// 使用“当前实例”碰撞体(已按怪物模型与缩放同步)计算头/腰/脚受击点
const halfH = collider.size.y * 0.5;
const bottomY = collider.offset.y - halfH;
const topY = collider.offset.y + halfH;
const h = Math.max(1, topY - bottomY);
// 1=头部 2=腰部 3=脚部
let localY = bottomY + h * 0.5; // 默认腰部
if (atkPosIndex == 1) {
localY = bottomY + h * 0.82;
} else if (atkPosIndex == 3) {
localY = bottomY + h * 0.18;
}
return ui.convertToWorldSpaceAR(v3(collider.offset.x, localY, 0));
}
/**获取移动速度 */
getMoveSpeed() {
let reduceSpeedMultiple = this._reduceSpeedMultiple;
if (this.Data.type == MonsterType.Boss) {
reduceSpeedMultiple = this._reduceSpeedMultiple + gg.game.CurentBattle.fubenBoxReduceBossSpeed / 10000;
}
// 计算这一帧应该移动的距离
const moveDistance = this._speed / reduceSpeedMultiple / this._specialModelSpeedMultiple * (this._accelerateSpeedMultiple + this._redMoonSpeedMultiple + 1);
return moveDistance;
}
// 尝试攻击玩家
private tryAttack(deltaTime: number) {
if (this.isMonsterPlayGude) {
this._lastAttackTime += deltaTime * gg.game.CurentBattle.TimeScale;
if (this._lastAttackTime >= 1 / this.Data.attack_speed) {
this._lastAttackTime = 0;
this.attack();
}
return
}
if (!gg.game.CurentBattle.canUpdateFrame()) return;
if (!this._atkTarget) return;
this._lastAttackTime += deltaTime * gg.game.CurentBattle.TimeScale;
if (this._lastAttackTime >= 1 / this.Data.attack_speed) {
this._lastAttackTime = 0;
this.attack();
}
}
/**获取攻击目标 */
public getAtkTarget() {
if (!this.Data || this.Data.type == MonsterType.TreasureBox) {
return
}
const map = gg.game.CurentBattle?.curMap;
if (!map || !isValid(map)) return;
this._atkTarget = map.getPathShortestTarget(this.node.worldPosition);
if (this._atkTarget) {
this._mapAgent.tryAutoSetTargetFromTetraMap();
}
}
teleportToBornPos() {
this.node.worldPosition = this._bornPos;
this.state = MonsterState.PATHFINDING;
this.getAtkTarget();
this.playWalk()
}
onMovePlayer() {
this._canHideGuideEff = true;
if (this.boxGuideEff && this.boxGuideEff.isValid) {
this.boxGuideEff.active = true;
this.scheduleOnce(() => {
if (this.boxGuideEff && this.boxGuideEff.isValid) {
this.boxGuideEff.active = false;
}
}, 20)
}
}
/**被秒杀 */
private isKill = false;
/**被斩杀 */
private isKillByHp = false;
/**是否碰撞过怪物 */
private isColliderMonster = false;
/**是否死亡 */
public IsDead: boolean = false;
onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
// 物理接触调试:若你看到「节点坐标相距很远但仍触发 contact」
// 打开此开关看真正的接触点(manifold)与两个碰撞体的 AABB
let skill: Skill = otherCollider.node.getComponent(Skill);
const tetriLevel = (skill as any)?.tetriLevel ?? 1;
let weaponData = skill.weaponData
if (!weaponData) {
return
}
if (this.isKill || this.isKillByHp || this.IsDead || skill.getIsDead()) return;
// 弹射换靶后仍与上一只怪重叠:不能依赖 getColliderMonster,否则一清标记就会立刻再次命中同一只
if (skill.shouldIgnoreCatapultContactFrom(this.node.uuid)) return;
if (skill.isWeapon20StraightPierce()) {
if (skill.hasPierceHitMonster(this.node.uuid)) return;
} else if (skill.getColliderMonster()) {
return;
}
//子弹碰到怪物就消失
if (weaponData.flight == SkillType.线 || weaponData.flight == SkillType.线
|| weaponData.flight == SkillType.线 || weaponData.flight == SkillType.线穿) {
if (weaponData.weaponid == WeaponType.DanGong) {
// if (!MTools.beforeTimes(100, "audio_timeStamp")) {
// gg.audio.playEffect({ name: '彩虹飞弹命中', path: 'sound/武器音效/' });
// }
}
this.attackByWeapon(weaponData.weaponid, otherCollider.node, tetriLevel)
if (skill.getSplitParent()) {
skill.setColliderMonster(true)
skill.removeBullet()
} else if (skill.catapultCount > 0 && !skill.getIsSplitBullet()) {
skill.setColliderMonster(true)
Tween.stopAllByTarget(skill.node)
skill.catapultCount--
const hitUuid = this.node.uuid
this.scheduleOnce(() => {
skill.findNextTarget(hitUuid)
}, 0)
} else if (skill.isWeapon20StraightPierce()) {
skill.markPierceHitMonster(this.node.uuid)
if (skill.subPiercingCount() <= 0) {
skill.removeBullet()
}
} else {
skill.setColliderMonster(true)
const pierceLeft = skill.subPiercingCount()
if (pierceLeft <= 0) {
skill.removeBullet()
} else {
skill.setColliderMonster(false)
}
}
} else if (weaponData.flight == SkillType.线) {
this.attackByWeapon(weaponData.weaponid, otherCollider.node, tetriLevel)
// 弹射次数在命中时扣减;避免 miss 时也扣次数导致异常
skill.setColliderMonster(true)
let isSplitBullet = skill.getIsSplitBullet()
//分裂子弹不弹射
// console.log('分裂子弹不弹射 isSplitBullet',isSplitBullet, skill.catapultCount)
if (skill.catapultCount <= 0 || isSplitBullet) {
skill.removeBullet()
} else {
Tween.stopAllByTarget(skill.node)
skill.catapultCount--
const hitUuid = this.node.uuid
this.scheduleOnce(() => {
skill.findNextTarget(hitUuid)
}, 0)
}
} else if (weaponData.flight == SkillType.线) {
this.attackByWeapon(weaponData.weaponid, otherCollider.node)
let originPos = selfCollider.node.worldPosition.clone()
//如果是分裂的子弹,不继续分裂
let isSplitBullet = (otherCollider.node.getComponent('Skill') as any).getIsSplitBullet()
if (!(otherCollider.node.getComponent('Skill') as any).getIsDead() && !isSplitBullet) {
// console.log('穿天猴碰到怪物分裂两个子弹+++')
let splitCount = 2
gg.game.CurentBattle.SkillController.splitBullet(weaponData.weaponid, originPos, splitCount, 0, 0, 0, tetriLevel)
}
if (skill.subPiercingCount() <= 0 && !skill.getIsDead()) {
if (isValid(otherCollider.node)) {
skill.removeBullet()
}
}
} else if (weaponData.flight == SkillType.线) {
skill.setColliderMonster(true)
this.attackByWeapon(weaponData.weaponid, otherCollider.node, tetriLevel)
// 抛物线命中后仍继续运动;是否旋转由 ConstantSpeedParabola.enableRotation 控制
//(这里不再 stop tween,避免回调不执行)
} else if (weaponData.flight == SkillType.线) {
//fix me
skill.setColliderMonster(true)
}
}
/**被武器攻击 */
attackByWeapon(weaponid: number, otherCollider?: Node, tetriLevel: number = 1, allAtkScale: number = 1) {
let weaponPropertyData = BattleDataManager.getWeaponAddProperty(weaponid, this._allBuff, tetriLevel)
let allAtk = weaponPropertyData.allAtk
if (this.Data && this.Data.type == MonsterType.TreasureBox) {
//宝箱增伤
allAtk = allAtk * (1 + gg.game.CurentBattle.fubenBoxAddDamage / 10000)
}
const atkMul = typeof allAtkScale === 'number' && Number.isFinite(allAtkScale) && allAtkScale > 0 ? allAtkScale : 1;
if (atkMul !== 1) {
allAtk *= atkMul;
}
//部分武器有控制时间,造成减速等效果
if (weaponPropertyData.controlTime > 0) {
this.resetControlBuff(1, weaponPropertyData.controlTime, weaponid)
}
//冰棍减速特效
if (weaponPropertyData.reduceSpeedTime > 0 && weaponPropertyData.reduceSpeedRange > 0 && weaponid == (WeaponType as any).IceBar) {
let reduceSpeed = Number(weaponPropertyData.reduceSpeedRange)//减速倍数
let reduceTime = Number(weaponPropertyData.reduceSpeedTime)//减速时间
this._reduceSpeedMultiple = 1 + reduceSpeed / 10000
this.resetIceBuff(reduceTime)
}
//跳跳糖自带感电特效
if (weaponPropertyData.reduceSpeedTime > 0 && weaponPropertyData.reduceSpeedRange > 0 && weaponid == (WeaponType as any).jumpSuger) {
let addDamage = Number(weaponPropertyData.reduceSpeedRange)//伤害增幅倍数
let addDamageTime = Number(weaponPropertyData.reduceSpeedTime)//伤害增幅时间
this._damageMultiple = 1 + addDamage / 10000
this.resetElecBuff(addDamageTime)
}
//特殊技能
if (weaponPropertyData.specialSkillInfo.length > 0) {
this.attackBySpecialWeapon(weaponid, weaponPropertyData.specialSkillInfo, allAtk, weaponPropertyData, otherCollider)
}
//特殊技能buff 减速,燃烧等
//console.log('当前武器特殊技能buff==',weaponPropertyData.specialSkillAddBuffInfo)
if (weaponPropertyData.specialSkillAddBuffInfo.length > 0) {
this.attackBySpecialWeaponBuff(weaponid, weaponPropertyData.specialSkillAddBuffInfo)
}
this.showBeAtkAni(weaponid)
this.subHP(weaponid, allAtk, weaponPropertyData.isCrit, weaponPropertyData.addRateAtk)
}
/**被特殊武器攻击 */
attackBySpecialWeapon(weaponid, specialSkillInfo, allAtk, weaponPropertyData, otherCollider?: Node,) {
for (let i = 0; i < specialSkillInfo.length; i++) {
let buffInfo = specialSkillInfo[i]
let arr = buffInfo.split(',')
let buffType = Number(arr[0])
//分裂子弹
if (buffType == SpecialSkillType.divideBullet || buffType == SpecialSkillType.splitBulletWithContinueTime) {
if (!isValid(otherCollider)) {
continue
}
//如果是分裂的子弹,不继续分裂
let isSplitBullet = (otherCollider.getComponent('Skill') as any).getIsSplitBullet()
if (isSplitBullet) {
//console.log('分裂子弹不继续分裂')
continue
}
// console.log('分裂子弹',buffInfo)
//分裂子弹
if (!this.isColliderMonster) {
// console.log('彩虹飞弹碰到怪物分裂三个子弹+++',new Date().getTime())
let splitCount = Number(arr[1]);
let damageRate = Number(arr[2]) || 0; //伤害比例
let stayTime = Number(arr[3]) || 0;
let damageRadius = Number(arr[4]) || 0;
//设置分裂的母体
otherCollider.getComponent(Skill).setSplitParent(true)
//分裂子弹
let originPos = otherCollider.worldPosition.clone()
const skSplit = otherCollider.getComponent(Skill);
const tetriLv = skSplit?.tetriLevel ?? 1;
gg.game.CurentBattle.SkillController.splitBullet(weaponid, originPos, splitCount, stayTime, damageRate, damageRadius, tetriLv)
}
//同一单位伤害递增
} else if (buffType == SpecialSkillType.damageIncrease) {
let rate = Number(arr[1]) / 10000//伤害增加倍数
let maxTimes = Number(arr[2])//最大增加次数
//记录被同种武器攻击次数
let atkCount = this._sameWeaponAtkCount.get(weaponid) || 0
atkCount++
if (atkCount > maxTimes) {
atkCount = maxTimes
}
this._sameWeaponAtkCount.set(weaponid, atkCount)
//同一单位伤害递增
this._sameWeaponAtkMultiple = 1 + rate * atkCount
//范围越来越大,伤害也越来越大
} else if (buffType == SpecialSkillType.moreLarge) {
let largeCount = Number(arr[1])//模型增加倍数
let damageInterval = Number(arr[2])//x秒增加一次伤害
let rate = Number(arr[3]) / 10000//伤害增加倍数
let aliveTime = (otherCollider.getComponent('Skill') as any).getAliveTime()
let addTime = Math.floor(aliveTime / damageInterval * rate * 100) / 100
this._damageSpecialMultiple = 1 + addTime
} else if (buffType == SpecialSkillType.extraDamage) {
//额外伤害
let damageTimes = Number(arr[1]) / 10000//伤害倍数
//添加一个技能表现 作用一次就行
let repeatCount = 1
//扣血
//播放动画anim fix me
this.playColaBomb('符文爆炸', this.node.position.clone(), 1)
this.subHP(weaponid, allAtk * damageTimes, weaponPropertyData.isCrit, weaponPropertyData.addRateAtk)
} else if (buffType == SpecialSkillType.extraExplode) {
let damageRadius = Number(arr[1])//伤害半径
let damageTimes = Number(arr[2])//伤害倍数
//
let fallMonsters = gg.game.CurentBattle.MonsterSpawner.getMonstersInArea(this.node.worldPosition.clone(), damageRadius)
//播放一次爆炸
this.playColaBomb('符文爆炸', this.node.position.clone(), 1)
for (let i = 0; i < fallMonsters.length; i++) {
let monster = fallMonsters[i]
monster.getComponent(Monster).subHP(weaponid, allAtk * damageTimes, weaponPropertyData.isCrit, weaponPropertyData.addRateAtk)
}
}
}
}
/**符文爆炸 */
public playColaBomb(bombSpine: string, pos: Vec3, scale: number) {
if (!BattlePerformance.rollShowCombatVfx()) return;
const mapNode = gg.game.CurentBattle?.curMap?.node;
if (!mapNode?.isValid) return;
const localPos = pos.clone();
// 碰撞回调内勿同步改节点树;且动态加载下 getNode 可能尚未命中 prefab
void gg.res.getNodeAsync(`prefab/爆炸/`, bombSpine, GBundle.BattleGameWeapon).then((spineNode) => {
if (!spineNode?.isValid || !mapNode?.isValid) return;
spineNode.setParent(mapNode);
spineNode.setPosition(localPos);
const adj = BattlePerformance.adjustVfxScale(scale);
spineNode.scale = v3(adj, adj, 0);
const bombComp = spineNode.getComponent(BombSpine);
if (!bombComp) {
gg.res.putNode(spineNode);
return;
}
bombComp.playSpine('play', () => {
bombComp.destoryNode();
}, 1, false);
});
}
/**生成爆炸Component */
public createBombComponent(bombSpine: string, pos: Vec3, scale: number, parentNode: Node = null, allowSkip = true) {
if (allowSkip && !BattlePerformance.rollShowCombatVfx()) {
return null;
}
let spineNode = gg.res.getNode(`prefab/爆炸/`, `${bombSpine}`, GBundle.BattleGameWeapon)
if (!spineNode) {
console.warn('[Monster] bombSpine 未就绪=', bombSpine);
return null;
}
const parent = parentNode?.isValid ? parentNode : gg.game.CurentBattle?.curMap?.node;
if (!parent?.isValid) {
gg.res.putNode(spineNode);
return null;
}
spineNode.setParent(parent);
spineNode.setPosition(pos)
const adj = BattlePerformance.adjustVfxScale(scale);
spineNode.scale = v3(adj, adj, 0)
let bombComp = spineNode.getComponent(BombSpine);
return bombComp
}
/**被特殊武器buff攻击 */
attackBySpecialWeaponBuff(weaponid, specialSkillAddBuffInfo) {
//console.log('被特殊武器buff攻击', specialSkillAddBuffInfo)
for (let i = 0; i < specialSkillAddBuffInfo.length; i++) {
let buffInfo = specialSkillAddBuffInfo[i]
let arr = buffInfo.split(',')
let buffType = Number(arr[0])
if (buffType == SpecialSkillAddBuffType.burn) {
//燃烧
let interval = Number(arr[1])//x秒造成一次伤害
let damageTimes = Number(arr[2]) / 10000//每次造成的伤害倍数
let allTime = Number(arr[3])//总时间
//概率
let rate = Number(arr[4]) / 10000//概率挂上buff
if (rate >= 1) {
let repeatCount = Math.ceil(allTime / interval)//造成伤害次数
this.resetBurnBuff(weaponid, damageTimes, repeatCount, interval)
} else {
if (Math.random() <= rate) {
let repeatCount = Math.ceil(allTime / interval)//造成伤害次数
this.resetBurnBuff(weaponid, damageTimes, repeatCount, interval)
}
}
} else if (buffType == SpecialSkillAddBuffType.reduceSpeed) {
//减速
let reduceSpeed = Number(arr[1])//减速倍数
let reduceTime = Number(arr[2])//减速时间
//概率
let rate = Number(arr[3])//减速概率
if (Math.random() * 10001 <= rate) {
this._reduceSpeedMultiple = 1 + reduceSpeed / 10000
this.resetIceBuff(reduceTime)
}
} else if (buffType == SpecialSkillAddBuffType.elec) {
//伤害增幅
let addDamage = Number(arr[1])//伤害增幅倍数
let addDamageTime = Number(arr[2])//伤害增幅时间
//概率
let rate = Number(arr[3])//感电概率
if (Math.random() * 10001 <= rate) {
this._damageMultiple = 1 + addDamage / 10000
this.resetElecBuff(addDamageTime)
}
} else if (buffType == SpecialSkillAddBuffType.control) {
//眩晕
let controlRate = Number(arr[1])//控制概率
let controlTime = Number(arr[2])//控制时间
if (Math.random() <= controlRate / 10000) {
this.resetControlBuff(1, controlTime, weaponid)
}
}
else if (buffType == SpecialSkillAddBuffType.kill) {
//秒杀
let killRate = Number(arr[1])//秒杀几率
if (Math.random() <= killRate / 10000) {
//秒杀成功
this.onKill();
}
}
else if (buffType == SpecialSkillAddBuffType.killHp) {
//斩杀
let killRate = Number(arr[1])//斩杀血线
if (this.hp / this.maxHp <= killRate / 10000) {
//斩杀成功
this.onKillForHp();
}
} else if (buffType == SpecialSkillAddBuffType.blood) {
//流血
let interval = Number(arr[1])//x秒造成一次伤害
let damageTimes = Number(arr[2]) / 10000 //每次造成的伤害倍数
let allTime = Number(arr[3])//总时间
let repeatCount = Math.ceil(allTime / interval)//造成伤害次数
this.resetBloodBuff(weaponid, damageTimes, repeatCount, interval)
}
}
}
/**被秒杀 */
onKill() {
//BOSS不会被秒杀
if (this.Data.type == MonsterType.MidBoss || this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss) return;
this.isKill = true;
this.showKillEff(1);
}
/**被斩杀 */
onKillForHp() {
//BOSS不会被秒杀
if (this.Data.type == MonsterType.MidBoss || this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss) return;
//斩杀成功
this.isKillByHp = true;
this.showKillEff(2);
}
/**获取冰棒武器的旋转速度 */
private getIceWeaponRotateSpeed(): number {
// 从SkillController获取冰棒武器的旋转速度
const skillController = gg.game.CurentBattle.SkillController;
const iceWeaponScheduleScr = skillController.node.getComponent('iceWeaponSchedule') as any;
if (iceWeaponScheduleScr) {
return iceWeaponScheduleScr.rotateSpeed;
}
// 默认旋转速度
return 1;
}
/**重置控制buff */
resetControlBuff(repeatCount, interval, weaponid) {
let nameStr = weaponid == (WeaponType as any).NiuPiTang ? '怪_粘液' : '怪_晕眩'
const apply = (spine: Node) => {
this._controlSpine = spine;
spine.active = true;
this._mapAgent.CanMove = false;
this.addBuff(SpecialSkillAddBuffType.control);
let com = this.node.getComponent(BuffControlSchedule);
if (!com) {
com = this.node.addComponent(BuffControlSchedule);
}
com.resetControlBuff(interval, repeatCount);
};
if (this._controlSpine) {
apply(this._controlSpine);
return;
}
void this.loadUpEffPrefab("prefab/", nameStr).then((node) => node && apply(node));
}
private _controlSpine: Node = null;
/**重置燃烧buff */
resetBurnBuff(weaponid, damageTimes, repeatCount, interval) {
const apply = (spine: Node) => {
this._burnSpine = spine;
spine.active = true;
this.addBuff(SpecialSkillAddBuffType.burn);
let com = this.node.getComponent(BuffBurnSchedule);
if (!com) {
com = this.node.addComponent(BuffBurnSchedule);
}
com.resetFireBuff(weaponid, damageTimes, interval, repeatCount);
};
if (this._burnSpine) {
apply(this._burnSpine);
return;
}
void this.loadUpEffPrefab("prefab/", "怪_火").then((node) => node && apply(node));
}
/**重置掉血buff */
resetBloodBuff(weaponid, damageTimes, interval, repeatCount) {
const apply = (spine: Node) => {
this._burnSpine = spine;
spine.active = true;
this.addBuff(SpecialSkillAddBuffType.burn);
let com = this.node.getComponent(BuffBurnSchedule);
if (!com) {
com = this.node.addComponent(BuffBurnSchedule);
}
com.resetFireBuff(weaponid, damageTimes, interval, repeatCount);
};
if (this._burnSpine) {
apply(this._burnSpine);
return;
}
void this.loadUpEffPrefab("prefab/", "怪_掉血").then((node) => node && apply(node));
}
private _burnSpine: Node = null;
/**重置冰霜buff */
resetIceBuff(reduceTime, reduceSpeed = 0, isNew = true) {
if (reduceSpeed != 0) {
this._reduceSpeedMultiple = 1 + reduceSpeed / 10000
}
const apply = (spine: Node) => {
this._reduceSpeedSpine = spine;
spine.active = true;
this.addBuff(SpecialSkillAddBuffType.reduceSpeed);
if (reduceTime != -1) {
let com = this.node.getComponent(BuffIceSchedule);
if (!com) {
com = this.node.addComponent(BuffIceSchedule);
}
com.resetIceBuff(reduceTime, 1, this._reduceSpeedSpine);
}
};
if (this._reduceSpeedSpine) {
apply(this._reduceSpeedSpine);
return;
}
void this.loadUpEffPrefab("prefab/", "怪_冰").then((node) => node && apply(node));
}
private _reduceSpeedSpine: Node = null;
/**重置加速buff(加速倍数,加速时间) */
resetAccelerateBuff(speedTimes, buffTime) {
const apply = (spine: Node) => {
this._accSpine = spine;
spine.active = true;
this.addBuff(SpecialSkillAddBuffType.accelerate);
let com = this.node.getOrAddComponent(BuffAccelerateSchedule);
com.resetAccelerateBuff(buffTime, 1, this._accSpine);
this._accelerateSpeedMultiple = speedTimes;
};
if (this._accSpine) {
apply(this._accSpine);
return;
}
void this.loadUpEffPrefab("prefab/", "怪_加速").then((node) => node && apply(node));
}
private _accSpine: Node = null;
/**重置感电buff */
resetElecBuff(addDamageTime) {
const apply = (spine: Node) => {
this._elecSpine = spine;
spine.active = true;
this.addBuff(SpecialSkillAddBuffType.elec);
if (addDamageTime != -1) {
let com = this.node.getComponent(BuffElecSchedule);
if (!com) {
com = this.node.addComponent(BuffElecSchedule);
}
com.resetElecBuff(addDamageTime, 1);
}
};
if (this._elecSpine) {
apply(this._elecSpine);
return;
}
void this.loadUpEffPrefab("prefab/", "怪_电").then((node) => node && apply(node));
}
private _elecSpine: Node = null;
getAllBuff() {
return this._allBuff
}
addBuff(buffType) {
if (this._allBuff.indexOf(buffType) == -1) {
this._allBuff.push(buffType)
}
}
removeBuff(buffType) {
let index = this._allBuff.indexOf(buffType)
if (index != -1) {
this._allBuff.splice(index, 1)
}
if (buffType == SpecialSkillAddBuffType.reduceSpeed) {
this._reduceSpeedMultiple = 1
if (this._reduceSpeedSpine) {
gg.res.putNode(this._reduceSpeedSpine);
this._reduceSpeedSpine = null;
}
} else if (buffType == SpecialSkillAddBuffType.elec) {
this._damageMultiple = 1
if (this._elecSpine) {
gg.res.putNode(this._elecSpine);
this._elecSpine = null;
}
} else if (buffType == SpecialSkillAddBuffType.control) {
// 控制结束:按当前状态恢复是否可移动(避免攻击/停顿时误开移动)
if (this._mapAgent) {
this._mapAgent.CanMove = this.isCanMove();
}
if (this._controlSpine) {
gg.res.putNode(this._controlSpine);
this._controlSpine = null;
}
} else if (buffType == SpecialSkillAddBuffType.accelerate) {
this._accelerateSpeedMultiple = 0
if (this._accSpine) {
gg.res.putNode(this._accSpine);
this._accSpine = null;
}
} else if (buffType == SpecialSkillAddBuffType.burn) {
if (this._burnSpine) {
gg.res.putNode(this._burnSpine);
this._burnSpine = null;
}
}
}
isCanMove() {
if (this.Data.id == 40041) return false;
if (this.state !== MonsterState.MOVING_FORWARD && this.state !== MonsterState.PATHFINDING) return false;
const isControlBuff = this._allBuff.indexOf(SpecialSkillAddBuffType.control) !== -1;
if (isControlBuff) return false;
if (this._isShowWenHaoEff) return false;
return true;
}
/**怪物掉血(武器id,掉血值,是否暴击,伤害倍率,是否炸弹道具) */
subHP(weaponId: number, hp: number, isCrit: boolean, addRateAtk: number = 1, isBombProp: boolean = false) {
if (hp <= 0) return
// 保护期间:受到伤害为 0
if (this._isProtecting) return
let trueHp = Math.floor(hp * this._damageMultiple * this._sameWeaponAtkMultiple * this._damageSpecialMultiple)
if (isBombProp) {
trueHp = hp
}
let haveHp = trueHp - this._curShieldHP;
if (this._curShieldHP > 0) {
this._curShieldHP -= trueHp;
this.updateShield();
if (this._curShieldHP <= 0) {
this.closeShield();
}
}
if (this.isKill || this.isKillByHp) {
trueHp = this.maxHp - this.hp;
this.hp -= this.maxHp;
} else if (haveHp > 0) {
this.hp -= trueHp;
}
gg.PRR.PlayerDamageTotal += trueHp
this.showSubHpNum(trueHp, isCrit, addRateAtk)
//setBooldNum
if (this.hp <= 0) {
this.hp = 0;
this.die();
} else {
this.showBeAtkEffect()
}
this.setTag("weaponAtkCount" + weaponId, true);
this.updateBloodBar();
//gg.game.CurentBattle.DamageStatistics.updateWeaponDamage(weaponId, trueHp)
}
/**
* @param hp ()
* @param isCoefficient
*/
addHP(hp: number, isCoefficient: boolean) {
if (isCoefficient) {
hp = Math.floor(this.maxHp * hp)
}
this.hp += hp;
if (this.hp > this.maxHp) {
this.hp = this.maxHp;
}
this.clearAddHpEff();
void (async () => {
if (!this.addHpEff1?.isValid) {
this.addHpEff1 = await this.loadUpEffPrefab("prefab/", "怪物加血");
}
if (!this.addHpEff1?.isValid) return;
this.addHpEff1.position = Vec3.ZERO;
this.addHpEff1.active = true;
this.addHpEff1.getComponent(sp.Skeleton).setAnimation(0, '加血怪物上层', false);
if (!this.addHpEff2?.isValid) {
this.addHpEff2 = await gg.res.getNodeAsync("prefab/", "怪物加血", GBundle.BattleGameMonster);
if (this.addHpEff2?.isValid) {
this.addHpEff2.setParent(this.upEff);
}
}
if (!this.addHpEff2?.isValid) return;
this.addHpEff2.position = Vec3.ZERO;
this.addHpEff2.active = true;
this.addHpEff2.getComponent(sp.Skeleton).setAnimation(0, '加血怪物下层', false);
this.addHpEff2.setSiblingIndex(0);
})();
this.scheduleOnce(() => {
this.clearAddHpEff();
}, 2)
this.updateBloodBar();
}
/**清除加血特效 */
clearAddHpEff() {
if (this.addHpEff1 && this.addHpEff1.isValid) gg.res.putNode(this.addHpEff1);
if (this.addHpEff2 && this.addHpEff2.isValid) gg.res.putNode(this.addHpEff2);
this.addHpEff1 = null;
this.addHpEff2 = null;
}
addHpEff1: Node = null;
addHpEff2: Node = null;
/**显示受击效果(怪物变色) */
showBeAtkEffect() {
if (MTools.beforeTimes(100, "_hurtColorTimeStamp" + this.uuid)) return;
Tween.stopAllByTarget(this.anim)
tween(this.anim)
.call(() => {
if (isValid(this.anim)) {
this.anim.color = new Color(255, 83, 83, 255)
}
})
.delay(0.35)
.call(() => {
if (isValid(this.anim)) {
this.anim.color = new Color(255, 255, 255, 255)
}
})
.start()
}
/**显示伤害值 */
async showSubHpNum(hp: number, isCrit: boolean, addRateAtk: number = 1) {
if (MTools.beforeTimes(150, "_hurtScoreTimeStamp" + this.uuid)) return
if (!acquireWeChatIosHurtFloatSpawn()) return
addRateAtk = 1
const scoreLayer = gg.game.CurentBattle?.ScoreLayer;
if (!scoreLayer?.isValid) return;
const bloodPos = this.node.getChildByName('bloodPos');
if (!bloodPos?.isValid) return;
let worldPos = bloodPos.worldPosition.clone()
let scoreName = addRateAtk <= 1 ? (isCrit ? 'critHurtScore' : 'atkMonsterScore') : 'rateDamageScore'
let scoreNode = await gg.res.getNodeAsync('prefab/数字/', scoreName, GBundle.tetriGame);
if (!scoreNode?.isValid) return;
scoreNode.setParent(scoreLayer);
gg.game.CurentBattle.setLayerMask(scoreNode, scoreLayer.layer);
scoreNode.active = true;
scoreNode.opacity = 255;
Tween.stopAllByTarget(scoreNode);
scoreNode.worldPosition = worldPos
scoreNode.getComponent(HurtScore).showHurtNum(hp, isCrit, addRateAtk > 1);
let index = gg.game.CurentBattle.ScoreLayer.children.findLastIndex((item) => item.name == scoreName);
if (index != -1) {
scoreNode.setSiblingIndex(index);
}
//}
}
/**显示秒杀/斩杀飘字1秒杀,2斩杀 */
async showKillEff(type: number) {
const scoreLayer = gg.game.CurentBattle?.ScoreLayer;
if (!scoreLayer?.isValid) return;
const bloodPos = this.node.getChildByName('bloodPos');
if (!bloodPos?.isValid) return;
let worldPos = bloodPos.worldPosition.clone()
let scoreNode = await gg.res.getNodeAsync('prefab/', 'killEff', GBundle.tetriGame)
if (!scoreNode?.isValid) return;
scoreNode.setParent(scoreLayer);
gg.game.CurentBattle.setLayerMask(scoreNode, scoreLayer.layer);
scoreNode.active = true;
scoreNode.opacity = 255;
Tween.stopAllByTarget(scoreNode);
scoreNode.worldPosition = worldPos;
scoreNode.find("秒杀").active = type == 1;
scoreNode.find("斩杀").active = type == 2;
let index = gg.game.CurentBattle.ScoreLayer.children.findLastIndex((item) => item.name == 'killEff');
if (index != -1) {
scoreNode.setSiblingIndex(index);
}
tween(scoreNode)
.set({ opacity: 255 })
.to(0.1, { scale: new Vec3(2, 2, 2) })
.to(0.25, { scale: new Vec3(1, 1, 1) })
.to(0.12, { opacity: 0 })
.call(() => {
gg.res.putNode(scoreNode)
})
.start();
}
/**记录摆放障碍物前的位置 */
recordSetObstacleBeforePos() {
this._setObstacleBeforePos = this.node.worldPosition.clone()
}
/**还原到摆放障碍物前的位置 */
resetSetObstacleBeforePos() {
if (!this._setObstacleBeforePos) return
this.node.worldPosition = this._setObstacleBeforePos
}
/**获取摆放障碍物前的位置 */
getSetObstacleBeforePos() {
return this._setObstacleBeforePos
}
/**怪物是否死亡 */
getIsDead() {
return this.IsDead
}
/**获取怪物总血量 */
getTotalHP() {
return this.maxHp
}
/**获取怪物类型 */
getMonsterType() {
return this.Data.type
}
/**
* 0100
* ( + ) / ( + )
*/
getRemainingHealthPercentForReport(): number {
if (this.IsDead || !this.Data || !(this.maxHp > 0)) return 0;
let numer = this.hp;
let denom = this.maxHp;
if (this._totalShieldHP > 1) {
numer = this.hp + this._curShieldHP;
denom = this.maxHp + this._totalShieldHP;
}
if (!(denom > 0)) return 0;
return Math.max(0, Math.min(100, Math.round((100 * numer) / denom)));
}
/**
* @param v
*/
addShieldHp(v: number) {
if (this.hp <= 0) return;
this._showShield = true;
this._totalShieldHP = Math.floor(this.maxHp * v);
if (this._totalShieldHP <= 0) this._totalShieldHP = 1;
this._curShieldHP = this._totalShieldHP;
this.updateShield();
if (!this._shielEffNode) {
void this.loadUpEffPrefab('prefab/', '怪物护盾').then((node) => {
if (!node) return;
this._shielEffNode = node;
this._applyShieldEffLayout();
});
} else {
this._shielEffNode.active = true;
this._applyShieldEffLayout();
}
}
private _applyShieldEffLayout() {
if (!this._shielEffNode?.isValid) return;
this._shielEffNode.active = true;
if (this.Data.type == MonsterType.SmallMonster) {
this._shielEffNode.x = 0;
this._shielEffNode.y = -30;
}
else if (this.Data.type == MonsterType.Boss) {
this._shielEffNode.x = -30;
this._shielEffNode.y = 0;
}
else {
this._shielEffNode.x = -15;
this._shielEffNode.y = -30;
}
}
/**更新盾 */
updateShield() {
let shields = this.boold.node.find("shields");
if (shields) {
// if (this._showShield) {
// bloodNode.active = true;
// }
if (this._showShield && !shields.active) {
shields.active = true;
}
let anim = shields.getComponentInChildren(sp.Skeleton);
let progress = shields.find("shieldsProgress").getComponent(Sprite);
if (shields.active) {
progress.fillRange = clamp01(this._curShieldHP / this._totalShieldHP);
}
if (this._showShield) {
anim.setAnimation(0, "待机", true);
} else {
anim.setAnimation(0, "裂开消失", false);
anim.setCompleteListener(() => {
anim.setCompleteListener(null);
if (shields.isValid) shields.active = false;
})
}
}
}
/**关闭护盾 */
closeShield() {
this._showShield = false;
if (this._shielEffNode) {
this._shielEffNode.active = false;
}
this.updateShield();
}
_curShieldHP: number = 0;
_totalShieldHP: number = 1;
_showShield: boolean = false;
_shielEffNode: Node = null;
// 怪物死亡
public async die() {
if (this.IsDead) return;
this.IsDead = true;
if (gg.game.CurentSelectBattleType == BattleType.BossMode
&& (this.Data.type == MonsterType.Boss || this.Data.type == MonsterType.BigBoss)) {
gg.game.CurentBattle.finishKillBoss = true;
}
if (gg.game.CurentBattle.CaptureZombieId != 0 && this.Data.id == gg.game.CurentBattle.CaptureZombieId && !gg.game.CurentBattle.isCaptureZombie) {
//发消息
gg.game.CurentBattle.isCaptureZombie = true
let worldPosi = this.node.worldPosition.clone()
GEvent.Ins.emit(GEvent.captureZombieSuccess, worldPosi)
}
if (!this.isMonsterPlayGude) {
gg.game.CurentBattle.monsterDie(this);
// 胜利结算时 checkWin 会把 IsGameOver 置 true;仍必须 remove,否则节点留在场上
if (!gg.game.CurentBattle.IsGameOver && this.Data.coin > 0) {
GEvent.Ins.emit(GEvent.showCoinFly, this.node.worldPosition.clone());
}
} else {
this.skin.scale_x = 1;
}
this.isMonsterPlayGude = false;
//console.log('怪物死亡,放入对象池==')
this.remove();
}
/**移除怪物 */
remove() {
this.IsDead = true;
this.clearAddHpEff();
//this.node.active = false;
//console.log('怪物死亡,放入对象池==');
this.removeBuff(SpecialSkillAddBuffType.burn);
this.removeBuff(SpecialSkillAddBuffType.reduceSpeed);
this.removeBuff(SpecialSkillAddBuffType.elec);
this.removeBuff(SpecialSkillAddBuffType.control);
this.removeBuff(SpecialSkillAddBuffType.accelerate);
this.node.getComponent(BuffBurnSchedule)?.removeSelf();
this.node.getComponent(BuffAccelerateSchedule)?.removeSelf();
this.node.getComponent(BuffControlSchedule)?.removeSelf();
this.node.getComponent(BuffElecSchedule)?.removeSelf();
this.node.getComponent(BuffIceSchedule)?.removeSelf();
this.node.getComponent(SingleWitch)?.destroy();
this.clear();
// 避免在物理 contact 回调里直接切换节点/刚体激活状态导致警告:
// "Can not active RigidBody in contact listener."
// 延迟到下一帧再回收进对象池。
const n = this.node;
this.scheduleOnce(() => {
if (isValid(n)) {
gg.res.putNode(n);
}
}, 0);
this._showShield = false;
}
clearNode() {
this.node.destroy()
}
setBloodMoon(show: boolean) {
this._redMoonSpeedMultiple = show ? gg.game.CurentBattle.RedMoonMultiplier : 0;
if (show) {
if (!this._accSpine) {
void this.loadUpEffPrefab('prefab/', '怪_加速').then((node) => {
if (!node) return;
this._accSpine = node;
this._accSpine.active = true;
});
} else {
this._accSpine.active = true;
}
}
if (!show && this._allBuff.indexOf(SpecialSkillAddBuffType.accelerate) == -1) {
if (this._accSpine) {
gg.res.putNode(this._accSpine);
this._accSpine = null;
}
}
}
showWenHaoEff() {
void gg.res.getNodeAsync('prefab/', '问号', GBundle.tetriGame).then((wenhao) => {
if (!wenhao?.isValid || !this.upEff?.isValid) return;
wenhao.setParent(this.upEff);
wenhao.active = true;
wenhao.worldPosition = this.bloodPos.worldPosition.clone().add(v3(0, 20, 0));
Tween.stopAllByTarget(wenhao);
wenhao.scale = v3(0, 0, 0);
this._isShowWenHaoEff = true;
tween(wenhao)
.to(0.3, { scale: new Vec3(1, 1, 1) }, { easing: easing.bounceOut })
.delay(1)
.call(() => {
if (wenhao && wenhao.isValid && wenhao.parent)
gg.res.putNode(wenhao);
this._isShowWenHaoEff = false;
}).start();
});
}
private _isShowWenHaoEff = false;
/**攻击动效 */
playStack() {
if (!this.Data || this.Data.type == MonsterType.TreasureBox) {
return
}
this.playSpine("攻击", null, 1, false);
}
/**走路动效 */
playWalk(speed = 1) {
if (!this.Data || this.Data.type == MonsterType.TreasureBox) {
return
}
if (gg.game.CurentSelectBattleType == BattleType.BossMode) {
let count = gg.game.CurentBattle.curFailReviveLeftNum;
speed = count < 3 ? 1 : 0.5;
if (this._allBuff.indexOf(SpecialSkillAddBuffType.reduceSpeed) >= 0) {
speed *= 0.5;
}
}
this.playSpine("走路", null, speed);
}
/**降落动效 */
playFall() {
if (!this.Data || this.Data.type == MonsterType.TreasureBox) {
return
}
this.playSpine("1降落", null, 1, false);
}
/**落地动效 */
playLand() {
if (!this.Data || this.Data.type == MonsterType.TreasureBox) {
return
}
this.playSpine("2落地", null, 1, false);
}
/**
* Spine
* @param name
* @param callback
* @param timeScale
* @param loop
*/
private playSpine(name: string, callback: Function = null, timeScale = 1, loop = true) {
// 节点或动画组件缺失时直接跳过,防止特殊怪(无 Spine)的报错
if (!this.node || !this.anim) {
console.warn("[Monster] playSpine skipped, anim missing. name =", name, "monsterId =", this.Data?.id);
return;
}
// 如果当前正在播放的动画是“死亡”,则不执行任何操作
if (this.anim.animation == "死亡") return;
// 设置动画播放完成后的回调函数
this.anim.setCompleteListener(() => {
if (callback) callback();
});
// 设置动画播放速度倍率,考虑了战斗速度的影响
this.anim.timeScale = timeScale * gg.game.CurentBattle.TimeScale;
// 播放指定名称的动画,并设置是否循环播放
this.anim.setAnimation(0, name, loop);
}
/**清除资源和引用以及状态等 */
clear() {
this._tagStatus.clear();
if (this.boold) {
gg.res.putNode(this.boold.node);
this.boold = null;
}
if (this.skin.children.length > 0) {
gg.res.putNode(this.skin.children[0]);
}
for (let i = this.downEff.children.length - 1; i >= 0; i--) {
let item = this.downEff.children[i];
gg.res.putNode(item);
}
for (let i = this.upEff.children.length - 1; i >= 0; i--) {
let item = this.upEff.children[i];
gg.res.putNode(item);
}
this.boxEff = null;
this._accSpine = null;
this._reduceSpeedSpine = null;
this._elecSpine = null;
this._controlSpine = null;
this._burnSpine = null;
this.boxGuideEff = null;
}
setSkinScale(scale) {
this.skin.scale_x = scale
}
}