Raid boss hunts for headless bots (real party, real boss), do=raid/raidinfo, docs
This commit is contained in:
+3
@@ -126,6 +126,9 @@ FakePlayerSiegeAttackerClans = 2
|
||||
# Also besiege castles owned by real players' clans.
|
||||
FakePlayerSiegeAttackPlayerCastles = False
|
||||
|
||||
# Raid boss hunts: every 30-90 minutes a real party of 5-9 bots goes for a live raid boss of its level.
|
||||
FakePlayerRaids = True
|
||||
|
||||
# Print bot activity totals (kills, exp, loot, trades, chat...) to the log every N minutes. 0 = off.
|
||||
FakePlayerStatsLogMinutes = 10
|
||||
|
||||
|
||||
+2
@@ -130,6 +130,7 @@ import org.l2jmobius.gameserver.managers.FakePlayerDashboard;
|
||||
import org.l2jmobius.gameserver.managers.FakePlayerHeadlessManager;
|
||||
import org.l2jmobius.gameserver.managers.FakePlayerIntentParser;
|
||||
import org.l2jmobius.gameserver.managers.FakePlayerRumorManager;
|
||||
import org.l2jmobius.gameserver.managers.FakePlayerRaidManager;
|
||||
import org.l2jmobius.gameserver.managers.FakePlayerSiegeManager;
|
||||
import org.l2jmobius.gameserver.managers.FakePlayerTelemetry;
|
||||
import org.l2jmobius.gameserver.managers.FakePlayerProgressionManager;
|
||||
@@ -362,6 +363,7 @@ public class GameServer
|
||||
DayNightSpawnManager.getInstance().trim().notifyChangeMode();
|
||||
DimensionalRiftManager.getInstance();
|
||||
RaidBossSpawnManager.getInstance();
|
||||
FakePlayerRaidManager.getInstance();
|
||||
|
||||
printSection("Siege");
|
||||
SiegeManager.getInstance().getSieges();
|
||||
|
||||
+2
@@ -69,6 +69,7 @@ public class FakePlayersConfig
|
||||
public static boolean FAKE_PLAYER_SIEGE_ATTACK_PLAYER_CASTLES;
|
||||
public static int FAKE_PLAYER_STATS_LOG_MINUTES;
|
||||
public static boolean FAKE_PLAYER_CHAT_LOG;
|
||||
public static boolean FAKE_PLAYER_RAIDS;
|
||||
|
||||
public static void load()
|
||||
{
|
||||
@@ -110,5 +111,6 @@ public class FakePlayersConfig
|
||||
FAKE_PLAYER_SIEGE_ATTACK_PLAYER_CASTLES = config.getBoolean("FakePlayerSiegeAttackPlayerCastles", false);
|
||||
FAKE_PLAYER_STATS_LOG_MINUTES = config.getInt("FakePlayerStatsLogMinutes", 10);
|
||||
FAKE_PLAYER_CHAT_LOG = config.getBoolean("FakePlayerChatLog", false);
|
||||
FAKE_PLAYER_RAIDS = config.getBoolean("FakePlayerRaids", true);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -84,6 +84,9 @@ public class FakePlayerBotState
|
||||
public int siegeCastleId = 0;
|
||||
public long siegeNextThink = 0;
|
||||
|
||||
// --- raid ---
|
||||
public int raidBossId = 0;
|
||||
|
||||
// --- telemetry ---
|
||||
public final AtomicLong kills = new AtomicLong();
|
||||
public final AtomicLong expGained = new AtomicLong();
|
||||
@@ -99,6 +102,7 @@ public class FakePlayerBotState
|
||||
public final AtomicLong skillsCast = new AtomicLong();
|
||||
public final AtomicLong rests = new AtomicLong();
|
||||
public final AtomicLong siegesJoined = new AtomicLong();
|
||||
public final AtomicLong raidsWon = new AtomicLong();
|
||||
public final AtomicLong migrations = new AtomicLong();
|
||||
public final AtomicLong professions = new AtomicLong();
|
||||
public final AtomicLong partiesJoined = new AtomicLong();
|
||||
|
||||
+14
@@ -522,6 +522,12 @@ public class FakePlayerHeadlessManager
|
||||
return;
|
||||
}
|
||||
|
||||
// Raid boss hunt: the party stays on the boss.
|
||||
if ((state.raidBossId != 0) && FakePlayerRaidManager.getInstance().handle(bot, state, now))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Perception and talk first: a farming bot still notices people and chats.
|
||||
if (FakePlayerSocial.getInstance().tick(bot, state, now))
|
||||
{
|
||||
@@ -1770,6 +1776,14 @@ public class FakePlayerHeadlessManager
|
||||
{
|
||||
return FakePlayerSiegeManager.getInstance().info();
|
||||
}
|
||||
case "raid":
|
||||
{
|
||||
return FakePlayerRaidManager.getInstance().start();
|
||||
}
|
||||
case "raidinfo":
|
||||
{
|
||||
return FakePlayerRaidManager.getInstance().info();
|
||||
}
|
||||
case "online":
|
||||
{
|
||||
try
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (c) 2013 L2jMobius
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package org.l2jmobius.gameserver.managers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.l2jmobius.commons.threads.ThreadPool;
|
||||
import org.l2jmobius.commons.util.Rnd;
|
||||
import org.l2jmobius.gameserver.config.custom.FakePlayersConfig;
|
||||
import org.l2jmobius.gameserver.entity.World;
|
||||
import org.l2jmobius.gameserver.entity.WorldObject;
|
||||
import org.l2jmobius.gameserver.entity.actor.Npc;
|
||||
import org.l2jmobius.gameserver.entity.actor.Player;
|
||||
import org.l2jmobius.gameserver.entity.actor.instance.RaidBoss;
|
||||
import org.l2jmobius.gameserver.entity.groups.Party;
|
||||
|
||||
/**
|
||||
* Raid boss hunts by headless bots: every 30-90 minutes a real party of 5-9
|
||||
* bots close to a live raid boss' level teleports to it, calls out the raid
|
||||
* and fights until the boss dies or 25 minutes pass, then everybody goes back
|
||||
* to farming. The party is a real L2 party (shared exp, loot rules), the boss
|
||||
* is a real raid (drop, raid points), the fight is the ordinary combat brain
|
||||
* with the boss forced as target. Grand bosses are never touched.
|
||||
* @author Mobius SPP
|
||||
*/
|
||||
public class FakePlayerRaidManager
|
||||
{
|
||||
private static final Logger LOGGER = Logger.getLogger(FakePlayerRaidManager.class.getName());
|
||||
|
||||
private static final long TICK = 60000;
|
||||
private static final long RAID_LENGTH = 25 * 60000L;
|
||||
private static final int PARTY_MIN = 5;
|
||||
private static final int PARTY_MAX = 9;
|
||||
|
||||
private long _nextRaidAt = System.currentTimeMillis() + Rnd.get(1800000, 3600000);
|
||||
private long _raidEndsAt = 0;
|
||||
private int _bossObjectId = 0;
|
||||
private String _bossName = "";
|
||||
private final List<Player> _party = new ArrayList<>();
|
||||
|
||||
protected FakePlayerRaidManager()
|
||||
{
|
||||
if (!FakePlayersConfig.FAKE_PLAYERS_ENABLED || !FakePlayersConfig.FAKE_PLAYER_RAIDS || (FakePlayersConfig.FAKE_PLAYER_HEADLESS_COUNT <= 0))
|
||||
{
|
||||
LOGGER.info(getClass().getSimpleName() + ": Disabled.");
|
||||
return;
|
||||
}
|
||||
ThreadPool.scheduleAtFixedRate(this::tick, TICK, TICK);
|
||||
LOGGER.info(getClass().getSimpleName() + ": Raid parties every 30-90 minutes.");
|
||||
}
|
||||
|
||||
private void tick()
|
||||
{
|
||||
try
|
||||
{
|
||||
final long now = System.currentTimeMillis();
|
||||
if (_bossObjectId != 0)
|
||||
{
|
||||
final WorldObject boss = World.findObject(_bossObjectId);
|
||||
final boolean gone = !(boss instanceof Npc bossNpc) || bossNpc.isDead();
|
||||
if (gone || (now >= _raidEndsAt))
|
||||
{
|
||||
finish(gone);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (now >= _nextRaidAt)
|
||||
{
|
||||
start();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.warning(getClass().getSimpleName() + ": tick failed: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks a live raid boss and a party of bots of a matching level, sends them in.
|
||||
* @return result text.
|
||||
*/
|
||||
public String start()
|
||||
{
|
||||
final long now = System.currentTimeMillis();
|
||||
final List<RaidBoss> bosses = new ArrayList<>();
|
||||
for (RaidBoss boss : RaidBossSpawnManager.getInstance().getBosses().values())
|
||||
{
|
||||
if ((boss != null) && !boss.isDead() && boss.isSpawned() && (boss.getLevel() >= 20) && (boss.getLevel() <= 80))
|
||||
{
|
||||
bosses.add(boss);
|
||||
}
|
||||
}
|
||||
if (bosses.isEmpty())
|
||||
{
|
||||
_nextRaidAt = now + 3600000;
|
||||
return "no live raid boss";
|
||||
}
|
||||
// Try a few bosses until one has enough bots of its level online and free.
|
||||
for (int attempt = 0; attempt < 6; attempt++)
|
||||
{
|
||||
final RaidBoss boss = bosses.get(Rnd.get(bosses.size()));
|
||||
final List<Player> party = new ArrayList<>();
|
||||
for (Player bot : FakePlayerHeadlessManager.getInstance().bots())
|
||||
{
|
||||
final FakePlayerBotState state = FakePlayerHeadlessManager.getInstance().stateOf(bot);
|
||||
if ((state == null) || bot.isDead() || (state.siegeSide != 0) || (state.tradeUntil > 0) || ((bot.getParty() != null) && (FakePlayerSocial.humanPartyAnchor(bot) != null)) || (Math.abs(bot.getLevel() - boss.getLevel()) > 6) || (bot.getLevel() < (boss.getLevel() - 2)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
party.add(bot);
|
||||
if (party.size() >= PARTY_MAX)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (party.size() < PARTY_MIN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
_bossObjectId = boss.getObjectId();
|
||||
_bossName = boss.getName();
|
||||
_raidEndsAt = now + RAID_LENGTH;
|
||||
_party.clear();
|
||||
_party.addAll(party);
|
||||
for (Player bot : party)
|
||||
{
|
||||
if (bot.getParty() != null)
|
||||
{
|
||||
bot.leaveParty(); // Farming squads dissolve into the raid party.
|
||||
}
|
||||
}
|
||||
final Player leader = party.get(0);
|
||||
final Party group = new Party(leader, leader.getPartyDistributionType());
|
||||
leader.setParty(group);
|
||||
int index = 0;
|
||||
for (Player bot : party)
|
||||
{
|
||||
final FakePlayerBotState state = FakePlayerHeadlessManager.getInstance().stateOf(bot);
|
||||
if (bot != leader)
|
||||
{
|
||||
bot.joinParty(group);
|
||||
}
|
||||
state.partiesJoined.incrementAndGet();
|
||||
state.raidBossId = boss.getObjectId();
|
||||
state.action("raid " + boss.getName());
|
||||
FakePlayerBotContext.setRaid(state.seedId, boss.getName(), RAID_LENGTH);
|
||||
FakePlayerHeadlessManager.getInstance().closeStore(bot);
|
||||
final int delay = 1000 + (index++ * 600);
|
||||
ThreadPool.schedule(() ->
|
||||
{
|
||||
if (!bot.isDead())
|
||||
{
|
||||
FakePlayerHeadlessManager.getInstance().teleport(bot, boss.getX() + Rnd.get(-250, 250), boss.getY() + Rnd.get(-250, 250), boss.getZ());
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
final String cry = FakePlayerUnderstanding.eventLine(FakePlayerBotRef.ofHeadless(leader), "raid_start", null, Map.of("boss", boss.getName()));
|
||||
if (cry != null)
|
||||
{
|
||||
ThreadPool.schedule(() -> FakePlayerHeadlessManager.getInstance().shout(leader, cry, "raid_start"), 6000);
|
||||
}
|
||||
LOGGER.info(getClass().getSimpleName() + ": Raid started - " + party.size() + " bots vs " + boss.getName() + " (lvl " + boss.getLevel() + ").");
|
||||
return "raid: " + party.size() + " bots vs " + boss.getName() + " lvl" + boss.getLevel();
|
||||
}
|
||||
_nextRaidAt = now + 1800000;
|
||||
return "no party of " + PARTY_MIN + "+ free bots for any live boss";
|
||||
}
|
||||
|
||||
private void finish(boolean bossDead)
|
||||
{
|
||||
for (Player bot : _party)
|
||||
{
|
||||
final FakePlayerBotState state = FakePlayerHeadlessManager.getInstance().stateOf(bot);
|
||||
if (state == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
state.raidBossId = 0;
|
||||
if (bossDead)
|
||||
{
|
||||
state.raidsWon.incrementAndGet();
|
||||
}
|
||||
FakePlayerBotContext.setPlan(state.seedId, "farm", 0);
|
||||
FakePlayerBotContext.setEvent(state.seedId, "raid_finished");
|
||||
state.action(bossDead ? ("raid won " + _bossName) : "raid over");
|
||||
if (bot.getParty() != null)
|
||||
{
|
||||
bot.leaveParty();
|
||||
}
|
||||
ThreadPool.schedule(() -> FakePlayerHeadlessManager.getInstance().returnToSpot(bot), 15000 + Rnd.get(45000));
|
||||
}
|
||||
LOGGER.info(getClass().getSimpleName() + ": Raid finished (" + (bossDead ? "boss dead" : "timeout") + ") - " + _bossName + ".");
|
||||
_party.clear();
|
||||
_bossObjectId = 0;
|
||||
_bossName = "";
|
||||
_nextRaidAt = System.currentTimeMillis() + Rnd.get(1800000, 5400000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bot the bot.
|
||||
* @param state its state.
|
||||
* @return true when the bot is on a raid and the raid logic took the tick.
|
||||
*/
|
||||
public boolean handle(Player bot, FakePlayerBotState state, long now)
|
||||
{
|
||||
if (state.raidBossId == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final WorldObject boss = World.findObject(state.raidBossId);
|
||||
if (!(boss instanceof Npc bossNpc) || bossNpc.isDead() || (_bossObjectId != state.raidBossId))
|
||||
{
|
||||
state.raidBossId = 0;
|
||||
return false;
|
||||
}
|
||||
if (bot.calculateDistance2D(bossNpc) > 3000)
|
||||
{
|
||||
// Back from the village after dying: rejoin.
|
||||
FakePlayerHeadlessManager.getInstance().teleport(bot, bossNpc.getX() + Rnd.get(-250, 250), bossNpc.getY() + Rnd.get(-250, 250), bossNpc.getZ());
|
||||
return true;
|
||||
}
|
||||
// Survival and healing first, then the boss (the brain handles a targeted creature in combat).
|
||||
if ((bot.getTarget() != bossNpc) || (!bot.isAttackingNow() && !bot.isCastingNow()))
|
||||
{
|
||||
bot.setTarget(bossNpc);
|
||||
bot.getAI().setIntentionAttack(bossNpc);
|
||||
}
|
||||
return FakePlayerBrain.act(bot, state, false) || true;
|
||||
}
|
||||
|
||||
public String info()
|
||||
{
|
||||
if (_bossObjectId != 0)
|
||||
{
|
||||
return "raid in progress: " + _party.size() + " bots vs " + _bossName + ", ends in " + Math.max(0, (_raidEndsAt - System.currentTimeMillis()) / 60000) + " min";
|
||||
}
|
||||
return "next raid in " + Math.max(0, (_nextRaidAt - System.currentTimeMillis()) / 60000) + " min";
|
||||
}
|
||||
|
||||
public static FakePlayerRaidManager getInstance()
|
||||
{
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final FakePlayerRaidManager INSTANCE = new FakePlayerRaidManager();
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -72,6 +72,7 @@ public class FakePlayerTelemetry
|
||||
long casts = 0;
|
||||
long rests = 0;
|
||||
long sieges = 0;
|
||||
long raids = 0;
|
||||
long migrations = 0;
|
||||
long professions = 0;
|
||||
long parties = 0;
|
||||
@@ -103,6 +104,7 @@ public class FakePlayerTelemetry
|
||||
casts += state.skillsCast.get();
|
||||
rests += state.rests.get();
|
||||
sieges += state.siegesJoined.get();
|
||||
raids += state.raidsWon.get();
|
||||
migrations += state.migrations.get();
|
||||
professions += state.professions.get();
|
||||
parties += state.partiesJoined.get();
|
||||
@@ -158,6 +160,7 @@ public class FakePlayerTelemetry
|
||||
totals.put("migrations", migrations);
|
||||
totals.put("professions", professions);
|
||||
totals.put("sieges", sieges);
|
||||
totals.put("raidsWon", raids);
|
||||
totals.put("uptimeMin", (System.currentTimeMillis() - _startedAt) / 60000);
|
||||
return totals;
|
||||
}
|
||||
@@ -235,7 +238,7 @@ public class FakePlayerTelemetry
|
||||
sb.append(" kills=").append(state.kills.get()).append(" exp=").append(state.expGained.get()).append(" pvp=").append(state.pvpKills.get()).append(" deaths=").append(state.deaths.get()).append('\n');
|
||||
sb.append(" loot=").append(state.lootPicked.get()).append(" sold=").append(state.itemsSold.get()).append(" earned=").append(state.adenaEarned.get()).append(" spent=").append(state.adenaSpent.get()).append(" adena=").append(bot.getAdena()).append(" stores=").append(state.storesOpened.get()).append('\n');
|
||||
sb.append(" casts=").append(state.skillsCast.get()).append(" rests=").append(state.rests.get()).append(" lines=").append(state.linesSaid.get()).append(" replies=").append(state.repliesGiven.get()).append(" parties=").append(state.partiesJoined.get()).append('\n');
|
||||
sb.append(" migrations=").append(state.migrations.get()).append(" professions=").append(state.professions.get()).append(" sieges=").append(state.siegesJoined.get()).append(" siegeSide=").append(state.siegeSide).append('\n');
|
||||
sb.append(" migrations=").append(state.migrations.get()).append(" professions=").append(state.professions.get()).append(" sieges=").append(state.siegesJoined.get()).append(" siegeSide=").append(state.siegeSide).append(" raidsWon=").append(state.raidsWon.get()).append(" raidBoss=").append(state.raidBossId).append('\n');
|
||||
sb.append(" shots=").append(bot.getAutoSoulShot()).append(" inventory=").append(bot.getInventory().getSize()).append(" hp=").append((int) bot.getCurrentHp()).append('/').append(bot.getMaxHp());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user