7 Commits

15 changed files with 314 additions and 17 deletions

1
.envrc Normal file
View File

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

3
.gitignore vendored
View File

@@ -1,2 +1,5 @@
.direnv
build/ build/
data.trace.db 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 "VALUE" VARCHAR
); );
ALTER TABLE "PUBLIC"."CONFIG" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_7" PRIMARY KEY("KEY"); 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 INSERT INTO "PUBLIC"."CONFIG" VALUES
('start_game', 'Hero Home,Main,Start'), ('start_game', 'Hero Home,Main,Start'),
('screen', '1000x800'), ('screen', '1000x800'),
('camera_scale', '2'), ('camera_scale', '2'),
('full_day_duration', '600'); ('full_day_duration', '600'),
('initial_time', '08:30');
CREATE MEMORY TABLE "PUBLIC"."LEVELS"( 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", "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 "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
}

28
flake.nix Normal file
View File

@@ -0,0 +1,28 @@
{
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;
};
});
}

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, "x": 3,
"y": 12, "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);" "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, "x": 5,
"y": 4, "y": 4,
@@ -100,6 +96,10 @@
"x": 18, "x": 18,
"y": 12, "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();" "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": [{ "labels": [{
"label": "entry", "label": "entry",
@@ -109,6 +109,14 @@
"label": "Start", "label": "Start",
"x": 10, "x": 10,
"y": 14 "y": 14
}, {
"label": "Grandma Waking",
"x": 11,
"y": 14
}, {
"label": "Grandma Origin",
"x": 11,
"y": 7
}] }]
} }
}, { }, {

View File

@@ -44,6 +44,12 @@
"name": "FSM 5", "name": "FSM 5",
"rows": 16, "rows": 16,
"columns": 16 "columns": 16
}, {
"uid": "413e09cf-ba41-4fe8-ac47-9697b5ad0245",
"source": "413e09cf-ba41-4fe8-ac47-9697b5ad0245.png",
"name": "Forrest",
"rows": 35,
"columns": 16
}], }],
"characterSets": [{ "characterSets": [{
"uid": "0dcbaf26-d634-4ca8-9691-7a8ff966f702", "uid": "0dcbaf26-d634-4ca8-9691-7a8ff966f702",
@@ -204,6 +210,12 @@
"name": "Heart Emoji", "name": "Heart Emoji",
"rows": 6, "rows": 6,
"columns": 4 "columns": 4
}, {
"uid": "78563669-8a6c-4024-82c8-7e4da5b76edd",
"source": "78563669-8a6c-4024-82c8-7e4da5b76edd.png",
"name": "Zzz",
"rows": 7,
"columns": 4
}], }],
"sounds": [{ "sounds": [{
"uid": "1311327d-4b74-4252-94da-23ee4129e357", "uid": "1311327d-4b74-4252-94da-23ee4129e357",

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.ai.NPC;
import com.bartlomiejpluta.base.api.character.Character; import com.bartlomiejpluta.base.api.character.Character;
import com.bartlomiejpluta.base.api.context.ContextHolder; 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.NoopAI;
import com.bartlomiejpluta.base.lib.ai.RandomMovementAI; import com.bartlomiejpluta.base.lib.ai.RandomMovementAI;
import com.bartlomiejpluta.base.util.path.Path;
import com.bartlomiejpluta.base.util.random.DiceRoller; import com.bartlomiejpluta.base.util.random.DiceRoller;
import com.bartlomiejpluta.demo.world.item.Item; import com.bartlomiejpluta.demo.world.item.Item;
import lombok.Getter; import lombok.Getter;
@@ -74,6 +76,20 @@ public class Friend extends Creature implements NPC {
return this; 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) { public Friend randomMovementAI(float intervalSeconds, Vector2ic origin, int radius) {
this.strategy = new RandomMovementAI<>(this, intervalSeconds, origin, radius); this.strategy = new RandomMovementAI<>(this, intervalSeconds, origin, radius);
return this; 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.character.Character;
import com.bartlomiejpluta.base.api.context.Context; import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.context.ContextHolder; 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.base.lib.character.CharacterDelegate;
import com.bartlomiejpluta.demo.runner.DemoRunner; 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 { public abstract class NamedCharacter extends CharacterDelegate {
protected final Context context; protected final Context context;
@@ -21,4 +27,22 @@ public abstract class NamedCharacter extends CharacterDelegate {
public int getDialogNameColor() { public int getDialogNameColor() {
return 0xFFFFFF; 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 dayNightCycle = false;
protected boolean controls = true;
@Override @Override
public void onCreate(Context context, GameMap map) { public void onCreate(Context context, GameMap map) {
this.context = context; this.context = context;
@@ -73,6 +75,10 @@ public abstract class BaseMapHandler implements MapHandler {
player.interact(); player.interact();
} }
if (!controls) {
return;
}
InputUtil.handleBasicControl(player, input); InputUtil.handleBasicControl(player, input);
} }

View File

@@ -1,25 +1,51 @@
package com.bartlomiejpluta.demo.map; package com.bartlomiejpluta.demo.map;
import A.animations;
import A.maps;
import com.bartlomiejpluta.base.api.context.Context; import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.map.model.GameMap; 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 com.bartlomiejpluta.demo.entity.Friend;
import java.util.concurrent.CompletableFuture; 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 { public class HeroHomeHandler extends BaseMapHandler {
private final PathFinder finder = new AstarPathFinder(100);
@Override @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); 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) { protected CompletableFuture<Object> triggerGrandmaDialog(Friend grandma) {
return dialog(player, "Good morning Grandma, how are you doing?") return dialog(grandma, "What are you going to do today, Luna?")
.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?"))
.thenCompose(n -> dialogChoice(player, .thenCompose(n -> dialogChoice(player,
"I'm going to fix your roof, Grandma", "I'm going to fix your roof, Grandma",
"I'd like to look for some hidden treasure around your house", "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) { protected CompletableFuture<Object> triggerNekoDialog(Friend neko) {
return dialog(player, "Ohhh, here you are Kitty...") return dialog(player, "Ohhh, here you are Kitty...")
.thenCompose(n -> CompletableFuture.allOf( .thenCompose(n -> CompletableFuture.allOf(
simple(A.animations.heart_emoji.$).scale(0.4f).animationSpeed(1.7f).offset(0, -30).run(context, player), player.runEmoji(animations.heart_emoji.$),
simple(A.animations.heart_emoji.$).scale(0.4f).animationSpeed(1.7f).offset(0, -15).delay(100).run(context, neko) neko.runEmoji(animations.heart_emoji.$, r -> r.offset(0, -15).delay(100))
)) ))
.thenCompose(n -> dialog(neko, "Meow, meow...")) .thenCompose(n -> dialog(neko, "Meow, meow..."))
.thenCompose(n -> dialog(neko, "Purr, purr...")); .thenCompose(n -> dialog(neko, "Purr, purr..."));

View File

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

View File

@@ -6,7 +6,11 @@ import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import static java.lang.Math.max;
import static java.lang.Math.min;
public class WorldTime { public class WorldTime {
private static final float MINUTES_PER_DAY = 1440f; // 60min * 24
@Getter @Getter
private float progress = 0; private float progress = 0;
@@ -18,6 +22,19 @@ public class WorldTime {
this.period = Float.parseFloat(dao.config.find("full_day_duration").getValue()); 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() { public int getHour() {
return (int) (progress * 24); return (int) (progress * 24);
} }