-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathHowToPlayScreen.java
More file actions
394 lines (344 loc) · 18 KB
/
Copy pathHowToPlayScreen.java
File metadata and controls
394 lines (344 loc) · 18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/*
* Authors: Jerry Li & Victor Jiang
* Date: June 13, 2025
* Description: This class operates the "How to Play" guide
*/
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.function.BiConsumer;
import javax.imageio.ImageIO;
import javax.swing.*;
public class HowToPlayScreen extends JPanel {
/**
* Inner static class representing a single page of the "How To Play" screen.
* Each page has a title and HTML content.
*/
public static class Page {
final String TITLE; // Title of the page
final String HTML; // HTML content of the page
// Constructor to initialize the title and HTML content of the page
Page(String TITLE, String html) {
this.TITLE = TITLE;
this.HTML = html;
}
}
// Array of pages to display in the "How To Play" screen
public static final Page[] PAGES = {
new Page(
"Game Mechanics Part 1",
"<html><div style='max-width:900px; font-size:15px; text-align:center;'>"
+ "Endless Night is a top-down, arena-style survival game. You control a light-wielder at the center of a battlefield, fending off waves of shadowy monsters. Between waves you choose powerful upgrades and skills to improve your arsenal. Survive 23 waves to reclaim the light—or fall into eternal darkness.<br><br>"
+ "<ul style='text-align:left; display:inline-block;'>"
+ "<li><b>Key Controls:</b> standard WASD to move</li>"
+ "<li><b>Wave System:</b> 23 total waves; each wave grows progressively harder.</li>"
+ "<li><b>Health:</b> stays the same after each wave. If HP hits zero mid-wave, you LOSE!</li>"
+ "<li><b>Wave Completion:</b> choose one of three upgrades or new skills between waves.</li>"
+ "<li><b>Boss Waves:</b> waves 21-23 (inclusive) feature powerful bosses to smash or be smashed.</li>"
+ "</ul>"
+ "</div></html>"),
new Page(
"Game Mechanics Part 2",
"<html><div style='max-width:900px; font-size:15px; text-align:center;'>"
+ "Monsters will randomly spawn in a roughly circular formation around you. They will always move towards you at a slow speed. If a monster comes into contact with you, you will lose HP. Stronger monsters will shoot projectiles that must be dodged or deal damage. There are 8 monsters in total, becoming stronger and stronger.<br><br>"
+ "Bosses are a special type of monster that have greatly increased stats and are much more difficult to defeat. A unique, almighty boss spawns in each of the final three waves. In the next few pages there will be more detailed info on monsters and bosses.<br><br>"
+ "Skills are powerful abilities that you can use to help defeat scary monsters. Skills attack passively and will either shoot towards your mouse (if aim) or at the nearest monster (homing). Each skill has three levels. After reaching level 3, a rare super upgrade may appear in later waves.<br><br>"
+ "</div></html>"),
new Page(
"Monsters, Bosses, & Skills",
"" // handled specially
)
};
public final GamePanel PARENT; // Reference to the parent game panel
public final Font UI_FONT; // Font used for UI elements
public final JLabel TITLE_LABEL, TEXT_LABEL; // Labels for the title and text content
public final GameButton PREV_BTN, NEXT_BTN, BACK_BTN; // Navigation buttons
public BufferedImage bgImage; // Background image for the screen
public int pageIndex = 0; // Current page index
// Constructor to initialize the "How To Play" screen
public HowToPlayScreen(GamePanel parent) {
PARENT = parent;
UI_FONT = PARENT.getGameFont();
setLayout(null);
setOpaque(false);
loadBackground();
// Initialize the title label
TITLE_LABEL = new JLabel("", SwingConstants.CENTER);
TITLE_LABEL.setFont(UI_FONT.deriveFont(Font.BOLD, 48f));
TITLE_LABEL.setForeground(Color.WHITE);
// Initialize the text label
TEXT_LABEL = new JLabel("", SwingConstants.CENTER);
TEXT_LABEL.setFont(UI_FONT.deriveFont(20f));
TEXT_LABEL.setForeground(Color.WHITE);
// Initialize navigation buttons
PREV_BTN = makeNav("Previous", e -> changePage(-1));
NEXT_BTN = makeNav("Next", e -> changePage(+1));
BACK_BTN = makeNav("Back", e -> {
changePage(-pageIndex);
PARENT.showScreen("main_menu");
});
// Configure tooltips
ToolTipManager.sharedInstance().setInitialDelay(100);
UIManager.put("ToolTip.background", new Color(15, 15, 25, 230));
UIManager.put("ToolTip.foreground", Color.WHITE);
UIManager.put("ToolTip.font", UI_FONT.deriveFont(14f));
UIManager.put("ToolTip.border",
BorderFactory.createLineBorder(new Color(0, 200, 255), 1, true));
UIManager.put("ToolTip.maxWidth", 300);
// Register custom tooltip class
ToolTipManager.sharedInstance().setLightWeightPopupEnabled(true);
refreshPage();
}
// Loads the background image for the screen
public void loadBackground() {
try {
bgImage = ImageIO.read(
getClass().getResourceAsStream("/assets/images/backgrounds/instructionsbg.png"));
} catch (IOException e) {
bgImage = null;
}
}
// Creates a navigation button with the specified text and action listener
public GameButton makeNav(String text, ActionListener al) {
GameButton b = new GameButton(text);
b.setFont(UI_FONT.deriveFont(28f));
b.setForeground(Color.WHITE);
b.addActionListener(al);
b.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
b.setFont(UI_FONT.deriveFont(32f));
}
@Override
public void mouseExited(MouseEvent e) {
b.setFont(UI_FONT.deriveFont(28f));
}
});
return b;
}
// Changes the current page by the specified delta
public void changePage(int delta) {
pageIndex = Math.max(0, Math.min(PAGES.length - 1, pageIndex + delta));
refreshPage();
}
// Refreshes the content of the current page
public void refreshPage() {
Page p;
removeAll();
p = PAGES[pageIndex];
TITLE_LABEL.setText(p.TITLE);
add(TITLE_LABEL);
add(PREV_BTN);
add(NEXT_BTN);
add(BACK_BTN);
if ("Monsters, Bosses, & Skills".equals(p.TITLE)) {
showIconGrid();
} else {
TEXT_LABEL.setText(p.HTML);
add(TEXT_LABEL);
}
positionComponents();
PREV_BTN.setEnabled(pageIndex > 0);
NEXT_BTN.setEnabled(pageIndex < PAGES.length - 1);
revalidate();
repaint();
}
// Creates a tooltip with the specified header and body
public String makeTip(String header, String body) {
return "<html><div style='text-align:left; width:280px; word-wrap:break-word;'>"
+ "<span style=\"font-size:14px;color:#00d0ff\"><b>" + header + "</b></span><br>"
+ "<span style=\"font-size:12px;color:#dddddd\">" + body + "</span></div></html>";
}
// Displays the icon grid for monsters, bosses, and skills
public void showIconGrid() {
int gap = 16;
int size = 64;
String[] monsterNames = {
"Shadeling", "Gloomspawn", "Vampire Bats", "Shadow Walker",
"Obsidian Maw", "Withering Wraith", "Midnight Abyss", "Chaos Demon"
};
String[] monsterTips = {
"<i>The first to rise when the light fell. Featureless, silent, and endless. Their bodies barely hold form, but their hunger is unmistakable.</i><br><br>First wave: 1",
"<i>Once angels of light. Now emptied of purpose, drifting aimlessly through shadow - their halos cracked, their forms unravelling.</i><br><br>First wave: 3",
"<i>They move like torn scraps of the night sky. Their powerful wings propel them through the shadows.</i><br><br>First wave: 6<br><br><b>Increased speed.</b>",
"<i>It doesn't run. It drifts. And when it stops, it's already behind you.</i><br><br>First wave: 10<br><b>Shoots small projectiles.</b>",
"<i>Its jaw begins where its chest should end. When it opens, the world seems to bend inward.</i><br><br>First wave: 14<br><b>Shoots projectiles faster.</b>",
"<i>You see it for a moment, and then something inside you feels smaller. Like it took something it shouldn't have.</i><br><br>First wave: 16<br><b>Shoots larger projectiles faster.</b>",
"<i>It doesn't move toward you. It is movement. A gravity you can't explain pulling you into something you were never meant to see.</i><br><br>First wave: 18<br><b>Shoots 8 projectiles in a circle.</b>",
"<i>It wasn't born. It fractured its way into being. Its limbs don't match and its existence bends space, time, and mercy.</i><br><br>First wave: 20<br><b>Shoots infrequent solid beams of void that do insane damage.</b>"
};
String[] bossNames = {
"Void Titan", "Eclipse Harbinger", "Anthony's Wrath"
};
String[] bossTips = {
"A colossal lumbering monstrosity with ridiculously high HP and that does area of effect shadow slams.<br><br>Spawns in wave 21.",
"A floating monster with crystals around it that charge up before releasing a devastating attack in the direction of the player. Player must hide behind a terrain object or will be killed instantly.<br><br>Spawns in wave 22.",
"???"
};
String[] skillNames = {
"Luminous Pulse", "Light Lance", "Photon Orbs",
"Angelic Summons", "Starfall Ritual"
};
String[] skillTips = {
"Radial area of effect region around you.<br><br>Super upgrade: \"solar flare\" unleashes a screen-wide burst that briefly stuns foes.",
"An aimed precision beam that aims towards nearest enemy.<br><br>Super upgrade: \"prismatic ray\" splits into five colors on impact.",
"Shoots homing projectile orbs.<br><br>Super upgrade: \"celestial constellation\" causes large explosions on hit.",
"Spawns light angels with a limited lifespan.<br><br>Super upgrade: \"archangel\" summons a single powerful ally.",
"Shoots omni-directional starlight projectiles.<br><br>Super upgrade: \"cosmic cataclysm\" rains meteors at your cursor."
};
String[] monsterIcons = {
"shadeling.png",
"gloomspawn.png",
"vampire_bats.png",
"shadow_walker.png",
"obsidian_maw.png",
"withering_wraith.png",
"midnight_abyss.png",
"chaos_demon.png"
};
String[] bossIcons = {
"void_titan.png",
"eclipse_harbinger.png",
"anthonys_wrath.png"
};
String[] skillIcons = {
"luminous_pulse.png",
"light_lance.png",
"photon_orbs.png",
"angelic_summons.png",
"starfall_ritual.png"
};
String[] superIcons = {
"solar_flare.png",
"prismatic_ray.png",
"celestial_constellation.png",
"archangel.png",
"cosmic_cataclysm.png"
};
JPanel container = new JPanel();
container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
container.setOpaque(false);
JLabel hover_label = new JLabel("Hover over icons for more info.");
hover_label.setFont(UI_FONT.deriveFont(Font.BOLD, 24f));
hover_label.setForeground(Color.WHITE);
hover_label.setAlignmentX(Component.CENTER_ALIGNMENT);
container.add(hover_label);
BiConsumer<String[], String[]> addRow = (names, tips) -> {
JPanel row = null;
JLabel groupLabel = null;
int i = 0;
JPanel iconGroup = null;
JLabel icon = null;
String imagePath = null;
BufferedImage img = null;
BufferedImage placeholderImg = null;
Graphics2D g2d = null;
JLabel superIcon = null;
String superImagePath = null;
row = new JPanel(new FlowLayout(FlowLayout.LEFT, gap, gap));
row.setOpaque(false);
groupLabel = new JLabel(
names == monsterNames ? "Monsters:"
: names == bossNames ? "Bosses:"
: "Skills:");
groupLabel.setFont(UI_FONT.deriveFont(Font.BOLD, 24f));
groupLabel.setForeground(Color.WHITE);
row.add(groupLabel);
for (i = 0; i < names.length; i++) {
iconGroup = new JPanel(new GridLayout(names == skillNames ? 2 : 1, 1));
iconGroup.setOpaque(false);
icon = new JLabel();
icon.setPreferredSize(new Dimension(size, size));
imagePath = null;
if (names == monsterNames) {
imagePath = "/assets/images/bordered_sprites/" + monsterIcons[i];
} else if (names == bossNames) {
imagePath = "/assets/images/bordered_sprites/" + bossIcons[i];
} else if (names == skillNames) {
imagePath = "/assets/images/bordered_sprites/" + skillIcons[i];
}
try {
if (imagePath != null) {
img = ImageIO.read(getClass().getResourceAsStream(imagePath));
icon.setIcon(new ImageIcon(img.getScaledInstance(size, size, Image.SCALE_SMOOTH)));
} else {
throw new IOException("No image path specified");
}
} catch (Exception e) {
// Fallback to placeholder if image loading fails
placeholderImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
g2d = placeholderImg.createGraphics();
g2d.setColor(new Color(255, 0, 0, 100));
g2d.fillRect(0, 0, size, size);
g2d.setColor(Color.WHITE);
g2d.drawRect(0, 0, size - 1, size - 1);
g2d.dispose();
icon.setIcon(new ImageIcon(placeholderImg));
}
// Set tooltip text based on type
if (names == skillNames) {
icon.setToolTipText(makeTip(names[i], tips[i] + "<br><br>Super version shown below."));
} else {
icon.setToolTipText(makeTip(names[i], tips[i]));
}
iconGroup.add(icon);
// Add super icon below the skill icon
if (names == skillNames) {
superIcon = new JLabel();
superIcon.setPreferredSize(new Dimension(size, size));
try {
superImagePath = "/assets/images/bordered_sprites/" + superIcons[i];
img = ImageIO.read(getClass().getResourceAsStream(superImagePath));
superIcon.setIcon(new ImageIcon(img.getScaledInstance(size, size, Image.SCALE_SMOOTH)));
} catch (Exception e) {
placeholderImg = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
g2d = placeholderImg.createGraphics();
g2d.setColor(new Color(0, 0, 255, 100));
g2d.fillRect(0, 0, size, size);
g2d.setColor(Color.WHITE);
g2d.drawRect(0, 0, size - 1, size - 1);
g2d.dispose();
superIcon.setIcon(new ImageIcon(placeholderImg));
}
// Set tooltip for super icons
superIcon.setToolTipText(makeTip("Super: " + names[i],
"Unlocked after reaching level 3. Enhances " + names[i] + "."));
iconGroup.add(superIcon);
}
row.add(iconGroup);
}
container.add(row);
};
addRow.accept(monsterNames, monsterTips);
addRow.accept(bossNames, bossTips);
addRow.accept(skillNames, skillTips);
container.setBounds(50, 130, getWidth() - 100, getHeight() - 200);
add(container);
}
// Positions the components on the screen
public void positionComponents() {
int w = getWidth() > 0 ? getWidth() : GamePanel.GAME_WIDTH;
int h = getHeight() > 0 ? getHeight() : GamePanel.GAME_HEIGHT;
int btnW = 140, btnH = 50, gap = 20;
int startX = (w - (btnW * 3 + gap * 2)) / 2;
int y = h - btnH - 40;
TITLE_LABEL.setBounds(50, 50, w - 100, 50);
TEXT_LABEL.setBounds((w - 900) / 2, 130, 900, 480);
PREV_BTN.setBounds(startX - 30, y, btnW + 30, btnH);
NEXT_BTN.setBounds(startX + btnW + gap, y, btnW, btnH);
BACK_BTN.setBounds(startX + 2 * (btnW + gap), y, btnW, btnH);
}
// Paints the background image with transparency
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2;
super.paintComponent(g);
if (bgImage != null) {
g2 = (Graphics2D) g.create();
g2.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2.drawImage(bgImage, 0, 0, getWidth(), getHeight(), null);
g2.dispose();
}
}
}