Handle debuff techniques better

Using a single target debuff technique on a group of monsters should only debuff the monster wave(s) from the attacked monster
This commit is contained in:
Melledy 2023-12-15 08:11:16 -08:00
parent 3b2633f78f
commit 246b5e0340
14 changed files with 129 additions and 134 deletions

View File

@ -62,7 +62,7 @@ public class SummonUnitInfo {
for (var task : this.OnTriggerEnter) {
if (task.getType().contains("AddMazeBuff")) {
// TODO get duration from params if buff duration is dynamic
var actionAddBuff = new MazeSkillAddBuff(task.getID(), 15);
var actionAddBuff = new MazeSkillAddBuff(task.getID(), 5);
actionAddBuff.setSendBuffPacket(true);
actions.add(actionAddBuff);

View File

@ -130,10 +130,6 @@ public class Battle {
return this.turnSnapshotList;
}
public int getMonsterWaveCount() {
return this.getWaves().size();
}
public void setCustomLevel(int level) {
for (var wave : this.getWaves()) {
wave.setCustomLevel(level);

View File

@ -2,7 +2,6 @@ package emu.lunarcore.game.battle;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import emu.lunarcore.GameConstants;
import emu.lunarcore.data.GameData;
@ -24,6 +23,7 @@ import emu.lunarcore.server.packet.send.PacketReEnterLastElementStageScRsp;
import emu.lunarcore.server.packet.send.PacketSceneCastSkillScRsp;
import emu.lunarcore.server.packet.send.PacketStartCocoonStageScRsp;
import emu.lunarcore.server.packet.send.PacketSyncLineupNotify;
import it.unimi.dsi.fastutil.ints.IntSet;
public class BattleService extends BaseGameService {
@ -31,15 +31,18 @@ public class BattleService extends BaseGameService {
super(server);
}
public void startBattle(Player player, int casterId, int attackedGroupId, MazeSkill castedSkill, Set<Integer> targets) {
public void startBattle(Player player, int casterId, int attackedGroupId, MazeSkill castedSkill, IntSet hitTargets, IntSet assistMonsters) {
// Setup variables
List<GameEntity> targetEntities = new ArrayList<>();
boolean isPlayerCaster = player.getScene().getAvatarEntityIds().contains(casterId);
GameAvatar castingAvatar = null;
// Check if attacker is the player or not
if (isPlayerCaster) {
// Player is the attacker
for (int entityId : targets) {
if (player.getScene().getAvatarEntityIds().contains(casterId)) {
// Get casting avatar
castingAvatar = player.getCurrentLeaderAvatar();
// Player is the attacker, add hit targets to the battle
for (int entityId : hitTargets) {
GameEntity entity = player.getScene().getEntities().get(entityId);
if (entity != null) {
@ -53,15 +56,6 @@ public class BattleService extends BaseGameService {
if (entity != null) {
targetEntities.add(entity);
}
// Add any assisting monsters from target list
for (int entityId : targets) {
entity = player.getScene().getEntities().get(entityId);
if (entity != null) {
targetEntities.add(entity);
}
}
}
// Skip if no attacked entities detected
@ -83,6 +77,8 @@ public class BattleService extends BaseGameService {
} else if (entity instanceof EntityProp prop) {
it.remove();
player.getScene().destroyProp(prop);
} else {
it.remove();
}
}
@ -93,10 +89,24 @@ public class BattleService extends BaseGameService {
// Skip battle if our technique does not trigger a battle
player.sendPacket(new PacketSceneCastSkillScRsp(attackedGroupId));
return;
}
}
// Add any assisting monsters from monster assist list
for (int entityId : assistMonsters) {
GameEntity entity = player.getScene().getEntities().get(entityId);
if (entity != null && entity instanceof EntityMonster monster) {
monsters.add(monster);
}
}
// Start battle
if (monsters.size() > 0) {
// Maze skill attack event
if (castedSkill != null && castingAvatar != null) {
castedSkill.onAttack(castingAvatar, targetEntities);
}
// Create battle and add npc monsters to it
Battle battle = new Battle(player, player.getLineupManager().getCurrentLineup(), monsters);
@ -107,23 +117,15 @@ public class BattleService extends BaseGameService {
}
// Add buffs to battle
if (isPlayerCaster) {
GameAvatar avatar = player.getCurrentLeaderAvatar();
if (avatar != null) {
// Maze skill attack event
if (castedSkill != null) {
castedSkill.onAttack(avatar, battle);
}
// Add elemental weakness buff to enemies
MazeBuff buff = battle.addBuff(avatar.getExcel().getDamageType().getEnterBattleBuff(), battle.getLineup().getLeader());
if (buff != null) {
buff.addTargetIndex(battle.getLineup().getLeader());
buff.addDynamicValue("SkillIndex", castedSkill.getIndex());
}
if (castingAvatar != null) {
// Add elemental weakness debuff to enemies
MazeBuff buff = battle.addBuff(castingAvatar.getExcel().getDamageType().getEnterBattleBuff(), battle.getLineup().getLeader());
if (buff != null) {
buff.addTargetIndex(battle.getLineup().getLeader());
buff.addDynamicValue("SkillIndex", castedSkill.getIndex());
}
} else {
// Ambush buff (for monsters)
// Ambush debuff (from monsters)
battle.addBuff(GameConstants.BATTLE_AMBUSH_BUFF_ID, -1, 1);
}

View File

@ -5,7 +5,6 @@ import java.util.List;
import emu.lunarcore.data.excel.AvatarExcel;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.Battle;
import emu.lunarcore.game.scene.entity.GameEntity;
import emu.lunarcore.proto.MotionInfoOuterClass.MotionInfo;
import lombok.Getter;
@ -28,7 +27,9 @@ public class MazeSkill {
this.attackActions = new ArrayList<>();
}
// Triggered when player casts a skill
/**
* Triggered when player casts a skill
*/
public void onCast(GameAvatar caster, MotionInfo castPosition) {
if (this.getCastActions().size() == 0) return;
@ -37,21 +38,25 @@ public class MazeSkill {
}
}
// Triggered when player attacks an enemy
public void onAttack(GameAvatar caster, Battle battle) {
/**
* Triggered when player casts a skill and it hits entities
*/
public void onCastHit(GameAvatar caster, List<? extends GameEntity> entities) {
if (this.getAttackActions().size() == 0) return;
for (var action : this.getAttackActions()) {
action.onAttack(caster, battle);
action.onCastHit(caster, entities);
}
}
// Triggered when player attacks an enemy
public void onAttack(GameAvatar caster, List<? extends GameEntity> entities) {
/**
* Triggered when player attacks an enemy
*/
public void onAttack(GameAvatar caster, List<? extends GameEntity> targets) {
if (this.getAttackActions().size() == 0) return;
for (var action : this.getAttackActions()) {
action.onAttack(caster, entities);
action.onAttack(caster, targets);
}
}
}

View File

@ -3,16 +3,21 @@ package emu.lunarcore.game.battle.skills;
import java.util.List;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.Battle;
import emu.lunarcore.game.scene.entity.GameEntity;
import emu.lunarcore.proto.MotionInfoOuterClass.MotionInfo;
public abstract class MazeSkillAction {
public abstract void onCast(GameAvatar caster, MotionInfo castPosition);
public void onCast(GameAvatar caster, MotionInfo castPosition) {
}
public abstract void onAttack(GameAvatar caster, Battle battle);
public void onCastHit(GameAvatar caster, List<? extends GameEntity> entities) {
}
public abstract void onAttack(GameAvatar caster, List<? extends GameEntity> entities);
public void onAttack(GameAvatar caster, List<? extends GameEntity> targets) {
}
}

View File

@ -3,7 +3,7 @@ package emu.lunarcore.game.battle.skills;
import java.util.List;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.Battle;
import emu.lunarcore.game.scene.SceneBuff;
import emu.lunarcore.game.scene.entity.EntityMonster;
import emu.lunarcore.game.scene.entity.GameEntity;
import emu.lunarcore.proto.MotionInfoOuterClass.MotionInfo;
@ -30,17 +30,18 @@ public class MazeSkillAddBuff extends MazeSkillAction {
}
@Override
public void onAttack(GameAvatar caster, Battle battle) {
// Get amount of monster waves in battle
int waveCount = battle.getMonsterWaveCount();
// Add buff for each wave id
for (int i = 0; i < waveCount; i++) {
battle.addBuff(buffId, battle.getLineup().getLeader(), 1 << i);
public void onAttack(GameAvatar caster, List<? extends GameEntity> targets) {
// Add debuff to monsters
for (GameEntity target : targets) {
if (target instanceof EntityMonster monster) {
// Set as temp buff
monster.setTempBuff(new SceneBuff(caster.getAvatarId(), buffId));
}
}
}
@Override
public void onAttack(GameAvatar caster, List<? extends GameEntity> entities) {
public void onCastHit(GameAvatar caster, List<? extends GameEntity> entities) {
for (GameEntity entity : entities) {
if (entity instanceof EntityMonster monster) {
// Add buff to monster

View File

@ -3,27 +3,15 @@ package emu.lunarcore.game.battle.skills;
import java.util.List;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.Battle;
import emu.lunarcore.game.scene.entity.EntityProp;
import emu.lunarcore.game.scene.entity.GameEntity;
import emu.lunarcore.proto.MotionInfoOuterClass.MotionInfo;
import lombok.Getter;
@Getter
public class MazeSkillHitProp extends MazeSkillAction {
@Override
public void onCast(GameAvatar caster, MotionInfo castPosition) {
// Skip
}
@Override
public void onAttack(GameAvatar caster, Battle battle) {
// Skip
}
@Override
public void onAttack(GameAvatar caster, List<? extends GameEntity> entities) {
public void onCastHit(GameAvatar caster, List<? extends GameEntity> entities) {
for (GameEntity entity : entities) {
if (entity instanceof EntityProp prop) {
caster.getScene().destroyProp(prop);

View File

@ -1,10 +1,6 @@
package emu.lunarcore.game.battle.skills;
import java.util.List;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.Battle;
import emu.lunarcore.game.scene.entity.GameEntity;
import emu.lunarcore.proto.MotionInfoOuterClass.MotionInfo;
public class MazeSkillModifyHP extends MazeSkillAction {
@ -19,14 +15,4 @@ public class MazeSkillModifyHP extends MazeSkillAction {
caster.getOwner().getCurrentLineup().heal(this.amount, false);
}
@Override
public void onAttack(GameAvatar caster, Battle battle) {
}
@Override
public void onAttack(GameAvatar caster, List<? extends GameEntity> entities) {
}
}

View File

@ -3,7 +3,6 @@ package emu.lunarcore.game.battle.skills;
import java.util.List;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.Battle;
import emu.lunarcore.game.scene.entity.GameEntity;
import emu.lunarcore.proto.MotionInfoOuterClass.MotionInfo;
@ -23,12 +22,7 @@ public class MazeSkillModifySP extends MazeSkillAction {
}
@Override
public void onAttack(GameAvatar caster, Battle battle) {
}
@Override
public void onAttack(GameAvatar caster, List<? extends GameEntity> entities) {
public void onCastHit(GameAvatar caster, List<? extends GameEntity> entities) {
}

View File

@ -1,11 +1,7 @@
package emu.lunarcore.game.battle.skills;
import java.util.List;
import emu.lunarcore.data.excel.SummonUnitExcel;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.Battle;
import emu.lunarcore.game.scene.entity.GameEntity;
import emu.lunarcore.proto.MotionInfoOuterClass.MotionInfo;
import emu.lunarcore.util.Position;
import lombok.Getter;
@ -25,14 +21,4 @@ public class MazeSkillSummonUnit extends MazeSkillAction {
caster.getScene().summonUnit(caster, excel, new Position(castPosition.getPos()), new Position(castPosition.getRot()), duration);
}
@Override
public void onAttack(GameAvatar caster, Battle battle) {
// Skip
}
@Override
public void onAttack(GameAvatar caster, List<? extends GameEntity> entities) {
// Skip
}
}

View File

@ -261,7 +261,7 @@ public class Scene implements Tickable {
// Handle task actions
for (var action : trigger.getActions()) {
action.onAttack(summonUnit.getCaster(), targets);
action.onCastHit(summonUnit.getCaster(), targets);
}
// Send packet

View File

@ -8,17 +8,28 @@ public class SceneBuff {
private int casterAvatarId; // Owner avatar id
private int buffId;
private int buffLevel;
private int duration;
private float duration;
private long createTime;
private long expiry;
public SceneBuff(int casterAvatarId, int buffId, int seconds) {
this.casterAvatarId = casterAvatarId;
public SceneBuff(int buffId) {
this.buffId = buffId;
this.buffLevel = 1;
this.createTime = System.currentTimeMillis();
this.duration = -1;
}
public SceneBuff(int casterAvatarId, int buffId) {
this(buffId);
this.casterAvatarId = casterAvatarId;
this.expiry = Long.MAX_VALUE;
}
public SceneBuff(int casterAvatarId, int buffId, int seconds) {
this(buffId);
this.casterAvatarId = casterAvatarId;
this.duration = seconds * 1000;
this.expiry = this.createTime + duration;
this.expiry = this.createTime + (long) duration;
}
public boolean isExpired(long timestamp) {
@ -33,7 +44,7 @@ public class SceneBuff {
.setLevel(this.getBuffLevel())
.setBaseAvatarId(this.getCasterAvatarId())
.setAddTimeMs(this.getCreateTime())
.setLifeTime(this.getDuration() / 10)
.setLifeTime(this.getDuration())
.setCount(1);
return proto;

View File

@ -32,6 +32,8 @@ public class EntityMonster implements GameEntity, Tickable {
private final Position rot;
private Int2ObjectMap<SceneBuff> buffs;
@Setter private SceneBuff tempBuff;
private int farmElementId;
@Setter private int customStageId;
@Setter private int customLevel;
@ -72,22 +74,36 @@ public class EntityMonster implements GameEntity, Tickable {
}
public synchronized void applyBuffs(Battle battle, int waveIndex) {
if (this.buffs == null) return;
for (var entry : this.buffs.int2ObjectEntrySet()) {
// Check expiry for buff
if (entry.getValue().isExpired(battle.getTimestamp())) {
continue;
}
// Get owner index
int ownerIndex = battle.getLineup().indexOf(entry.getValue().getCasterAvatarId());
// Add buff to battle if owner exists
if (ownerIndex != -1) {
battle.addBuff(entry.getIntKey(), ownerIndex, 1 << waveIndex);
if (this.buffs != null) {
for (var entry : this.buffs.int2ObjectEntrySet()) {
// Check expiry for buff
if (entry.getValue().isExpired(battle.getTimestamp())) {
continue;
}
// Add buff to battle
this.applyBuff(battle, entry.getValue(), waveIndex);
}
}
if (this.getTempBuff() != null) {
this.applyBuff(battle, this.getTempBuff(), waveIndex);
this.tempBuff = null;
}
}
private boolean applyBuff(Battle battle, SceneBuff buff, int waveIndex) {
// Get index of owner in lineup
int ownerIndex = battle.getLineup().indexOf(buff.getCasterAvatarId());
// Add buff to battle if owner exists
if (ownerIndex != -1) {
battle.addBuff(buff.getBuffId(), ownerIndex, 1 << waveIndex);
return true;
}
// Failure
return false;
}
@Override

View File

@ -1,8 +1,5 @@
package emu.lunarcore.server.packet.recv;
import java.util.LinkedHashSet;
import java.util.Set;
import emu.lunarcore.game.avatar.GameAvatar;
import emu.lunarcore.game.battle.skills.MazeSkill;
import emu.lunarcore.game.player.Player;
@ -13,10 +10,12 @@ import emu.lunarcore.server.packet.Opcodes;
import emu.lunarcore.server.packet.PacketHandler;
import emu.lunarcore.server.packet.send.PacketSceneCastSkillMpUpdateScNotify;
import emu.lunarcore.server.packet.send.PacketSceneCastSkillScRsp;
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
@Opcodes(CmdId.SceneCastSkillCsReq)
public class HandlerSceneCastSkillCsReq extends PacketHandler {
@Override
public void handle(GameSession session, byte[] data) throws Exception {
var req = SceneCastSkillCsReq.parseFrom(data);
@ -52,13 +51,19 @@ public class HandlerSceneCastSkillCsReq extends PacketHandler {
}
if (req.hasHitTargetIdList()) {
// Create target list
Set<Integer> targets = new LinkedHashSet<>();
req.getHitTargetIdList().forEach(targets::add);
req.getAssistMonsterIdList().forEach(targets::add);
// Parse targets efficiently (skips integer boxing)
IntSet hitTargets = new IntLinkedOpenHashSet();
for (int i = 0; i < req.getHitTargetIdList().length(); i++) {
hitTargets.add(req.getHitTargetIdList().get(i));
}
IntSet assistMonsters = new IntLinkedOpenHashSet();
for (int i = 0; i < req.getAssistMonsterIdList().length(); i++) {
assistMonsters.add(req.getAssistMonsterIdList().get(i));
}
// Start battle
session.getServer().getBattleService().startBattle(player, req.getCasterId(), req.getAttackedGroupId(), skill, targets);
session.getServer().getBattleService().startBattle(player, req.getCasterId(), req.getAttackedGroupId(), skill, hitTargets, assistMonsters);
} else {
// We had no targets for some reason
session.send(new PacketSceneCastSkillScRsp(req.getAttackedGroupId()));