Skip to content

Instantly share code, notes, and snippets.

@earthiverse
Last active January 6, 2022 07:24
Show Gist options
  • Save earthiverse/1bb14ef2071f8fe92f63249c4d6649d9 to your computer and use it in GitHub Desktop.
Save earthiverse/1bb14ef2071f8fe92f63249c4d6649d9 to your computer and use it in GitHub Desktop.
pattack tests
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-undef */
const MPOT0_RECOVERY = G.items.mpot0.gives[0][1]
const MPOT1_RECOVERY = G.items.mpot1.gives[0][1]
const HPOT0_RECOVERY = G.items.hpot0.gives[0][1]
const HPOT1_RECOVERY = G.items.hpot1.gives[0][1]
function ms_to_next_skill(skill) {
const next_skill = parent.next_skill[skill]
if (next_skill == undefined) return 0
const ms = parent.next_skill[skill].getTime() - Date.now()
return ms < 0 ? 0 : ms
}
async function moveLoop() {
try {
let nearest = get_nearest_monster()
if (!is_in_range(nearest)) {
// Move closer
move(
character.x + (nearest.x - character.x) / 2,
character.y + (nearest.y - character.y) / 2
)
}
} catch (e) {
console.error(e)
}
setTimeout(async () => { moveLoop() }, 250)
}
moveLoop()
async function lootLoop() {
try {
// The built in loot() does pretty much all of the work for us!
loot()
} catch (e) {
console.error(e)
}
setTimeout(async () => { lootLoop() }, 250)
}
lootLoop()
async function regenLoop() {
try {
const hpRatio = character.hp / character.max_hp
const hpMissing = character.max_hp - character.hp
const mpRatio = character.mp / character.max_mp
const mpMissing = character.max_mp - character.mp
const minPing = Math.min(...parent.pings)
if (character.rip) {
// Don't heal if we're dead
setTimeout(async () => { regenLoop() }, Math.max(100, ms_to_next_skill("use_hp")))
return
}
if (mpRatio < hpRatio) {
// We want to heal MP
const mpot0 = locate_item("mpot0")
const mpot1 = locate_item("mpot1")
if (mpot1 !== -1 && mpMissing >= MPOT1_RECOVERY) {
equip(mpot1)
// Equip doesn't return a promise yet. When it does, remove the setTimeout to just the function inside of it
setTimeout(() => { reduce_cooldown("use_hp", minPing) }, 2 * minPing)
} else if (mpot0 !== -1 && mpMissing >= MPOT0_RECOVERY) {
equip(mpot0)
// Equip doesn't return a promise yet. When it does, remove the setTimeout to just the function inside of it
setTimeout(() => { reduce_cooldown("use_hp", minPing) }, 2 * minPing)
} else {
use_skill("regen_mp")
// Use Skill doesn't return a promise yet. When it does, remove the setTimeout to just the function inside of it
setTimeout(() => { reduce_cooldown("use_hp", minPing) }, 2 * minPing)
}
} else if (character.hp !== character.max_hp) {
// We want to heal HP
const hpot0 = locate_item("hpot0")
const hpot1 = locate_item("hpot1")
if (hpot1 !== -1 && hpMissing >= HPOT1_RECOVERY) {
equip(hpot1)
// Equip doesn't return a promise yet. When it does, remove the setTimeout to just the function inside of it
setTimeout(() => { reduce_cooldown("use_hp", minPing) }, 2 * minPing)
} else if (hpot0 !== -1 && hpMissing >= HPOT0_RECOVERY) {
equip(hpot0)
// Equip doesn't return a promise yet. When it does, remove the setTimeout to just the function inside of it
setTimeout(() => { reduce_cooldown("use_hp", minPing) }, 2 * minPing)
} else {
use_skill("regen_hp")
// Use Skill doesn't return a promise yet. When it does, remove the setTimeout to just the function inside of it
setTimeout(() => { reduce_cooldown("use_hp", minPing) }, 2 * minPing)
}
}
} catch (e) {
console.error(e)
}
setTimeout(async () => { regenLoop() }, Math.max(500, ms_to_next_skill("use_hp")))
}
regenLoop()
/* eslint-disable no-undef */
load_code("base")
load_code("pattack10")
const TenMinutesInMs = 10 * 60 * 1000
let started
let numKilled = 0
let numCalls = 0
character.on("target_hit", (data) => { if (data.kill) numKilled += 1 })
async function attackLoop() {
/** NOTE: We're now using a try/catch so setTimeout will still be called if our code fails for whatever reason */
try {
numCalls += 1
if (started == undefined) started = Date.now()
if (Date.now() > started + TenMinutesInMs) {
show_json({
script: "pattack10",
numKilled: numKilled,
numCalls: numCalls,
pings: parent.pings,
level: character.level,
server: server
})
started = Date.now()
numKilled = 0
numCalls = 0
}
const nearest = get_nearest_monster()
if (!nearest) {
set_message("No Monsters")
return
}
set_message("Attacking")
/** NOTE: We're now awaiting the attack */
newAttack(nearest)
} catch (e) {
console.error(e)
}
// NOTE: We are now using setTimeout instead of setInterval, so our next attack will dynamically adjust when it runs
// NOTE: ms_to_next_skill is from base.js
setTimeout(async () => { attackLoop() }, 50)
}
attackLoop()
{
"script": "beg_pattack",
"numKilled": 643,
"numCalls": 11542,
"pings": [
343,
292,
290,
340,
289,
293,
284,
287,
333,
415,
283,
298,
317,
454,
285,
295,
564,
296,
293,
302,
449,
294,
284,
290,
292,
315,
289,
285,
286,
284,
285,
286,
300,
287,
286,
618,
290,
285,
415,
300
],
"level": 76,
"server": {
"mode": "normal",
"pvp": false,
"region": "EU",
"id": "II"
}
}
{
"script": "beg_pattack",
"numKilled": 640,
"numCalls": 11404,
"pings": [
291,
305,
286,
332,
290,
293,
290,
301,
382,
302,
388,
388,
468,
499,
466,
474,
542,
316,
288,
284,
304,
283,
292,
378,
293,
293,
290,
296,
285,
292,
299,
287,
284,
471,
584,
371,
456,
401,
292,
355
],
"level": 76,
"server": {
"mode": "normal",
"pvp": false,
"region": "EU",
"id": "II"
}
}
/* eslint-disable no-undef */
load_code("base")
const TenMinutesInMs = 10 * 60 * 1000
let started
let numKilled = 0
let numCalls = 0
character.on("target_hit", (data) => { if (data.kill) numKilled += 1 })
async function attackLoop() {
/** NOTE: We're now using a try/catch so setTimeout will still be called if our code fails for whatever reason */
try {
numCalls += 1
if (started == undefined) started = Date.now()
if (Date.now() > started + TenMinutesInMs) {
show_json({
script: "next_skill",
numKilled: numKilled,
numCalls: numCalls,
pings: parent.pings,
level: character.level,
server: server
})
started = Date.now()
numKilled = 0
numCalls = 0
}
const nearest = get_nearest_monster()
if (!nearest) {
set_message("No Monsters")
return
}
if (can_attack(nearest)) {
set_message("Attacking")
/** NOTE: We're now awaiting the attack */
await attack(nearest)
}
} catch (e) {
console.error(e)
}
// NOTE: We are now using setTimeout instead of setInterval, so our next attack will dynamically adjust when it runs
// NOTE: ms_to_next_skill is from base.js
setTimeout(async () => { attackLoop() }, Math.max(1, ms_to_next_skill("attack")))
}
attackLoop()
{
"script": "next_skill",
"numKilled": 650,
"numCalls": 651,
"pings": [
291,
284,
287,
284,
285,
285,
285,
284,
298,
291,
293,
283,
290,
289,
286,
293,
300,
286,
285,
285,
284,
285,
284,
284,
290,
291,
288,
289,
286,
406,
283,
284,
284,
284,
296,
285,
290,
290,
292,
283
],
"level": 76,
"server": {
"mode": "normal",
"pvp": false,
"region": "EU",
"id": "II"
}
}
//settings
const DEBUGLOG=true;
const reset=false
const safeCoefficient = 2 // recommended value: 1-2.5 - should be higher for low std, and lower for high std. low std= <15, high std >15
const limits = {attack:10} //recommended value: 5-10 - Sends Y attacks within a interval. Should be safe up to 15, but should revise if you have a high attack speed.
const samples = 50 // recommended value: 50-100 - the higher the value, the more rigid to lag spikes. Should be safe with both 15-500.
game_log("pattack loaded")
const timingsForAttacks = {};
const savedNeedle = get('pattackNeedle' + character.id);
var needle = savedNeedle || 0
const savedSample = get('pattack' + character.id)
var samplesTimes = savedSample
if (samplesTimes) {
for(var i=0; i<samplesTimes.length;i++){
if (typeof samplesTimes[i] !== 'number') {
samplesTimes=undefined;
/*cleanup if savedSample is corrupt*/
break;
}
}
}
if (reset || !samplesTimes) {
samplesTimes = new Array(samples) //the lower this number, the more responsive
const spans = [-1*60, -1*30]
const interval = spans[1] - spans[0]
for(let i = 0; i < 100; i++) {
samplesTimes[i] = Math.floor(Math.random() * interval) + spans[0]
}
}
//attack = newAttack
const targets = {}; //enable target switching to last second
function newAttack(target) {
return _use("attack",target)
}
function newUse_skill(skill,target,extra_args) {
return _use(skill,target,extra_args)
}
function getCD(skill) {
if (!G.skills[skill]) return parent.next_skill[skill]
const { share } = G.skills[skill]
if (share) {
return parent.next_skill[share]
} else {
return parent.next_skill[skill]
}
}
function getCDName(skill) {
return G.skills[skill].share || skill
}
function _use(skill,target,extra_args) {
/*enable target switching to last second*/targets[skill]=target;
if (!parent.next_skill) {
return Promise.reject("Something is strange - Wait for parent.next_skill to init")
}
const cooldownTime = getCD(skill)
if (!cooldownTime) {
oldUse_skill(skill,target,extra_args); return Promise.reject("No timer on this spell?")
}
if (!!cooldownTime &&
timingsForAttacks[getCDName(skill)] === cooldownTime &&
mssince(cooldownTime) < 0)
return Promise.reject("cooldown: "+skill+ " "+mssince(cooldownTime)) //if we already timed on the attack time
if (mssince(cooldownTime) < -700) {
return Promise.reject("cooldown: "+skill+ " "+mssince(cooldownTime))
}//if more than 100ms left say it's on cooldown
timingsForAttacks[getCDName(skill)] = cooldownTime //lock function until attack changes, also remeber the timer which is what we compare with
return _pTiming(skill,target,extra_args)
}
!window.oldAttack && (window.oldAttack = attack) //save old attack
!window.oldUse_skill && (window.oldUse_skill = use_skill) //save old attack
function _pTiming(skill,target,extra_args) {
const nowTime = new Date().getTime()
const cooldownTime = getCD(skill).getTime()
const av = avg(samplesTimes)
const st = std(samplesTimes)
const min = av - st;
const max = av + st
const amin = Math.min(0, Math.max(-300, (min - (safeCoefficient*st)))) //make sure it's between good pings, -300 and 0, things get wierd when ping > attackspeed
const amax = Math.min(0, (max + (safeCoefficient*st)))
const spamTimeStart = cooldownTime + amin
const spamTimeEnd = cooldownTime + amax
const attemptLimit = limits[skill] || 5
const targetMS = 2
const interval = (st*2*safeCoefficient)
const attempts = Math.round(Math.min(interval/targetMS,attemptLimit))
const spanPiece = interval / attempts
if (nowTime > cooldownTime) {
DEBUGLOG && console.log(`Instant skip queue - ${skill}: \tmin ${Math.floor(min)} \tmax ${Math.floor(max)} \tav${Math.floor(av)} \tstd${Math.floor(st)}`)
return _use_skill(skill,targets[skill],extra_args)
}
var results = new Array(attempts)
return new Promise((resolve,reject) => {
for (let i = 0; i < results.length; i++) {
const inc = i * spanPiece
const timing = (spamTimeStart + inc)
const value = (cooldownTime-timing) * -1
const timeout = timing-nowTime
const cb = async () => {
try {
results[i] = await _use_skill(skill,targets[skill],extra_args)
resolve();
DEBUGLOG && console.log(`Success ${skill}: \tattempt ${i} out of ${results.length} sent. Maximum allowed attempts: ${limits[skill]} \tvalue ${Math.floor(value)} \t(ADJUSTED FROM ${Math.floor(timeout)}) \t${Math.floor(inc)} MS:${spanPiece} \tDiff ${Math.floor(max - min)} \tmin ${Math.floor(min)} \tmax ${Math.floor(max)} \tav${Math.floor(av)} \tstd${Math.floor(st)}`)
record(value)
results[i] = true
} catch (e) {
results[i] = false
if (i === attempts - 1) { //if last attempt
if (results.findIndex(v => !!v) === -1) {//and no attempt succeeded
if (e.reason === 'cooldown') {
reject();
record(value)
}
timingsForAttacks[skill] = new Date(0);
}
}
}
}
setTimeout(cb,timeout)
results[i]=cb
}
})
}
function _use_skill(skill,target,extra_args) {
if (skill === "attack")
return oldAttack(target);
return oldUse_skill(skill,target,extra_args)
}
function record(v) {
samplesTimes[needle++ % samplesTimes.length] = v
if (needle % 10 === 0) {
set('pattack' + character.id, samplesTimes)
set('pattackNeedle' + character.id, needle)
}
}
function std(array) {
return Math.sqrt(avg(array.map(value => (value - avg(array)) ** 2)))
}
function avg(array) {
return array.reduce((sum, value) => sum + value) / array.length
}
/* eslint-disable no-undef */
load_code("base")
const TenMinutesInMs = 10 * 60 * 1000
let started
let numKilled = 0
let numCalls = 0
character.on("target_hit", (data) => { if (data.kill) numKilled += 1 })
async function attackLoop() {
try {
numCalls += 1
if (started == undefined) started = Date.now()
if (Date.now() > started + TenMinutesInMs) {
show_json({
script: "reduce_cooldown",
numKilled: numKilled,
numCalls: numCalls,
pings: parent.pings,
level: character.level,
server: server
})
started = Date.now()
numKilled = 0
numCalls = 0
}
const nearest = get_nearest_monster()
if (!nearest) {
set_message("No Monsters")
} else if (can_attack(nearest)) {
set_message("Attacking")
await attack(nearest)
/** NOTE: We're now reducing the cooldown based on the ping */
reduce_cooldown("attack", Math.min(...parent.pings))
}
} catch (e) {
console.error(e)
}
setTimeout(async () => { attackLoop() }, Math.max(1, ms_to_next_skill("attack")))
}
attackLoop()
{
"script": "reduce_cooldown",
"numKilled": 978,
"numCalls": 983,
"pings": [
289,
289,
391,
290,
289,
307,
290,
291,
289,
289,
288,
290,
290,
282,
288,
292,
285,
301,
286,
285,
288,
284,
283,
285,
296,
285,
289,
289,
297,
288,
290,
292,
289,
293,
289,
288,
290,
290,
298,
292
],
"level": 76,
"server": {
"mode": "normal",
"pvp": false,
"region": "EU",
"id": "II"
}
}
{
"script": "reduce_cooldown",
"numKilled": 977,
"numCalls": 982,
"pings": [
296,
314,
285,
286,
288,
286,
285,
285,
285,
290,
291,
292,
305,
285,
315,
287,
290,
372,
288,
289,
286,
289,
291,
302,
288,
313,
290,
288,
286,
301,
312,
286,
293,
286,
285,
292,
292,
291,
294,
290
],
"level": 76,
"server": {
"mode": "normal",
"pvp": false,
"region": "EU",
"id": "II"
}
}
{
"script": "reduce_cooldown",
"numKilled": 971,
"numCalls": 988,
"pings": [
309,
291,
292,
659,
292,
285,
291,
298,
284,
300,
299,
310,
307,
317,
294,
289,
288,
284,
301,
287,
286,
287,
320,
292,
290,
287,
779,
289,
298,
285,
295,
286,
284,
291,
317,
294,
293,
300,
283,
332
],
"level": 76,
"server": {
"mode": "normal",
"pvp": false,
"region": "EU",
"id": "II"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment