|
17 | 17 |
|
18 | 18 | package com.lambda.module.modules.combat |
19 | 19 |
|
20 | | -import com.github.kittinunf.fuel.Fuel |
21 | | -import com.github.kittinunf.fuel.gson.responseObject |
| 20 | +import com.lambda.context.SafeContext |
22 | 21 | import com.lambda.event.events.ConnectionEvent |
23 | 22 | import com.lambda.event.events.TickEvent |
24 | 23 | import com.lambda.event.listener.SafeListener.Companion.listen |
| 24 | +import com.lambda.event.listener.SafeListener.Companion.listenConcurrently |
25 | 25 | import com.lambda.module.Module |
26 | 26 | import com.lambda.module.tag.ModuleTag |
| 27 | +import com.lambda.network.LambdaHttp |
27 | 28 | import com.lambda.threading.onShutdown |
28 | | -import com.lambda.threading.runGameScheduled |
29 | | -import com.lambda.threading.runSafe |
| 29 | +import com.lambda.util.Timer |
30 | 30 | import com.lambda.util.player.spawnFakePlayer |
31 | 31 | import com.mojang.authlib.GameProfile |
| 32 | +import io.ktor.client.call.* |
| 33 | +import io.ktor.client.request.* |
32 | 34 | import net.minecraft.client.network.OtherClientPlayerEntity |
33 | 35 | import net.minecraft.client.network.PlayerListEntry |
34 | 36 | import net.minecraft.entity.Entity |
35 | 37 | import java.util.* |
36 | | -import kotlin.concurrent.fixedRateTimer |
| 38 | +import kotlin.time.Duration.Companion.seconds |
37 | 39 |
|
38 | 40 | object FakePlayer : Module( |
39 | 41 | name = "FakePlayer", |
40 | 42 | description = "Spawns a fake player", |
41 | | - defaultTags = setOf(ModuleTag.COMBAT, ModuleTag.RENDER) |
| 43 | + defaultTags = setOf(ModuleTag.COMBAT), |
42 | 44 | ) { |
43 | 45 | private val playerName by setting("Name", "Steve") |
44 | | - private val fetchKey get() = playerName.lowercase() // Nicknames aren't case-sensitive |
45 | 46 |
|
46 | | - private var fakePlayer: OtherClientPlayerEntity? = null; set(value) { |
47 | | - runSafe { |
48 | | - field?.let { |
49 | | - world.removeEntity(it.id, Entity.RemovalReason.DISCARDED) |
50 | | - } |
51 | | - value?.let { |
52 | | - world.addEntity(it) |
53 | | - } |
54 | | - } |
| 47 | + private var fakePlayer_field: OtherClientPlayerEntity? = null |
| 48 | + private var SafeContext.fakePlayer |
| 49 | + get() = fakePlayer_field |
| 50 | + set(value) { fakePlayer_field = value; value?.let { world.addEntity(it) } } |
55 | 51 |
|
56 | | - field = value |
57 | | - } |
| 52 | + private val nilProfile: GameProfile |
| 53 | + get() = GameProfile(UUID(0, 0), playerName) |
58 | 54 |
|
59 | | - private val nilUuid = UUID(0, 0) |
60 | | - private val cachedProfiles = hashMapOf<String, GameProfile>() |
| 55 | + private val cachedProfiles = mutableMapOf<String, GameProfile>() |
| 56 | + private val fetchTimer = Timer() |
61 | 57 |
|
62 | 58 | init { |
63 | 59 | listen<TickEvent.Pre> { |
64 | | - fakePlayer = cachedProfiles[fetchKey]?.let { cached -> |
65 | | - // Keep fetched fake player |
66 | | - fakePlayer?.gameProfile?.also { profile -> |
67 | | - if (profile is FetchedGameProfile && profile.name == cached.name) return@let fakePlayer |
68 | | - } |
69 | | - |
70 | | - // Spawn fetched fake player |
71 | | - spawnFakePlayer( |
72 | | - profile = cached, |
73 | | - reference = fakePlayer ?: player, |
74 | | - addToWorld = false |
75 | | - ) |
76 | | - } ?: fakePlayer?.takeIf { it.gameProfile.name == playerName } ?: spawnFakePlayer( |
77 | | - // Spawn offline fake player while fetching |
78 | | - profile = GameProfile(nilUuid, playerName), |
79 | | - reference = fakePlayer ?: player, |
80 | | - addToWorld = false |
81 | | - ) |
| 60 | + fakePlayer = cachedProfiles[playerName] |
| 61 | + ?.let { spawnFakePlayer(it, fakePlayer ?: player, addToWorld = false) } |
| 62 | + ?.takeUnless { it == fakePlayer?.gameProfile } |
| 63 | + ?: fakePlayer?.takeIf { playerName == it.gameProfile.name } |
| 64 | + ?: spawnFakePlayer(nilProfile, fakePlayer ?: player, addToWorld = false) |
82 | 65 | } |
83 | 66 |
|
84 | | - fixedRateTimer( |
85 | | - name = "FakePlayer profile fetcher", |
86 | | - daemon = true, |
87 | | - initialDelay = 0L, |
88 | | - period = 2000L |
89 | | - ) { |
90 | | - cachedProfiles[fetchKey] ?: runSafe { |
91 | | - val (requestedProfile, _) = |
92 | | - Fuel.get("https://api.mojang.com/users/profiles/minecraft/$fetchKey") |
93 | | - .responseObject<GameProfile>().third |
| 67 | + listenConcurrently<TickEvent.Pre>(priority = 1000) { |
| 68 | + if (!fetchTimer.timePassed(2.seconds)) return@listenConcurrently |
94 | 69 |
|
95 | | - val uuid = requestedProfile?.id ?: nilUuid |
96 | | - |
97 | | - val fetchedProperties = mc.sessionService.fetchProfile(uuid, true)?.profile?.properties |
| 70 | + cachedProfiles.getOrPut(playerName) { fetchProfile(playerName) } |
| 71 | + } |
98 | 72 |
|
99 | | - val profile = FetchedGameProfile(nilUuid, playerName).apply { |
100 | | - fetchedProperties?.forEach { key, value -> properties.put(key, value) } |
101 | | - } |
| 73 | + listen<ConnectionEvent.Connect.Pre> { disable() } |
| 74 | + onShutdown { disable() } |
| 75 | + onDisable { fakePlayer?.discard(); fakePlayer = null } |
| 76 | + } |
102 | 77 |
|
103 | | - runGameScheduled { |
104 | | - // This is the cache that mc pulls profile data from when it fetches skins. |
105 | | - mc.networkHandler?.playerListEntries?.put(profile.id, PlayerListEntry(profile, false)) |
106 | | - cachedProfiles[fetchKey] = profile |
107 | | - } |
108 | | - } |
109 | | - } |
| 78 | + suspend fun SafeContext.fetchProfile(key: String): GameProfile { |
| 79 | + val requestedProfile = LambdaHttp.get("https://api.mojang.com/users/profiles/minecraft/$key") |
| 80 | + .body<GameProfile>() |
110 | 81 |
|
111 | | - onDisable { |
112 | | - fakePlayer = null |
113 | | - } |
| 82 | + // Fetch the skin properties from mojang |
| 83 | + val properties = mc.sessionService.fetchProfile(requestedProfile.id, true)?.profile?.properties |
114 | 84 |
|
115 | | - onShutdown { |
116 | | - disable() |
117 | | - } |
| 85 | + // We use the nil profile to avoid the nil username if something wrong happens |
| 86 | + // Check the GameProfile deserializer you'll understand |
| 87 | + val profile = nilProfile |
| 88 | + properties?.let { profile.properties.putAll(it) } |
118 | 89 |
|
119 | | - listen<ConnectionEvent.Connect.Pre> { |
120 | | - disable() |
121 | | - } |
| 90 | + mc.networkHandler?.playerListEntries?.put(profile.id, PlayerListEntry(profile, false)) |
122 | 91 |
|
123 | | - listen<ConnectionEvent.Disconnect> { |
124 | | - disable() |
125 | | - } |
| 92 | + return profile |
126 | 93 | } |
127 | | - |
128 | | - private class FetchedGameProfile(id: UUID, name: String) : GameProfile(id, name) |
129 | 94 | } |
0 commit comments