Skip to content

Commit b081280

Browse files
HarleyGilpinGregHibEbp90
authored
Add report abuse, mute, ban and black mark systems (#1041)
* feat: add report abuse, mute, ban and black mark systems * chore: clean up pass * feat: add black marks on mute and ban Mutes add one black mark, bans two (including offline bans), so punishments accumulate toward the permanent punishment limit. Report-issued mutes record the rule broken. * fix: ban adds a single black mark Appending two identical marks in the same millisecond caused duplicate entries to be dropped. * fix: register Reports in Koin module ReportAbuse script injected Reports but no single was bound, causing NoDefinitionFoundException at startup. * Update game/src/main/kotlin/content/social/report/Mute.kt Co-authored-by: Greg <GregHib@users.noreply.github.com> * refactor: store punishments as epoch seconds Longs aren't supported by the variable format system; sub-second precision isn't needed for mutes, bans, black marks or reports. Switch them to epoch seconds (Int), with PERMANENT as Int.MAX_VALUE. Chat message evidence keeps millisecond precision. * refactor: rename permban command to perm_ban for consistency * Fix type mismatch on time * Stop force logout from reconnecting --------- Co-authored-by: Greg <GregHib@users.noreply.github.com> Co-authored-by: Ebp90 <40249395+Ebp90@users.noreply.github.com>
1 parent 3203ce5 commit b081280

32 files changed

Lines changed: 1140 additions & 14 deletions

File tree

data/entity/player/dialogue/dialogue.ifaces.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ id = 5
258258
[.close_quick_chat]
259259
id = 4
260260

261+
[.chat_line0]
262+
id = "180-279"
263+
261264
[dialogue_select2_models]
262265
id = 140
263266
type = "dialogue_box"

data/entity/player/modal/chat_box/chat_box.ifaces.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ type = "chat_box"
3434
id = 754
3535
type = "private_chat"
3636

37+
[.line1]
38+
id = "1-5"
39+

data/social/report/report.ifaces.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@ id = 110
1313
[report_abuse]
1414
id = 594
1515

16+
[.mute_confirm]
17+
id = 8
18+
19+
[.mute_entry]
20+
id = 52
21+
22+
[.mute_select]
23+
id = 66
24+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[muted_until]
2+
format = "int"
3+
persist = true
4+
5+
[banned_until]
6+
format = "int"
7+
persist = true
8+
9+
[black_marks]
10+
persist = true

database/src/main/kotlin/world/gregs/voidps/storage/DatabaseStorage.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.zaxxer.hikari.HikariDataSource
55
import org.jetbrains.exposed.sql.*
66
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
77
import org.jetbrains.exposed.sql.transactions.transaction
8+
import world.gregs.voidps.engine.data.AbuseReport
89
import world.gregs.voidps.engine.data.PlayerSave
910
import world.gregs.voidps.engine.data.Storage
1011
import world.gregs.voidps.engine.data.config.AccountDefinition
@@ -200,6 +201,19 @@ class DatabaseStorage : Storage {
200201
saveHistories(accounts, playerIds)
201202
}
202203

204+
override fun saveReport(report: AbuseReport): Unit = transaction {
205+
ReportsTable.insert {
206+
it[reporter] = report.reporter
207+
it[reported] = report.reported
208+
it[rule] = report.rule
209+
it[ruleName] = report.ruleName
210+
it[mute] = report.mute
211+
it[suggestion] = report.suggestion
212+
it[time] = report.time
213+
it[evidence] = report.evidence
214+
}
215+
}
216+
203217
override fun exists(accountName: String): Boolean = transaction {
204218
val lower = accountName.lowercase()
205219
AccountsTable
@@ -565,7 +579,7 @@ class DatabaseStorage : Storage {
565579
}
566580
}
567581

568-
internal val tables = arrayOf(AccountsTable, ExperienceTable, LevelsTable, VariablesTable, InventoriesTable, OffersTable, ActiveOffersTable, PlayerHistoryTable, ClaimsTable, ItemHistoryTable)
582+
internal val tables = arrayOf(AccountsTable, ExperienceTable, LevelsTable, VariablesTable, InventoriesTable, OffersTable, ActiveOffersTable, PlayerHistoryTable, ClaimsTable, ItemHistoryTable, ReportsTable)
569583

570584
private const val TYPE_STRING = 0.toByte()
571585
private const val TYPE_INT = 1.toByte()

database/src/main/kotlin/world/gregs/voidps/storage/Tables.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,20 @@ internal object ClaimsTable : Table("grand_exchange_claims") {
157157
val coins = integer("coins")
158158
}
159159

160+
internal object ReportsTable : Table("abuse_reports") {
161+
val id = integer("id").autoIncrement().uniqueIndex()
162+
val reporter = varchar("reporter", 12)
163+
val reported = text("reported")
164+
val rule = integer("rule")
165+
val ruleName = text("rule_name")
166+
val mute = bool("mute")
167+
val suggestion = text("suggestion")
168+
val time = integer("time")
169+
val evidence = array<String>("evidence")
170+
171+
override val primaryKey = PrimaryKey(id, name = "pk_report_id")
172+
}
173+
160174
internal object ItemHistoryTable : Table("grand_exchange_item_history") {
161175
val item = text("item")
162176
val timestamp = long("timestamp")

database/src/test/kotlin/world/gregs/voidps/storage/DatabaseStorageTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,41 @@ import org.jetbrains.exposed.sql.selectAll
55
import org.jetbrains.exposed.sql.transactions.transaction
66
import org.junit.jupiter.api.Test
77
import org.junit.jupiter.api.assertThrows
8+
import world.gregs.voidps.engine.data.AbuseReport
9+
import kotlin.test.assertEquals
810

911
class DatabaseStorageTest : StorageTest(), DatabaseTest {
1012

1113
override val storage = DatabaseStorage()
1214

15+
@Test
16+
fun `Store an abuse report`() {
17+
val report = AbuseReport(
18+
reporter = "mod_steve",
19+
reported = "Durial321",
20+
rule = 6,
21+
ruleName = "Macroing",
22+
mute = true,
23+
suggestion = "extra info",
24+
time = 1234567890,
25+
evidence = listOf("[00:00:01] public: free armour trimming", "[00:00:02] public: selling gf"),
26+
)
27+
28+
storage.saveReport(report)
29+
30+
transaction {
31+
val row = ReportsTable.selectAll().single()
32+
assertEquals(report.reporter, row[ReportsTable.reporter])
33+
assertEquals(report.reported, row[ReportsTable.reported])
34+
assertEquals(report.rule, row[ReportsTable.rule])
35+
assertEquals(report.ruleName, row[ReportsTable.ruleName])
36+
assertEquals(report.mute, row[ReportsTable.mute])
37+
assertEquals(report.suggestion, row[ReportsTable.suggestion])
38+
assertEquals(report.time, row[ReportsTable.time])
39+
assertEquals(report.evidence, row[ReportsTable.evidence])
40+
}
41+
}
42+
1343
@Test
1444
fun `Saving variable with invalid format throws exception`() {
1545
assertThrows<IllegalArgumentException> {

engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ class PlayerAccountLoader(
5555
client.disconnect(Response.GAME_UPDATE)
5656
return null
5757
}
58-
var player = storage.load(username)?.toPlayer()
58+
val save = storage.load(username)
59+
if (save != null && banned(save.variables)) {
60+
client.disconnect(Response.ACCOUNT_DISABLED)
61+
return null
62+
}
63+
var player = save?.toPlayer()
5964
if (player == null) {
6065
if (!Settings["development.accountCreation", false]) {
6166
client.disconnect(Response.INVALID_CREDENTIALS)
@@ -73,6 +78,11 @@ class PlayerAccountLoader(
7378
}
7479
}
7580

81+
private fun banned(variables: Map<String, Any>): Boolean {
82+
val until = (variables["banned_until"] as? Number)?.toInt() ?: return false
83+
return until > (System.currentTimeMillis() / 1000).toInt()
84+
}
85+
7686
suspend fun connect(player: Player, client: Client, displayMode: Int = 0, viewport: Boolean = true) {
7787
if (!accounts.setup(player, client, displayMode, viewport)) {
7888
logger.warn { "Error setting up account" }

engine/src/main/kotlin/world/gregs/voidps/engine/client/instruction/InstructionHandlers.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class InstructionHandlers(
5050
var chatTypeChangeHandler: ChatTypeChange.(Player) -> Unit = empty()
5151
var clanChatKickHandler: ClanChatKick.(Player) -> Unit = empty()
5252
var clanChatRankHandler: ClanChatRank.(Player) -> Unit = empty()
53+
var reportAbuseHandler: ReportAbuse.(Player) -> Unit = empty()
5354

5455
private fun <I : Instruction> empty(): I.(Player) -> Unit {
5556
val logger = InlineLogger("InstructionHandler")
@@ -98,6 +99,7 @@ class InstructionHandlers(
9899
is ChatTypeChange -> chatTypeChangeHandler.invoke(instruction, player)
99100
is ClanChatKick -> clanChatKickHandler.invoke(instruction, player)
100101
is ClanChatRank -> clanChatRankHandler.invoke(instruction, player)
102+
is ReportAbuse -> reportAbuseHandler.invoke(instruction, player)
101103
is SongEnd -> songEndHandler.invoke(instruction, player)
102104
else -> return false
103105
}
@@ -132,6 +134,7 @@ inline fun <reified I : Instruction> instruction(noinline handler: I.(Player) ->
132134
ChatTypeChange::class -> get<InstructionHandlers>().chatTypeChangeHandler = handler as ChatTypeChange.(Player) -> Unit
133135
ClanChatKick::class -> get<InstructionHandlers>().clanChatKickHandler = handler as ClanChatKick.(Player) -> Unit
134136
ClanChatRank::class -> get<InstructionHandlers>().clanChatRankHandler = handler as ClanChatRank.(Player) -> Unit
137+
ReportAbuse::class -> get<InstructionHandlers>().reportAbuseHandler = handler as ReportAbuse.(Player) -> Unit
135138
else -> throw UnsupportedOperationException("Unknown Instruction type: ${I::class}")
136139
}
137140
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package world.gregs.voidps.engine.data
2+
3+
/**
4+
* A player submitted report about another player breaking a rule
5+
* @param reporter Account name of the player filing the report
6+
* @param reported Display name of the accused player as submitted
7+
* @param rule Identifier of the rule broken
8+
* @param ruleName Readable name of the rule broken
9+
* @param mute Whether a moderator requested the accused be muted
10+
* @param suggestion Additional text submitted with the report
11+
* @param time Epoch second timestamp the report was received
12+
* @param evidence Recent chat messages sent by the accused player
13+
*/
14+
data class AbuseReport(
15+
val reporter: String,
16+
val reported: String,
17+
val rule: Int,
18+
val ruleName: String,
19+
val mute: Boolean,
20+
val suggestion: String,
21+
val time: Int,
22+
val evidence: List<String>,
23+
)

0 commit comments

Comments
 (0)