Last active
November 11, 2022 01:40
-
-
Save YoukaiCat/e6eab1eae89a5a035ef2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name HentaiVerseBot | |
// @namespace YoukaiCat | |
// @description HentaiVerse Bot | |
// @include http://hentaiverse.org/* | |
// @version 0.0.1 | |
// @grant none | |
// @require http://code.jquery.com/jquery-2.1.4.min.js | |
// ==/UserScript== | |
"use strict"; | |
let HentaiVerseBot = () => { | |
let log = (object) => console.log(object.toString()); | |
//Случайное число в промежутке [min...max] | |
let randInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; | |
class Stat { | |
constructor(name, amount) { | |
this.name = name; | |
this.amount = amount; | |
} | |
toString() { | |
return `${this.name}: ${this.amount}`; | |
} | |
} | |
class StatCreator { | |
//Здровье, мана, дух и оверчардж | |
static simpleStatFrom(object) { | |
const FullWidth = 120; | |
let alt = object.attr("alt"); | |
let style = object.attr("style"); | |
let match = /\w\:(\d{0,3})px/.exec(style); | |
let width = parseInt(match[1]); | |
let percentage = width * 100 / FullWidth; //.find("~ .cwbt > .cwbt1 > .fd2 > div") | |
return new Stat(alt, percentage); | |
} | |
static staminaStatFrom(object) { | |
let text = object.text(); | |
let match = /Stamina:\s*(\d{0,3})/.exec(text); | |
let persentage = parseInt(match[1]); | |
return new Stat("stamina", persentage); | |
} | |
} | |
class ClickableObject { | |
constructor(object) { | |
this.object = object; | |
} | |
click() { | |
let sleep = (milliseconds) => { | |
let currentTime = () => new Date().getTime(); | |
let start = currentTime(); | |
while (true) { | |
if ((currentTime() - start) > milliseconds) { | |
break; | |
} | |
} | |
}; | |
let timeout = randInRange(800, 3000); | |
// sleep(timeout); //Мы не быстрые | |
this.object.hover(); //Навести курсор, вдруг анти-бот анализирует? | |
// sleep(300); | |
this.object.click(); //DOIT | |
// sleep(10000); //Заморозим выполнение, пока страничка не перезагрузилась | |
} | |
} | |
class Ability extends ClickableObject { | |
constructor(object, name, cost, cooldown) { | |
super(object); | |
this.name = name; | |
this.cost = cost; | |
this.cooldown = cooldown; | |
} | |
toString() { | |
return `${this.name}, ${this.cost}, ${this.cooldown}`; | |
} | |
} | |
class Spell extends Ability { | |
constructor(object, name, cost, cooldown) { | |
super(object, name, cost, cooldown) | |
} | |
} | |
class WeaponSkill extends Ability { | |
constructor(object, name, cost, cooldown) { | |
super(object, name, cost, cooldown) | |
} | |
} | |
class InnateSkill extends Ability { | |
constructor(object, name) { | |
super(object, name, 0, 0) | |
} | |
} | |
class AbilityCreator { | |
static abilityFrom(object) { | |
let onmouseover = object.attr("onmouseover"); | |
let match = /battle\.set\_infopane\_spell\(\'([^']+)\'\, \'[^']*\'\, \'[^']*\'\, (\d+)\, (\d+)\, (\d+)\)/.exec(onmouseover); | |
let name = match[1]; | |
let magicCost = parseInt(match[2]); | |
let overchargeCost = parseInt(match[3]); | |
let cooldown = parseInt(match[4]); | |
if (magicCost === 0 && overchargeCost === 0) { | |
return new InnateSkill(object, name); | |
} else if (magicCost === 0 && overchargeCost > 0) { | |
return new WeaponSkill(object, name, overchargeCost, cooldown); | |
} else if (magicCost > 0 && overchargeCost === 0) { | |
return new Spell(object, name, magicCost, cooldown) | |
} else { | |
console.log("Unknown ability:" + name); | |
} | |
} | |
} | |
class Effect { | |
constructor(name, duration) { | |
this.name = name; | |
this.duration = duration; | |
} | |
toString() { | |
return `${this.name}, ${this.duration}`; | |
} | |
} | |
class EffectCreator { | |
static effectFrom(object) { | |
let onmouseover = object.attr("onmouseover"); | |
let match = /battle\.set\_infopane\_effect\(\'([^']+)\'\, \'[^']*\'\, (\d+)\)/.exec(onmouseover); | |
let name = match[1]; | |
let duration = parseInt(match[2]); | |
return new Effect(name, duration); | |
} | |
} | |
class Item extends ClickableObject { | |
constructor(object, name) { | |
super(object); | |
this.name = name; | |
} | |
toString() { | |
return `${this.name}`; | |
} | |
} | |
class ItemCreator { | |
static itemFrom(object) { | |
let onmouseover = object.attr("onmouseover"); | |
let match = /battle\.set\_infopane\_item\(\'([^']+)\'\,\'[^']*\'\,\'[^']*\'\)/.exec(onmouseover); | |
let name = match[1]; | |
return new Item(object, name); | |
} | |
} | |
class Enemy extends ClickableObject { | |
constructor(object, name, level, stats, effects) { | |
super(object); | |
this.name = name; | |
this.level = level; | |
this.stats = stats; | |
this.effects = effects; | |
} | |
toString() { | |
return `${this.name}, ${this.level}, ${this.stats}, ${this.effects}`; | |
} | |
} | |
class Boss extends Enemy { | |
constructor(object, name, level, stats, effects, bossTier) { | |
super(object, name, level, stats, effects); | |
this.bossTier = bossTier; | |
} | |
} | |
class EnemyCreator { | |
static enemyFrom(object) { | |
let name = $(object.find(".btm3 > .fd2 > div")[0]).text(); | |
let level = $(object.find(".btm2 > div > .fd4 > div")[0]).text(); | |
let stats = jQuery.makeArray(object.find(".chbd > img[class=chb2]")).map(x => StatCreator.simpleStatFrom($(x))); | |
let effects = jQuery.makeArray(object.find(".btm6 > img")).map(x => EffectCreator.effectFrom($(x))); | |
let style = object.attr("style"); | |
let match = /border\:\d+px ridge (\#[\d\w]+)/.exec(style); | |
let color = match[1]; | |
// Боссы отличаются по цвету | |
if (color === "#5C0D12") { | |
return new Enemy(object, name, level, stats, effects); | |
} else { | |
return new Boss(object, name, level, stats, effects, 0); | |
} | |
} | |
} | |
class PlayerProperty { | |
constructor(objects) { | |
objects.reduce((x, y) => Object.defineProperty(x, y.name, { value: y }), this); | |
} | |
has(name) { | |
return this.hasOwnProperty(name); | |
} | |
use(name) { | |
return this[name].click(); | |
} | |
useIfAvailable(name) { | |
if (this.has(name)) { | |
this.use(name); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
} | |
class Player { | |
constructor(stats, abilities, effects, items, enemies) { | |
this.stats = new PlayerProperty(stats); | |
this.abilities = new PlayerProperty(abilities); | |
this.effects = new PlayerProperty(effects); | |
this.items = new PlayerProperty(items); | |
this.enemies = enemies; | |
this.stopped = false; | |
} | |
notify(message) { | |
new Notification(message); | |
} | |
notifyAndStop(message) { | |
this.stopped = true; | |
this.notify(message); | |
} | |
checkBosses() { | |
let isBoss = (enemy) => enemy instanceof Boss; | |
if (this.enemies.some(isBoss)) { | |
this.notifyAndStop("ХОЗЯИН, ПОМОГИ, БОСС!!"); | |
} | |
} | |
checkStamina() { | |
if (this.stats.stamina.amount < 80) { | |
this.notify("НЕЭФФЕКТИВНАЯ ПРОКАЧКА"); | |
} | |
} | |
useFirstAvailable(entitiesNamePairs) { | |
for (let pair of entitiesNamePairs) { | |
if (pair[0].useIfAvailable(pair[1])) { | |
return true; | |
} | |
} | |
return false; | |
} | |
checkHealth() { | |
if (this.stats.health.amount < 40) { | |
let alternatives = [[this.items, "Mystic Gem"], [this.items, "Mana Gem"], [this.items, "Health Gem"], | |
[this.abilities, "Cure"], [this.items, "Mana Potion"], [this.items, "Health Potion"]]; | |
if (!this.useFirstAvailable(alternatives)) { | |
this.notifyAndStop("НЕЧЕМ ПОПОЛНИТЬ ХП!"); | |
} | |
} | |
} | |
checkMagic() { | |
if (this.stats.magic.amount < 35) { | |
let alternatives = [[this.items, "Mana Gem"], [this.items, "Mana Potion"]]; | |
if (!this.useFirstAvailable(alternatives)) { | |
this.notifyAndStop("НЕЧЕМ ПОПОЛНИТЬ МАНУ!"); | |
} | |
} | |
} | |
checkBuff(name) { | |
let effectItemPairs = { | |
"Replenishment": "Mana Draught", | |
"Regeneration": "Health Draught" | |
}; | |
let effectSpellPairs = { | |
"Hastened": "Haste" | |
}; | |
if (!this.effects.has(name)) { | |
if (effectItemPairs.hasOwnProperty(name)) { | |
if (!this.items.useIfAvailable(effectItemPairs[name])) { | |
this.notifyAndStop(`НЕТ ИТЕМА: ${name}!`); | |
} | |
} else { | |
if (!((effectSpellPairs.hasOwnProperty(name) && | |
this.abilities.useIfAvailable(effectSpellPairs[name]) | |
) || this.abilities.useIfAvailable(name))) { | |
this.notifyAndStop(`НЕ КАСТУЕТСЯ БАФФ: ${name}`); | |
} | |
} | |
} | |
} | |
checkExpensiveBuff(name) { | |
if (!this.effects.has(name)) { | |
if (this.effects.has("Channeling")) { | |
this.abilities.useIfAvailable(name); | |
} else { | |
this.items.useIfAvailable("Mystic Gem"); | |
} | |
} | |
} | |
checkBuffs() { | |
this.checkExpensiveBuff("Heartseeker"); | |
["Replenishment", "Regeneration", "Regen", "Protection", | |
"Shadow Veil", "Hastened"].forEach(x => { if (!this.stopped) this.checkBuff(x) }); | |
} | |
hitEnemy() { | |
if (this.enemies.length > 0) { | |
let n = randInRange(0, this.enemies.length - 1); | |
let enemy = this.enemies[n]; | |
if (this.stats.overcharge.amount > 98) { | |
if (this.effects.has("Chain 2") && this.abilities.useIfAvailable("Frenzied Blows")) { | |
enemy.click(); | |
} else if (this.effects.has("Chain 1") && this.abilities.useIfAvailable("Backstab")) { | |
enemy.click(); | |
} else if (this.abilities.useIfAvailable("Iris Strike")) { | |
enemy.click(); | |
} | |
} else { | |
enemy.click(); | |
} | |
} else { | |
this.notifyAndStop("НЕТ МОНСТРОВ, НЕКОГО БИТЬ!"); | |
} | |
} | |
fight() { | |
//TODO: check Riddlemaster | |
let newRound = $("#ckey_continue"); | |
if (newRound.length > 0) { | |
newRound.click(); | |
} else { | |
[this.checkBosses, this.checkStamina, this.checkHealth, this.checkMagic, | |
this.checkBuffs, this.hitEnemy].forEach(f => { if (!this.stopped) f.call(this) }); | |
} | |
} | |
} | |
class HentaiVerse { | |
static getSimpleStats() { | |
let elements = jQuery.makeArray($(".cwb2")); | |
return elements.map(x => StatCreator.simpleStatFrom($(x))); | |
} | |
static getStaminaStat() { | |
return StatCreator.staminaStatFrom($($("div:contains('Stamina:')")[2]).find("div")); | |
} | |
static getStats() { | |
return HentaiVerse.getSimpleStats().concat(HentaiVerse.getStaminaStat()); | |
} | |
static getAbilities() { | |
let elements = jQuery.makeArray($(".bts > div")); | |
return elements | |
.map(x => $(x)) | |
.filter(x => x.attr("onclick") !== undefined) | |
.map(x => AbilityCreator.abilityFrom(x)); | |
} | |
static getEffects() { | |
let elements = jQuery.makeArray($(".bte > img")); | |
return elements.map(x => EffectCreator.effectFrom($(x))); | |
} | |
static getItems() { | |
let elements = jQuery.makeArray($(".bti3 > div")); | |
return elements | |
.map(x => $(x)) | |
.filter(x => x.attr("onclick") !== undefined) | |
.map(x => ItemCreator.itemFrom(x)); | |
} | |
static getMonsters() { | |
let elements = jQuery.makeArray($("#monsterpane > div")); | |
return elements | |
.map(x => $(x)) | |
.filter(x => x.attr("onclick") !== undefined) | |
.map(x => EnemyCreator.enemyFrom(x)); | |
} | |
static main() { | |
let player = new Player(HentaiVerse.getStats(), HentaiVerse.getAbilities(), | |
HentaiVerse.getEffects(), HentaiVerse.getItems(), HentaiVerse.getMonsters()); | |
setTimeout(() => player.fight(), randInRange(800, 3000)); | |
} | |
} | |
return HentaiVerse; | |
}; | |
$(document).ready(() => HentaiVerseBot().main()); //ruby -run -ehttpd . -p8000 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment