Skip to content

Commit 97b67ae

Browse files
committed
Add angry coop prevention support
fix #41
1 parent 656894f commit 97b67ae

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

src/client/java/com/coflnet/CoflModClient.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,13 @@ public String getString() {
489489
return "Set max coin amount before sell protection blocks sells (e.g. 1000, 2k, 3m)";
490490
}
491491
});
492+
if("angrycoopprotectionenabled".contains(currentWord.toLowerCase()) || currentWord.equals("set"))
493+
builder.suggest("set angryCoopProtectionEnabled", new Message() {
494+
@Override
495+
public String getString() {
496+
return "Enable or disable angry co-op protection (true/false)";
497+
}
498+
});
492499
for (Settings suggestion : CoflCore.config.knownSettings) {
493500
if (suggestion.getSettingKey().toLowerCase().contains(currentWord.toLowerCase()) || currentWord.equals("set") || currentWord.equals("s")) {
494501
String settingInfo = suggestion.getSettingInfo();
@@ -564,6 +571,16 @@ public String getString() {
564571
sendChatMessage("§7Examples: 1000, 2k, 3m, 1.5b");
565572
return 1;
566573
}
574+
} else if (args[1].equals("angryCoopProtectionEnabled")) {
575+
if (args.length >= 3) {
576+
boolean enabled = args[2].equalsIgnoreCase("true") || args[2].equals("1");
577+
com.coflnet.config.AngryCoopProtectionManager.setEnabled(enabled);
578+
sendChatMessage("§aAngry Co-op Protection " + (enabled ? "enabled" : "disabled"));
579+
return 1;
580+
} else {
581+
sendChatMessage("§cUsage: /cofl set angryCoopProtectionEnabled <true/false>");
582+
return 1;
583+
}
567584
}
568585
} else if (args.length >= 1 && args[0].equals("sellprotection")) {
569586
if (args.length == 1) {
@@ -577,6 +594,14 @@ public String getString() {
577594
sendChatMessage("§7Examples: §e1000§7, §e2k§7, §e3m§7, §e1.5b");
578595
return 1;
579596
}
597+
} else if (args.length >= 1 && args[0].equals("angrycoop")) {
598+
if (args.length == 1) {
599+
com.coflnet.config.CoflModConfig config = com.coflnet.config.AngryCoopProtectionManager.getConfig();
600+
sendChatMessage("§6=== Angry Co-op Protection Settings ===");
601+
sendChatMessage("§7Enabled: " + (config.angryCoopProtectionEnabled ? "§aYes" : "§cNo"));
602+
sendChatMessage("§7Usage: §e/cofl set angryCoopProtectionEnabled <true/false>");
603+
return 1;
604+
}
580605
}
581606

582607
// Pass to CoflSkyCommand for other commands
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.coflnet.config;
2+
3+
/**
4+
* Utility class for managing the Angry Co-op protection configuration.
5+
*/
6+
public class AngryCoopProtectionManager {
7+
private static CoflModConfig config = null;
8+
9+
private static void ensureConfig() {
10+
if (config == null) {
11+
config = CoflModConfig.load();
12+
}
13+
}
14+
15+
public static void reloadConfig() {
16+
config = CoflModConfig.load();
17+
}
18+
19+
public static CoflModConfig getConfig() {
20+
ensureConfig();
21+
return config;
22+
}
23+
24+
public static boolean isEnabled() {
25+
return getConfig().angryCoopProtectionEnabled;
26+
}
27+
28+
public static void setEnabled(boolean enabled) {
29+
CoflModConfig cfg = getConfig();
30+
cfg.angryCoopProtectionEnabled = enabled;
31+
cfg.save();
32+
reloadConfig();
33+
}
34+
}

src/client/java/com/coflnet/config/CoflModConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class CoflModConfig {
1919
// Sell protection settings
2020
public boolean sellProtectionEnabled = true;
2121
public long sellProtectionThreshold = 1000000; // Default: 1 million coins
22+
23+
public boolean angryCoopProtectionEnabled = true;
2224

2325
public static CoflModConfig load() {
2426
try {
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package com.coflnet.mixin;
2+
3+
import com.coflnet.config.AngryCoopProtectionManager;
4+
import net.minecraft.client.MinecraftClient;
5+
import net.minecraft.client.gui.screen.ingame.HandledScreen;
6+
import net.minecraft.component.DataComponentTypes;
7+
import net.minecraft.entity.player.PlayerInventory;
8+
import net.minecraft.item.ItemStack;
9+
import net.minecraft.screen.slot.Slot;
10+
import net.minecraft.text.Text;
11+
import org.jetbrains.annotations.Nullable;
12+
import org.lwjgl.glfw.GLFW;
13+
import org.spongepowered.asm.mixin.Mixin;
14+
import org.spongepowered.asm.mixin.Shadow;
15+
import org.spongepowered.asm.mixin.injection.At;
16+
import org.spongepowered.asm.mixin.injection.Inject;
17+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
18+
19+
import java.util.Locale;
20+
import java.util.Optional;
21+
22+
@Mixin(HandledScreen.class)
23+
public abstract class AuctionProtectionMixin {
24+
25+
private enum ScreenMode {
26+
SELLER,
27+
BIDDER
28+
}
29+
30+
@Shadow @Nullable protected Slot focusedSlot;
31+
@Shadow public abstract @Nullable Slot getSlotAt(double x, double y);
32+
33+
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
34+
private void onAngryCoopMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
35+
try {
36+
if (!AngryCoopProtectionManager.isEnabled()) {
37+
return;
38+
}
39+
40+
if (button != 0 && button != 1) {
41+
return;
42+
}
43+
44+
MinecraftClient client = MinecraftClient.getInstance();
45+
if (client.player == null) {
46+
return;
47+
}
48+
49+
HandledScreen<?> screen = (HandledScreen<?>) (Object) this;
50+
String screenTitle = stripFormatting(screen.getTitle().getString());
51+
ScreenMode mode = determineScreenMode(screenTitle);
52+
if (mode == null) {
53+
return;
54+
}
55+
56+
Slot clickedSlot = getSlotAt(mouseX, mouseY);
57+
if (clickedSlot == null || !clickedSlot.hasStack()) {
58+
return;
59+
}
60+
61+
ItemStack clickedStack = clickedSlot.getStack();
62+
if (clickedStack.isEmpty()) {
63+
return;
64+
}
65+
66+
boolean ctrlPressed = GLFW.glfwGetKey(client.getWindow().getHandle(), GLFW.GLFW_KEY_LEFT_CONTROL) == GLFW.GLFW_PRESS
67+
|| GLFW.glfwGetKey(client.getWindow().getHandle(), GLFW.GLFW_KEY_RIGHT_CONTROL) == GLFW.GLFW_PRESS;
68+
69+
String playerNameLower = client.player.getGameProfile().getName().toLowerCase(Locale.ROOT);
70+
String clickedName = stripFormatting(clickedStack.getName().getString()).trim();
71+
72+
if (isClaimAll(clickedName)) {
73+
if (!ctrlPressed && hasForeignEntry(screen, playerNameLower, client.player.getInventory(), mode)) {
74+
client.player.sendMessage(Text.literal(getClaimAllMessage(mode)), false);
75+
cir.setReturnValue(true);
76+
}
77+
return;
78+
}
79+
80+
Optional<String> foreignActor = getForeignActor(clickedStack, playerNameLower, mode);
81+
if (foreignActor.isEmpty()) {
82+
return;
83+
}
84+
85+
if (ctrlPressed) {
86+
return;
87+
}
88+
89+
client.player.sendMessage(Text.literal(getBlockedClickMessage(mode, foreignActor.get())), false);
90+
cir.setReturnValue(true);
91+
} catch (Exception e) {
92+
System.out.println("[AuctionProtectionMixin] mouseClicked failed: " + e.getMessage());
93+
}
94+
}
95+
96+
private boolean isClaimAll(String name) {
97+
return name.trim().equalsIgnoreCase("Claim All");
98+
}
99+
100+
private ScreenMode determineScreenMode(String title) {
101+
String lower = title.toLowerCase(Locale.ROOT);
102+
if (lower.contains("manage auctions")) {
103+
return ScreenMode.SELLER;
104+
}
105+
if (lower.contains("your bids")) {
106+
return ScreenMode.BIDDER;
107+
}
108+
return null;
109+
}
110+
111+
private Optional<String> getForeignActor(ItemStack stack, String playerNameLower, ScreenMode mode) {
112+
var loreComponent = stack.get(DataComponentTypes.LORE);
113+
if (loreComponent == null) {
114+
return Optional.empty();
115+
}
116+
117+
for (Text line : loreComponent.lines()) {
118+
String raw = stripFormatting(line.getString());
119+
String lower = raw.toLowerCase(Locale.ROOT);
120+
121+
if (mode == ScreenMode.SELLER) {
122+
if (lower.contains("this is your own auction")) {
123+
return Optional.empty();
124+
}
125+
126+
if (lower.startsWith("seller:")) {
127+
if (!lower.contains(playerNameLower)) {
128+
String seller = raw.substring("Seller:".length()).trim();
129+
return Optional.of(seller.isEmpty() ? "Unknown" : seller);
130+
} else {
131+
return Optional.empty();
132+
}
133+
}
134+
} else {
135+
if (lower.startsWith("bidder:")) {
136+
if (lower.startsWith("bidder: you")) {
137+
return Optional.empty();
138+
}
139+
if (!lower.contains(playerNameLower)) {
140+
String bidder = raw.substring("Bidder:".length()).trim();
141+
return Optional.of(bidder.isEmpty() ? "Unknown" : bidder);
142+
} else {
143+
return Optional.empty();
144+
}
145+
}
146+
147+
if (lower.contains("you are the highest bidder")) {
148+
return Optional.empty();
149+
}
150+
}
151+
}
152+
153+
return Optional.empty();
154+
}
155+
156+
private boolean hasForeignEntry(HandledScreen<?> screen, String playerNameLower, PlayerInventory playerInventory, ScreenMode mode) {
157+
for (Slot slot : screen.getScreenHandler().slots) {
158+
if (slot.inventory == playerInventory) {
159+
continue;
160+
}
161+
162+
if (!slot.hasStack()) {
163+
continue;
164+
}
165+
166+
if (getForeignActor(slot.getStack(), playerNameLower, mode).isPresent()) {
167+
return true;
168+
}
169+
}
170+
return false;
171+
}
172+
173+
private String getClaimAllMessage(ScreenMode mode) {
174+
String reason = mode == ScreenMode.SELLER
175+
? "auctions listed by co-op members"
176+
: "bids placed by co-op members";
177+
return "§c[SkyCofl Angry Coop] §fBlocked Claim All because " + reason + " were detected. Hold §bCtrl§f to override.";
178+
}
179+
180+
private String getBlockedClickMessage(ScreenMode mode, String foreignName) {
181+
String action = mode == ScreenMode.SELLER ? "listed by" : "bid on by";
182+
return "§c[SkyCofl Angry Coop] §fBlocked claiming auction " + action + " §e" + foreignName + "§f. Hold §bCtrl§f to override.";
183+
}
184+
185+
private String stripFormatting(String text) {
186+
if (text == null) {
187+
return "";
188+
}
189+
190+
StringBuilder builder = new StringBuilder(text.length());
191+
boolean skip = false;
192+
for (int i = 0; i < text.length(); i++) {
193+
char c = text.charAt(i);
194+
if (c == '§') {
195+
skip = true;
196+
continue;
197+
}
198+
if (skip) {
199+
skip = false;
200+
continue;
201+
}
202+
builder.append(c);
203+
}
204+
return builder.toString();
205+
}
206+
}

src/client/resources/coflmod.client.mixins.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"NewItemInChestMixin",
1111
"ScreenMixin",
1212
"ScrollableWidgetMixin",
13+
"AuctionProtectionMixin",
1314
"SellProtectionMixin"
1415
],
1516
"injectors": {

0 commit comments

Comments
 (0)