9 Commits
v0.5 ... master

Author SHA1 Message Date
cf0df54548 Update map assets references in project.json
Because BASE Editor is reusing binary deserializer for JSON serializer,
the map assets 'source' property in project.json points to binary files
with ".dat" extension. It does not impact on how the editor works
because the asset sources are recreated when importing using UIDs, so
just to be consistent all the .json file references were replaced with
the .dat ones.
2025-07-11 15:06:08 +02:00
e8cc766ded Improve support for Nix dev shell 2025-07-11 14:41:10 +02:00
2e4459b92c Change initial world time 2025-07-11 14:30:14 +02:00
d66aabd58a Use BASE editor CLI interface to build app seamlessly 2025-07-11 10:09:47 +02:00
1e8ff04930 Improve Nix derivation 2025-07-10 15:12:16 +02:00
8edbc48ff1 Migrate Nix shell config to Flake 2023-12-09 22:34:56 +01:00
9d42bb8ee3 Enable BASE on NixOS platform 2023-12-07 22:48:42 +01:00
d08ba8eb5a Fix missing Forrest tileset 2023-11-16 14:17:59 +01:00
990ad6519e Improve the game start with some simple Grandma dialog 2023-11-16 14:17:59 +01:00
16 changed files with 325 additions and 20 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
.direnv
build/
data.trace.db
*.jar
result

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -107,12 +107,13 @@ CREATE MEMORY TABLE "PUBLIC"."CONFIG"(
"VALUE" VARCHAR
);
ALTER TABLE "PUBLIC"."CONFIG" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_7" PRIMARY KEY("KEY");
-- 4 +/- SELECT COUNT(*) FROM PUBLIC.CONFIG;
-- 5 +/- SELECT COUNT(*) FROM PUBLIC.CONFIG;
INSERT INTO "PUBLIC"."CONFIG" VALUES
('start_game', 'Hero Home,Main,Start'),
('screen', '1000x800'),
('camera_scale', '2'),
('full_day_duration', '600');
('full_day_duration', '600'),
('initial_time', '08:30');
CREATE MEMORY TABLE "PUBLIC"."LEVELS"(
"LEVEL" INT DEFAULT NEXT VALUE FOR "PUBLIC"."SYSTEM_SEQUENCE_704587BB_DC0E_44AB_A7F0_3DE0CA44FE3F" NOT NULL NULL_TO_DEFAULT SEQUENCE "PUBLIC"."SYSTEM_SEQUENCE_704587BB_DC0E_44AB_A7F0_3DE0CA44FE3F",
"MAX_HP" VARCHAR NOT NULL

111
flake.lock generated Normal file
View File

@@ -0,0 +1,111 @@
{
"nodes": {
"base": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"gradle2nix": "gradle2nix",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1752221128,
"narHash": "sha256-dFWsRVenkLOtbBx67JfAjVD5PyiKJY4TMCbXbf7bGT4=",
"ref": "refs/heads/master",
"rev": "41cc804cc3855553587eafd13597054d93097972",
"revCount": 600,
"type": "git",
"url": "https://git.orleander.pl/bartek/base.git"
},
"original": {
"type": "git",
"url": "https://git.orleander.pl/bartek/base.git"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gradle2nix": {
"inputs": {
"flake-utils": [
"base",
"flake-utils"
],
"nixpkgs": [
"base",
"nixpkgs"
]
},
"locked": {
"lastModified": 1743629487,
"narHash": "sha256-MjnEgT9MhO2HknLhrx7GvBRVxdOzSKydIJMyzawe2Fk=",
"owner": "tadfisher",
"repo": "gradle2nix",
"rev": "293ecbdc10d32d9d4bdc2d23213b9be09ce247ee",
"type": "github"
},
"original": {
"owner": "tadfisher",
"ref": "v2",
"repo": "gradle2nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1751943650,
"narHash": "sha256-7orTnNqkGGru8Je6Un6mq1T8YVVU/O5kyW4+f9C1mZQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88983d4b665fb491861005137ce2b11a9f89f203",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-25.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"base": "base",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

30
flake.nix Normal file
View File

@@ -0,0 +1,30 @@
{
description = "The BASE engine demo game";
inputs = {
nixpkgs.url = "nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils";
base.url = "git+https://git.orleander.pl/bartek/base.git";
base.inputs.nixpkgs.follows = "nixpkgs";
base.inputs.flake-utils.follows = "flake-utils";
};
outputs = inputs @ {
self,
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {inherit system;};
in {
packages = rec {
game = pkgs.callPackage ./game.nix (inputs // {inherit system;});
default = game;
};
devShells.default = pkgs.callPackage ./shell.nix {inherit system;};
});
}

44
game.nix Normal file
View File

@@ -0,0 +1,44 @@
{
pkgs,
base,
lib,
stdenv,
system,
makeWrapper,
jdk17,
xorg,
openjfx17,
glib,
alsa-lib,
libGL,
gtk3,
...
}:
stdenv.mkDerivation rec {
pname = "base-demo";
version = "0.0.1";
src = ./.;
nativeBuildInputs = [base.packages.${system}.default makeWrapper];
buildPhase = ''
base-editor -bHp $src/project.json -o build
'';
installPhase = ''
mkdir -p $out/bin
mkdir -p $out/share/java
cp build/out/game.jar $out/share/java/base-demo-game.jar
makeWrapper "${jdk17}/bin/java" $out/bin/base-demo \
--add-flags "-jar $out/share/java/base-demo-game.jar" \
--prefix LD_LIBRARY_PATH : "${xorg.libXtst}/lib" \
--prefix LD_LIBRARY_PATH : "${openjfx17}/lib" \
--prefix LD_LIBRARY_PATH : "${glib.out}/lib" \
--prefix LD_LIBRARY_PATH : "${alsa-lib}/lib" \
--prefix LD_LIBRARY_PATH : "${libGL}/lib" \
--prefix LD_LIBRARY_PATH : "${gtk3}/lib"
'';
}

View File

@@ -84,10 +84,6 @@
"x": 3,
"y": 12,
"code": "/* \n * Following final parameters are available to use:\n * x: int - the x coordinate of tile the object has been created on\n * y: int - the y coordinate of tile the object has been created on \n * layer: ObjectLayer - current object layer\n * map: GameMap - current map\n */\nwarp(here, A.maps.hero_house.main.home);"
}, {
"x": 11,
"y": 7,
"code": "/* \n * Following final parameters are available to use:\n * here: MapPin - the composite object containing current map UID, \n * layer\u0027s index and x,y coordinates of the current tile \n * x: int - the x coordinate of the current tile\n * y: int - the y coordinate of the current tile \n * layer: ObjectLayer - current object layer\n * map: GameMap - current map\n */\nfriend(here, \"grandma\")\n\t.randomMovementAI(4f, here.toCoordinates(), 5)\n\t.interaction(this::triggerGrandmaDialog);"
}, {
"x": 5,
"y": 4,
@@ -100,6 +96,10 @@
"x": 18,
"y": 12,
"code": "/* \n * Following final parameters are available to use:\n * here: MapPin - the composite object containing current map UID, \n * layer\u0027s index and x,y coordinates of the current tile \n * x: int - the x coordinate of the current tile\n * y: int - the y coordinate of the current tile \n * layer: ObjectLayer - current object layer\n * map: GameMap - current map \n * handler: HeroHomeHandler - current map handler\n * runner: DemoRunner - the game runner of the project\n * context: Context - the game context\n */\nchest(here, \"enforced_chest_left\")\n\t.addItem(new MeleeWeapon(\"wooden_sword\"))\n\t.addItem(new Medicament(\"small_life_potion\", 4))\n\t.shuffle();"
}, {
"x": 13,
"y": 15,
"code": "/* \n * Following final parameters are available to use:\n * here: MapPin - the composite object containing current map UID, \n * layer\u0027s index and x,y coordinates of the current tile \n * x: int - the x coordinate of the current tile\n * y: int - the y coordinate of the current tile \n * layer: ObjectLayer - current object layer\n * map: GameMap - current map\n */\nfriend(here, \"grandma\")\n\t.followPath(this::grandmaPath)\n\t.interaction(this::triggerGrandmaDialog);"
}],
"labels": [{
"label": "entry",
@@ -109,6 +109,14 @@
"label": "Start",
"x": 10,
"y": 14
}, {
"label": "Grandma Waking",
"x": 11,
"y": 14
}, {
"label": "Grandma Origin",
"x": 11,
"y": 7
}]
}
}, {

View File

@@ -3,15 +3,15 @@
"runner": "com.bartlomiejpluta.demo.runner.DemoRunner",
"maps": [{
"uid": "d1b85d85-c52a-46f5-b81e-444847f8ddae",
"source": "d1b85d85-c52a-46f5-b81e-444847f8ddae.json",
"source": "d1b85d85-c52a-46f5-b81e-444847f8ddae.dat",
"name": "Hero Home"
}, {
"uid": "b602601a-e9b0-44bf-bc0d-5f31c9964ba1",
"source": "b602601a-e9b0-44bf-bc0d-5f31c9964ba1.json",
"source": "b602601a-e9b0-44bf-bc0d-5f31c9964ba1.dat",
"name": "Hero House"
}, {
"uid": "8fbb151f-682a-4357-ba92-157e4097898f",
"source": "8fbb151f-682a-4357-ba92-157e4097898f.json",
"source": "8fbb151f-682a-4357-ba92-157e4097898f.dat",
"name": "Forrest"
}],
"tileSets": [{
@@ -44,6 +44,12 @@
"name": "FSM 5",
"rows": 16,
"columns": 16
}, {
"uid": "413e09cf-ba41-4fe8-ac47-9697b5ad0245",
"source": "413e09cf-ba41-4fe8-ac47-9697b5ad0245.png",
"name": "Forrest",
"rows": 35,
"columns": 16
}],
"characterSets": [{
"uid": "0dcbaf26-d634-4ca8-9691-7a8ff966f702",
@@ -204,6 +210,12 @@
"name": "Heart Emoji",
"rows": 6,
"columns": 4
}, {
"uid": "78563669-8a6c-4024-82c8-7e4da5b76edd",
"source": "78563669-8a6c-4024-82c8-7e4da5b76edd.png",
"name": "Zzz",
"rows": 7,
"columns": 4
}],
"sounds": [{
"uid": "1311327d-4b74-4252-94da-23ee4129e357",

6
shell.nix Normal file
View File

@@ -0,0 +1,6 @@
{pkgs, ...}:
pkgs.mkShell {
name = "base-demo-game-development-shell";
LD_LIBRARY_PATH = with pkgs; "${xorg.libXtst}/lib:${openjfx17}/lib:${glib.out}/lib:${alsa-lib}/lib:${libGL}/lib:${gtk3}/lib";
}

View File

@@ -5,8 +5,10 @@ import com.bartlomiejpluta.base.api.ai.AI;
import com.bartlomiejpluta.base.api.ai.NPC;
import com.bartlomiejpluta.base.api.character.Character;
import com.bartlomiejpluta.base.api.context.ContextHolder;
import com.bartlomiejpluta.base.lib.ai.FollowPathAI;
import com.bartlomiejpluta.base.lib.ai.NoopAI;
import com.bartlomiejpluta.base.lib.ai.RandomMovementAI;
import com.bartlomiejpluta.base.util.path.Path;
import com.bartlomiejpluta.base.util.random.DiceRoller;
import com.bartlomiejpluta.demo.world.item.Item;
import lombok.Getter;
@@ -74,6 +76,20 @@ public class Friend extends Creature implements NPC {
return this;
}
public Friend followPath(@NonNull Path<Friend> path) {
var ai = new FollowPathAI<>(this);
ai.setPath(path);
this.strategy = ai;
return this;
}
public Friend followPath(@NonNull Function<Friend, Path<Friend>> pathSupplier) {
var ai = new FollowPathAI<>(this);
ai.setPath(pathSupplier.apply(this));
this.strategy = ai;
return this;
}
public Friend randomMovementAI(float intervalSeconds, Vector2ic origin, int radius) {
this.strategy = new RandomMovementAI<>(this, intervalSeconds, origin, radius);
return this;

View File

@@ -3,8 +3,14 @@ package com.bartlomiejpluta.demo.entity;
import com.bartlomiejpluta.base.api.character.Character;
import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.context.ContextHolder;
import com.bartlomiejpluta.base.lib.animation.AnimationRunner;
import com.bartlomiejpluta.base.lib.animation.SimpleAnimationRunner;
import com.bartlomiejpluta.base.lib.character.CharacterDelegate;
import com.bartlomiejpluta.demo.runner.DemoRunner;
import lombok.NonNull;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public abstract class NamedCharacter extends CharacterDelegate {
protected final Context context;
@@ -21,4 +27,22 @@ public abstract class NamedCharacter extends CharacterDelegate {
public int getDialogNameColor() {
return 0xFFFFFF;
}
public CompletableFuture<Void> runEmoji(@NonNull String animation) {
return runEmoji(animation, null);
}
public CompletableFuture<Void> runEmoji(@NonNull String animation, Consumer<SimpleAnimationRunner> customizer) {
var runner = AnimationRunner
.simple(animation)
.scale(0.4f)
.animationSpeed(1.6f)
.offset(0, -30);
if (customizer != null) {
customizer.accept(runner);
}
return runner.run(context, this);
}
}

View File

@@ -41,6 +41,8 @@ public abstract class BaseMapHandler implements MapHandler {
protected boolean dayNightCycle = false;
protected boolean controls = true;
@Override
public void onCreate(Context context, GameMap map) {
this.context = context;
@@ -73,6 +75,10 @@ public abstract class BaseMapHandler implements MapHandler {
player.interact();
}
if (!controls) {
return;
}
InputUtil.handleBasicControl(player, input);
}

View File

@@ -1,25 +1,51 @@
package com.bartlomiejpluta.demo.map;
import A.animations;
import A.maps;
import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.map.model.GameMap;
import com.bartlomiejpluta.base.util.path.CharacterPath;
import com.bartlomiejpluta.base.util.pathfinder.AstarPathFinder;
import com.bartlomiejpluta.base.util.pathfinder.PathFinder;
import com.bartlomiejpluta.demo.entity.Friend;
import java.util.concurrent.CompletableFuture;
import static com.bartlomiejpluta.base.lib.animation.AnimationRunner.simple;
import static com.bartlomiejpluta.base.api.move.Direction.LEFT;
public class HeroHomeHandler extends BaseMapHandler {
private final PathFinder finder = new AstarPathFinder(100);
@Override
public void onOpen(Context context, GameMap map) {
public void onCreate(Context context, GameMap map) {
super.onCreate(context, map);
map.setAmbientColor(0.05f, 0.01f, 0.01f);
dialog(player, "Ahhh, another beautiful day for an adventure... Let's go!");
}
protected CharacterPath<Friend> grandmaPath(Friend grandma) {
return new CharacterPath<Friend>()
.run(() -> controls = false)
.run(() -> player.runEmoji(A.animations.zzz.$, r -> r.repeat(5)))
.insertPath(finder.findPath(grandma.getLayer(), grandma, maps.hero_home.main.grandma_waking.toCoordinates()))
.wait(2f)
.turn(LEFT)
.wait(1f)
.suspend(() -> morningDialogWithGrandma(grandma))
.wait(1f)
.run(() -> controls = true)
.insertPath(finder.findPath(grandma.getLayer(), maps.hero_home.main.grandma_waking.toCoordinates(), maps.hero_home.main.grandma_origin.toCoordinates()))
.run(() -> grandma.randomMovementAI(4f, grandma.getCoordinates(), 4));
}
private CompletableFuture<Object> morningDialogWithGrandma(Friend grandma) {
return dialog(grandma, "Hello Honey, wake up, it's another beautiful day!")
.thenCompose(n -> dialog(player, "Ahhh, good morning Grandma!"))
.thenCompose(n -> dialog(grandma, "Have a wonderful day, Luna!"))
.thenCompose(n -> dialog(player, "Thank you Grandma, have a nice day too!"));
}
protected CompletableFuture<Object> triggerGrandmaDialog(Friend grandma) {
return dialog(player, "Good morning Grandma, how are you doing?")
.thenCompose(n -> dialog(grandma, "Hello Honey... I'm fine thank you. Have a sit, I will bring you a breakfast in a moment."))
.thenCompose(n -> dialog(player, "Thank you Grandma."))
.thenCompose(n -> dialog(grandma, "What are you going to do today, Luna?"))
return dialog(grandma, "What are you going to do today, Luna?")
.thenCompose(n -> dialogChoice(player,
"I'm going to fix your roof, Grandma",
"I'd like to look for some hidden treasure around your house",
@@ -46,8 +72,8 @@ public class HeroHomeHandler extends BaseMapHandler {
protected CompletableFuture<Object> triggerNekoDialog(Friend neko) {
return dialog(player, "Ohhh, here you are Kitty...")
.thenCompose(n -> CompletableFuture.allOf(
simple(A.animations.heart_emoji.$).scale(0.4f).animationSpeed(1.7f).offset(0, -30).run(context, player),
simple(A.animations.heart_emoji.$).scale(0.4f).animationSpeed(1.7f).offset(0, -15).delay(100).run(context, neko)
player.runEmoji(animations.heart_emoji.$),
neko.runEmoji(animations.heart_emoji.$, r -> r.offset(0, -15).delay(100))
))
.thenCompose(n -> dialog(neko, "Meow, meow..."))
.thenCompose(n -> dialog(neko, "Purr, purr..."));

View File

@@ -74,13 +74,13 @@ public class DemoRunner implements GameRunner {
var layer = map.layer(start[1]);
var label = layer.label(start[2]);
time.setTime(dao.config.find("initial_time").getValue());
context.openMap(map.$);
context.getMap().getObjectLayer(layer.$).addEntity(this.player);
player.setCoordinates(label.getX(), label.getY());
context.resume();
hud.show();
var x = A.maps.hero_home.main.entry;
}
public void returnToStartMenu() {

View File

@@ -6,7 +6,11 @@ import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import static java.lang.Math.max;
import static java.lang.Math.min;
public class WorldTime {
private static final float MINUTES_PER_DAY = 1440f; // 60min * 24
@Getter
private float progress = 0;
@@ -18,6 +22,19 @@ public class WorldTime {
this.period = Float.parseFloat(dao.config.find("full_day_duration").getValue());
}
public void setProgress(float progress) {
this.progress = max(0, min(1, progress));
}
public void setTime(@NonNull String time) {
var splitted = time.split(":");
this.setTime(Integer.valueOf(splitted[0], 10), Integer.valueOf(splitted[1], 10));
}
public void setTime(int h, int m) {
this.progress = (h * 60 + m) / MINUTES_PER_DAY;
}
public int getHour() {
return (int) (progress * 24);
}