Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
63bc534
docs: make Dockerfile link relative
IsaacThoman Oct 14, 2025
3153873
feat: add server-side prop physics
IsaacThoman Oct 24, 2025
6c21eaa
docs: add idea run commands
IsaacThoman Oct 24, 2025
5412fb7
chore: upgrade to deno 2.5.4
IsaacThoman Oct 24, 2025
0b9367a
Merge branch 'deno-upgrade-2.5.4' into feat-prop-physics
IsaacThoman Oct 24, 2025
96b104e
chore: update imports to work w/ new Deno version
IsaacThoman Oct 24, 2025
76cb339
Merge pull request #345 from candirugame/feat-prop-physics
IsaacThoman Oct 24, 2025
aafd68e
feat: init world with props
IsaacThoman Oct 25, 2025
3413f45
feat: apply impulse when prop is shot
IsaacThoman Oct 25, 2025
a1bb611
feat: players repel props
IsaacThoman Oct 25, 2025
5bc4300
chose: smaller shot impulse constants
IsaacThoman Oct 25, 2025
1d00682
Merge pull request #346 from candirugame/feat-physics-player-interaction
IsaacThoman Oct 25, 2025
2ed1878
revert: set durability defaults to off
IsaacThoman Dec 16, 2025
54b865a
Merge pull request #347 from candirugame/revert-durability-default-off
IsaacThoman Dec 16, 2025
d6608c8
ci: properly cache deno deps
IsaacThoman Dec 16, 2025
d1b00d6
fmt: map.json
IsaacThoman Dec 16, 2025
ee3c2a2
fix: try/catch around .env write for prod
IsaacThoman Dec 16, 2025
42e4b96
Merge pull request #348 from candirugame/ci-fix-docker-cache
IsaacThoman Dec 16, 2025
c2354f9
fix: clear physics accum before map loads
IsaacThoman Dec 16, 2025
84fc1b9
Merge pull request #349 from candirugame/fix-no-physics-accum-before-…
IsaacThoman Dec 16, 2025
b088412
fix: use built-in secrets.GITHUB_TOKEN
IsaacThoman Dec 16, 2025
d86387f
chore: add idea docker run script
IsaacThoman Dec 17, 2025
58af6d1
feat: command auto-complete
IsaacThoman Dec 17, 2025
ae1352a
slightly better alpha
IsaacThoman Dec 17, 2025
106fea9
Merge pull request #350 from candirugame/feat-command-tab-complete
IsaacThoman Dec 17, 2025
a68ac5a
chore: delta time cap
IsaacThoman Dec 17, 2025
fe1e4ad
fix: still use default candiru.xyz peer even if unable to write to disk
IsaacThoman Dec 23, 2025
c914dff
Merge pull request #351 from candirugame/fix-default-peer-no-write
IsaacThoman Dec 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:
id-token: write # To enable use of OIDC for npm provenance

env:
DENO_VERSION: '2.4.2'
DENO_VERSION: '2.5.4'

jobs:
# Build Job
Expand Down Expand Up @@ -154,7 +154,6 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
with:
persist-credentials: 'false'
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v3
Expand All @@ -164,7 +163,7 @@ jobs:
run: npm clean-install
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.ACTIONS_DEPLOY_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_NAME: 'github-actions[bot]'
GIT_AUTHOR_EMAIL: 'github-actions[bot]@users.noreply.github.com'
GIT_COMMITTER_NAME: 'github-actions[bot]'
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,5 @@ dist
.nx/workspace-data/

servers.txt
candiru
candiru
*.mjs
17 changes: 17 additions & 0 deletions .run/dev.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="dev" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="deno task dev" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
17 changes: 17 additions & 0 deletions .run/docker.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="docker" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="deno task docker" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
17 changes: 17 additions & 0 deletions .run/start.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="start" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="deno task start" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="deno task start" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
86 changes: 50 additions & 36 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,59 +1,73 @@
# Define Deno version (using the version from your current Dockerfile)
ARG DENO_VERSION=2.4.2
# syntax=docker/dockerfile:1.7-labs
ARG DENO_VERSION=2.5.4
ARG BIN_IMAGE=denoland/deno:bin-${DENO_VERSION}

# Stage to get the Deno binary
# Stage: get the Deno binary
FROM ${BIN_IMAGE} AS bin

# Stage to get tini init system
FROM buildpack-deps:20.04-curl AS tini
# Stage: get tini
FROM buildpack-deps:22.04-curl AS tini
ARG TINI_VERSION=0.19.0
ARG TARGETARCH
# Download tini for the target architecture
RUN curl -fsSL https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-${TARGETARCH} \
--output /tini \
&& chmod +x /tini

# Final runtime stage using distroless/cc for a minimal image
FROM gcr.io/distroless/cc

# Set the working directory inside the container
# Stage: builder
FROM debian:bookworm-slim AS builder
WORKDIR /app
ENV DENO_DIR=/deno-dir
RUN mkdir -p "$DENO_DIR"

# Set Deno environment variables
ENV DENO_DIR=/deno-dir/
ENV DENO_INSTALL_ROOT=/usr/local
ARG DENO_VERSION
ENV DENO_VERSION=${DENO_VERSION}

# Copy the Deno binary from the 'bin' stage
# Copy Deno binary into builder
COPY --from=bin /deno /bin/deno

# Copy tini from the 'tini' stage
COPY --from=tini /tini /tini
# Copy dependency manifests first for better layer caching
COPY deno.json import_map.json ./

# Copy Deno configuration files
COPY deno.json .
COPY import_map.json .
# Cache dependencies before copying source (cache survives source changes)
RUN --mount=type=cache,target=/tmp/deno-cache bash -c "set -euo pipefail \
&& export DENO_DIR=/tmp/deno-cache \
&& /bin/deno cache --import-map=import_map.json main.ts || true"

# Copy the rest of your application source code
# Copy rest of app
COPY . .

RUN ["/bin/deno", "task", "build"]
# Build and cache final deps
RUN --mount=type=cache,target=/tmp/deno-cache bash -c "set -euo pipefail \
&& export DENO_DIR=/tmp/deno-cache \
&& /bin/deno task build \
&& /bin/deno task cache \
&& mkdir -p /deno-dir \
&& rm -rf /deno-dir/* \
&& cp -a /tmp/deno-cache/. /deno-dir"

# Final runtime stage
FROM debian:bookworm-slim

RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates tzdata \
&& rm -rf /var/lib/apt/lists/*

# Cache application dependencies using Deno
# This step downloads and caches the modules needed by main.ts
# Use exec form because distroless doesn't have /bin/sh
#RUN ["/bin/deno", "cache", "main.ts"]
RUN ["/bin/deno", "task", "cache"]
RUN useradd --uid 1993 --user-group deno \
&& mkdir -p /deno-dir /app

# Expose the port your application listens on
ENV DENO_DIR=/deno-dir
ENV DENO_INSTALL_ROOT=/usr/local
ARG DENO_VERSION
ENV DENO_VERSION=${DENO_VERSION}

COPY --from=bin /deno /usr/bin/deno
COPY --from=tini /tini /tini
RUN chmod +x /usr/bin/deno /tini

COPY --chown=deno:deno --from=builder /app /app
COPY --chown=deno:deno --from=builder /deno-dir /deno-dir

WORKDIR /app
EXPOSE 3000

# Set the entrypoint to run Deno via tini
# tini handles signal forwarding and zombie reaping
ENTRYPOINT ["/tini", "--", "/bin/deno"]
USER deno

# Set the default command to run your main application file
# Includes the necessary permissions based on your 'start' task in deno.json
CMD ["run", "--allow-read", "--allow-env", "--allow-net", "--allow-write", "main.ts"]
ENTRYPOINT ["/tini", "--", "/usr/bin/deno"]
CMD ["task", "startnobuild"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Live versions:
Run the project with either Deno 2 or Docker.

- Deno 2: https://deno.com/
- Dockerfile: https://github.com/candirugame/candiru/blob/dev/Dockerfile
- Dockerfile: [candiru/Dockerfile](Dockerfile)

## Running with Deno 2

Expand Down
14 changes: 13 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,22 @@
"lib": ["ES2020", "DOM", "DOM.Iterable", "deno.ns"]
},
"imports": {
"@dimforge/rapier3d-compat": "npm:@dimforge/rapier3d-compat@0.16.2",
"@gltf-transform/core": "npm:@gltf-transform/core@4.1.3",
"@gltf-transform/extensions": "npm:@gltf-transform/extensions@4.1.3",
"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
"@oak/oak": "jsr:@oak/oak@^17.1.3",
"@std/http": "jsr:@std/http@^1.0.10",
"vite": "npm:vite@^5.4.8"
"@std/path": "jsr:@std/path@^1.0.6",
"@std/path/from-file-url": "jsr:@std/path@^1.0.6/from-file-url",
"draco3dgltf": "npm:draco3dgltf@1.5.6",
"gl-matrix": "npm:gl-matrix@3.4.4",
"socket_io": "https://deno.land/x/socket_io@0.2.0/mod.ts",
"std/crypto": "https://deno.land/std@0.207.0/crypto/mod.ts",
"std/http/server": "https://deno.land/std@0.150.0/http/server.ts",
"std/fs/walk": "https://deno.land/std@0.207.0/fs/walk.ts",
"vite": "npm:vite@^5.4.8",
"zod": "https://deno.land/x/zod@v3.23.8/mod.ts"
},
"lint": {
"include": ["src/"],
Expand Down
46 changes: 45 additions & 1 deletion deno.lock

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion import_map.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
{
"imports": {
"@dimforge/rapier3d-compat": "npm:@dimforge/rapier3d-compat@0.16.2",
"@gltf-transform/core": "npm:@gltf-transform/core@4.1.3",
"@gltf-transform/extensions": "npm:@gltf-transform/extensions@4.1.3",
"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
"@oak/oak": "jsr:@oak/oak@^17.1.3",
"@std/http": "jsr:@std/http@^1.0.10",
"vite": "npm:vite@^5.4.8"
"@std/path": "jsr:@std/path@^1.0.6",
"@std/path/from-file-url": "jsr:@std/path@^1.0.6/from-file-url",
"draco3dgltf": "npm:draco3dgltf@1.5.6",
"gl-matrix": "npm:gl-matrix@3.4.4",
"socket_io": "https://deno.land/x/socket_io@0.2.0/mod.ts",
"std/crypto": "https://deno.land/std@0.207.0/crypto/mod.ts",
"std/http/server": "https://deno.land/std@0.150.0/http/server.ts",
"std/fs/walk": "https://deno.land/std@0.207.0/fs/walk.ts",
"vite": "npm:vite@^5.4.8",
"zod": "https://deno.land/x/zod@v3.23.8/mod.ts"
}
}
3 changes: 2 additions & 1 deletion public/maps/alpha_1/map.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@
"position": { "x": -1, "y": 0.25, "z": -6 },
"itemId": 3
}
]
],
"props": []
}
29 changes: 29 additions & 0 deletions public/maps/crackhouse_1/map.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,5 +227,34 @@
"position": { "x": -4.71, "y": 3.24, "z": 10.22 },
"itemId": 1
}
],
"props": [
{
"url": "/models/simplified_possum.glb",
"position": { "x": 9, "y": 5, "z": -5 },
"scale": { "x": 1, "y": 1, "z": 1 },
"name": "big guy",
"health": 100000,
"doPhysics": true,
"playersCollide": true
},
{
"url": "/models/simplified_possum.glb",
"position": { "x": 9, "y": 4, "z": -5 },
"scale": { "x": 1, "y": 1, "z": 1 },
"name": "big guy",
"health": 100000,
"doPhysics": true,
"playersCollide": true
},
{
"url": "/models/simplified_possum.glb",
"position": { "x": 9, "y": 6, "z": -5 },
"scale": { "x": 1, "y": 1, "z": 1 },
"name": "big guy",
"health": 100000,
"doPhysics": true,
"playersCollide": true
}
]
}
3 changes: 2 additions & 1 deletion public/maps/deathmatch_1/map.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@
"position": { "x": -1, "y": 0.25, "z": -6 },
"itemId": 2
}
]
],
"props": []
}
10 changes: 10 additions & 0 deletions public/maps/empty/map.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,15 @@
"position": { "x": 0, "y": 5, "z": 0 },
"itemId": 1
}
],
"props": [
{
"url": "/models/hexagon.glb",
"position": { "x": 0, "y": 1.5, "z": 0 },
"scale": { "x": 5, "y": 5, "z": 5 },
"name": "Showcase Hexagon",
"doPhysics": false,
"playersCollide": false
}
]
}
25 changes: 25 additions & 0 deletions src/client/core/CommandManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,31 @@ export class CommandManager {
}
return match;
}

public getCommandNames(): string[] {
return this.commands.map((cmd) => cmd.getCmdName());
}

public getSuggestions(input: string): string[] {
if (!input.startsWith('/')) return [];

const query = input.slice(1).toLowerCase().trim();
const parts = query.split(' ');

if (parts.length > 1) return [];

const clientCommands = this.commands.map((cmd) => cmd.getCmdName());
const serverCommands = ['help', 'kill', 'thumbsup', 'thumbsdown', 'octopus', 'goblin', 'ping', 'version', 'clear'];
const allCommands = [...new Set([...clientCommands, ...serverCommands])].sort();

if (query === '') {
return allCommands.map((cmd) => '/' + cmd);
}

return allCommands
.filter((cmd) => cmd.startsWith(query))
.map((cmd) => '/' + cmd);
}
}

class Command {
Expand Down
2 changes: 1 addition & 1 deletion src/client/core/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class Game {

animate() {
if (this.stopped) return;
const deltaTime = this.clock.getDelta();
const deltaTime = Math.min(this.clock.getDelta(), 1 / 5);

// Basic frame profiler when enabled
const profilerEnabled = SettingsManager.settings.profilerMode;
Expand Down
4 changes: 3 additions & 1 deletion src/client/core/Networking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface ServerInfo {
memUsageHeapTotal: number;
memUsageExternal: number;
idleKickTime: number;
durabilityEnabled: boolean;
}

interface LastUploadedLocalPlayer {
Expand Down Expand Up @@ -116,6 +117,7 @@ export class Networking {
memUsageHeapTotal: 0,
memUsageExternal: 0,
idleKickTime: 60,
durabilityEnabled: false,
};

this.setupSocketListeners();
Expand Down Expand Up @@ -438,7 +440,7 @@ export class Networking {
}
}

private lastThrownItemTimestamp: number;
private lastThrownItemTimestamp: number = 0;

public broadcastThrownItem(trajectory: Trajectory) {
this.socket.emit('throwItem', {
Expand Down
5 changes: 3 additions & 2 deletions src/client/core/RemoteItemRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,10 @@ export class RemoteItemRenderer {
this.itemsToRender = this.itemsToRender.filter((item) => {
const existsInNewData = newWorldItemsData.some((worldItemData) => worldItemData.id === item.id);
const createdOnClient = item.id > 1000000000; // Assuming server IDs are below this threshold
const createdRecently = item.item.getTrajectoryDuration() &&
const trajectoryDuration = item.item.getTrajectoryDuration();
const createdRecently = trajectoryDuration !== undefined &&
(Date.now() / 1000 - item.item.getCreationTimestamp() <
item.item.getTrajectoryDuration() + this.renderer.getLatency() / 1000 * 1.2 + 0.15);
trajectoryDuration + this.renderer.getLatency() / 1000 * 1.2 + 0.15);

const shouldKeep = existsInNewData || (createdOnClient && createdRecently);
if (!shouldKeep) item.item.destroy();
Expand Down
Loading