📊 Add scoreboard and played time.
This commit is contained in:
@@ -3,7 +3,7 @@ import React from "@rbxts/react";
|
|||||||
import { StrictMode } from "@rbxts/react";
|
import { StrictMode } from "@rbxts/react";
|
||||||
import { createPortal, createRoot } from "@rbxts/react-roblox";
|
import { createPortal, createRoot } from "@rbxts/react-roblox";
|
||||||
import { Players, StarterGui } from "@rbxts/services";
|
import { Players, StarterGui } from "@rbxts/services";
|
||||||
import PlayersBoard from "shared/gui/players_board";
|
import PlayersBoard from "client/gui/players_board";
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
class GuiController implements OnStart {
|
class GuiController implements OnStart {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { lerp, useMotion } from "@rbxts/pretty-react-hooks";
|
import { lerp, useKeyPress, useMotion } from "@rbxts/pretty-react-hooks";
|
||||||
import React, { Component, ReactNode, StrictMode, useEffect, useState } from "@rbxts/react";
|
import React, { Component, ReactNode, StrictMode, useEffect, useState } from "@rbxts/react";
|
||||||
import { Players } from "@rbxts/services";
|
import { Players } from "@rbxts/services";
|
||||||
import Profile from "./profile";
|
import Profile from "./profile";
|
||||||
@@ -55,6 +55,9 @@ function PlayerEntry({
|
|||||||
export default function PlayersBoard() {
|
export default function PlayersBoard() {
|
||||||
const [players, setPlayers] = useState<Player[]>(Players.GetPlayers());
|
const [players, setPlayers] = useState<Player[]>(Players.GetPlayers());
|
||||||
const [selectedPlayer, setSelectedPlayer] = useState<Player>();
|
const [selectedPlayer, setSelectedPlayer] = useState<Player>();
|
||||||
|
const [activated, setActivated] = useState(true);
|
||||||
|
const tabPressed = useKeyPress(["Tab"]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const playerAdded = Players.PlayerAdded.Connect(() => {
|
const playerAdded = Players.PlayerAdded.Connect(() => {
|
||||||
setPlayers(Players.GetPlayers());
|
setPlayers(Players.GetPlayers());
|
||||||
@@ -68,6 +71,10 @@ export default function PlayersBoard() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tabPressed) setActivated(!activated);
|
||||||
|
}, [tabPressed]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<frame
|
<frame
|
||||||
@@ -75,6 +82,7 @@ export default function PlayersBoard() {
|
|||||||
Position={UDim2.fromScale(0.8, 0.1)}
|
Position={UDim2.fromScale(0.8, 0.1)}
|
||||||
BackgroundColor3={new Color3(0, 0.72, 1)}
|
BackgroundColor3={new Color3(0, 0.72, 1)}
|
||||||
BackgroundTransparency={0.7}
|
BackgroundTransparency={0.7}
|
||||||
|
Visible={activated}
|
||||||
>
|
>
|
||||||
<frame
|
<frame
|
||||||
BorderColor3={new Color3(1, 1, 1)}
|
BorderColor3={new Color3(1, 1, 1)}
|
||||||
@@ -1,25 +1,47 @@
|
|||||||
import React, { useEffect, useRef, useState } from "@rbxts/react";
|
import React, { useEffect, useRef, useState } from "@rbxts/react";
|
||||||
import { LocalizationService, Players } from "@rbxts/services";
|
import { LocalizationService, Players, RunService } from "@rbxts/services";
|
||||||
|
import { ProfileClientEvents } from "client/networking";
|
||||||
|
|
||||||
function TimePlayed({ targetTime }: { targetTime: number }) {
|
function TimePlayed({
|
||||||
const [time, setTime] = useState();
|
targetTime,
|
||||||
const [id, setId] = useState<DateTime>();
|
reeloffset,
|
||||||
|
timeOrigin,
|
||||||
|
}: {
|
||||||
|
targetTime: number;
|
||||||
|
timeOrigin: number;
|
||||||
|
reeloffset: number;
|
||||||
|
}) {
|
||||||
|
const [time, setTime] = useState<number>(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let killed = false;
|
const connection = RunService.PreRender.Connect(() => {
|
||||||
setId(DateTime.fromUnixTimestamp(targetTime));
|
setTime(targetTime + os.time() - timeOrigin);
|
||||||
task.spawn(() => {
|
|
||||||
while (true) {
|
|
||||||
if (killed) return;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return () => {
|
return () => connection.Disconnect();
|
||||||
killed = true;
|
|
||||||
};
|
|
||||||
}, [targetTime]);
|
}, [targetTime]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<textlabel
|
||||||
|
Text={
|
||||||
|
targetTime === -1
|
||||||
|
? "XX:XX:XX"
|
||||||
|
: DateTime.fromUnixTimestamp(time).FormatUniversalTime(
|
||||||
|
"HH:mm:ss",
|
||||||
|
LocalizationService.RobloxLocaleId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Size={new UDim2(1, -reeloffset, 0.3, 0)}
|
||||||
|
BackgroundTransparency={1}
|
||||||
|
TextScaled={true}
|
||||||
|
Position={new UDim2(0, reeloffset, 0.45, 0)}
|
||||||
|
TextColor3={new Color3(0, 0, 0)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Profile({ player }: { player: Player }) {
|
export default function Profile({ player }: { player: Player }) {
|
||||||
const [userIcon, setUserIcon] = useState<string>();
|
const [userIcon, setUserIcon] = useState<string>();
|
||||||
|
const [timePlayed, setTimePlayed] = useState<number>(-1);
|
||||||
|
const [timeOrigin, setTimeOrigin] = useState<number>(-1);
|
||||||
const ref = useRef<ImageLabel>();
|
const ref = useRef<ImageLabel>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const [image] = Players.GetUserThumbnailAsync(
|
const [image] = Players.GetUserThumbnailAsync(
|
||||||
@@ -28,6 +50,12 @@ export default function Profile({ player }: { player: Player }) {
|
|||||||
Enum.ThumbnailSize.Size180x180,
|
Enum.ThumbnailSize.Size180x180,
|
||||||
);
|
);
|
||||||
setUserIcon(image);
|
setUserIcon(image);
|
||||||
|
if (RunService.IsRunning()) {
|
||||||
|
ProfileClientEvents.getPlayerStartTime(player.UserId).then((value) => {
|
||||||
|
setTimePlayed(value[0]);
|
||||||
|
setTimeOrigin(value[1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [player]);
|
}, [player]);
|
||||||
const reeloffset = ref.current?.AbsoluteSize.X ?? 0;
|
const reeloffset = ref.current?.AbsoluteSize.X ?? 0;
|
||||||
return (
|
return (
|
||||||
@@ -72,14 +100,7 @@ export default function Profile({ player }: { player: Player }) {
|
|||||||
Position={new UDim2(0, reeloffset, 0.25, 0)}
|
Position={new UDim2(0, reeloffset, 0.25, 0)}
|
||||||
TextColor3={new Color3(0.13, 0.13, 0.13)}
|
TextColor3={new Color3(0.13, 0.13, 0.13)}
|
||||||
/>
|
/>
|
||||||
<textlabel
|
<TimePlayed reeloffset={reeloffset} targetTime={timePlayed} timeOrigin={timeOrigin} />
|
||||||
Text={DateTime.fromUnixTimestamp().FormatLocalTime("LTS", LocalizationService.RobloxLocaleId)}
|
|
||||||
Size={new UDim2(1, -reeloffset, 0.3, 0)}
|
|
||||||
BackgroundTransparency={1}
|
|
||||||
TextScaled={true}
|
|
||||||
Position={new UDim2(0, reeloffset, 0.45, 0)}
|
|
||||||
TextColor3={new Color3(0, 0, 0)}
|
|
||||||
/>
|
|
||||||
<textlabel
|
<textlabel
|
||||||
Text={`Lev : ${0}`}
|
Text={`Lev : ${0}`}
|
||||||
Size={new UDim2(1, -reeloffset, 0.3, 0)}
|
Size={new UDim2(1, -reeloffset, 0.3, 0)}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
import { GameplayEvents } from "shared/networking";
|
import { GameplayEvents, ProfileEvents } from "shared/networking";
|
||||||
|
|
||||||
export const GameplayClientEvents = GameplayEvents.createClient({});
|
export const GameplayClientEvents = GameplayEvents.createClient({});
|
||||||
|
export const ProfileClientEvents = ProfileEvents.createClient({});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
import { GameplayEvents } from "shared/networking";
|
import { GameplayEvents, ProfileEvents } from "shared/networking";
|
||||||
|
|
||||||
export const GameplayServerEvents = GameplayEvents.createServer({});
|
export const GameplayServerEvents = GameplayEvents.createServer({});
|
||||||
|
export const ProfileServerEvents = ProfileEvents.createServer({});
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
import { Modding, OnStart, Service } from "@flamework/core";
|
import { Modding, OnStart, Service } from "@flamework/core";
|
||||||
import { Players } from "@rbxts/services";
|
import { Players } from "@rbxts/services";
|
||||||
import { OnPlayerJoined } from "shared/modding/player_events";
|
import { OnPlayerJoined, OnPlayerQuit } from "shared/modding/player_events";
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
class PlayerJoinService implements OnStart {
|
class PlayerJoinService implements OnStart {
|
||||||
onStart() {
|
onStart() {
|
||||||
const listeners = new Set<OnPlayerJoined>();
|
const playerJoinListener = new Set<OnPlayerJoined>();
|
||||||
|
const playerQuitListener = new Set<OnPlayerQuit>();
|
||||||
|
|
||||||
Modding.onListenerAdded<OnPlayerJoined>((object) => listeners.add(object));
|
Modding.onListenerAdded<OnPlayerJoined>((object) => playerJoinListener.add(object));
|
||||||
Modding.onListenerRemoved<OnPlayerJoined>((object) => listeners.delete(object));
|
Modding.onListenerRemoved<OnPlayerJoined>((object) => playerJoinListener.delete(object));
|
||||||
|
Modding.onListenerAdded<OnPlayerQuit>((object) => playerQuitListener.add(object));
|
||||||
|
Modding.onListenerRemoved<OnPlayerQuit>((object) => playerQuitListener.delete(object));
|
||||||
|
|
||||||
Players.PlayerAdded.Connect((player) => {
|
Players.PlayerAdded.Connect((player) => {
|
||||||
for (const listener of listeners) {
|
for (const listener of playerJoinListener) {
|
||||||
task.spawn(() => listener.onPlayerJoined(player));
|
task.spawn(() => listener.onPlayerJoined(player));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Players.PlayerRemoving.Connect((player) => {
|
||||||
|
for (const listener of playerQuitListener) {
|
||||||
|
task.spawn(() => listener.onPlayerQuit(player));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (const player of Players.GetPlayers()) {
|
for (const player of Players.GetPlayers()) {
|
||||||
for (const listener of listeners) {
|
for (const listener of playerJoinListener) {
|
||||||
task.spawn(() => listener.onPlayerJoined(player));
|
task.spawn(() => listener.onPlayerJoined(player));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/server/services/stats.ts
Normal file
43
src/server/services/stats.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { OnStart, Service } from "@flamework/core";
|
||||||
|
import { DataStoreService } from "@rbxts/services";
|
||||||
|
import { t } from "@rbxts/t";
|
||||||
|
import { ProfileServerEvents } from "server/networking";
|
||||||
|
import { OnPlayerJoined, OnPlayerQuit } from "shared/modding/player_events";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
class StatsService implements OnPlayerJoined, OnStart, OnPlayerQuit {
|
||||||
|
firstJoinStoreCache = new Map<number, [number, number]>();
|
||||||
|
firstJoinStore!: DataStore;
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
this.firstJoinStore = DataStoreService.GetDataStore("Stats_Players_Played_Time");
|
||||||
|
|
||||||
|
ProfileServerEvents.getPlayerStartTime.setCallback((_, playerId) => {
|
||||||
|
const playedtime = this.getPlayerPlayedTime(playerId);
|
||||||
|
return playedtime!!;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlayerJoined(player: Player): void {
|
||||||
|
const playerId = tostring(player.UserId);
|
||||||
|
const [value] = this.firstJoinStore.GetAsync(playerId);
|
||||||
|
|
||||||
|
if (!t.optional(t.number)(value)) throw `Bad Data in DataBase for ${player.UserId}`;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
this.firstJoinStore.SetAsync(playerId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.firstJoinStoreCache.set(player.UserId, [value ?? 0, os.time()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlayerPlayedTime(playerId: number) {
|
||||||
|
return this.firstJoinStoreCache.get(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlayerQuit(player: Player): void {
|
||||||
|
const [value, time] = this.firstJoinStoreCache.get(player.UserId)!!;
|
||||||
|
const t = value + os.time() - time;
|
||||||
|
this.firstJoinStore.SetAsync(tostring(player.UserId), t, [player.UserId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
export interface OnPlayerJoined {
|
export interface OnPlayerJoined {
|
||||||
onPlayerJoined(player: Player): void;
|
onPlayerJoined(player: Player): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OnPlayerQuit {
|
||||||
|
onPlayerQuit(player: Player): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,11 @@ interface GameplayClient {
|
|||||||
|
|
||||||
interface GameplayServer {}
|
interface GameplayServer {}
|
||||||
|
|
||||||
|
interface ProfileClient {
|
||||||
|
getPlayerStartTime(playerId: number): [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProfileServer {}
|
||||||
|
|
||||||
export const GameplayEvents = Networking.createEvent<GameplayClient, GameplayServer>();
|
export const GameplayEvents = Networking.createEvent<GameplayClient, GameplayServer>();
|
||||||
|
export const ProfileEvents = Networking.createFunction<ProfileClient, ProfileServer>();
|
||||||
|
|||||||
Reference in New Issue
Block a user