-
Notifications
You must be signed in to change notification settings - Fork 754
Feat: Singleplayer Achievements #2734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
736246b
7753380
e5c1004
042b427
f8e3619
1b432de
6f75d78
51384bf
6acbc1d
90ba5fc
eb70ec1
6601553
d1f05f9
26a8cc1
38f6b4d
90d28c8
819fb76
cbaa59a
7c42e45
5473a0a
f86ce97
92f6812
3e0e9ca
175669d
fb9ca70
ad2a2ef
4f2c5b4
6fcfb78
51a9dac
7e5bd1b
858638a
99f0374
9cc040d
3426dfd
e52e482
9d038bf
385424a
02d6a93
ec04a88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { LitElement, html } from "lit"; | ||
| import { customElement, query, state } from "lit/decorators.js"; | ||
| import { translateText } from "../client/Utils"; | ||
| import { UserMeResponse } from "../core/ApiSchemas"; | ||
| import { | ||
| Difficulty, | ||
| Duos, | ||
|
|
@@ -49,6 +50,8 @@ export class SinglePlayerModal extends LitElement { | |
| @state() private useRandomMap: boolean = false; | ||
| @state() private gameMode: GameMode = GameMode.FFA; | ||
| @state() private teamCount: TeamCountConfig = 2; | ||
| @state() private showAchievements: boolean = false; | ||
| @state() private mapWins: Map<GameMapType, Set<Difficulty>> = new Map(); | ||
|
|
||
| @state() private disabledUnits: UnitType[] = []; | ||
|
|
||
|
|
@@ -57,9 +60,17 @@ export class SinglePlayerModal extends LitElement { | |
| connectedCallback() { | ||
| super.connectedCallback(); | ||
| window.addEventListener("keydown", this.handleKeyDown); | ||
| document.addEventListener( | ||
| "userMeResponse", | ||
| this.handleUserMeResponse as EventListener, | ||
| ); | ||
| } | ||
|
|
||
| disconnectedCallback() { | ||
| document.removeEventListener( | ||
| "userMeResponse", | ||
| this.handleUserMeResponse as EventListener, | ||
| ); | ||
| window.removeEventListener("keydown", this.handleKeyDown); | ||
| super.disconnectedCallback(); | ||
| } | ||
|
|
@@ -71,13 +82,76 @@ export class SinglePlayerModal extends LitElement { | |
| } | ||
| }; | ||
|
|
||
| private toggleAchievements = () => { | ||
| this.showAchievements = !this.showAchievements; | ||
| }; | ||
|
|
||
| private handleUserMeResponse = ( | ||
| event: CustomEvent<UserMeResponse | false>, | ||
| ) => { | ||
| this.applyAchievements(event.detail); | ||
| }; | ||
|
|
||
| private applyAchievements(userMe: UserMeResponse | false) { | ||
| if (!userMe) { | ||
| this.mapWins = new Map(); | ||
| return; | ||
| } | ||
|
|
||
| const achievements = Array.isArray(userMe.player.achievements) | ||
| ? userMe.player.achievements | ||
| : []; | ||
|
|
||
| const completions = | ||
| achievements | ||
| .find((achievement) => achievement?.type === "singleplayer-map") | ||
| ?.data ?? []; | ||
|
|
||
| const winsMap = new Map<GameMapType, Set<Difficulty>>(); | ||
| for (const entry of completions) { | ||
| const { mapName, difficulty } = entry ?? {}; | ||
| const isValidMap = | ||
| typeof mapName === "string" && | ||
| Object.values(GameMapType).includes(mapName as GameMapType); | ||
| const isValidDifficulty = | ||
| typeof difficulty === "string" && | ||
| Object.values(Difficulty).includes(difficulty as Difficulty); | ||
| if (!isValidMap || !isValidDifficulty) continue; | ||
|
|
||
| const map = mapName as GameMapType; | ||
| const set = winsMap.get(map) ?? new Set<Difficulty>(); | ||
| set.add(difficulty as Difficulty); | ||
| winsMap.set(map, set); | ||
| } | ||
|
|
||
| this.mapWins = winsMap; | ||
| } | ||
|
|
||
| render() { | ||
| return html` | ||
| <o-modal title=${translateText("single_modal.title")}> | ||
| <div class="options-layout"> | ||
| <!-- Map Selection --> | ||
| <div class="options-section"> | ||
| <div class="option-title">${translateText("map.map")}</div> | ||
| <div | ||
| class="option-title" | ||
| style="position:relative; display:flex; align-items:center; justify-content:center; width:100%;" | ||
| > | ||
| <span style="text-align:center; width:100%;"> | ||
| ${translateText("map.map")} | ||
| </span> | ||
| <button | ||
| @click=${this.toggleAchievements} | ||
| title=${translateText("single_modal.toggle_achievements")} | ||
| style="display:flex; align-items:center; justify-content:center; width:28px; height:28px; border:1px solid rgba(255,255,255,0.2); border-radius:6px; background:rgba(255,255,255,0.06); cursor:pointer; padding:4px; position:absolute; right:0; top:50%; transform:translateY(-50%);" | ||
| > | ||
| <img | ||
| src="/images/MedalIconWhite.svg" | ||
| alt="Toggle achievements" | ||
| style=${`width:18px; height:18px; opacity:${this.showAchievements ? "1" : "0.5"};`} | ||
| /> | ||
| </button> | ||
| </div> | ||
|
Comment on lines
+136
to
+154
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if MedalIconWhite.svg exists
echo "=== Searching for MedalIconWhite.svg ==="
fd -t f "MedalIconWhite.svg"
echo -e "\n=== Checking public/images directory ==="
ls -la public/images/ 2>/dev/null || echo "public/images not found"
echo -e "\n=== Checking assets/images directory ==="
ls -la assets/images/ 2>/dev/null || echo "assets/images not found"
echo -e "\n=== Listing all .svg files in repo ==="
fd -t f "\.svg$" | head -20Repository: openfrontio/OpenFrontIO Length of output: 938 🏁 Script executed: #!/bin/bash
# Examine SinglePlayerModal.ts for styling approach
echo "=== Checking SinglePlayerModal.ts structure ==="
wc -l src/client/SinglePlayerModal.ts
echo -e "\n=== Looking for createRenderRoot ==="
rg "createRenderRoot" src/client/SinglePlayerModal.ts -A 3
echo -e "\n=== Looking for static styles in component ==="
rg "static styles|css\`" src/client/SinglePlayerModal.ts -B 2 -A 5Repository: openfrontio/OpenFrontIO Length of output: 294 🏁 Script executed: #!/bin/bash
# Check for other components to understand styling patterns
echo "=== Looking at Lit component styling patterns ==="
fd -t f "\.ts$" src/client/ | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && grep -l "static styles" {}'
echo -e "\n=== Check for inline style usage in similar files ==="
rg "style=" src/client/SinglePlayerModal.ts | head -5Repository: openfrontio/OpenFrontIO Length of output: 1196 Fix the asset path for MedalIconWhite.svg. The image path Consider extracting inline styles for maintainability. The toggle button works correctly, but the inline styles on lines 137, 145, and 150 make the template harder to read. However, since this component uses light DOM (
Extracting styles improves readability and makes changes easier to maintain. 🤖 Prompt for AI Agents |
||
| <div class="option-cards flex-col"> | ||
| <!-- Use the imported mapCategories --> | ||
| ${Object.entries(mapCategories).map( | ||
|
|
@@ -103,6 +177,8 @@ export class SinglePlayerModal extends LitElement { | |
| .mapKey=${mapKey} | ||
| .selected=${!this.useRandomMap && | ||
| this.selectedMap === mapValue} | ||
| .showMedals=${this.showAchievements} | ||
| .wins=${this.mapWins.get(mapValue) ?? new Set()} | ||
| .translation=${translateText( | ||
| `map.${mapKey?.toLowerCase()}`, | ||
| )} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.