pala M-faction-1: socle factions + /f create/info/invite/join/leave/disband
This commit is contained in:
@@ -601,6 +601,11 @@ avant M4 (TODO:crafts-machines).
|
|||||||
- M-rtp LIVRE 2026-10-19 ~04:00 : /rtp (wiki commandes : aleatoire overworld, cooldown 15 min,
|
- M-rtp LIVRE 2026-10-19 ~04:00 : /rtp (wiki commandes : aleatoire overworld, cooldown 15 min,
|
||||||
position safe via heightmap, 16 essais, refuse ocean). ECART : pas de limite "3/version".
|
position safe via heightmap, 16 essais, refuse ocean). ECART : pas de limite "3/version".
|
||||||
VERIFIE : build SUCCESSFUL, boot Done, 0 erreur, 23 cles. UNVERIFIED en jeu.
|
VERIFIE : build SUCCESSFUL, boot Done, 0 erreur, 23 cles. UNVERIFIED en jeu.
|
||||||
|
- M-faction-1 LIVRE 2026-10-19 ~04:10 : socle factions (PalaFactions, pala-factions.json,
|
||||||
|
flush tick + arret) + /f create (100 lingots, GUI touche F infaisable — R2) / info /
|
||||||
|
invite / join / leave / disband. Lecon : Inventory.items prive en 26.3 -> getItem(i).
|
||||||
|
Grades/claims/elo : M-faction-2. VERIFIE : build SUCCESSFUL, boot Done, 0 erreur.
|
||||||
|
UNVERIFIED en jeu (2 joueurs).
|
||||||
- M-palaforge LIVRE 2026-10-19 ~03:55 : bloc + BE partagee (3e mode) + gate mineur 14 +
|
- M-palaforge LIVRE 2026-10-19 ~03:55 : bloc + BE partagee (3e mode) + gate mineur 14 +
|
||||||
table fonte wiki §3.2 (31 items, ratio floor usure) + fuels bois (+buche/planches/baton,
|
table fonte wiki §3.2 (31 items, ratio floor usure) + fuels bois (+buche/planches/baton,
|
||||||
lave exclue : seau non rendu) + craft (1 findium + 4 furnaces + 4 briques, positions
|
lave exclue : seau non rendu) + craft (1 findium + 4 furnaces + 4 briques, positions
|
||||||
|
|||||||
@@ -145,6 +145,34 @@ public final class PalaCommands {
|
|||||||
.executes(command -> balance(command.getSource())));
|
.executes(command -> balance(command.getSource())));
|
||||||
dispatcher.register(Commands.literal("rtp")
|
dispatcher.register(Commands.literal("rtp")
|
||||||
.executes(command -> rtp(command.getSource())));
|
.executes(command -> rtp(command.getSource())));
|
||||||
|
dispatcher.register(Commands.literal("f")
|
||||||
|
.then(Commands.literal("create")
|
||||||
|
.then(Commands.argument("nom",
|
||||||
|
com.mojang.brigadier.arguments.StringArgumentType.word())
|
||||||
|
.executes(command -> fCreate(command.getSource(),
|
||||||
|
com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
.getString(command, "nom")))))
|
||||||
|
.then(Commands.literal("info")
|
||||||
|
.executes(command -> fInfo(command.getSource(), null))
|
||||||
|
.then(Commands.argument("faction",
|
||||||
|
com.mojang.brigadier.arguments.StringArgumentType.word())
|
||||||
|
.executes(command -> fInfo(command.getSource(),
|
||||||
|
com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
.getString(command, "faction")))))
|
||||||
|
.then(Commands.literal("invite")
|
||||||
|
.then(Commands.argument("cible", EntityArgument.player())
|
||||||
|
.executes(command -> fInvite(command.getSource(),
|
||||||
|
EntityArgument.getPlayer(command, "cible")))))
|
||||||
|
.then(Commands.literal("join")
|
||||||
|
.then(Commands.argument("faction",
|
||||||
|
com.mojang.brigadier.arguments.StringArgumentType.word())
|
||||||
|
.executes(command -> fJoin(command.getSource(),
|
||||||
|
com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
.getString(command, "faction")))))
|
||||||
|
.then(Commands.literal("leave")
|
||||||
|
.executes(command -> fLeave(command.getSource())))
|
||||||
|
.then(Commands.literal("disband")
|
||||||
|
.executes(command -> fDisband(command.getSource()))));
|
||||||
net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents.END_SERVER_TICK
|
net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents.END_SERVER_TICK
|
||||||
.register(PalaCommands::tickSpawnQueue);
|
.register(PalaCommands::tickSpawnQueue);
|
||||||
net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents.END_SERVER_TICK
|
net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents.END_SERVER_TICK
|
||||||
@@ -289,6 +317,199 @@ public final class PalaCommands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// M-faction-1 (wiki Gestion faction) : create (100 lingots paladium, la GUI touche F
|
||||||
|
// etant infaisable pur serveur — R2), info, invite, join, leave, disband.
|
||||||
|
// Nom : 3-16 [A-Za-z0-9_] (UNVERIFIED, wiki sans spec). Grades/claims/elo : M-faction-2.
|
||||||
|
private static int fCreate(CommandSourceStack source, String nom) {
|
||||||
|
try {
|
||||||
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
|
if (!nom.matches("[A-Za-z0-9_]{3,16}")) {
|
||||||
|
source.sendFailure(Component.literal("§cNom invalide (3-16 caracteres, lettres/chiffres/_)."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fr.surviepote.pala.core.PalaFactions.get().byMember(player.getUUID().toString()) != null) {
|
||||||
|
source.sendFailure(Component.literal("§cVous etes deja dans une faction."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fr.surviepote.pala.core.PalaFactions.get().byName(nom) != null) {
|
||||||
|
source.sendFailure(Component.literal("§cCe nom est deja pris."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!takeItems(player, fr.surviepote.pala.PalaItems.PALADIUM_INGOT, 100)) {
|
||||||
|
source.sendFailure(Component.literal("§cCreation : 100 lingots de paladium requis."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fr.surviepote.pala.core.PalaFactions.Faction faction =
|
||||||
|
new fr.surviepote.pala.core.PalaFactions.Faction();
|
||||||
|
faction.name = nom;
|
||||||
|
faction.owner = player.getUUID().toString();
|
||||||
|
faction.members.add(player.getUUID().toString());
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get().put(faction);
|
||||||
|
source.getServer().getPlayerList().broadcastSystemMessage(
|
||||||
|
Component.literal("§6[Faction] §e" + player.getScoreboardName()
|
||||||
|
+ " §7a cree la faction §e" + nom + " §7!"),
|
||||||
|
false);
|
||||||
|
return Command.SINGLE_SUCCESS;
|
||||||
|
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean takeItems(ServerPlayer player, net.minecraft.world.item.Item item, int count) {
|
||||||
|
net.minecraft.world.entity.player.Inventory inventory = player.getInventory();
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 0; i < inventory.getContainerSize(); i++) {
|
||||||
|
net.minecraft.world.item.ItemStack stack = inventory.getItem(i);
|
||||||
|
if (!stack.isEmpty() && stack.is(item)) {
|
||||||
|
total += stack.getCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total < count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int rest = count;
|
||||||
|
for (int i = 0; i < inventory.getContainerSize() && rest > 0; i++) {
|
||||||
|
net.minecraft.world.item.ItemStack stack = inventory.getItem(i);
|
||||||
|
if (!stack.isEmpty() && stack.is(item)) {
|
||||||
|
int take = Math.min(rest, stack.getCount());
|
||||||
|
stack.shrink(take);
|
||||||
|
rest -= take;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int fInfo(CommandSourceStack source, String nom) {
|
||||||
|
fr.surviepote.pala.core.PalaFactions.Faction faction;
|
||||||
|
if (nom == null) {
|
||||||
|
try {
|
||||||
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
|
faction = fr.surviepote.pala.core.PalaFactions.get()
|
||||||
|
.byMember(player.getUUID().toString());
|
||||||
|
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
faction = fr.surviepote.pala.core.PalaFactions.get().byName(nom);
|
||||||
|
}
|
||||||
|
if (faction == null) {
|
||||||
|
source.sendFailure(Component.literal("§cFaction inconnue (ou vous n'en avez pas)."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
source.sendSuccess(() -> Component.literal("§6[Faction " + faction.name + "] §7"
|
||||||
|
+ faction.members.size() + " membres"
|
||||||
|
+ (faction.description.isEmpty() ? "" : " : " + faction.description)), false);
|
||||||
|
return Command.SINGLE_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int fInvite(CommandSourceStack source, ServerPlayer cible) {
|
||||||
|
try {
|
||||||
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
|
fr.surviepote.pala.core.PalaFactions.Faction faction =
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get()
|
||||||
|
.byMember(player.getUUID().toString());
|
||||||
|
if (faction == null) {
|
||||||
|
source.sendFailure(Component.literal("§cVous n'etes dans aucune faction."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!faction.owner.equals(player.getUUID().toString())) {
|
||||||
|
source.sendFailure(Component.literal("§cSeul le chef peut inviter."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fr.surviepote.pala.core.PalaFactions.get()
|
||||||
|
.byMember(cible.getUUID().toString()) != null) {
|
||||||
|
source.sendFailure(Component.literal("§cCe joueur est deja dans une faction."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!faction.invites.contains(cible.getUUID().toString())) {
|
||||||
|
faction.invites.add(cible.getUUID().toString());
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get().markDirty();
|
||||||
|
}
|
||||||
|
cible.sendSystemMessage(Component.literal("§6[Faction] §7" + player.getScoreboardName()
|
||||||
|
+ " vous invite dans " + faction.name + " : §a/f join " + faction.name));
|
||||||
|
source.sendSuccess(() -> Component.literal(
|
||||||
|
"§6[Faction] §7Invitation envoyee a " + cible.getScoreboardName() + "."), false);
|
||||||
|
return Command.SINGLE_SUCCESS;
|
||||||
|
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int fJoin(CommandSourceStack source, String nom) {
|
||||||
|
try {
|
||||||
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
|
fr.surviepote.pala.core.PalaFactions.Faction faction =
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get().byName(nom);
|
||||||
|
if (faction == null) {
|
||||||
|
source.sendFailure(Component.literal("§cFaction inconnue."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (fr.surviepote.pala.core.PalaFactions.get()
|
||||||
|
.byMember(player.getUUID().toString()) != null) {
|
||||||
|
source.sendFailure(Component.literal("§cVous etes deja dans une faction."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!faction.invites.remove(player.getUUID().toString())) {
|
||||||
|
source.sendFailure(Component.literal("§cPas d'invitation pour cette faction."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
faction.members.add(player.getUUID().toString());
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get().markDirty();
|
||||||
|
source.getServer().getPlayerList().broadcastSystemMessage(
|
||||||
|
Component.literal("§6[Faction] §e" + player.getScoreboardName()
|
||||||
|
+ " §7a rejoint §e" + faction.name + " §7!"),
|
||||||
|
false);
|
||||||
|
return Command.SINGLE_SUCCESS;
|
||||||
|
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int fLeave(CommandSourceStack source) {
|
||||||
|
try {
|
||||||
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
|
fr.surviepote.pala.core.PalaFactions.Faction faction =
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get()
|
||||||
|
.byMember(player.getUUID().toString());
|
||||||
|
if (faction == null) {
|
||||||
|
source.sendFailure(Component.literal("§cVous n'etes dans aucune faction."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (faction.owner.equals(player.getUUID().toString())) {
|
||||||
|
source.sendFailure(
|
||||||
|
Component.literal("§cLe chef doit dissoudre (/f disband) ou transferer."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
faction.members.remove(player.getUUID().toString());
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get().markDirty();
|
||||||
|
source.sendSuccess(() -> Component.literal(
|
||||||
|
"§6[Faction] §7Vous avez quitte " + faction.name + "."), false);
|
||||||
|
return Command.SINGLE_SUCCESS;
|
||||||
|
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int fDisband(CommandSourceStack source) {
|
||||||
|
try {
|
||||||
|
ServerPlayer player = source.getPlayerOrException();
|
||||||
|
fr.surviepote.pala.core.PalaFactions.Faction faction =
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get()
|
||||||
|
.byMember(player.getUUID().toString());
|
||||||
|
if (faction == null || !faction.owner.equals(player.getUUID().toString())) {
|
||||||
|
source.sendFailure(Component.literal("§cSeul le chef peut dissoudre."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fr.surviepote.pala.core.PalaFactions.get().remove(faction.name);
|
||||||
|
source.getServer().getPlayerList().broadcastSystemMessage(
|
||||||
|
Component.literal("§6[Faction] §e" + faction.name + " §7a ete dissoute."),
|
||||||
|
false);
|
||||||
|
return Command.SINGLE_SUCCESS;
|
||||||
|
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// M1 : prospecte les minerais pala dans un cube de rayon N autour du joueur.
|
// M1 : prospecte les minerais pala dans un cube de rayon N autour du joueur.
|
||||||
// Affiche comptes par minerai + histogramme Y (8 bandes de 16) pour amethyste/titane.
|
// Affiche comptes par minerai + histogramme Y (8 bandes de 16) pour amethyste/titane.
|
||||||
// Test distribution worldgen (couches 6-10 + deep ocean). Rayon capé à 32 (65^3 lectures).
|
// Test distribution worldgen (couches 6-10 + deep ocean). Rayon capé à 32 (65^3 lectures).
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public final class PalaCore {
|
|||||||
public static void register() {
|
public static void register() {
|
||||||
boolean hadFile = PalaConfig.load();
|
boolean hadFile = PalaConfig.load();
|
||||||
PalaStorage.get().load();
|
PalaStorage.get().load();
|
||||||
|
PalaFactions.get().load();
|
||||||
Pala.LOGGER.info("[SurviePote] Pala core charge (config {}).",
|
Pala.LOGGER.info("[SurviePote] Pala core charge (config {}).",
|
||||||
hadFile ? "existante" : "creee par defaut");
|
hadFile ? "existante" : "creee par defaut");
|
||||||
|
|
||||||
@@ -25,11 +26,13 @@ public final class PalaCore {
|
|||||||
if (tickCounter >= 100) {
|
if (tickCounter >= 100) {
|
||||||
tickCounter = 0;
|
tickCounter = 0;
|
||||||
PalaStorage.get().saveIfDirty();
|
PalaStorage.get().saveIfDirty();
|
||||||
|
PalaFactions.get().saveIfDirty();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
|
ServerLifecycleEvents.SERVER_STOPPING.register(server -> {
|
||||||
PalaStorage.get().save();
|
PalaStorage.get().save();
|
||||||
|
PalaFactions.get().save();
|
||||||
Pala.LOGGER.info("[SurviePote] Pala core sauvegarde ({} joueurs).",
|
Pala.LOGGER.info("[SurviePote] Pala core sauvegarde ({} joueurs).",
|
||||||
PalaStorage.get().playerCount());
|
PalaStorage.get().playerCount());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package fr.surviepote.pala.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
|
import fr.surviepote.pala.Pala;
|
||||||
|
|
||||||
|
// M-faction-1 (wiki Gestion faction) : donnees factions persistantes (pala-factions.json).
|
||||||
|
// Creation via commande (100 lingots paladium, GUI touche F infaisable pur serveur — R2).
|
||||||
|
// V1 : nom (unique, casse preservee, cle minuscule), owner, membres, description, invites.
|
||||||
|
public final class PalaFactions {
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
|
public static final class Faction {
|
||||||
|
public String name = "";
|
||||||
|
public String owner = "";
|
||||||
|
public List<String> members = new ArrayList<>();
|
||||||
|
public String description = "";
|
||||||
|
public List<String> invites = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, Faction> factions = new HashMap<>();
|
||||||
|
private boolean dirty = false;
|
||||||
|
|
||||||
|
private static final PalaFactions INSTANCE = new PalaFactions();
|
||||||
|
|
||||||
|
private PalaFactions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PalaFactions get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path file() {
|
||||||
|
return FabricLoader.getInstance().getConfigDir().resolve("pala-factions.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Faction byName(String name) {
|
||||||
|
return factions.get(name.toLowerCase(java.util.Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Faction byMember(String uuid) {
|
||||||
|
for (Faction faction : factions.values()) {
|
||||||
|
if (faction.members.contains(uuid)) {
|
||||||
|
return faction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(Faction faction) {
|
||||||
|
factions.put(faction.name.toLowerCase(java.util.Locale.ROOT), faction);
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(String name) {
|
||||||
|
if (factions.remove(name.toLowerCase(java.util.Locale.ROOT)) != null) {
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markDirty() {
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int count() {
|
||||||
|
return factions.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load() {
|
||||||
|
Path path = file();
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (Reader reader = Files.newBufferedReader(path)) {
|
||||||
|
Map<?, ?> raw = GSON.fromJson(reader, Map.class);
|
||||||
|
if (raw == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<?, ?> entry : raw.entrySet()) {
|
||||||
|
Faction faction = GSON.fromJson(GSON.toJson(entry.getValue()), Faction.class);
|
||||||
|
if (faction != null && faction.name != null && !faction.name.isEmpty()) {
|
||||||
|
if (faction.members == null) {
|
||||||
|
faction.members = new ArrayList<>();
|
||||||
|
}
|
||||||
|
if (faction.invites == null) {
|
||||||
|
faction.invites = new ArrayList<>();
|
||||||
|
}
|
||||||
|
factions.put(String.valueOf(entry.getKey()), faction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException | com.google.gson.JsonParseException e) {
|
||||||
|
Pala.LOGGER.warn("[SurviePote] pala-factions.json illisible, demarrage vide : {}", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveIfDirty() {
|
||||||
|
if (!dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dirty = false;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
try (Writer writer = Files.newBufferedWriter(file())) {
|
||||||
|
GSON.toJson(factions, writer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Pala.LOGGER.warn("[SurviePote] Ecriture pala-factions.json impossible : {}", e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user