|
import * as Koa from "koa"; |
|
import * as Router from "koa-router"; |
|
|
|
const app = new Koa(); |
|
const router = new Router(); |
|
|
|
enum Stat { |
|
Health = 0, |
|
Shells = 6, |
|
} |
|
|
|
interface Stats { |
|
health: number; |
|
shells: number; |
|
} |
|
|
|
router |
|
.get("/stats", async (ctx, next) => { |
|
ctx.body = await readStats(); |
|
}) |
|
.post("/attack", async (ctx, next) => { |
|
await attack(); |
|
ctx.body = {}; |
|
}); |
|
|
|
app |
|
.use(router.routes()) |
|
.use(router.allowedMethods()) |
|
.listen(1337); |
|
|
|
const clientState = importSymbol("cl"); |
|
const attackDown: any = new NativeFunction(importSymbol("IN_AttackDown"), "void", []); |
|
const attackUp: any = new NativeFunction(importSymbol("IN_AttackUp"), "void", []); |
|
|
|
function readStats(): Promise<Stats> { |
|
return perform(() => { |
|
return { |
|
health: readStat(Stat.Health), |
|
shells: readStat(Stat.Shells), |
|
}; |
|
}); |
|
} |
|
|
|
function readStat(stat: Stat): number { |
|
return Memory.readInt(clientState.add(28 + stat * 4)); |
|
} |
|
|
|
async function attack(): Promise<void> { |
|
await perform(() => { |
|
attackDown(); |
|
}); |
|
await sleep(50); |
|
await perform(() => { |
|
attackUp(); |
|
}); |
|
} |
|
|
|
function sleep(duration: number): Promise<void> { |
|
return new Promise(resolve => { |
|
setTimeout(() => { resolve(); }, duration); |
|
}); |
|
} |
|
|
|
type PendingWork = () => any; |
|
|
|
const pending: PendingWork[] = []; |
|
|
|
function perform<T>(f: () => T): Promise<T> { |
|
return new Promise((resolve, reject) => { |
|
pending.push(() => { |
|
try { |
|
const result = f(); |
|
resolve(result); |
|
} catch (e) { |
|
reject(e); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
Interceptor.attach(importSymbol("IN_SendKeyEvents"), { |
|
onEnter() { |
|
while (pending.length > 0) { |
|
const f = pending.shift(); |
|
f(); |
|
} |
|
} |
|
}); |
|
|
|
function importSymbol(name: string): NativePointer { |
|
return Module.findExportByName("QuakeSpasm", name); |
|
} |