Skip to content

Commit 00c9025

Browse files
committed
feat: add physics settings, input binding profile management, and RPG state serialization to settings menu
1 parent 1e302b3 commit 00c9025

File tree

22 files changed

+606
-22
lines changed

22 files changed

+606
-22
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.jvn.core.input;
2+
3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.nio.file.Path;
6+
import java.nio.file.Paths;
7+
8+
/**
9+
* Utility to persist/retrieve ActionBindingProfile from disk.
10+
*/
11+
public class ActionBindingProfileStore {
12+
private final Path path;
13+
14+
public ActionBindingProfileStore(String path) {
15+
this.path = Paths.get(path == null || path.isBlank() ? defaultPath() : path);
16+
}
17+
18+
public ActionBindingProfile load() {
19+
try {
20+
if (!Files.exists(path)) return new ActionBindingProfile();
21+
String data = Files.readString(path);
22+
return ActionBindingProfile.deserialize(data);
23+
} catch (IOException e) {
24+
return new ActionBindingProfile();
25+
}
26+
}
27+
28+
public void save(ActionBindingProfile profile) throws IOException {
29+
if (profile == null) return;
30+
Files.createDirectories(path.getParent());
31+
Files.writeString(path, profile.serialize());
32+
}
33+
34+
public String getPath() { return path.toString(); }
35+
36+
public static String defaultPath() {
37+
return System.getProperty("user.home") + "/.jvn/input-bindings.properties";
38+
}
39+
}

core/src/main/java/com/jvn/core/menu/MainMenuScene.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ public void activateSelected() {
4949
switch (selected) {
5050
case 0 -> startNewGame();
5151
case 1 -> engine.scenes().push(new LoadMenuScene(engine, saveManager, defaultScriptName, settingsModel, audio));
52-
case 2 -> engine.scenes().push(new SettingsScene(settingsModel, audio));
52+
case 2 -> {
53+
com.jvn.core.input.ActionBindingProfile profile = com.jvn.core.input.ActionBindingProfile.deserialize(settingsModel.getInputProfileSerialized());
54+
engine.scenes().push(new SettingsScene(settingsModel, audio, profile));
55+
}
5356
case 3 -> engine.stop();
5457
}
5558
}
@@ -70,11 +73,19 @@ private void startNewGame() {
7073
s.setAutoPlayDelay(settingsModel.getAutoPlayDelay());
7174
s.setSkipUnreadText(settingsModel.isSkipUnreadText());
7275
s.setSkipAfterChoices(settingsModel.isSkipAfterChoices());
76+
s.setPhysicsFixedStepMs(settingsModel.getPhysicsFixedStepMs());
77+
s.setPhysicsMaxSubSteps(settingsModel.getPhysicsMaxSubSteps());
78+
s.setPhysicsDefaultFriction(settingsModel.getPhysicsDefaultFriction());
79+
s.setInputProfilePath(settingsModel.getInputProfilePath());
80+
s.setInputProfileSerialized(settingsModel.getInputProfileSerialized());
7381
if (audio != null) {
7482
audio.setBgmVolume(s.getBgmVolume());
7583
audio.setSfxVolume(s.getSfxVolume());
7684
audio.setVoiceVolume(s.getVoiceVolume());
7785
}
86+
if (engine != null) {
87+
engine.setFixedUpdateStepMs(settingsModel.getPhysicsFixedStepMs(), settingsModel.getPhysicsMaxSubSteps());
88+
}
7889
engine.scenes().push(vnScene);
7990
}
8091

core/src/main/java/com/jvn/core/menu/SettingsScene.java

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,36 @@
22

33
import com.jvn.core.audio.AudioFacade;
44
import com.jvn.core.scene.Scene;
5+
import com.jvn.core.input.ActionBindingProfile;
6+
import com.jvn.core.input.ActionBindingProfileStore;
57
import com.jvn.core.vn.VnSettings;
68
import com.jvn.core.vn.VnSettingsStore;
79

810
public class SettingsScene implements Scene {
911
private final VnSettings settings;
1012
private final AudioFacade audio; // optional, to apply volumes live
1113
private int selected = 0;
14+
private ActionBindingProfile bindings;
15+
private String bindingStatus = "";
1216

13-
public SettingsScene(VnSettings settings) { this(settings, null); }
17+
public SettingsScene(VnSettings settings) { this(settings, null, null); }
1418

15-
public SettingsScene(VnSettings settings, AudioFacade audio) {
19+
public SettingsScene(VnSettings settings, AudioFacade audio) { this(settings, audio, null); }
20+
21+
public SettingsScene(VnSettings settings, AudioFacade audio, ActionBindingProfile bindings) {
1622
this.settings = settings;
1723
this.audio = audio;
24+
if (bindings != null) {
25+
this.bindings = bindings;
26+
} else if (settings != null && settings.getInputProfileSerialized() != null && !settings.getInputProfileSerialized().isBlank()) {
27+
this.bindings = ActionBindingProfile.deserialize(settings.getInputProfileSerialized());
28+
} else {
29+
this.bindings = new ActionBindingProfile();
30+
}
1831
}
1932

2033
public VnSettings model() { return settings; }
21-
public int itemCount() { return 7; }
34+
public int itemCount() { return 11; }
2235

2336
public int getSelected() { return selected; }
2437
public void moveSelection(int delta) {
@@ -50,16 +63,22 @@ public void adjustCurrent(int delta) {
5063
case 4 -> settings.setAutoPlayDelay(settings.getAutoPlayDelay() + delta * 100L);
5164
case 5 -> settings.setSkipUnreadText(!settings.isSkipUnreadText());
5265
case 6 -> settings.setSkipAfterChoices(!settings.isSkipAfterChoices());
66+
case 7 -> settings.setPhysicsFixedStepMs(Math.max(0, settings.getPhysicsFixedStepMs() + delta * 5));
67+
case 8 -> settings.setPhysicsMaxSubSteps(Math.max(1, settings.getPhysicsMaxSubSteps() + delta));
68+
case 9 -> settings.setPhysicsDefaultFriction(settings.getPhysicsDefaultFriction() + delta * 0.05);
69+
case 10 -> {
70+
// save profile when adjusting right, load when adjusting left
71+
if (delta > 0) saveBindingsToDisk();
72+
else loadBindingsFromDisk();
73+
}
5374
default -> {}
5475
}
5576
}
5677

5778
public void toggleCurrent() {
58-
if (selected == 5) {
59-
settings.setSkipUnreadText(!settings.isSkipUnreadText());
60-
} else if (selected == 6) {
61-
settings.setSkipAfterChoices(!settings.isSkipAfterChoices());
62-
}
79+
if (selected == 5) settings.setSkipUnreadText(!settings.isSkipUnreadText());
80+
else if (selected == 6) settings.setSkipAfterChoices(!settings.isSkipAfterChoices());
81+
else if (selected == 10) loadBindingsFromDisk();
6382
}
6483

6584
@Override
@@ -84,6 +103,13 @@ public void setValueByIndex(int idx, double value01) {
84103
}
85104
case 5 -> settings.setSkipUnreadText(v >= 0.5);
86105
case 6 -> settings.setSkipAfterChoices(v >= 0.5);
106+
case 7 -> settings.setPhysicsFixedStepMs(Math.round(v * 50)); // 0..50 ms
107+
case 8 -> settings.setPhysicsMaxSubSteps(1 + (int) Math.round(v * 7)); // 1..8
108+
case 9 -> settings.setPhysicsDefaultFriction(v);
109+
case 10 -> {
110+
// slider not used; interpret >0.5 as save
111+
if (v > 0.5) saveBindingsToDisk(); else loadBindingsFromDisk();
112+
}
87113
default -> {}
88114
}
89115
// Live-apply volumes
@@ -100,4 +126,26 @@ public void onExit() {
100126
new VnSettingsStore().save(settings);
101127
} catch (Exception ignored) {}
102128
}
129+
130+
public String getBindingStatus() { return bindingStatus; }
131+
132+
public void loadBindingsFromDisk() {
133+
ActionBindingProfileStore store = new ActionBindingProfileStore(settings.getInputProfilePath());
134+
bindings = store.load();
135+
settings.setInputProfileSerialized(bindings.serialize());
136+
bindingStatus = "Loaded from " + store.getPath();
137+
}
138+
139+
public ActionBindingProfile getBindings() { return bindings; }
140+
141+
private void saveBindingsToDisk() {
142+
ActionBindingProfileStore store = new ActionBindingProfileStore(settings.getInputProfilePath());
143+
try {
144+
store.save(bindings);
145+
settings.setInputProfileSerialized(bindings.serialize());
146+
bindingStatus = "Saved to " + store.getPath();
147+
} catch (Exception e) {
148+
bindingStatus = "Save failed";
149+
}
150+
}
103151
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.jvn.core.rpg;
2+
3+
import com.jvn.core.math.Rect;
4+
import com.jvn.core.physics.PhysicsWorld2D;
5+
import com.jvn.core.tween.TweenRunner;
6+
7+
import java.util.ArrayList;
8+
import java.util.Iterator;
9+
import java.util.List;
10+
11+
public class CombatInteractionSystem {
12+
public static class Hitbox {
13+
public String id;
14+
public int team;
15+
public Rect bounds = new Rect();
16+
}
17+
18+
public static class DamagePopup {
19+
public String text;
20+
public double x, y;
21+
public double alpha = 1.0;
22+
public double lifetimeMs = 800;
23+
public double elapsedMs = 0;
24+
}
25+
26+
private final PhysicsWorld2D world;
27+
private final List<Hitbox> hitboxes = new ArrayList<>();
28+
private final List<DamagePopup> popups = new ArrayList<>();
29+
private final TweenRunner tweens;
30+
31+
public CombatInteractionSystem(PhysicsWorld2D world, TweenRunner tweens) {
32+
this.world = world;
33+
this.tweens = tweens;
34+
}
35+
36+
public void registerHitbox(Hitbox hb) { if (hb != null) hitboxes.add(hb); }
37+
public void clearHitboxes() { hitboxes.clear(); }
38+
39+
public List<Hitbox> getTargetsAt(double x, double y, int attackerTeam) {
40+
List<Hitbox> out = new ArrayList<>();
41+
for (Hitbox hb : hitboxes) {
42+
if (hb.team == attackerTeam) continue;
43+
if (hb.bounds.contains(x, y)) out.add(hb);
44+
}
45+
return out;
46+
}
47+
48+
public void spawnDamagePopup(String text, double x, double y) {
49+
DamagePopup p = new DamagePopup();
50+
p.text = text;
51+
p.x = x;
52+
p.y = y;
53+
popups.add(p);
54+
}
55+
56+
public List<DamagePopup> getPopups() { return popups; }
57+
58+
public void update(long deltaMs) {
59+
if (deltaMs <= 0) return;
60+
Iterator<DamagePopup> it = popups.iterator();
61+
while (it.hasNext()) {
62+
DamagePopup p = it.next();
63+
p.elapsedMs += deltaMs;
64+
double t = Math.min(1.0, p.elapsedMs / p.lifetimeMs);
65+
p.alpha = 1.0 - t;
66+
p.y -= deltaMs * 0.02; // float up
67+
if (p.elapsedMs >= p.lifetimeMs) it.remove();
68+
}
69+
}
70+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.jvn.core.rpg;
2+
3+
import java.io.Serializable;
4+
5+
public class Combatant implements Serializable {
6+
private String id;
7+
private RpgStats stats = new RpgStats();
8+
private double atb;
9+
private int team; // 0 player, 1 enemy, etc.
10+
private boolean actedThisRound;
11+
12+
public Combatant() {}
13+
public Combatant(String id, RpgStats stats, int team) {
14+
this.id = id;
15+
if (stats != null) this.stats = stats;
16+
this.team = team;
17+
}
18+
19+
public String getId() { return id; }
20+
public void setId(String id) { this.id = id; }
21+
public RpgStats getStats() { return stats; }
22+
public void setStats(RpgStats stats) { if (stats != null) this.stats = stats; }
23+
public double getAtb() { return atb; }
24+
public void setAtb(double atb) { this.atb = atb; }
25+
public int getTeam() { return team; }
26+
public void setTeam(int team) { this.team = team; }
27+
public boolean hasActedThisRound() { return actedThisRound; }
28+
public void setActedThisRound(boolean actedThisRound) { this.actedThisRound = actedThisRound; }
29+
public double getSpeed() { return stats == null ? 0 : stats.getSpeed(); }
30+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.jvn.core.rpg;
2+
3+
import java.io.Serializable;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
7+
public class Equipment implements Serializable {
8+
private final Map<String, String> slots = new HashMap<>();
9+
10+
public Map<String, String> getSlots() { return slots; }
11+
12+
public void set(String slot, String itemId) {
13+
if (slot == null || slot.isBlank()) return;
14+
if (itemId == null || itemId.isBlank()) slots.remove(slot);
15+
else slots.put(slot, itemId);
16+
}
17+
18+
public String get(String slot) { return slots.get(slot); }
19+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.jvn.core.rpg;
2+
3+
import java.io.Serializable;
4+
import java.util.Collections;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
public class Inventory implements Serializable {
9+
private int slots;
10+
private final Map<String, Integer> itemCounts = new HashMap<>();
11+
12+
public int getSlots() { return slots; }
13+
public void setSlots(int slots) { this.slots = Math.max(0, slots); }
14+
15+
public Map<String, Integer> getItemCounts() { return Collections.unmodifiableMap(itemCounts); }
16+
17+
public int getCount(String itemId) {
18+
if (itemId == null) return 0;
19+
return itemCounts.getOrDefault(itemId, 0);
20+
}
21+
22+
public boolean add(String itemId, int count) {
23+
if (itemId == null || itemId.isBlank() || count <= 0) return false;
24+
boolean hasItem = itemCounts.containsKey(itemId);
25+
if (slots > 0 && !hasItem && itemCounts.size() >= slots) return false;
26+
itemCounts.put(itemId, getCount(itemId) + count);
27+
return true;
28+
}
29+
30+
public boolean remove(String itemId, int count) {
31+
if (itemId == null || itemId.isBlank() || count <= 0) return false;
32+
int existing = getCount(itemId);
33+
if (existing < count) return false;
34+
int remaining = existing - count;
35+
if (remaining <= 0) itemCounts.remove(itemId);
36+
else itemCounts.put(itemId, remaining);
37+
return true;
38+
}
39+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.jvn.core.rpg;
2+
3+
import java.io.Serializable;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
7+
public class Item implements Serializable {
8+
public enum Type { CONSUMABLE, EQUIPMENT, KEY }
9+
10+
private String id;
11+
private String name;
12+
private String description;
13+
private Type type = Type.CONSUMABLE;
14+
private Map<String, Double> modifiers = new HashMap<>();
15+
16+
public String getId() { return id; }
17+
public void setId(String id) { this.id = id; }
18+
public String getName() { return name; }
19+
public void setName(String name) { this.name = name; }
20+
public String getDescription() { return description; }
21+
public void setDescription(String description) { this.description = description; }
22+
public Type getType() { return type; }
23+
public void setType(Type type) { this.type = type == null ? Type.CONSUMABLE : type; }
24+
25+
public Map<String, Double> getModifiers() { return modifiers; }
26+
public void setModifiers(Map<String, Double> modifiers) { this.modifiers = modifiers == null ? new HashMap<>() : modifiers; }
27+
}

0 commit comments

Comments
 (0)