diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/dashboard.html b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/dashboard.html index 12a0524..81c8b85 100644 --- a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/dashboard.html +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/dist/game/data/dashboard.html @@ -38,6 +38,8 @@ canvas{width:100%;height:100%;display:block;background:#0d1014}
живой срез мира, обновление каждые 5 сек
Итого
+
Что реально сделали
+
Фильтр
@@ -180,6 +182,17 @@ document.getElementById('pGo').onclick=()=>{ })}; refresh();setInterval(refresh,5000); +function telemetry(){ + fetch('/api/stats').then(r=>r.json()).then(t=>{ + const keys=['kills','exp','loot','itemsSold','adenaEarned','adenaSpent','stores','skillsCast','rests','lines','replies','parties','migrations','professions','sieges','pvpKills','deaths']; + let h=''; + keys.forEach(k=>{h+='
'+k+''+(t[k]??0)+'
';}); + h+='
сейчасбой '+t.fighting+' · сидят '+t.sitting+' · торгуют '+t.trading+' · в пати '+t.inParty+' · на осаде '+t.besieging+'
'; + h+='
аптайм'+t.uptimeMin+' мин
'; + document.getElementById('telemetry').innerHTML=h; + }).catch(()=>{}); +} +telemetry();setInterval(telemetry,10000); window.addEventListener('resize',draw); diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerBotState.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerBotState.java index 7b87465..3c66cbd 100644 --- a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerBotState.java +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerBotState.java @@ -79,6 +79,7 @@ public class FakePlayerBotState public boolean giftedStock = false; // --- siege --- + public boolean temporary = false; // Logged in on demand for a siege; leaves the world afterwards. public byte siegeSide = 0; // 0 none, 1 attacker, 2 defender. public int siegeCastleId = 0; public long siegeNextThink = 0; diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerHeadlessManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerHeadlessManager.java index 342995b..920f9f1 100644 --- a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerHeadlessManager.java +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerHeadlessManager.java @@ -191,7 +191,9 @@ public class FakePlayerHeadlessManager // Believable player title instead of a personality label. player.setTitle(TITLES[Rnd.get(TITLES.length)]); - // Restore clan membership so crests, clan wars and sieges work like for real players. + // The bot is online for the engine (clan member lists, sieges, parties count it like a logged in player). + player.setOnlineStatus(true, false); + // Restore clan membership so crests, clan skills, clan wars and sieges work like for real players. if (player.getClanId() > 0) { final Clan clan = ClanTable.getInstance().getClan(player.getClanId()); @@ -200,6 +202,11 @@ public class FakePlayerHeadlessManager player.setClan(clan); player.setPledgeType(0); player.setPowerGrade((clan.getLeaderId() == player.getObjectId()) ? 1 : 6); + final org.l2jmobius.gameserver.entity.clan.ClanMember member = clan.getClanMember(player.getObjectId()); + if (member != null) + { + member.setPlayer(player); // Clan skills, siege skills for the leader, "online" for the clan. + } } } // Place the bot in a level appropriate farming zone (shared zone table). Clanmates of a @@ -247,6 +254,16 @@ public class FakePlayerHeadlessManager * @return the headless player, or null. */ public Player ensureOnline(int charId) + { + return ensureOnline(charId, false); + } + + /** + * @param charId the character id. + * @param temporary true when the bot should log out again after its job (siege reinforcement). + * @return the headless player, or null. + */ + public Player ensureOnline(int charId, boolean temporary) { if (charId <= 0) { @@ -266,11 +283,46 @@ public class FakePlayerHeadlessManager final Player bot = spawnOne(charId); if (bot != null) { - LOGGER.info(getClass().getSimpleName() + ": " + bot.getName() + " logged in on demand (" + _bots.size() + " headless players now)."); + final FakePlayerBotState state = stateOf(bot); + if ((state != null) && temporary) + { + state.temporary = true; + } + LOGGER.info(getClass().getSimpleName() + ": " + bot.getName() + " logged in on demand" + (temporary ? " for a siege" : "") + " (" + _bots.size() + " headless players now)."); } return bot; } + /** + * Logs a temporary bot out (siege reinforcements after the battle): stores it and removes it from the world. + * @param bot the bot. + */ + public void despawn(Player bot) + { + final FakePlayerBotState state = stateOf(bot); + if ((state == null) || !state.temporary || (state.siegeSide != 0)) + { + return; + } + try + { + closeStore(bot); + if (bot.getParty() != null) + { + bot.leaveParty(); + } + _states.remove(bot.getObjectId()); + _bots.remove(bot); + bot.setOnlineStatus(false, false); + bot.deleteMe(); + LOGGER.info(getClass().getSimpleName() + ": " + bot.getName() + " logged out (" + _bots.size() + " headless players now)."); + } + catch (Exception e) + { + LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not log out " + bot.getName(), e); + } + } + // ======================== Registry ======================== /** diff --git a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerSiegeManager.java b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerSiegeManager.java index 04d7088..4cdefbd 100644 --- a/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerSiegeManager.java +++ b/src_mobius/mobius/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/managers/FakePlayerSiegeManager.java @@ -287,17 +287,60 @@ public class FakePlayerSiegeManager } _battles.put(castle.getResidenceId(), battle); + // Reinforcements: a registered clan brings its strongest members even if they were not in the + // random population sample (they log out again after the battle). + final Set battleClans = new HashSet<>(battle.attackerClans); + battleClans.addAll(battle.defenderClans); + for (int clanId : battleClans) + { + final Clan clan = ClanTable.getInstance().getClan(clanId); + if (clan == null) + { + continue; + } + int online = 0; + for (Player bot : FakePlayerHeadlessManager.getInstance().bots()) + { + if ((bot.getClanId() == clanId) && (bot.getLevel() >= (MIN_LEVEL - 10))) + { + online++; + } + } + final List strongest = new ArrayList<>(clan.getMembers()); + strongest.sort((a, b) -> Integer.compare(b.getLevel(), a.getLevel())); + for (org.l2jmobius.gameserver.entity.clan.ClanMember member : strongest) + { + if ((online >= 8) || (member.getLevel() < MIN_LEVEL)) + { + break; + } + if (FakePlayerHeadlessManager.getInstance().ensureOnline(member.getObjectId(), true) != null) + { + online++; + } + } + FakePlayerHeadlessManager.getInstance().ensureOnline(clan.getLeaderId(), true); + } + // Participants: online headless members of the registered clans. final Map perClan = new HashMap<>(); + int members = 0; + int tooLow = 0; for (Player bot : FakePlayerHeadlessManager.getInstance().bots()) { final int clanId = bot.getClanId(); final boolean attacker = battle.attackerClans.contains(clanId); final boolean defender = battle.defenderClans.contains(clanId); - if ((!attacker && !defender) || (bot.getLevel() < (MIN_LEVEL - 10))) + if (!attacker && !defender) { continue; } + members++; + if (bot.getLevel() < (MIN_LEVEL - 10)) + { + tooLow++; + continue; + } final int count = perClan.getOrDefault(clanId, 0); if ((count >= MAX_PER_CLAN) && !bot.isClanLeader()) { @@ -315,6 +358,12 @@ public class FakePlayerSiegeManager state.siegesJoined.incrementAndGet(); state.action((attacker ? "siege attack " : "siege defend ") + castle.getName()); bot.setSiegeState(state.siegeSide); + bot.setSiegeSide(castle.getResidenceId()); + if (bot.isClanLeader() && (bot.getKnownSkill(CommonSkill.SEAL_OF_RULER.getId()) == null)) + { + SiegeManager.getInstance().addSiegeSkills(bot); + } + bot.updateUserInfo(); FakePlayerBotContext.setPlan(state.seedId, "siege", 3 * 3600000L); FakePlayerBotContext.setSlot(state.seedId, "castle", castle.getName()); battle.participants.add(bot); @@ -345,7 +394,7 @@ public class FakePlayerSiegeManager ThreadPool.schedule(() -> cry(bot, seam), 6000 + Rnd.get(4000)); } } - LOGGER.info(getClass().getSimpleName() + ": Siege of " + castle.getName() + " - " + battle.participants.size() + " bots joined (attacker clans " + battle.attackerClans + ", defender clans " + battle.defenderClans + ")."); + LOGGER.info(getClass().getSimpleName() + ": Siege of " + castle.getName() + " - " + battle.participants.size() + " bots joined of " + members + " clan members online (" + tooLow + " too low), attacker clans " + battle.attackerClans + ", defender clans " + battle.defenderClans + "."); } /** @@ -365,7 +414,7 @@ public class FakePlayerSiegeManager double best = -1; for (Door door : castle.getDoors()) { - if (!door.isShowHp()) + if (!door.isShowHp() || door.isWall()) { continue; } @@ -487,7 +536,7 @@ public class FakePlayerSiegeManager Door gate = null; for (Door door : battle.castle.getDoors()) { - if (door.isOpen() || door.isDead() || !door.isAutoAttackable(bot)) + if (door.isOpen() || door.isDead() || door.isWall() || !door.isAutoAttackable(bot)) { continue; } @@ -563,13 +612,27 @@ public class FakePlayerSiegeManager } state.siegeSide = 0; state.siegeCastleId = 0; + bot.setSiegeState((byte) 0); + bot.setSiegeSide(0); + bot.setInSiege(false); + bot.updateUserInfo(); FakePlayerBotContext.setPlan(state.seedId, "farm", 0); FakePlayerBotContext.setEvent(state.seedId, "siege_finished"); state.action("siege over"); - // Back to the farming spot after a while (the engine already sends everybody to town). + // Back to the farming spot after a while (the engine already sends everybody to town); + // reinforcements that only came for the battle log out instead. ThreadPool.schedule(() -> { - if (!bot.isDead() && (FakePlayerHeadlessManager.getInstance().stateOf(bot) != null) && (FakePlayerHeadlessManager.getInstance().stateOf(bot).siegeSide == 0)) + final FakePlayerBotState current = FakePlayerHeadlessManager.getInstance().stateOf(bot); + if ((current == null) || (current.siegeSide != 0)) + { + return; + } + if (current.temporary) + { + FakePlayerHeadlessManager.getInstance().despawn(bot); + } + else if (!bot.isDead()) { FakePlayerHeadlessManager.getInstance().returnToSpot(bot); }