Skip to content
This repository has been archived by the owner on Sep 8, 2024. It is now read-only.

Commit

Permalink
feat: KarmaV2 (#365)
Browse files Browse the repository at this point in the history
See #365
  • Loading branch information
danirod authored Jul 13, 2021
1 parent 797f1e3 commit c3e59fc
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 26 deletions.
22 changes: 13 additions & 9 deletions spec/lib/karma.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { expect } from "chai";
import "mocha";

import { getLevel } from "../../src/lib/karma";
import { getLevelV2 } from "../../src/lib/karma";

describe("karma", () => {
describe("#getLevel", () => {
describe("#getLevelV2", () => {
it("works for positives", () => {
expect(getLevel(1)).to.eq(1);
expect(getLevel(49)).to.eq(1);
expect(getLevel(50)).to.eq(2);
expect(getLevelV2(1)).to.eq(1);
expect(getLevelV2(29)).to.eq(1);
expect(getLevelV2(30)).to.eq(2);
expect(getLevelV2(50)).to.eq(2);
expect(getLevelV2(61)).to.eq(3);
expect(getLevelV2(3879)).to.eq(49);
expect(getLevelV2(3890)).to.eq(50);
});

it("works for negatives", () => {
expect(getLevel(-1)).to.eq(-1);
expect(getLevel(-49)).to.eq(-1);
expect(getLevel(-50)).to.eq(-2);
expect(getLevelV2(-1)).to.eq(-1);
expect(getLevelV2(-29)).to.eq(-1);
expect(getLevelV2(-30)).to.eq(-2);
});

it("works for zero", () => {
expect(getLevel(0)).to.eq(0);
expect(getLevelV2(0)).to.eq(0);
});
});
});
25 changes: 10 additions & 15 deletions src/hooks/karma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ import {
Channel,
GuildMember,
Message,
MessageEmbedOptions,
MessageReaction,
PartialMessage,
PartialUser,
TextChannel,
User,
} from "discord.js";
import { Hook } from "../lib/hook";
import { canReceivePoints, getLevel, getLevelUpMessage } from "../lib/karma";
import { canReceivePoints, getLevelV2, getLevelUpMessage } from "../lib/karma";
import { KarmaDatabase } from "../lib/karma/database";
import Member from "../lib/member";
import Server from "../lib/server";
import applyWarn, { notifyPublicModlog } from "../lib/warn";
import { notifyPublicModlog } from "../lib/warn";
import Makibot from "../Makibot";

async function prefetchMessage(message: Message | PartialMessage): Promise<Message> {
Expand Down Expand Up @@ -186,15 +185,14 @@ export default class KarmaService implements Hook {

private async assertLevel(gm: GuildMember, channel: TextChannel): Promise<void> {
const member = new Member(gm);
const points = member.tagbag.tag("karma:offset").get(0) + (await this.karma.count(gm.id));
const expectedLevel = getLevel(points);
const karma = await member.getKarma();

/*
* Control mute for members with negative karma.
* TODO: This is an ugly patch. Negative levels should be fixed (and not return -1).
* Members with negative level should be the ones muted.
*/
if (points <= -3) {
if (karma.points <= -3) {
/* First, make sure this person is silenced. */
await member.setMuted(true);

Expand All @@ -207,26 +205,23 @@ export default class KarmaService implements Hook {
`Has sido silenciado automáticamente, <@${gm.user.id}>`,
"Silenciado automáticamente al tener karma excesivamente negativo"
);
alreadyWarnedTag.set(true);
await alreadyWarnedTag.set(true);
}
}

const currentLevel = member.tagbag.tag("karma:level");
const expectedLevel = getLevelV2(karma.points);
if (currentLevel.get(0) != expectedLevel) {
currentLevel.set(expectedLevel);
await currentLevel.set(expectedLevel);

const highScoreLevel = member.tagbag.tag("karma:max");
if (highScoreLevel.get(0) < expectedLevel) {
highScoreLevel.set(expectedLevel);

/* A temporal fix to avoid spamming messages to most existing members. */
if (expectedLevel > 1) {
channel.send(getLevelUpMessage(gm.id, expectedLevel));
}
await highScoreLevel.set(expectedLevel);
await channel.send(getLevelUpMessage(gm.id, expectedLevel));
}
}

/* Update presence in the tiers. */
member.setCrew(currentLevel.get(0));
await member.setCrew(currentLevel.get(0));
}
}
10 changes: 9 additions & 1 deletion src/lib/http/middlewares/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MiddlewareLocals as GuildMiddlewareLocals } from "./guild";

import Member from "../../member";
import Makibot from "../../../Makibot";
import { getLevel } from "../../karma";
import { getLevelV1, getLevelV2 } from "../../karma";

export interface MiddlewareLocals extends GuildMiddlewareLocals {
guildMember: GuildMember;
Expand Down Expand Up @@ -77,6 +77,14 @@ export default function memberMiddleware(makibot: Makibot): express.Router {
if (isNaN(offset) || offset < 0) {
res.status(400).contentType("text/plain").send("Offset must be a positive number");
} else {
/* Generation. */
const karmagen = res.locals.member.tagbag.tag("karma:ver").get<string>("v1");
const levelFormulas = {
v1: getLevelV1,
v2: getLevelV2,
};
const getLevel = levelFormulas[karmagen];

/* Bump the offset. */
await res.locals.member.tagbag.tag("karma:offset").set(offset);

Expand Down
36 changes: 35 additions & 1 deletion src/lib/karma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function getLevelUpMessage(id: Snowflake, level: number): string {
}
}

export function getLevel(points: number): number {
export function getLevelV1(points: number): number {
/*
* Considerations:
* - Always add 1 because it is not possible to have level 0, you always start from level 1.
Expand All @@ -41,3 +41,37 @@ export function getLevel(points: number): number {
return Math.trunc(progress) + 1;
}
}

export function getLevelV2(points: number): number {
/*
* Formula: XP = OFFT * (LVL-1)^(1 + LVL/MULT)
* Where: OFFT = 30 and MULT = 200
*
* Rationale: KarmaV2 alters the offset (goes down from 50 to 30)
* and also changes the exponent so that it is higher once the
* level goes up.
*
* Screw inverse formulas, this formula is so complex that I prefer
* to build a loop that iterates until the desired level gets
* computed.
*
* TODO: Can this be memoized?
*/
if (points === 0) {
return 0;
} else if (points < 0) {
return -1 * getLevelV2(-points);
}

const offset = 30;
const mult = 200;

let pts,
level = 0;
do {
level++;
pts = Math.ceil(offset * Math.pow(level - 1, 1 + level / mult));
} while (pts <= points);

return level - 1;
}
27 changes: 27 additions & 0 deletions src/lib/member.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GuildMember, Role } from "discord.js";
import Makibot from "../Makibot";
import { getLevelV2 } from "./karma";
import Server from "./server";
import TagBag from "./tagbag";

Expand All @@ -14,6 +15,7 @@ interface KarmaStats {
points: number;
level: number;
total: number;
version: string;
}

export default class Member {
Expand Down Expand Up @@ -118,8 +120,15 @@ export default class Member {
const [total, messages, upvotes, downvotes, stars, hearts, waves] = results;
const offset = this.tagbag.tag("karma:offset").get(0);
const level = this.tagbag.tag("karma:level").get(1);
const version = this.tagbag.tag("karma:ver").get<string>("v1");
const points = offset + total;

/* Upgrade to the latest version of the karma formula. */
if (version !== "v2") {
await this.upgradeKarma();
return this.getKarma();
}

return {
downvotes,
hearts,
Expand All @@ -131,9 +140,27 @@ export default class Member {
upvotes,
waves,
total,
version,
};
}

async upgradeKarma(): Promise<void> {
const version = this.tagbag.tag("karma:ver").get<string>("v1");

if (version === "v1") {
/* Upgrade to v2. */
const total = await this.client.karma.count(this.id);
const offset = this.tagbag.tag("karma:offset").get(0);
const points = total + offset;

const levelV2 = getLevelV2(points);
await this.tagbag.tag("karma:level").set(levelV2);
await this.tagbag.tag("karma:max").set(levelV2);
await this.tagbag.tag("karma:ver").set("v2");
await this.setCrew(levelV2);
}
}

async setVerification(value: boolean): Promise<boolean> {
return this.setRole(this.server.verifiedRole, value);
}
Expand Down

0 comments on commit c3e59fc

Please sign in to comment.