Last active
May 7, 2026 09:35
-
-
Save vaakx-dev/564c2cf10d291e3a643324e5908e3031 to your computer and use it in GitHub Desktop.
override the ui.playerChip and get icons from gits to show player pfps not rank
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // arcon-manifest: | |
| // name: player-pfp-chips | |
| // permissions: [game, util, storage, chat, net, hud, ui, ui.player_chip] | |
| // | |
| // player pfps - peer-to-peer 64x64 player icons over the istrolid "pfp" chat channel | |
| // - upload via right-click your own chip -> Set my icon -> file picker | |
| // - icons broadcast on server join + on demand (REQ from late joiners) | |
| // - block per-name to drop incoming icons and refuse to display cached | |
| (function () { | |
| if (!window.arcon) { | |
| console.error("player_pfps: arcon not found, load arcon.js first"); | |
| return; | |
| } | |
| if (!arcon.require({ major: 1, minor: 4 })) return; | |
| if (!arcon.ui_module) { | |
| console.error("player_pfps: arcon-ui not loaded; chip/chat surfaces unavailable"); | |
| return; | |
| } | |
| var CHANNEL = "pfp"; | |
| var MAX_DIM = 64; | |
| var MAX_CACHE = 800; | |
| var REPLY_THROTTLE_MS = 30000; | |
| var REQ_PREFIX = "REQ "; | |
| var DATAURL_PREFIX = "data:image/"; | |
| var BROADCAST_DELAY_MS = 1000; | |
| var REQUEST_DELAY_MS = 2500; | |
| var store = arcon.storage.namespace("pfp"); | |
| var cache = store.get("cache", {}); | |
| var my_icon = store.get("my_icon", null); | |
| var blocked = store.get("blocked", []); | |
| var blocked_set = {}; | |
| for (var i = 0; i < blocked.length; i++) blocked_set[blocked[i]] = true; | |
| var last_reply_at = {}; | |
| var seen_hashes = {}; | |
| function is_blocked(name) { return !!blocked_set[name]; } | |
| function set_blocked(name, val) { | |
| if (val) { | |
| if (!blocked_set[name]) { | |
| blocked.push(name); | |
| blocked_set[name] = true; | |
| delete cache[name]; | |
| store.set("cache", cache); | |
| } | |
| } else { | |
| delete blocked_set[name]; | |
| var idx = blocked.indexOf(name); | |
| if (idx >= 0) blocked.splice(idx, 1); | |
| } | |
| store.set("blocked", blocked); | |
| } | |
| function save_my_icon(dataurl) { | |
| my_icon = dataurl; | |
| store.set("my_icon", dataurl); | |
| } | |
| function clear_my_icon() { | |
| my_icon = null; | |
| store.del("my_icon"); | |
| var cmd = arcon.game.commander; | |
| if (cmd && cmd.name) { | |
| delete cache[cmd.name]; | |
| store.set("cache", cache); | |
| } | |
| } | |
| function save_to_cache(name, dataurl) { | |
| store.lru_set("cache", name, dataurl, MAX_CACHE); | |
| cache = store.get("cache", {}); | |
| } | |
| var ch = arcon.chat.channel(CHANNEL); | |
| function broadcast_my_icon() { | |
| if (!my_icon) return; | |
| ch.send(my_icon); | |
| } | |
| function get_known_player_names() { | |
| var players = arcon.game.players(); | |
| var names = []; | |
| for (var i = 0; i < players.length; i++) { | |
| var p = players[i]; | |
| if (p && p.name && p.name !== "Server") names.push(p.name); | |
| } | |
| return names; | |
| } | |
| function request_unknown_pfps() { | |
| var cmd = arcon.game.commander; | |
| if (!cmd) return; | |
| var unknown = []; | |
| var names = get_known_player_names(); | |
| for (var i = 0; i < names.length; i++) { | |
| var n = names[i]; | |
| if (n === cmd.name) continue; | |
| if (cache[n]) continue; | |
| if (is_blocked(n)) continue; | |
| unknown.push(n); | |
| } | |
| if (unknown.length === 0) return; | |
| ch.send(REQ_PREFIX + unknown.join(",")); | |
| } | |
| ch.on_message(function (msg) { | |
| if (!msg || !msg.name) return; | |
| var cmd = arcon.game.commander; | |
| if (!cmd) return; | |
| if (msg.name === cmd.name) return; | |
| var text = msg.text || ""; | |
| if (text.indexOf(REQ_PREFIX) === 0) { | |
| if (is_blocked(msg.name)) return; | |
| var requested = text.slice(REQ_PREFIX.length).split(","); | |
| if (requested.indexOf(cmd.name) === -1) return; | |
| if (!my_icon) return; | |
| var now = Date.now(); | |
| if (last_reply_at[msg.name] && now - last_reply_at[msg.name] < REPLY_THROTTLE_MS) return; | |
| last_reply_at[msg.name] = now; | |
| ch.send(my_icon); | |
| return; | |
| } | |
| if (text.indexOf(DATAURL_PREFIX) === 0) { | |
| if (is_blocked(msg.name)) return; | |
| var h = arcon.util.hash(text); | |
| if (seen_hashes[h] && cache[msg.name] === text) return; | |
| seen_hashes[h] = true; | |
| arcon.util.resize_image(text, MAX_DIM).then(function (normalized) { | |
| save_to_cache(msg.name, normalized); | |
| arcon.ui.refresh(); | |
| }).catch(function () {}); | |
| } | |
| }); | |
| arcon.net.on_join_server(function () { | |
| setTimeout(broadcast_my_icon, BROADCAST_DELAY_MS); | |
| setTimeout(request_unknown_pfps, REQUEST_DELAY_MS); | |
| }); | |
| var chip = arcon.ui_module.player_chip; | |
| chip.add_icon_provider(function (player) { | |
| var cmd = arcon.game.commander; | |
| if (cmd && cmd.fleet && cmd.fleet.pfp) { | |
| var local = cmd.fleet.pfp[player.name]; | |
| if (local) return local; | |
| } | |
| if (is_blocked(player.name)) return null; | |
| return cache[player.name] || null; | |
| }); | |
| chip.add_menu_item({ | |
| label_fn: function () { return my_icon ? "Change my icon" : "Set my icon"; }, | |
| show_for: "self", | |
| on_click: function () { open_upload_picker(); } | |
| }); | |
| chip.add_menu_item({ | |
| label_fn: function () { return "Remove my icon"; }, | |
| show_for: "self", | |
| is_visible: function () { return !!my_icon; }, | |
| on_click: function () { | |
| clear_my_icon(); | |
| arcon.ui.menu.close(); | |
| } | |
| }); | |
| chip.add_menu_item({ | |
| label_fn: function (p) { return is_blocked(p.name) ? "Unblock pfp" : "Block pfp"; }, | |
| show_for: "others", | |
| on_click: function (p) { | |
| set_blocked(p.name, !is_blocked(p.name)); | |
| arcon.ui.menu.close(); | |
| } | |
| }); | |
| function open_upload_picker() { | |
| arcon.ui.menu.close(); | |
| arcon.ui.file_picker({ | |
| accept: "image/*", | |
| on_pick: function (dataurl) { | |
| arcon.util.resize_image(dataurl, MAX_DIM).then(function (resized) { | |
| show_preview(resized); | |
| }).catch(function () { | |
| console.error("player_pfps: could not load image"); | |
| }); | |
| } | |
| }); | |
| } | |
| function show_preview(dataurl) { | |
| arcon.ui.modal({ | |
| title: "Set your icon", | |
| width: 240, | |
| body_fn: function () { | |
| var d = arcon.ui.dom(); | |
| d.div(function () { | |
| d.text_align("center"); | |
| d.padding(10); | |
| d.img({ src: dataurl, width: 64, height: 64 }, function () { | |
| d.background_color("rgba(255,255,255,.05)"); | |
| }); | |
| }); | |
| }, | |
| confirm_label: "Use this", | |
| on_confirm: function () { | |
| save_my_icon(dataurl); | |
| var cmd = arcon.game.commander; | |
| if (cmd && cmd.name) save_to_cache(cmd.name, dataurl); | |
| broadcast_my_icon(); | |
| arcon.ui.refresh(); | |
| } | |
| }); | |
| } | |
| arcon.hud.register("player_pfps", function () {}, { | |
| mode: "any", | |
| config: { | |
| max_dim: MAX_DIM, | |
| max_cache: MAX_CACHE, | |
| reply_throttle_ms: REPLY_THROTTLE_MS, | |
| channel: CHANNEL | |
| } | |
| }); | |
| }.call(this)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment