Skip to content

Commit 7ea0bee

Browse files
committed
feat(editor): deepen scene2d block rule controls (v2.0.21)
1 parent 16c9704 commit 7ea0bee

4 files changed

Lines changed: 148 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [2.0.21] - 2026-04-05
6+
7+
### Added
8+
- **Scene2D Deeper Block Rule Editing:** `CustomGameEditor.java` now exposes a focused second-pass block authoring set with special color, special chance/frequency, flashing special, match-any-color, and counter-type controls.
9+
10+
### Changed
11+
- Expanded block detail readouts so authored special behavior is visible alongside color and usage data.
12+
- Bumped `VERSION.md` to `2.0.21`.
13+
14+
### Validation
15+
- `./gradlew compileJava`
16+
517
## [2.0.20] - 2026-04-05
618

719
### Added

HANDOFF.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,16 @@ This session focused on the newer Scene2D `CustomGameEditor` so the Java port ha
115115
- Added a focused first-pass block authoring workflow with block navigation, rename/color editing, normal/garbage/filler usage toggles, and block override assignment for the selected piece.
116116
- Bumped the Java repo version again to `2.0.20`.
117117

118+
### Additional Follow-Up - 2026-04-05 (Deeper Block Rule Editing)
119+
- Added a focused second-pass block authoring set with special color, special chance/frequency, flashing special, match-any-color, and counter-type controls.
120+
- Expanded block detail readouts so authored special behavior is visible alongside color and usage data.
121+
- Bumped the Java repo version again to `2.0.21`.
122+
118123
### Recommended Next Steps
119124
1. Bridge Scene2D custom editor state into the older TWL editor stack where useful.
120125
2. Replace the temporary reflection bridge with a direct adapter once the exact TWL/LWJGL API contract is fully normalized.
121126
3. Add a targeted Java regression test or smoke harness for nD puzzle bootstrap + editor launch.
122-
4. Consider deeper block-rule editing (special blocks, extra colors, special colors) as the next content-authoring expansion.
127+
4. Consider further block-rule depth like extra colors, special piece conversion hooks, and chain-behavior flags.
123128

124129
## Summary
125130
This session focused on modernizing the internal Swing-based Editor tools (`SpriteEditor`, `MapCanvas`, `DialogueEditor`) to include features found in industry-standard tools like Aseprite and Pyxel Edit.

VERSION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.20
1+
2.0.21

src/main/java/com/bobsgame/client/engine/game/gui/customGameEditor/CustomGameEditor.java

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public class CustomGameEditor extends Scene2DPanel {
4242
private final TextButton nextBlockBtn;
4343
private final TextButton renameBlockBtn;
4444
private final TextButton recolorBlockBtn;
45+
private final TextButton recolorSpecialBlockBtn;
46+
private final TextButton blockSpecialChanceBtn;
47+
private final TextButton blockSpecialFrequencyBtn;
4548
private final TextButton assignBlockToPieceBtn;
4649
private final TextButton clearPieceBlockBtn;
4750
private final TextButton addPieceBtn;
@@ -77,6 +80,9 @@ public class CustomGameEditor extends Scene2DPanel {
7780
private final CheckBox blockUseNormalCheckbox;
7881
private final CheckBox blockUseGarbageCheckbox;
7982
private final CheckBox blockUseFillerCheckbox;
83+
private final CheckBox blockFlashingCheckbox;
84+
private final CheckBox blockMatchAnyColorCheckbox;
85+
private final CheckBox blockCounterCheckbox;
8086
private final CheckBox cascadeGravityCheckbox;
8187
private final CheckBox disconnectedGravityCheckbox;
8288
private final CheckBox chainRowCheckbox;
@@ -141,6 +147,9 @@ public CustomGameEditor(Engine engine) {
141147
blockUseNormalCheckbox = new CheckBox(" Use in normal pieces", skin);
142148
blockUseGarbageCheckbox = new CheckBox(" Use as garbage", skin);
143149
blockUseFillerCheckbox = new CheckBox(" Use as filler", skin);
150+
blockFlashingCheckbox = new CheckBox(" Flashing special", skin);
151+
blockMatchAnyColorCheckbox = new CheckBox(" Match any color", skin);
152+
blockCounterCheckbox = new CheckBox(" Counter type", skin);
144153
cascadeGravityCheckbox = new CheckBox(" Cascade gravity", skin);
145154
disconnectedGravityCheckbox = new CheckBox(" Disconnected-only gravity", skin);
146155
chainRowCheckbox = new CheckBox(" Chain rows", skin);
@@ -177,6 +186,9 @@ public CustomGameEditor(Engine engine) {
177186
nextBlockBtn = new TextButton("Block >", skin);
178187
renameBlockBtn = new TextButton("Rename Block", skin);
179188
recolorBlockBtn = new TextButton("Set Block Color", skin);
189+
recolorSpecialBlockBtn = new TextButton("Set Special Color", skin);
190+
blockSpecialChanceBtn = new TextButton("Special Chance", skin);
191+
blockSpecialFrequencyBtn = new TextButton("Special Frequency", skin);
180192
assignBlockToPieceBtn = new TextButton("Assign Block To Piece", skin);
181193
clearPieceBlockBtn = new TextButton("Clear Piece Block", skin);
182194
addPieceBtn = new TextButton("Add Piece Type", skin);
@@ -222,6 +234,9 @@ public CustomGameEditor(Engine engine) {
222234
blockControlsRow1.add(nextBlockBtn);
223235
blockControlsRow1.add(renameBlockBtn);
224236
blockControlsRow1.add(recolorBlockBtn);
237+
blockControlsRow1.add(recolorSpecialBlockBtn);
238+
blockControlsRow1.add(blockSpecialChanceBtn);
239+
blockControlsRow1.add(blockSpecialFrequencyBtn);
225240

226241
Table blockControlsRow2 = new Table();
227242
blockControlsRow2.defaults().pad(4);
@@ -230,6 +245,9 @@ public CustomGameEditor(Engine engine) {
230245
blockControlsRow2.add(blockUseNormalCheckbox);
231246
blockControlsRow2.add(blockUseGarbageCheckbox);
232247
blockControlsRow2.add(blockUseFillerCheckbox);
248+
blockControlsRow2.add(blockFlashingCheckbox);
249+
blockControlsRow2.add(blockMatchAnyColorCheckbox);
250+
blockControlsRow2.add(blockCounterCheckbox);
233251

234252
Table controlsRow1 = new Table();
235253
controlsRow1.defaults().pad(4);
@@ -393,6 +411,24 @@ public void clicked(InputEvent event, float x, float y) {
393411
recolorCurrentBlock();
394412
}
395413
});
414+
recolorSpecialBlockBtn.addListener(new ClickListener() {
415+
@Override
416+
public void clicked(InputEvent event, float x, float y) {
417+
recolorCurrentSpecialBlock();
418+
}
419+
});
420+
blockSpecialChanceBtn.addListener(new ClickListener() {
421+
@Override
422+
public void clicked(InputEvent event, float x, float y) {
423+
editCurrentBlockSpecialChance();
424+
}
425+
});
426+
blockSpecialFrequencyBtn.addListener(new ClickListener() {
427+
@Override
428+
public void clicked(InputEvent event, float x, float y) {
429+
editCurrentBlockSpecialFrequency();
430+
}
431+
});
396432
assignBlockToPieceBtn.addListener(new ClickListener() {
397433
@Override
398434
public void clicked(InputEvent event, float x, float y) {
@@ -423,6 +459,24 @@ public void clicked(InputEvent event, float x, float y) {
423459
applySelectedBlockUsage();
424460
}
425461
});
462+
blockFlashingCheckbox.addListener(new ClickListener() {
463+
@Override
464+
public void clicked(InputEvent event, float x, float y) {
465+
applySelectedBlockUsage();
466+
}
467+
});
468+
blockMatchAnyColorCheckbox.addListener(new ClickListener() {
469+
@Override
470+
public void clicked(InputEvent event, float x, float y) {
471+
applySelectedBlockUsage();
472+
}
473+
});
474+
blockCounterCheckbox.addListener(new ClickListener() {
475+
@Override
476+
public void clicked(InputEvent event, float x, float y) {
477+
applySelectedBlockUsage();
478+
}
479+
});
426480

427481
addPieceBtn.addListener(new ClickListener() {
428482
@Override
@@ -690,6 +744,70 @@ public void onCancel() {
690744
);
691745
}
692746

747+
private void recolorCurrentSpecialBlock() {
748+
final BlockType blockType = getSelectedBlock();
749+
if (blockType == null) return;
750+
final BobColor currentColor = blockType.specialColor == null ? (blockType.colors.isEmpty() ? new BobColor(255, 0, 255) : blockType.colors.get(0)) : blockType.specialColor;
751+
Engine.GUIManager().showStringDialog(
752+
"Set special color as hex (#RRGGBB)",
753+
bobColorToHex(currentColor),
754+
new Scene2DStringDialog.StringDialogListener() {
755+
@Override
756+
public void onResult(String text) {
757+
blockType.specialColor = parseHexColor(text, currentColor);
758+
refreshEditorState();
759+
pushRecentAction("Updated special block color for " + (blockType.name == null || blockType.name.isEmpty() ? "selected block" : blockType.name) + ".");
760+
}
761+
762+
@Override
763+
public void onCancel() {
764+
}
765+
}
766+
);
767+
}
768+
769+
private void editCurrentBlockSpecialChance() {
770+
final BlockType blockType = getSelectedBlock();
771+
if (blockType == null) return;
772+
Engine.GUIManager().showNumberDialog(
773+
"Set special chance (1 in N, 0 disables)",
774+
blockType.randomSpecialBlockChanceOneOutOf,
775+
new com.bobsgame.client.engine.game.gui.Scene2DNumberDialog.NumberDialogListener() {
776+
@Override
777+
public void onResult(int value) {
778+
blockType.randomSpecialBlockChanceOneOutOf = Math.max(0, value);
779+
refreshEditorState();
780+
pushRecentAction("Updated special block chance for " + (blockType.name == null || blockType.name.isEmpty() ? "selected block" : blockType.name) + ".");
781+
}
782+
783+
@Override
784+
public void onCancel() {
785+
}
786+
}
787+
);
788+
}
789+
790+
private void editCurrentBlockSpecialFrequency() {
791+
final BlockType blockType = getSelectedBlock();
792+
if (blockType == null) return;
793+
Engine.GUIManager().showNumberDialog(
794+
"Set special frequency (every N pieces, 0 disables)",
795+
blockType.frequencySpecialBlockTypeOnceEveryNPieces,
796+
new com.bobsgame.client.engine.game.gui.Scene2DNumberDialog.NumberDialogListener() {
797+
@Override
798+
public void onResult(int value) {
799+
blockType.frequencySpecialBlockTypeOnceEveryNPieces = Math.max(0, value);
800+
refreshEditorState();
801+
pushRecentAction("Updated special block frequency for " + (blockType.name == null || blockType.name.isEmpty() ? "selected block" : blockType.name) + ".");
802+
}
803+
804+
@Override
805+
public void onCancel() {
806+
}
807+
}
808+
);
809+
}
810+
693811
private String bobColorToHex(BobColor color) {
694812
return String.format("#%02x%02x%02x", color.ri(), color.gi(), color.bi());
695813
}
@@ -713,8 +831,11 @@ private void applySelectedBlockUsage() {
713831
blockType.isGarbageBlockType = blockUseGarbageCheckbox.isChecked();
714832
blockType.useAsPlayingFieldFiller = blockUseFillerCheckbox.isChecked();
715833
blockType.useAsPlayingFieldFillerBlock = blockUseFillerCheckbox.isChecked();
834+
blockType.flashingSpecialType = blockFlashingCheckbox.isChecked();
835+
blockType.matchAnyColor = blockMatchAnyColorCheckbox.isChecked();
836+
blockType.counterType = blockCounterCheckbox.isChecked();
716837
refreshEditorState();
717-
pushRecentAction("Updated usage flags for " + (blockType.name == null || blockType.name.isEmpty() ? "selected block" : blockType.name) + ".");
838+
pushRecentAction("Updated block behavior flags for " + (blockType.name == null || blockType.name.isEmpty() ? "selected block" : blockType.name) + ".");
718839
}
719840

720841
private void assignSelectedBlockToPiece() {
@@ -1550,6 +1671,9 @@ private void refreshEditorState() {
15501671
blockUseNormalCheckbox.setChecked(blockType != null && blockType.useInNormalPieces);
15511672
blockUseGarbageCheckbox.setChecked(blockType != null && (blockType.useAsGarbage || blockType.useAsGarbageBlock || blockType.isGarbageBlockType));
15521673
blockUseFillerCheckbox.setChecked(blockType != null && (blockType.useAsPlayingFieldFiller || blockType.useAsPlayingFieldFillerBlock));
1674+
blockFlashingCheckbox.setChecked(blockType != null && blockType.flashingSpecialType);
1675+
blockMatchAnyColorCheckbox.setChecked(blockType != null && blockType.matchAnyColor);
1676+
blockCounterCheckbox.setChecked(blockType != null && blockType.counterType);
15531677

15541678
cascadeGravityCheckbox.setChecked(currentGameType.moveDownAllLinesOverBlankSpacesAtOnce);
15551679
disconnectedGravityCheckbox.setChecked(currentGameType.gravityRule_onlyMoveDownDisconnectedBlocks);
@@ -1579,7 +1703,10 @@ private void refreshEditorState() {
15791703
blockDetailsLabel.setText(blockType == null
15801704
? "Add a block to start configuring block types."
15811705
: "Color: " + bobColorToHex(blockType.colors.isEmpty() ? new BobColor(128, 128, 128) : blockType.colors.get(0))
1582-
+ " | Usage: " + (blockType.useInNormalPieces ? "normal " : "") + (blockType.useAsGarbage || blockType.useAsGarbageBlock ? "garbage " : "") + (blockType.useAsPlayingFieldFiller || blockType.useAsPlayingFieldFillerBlock ? "filler" : "")
1706+
+ " | Special: " + bobColorToHex(blockType.specialColor == null ? (blockType.colors.isEmpty() ? new BobColor(255, 0, 255) : blockType.colors.get(0)) : blockType.specialColor)
1707+
+ " | Usage: " + (blockType.useInNormalPieces ? "normal " : "") + (blockType.useAsGarbage || blockType.useAsGarbageBlock ? "garbage " : "") + (blockType.useAsPlayingFieldFiller || blockType.useAsPlayingFieldFillerBlock ? "filler " : "")
1708+
+ "| Flags: " + (blockType.flashingSpecialType ? "flashing " : "") + (blockType.matchAnyColor ? "match-any " : "") + (blockType.counterType ? "counter " : "")
1709+
+ "| Chance/Frequency: " + blockType.randomSpecialBlockChanceOneOutOf + "/" + blockType.frequencySpecialBlockTypeOnceEveryNPieces
15831710
);
15841711
pieceLabel.setText(pieceType == null ? "Piece: none" : "Piece: " + pieceType.name + " (" + (selectedPieceIndex + 1) + "/" + currentGameType.pieceTypes.size() + ")" + " | Block override: " + selectedPieceBlockOverride);
15851712
rotationLabel.setText(rotation == null ? "Rotation: none" : "Rotation: " + selectedRotationIndex + " (" + getFilledCellCount(rotation) + " blocks)");

0 commit comments

Comments
 (0)