Raid boss hunts for headless bots (real party, real boss), do=raid/raidinfo, docs

This commit is contained in:
2026-09-03 21:01:57 +02:00
parent 5fc54be523
commit 8af080ee3d
9 changed files with 303 additions and 2 deletions
+3 -1
View File
@@ -12,13 +12,15 @@
**Осады** (`FakePlayerSieges`). По расписанию `SiegeSchedule.xml` бот-кланы с ≥3 членами 40+ онлайн регистрируются атакующими на замки бот-кланов (по 2 клана на замок, `FakePlayerSiegeAttackerClans`; на замки игроков — только при `FakePlayerSiegeAttackPlayerCastles = True`). Лидеры кланов подгружаются в мир заранее. В начале осады атакующие собираются за внешними воротами, защитники — у артефакта. Атакующие бьют врагов, ломают ворота, идут к артефакту, лидер клана кастует Печать Правителя (3 минуты, остальные прикрывают) — замок переходит настоящей механикой движка. Крики в крик-чат: начало, ворота, печать, победа/поражение. Форс: `do=siege&arg=Giran`, `do=siegeend`, `do=siegeinfo`. **Осады** (`FakePlayerSieges`). По расписанию `SiegeSchedule.xml` бот-кланы с ≥3 членами 40+ онлайн регистрируются атакующими на замки бот-кланов (по 2 клана на замок, `FakePlayerSiegeAttackerClans`; на замки игроков — только при `FakePlayerSiegeAttackPlayerCastles = True`). Лидеры кланов подгружаются в мир заранее. В начале осады атакующие собираются за внешними воротами, защитники — у артефакта. Атакующие бьют врагов, ломают ворота, идут к артефакту, лидер клана кастует Печать Правителя (3 минуты, остальные прикрывают) — замок переходит настоящей механикой движка. Крики в крик-чат: начало, ворота, печать, победа/поражение. Форс: `do=siege&arg=Giran`, `do=siegeend`, `do=siegeinfo`.
**Рейды** (`FakePlayerRaids`). Раз в 30–90 минут собирается настоящая пати из 5–9 ботов уровня живого рейд-босса (20–80, гранд-боссы не трогаются), телепорт к боссу, боевой клич, бой до смерти босса или 25 минут (умершие встают в городе и возвращаются к боссу), потом все расходятся по спотам с событием «после рб» в контексте чата. `do=raid` — запустить сейчас, `do=raidinfo` — статус.
**Пати.** Соклановцы одного уровня стартуют на одном споте и собираются в настоящие пати (до 6), держатся лидера, ассистят его цель, хилеры лечат. С игроком: «го пати» → бот **приглашает сам** (обычное окно приглашения), инвайт игроку бот принимает. **Пати.** Соклановцы одного уровня стартуют на одном споте и собираются в настоящие пати (до 6), держатся лидера, ассистят его цель, хилеры лечат. С игроком: «го пати» → бот **приглашает сам** (обычное окно приглашения), инвайт игроку бот принимает.
**Чат — один движок на всех.** Шёпот и общий чат идут через `FakePlayerUnderstanding`: интенты с опечатками, память (кто убивал/помогал/соклановец/враг по кв), позиция на 15 минут (второй раз «я ж сказал: …»), молчание на первое сообщение незнакомца, реакция на капс, зеркалирование стиля, идиолект (свой смех, паразит, сленг, мат у трети), факты контекста (план: фарм/торговля/осада/переезд, событие: ап, ганг, профа, конец осады) и слоты (`{zone} {level} {mob} {town} {crowd} {drop} {clan} {enemyclan} {castle}`). Боты **разговаривают друг с другом**, когда рядом есть живой игрок: здороваются при встрече, задают вопросы (`bot_talk`: «как фарм», «дроп есть?», «какой лвл», «го в пати», «пк ходят?», «клан есть?»…), отвечают через тот же разбор интентов (до 3 реплик в цепочке), подхватывают жалобы и вопросы, поздравляют с лвлом, сплетничают. Зов на помощь при проигрываемом пвп — соклановцы в радиусе 1500 подтягиваются. LLM (Ollama) по умолчанию только для фраз без интента (`FakePlayerLlmPriority = seams`), `llm` — наоборот. **Чат — один движок на всех.** Шёпот и общий чат идут через `FakePlayerUnderstanding`: интенты с опечатками, память (кто убивал/помогал/соклановец/враг по кв), позиция на 15 минут (второй раз «я ж сказал: …»), молчание на первое сообщение незнакомца, реакция на капс, зеркалирование стиля, идиолект (свой смех, паразит, сленг, мат у трети), факты контекста (план: фарм/торговля/осада/переезд, событие: ап, ганг, профа, конец осады) и слоты (`{zone} {level} {mob} {town} {crowd} {drop} {clan} {enemyclan} {castle}`). Боты **разговаривают друг с другом**, когда рядом есть живой игрок: здороваются при встрече, задают вопросы (`bot_talk`: «как фарм», «дроп есть?», «какой лвл», «го в пати», «пк ходят?», «клан есть?»…), отвечают через тот же разбор интентов (до 3 реплик в цепочке), подхватывают жалобы и вопросы, поздравляют с лвлом, сплетничают. Зов на помощь при проигрываемом пвп — соклановцы в радиусе 1500 подтягиваются. LLM (Ollama) по умолчанию только для фраз без интента (`FakePlayerLlmPriority = seams`), `llm` — наоборот.
**Проверка, что они это делают, а не делают вид**: `do=stats` (сумма по населению), `do=stats&name=Ник` (один бот: убийства, опыт, лут, продано/куплено/адена, лавки, касты, отдыхи, реплики, пати, переезды, профы, осады, последнее действие), `/api/stats` (JSON), строка `FakePlayerTelemetry: last N min -> kills=+… exp=+… loot=+…` в логе раз в `FakePlayerStatsLogMinutes`, `FakePlayerChatLog = True` — каждая реплика в лог с каналом и швом. Оффлайн: `tools/run_harness.sh` (движок чата на реальных XML), `python tools/check_data.py` (пулы, слоты, швы, скиллы профилей). **Проверка, что они это делают, а не делают вид**: `do=stats` (сумма по населению), `do=stats&name=Ник` (один бот: убийства, опыт, лут, продано/куплено/адена, лавки, касты, отдыхи, реплики, пати, переезды, профы, осады, последнее действие), `/api/stats` (JSON), строка `FakePlayerTelemetry: last N min -> kills=+… exp=+… loot=+…` в логе раз в `FakePlayerStatsLogMinutes`, `FakePlayerChatLog = True` — каждая реплика в лог с каналом и швом. Оффлайн: `tools/run_harness.sh` (движок чата на реальных XML), `python tools/check_data.py` (пулы, слоты, швы, скиллы профилей).
Настройки — секция «SPP additions» в `game\config\Custom\FakePlayers.ini`: `FakePlayerLlmPriority`, `FakePlayerBotDialogues`, `FakePlayerEconomy`, `FakePlayerProfessionChange`, `FakePlayerSieges`, `FakePlayerSiegeAttackerClans`, `FakePlayerSiegeAttackPlayerCastles`, `FakePlayerStatsLogMinutes`, `FakePlayerChatLog`. Настройки — секция «SPP additions» в `game\config\Custom\FakePlayers.ini`: `FakePlayerLlmPriority`, `FakePlayerBotDialogues`, `FakePlayerEconomy`, `FakePlayerProfessionChange`, `FakePlayerSieges`, `FakePlayerSiegeAttackerClans`, `FakePlayerSiegeAttackPlayerCastles`, `FakePlayerRaids`, `FakePlayerStatsLogMinutes`, `FakePlayerChatLog`.
Ограничения: NPC-режим ботов (`FakePlayerProgression = True`) не поддерживается — шаблонов `fpc_spp_*` в сервере нет; смертность у магов и на спавне в центре стай выше, чем у живых (см. `AUDIT.md`). Ограничения: NPC-режим ботов (`FakePlayerProgression = True`) не поддерживается — шаблонов `fpc_spp_*` в сервере нет; смертность у магов и на спавне в центре стай выше, чем у живых (см. `AUDIT.md`).
+1
View File
@@ -19,6 +19,7 @@
| `do=talk&name=Бот&arg=ТвойНик` | два бота телепортируются к тебе и начинают диалог (вопрос → ответ через разбор интентов) | | `do=talk&name=Бот&arg=ТвойНик` | два бота телепортируются к тебе и начинают диалог (вопрос → ответ через разбор интентов) |
| `do=invite&arg=ТвойНик` | бот прыгает к тебе и **приглашает в пати** (окно приглашения у тебя) | | `do=invite&arg=ТвойНик` | бот прыгает к тебе и **приглашает в пати** (окно приглашения у тебя) |
| `do=siege&arg=Giran` | форс-осада: регистрирует бот-кланы атакующими и запускает осаду движка; `do=siegeend&arg=Giran` — закончить; `do=siegeinfo` — расписание, зарегистрированные кланы, сколько ботов в бою | | `do=siege&arg=Giran` | форс-осада: регистрирует бот-кланы атакующими и запускает осаду движка; `do=siegeend&arg=Giran` — закончить; `do=siegeinfo` — расписание, зарегистрированные кланы, сколько ботов в бою |
| `do=raid` | собрать рейд-пати (5–9 ботов уровня босса) и отправить на живого рейд-босса; `do=raidinfo` — что сейчас с рейдом |
| `do=online&arg=charId` | подгрузить конкретного сид-персонажа в мир (так осадный менеджер подтягивает лидеров кланов) | | `do=online&arg=charId` | подгрузить конкретного сид-персонажа в мир (так осадный менеджер подтягивает лидеров кланов) |
`/api/stats` — то же, что `do=stats`, в JSON; `/api/sieges` — как `do=siegeinfo`. `/api/stats` — то же, что `do=stats`, в JSON; `/api/sieges` — как `do=siegeinfo`.
@@ -126,6 +126,9 @@ FakePlayerSiegeAttackerClans = 2
# Also besiege castles owned by real players' clans. # Also besiege castles owned by real players' clans.
FakePlayerSiegeAttackPlayerCastles = False 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. # Print bot activity totals (kills, exp, loot, trades, chat...) to the log every N minutes. 0 = off.
FakePlayerStatsLogMinutes = 10 FakePlayerStatsLogMinutes = 10
@@ -130,6 +130,7 @@ import org.l2jmobius.gameserver.managers.FakePlayerDashboard;
import org.l2jmobius.gameserver.managers.FakePlayerHeadlessManager; import org.l2jmobius.gameserver.managers.FakePlayerHeadlessManager;
import org.l2jmobius.gameserver.managers.FakePlayerIntentParser; import org.l2jmobius.gameserver.managers.FakePlayerIntentParser;
import org.l2jmobius.gameserver.managers.FakePlayerRumorManager; import org.l2jmobius.gameserver.managers.FakePlayerRumorManager;
import org.l2jmobius.gameserver.managers.FakePlayerRaidManager;
import org.l2jmobius.gameserver.managers.FakePlayerSiegeManager; import org.l2jmobius.gameserver.managers.FakePlayerSiegeManager;
import org.l2jmobius.gameserver.managers.FakePlayerTelemetry; import org.l2jmobius.gameserver.managers.FakePlayerTelemetry;
import org.l2jmobius.gameserver.managers.FakePlayerProgressionManager; import org.l2jmobius.gameserver.managers.FakePlayerProgressionManager;
@@ -362,6 +363,7 @@ public class GameServer
DayNightSpawnManager.getInstance().trim().notifyChangeMode(); DayNightSpawnManager.getInstance().trim().notifyChangeMode();
DimensionalRiftManager.getInstance(); DimensionalRiftManager.getInstance();
RaidBossSpawnManager.getInstance(); RaidBossSpawnManager.getInstance();
FakePlayerRaidManager.getInstance();
printSection("Siege"); printSection("Siege");
SiegeManager.getInstance().getSieges(); SiegeManager.getInstance().getSieges();
@@ -69,6 +69,7 @@ public class FakePlayersConfig
public static boolean FAKE_PLAYER_SIEGE_ATTACK_PLAYER_CASTLES; public static boolean FAKE_PLAYER_SIEGE_ATTACK_PLAYER_CASTLES;
public static int FAKE_PLAYER_STATS_LOG_MINUTES; public static int FAKE_PLAYER_STATS_LOG_MINUTES;
public static boolean FAKE_PLAYER_CHAT_LOG; public static boolean FAKE_PLAYER_CHAT_LOG;
public static boolean FAKE_PLAYER_RAIDS;
public static void load() public static void load()
{ {
@@ -110,5 +111,6 @@ public class FakePlayersConfig
FAKE_PLAYER_SIEGE_ATTACK_PLAYER_CASTLES = config.getBoolean("FakePlayerSiegeAttackPlayerCastles", false); FAKE_PLAYER_SIEGE_ATTACK_PLAYER_CASTLES = config.getBoolean("FakePlayerSiegeAttackPlayerCastles", false);
FAKE_PLAYER_STATS_LOG_MINUTES = config.getInt("FakePlayerStatsLogMinutes", 10); FAKE_PLAYER_STATS_LOG_MINUTES = config.getInt("FakePlayerStatsLogMinutes", 10);
FAKE_PLAYER_CHAT_LOG = config.getBoolean("FakePlayerChatLog", false); FAKE_PLAYER_CHAT_LOG = config.getBoolean("FakePlayerChatLog", false);
FAKE_PLAYER_RAIDS = config.getBoolean("FakePlayerRaids", true);
} }
} }
@@ -84,6 +84,9 @@ public class FakePlayerBotState
public int siegeCastleId = 0; public int siegeCastleId = 0;
public long siegeNextThink = 0; public long siegeNextThink = 0;
// --- raid ---
public int raidBossId = 0;
// --- telemetry --- // --- telemetry ---
public final AtomicLong kills = new AtomicLong(); public final AtomicLong kills = new AtomicLong();
public final AtomicLong expGained = new AtomicLong(); public final AtomicLong expGained = new AtomicLong();
@@ -99,6 +102,7 @@ public class FakePlayerBotState
public final AtomicLong skillsCast = new AtomicLong(); public final AtomicLong skillsCast = new AtomicLong();
public final AtomicLong rests = new AtomicLong(); public final AtomicLong rests = new AtomicLong();
public final AtomicLong siegesJoined = new AtomicLong(); public final AtomicLong siegesJoined = new AtomicLong();
public final AtomicLong raidsWon = new AtomicLong();
public final AtomicLong migrations = new AtomicLong(); public final AtomicLong migrations = new AtomicLong();
public final AtomicLong professions = new AtomicLong(); public final AtomicLong professions = new AtomicLong();
public final AtomicLong partiesJoined = new AtomicLong(); public final AtomicLong partiesJoined = new AtomicLong();
@@ -522,6 +522,12 @@ public class FakePlayerHeadlessManager
return; 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. // Perception and talk first: a farming bot still notices people and chats.
if (FakePlayerSocial.getInstance().tick(bot, state, now)) if (FakePlayerSocial.getInstance().tick(bot, state, now))
{ {
@@ -1770,6 +1776,14 @@ public class FakePlayerHeadlessManager
{ {
return FakePlayerSiegeManager.getInstance().info(); return FakePlayerSiegeManager.getInstance().info();
} }
case "raid":
{
return FakePlayerRaidManager.getInstance().start();
}
case "raidinfo":
{
return FakePlayerRaidManager.getInstance().info();
}
case "online": case "online":
{ {
try try
@@ -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();
}
}
@@ -72,6 +72,7 @@ public class FakePlayerTelemetry
long casts = 0; long casts = 0;
long rests = 0; long rests = 0;
long sieges = 0; long sieges = 0;
long raids = 0;
long migrations = 0; long migrations = 0;
long professions = 0; long professions = 0;
long parties = 0; long parties = 0;
@@ -103,6 +104,7 @@ public class FakePlayerTelemetry
casts += state.skillsCast.get(); casts += state.skillsCast.get();
rests += state.rests.get(); rests += state.rests.get();
sieges += state.siegesJoined.get(); sieges += state.siegesJoined.get();
raids += state.raidsWon.get();
migrations += state.migrations.get(); migrations += state.migrations.get();
professions += state.professions.get(); professions += state.professions.get();
parties += state.partiesJoined.get(); parties += state.partiesJoined.get();
@@ -158,6 +160,7 @@ public class FakePlayerTelemetry
totals.put("migrations", migrations); totals.put("migrations", migrations);
totals.put("professions", professions); totals.put("professions", professions);
totals.put("sieges", sieges); totals.put("sieges", sieges);
totals.put("raidsWon", raids);
totals.put("uptimeMin", (System.currentTimeMillis() - _startedAt) / 60000); totals.put("uptimeMin", (System.currentTimeMillis() - _startedAt) / 60000);
return totals; 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(" 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(" 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(" 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()); 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(); return sb.toString();
} }