Skip to content

Commit a24710a

Browse files
authored
A timer and icon flashing for betryal debuff (#2430)
Resolves #2096 ## Description: (TO CLEARIFY THEIRS NO GRACE PERIOD ADDED, AS THAT ISSUE THAT WOULD OF NEEDED IT WAS FIXED BEFORE ON ITS OWN) Shows the amount left in the UI for the player who trigged it <img width="374" height="80" alt="image" src="https://github.com/user-attachments/assets/f269c015-5a78-4e85-a9c0-cdf039d93d2a" /> also the betryal icon, after 15 seconds starts a slow flash, then after 10 seconds it speeds up, and then at 5 seconds it quickly flashs. this was a nice way to show the time left without adding any new ui componets. video link 36 seconds (https://streamable.com/cwzxch) ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: notifxy (1379678982676676639)
1 parent 34251c0 commit a24710a

File tree

3 files changed

+103
-9
lines changed

3 files changed

+103
-9
lines changed

resources/lang/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,8 @@
580580
"alliance_renewed": "Your alliance with {name} has been renewed",
581581
"wants_to_renew_alliance": "{name} wants to renew your alliance",
582582
"ignore": "Ignore",
583-
"unit_voluntarily_deleted": "Unit voluntarily deleted"
583+
"unit_voluntarily_deleted": "Unit voluntarily deleted",
584+
"betrayal_debuff_ends": "{time} seconds left until betrayal debuff ends"
584585
},
585586
"unit_info_modal": {
586587
"structure_info": "Structure Info",

src/client/graphics/layers/EventsDisplay.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,30 @@ export class EventsDisplay extends LitElement implements Layer {
877877
`;
878878
}
879879

880+
private renderBetrayalDebuffTimer() {
881+
const myPlayer = this.game.myPlayer();
882+
if (!myPlayer || !myPlayer.isTraitor()) {
883+
return html``;
884+
}
885+
886+
const remainingTicks = myPlayer.getTraitorRemainingTicks();
887+
const remainingSeconds = Math.ceil(remainingTicks / 10);
888+
889+
if (remainingSeconds <= 0) {
890+
return html``;
891+
}
892+
893+
return html`
894+
${this.renderButton({
895+
content: html`${translateText("events_display.betrayal_debuff_ends", {
896+
time: remainingSeconds,
897+
})}`,
898+
className: "text-left text-yellow-400",
899+
translate: false,
900+
})}
901+
`;
902+
}
903+
880904
render() {
881905
if (!this.active || !this._isVisible) {
882906
return html``;
@@ -1081,6 +1105,24 @@ export class EventsDisplay extends LitElement implements Layer {
10811105
`
10821106
: ""}
10831107
1108+
<!--- Betrayal debuff timer row -->
1109+
${(() => {
1110+
const myPlayer = this.game.myPlayer();
1111+
return (
1112+
myPlayer &&
1113+
myPlayer.isTraitor() &&
1114+
myPlayer.getTraitorRemainingTicks() > 0
1115+
);
1116+
})()
1117+
? html`
1118+
<tr class="lg:px-2 lg:py-1 p-1">
1119+
<td class="lg:px-2 lg:py-1 p-1 text-left">
1120+
${this.renderBetrayalDebuffTimer()}
1121+
</td>
1122+
</tr>
1123+
`
1124+
: ""}
1125+
10841126
<!--- Outgoing attacks row -->
10851127
${this.outgoingAttacks.length > 0
10861128
? html`
@@ -1119,7 +1161,15 @@ export class EventsDisplay extends LitElement implements Layer {
11191161
this.incomingAttacks.length === 0 &&
11201162
this.outgoingAttacks.length === 0 &&
11211163
this.outgoingLandAttacks.length === 0 &&
1122-
this.outgoingBoats.length === 0
1164+
this.outgoingBoats.length === 0 &&
1165+
!(() => {
1166+
const myPlayer = this.game.myPlayer();
1167+
return (
1168+
myPlayer &&
1169+
myPlayer.isTraitor() &&
1170+
myPlayer.getTraitorRemainingTicks() > 0
1171+
);
1172+
})()
11231173
? html`
11241174
<tr>
11251175
<td

src/client/graphics/layers/NameLayer.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,21 @@ export class NameLayer implements Layer {
118118
this.container.style.zIndex = "2";
119119
document.body.appendChild(this.container);
120120

121+
// Add CSS keyframes for traitor icon flashing animation
122+
// Append to container instead of document.head to keep styles scoped to this component
123+
const style = document.createElement("style");
124+
style.textContent = `
125+
@keyframes traitorFlash {
126+
0%, 100% {
127+
opacity: 1;
128+
}
129+
50% {
130+
opacity: 0.3;
131+
}
132+
}
133+
`;
134+
this.container.appendChild(style);
135+
121136
this.eventBus.on(AlternateViewEvent, (e) => this.onAlternateViewChange(e));
122137
}
123138

@@ -410,16 +425,44 @@ export class NameLayer implements Layer {
410425
}
411426

412427
// Traitor icon
413-
const existingTraitor = iconsDiv.querySelector('[data-icon="traitor"]');
428+
let existingTraitor = iconsDiv.querySelector('[data-icon="traitor"]');
414429
if (render.player.isTraitor()) {
430+
const remainingTicks = render.player.getTraitorRemainingTicks();
431+
// Use precise seconds (not rounded) for smoother transitions, rounded to 0.5s intervals
432+
const remainingSeconds = Math.round((remainingTicks / 10) * 2) / 2;
433+
415434
if (!existingTraitor) {
416-
iconsDiv.appendChild(
417-
this.createIconElement(
418-
this.traitorIconImage.src,
419-
iconSize,
420-
"traitor",
421-
),
435+
existingTraitor = this.createIconElement(
436+
this.traitorIconImage.src,
437+
iconSize,
438+
"traitor",
422439
);
440+
iconsDiv.appendChild(existingTraitor);
441+
}
442+
443+
// Apply flashing animation - smooth speed increase starting at 15s
444+
if (existingTraitor instanceof HTMLImageElement) {
445+
if (remainingSeconds <= 15) {
446+
// Smooth transition: starts at 1s at 15 seconds, decreases to 0.2s at 0 seconds
447+
// Using cubic ease-out for slower, more gradual acceleration
448+
const clampedSeconds = Math.max(0, Math.min(15, remainingSeconds));
449+
const normalizedTime = clampedSeconds / 15; // 0 to 1 (1 = 15s remaining, 0 = 0s remaining)
450+
451+
// Cubic ease-out: slower acceleration, smoother transition
452+
const easedProgress = 1 - Math.pow(1 - normalizedTime, 3);
453+
454+
const maxDuration = 1.0; // Slow flash at 15 seconds
455+
const minDuration = 0.2; // Fast flash at 0 seconds
456+
const duration =
457+
minDuration + (maxDuration - minDuration) * easedProgress;
458+
const animationDuration = `${duration.toFixed(2)}s`;
459+
460+
existingTraitor.style.animation = `traitorFlash ${animationDuration} infinite`;
461+
existingTraitor.style.animationTimingFunction = "ease-in-out";
462+
} else {
463+
// Don't flash if more than 15 seconds remaining
464+
existingTraitor.style.animation = "none";
465+
}
423466
}
424467
} else if (existingTraitor) {
425468
existingTraitor.remove();

0 commit comments

Comments
 (0)