u/Ok_Swimmer3087

Patapon 3 Overhaul Cannogabang Build

I want to transition into cannogabang after finally reaching lvl32 grenburr, still my grenburr deals a ton of damage 1-137k range but the problem is he barely lands his guillotine cause he gets caught on fire every 3 seconds and just stops, I just can't farm the Bonedeth on the Cliff lvl14 mission anymore because of this. So i tried using cannogabang and made him reach level 25, but It feels like he lacks the damage now, since his monster hunter skill set has been changed and now I don't know where to get more damage for him, I use the ice howitzer(Godlike) lvl 23, peerless bovine, artillery attack, flame master, then 2 any (greatsword, horm) attack skill set additional damage even just half. What else should I consider using/getting? or should I just switch hero or is there a much more optimal mission to farm exp and chests?

reddit.com
u/Ok_Swimmer3087 — 3 days ago

Patapon 3 Mods

I have played patapon 3 in my phone using PPSSPP before and I enjoyed the game quite a lot, and this time I revisited it and was reminded on how grindy this game is. But I think its time for me to step it up a bit and play one of the modded versions for a new experience. Currently there was the DxD and Overhaul mod, I don't know the difference between them other than the DxD mod has this other world where it spawns so many monsters and Overhaul adds a lot a stuff but which one should I play? I just enjoy playing the single player stuff mostly and the diminishing painful grind for exp for leveling up the better. I don't know how install the mods since this is quite new to me

reddit.com
u/Ok_Swimmer3087 — 8 days ago

Supabase Realtime Presence creating duplicate entries per user (React 18 StrictMode?)

Running into a weird bug with Supabase Realtime Presence in a React + TypeScript app and want to sanity-check my theory before I ship a fix.

Setup: I have a useRoomChannel hook that subscribes to a presence channel keyed by user_id, tracks { user_id, username, ready } on subscribe, and lets users toggle their ready status by calling .track() again with the updated value.

import useAuth from "@/hooks/useAuth"
import { supabase } from "@/lib/supabase"
import { useFetchRoomQuery } from "@/store/api/supabaseApi"
import type {
  GroupedMessages,
  PlayerPressence,
  RoomMessage,
} from "@/types/roomTypes"
import type { RealtimeChannel } from "@supabase/supabase-js"
import { useEffect, useMemo, useRef, useState } from "react"
import { useNavigate } from "react-router-dom"
import { toast } from "sonner"

const groupMessages = (messages: RoomMessage[]) => {
  let groupedMessages: GroupedMessages[] = []
  let group: GroupedMessages = { user_id: "", username: "", messages: [] }

  messages.forEach((m) => {
    if (group.user_id === m.user_id) {
      group.messages.push(m.message)
    } else {
      if (group.messages.length > 0) groupedMessages.push(group)
      group = {
        user_id: m.user_id,
        username: m.username,
        messages: [m.message],
      }
    }
  })
  if (group.messages.length > 0) groupedMessages.push(group)

  return groupedMessages
}

const useRoomChannel = (roomId: string) => {
  const { auth } = useAuth()
  const { data: room, isLoading } = useFetchRoomQuery(roomId!)
  const [players, setPlayers] = useState<PlayerPressence[]>([])
  const [messages, setMessages] = useState<RoomMessage[]>([])
  const [channel, setChannel] = useState<RealtimeChannel | null>(null)
  const navigate = useNavigate()
  const readyRef = useRef(false)

  const isHost = auth.user_id === room?.host
  const self = useMemo(() => {
    return players.find((p) => p.user_id === auth.user_id)
  }, [players, auth.user_id])

  //Realtime Pressence
  useEffect(() => {
    if (isLoading) return

    const roomChannel = supabase.channel(`room:${roomId}`, {
      config: { presence: { key: auth.user_id! } },
    })

    roomChannel
      .on("presence", { event: "sync" }, () => {
        const state = roomChannel.presenceState<PlayerPressence>()
        console.log(state)
        const players = Object.values(state).map((data) => data[0])
        setPlayers(players)
      })
      .on("presence", { event: "join" }, ({ newPresences }) => {
        console.log("New presence: ", newPresences)
      })
      .on("presence", { event: "leave" }, ({ leftPresences }) => {
        console.log("Left Presences: ", leftPresences)
      })
      .on("broadcast", { event: "chat_message" }, ({ payload }) => {
        setMessages((prev) => [...prev, payload])
      })
      .on("broadcast", { event: "game_start" }, () => {
        navigate(`/room/${roomId}/game`)
      })
      .on("broadcast", { event: "close_room" }, () => {
        navigate(`/`)
      })
      .subscribe(async (status) => {
        if (status === "SUBSCRIBED") {
          await roomChannel.track({
            user_id: auth.user_id,
            username: auth.username,
            ready: isHost ? true : readyRef.current,
          })
        }
      })

    setChannel(roomChannel)

    return () => {
      supabase.removeChannel(roomChannel)
    }
  }, [isLoading, roomId, auth.user_id])


  const sendMessage = async (content: string) => {
    if (!channel || !content.trim()) return

    const message: RoomMessage = {
      user_id: auth.user_id!,
      username: auth.username!,
      message: content,
    }

    await channel.send({
      type: "broadcast",
      event: "chat_message",
      payload: message,
    })

    setMessages((prev) => [...prev, message])
  }


  const closeRoom = async () => {
    try {
      if (isHost) {
        const { error } = await supabase
          .from("rooms")
          .delete()
          .eq("room_id", roomId!)
        if (error) throw error

        await channel?.send({
          type: "broadcast",
          event: "close_room",
        })
      }

      navigate("/")
    } catch (error) {
      toast.error("Error: cannot close the room")
    }
  }

  const gameStart = async () => {
    await channel?.send({
      type: "broadcast",
      event: "game_start",
    })

    navigate(`/room/${roomId}/game`)
  }

  const toggleStatus = async () => {
    await channel?.track({
      user_id: auth.user_id,
      username: auth.username,
      ready: !self?.ready,
    })

    readyRef.current = !self?.ready
  }

  const groupedMessages = useMemo(() => groupMessages(messages), [messages])

  return {
    self,
    room,
    isHost,
    players,
    messages: groupedMessages,
    loadingRoom: isLoading,
    sendMessage,
    gameStart,
    closeRoom,
    toggleStatus,
  }
}

export default useRoomChannel

The bug: Instead of updating the existing presence entry, track() sometimes creates a second entry for the same user_id, each with a different presence_ref:

"5a1156a7-e9d6-40bc-9cb0-132019cf2fd0": [
  { "ready": false, "user_id": "5a1156a7...", "username": "Gordon_896" },
  { "ready": true,  "user_id": "5a1156a7...", "username": "Gordon_896" }
]

After that, my UI only reflects one of the two entries, and toggling status stops working reliably for that user.

Questions:

  1. Is this actually a known StrictMode + Realtime interaction, or is there something else going on?
  2. Is a module-level channel registry (dedupe/refcount channels by topic outside the component, so StrictMode's phantom mount/cleanup resolves synchronously before the "real" one subscribes) the recommended pattern here, or is there a simpler/more idiomatic Supabase-native way to handle this?
  3. Has anyone confirmed this does NOT happen in production builds (no StrictMode), or is there a similar race that can still bite you outside of dev?

Any pointers from people who've dealt with Presence + React lifecycle quirks would be much appreciated.

reddit.com
u/Ok_Swimmer3087 — 21 days ago