2 Commits

Author SHA1 Message Date
036f77d510 longer rumble 2025-05-25 00:05:12 +02:00
b85ce27b2a gamepad rumbling PoC 2025-05-24 22:19:19 +02:00
203 changed files with 5589 additions and 10557 deletions

View File

@@ -1,2 +0,0 @@
[env]
BEVY_ASSET_ROOT = { value = "", relative = true }

View File

@@ -1,59 +0,0 @@
name: "Discord Webhook"
description: "Send a message to Discord via webhook using curl"
inputs:
webhook_url:
description: "Discord webhook URL"
required: true
message:
description: "Message to send"
required: true
username:
description: "Override the default username of the webhook"
required: false
default: ""
avatar_url:
description: "Override the default avatar of the webhook"
required: false
default: ""
color:
description: "Embed color (decimal number)"
required: false
default: ""
runs:
using: "composite"
steps:
- name: Send Discord notification
shell: bash
run: |
MESSAGE='${{ inputs.message }}'
if [ -n "${{ inputs.color }}" ]; then
# Send as embed with color
PAYLOAD=$(jq -n \
--arg content "$MESSAGE" \
--arg username "${{ inputs.username }}" \
--arg avatar "${{ inputs.avatar_url }}" \
--arg color "${{ inputs.color }}" \
'{
content: (if $content != "" then $content else null end),
username: (if $username != "" then $username else null end),
avatar_url: (if $avatar != "" then $avatar else null end),
embeds: (if $color != "" then [{description: $content, color: ($color | tonumber)}] else null end)
} | with_entries(select(.value != null))')
else
# Send as simple message
PAYLOAD=$(jq -n \
--arg content "$MESSAGE" \
--arg username "${{ inputs.username }}" \
--arg avatar "${{ inputs.avatar_url }}" \
'{
content: $content,
username: (if $username != "" then $username else null end),
avatar_url: (if $avatar != "" then $avatar else null end)
} | with_entries(select(.value != null))')
fi
curl -H "Content-Type: application/json" \
-d "$PAYLOAD" \
"${{ inputs.webhook_url }}"

View File

@@ -15,22 +15,22 @@ jobs:
toolchain: stable
components: clippy, rustfmt
- name: install linux dependencies
if: runner.os == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
- name: "Install and cache dependencies (Linux)"
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
version: 1.0
- name: Build
run: |
cargo build --release --locked --target=x86_64-unknown-linux-gnu --bin hedz_reloaded
cargo build --release --locked --target=x86_64-unknown-linux-gnu
- name: Archive
run: |
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded ./
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded hedz_reloaded
tar -czf steamos.tar.gz hedz_reloaded
- uses: christopherhx/gitea-upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: steamos.tar.gz
path: ./steamos.tar.gz

View File

@@ -20,22 +20,23 @@ jobs:
run: |
which cargo-xwin || cargo install --locked cargo-xwin
- name: install dependencies
run: |
sudo apt-get update
sudo apt-get install -y lld clang cmake ninja-build
- name: "Install and cache dependencies (Windows)"
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: lld clang cmake ninja-build
version: 1.0
- name: Build binaries (Windows)
run: |
cargo xwin build --locked --release --target=x86_64-pc-windows-msvc --bin hedz_reloaded
cargo xwin build --locked --release --target=x86_64-pc-windows-msvc
- name: Archive
run: |
ls -lisa target/x86_64-pc-windows-msvc/release/
cp target/x86_64-pc-windows-msvc/release/hedz_reloaded.exe ./
cp target/x86_64-pc-windows-msvc/release/hedz_reloaded.exe hedz_reloaded.exe
tar -czf win.tar.gz hedz_reloaded.exe
- uses: christopherhx/gitea-upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: win.tar.gz
path: ./win.tar.gz

View File

@@ -9,22 +9,25 @@ jobs:
ci:
strategy:
matrix:
os: ["linux", "macos"]
runs-on: ["self-hosted", "${{ matrix.os }}"]
os: [[self-hosted, linux], [self-hosted, macos]]
runs-on: "${{ matrix.os }}"
steps:
- uses: actions/checkout@v4
- uses: cargo-bins/cargo-binstall@main
if: runner.os == 'linux'
- uses: extractions/setup-just@v1
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy, rustfmt
- name: install linux dependencies
- name: "Install and cache dependencies (Linux)"
if: runner.os == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
version: 1.0
- name: Formatting
run: |
@@ -36,10 +39,8 @@ jobs:
- name: Build
run: |
cargo build --bin hedz_reloaded
cargo build --bin hedz_reloaded_server --no-default-features
cargo build
- name: Tests
run: |
cargo test --lib hedz_reloaded
cargo test --lib hedz_reloaded --no-default-features
cargo test

View File

@@ -15,19 +15,15 @@ jobs:
toolchain: stable
components: clippy, rustfmt
- name: install linux dependencies
if: runner.os == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
- name: "Install and cache dependencies (Linux)"
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
version: 1.0
- name: Build client
- name: Build
run: |
cargo build --locked --target=x86_64-unknown-linux-gnu --bin hedz_reloaded
- name: Build server
run: |
cargo build --locked --target=x86_64-unknown-linux-gnu --bin hedz_reloaded_server --no-default-features
cargo build --locked --target=x86_64-unknown-linux-gnu
- name: Lints
run: |
@@ -35,10 +31,10 @@ jobs:
- name: Archive
run: |
cp target/x86_64-unknown-linux-gnu/debug/hedz_reloaded target/x86_64-unknown-linux-gnu/debug/hedz_reloaded_server ./
tar -czf steamos-debug.tar.gz hedz_reloaded hedz_reloaded_server
cp target/x86_64-unknown-linux-gnu/debug/hedz_reloaded hedz_reloaded
tar -czf steamos-debug.tar.gz hedz_reloaded
- uses: christopherhx/gitea-upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: steamos-debug.tar.gz
path: ./steamos-debug.tar.gz

View File

@@ -22,17 +22,17 @@ jobs:
- name: Build (lipo)
run: |
cargo build --release --target=x86_64-apple-darwin --bin hedz_reloaded
cargo build --release --target=aarch64-apple-darwin --bin hedz_reloaded
cargo build --release --target=x86_64-apple-darwin
cargo build --release --target=aarch64-apple-darwin
lipo -create -output target/release/hedz_reloaded target/aarch64-apple-darwin/release/hedz_reloaded target/x86_64-apple-darwin/release/hedz_reloaded
- name: Archive
run: |
cp target/release/hedz_reloaded ./
cp target/release/hedz_reloaded ./hedz_reloaded
tar -czf hedz-macos.tar.gz hedz_reloaded
ls -lisah hedz-macos.tar.gz
- uses: christopherhx/gitea-upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: hedz-macos
path: ./hedz-macos.tar.gz
@@ -80,10 +80,10 @@ jobs:
id: vars
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "branch=$(git branch --show-current)" >> $GITHUB_OUTPUT
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
- name: Discord notification
uses: ./.github/actions/discord-webhook
uses: appleboy/discord-action@v1.2.0
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
message: New macos release (`${{ steps.vars.outputs.branch }}`) [`${{ steps.vars.outputs.sha_short }}`]
@@ -103,7 +103,7 @@ jobs:
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
version: 2
version: 1.0
- uses: leafwing-studios/cargo-cache@v2
with:
@@ -111,14 +111,14 @@ jobs:
- name: Build
run: |
cargo build --locked --release --target=x86_64-unknown-linux-gnu --bin hedz_reloaded
cargo build --locked --release --target=x86_64-unknown-linux-gnu
- name: Archive
run: |
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded ./
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded hedz_reloaded
tar -czf steamos.tar.gz hedz_reloaded
- uses: christopherhx/gitea-upload-artifact@v4
- uses: actions/upload-artifact@v4
with:
name: steamos.tar.gz
path: ./steamos.tar.gz
@@ -126,7 +126,7 @@ jobs:
- name: Copy Binary for SteamOS
run: |
mkdir -p build/steamos/content
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded build/steamos/content/
cp target/x86_64-unknown-linux-gnu/release/hedz_reloaded build/steamos/content/hedz_reloaded
- name: Install SteamCMD
run: |
@@ -161,10 +161,10 @@ jobs:
id: vars
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "branch=$(git branch --show-current)" >> $GITHUB_OUTPUT
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
- name: Discord notification
uses: ./.github/actions/discord-webhook
uses: appleboy/discord-action@v1.2.0
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
message: New steamos release (`${{ steps.vars.outputs.branch }}`) [`${{ steps.vars.outputs.sha_short }}`]
@@ -185,19 +185,20 @@ jobs:
run: |
which cargo-xwin || cargo install --locked cargo-xwin
- name: install dependencies
run: |
sudo apt-get update
sudo apt-get install -y lld clang cmake ninja-build
- name: "Install and cache dependencies (Windows)"
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: lld clang cmake ninja-build
version: 1.0
- name: Build binaries (Windows)
run: |
cargo xwin build --locked --release --target=x86_64-pc-windows-msvc --bin hedz_reloaded
cargo xwin build --locked --release --target=x86_64-pc-windows-msvc
- name: Move binary
run: |
ls -lisa target/x86_64-pc-windows-msvc/release/
cp target/x86_64-pc-windows-msvc/release/hedz_reloaded.exe build/win/
cp target/x86_64-pc-windows-msvc/release/hedz_reloaded.exe build/win/hedz_reloaded.exe
- uses: ./.github/actions/steamcmd
with:
@@ -216,10 +217,10 @@ jobs:
id: vars
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "branch=$(git branch --show-current)" >> $GITHUB_OUTPUT
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
- name: Discord notification
uses: ./.github/actions/discord-webhook
uses: appleboy/discord-action@v1.2.0
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
message: New windows release (`${{ steps.vars.outputs.branch }}`) [`${{ steps.vars.outputs.sha_short }}`]

View File

@@ -1,15 +0,0 @@
name: test matrix
on:
workflow_dispatch:
jobs:
test-single:
runs-on: linux
steps:
- run: echo "Single label works"
test-array:
runs-on: [self-hosted, linux]
steps:
- run: echo "Array label works"

View File

@@ -1,2 +0,0 @@
imports_granularity = "Crate"
group_imports = "One"

3941
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,17 @@
[workspace]
resolver = "3"
members = ["crates/*"]
[package]
name = "hedz_reloaded"
version = "0.1.0"
edition = "2024"
build = "build.rs"
[workspace.dependencies]
avian3d = { version = "0.4.0", default-features = false, features = [
[profile.dev.package."*"]
opt-level = 3
[features]
dbg = ["avian3d/debug-plugin", "dep:bevy-inspector-egui"]
[dependencies]
avian3d = { version = "0.3", default-features = false, features = [
"3d",
"f32",
"parry-f32",
@@ -11,72 +19,29 @@ avian3d = { version = "0.4.0", default-features = false, features = [
"bevy_scene",
"bevy_picking", # todo: Consider if this one is necessary
"parallel",
"serialize",
] }
bevy = { version = "0.17.0", default-features = false, features = [
"animation",
"async_executor",
"bevy_asset",
"bevy_color",
"bevy_core_pipeline",
"bevy_gilrs",
"bevy_gizmos",
"bevy_gltf",
"bevy_input_focus",
"bevy_log",
"bevy_mesh_picking_backend",
"bevy_pbr",
"bevy_picking",
"bevy_render",
"bevy_scene",
"bevy_sprite",
"bevy_sprite_picking_backend",
"bevy_state",
"bevy_text",
"bevy_ui",
"bevy_ui_render",
"bevy_ui_picking_backend",
"default_font",
"hdr",
"multi_threaded",
"png",
"reflect_auto_register",
"smaa_luts",
"std",
"sysinfo_plugin",
"tonemapping_luts",
"vorbis",
"webgl2",
"zstd_rust",
"track_location",
] }
bevy-inspector-egui = "0.34"
bevy-persistent = { version = "0.9", features = ["ron"] }
bevy-steamworks = "0.15.0"
bevy_asset_loader = "=0.24.0-rc.1"
bevy_ballistic = { git = "https://github.com/rustunit/bevy_ballistic.git", rev = "b08ffec" }
bevy_common_assets = { version = "0.14.0", features = ["ron"] }
bevy_debug_log = { git = "https://github.com/rustunit/bevy_debug_log.git", rev = "86051a0" }
bevy_replicon = "0.37.1"
# TODO: i dont think we need this in dedicated server mode
bevy_replicon_renet = { version = "0.13.0", features = ["renet_steam"] }
bevy_sprite3d = "7.0.0"
bevy_trenchbroom = { version = "0.10", default-features = false, features = [
"physics-integration",
] }
bevy_trenchbroom_avian = "0.10"
clap = { version = "=4.5.47", features = ["derive"] }
dirs = "6.0.0"
happy_feet = { git = "https://github.com/PROMETHIA-27/happy_feet.git", rev = "e3a4660e0b68f9bf4d6facb0fb4d93f2d7848b27", features = [
"serde",
] }
bevy = { version = "0.16.0", features = ["track_location"] }
bevy_trenchbroom = { version = "0.8.0", features = ["avian"] }
nil = "0.14.0"
bevy_asset_loader = "0.23.0-rc.3"
bevy_sprite3d = "5.0.0"
rand = "=0.8.5"
ron = "0.8"
bevy-inspector-egui = { version = "0.31", optional = true }
bevy-steamworks = "0.13.0"
steamworks = "0.11"
bevy_ballistic = "0.4.0"
bevy-ui-gradients = "0.4.0"
bevy_debug_log = "0.6.0"
bevy_common_assets = { version = "0.13.0", features = ["ron"] }
serde = { version = "1.0.219", features = ["derive"] }
shared = { path = "crates/shared" }
# TODO: i dont think we need this in dedicated server mode
steamworks = "0.12"
ron = "0.8"
[profile.dev.package."*"]
opt-level = 3
[build-dependencies]
vergen-gitcl = "1.0"
[lints.clippy]
too_many_arguments = "allow"
type_complexity = "allow"
[patch.crates-io]
bevy-steamworks = { git = "https://github.com/extrawurst/bevy_steamworks.git", branch = "derive-debug-event" }

View File

@@ -1,20 +1,20 @@
([
/*00*/(key:"angry demonstrator", ability:Thrown, aps:2, ammo:10, range:90, damage:25, projectile:"molotov", interrupt_shoot:false),
/*01*/(key:"carnival knife thrower", ability:Arrow, range:60, ammo:5),
/*02*/(key:"chicago gangster", ability:Gun, aps:7.4, ammo:25, range:60, damage:12),
/*00*/(key:"angry demonstrator", ability:Thrown, aps:2, ammo:10, range:90, damage:25, projectile:"molotov"),
/*01*/(key:"carnival knife thrower", range:60, ammo:5),
/*02*/(key:"chicago gangster", ability:Gun, ammo:25, range:60),
/*03*/(key:"commando", ability:Gun, aps:7.4, ammo:26, range:60, damage:12),
/*04*/(key:"field medic", ability:Medic, aps:20),
/*05*/(key:"geisha", ability:Curver, range:60, ammo:10, damage:25, projectile:"fan"),
/*05*/(key:"geisha"),
/*06*/(key:"goblin", ability:Arrow, aps:2, ammo:5, range:90, damage:50),
/*07*/(key:"green grocer", ability:Curver, range:60, ammo:10, damage:25, projectile:"carrot"),
/*08*/(key:"highland hammer thrower", ability:Thrown, aps:2, ammo:10, damage:25, range:80, projectile:"hammer", interrupt_shoot:false),
/*09*/(key:"legionnaire", ability:Gun, aps:2, ammo:25, range:60, damage:13, shoot_offset:0.1667),
/*10*/(key:"mig pilot", ability:Missile, ammo:5, range:60, damage:100, controls:Plane, interrupt_shoot:false),
/*11*/(key:"nanny", ability:None, ammo:5, range:60, projectile:"handbag"),
/*12*/(key:"panic attack", ability:Turbo),
/*13*/(key:"salty sea dog", ability:None),
/*14*/(key:"snow plough operator", ability:None),
/*15*/(key:"soldier ant", ability:None),
/*07*/(key:"green grocer", range:60, ammo:10, damage:25),
/*08*/(key:"highland hammer thrower", ability:Thrown, aps:2, ammo:10, damage:25, range:80, projectile:"hammer"),
/*09*/(key:"legionnaire", ability:Gun, aps:1.5, ammo:25, range:60, damage:13),
/*10*/(key:"mig pilot", ability:Missile, ammo:5, range:60, damage:100, controls:Plane),
/*11*/(key:"nanny", ability:Thrown, range:60),
/*12*/(key:"panic attack"),
/*13*/(key:"salty sea dog"),
/*14*/(key:"snow plough operator"),
/*15*/(key:"soldier ant"),
/*16*/(key:"super market shopper", ability:Thrown, ammo:10, range:80, damage:17, projectile:"handbag"),
/*17*/(key:"troll", ability:Thrown, ammo:10, range:80, damage:17, projectile:"club"),
/*17*/(key:"troll", ability:Thrown, ammo:10, range:80, damage:17),
])

View File

@@ -16,12 +16,12 @@
}
// brush 1
{
( -256 -832 2320 ) ( -256 -768 272 ) ( -256 -768 2320 ) TinyTexPack2/Brick/Brick_20_window [ 0 -1 0 0 ] [ 0 0 -1 16 ] 90 1 1
( 768 -832 2320 ) ( -256 -832 272 ) ( -256 -832 2320 ) TinyTexPack2/Brick/Brick_20_window [ 1 0 0 0 ] [ 0 0 -1 16 ] 90 1 1
( 768 -768 272 ) ( -256 -832 272 ) ( 768 -832 272 ) TinyTexPack2/Brick/Brick_20_window [ -1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( 768 -768 2320 ) ( -256 -832 2320 ) ( -256 -768 2320 ) TinyTexPack2/Brick/Brick_20_window [ 1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( 768 -768 2320 ) ( -256 -768 272 ) ( 768 -768 272 ) TinyTexPack2/Brick/Brick_20_window [ -1 0 0 0 ] [ 0 0 -1 16 ] 90 1 1
( 768 -768 2320 ) ( 768 -832 272 ) ( 768 -832 2320 ) TinyTexPack2/Brick/Brick_20_window [ 0 1 0 0 ] [ 0 0 -1 16 ] 90 1 1
( -256 -832 2320 ) ( -256 -768 272 ) ( -256 -768 2320 ) TinyTexPack2/Brick/Brick_20_window [ 0 -1 0 0 ] [ 0 0 -1 16 ] 270 1 1
( 768 -832 2320 ) ( -256 -832 272 ) ( -256 -832 2320 ) TinyTexPack2/Brick/Brick_20_window [ 1 0 0 0 ] [ 0 0 -1 16 ] 270 1 1
( 768 -768 272 ) ( -256 -832 272 ) ( 768 -832 272 ) TinyTexPack2/Brick/Brick_20_window [ -1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( 768 -768 2320 ) ( -256 -832 2320 ) ( -256 -768 2320 ) TinyTexPack2/Brick/Brick_20_window [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( 768 -768 2320 ) ( -256 -768 272 ) ( 768 -768 272 ) TinyTexPack2/Brick/Brick_20_window [ -1 0 0 0 ] [ 0 0 -1 16 ] 270 1 1
( 768 -768 2320 ) ( 768 -832 272 ) ( 768 -832 2320 ) TinyTexPack2/Brick/Brick_20_window [ 0 1 0 0 ] [ 0 0 -1 16 ] 270 1 1
}
// brush 2
{
@@ -1074,7 +1074,7 @@
// brush 119
{
( -5376 3584 -1440 ) ( -5376 3584 -1439 ) ( -5376 3583 -1440 ) airlock/airlock-wall-outside [ -1.9967346175427393e-16 -1 0 64 ] [ 0 0 -1 -64 ] 180 1 1
( -5376 3392 -1440 ) ( -5377 3392 -1440 ) ( -5376 3392 -1439 ) airlock/airlock-wall [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5376 3515.4285714285716 -1440 ) ( -5377 3515.4285714285716 -1440 ) ( -5376 3515.4285714285716 -1439 ) airlock/airlock-wall [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5344 3968 -1472 ) ( -5345 3968 -1472 ) ( -5344 3967 -1472 ) airlock/airlock-wall [ -1 0 0 0 ] [ 0 -1 0 64 ] 180 1 1
( -5312 3584 -1248 ) ( -5376 3680 -1472 ) ( -5312 3680 -1472 ) airlock/airlock-wall-outside [ -1 0 0 0 ] [ 0 0.39391929857916613 -0.9191450300180586 33.614258 ] 180 1 1
( -5312 3968 -1472 ) ( -5312 3967 -1472 ) ( -5312 3968 -1471 ) airlock/airlock-wall [ 1.9967346175427393e-16 1 0 -64 ] [ 0 0 -1 -64 ] 180 1 1
@@ -1082,8 +1082,8 @@
// brush 120
{
( -5376 3584 -1440 ) ( -5376 3584 -1439 ) ( -5376 3583 -1440 ) airlock/airlock-wall-outside [ -1.9967346175427393e-16 -1 0 64 ] [ 0 0 -1 -64 ] 180 1 1
( -5376 3392 -1440 ) ( -5377 3392 -1440 ) ( -5376 3392 -1439 ) airlock/airlock-wall [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5312 3872 -704 ) ( -5312 3680 -704 ) ( -5440 3680 -704 ) airlock/airlock-wall [ 1 0 0 0 ] [ 0 -1 0 64 ] 180 1 1
( -5376 3515.4285714285716 -1440 ) ( -5377 3515.4285714285716 -1440 ) ( -5376 3515.4285714285716 -1439 ) airlock/airlock-wall [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5312 3872 -1024 ) ( -5312 3680 -1024 ) ( -5440 3680 -1024 ) airlock/airlock-wall [ 1 0 0 0 ] [ 0 -1 0 64 ] 180 1 1
( -5312 3680 -1024 ) ( -5376 3584 -1248 ) ( -5312 3584 -1248 ) airlock/airlock-wall-outside [ -1 7.105427357601002e-15 0 0 ] [ -2.7989649608114045e-15 -0.39391929857916613 -0.9191450300180586 84.0361 ] 180 1 1
( -5312 3968 -1472 ) ( -5312 3967 -1472 ) ( -5312 3968 -1471 ) airlock/airlock-wall [ 1.9967346175427393e-16 1 0 -64 ] [ 0 0 -1 -64 ] 180 1 1
}
@@ -1091,8 +1091,8 @@
{
( -5376 3584 -1440 ) ( -5376 3584 -1439 ) ( -5376 3583 -1440 ) airlock/airlock-wall-outside [ -1.9967346175427393e-16 -1 0 64 ] [ 0 0 -1 -64 ] 180 1 1
( -5312 3968 -1248 ) ( -5440 3872 -1024 ) ( -5312 3872 -1024 ) airlock/airlock-wall-outside [ 1 0 0 0 ] [ 0 0.3939192985791677 -0.9191450300180579 33.614502 ] 180 1 1
( -5312 3872 -704 ) ( -5312 3680 -704 ) ( -5440 3680 -704 ) airlock/airlock-wall [ 1 0 0 0 ] [ 0 -1 0 64 ] 180 1 1
( -5344 4096 -1472 ) ( -5344 4096 -1471 ) ( -5345 4096 -1472 ) airlock/airlock-wall [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5312 3872 -1024 ) ( -5312 3680 -1024 ) ( -5440 3680 -1024 ) airlock/airlock-wall [ 1 0 0 0 ] [ 0 -1 0 64 ] 180 1 1
( -5344 4032 -1472 ) ( -5344 4032 -1471 ) ( -5345 4032 -1472 ) airlock/airlock-wall [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5312 3968 -1472 ) ( -5312 3967 -1472 ) ( -5312 3968 -1471 ) airlock/airlock-wall [ 1.9967346175427393e-16 1 0 -64 ] [ 0 0 -1 -64 ] 180 1 1
}
// brush 122
@@ -1100,125 +1100,9 @@
( -5376 3584 -1440 ) ( -5376 3584 -1439 ) ( -5376 3583 -1440 ) airlock/airlock-wall-outside [ -1.9967346175427393e-16 -1 0 64 ] [ 0 0 -1 -64 ] 180 1 1
( -5312 3872 -1472 ) ( -5376 3968 -1248 ) ( -5312 3968 -1248 ) airlock/airlock-wall-outside [ 1 0 0 0 ] [ 0 -0.3939192985791677 -0.9191450300180579 84.03613 ] 180 1 1
( -5344 3968 -1472 ) ( -5345 3968 -1472 ) ( -5344 3967 -1472 ) airlock/airlock-wall [ -1 0 0 0 ] [ 0 -1 0 64 ] 180 1 1
( -5344 4096 -1472 ) ( -5344 4096 -1471 ) ( -5345 4096 -1472 ) airlock/airlock-wall [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5344 4032 -1472 ) ( -5344 4032 -1471 ) ( -5345 4032 -1472 ) airlock/airlock-wall [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 -64 ] 180 1 1
( -5312 3968 -1472 ) ( -5312 3967 -1472 ) ( -5312 3968 -1471 ) airlock/airlock-wall [ 1.9967346175427393e-16 1 0 -64 ] [ 0 0 -1 -64 ] 180 1 1
}
// brush 123
{
( -5376 3392 -1472 ) ( -5568 3392 -1600 ) ( -5568 4096 -1600 ) canals [ -0.7071067811865474 0 -0.7071067811865477 0 ] [ 0 -1 0 0 ] 0 1 1
( -5376 3392 -1600 ) ( -5568 3392 -1600 ) ( -5376 3392 -1472 ) canals [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5568 4096 -1600 ) ( -5568 3392 -1600 ) ( -5376 3392 -1600 ) canals [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( -5376 4096 -1472 ) ( -5568 4096 -1600 ) ( -5376 4096 -1600 ) canals [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5376 3392 -1472 ) ( -5376 4096 -1472 ) ( -5376 4096 -1600 ) canals [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
// brush 124
{
( -7168 3392 -1600 ) ( -7168 3392 -1599 ) ( -7168 3391 -1600 ) canals [ 1.9967346175427393e-16 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -4416 3328 -1536 ) ( -4416 3328 -1535 ) ( -4415 3328 -1536 ) canals [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -4480 3392 -1600 ) ( -4480 3391 -1600 ) ( -4479 3392 -1600 ) canals [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( -4416 3328 -768 ) ( -4415 3328 -768 ) ( -4416 3327 -768 ) canals [ 1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( -4480 3392 -1600 ) ( -4479 3392 -1600 ) ( -4480 3392 -1599 ) canals [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5376 3328 -1536 ) ( -5376 3327 -1536 ) ( -5376 3328 -1535 ) canals [ -1.9967346175427393e-16 -1 0 0 ] [ 0 0 -1 0 ] 180 1 1
}
// brush 125
{
( -7168 4160 -1600 ) ( -7168 4160 -1599 ) ( -7168 4159 -1600 ) canals [ 1.9967346175427393e-16 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -4416 4096 -1536 ) ( -4416 4096 -1535 ) ( -4415 4096 -1536 ) canals [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -4480 4160 -1600 ) ( -4480 4159 -1600 ) ( -4479 4160 -1600 ) canals [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( -4416 4096 -768 ) ( -4415 4096 -768 ) ( -4416 4095 -768 ) canals [ 1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( -4480 4160 -1600 ) ( -4479 4160 -1600 ) ( -4480 4160 -1599 ) canals [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5376 4096 -1536 ) ( -5376 4095 -1536 ) ( -5376 4096 -1535 ) canals [ -1.9967346175427393e-16 -1 0 0 ] [ 0 0 -1 0 ] 180 1 1
}
// brush 126
{
( -7136 3392 -1664 ) ( -7136 3393 -1664 ) ( -7136 3392 -1663 ) canals [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5696 3392 -1664 ) ( -5696 3392 -1663 ) ( -5695 3392 -1664 ) canals [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5696 3392 -1664 ) ( -5695 3392 -1664 ) ( -5696 3393 -1664 ) canals [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( -5568 4096 -1600 ) ( -5568 4097 -1600 ) ( -5567 4096 -1600 ) canals [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( -5568 4096 -1600 ) ( -5567 4096 -1600 ) ( -5568 4096 -1599 ) canals [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5568 4096 -1600 ) ( -5568 4096 -1599 ) ( -5568 4097 -1600 ) canals [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
// brush 127
{
( -6496 3392 -768 ) ( -6496 3393 -768 ) ( -6496 3392 -767 ) canals [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5504 3392 -768 ) ( -5504 3392 -767 ) ( -5503 3392 -768 ) canals [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5504 3392 -768 ) ( -5503 3392 -768 ) ( -5504 3393 -768 ) canals [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( -5376 4096 -704 ) ( -5376 4097 -704 ) ( -5375 4096 -704 ) canals [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( -5376 4096 -704 ) ( -5375 4096 -704 ) ( -5376 4096 -703 ) canals [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( -5376 4096 -704 ) ( -5376 4096 -703 ) ( -5376 4097 -704 ) canals [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
// brush 128
{
( -6560 4096 -304 ) ( -6560 4097 -304 ) ( -6560 4096 -303 ) canals [ 4.812271809458903e-16 -0.9999999999999999 0 64 ] [ 0 0 -1 16 ] 270 1 1
( -6496 3392 -368 ) ( -6497 3392 -368 ) ( -6496 3392 -367 ) canals [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] [ 0 0 -1 16 ] 180 1 1
( -6496 3648 -768 ) ( -6496 3649 -768 ) ( -6497 3648 -768 ) canals [ 4.812271809458903e-16 -0.9999999999999999 0 64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 180 1 1
( -6688 4096 -304 ) ( -6689 4096 -304 ) ( -6688 4097 -304 ) canals [ -4.812271809458903e-16 0.9999999999999999 0 -64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 270 1 1
( -6688 4096 -304 ) ( -6688 4096 -303 ) ( -6689 4096 -304 ) canals [ -1.0000000000000007 5.456456664689579e-16 0 -128 ] [ 0 0 -1 16 ] 0 1 1
( -6496 3648 -368 ) ( -6496 3648 -367 ) ( -6496 3649 -368 ) canals [ -4.812271809458903e-16 0.9999999999999999 0 -64 ] [ 0 0 -1 16 ] 0 1 1
}
// brush 129
{
( -7296 4096 -304 ) ( -7296 4097 -304 ) ( -7296 4096 -303 ) tile [ 0 -1 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -8544 4096 -304 ) ( -8544 4096 -303 ) ( -8543 4096 -304 ) tile [ 1 0 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -8544 4096 -304 ) ( -8543 4096 -304 ) ( -8544 4097 -304 ) tile [ -1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -7648 4544 -240 ) ( -7648 4545 -240 ) ( -7647 4544 -240 ) tile [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -7648 4224 -240 ) ( -7647 4224 -240 ) ( -7648 4224 -239 ) tile [ -1 0 0 0 ] [ 0 0 -1 16 ] 180 1 1
( -6432 4544 -240 ) ( -6432 4544 -239 ) ( -6432 4545 -240 ) tile [ 0 1 0 0 ] [ 0 0 -1 16 ] 270 1 1
}
// brush 130
{
( -7232 4096 -304 ) ( -7232 4097 -304 ) ( -7232 4096 -303 ) canals [ 4.812271809458903e-16 -0.9999999999999999 0 64 ] [ 0 0 -1 16 ] 270 1 1
( -7168 3392 -368 ) ( -7169 3392 -368 ) ( -7168 3392 -367 ) canals [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] [ 0 0 -1 16 ] 180 1 1
( -7168 3648 -1600 ) ( -7168 3649 -1600 ) ( -7169 3648 -1600 ) canals [ 4.812271809458903e-16 -0.9999999999999999 0 64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 180 1 1
( -7360 4096 -304 ) ( -7361 4096 -304 ) ( -7360 4097 -304 ) canals [ -4.812271809458903e-16 0.9999999999999999 0 -64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 270 1 1
( -7360 4096 -304 ) ( -7360 4096 -303 ) ( -7361 4096 -304 ) canals [ -1.0000000000000007 5.456456664689579e-16 0 -128 ] [ 0 0 -1 16 ] 0 1 1
( -7168 3648 -368 ) ( -7168 3648 -367 ) ( -7168 3649 -368 ) canals [ -4.812271809458903e-16 0.9999999999999999 0 -64 ] [ 0 0 -1 16 ] 0 1 1
}
// brush 131
{
( -7296 3264 -304 ) ( -7296 3265 -304 ) ( -7296 3264 -303 ) tile [ 0 -1 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -8544 3136 -304 ) ( -8544 3136 -303 ) ( -8543 3136 -304 ) tile [ 1 0 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -8544 3264 -304 ) ( -8543 3264 -304 ) ( -8544 3265 -304 ) tile [ -1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -7648 3712 -240 ) ( -7648 3713 -240 ) ( -7647 3712 -240 ) tile [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -7648 3392 -240 ) ( -7647 3392 -240 ) ( -7648 3392 -239 ) tile [ -1 0 0 0 ] [ 0 0 -1 16 ] 180 1 1
( -6304 3712 -240 ) ( -6304 3712 -239 ) ( -6304 3713 -240 ) tile [ 0 1 0 0 ] [ 0 0 -1 16 ] 270 1 1
}
// brush 132
{
( -6560 5344 -304 ) ( -6560 5344 -303 ) ( -6560 5343 -304 ) tile [ 1.9967346175427393e-16 1 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -6112 3392 -240 ) ( -6112 3392 -239 ) ( -6111 3392 -240 ) tile [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -6560 5344 -304 ) ( -6560 5343 -304 ) ( -6559 5344 -304 ) tile [ -1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -6112 4448 -240 ) ( -6111 4448 -240 ) ( -6112 4447 -240 ) tile [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -6560 4096 -304 ) ( -6559 4096 -304 ) ( -6560 4096 -303 ) tile [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -6304 4448 -240 ) ( -6304 4447 -240 ) ( -6304 4448 -239 ) tile [ -1.9967346175427393e-16 -1 0 0 ] [ 0 0 -1 16 ] 180 1 1
}
// brush 133
{
( -7296 5344 -304 ) ( -7296 5344 -303 ) ( -7296 5343 -304 ) tile [ 1.9967346175427393e-16 1 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -6848 3392 -240 ) ( -6848 3392 -239 ) ( -6847 3392 -240 ) tile [ -1 1.9967346175427393e-16 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -7296 5344 -304 ) ( -7296 5343 -304 ) ( -7295 5344 -304 ) tile [ -1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -6848 4448 -240 ) ( -6847 4448 -240 ) ( -6848 4447 -240 ) tile [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -7296 4096 -304 ) ( -7295 4096 -304 ) ( -7296 4096 -303 ) tile [ 1 -1.9967346175427393e-16 0 0 ] [ 0 0 -1 16 ] 270 1 1
( -7168 4448 -240 ) ( -7168 4447 -240 ) ( -7168 4448 -239 ) tile [ -1.9967346175427393e-16 -1 0 0 ] [ 0 0 -1 16 ] 180 1 1
}
// brush 134
{
( -7168 3328 -368 ) ( -7168 3329 -368 ) ( -7168 3328 -367 ) canals [ 6.762803511326634e-16 1.0000000000000007 0 128 ] [ 0 0 -1 16 ] 180 1 1
( -7008 3328 -368 ) ( -7008 3328 -367 ) ( -7007 3328 -368 ) canals [ -0.9999999999999998 -2.9040269150165053e-16 0 -64 ] [ 0 0 -1 16 ] 0 1 1
( -7008 3328 -768 ) ( -7007 3328 -768 ) ( -7008 3329 -768 ) canals [ 4.812271809458903e-16 -0.9999999999999999 0 64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 180 1 1
( -6560 3520 -304 ) ( -6560 3521 -304 ) ( -6559 3520 -304 ) canals [ -4.812271809458903e-16 0.9999999999999999 0 -64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 270 1 1
( -6560 3392 -304 ) ( -6559 3392 -304 ) ( -6560 3392 -303 ) canals [ 0.9999999999999998 2.9040269150165053e-16 0 64 ] [ 0 0 -1 16 ] 270 1 1
( -6560 3520 -304 ) ( -6560 3520 -303 ) ( -6560 3521 -304 ) canals [ -6.762803511326634e-16 -1.0000000000000007 0 -128 ] [ 0 0 -1 16 ] 0 1 1
}
// brush 135
{
( -7168 4096 -368 ) ( -7168 4097 -368 ) ( -7168 4096 -367 ) canals [ 6.762803511326634e-16 1.0000000000000007 0 128 ] [ 0 0 -1 16 ] 180 1 1
( -7008 4096 -368 ) ( -7008 4096 -367 ) ( -7007 4096 -368 ) canals [ -0.9999999999999998 -2.9040269150165053e-16 0 -64 ] [ 0 0 -1 16 ] 0 1 1
( -7008 4096 -768 ) ( -7007 4096 -768 ) ( -7008 4097 -768 ) canals [ 4.812271809458903e-16 -0.9999999999999999 0 64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 180 1 1
( -6560 4288 -304 ) ( -6560 4289 -304 ) ( -6559 4288 -304 ) canals [ -4.812271809458903e-16 0.9999999999999999 0 -64 ] [ 1.0000000000000007 -5.456456664689579e-16 0 128 ] 270 1 1
( -6560 4160 -304 ) ( -6559 4160 -304 ) ( -6560 4160 -303 ) canals [ 0.9999999999999998 2.9040269150165053e-16 0 64 ] [ 0 0 -1 16 ] 270 1 1
( -6560 4288 -304 ) ( -6560 4288 -303 ) ( -6560 4289 -304 ) canals [ -6.762803511326634e-16 -1.0000000000000007 0 -128 ] [ 0 0 -1 16 ] 0 1 1
}
}
// entity 1
{
@@ -1229,11 +1113,10 @@
{
"classname" "enemy_spawn"
"origin" "648 -680 -232"
"angles" "0 90 0"
"angles" "0 -90 0"
"head" "field medic"
"key" "fence_gate"
"disable_ai" "true"
"spawn_order" "0"
}
// entity 3
{
@@ -1245,9 +1128,8 @@
"classname" "enemy_spawn"
"origin" "3256 248 -232"
"head" "field medic"
"angles" "0 180 0"
"angles" "0 0 0"
"disable_ai" "true"
"spawn_order" "1"
}
// entity 5
{
@@ -1502,18 +1384,16 @@
{
"classname" "enemy_spawn"
"origin" "2568 4504 -232"
"angles" "0 0 0"
"angles" "0 180 0"
"head" "super market shopper"
"spawn_order" "2"
}
// entity 24
{
"classname" "enemy_spawn"
"origin" "648 6760 -232"
"angles" "0 -90 0"
"angles" "0 90 0"
"head" "green grocer"
"key" "fence_shaft"
"spawn_order" "3"
}
// entity 25
{
@@ -1717,189 +1597,3 @@
"origin" "2712 5240 792"
"head_id" "13"
}
// entity 45
{
"classname" "enemy_spawn"
"origin" "-184 -584 -232"
"angles" "0 0 0"
"head" "commando"
"key" ""
"disable_ai" "true"
}
// entity 46
{
"classname" "enemy_spawn"
"origin" "-184 -504 -232"
"angles" "0 0 0"
"head" "angry demonstrator"
"key" ""
"disable_ai" "true"
}
// entity 47
{
"classname" "enemy_spawn"
"origin" "-184 -440 -232"
"angles" "0 0 0"
"head" "goblin"
"key" ""
"disable_ai" "true"
}
// entity 48
{
"classname" "enemy_spawn"
"origin" "-184 -360 -232"
"angles" "0 0 0"
"head" "green grocer"
"key" ""
"disable_ai" "true"
}
// entity 49
{
"classname" "enemy_spawn"
"origin" "-184 -280 -232"
"angles" "0 0 0"
"head" "highland hammer thrower"
"key" ""
"disable_ai" "true"
}
// entity 50
{
"classname" "enemy_spawn"
"origin" "-184 -184 -232"
"angles" "0 0 0"
"head" "legionnaire"
"key" ""
"disable_ai" "true"
}
// entity 51
{
"classname" "enemy_spawn"
"origin" "-184 -104 -232"
"angles" "0 0 0"
"head" "mig pilot"
"key" ""
"disable_ai" "true"
}
// entity 52
{
"classname" "enemy_spawn"
"origin" "-184 -8 -232"
"angles" "0 0 0"
"head" "soldier ant"
"key" ""
"disable_ai" "true"
}
// entity 53
{
"classname" "enemy_spawn"
"origin" "-184 -680 -232"
"angles" "0 0 0"
"head" "super market shopper"
"key" ""
"disable_ai" "true"
}
// entity 54
{
"classname" "enemy_spawn"
"origin" "-184 88 -232"
"angles" "0 0 0"
"head" "nanny"
"key" ""
"disable_ai" "true"
}
// entity 55
{
"classname" "enemy_spawn"
"origin" "-184 184 -232"
"angles" "0 0 0"
"head" "chicago gangster"
"key" ""
"disable_ai" "true"
}
// entity 56
{
"classname" "enemy_spawn"
"origin" "-184 264 -232"
"angles" "0 0 0"
"head" "carnival knife thrower"
"key" ""
"disable_ai" "true"
}
// entity 57
{
"classname" "enemy_spawn"
"origin" "-184 344 -232"
"angles" "0 0 0"
"head" "snow plough operator"
"key" ""
"disable_ai" "true"
}
// entity 58
{
"classname" "enemy_spawn"
"origin" "-184 424 -232"
"angles" "0 0 0"
"head" "salty sea dog"
"key" ""
"disable_ai" "true"
}
// entity 59
{
"classname" "enemy_spawn"
"origin" "-184 504 -232"
"angles" "0 0 0"
"head" "geisha"
"key" ""
"disable_ai" "true"
}
// entity 60
{
"classname" "platform"
"target" "elevator_02_target"
// brush 0
{
( -7168 3840 -2720 ) ( -7168 3841 -2720 ) ( -7168 3840 -2719 ) canals [ 0 -1 0 0 ] [ 0 0 -1 -96 ] 90 1 1
( -7136 3392 -2720 ) ( -7136 3392 -2719 ) ( -7135 3392 -2720 ) canals [ 1 0 0 0 ] [ 0 0 -1 -96 ] 90 1 1
( -7136 3840 -2720 ) ( -7135 3840 -2720 ) ( -7136 3841 -2720 ) canals [ -1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( -6496 4032 -1408 ) ( -6496 4033 -1408 ) ( -6495 4032 -1408 ) canals [ 1 0 0 0 ] [ 0 -1 0 0 ] 90 1 1
( -6496 4096 -2656 ) ( -6495 4096 -2656 ) ( -6496 4096 -2655 ) canals [ -1 0 0 0 ] [ 0 0 -1 -96 ] 90 1 1
( -6560 4032 -2656 ) ( -6560 4032 -2655 ) ( -6560 4033 -2656 ) canals [ 0 1 0 0 ] [ 0 0 -1 -96 ] 90 1 1
}
// brush 1
{
( -6848 3712 -1408 ) ( -6848 3713 -1408 ) ( -6848 3712 -1407 ) origin [ 0 -1 0 0 ] [ 0 0 -1 0 ] 270 1 1
( -6848 3712 -1408 ) ( -6848 3712 -1407 ) ( -6847 3712 -1408 ) origin [ 1 0 0 0 ] [ 0 0 -1 0 ] 270 1 1
( -6848 3712 -1408 ) ( -6847 3712 -1408 ) ( -6848 3713 -1408 ) origin [ -1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -6784 3776 -1376 ) ( -6784 3777 -1376 ) ( -6783 3776 -1376 ) origin [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -6784 3776 -1376 ) ( -6783 3776 -1376 ) ( -6784 3776 -1375 ) origin [ -1 0 0 0 ] [ 0 0 -1 0 ] 270 1 1
( -6784 3776 -1376 ) ( -6784 3776 -1375 ) ( -6784 3777 -1376 ) origin [ 0 1 0 0 ] [ 0 0 -1 0 ] 270 1 1
}
}
// entity 61
{
"classname" "platform_target"
"origin" "-6824 3736 -264"
"targetname" "elevator_02_target"
}
// entity 62
{
"classname" "water"
// brush 0
{
( -7296 6976 -1632 ) ( -7296 6977 -1632 ) ( -7296 6976 -1631 ) water [ 0 -1 0 0 ] [ 0 0 -1 -32 ] 180 1 1
( -6080 3264 -1632 ) ( -6080 3264 -1631 ) ( -6079 3264 -1632 ) water [ 1 0 0 0 ] [ 0 0 -1 -32 ] 90 1 1
( -6080 6976 -1632 ) ( -6079 6976 -1632 ) ( -6080 6977 -1632 ) water [ -1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -5568 7360 -1568 ) ( -5568 7361 -1568 ) ( -5567 7360 -1568 ) water [ 1 0 0 0 ] [ 0 -1 0 0 ] 270 1 1
( -5568 4224 -1568 ) ( -5567 4224 -1568 ) ( -5568 4224 -1567 ) water [ -1 0 0 0 ] [ 0 0 -1 -32 ] 270 1 1
( -5504 7360 -1568 ) ( -5504 7360 -1567 ) ( -5504 7361 -1568 ) water [ 0 1 0 0 ] [ 0 0 -1 -32 ] 270 1 1
}
}
// entity 63
{
"classname" "enemy_spawn"
"origin" "-184 584 -232"
"angles" "0 0 0"
"head" "troll"
"key" ""
"disable_ai" "true"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,54 +0,0 @@
[package]
name = "hedz_reloaded"
version = "0.1.0"
edition = "2024"
build = "build.rs"
[[bin]]
name = "hedz_reloaded_server"
[features]
default = ["client"]
client = [
"bevy/bevy_audio",
"bevy/bevy_window",
# depend on `winit`
"bevy/bevy_winit",
"bevy/x11",
"bevy/custom_cursor",
"bevy_replicon/client",
"bevy_replicon_renet/client",
"bevy_trenchbroom/client",
]
dbg = ["avian3d/debug-plugin", "bevy/debug", "dep:bevy-inspector-egui"]
[dependencies]
avian3d = { workspace = true }
bevy = { workspace = true }
bevy-inspector-egui = { workspace = true, optional = true }
bevy-persistent = { workspace = true }
bevy-steamworks = { workspace = true }
bevy_asset_loader = { workspace = true }
bevy_ballistic = { workspace = true }
bevy_common_assets = { workspace = true }
bevy_debug_log = { workspace = true }
bevy_replicon = { workspace = true }
bevy_replicon_renet = { workspace = true }
bevy_sprite3d = { workspace = true }
bevy_trenchbroom = { workspace = true }
bevy_trenchbroom_avian = { workspace = true }
clap = { workspace = true }
dirs = { workspace = true }
happy_feet = { workspace = true }
nil = { workspace = true }
rand = { workspace = true }
ron = { workspace = true }
serde = { workspace = true }
steamworks = { workspace = true }
[build-dependencies]
vergen-gitcl = "1.0"
[lints.clippy]
too_many_arguments = "allow"
type_complexity = "allow"

View File

@@ -1,206 +0,0 @@
use crate::{
GameState,
abilities::{BuildExplosionSprite, ProjectileId, TriggerCurver},
heads_database::HeadsDatabase,
hitpoints::Hit,
physics_layers::GameLayer,
tb_entities::EnemySpawn,
utils::global_observer,
};
use avian3d::prelude::*;
use bevy::prelude::*;
use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients};
use serde::{Deserialize, Serialize};
const MAX_SHOT_AGES: f32 = 15.;
#[derive(Component, Reflect, Deserialize, Serialize)]
#[reflect(Component)]
pub struct CurverProjectile {
time: f32,
damage: u32,
projectile: String,
}
pub fn plugin(app: &mut App) {
app.register_type::<CurverProjectile>();
app.add_systems(
Update,
(shot_collision, enemy_hit).run_if(in_state(GameState::Playing)),
);
#[cfg(feature = "client")]
app.add_systems(Update, shot_visuals.run_if(in_state(GameState::Playing)));
app.add_systems(
FixedUpdate,
(update, timeout).run_if(in_state(GameState::Playing)),
);
global_observer!(app, on_trigger_curver);
}
#[cfg(feature = "client")]
fn shot_visuals(
mut commands: Commands,
query: Query<(Entity, &CurverProjectile), Added<CurverProjectile>>,
) {
for (entity, projectile) in query.iter() {
if commands.get_entity(entity).is_ok() {
let child = commands
.spawn((
crate::utils::auto_rotate::AutoRotation(
Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3),
),
crate::protocol::GltfSceneRoot::Projectile(projectile.projectile.clone()),
))
.id();
commands.entity(entity).add_child(child);
}
}
}
fn on_trigger_curver(
trigger: On<TriggerCurver>,
mut commands: Commands,
query_transform: Query<&Transform>,
time: Res<Time>,
heads_db: Res<HeadsDatabase>,
) {
let state = trigger.event().0;
let rotation = if let Some(target) = state.target {
let t = query_transform
.get(target)
.expect("target must have transform");
Transform::from_translation(state.pos)
.looking_at(t.translation, Vec3::Y)
.rotation
} else {
state.rot()
};
let head = heads_db.head_stats(state.head);
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
let id = commands
.spawn((
Name::new("projectile-curver"),
CurverProjectile {
time: time.elapsed_secs(),
damage: head.damage,
projectile: head.projectile.clone(),
},
Collider::capsule_endpoints(0.5, Vec3::new(0., 0., 1.), Vec3::new(0., 0., -1.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
RigidBody::Kinematic,
CollisionEventsEnabled,
Visibility::default(),
transform,
Replicated,
ProjectileId(state.trigger_id),
))
.id();
debug!(id=?id, trigger_id = state.trigger_id, "Curver");
}
fn enemy_hit(
mut commands: Commands,
mut collision_message_reader: MessageReader<CollisionStart>,
query_shot: Query<&CurverProjectile>,
query_npc: Query<&EnemySpawn>,
) {
for CollisionStart {
collider1: e1,
collider2: e2,
..
} in collision_message_reader.read()
{
if !query_shot.contains(*e1) && !query_shot.contains(*e2) {
continue;
}
if !query_npc.contains(*e1) && !query_npc.contains(*e2) {
continue;
}
let (enemy_entity, projectile) = if query_npc.contains(*e1) {
(*e1, query_shot.get(*e2))
} else {
(*e2, query_shot.get(*e1))
};
if let Ok(projectile) = projectile {
let damage = projectile.damage;
commands
.entity(enemy_entity)
.trigger(|entity| Hit { entity, damage });
}
}
}
fn update(mut query: Query<&mut Transform, With<CurverProjectile>>) {
for mut transform in query.iter_mut() {
let forward = transform.forward();
transform.translation += forward * 0.5;
}
}
fn timeout(mut commands: Commands, query: Query<(Entity, &CurverProjectile)>, time: Res<Time>) {
let current_time = time.elapsed_secs();
for (e, CurverProjectile { time, .. }) in query.iter() {
if current_time > time + MAX_SHOT_AGES {
commands.entity(e).despawn();
}
}
}
fn shot_collision(
mut commands: Commands,
mut collision_message_reader: MessageReader<CollisionStart>,
query_shot: Query<&Transform, With<CurverProjectile>>,
sensors: Query<(), With<Sensor>>,
) {
for CollisionStart {
collider1: e1,
collider2: e2,
..
} in collision_message_reader.read()
{
if !query_shot.contains(*e1) && !query_shot.contains(*e2) {
continue;
}
if sensors.contains(*e1) && sensors.contains(*e2) {
continue;
}
let shot_entity = if query_shot.contains(*e1) { *e1 } else { *e2 };
let Ok(shot_pos) = query_shot.get(shot_entity).map(|t| t.translation) else {
continue;
};
if let Ok(mut entity) = commands.get_entity(shot_entity) {
entity.try_despawn();
} else {
continue;
}
commands.server_trigger(ToClients {
message: BuildExplosionSprite {
pos: shot_pos,
pixels_per_meter: 128.,
time: 0.01,
},
mode: SendMode::Broadcast,
});
}
}

View File

@@ -1,156 +0,0 @@
use super::TriggerMissile;
use crate::{
GameState,
abilities::{ExplodingProjectile, ExplodingProjectileSet, ProjectileId},
heads_database::HeadsDatabase,
physics_layers::GameLayer,
protocol::{GltfSceneRoot, PlaySound},
utils::{global_observer, trail::SpawnTrail},
};
use avian3d::prelude::*;
use bevy::prelude::*;
use bevy_replicon::prelude::Replicated;
use std::f32::consts::PI;
const MAX_SHOT_AGES: f32 = 15.;
const MISSLE_SPEED: f32 = 3.;
#[derive(Component, Reflect)]
#[reflect(Component)]
struct MissileProjectile {
time: f32,
damage: u32,
}
pub fn plugin(app: &mut App) {
app.add_systems(
FixedUpdate,
shot_collision.in_set(ExplodingProjectileSet::Mark),
);
app.add_systems(
FixedUpdate,
(update, timeout).run_if(in_state(GameState::Playing)),
);
global_observer!(app, on_trigger_missile);
}
fn on_trigger_missile(
trigger: On<TriggerMissile>,
mut commands: Commands,
query_transform: Query<&Transform>,
time: Res<Time>,
heads_db: Res<HeadsDatabase>,
) {
let state = trigger.event().0;
let rotation = if let Some(target) = state.target {
let t = query_transform
.get(target)
.expect("target must have transform");
Transform::from_translation(state.pos)
.looking_at(t.translation, Vec3::Y)
.rotation
} else {
state.rot()
};
let head = heads_db.head_stats(state.head);
let mut transform = Transform::from_translation(state.pos).with_rotation(rotation);
transform.translation += transform.forward().as_vec3() * 2.0;
let id = commands
.spawn((
Name::new("projectile-missile"),
MissileProjectile {
time: time.elapsed_secs(),
damage: head.damage,
},
SpawnTrail::new(
12,
LinearRgba::rgb(1., 0.0, 0.),
LinearRgba::rgb(0.9, 0.9, 0.),
10.,
)
.init_with_pos(),
Collider::capsule_endpoints(0.4, Vec3::new(0., 0., 2.), Vec3::new(0., 0., -2.)),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
Sensor,
RigidBody::Kinematic,
CollisionEventsEnabled,
Visibility::default(),
transform,
Replicated,
ProjectileId(state.trigger_id),
children![(
Transform::from_rotation(Quat::from_rotation_x(PI / 2.).inverse()),
GltfSceneRoot::Projectile("missile".to_string()),
Replicated
)],
))
.id();
debug!(id=?id, trigger_id = state.trigger_id, "Missile");
}
fn update(mut query: Query<&mut Transform, With<MissileProjectile>>) {
for mut transform in query.iter_mut() {
let forward = transform.forward();
transform.translation += forward * MISSLE_SPEED;
}
}
fn timeout(mut commands: Commands, query: Query<(Entity, &MissileProjectile)>, time: Res<Time>) {
let current_time = time.elapsed_secs();
for (e, MissileProjectile { time, .. }) in query.iter() {
if current_time > time + MAX_SHOT_AGES {
commands.entity(e).despawn();
}
}
}
fn shot_collision(
mut commands: Commands,
mut collision_message_reader: MessageReader<CollisionStart>,
query_shot: Query<(&MissileProjectile, &Transform)>,
sensors: Query<(), With<Sensor>>,
) {
for CollisionStart {
collider1: e1,
collider2: e2,
..
} in collision_message_reader.read()
{
if !query_shot.contains(*e1) && !query_shot.contains(*e2) {
continue;
}
if sensors.contains(*e1) && sensors.contains(*e2) {
continue;
}
let shot_entity = if query_shot.contains(*e1) { *e1 } else { *e2 };
let Ok((shot_pos, damage)) = query_shot
.get(shot_entity)
.map(|(projectile, t)| (t.translation, projectile.damage))
else {
continue;
};
commands.entity(shot_entity).insert(ExplodingProjectile {
sound: PlaySound::MissileExplosion,
damage,
position: shot_pos,
radius: 6.0,
animation: true,
anim_pixels_per_meter: 16.0,
anim_time: 0.01,
});
}
}

View File

@@ -1,351 +0,0 @@
pub mod arrow;
pub mod curver;
pub mod gun;
pub mod healing;
pub mod missile;
pub mod thrown;
use crate::{
GameState,
aim::AimTarget,
character::CharacterHierarchy,
control::Inputs,
global_observer,
heads::ActiveHeads,
heads_database::HeadsDatabase,
loading_assets::GameAssets,
physics_layers::GameLayer,
player::Player,
protocol::PlaySound,
utils::{Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer},
};
use bevy::{light::NotShadowCaster, prelude::*};
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, Signature, ToClients};
use bevy_sprite3d::Sprite3d;
pub use healing::Healing;
use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Reflect, Default, Serialize, Deserialize)]
pub enum HeadAbility {
#[default]
None,
Arrow,
Thrown,
Gun,
Missile,
Medic,
Curver,
Boat,
Turbo,
Spray,
}
#[derive(Debug, Reflect, Clone, Copy)]
pub struct TriggerData {
target: Option<Entity>,
dir: Dir3,
pos: Vec3,
target_layer: GameLayer,
head: usize,
trigger_id: usize,
}
impl TriggerData {
pub fn new(
target: Option<Entity>,
dir: Dir3,
pos: Vec3,
target_layer: GameLayer,
head: usize,
trigger_id: usize,
) -> Self {
Self {
target,
dir,
pos,
target_layer,
head,
trigger_id,
}
}
pub fn rot(&self) -> Quat {
// as it turns out, `glam` comes with some `looking_to` functions for left and right handed coordinate systems, but it seems like they're wrong?
// at the very least they give some very odd results and the cross multiplications inside all look backwards compared to what bevy transforms do.
Transform::default().looking_to(self.dir, Vec3::Y).rotation
}
}
#[derive(Event, Reflect)]
pub struct TriggerGun(pub TriggerData);
#[derive(Event, Reflect)]
pub struct TriggerArrow(pub TriggerData);
#[derive(Event, Reflect)]
pub struct TriggerThrow(pub TriggerData);
#[derive(Event, Reflect)]
pub struct TriggerMissile(pub TriggerData);
#[derive(Event, Reflect)]
pub struct TriggerCurver(pub TriggerData);
#[derive(Component, Default, Reflect)]
#[reflect(Component)]
pub struct PlayerTriggerState {
next_trigger_timestamp: f32,
projectile_count: usize,
}
#[derive(Component, Reflect, Deserialize, Serialize, Hash)]
#[reflect(Component)]
#[require(Signature::of::<ProjectileId>())]
pub struct ProjectileId(pub usize);
#[derive(Component)]
#[component(storage = "SparseSet")]
#[allow(dead_code)]
pub struct ExplodingProjectile {
sound: PlaySound,
damage: u32,
position: Vec3,
radius: f32,
animation: bool,
anim_pixels_per_meter: f32,
anim_time: f32,
}
// TODO: move explosions into separate modul
#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ExplodingProjectileSet {
Mark,
Explode,
}
pub fn plugin(app: &mut App) {
app.add_plugins(gun::plugin);
app.add_plugins(thrown::plugin);
app.add_plugins(arrow::plugin);
app.add_plugins(missile::plugin);
app.add_plugins(healing::plugin);
app.add_plugins(curver::plugin);
app.configure_sets(
FixedUpdate,
ExplodingProjectileSet::Explode
.after(ExplodingProjectileSet::Mark)
.run_if(in_state(GameState::Playing)),
);
app.add_systems(OnEnter(GameState::Playing), setup);
app.add_systems(
FixedUpdate,
(update, update_heal_ability)
.chain()
.run_if(in_state(GameState::Playing)),
);
app.add_systems(
FixedUpdate,
explode_projectiles.in_set(ExplodingProjectileSet::Explode),
);
global_observer!(app, build_explosion_sprite);
}
fn explode_projectiles(mut commands: Commands, query: Query<(Entity, &ExplodingProjectile)>) {
for (shot_entity, projectile) in query.iter() {
debug!(id=?shot_entity, "Projectile explosion");
if let Ok(mut entity) = commands.get_entity(shot_entity) {
entity.try_despawn();
} else {
continue;
}
commands.server_trigger(ToClients {
mode: SendMode::Broadcast,
message: projectile.sound.clone(),
});
commands.trigger(Explosion {
damage: projectile.damage,
position: projectile.position,
//TODO: should be around 1 grid in distance
radius: projectile.radius,
});
//TODO: support different impact animations
if projectile.animation {
commands.server_trigger(ToClients {
mode: SendMode::Broadcast,
message: BuildExplosionSprite {
pos: projectile.position,
pixels_per_meter: projectile.anim_pixels_per_meter,
time: projectile.anim_time,
},
});
}
}
}
fn update(
mut commands: Commands,
mut query: Query<
(
Entity,
&mut ActiveHeads,
&mut PlayerTriggerState,
&AimTarget,
&Inputs,
),
With<Player>,
>,
query_transform: Query<&Transform>,
heads_db: Res<HeadsDatabase>,
time: Res<Time>,
character: CharacterHierarchy,
) {
for (player, mut active_heads, mut trigger_state, target, inputs) in query.iter_mut() {
if inputs.trigger && trigger_state.next_trigger_timestamp < time.elapsed_secs() {
let Some(state) = active_heads.current() else {
return;
};
if !state.has_ammo() {
return;
}
let target = if let Some(target) = target.0
&& query_transform.get(target).is_ok()
{
Some(target)
} else {
None
};
let Some(projectile_origin) = character
.projectile_origin(player)
.map(|origin| origin.translation())
else {
return;
};
let head = heads_db.head_stats(state.head);
if matches!(head.ability, HeadAbility::None | HeadAbility::Medic) {
return;
}
active_heads.use_ammo(time.elapsed_secs());
trigger_state.next_trigger_timestamp = time.elapsed_secs() + (1. / head.aps);
trigger_state.projectile_count += 1;
let trigger_state = TriggerData {
dir: Dir3::try_from(inputs.look_dir).unwrap_or(Dir3::NEG_Z),
pos: projectile_origin,
target,
target_layer: GameLayer::Npc,
head: state.head,
trigger_id: trigger_state.projectile_count,
};
match head.ability {
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
HeadAbility::Arrow => commands.trigger(TriggerArrow(trigger_state)),
HeadAbility::Curver => commands.trigger(TriggerCurver(trigger_state)),
_ => panic!("Unhandled head ability"),
};
}
}
}
fn update_heal_ability(
mut commands: Commands,
players: Query<(Entity, &ActiveHeads, Ref<Inputs>), With<Player>>,
heads_db: Res<HeadsDatabase>,
) {
for (player, active_heads, inputs) in players.iter() {
if inputs.is_changed() {
let Some(state) = active_heads.current() else {
return;
};
let head = heads_db.head_stats(state.head);
if !matches!(head.ability, HeadAbility::Medic) {
return;
}
use crate::abilities::healing::HealingState;
if inputs.trigger {
use crate::abilities::healing::HealingStateChanged;
commands.trigger(HealingStateChanged {
state: HealingState::Started,
entity: player,
});
} else {
use crate::abilities::healing::HealingStateChanged;
commands.trigger(HealingStateChanged {
state: HealingState::Stopped,
entity: player,
});
}
}
}
}
#[derive(Resource)]
struct ShotAssets {
image: Handle<Image>,
layout: Handle<TextureAtlasLayout>,
}
fn setup(mut commands: Commands, assets: Res<GameAssets>, asset_server: Res<AssetServer>) {
let layout = TextureAtlasLayout::from_grid(UVec2::splat(256), 7, 6, None, None);
let texture_atlas_layout = asset_server.add(layout);
commands.insert_resource(ShotAssets {
image: assets.impact_atlas.clone(),
layout: texture_atlas_layout,
});
}
#[derive(Clone, Copy, Event, Serialize, Deserialize, PartialEq)]
pub struct BuildExplosionSprite {
pos: Vec3,
pixels_per_meter: f32,
time: f32,
}
fn build_explosion_sprite(
trigger: On<BuildExplosionSprite>,
mut commands: Commands,
assets: Res<ShotAssets>,
) {
commands.spawn((
Transform::from_translation(trigger.event().pos),
Sprite3d {
pixels_per_metre: trigger.event().pixels_per_meter,
alpha_mode: AlphaMode::Blend,
unlit: true,
..default()
},
Sprite {
image: assets.image.clone(),
texture_atlas: Some(TextureAtlas {
layout: assets.layout.clone(),
index: 0,
}),
..default()
},
Billboard::All,
NotShadowCaster,
AnimationTimer::new(Timer::from_seconds(
trigger.event().time,
TimerMode::Repeating,
)),
));
}

View File

@@ -1,154 +0,0 @@
use super::TriggerThrow;
use crate::{
GameState,
abilities::{ExplodingProjectile, ExplodingProjectileSet, ProjectileId},
heads_database::HeadsDatabase,
physics_layers::GameLayer,
protocol::PlaySound,
utils::global_observer,
};
use avian3d::prelude::*;
use bevy::prelude::*;
use bevy_ballistic::launch_velocity;
use bevy_replicon::prelude::Replicated;
use serde::{Deserialize, Serialize};
#[derive(Component, Serialize, Deserialize, PartialEq)]
pub struct ThrownProjectile {
impact_animation: bool,
damage: u32,
projectile: String,
}
pub fn plugin(app: &mut App) {
app.add_systems(
FixedUpdate,
shot_collision
.in_set(ExplodingProjectileSet::Mark)
.run_if(in_state(GameState::Playing)),
);
#[cfg(feature = "client")]
app.add_systems(Update, shot_visuals.run_if(in_state(GameState::Playing)));
global_observer!(app, on_trigger_thrown);
}
#[cfg(feature = "client")]
fn shot_visuals(
mut commands: Commands,
query: Query<(Entity, &ThrownProjectile), Added<ThrownProjectile>>,
) {
for (entity, thrown) in query.iter() {
commands.entity(entity).try_insert((
crate::utils::auto_rotate::AutoRotation(
Quat::from_rotation_x(0.4) * Quat::from_rotation_z(0.3),
),
crate::protocol::GltfSceneRoot::Projectile(thrown.projectile.clone()),
));
}
}
fn on_trigger_thrown(
trigger: On<TriggerThrow>,
mut commands: Commands,
query_transform: Query<&Transform>,
heads_db: Res<HeadsDatabase>,
) {
let state = trigger.event().0;
#[cfg(feature = "client")]
commands.trigger(PlaySound::Throw);
const SPEED: f32 = 35.;
let pos = state.pos;
let vel = if let Some(target) = state.target
&& let Ok(t) = query_transform.get(target)
{
launch_velocity(pos, t.translation, SPEED, 9.81)
.map(|(low, _)| low)
.unwrap()
} else {
((state.dir.as_vec3() * 2.0) + Vec3::Y).normalize() * SPEED
};
let head = heads_db.head_stats(state.head);
//TODO: projectile db?
let explosion_animation = !matches!(state.head, 8 | 16);
let id = commands
.spawn((
Transform::from_translation(pos),
Name::new("projectile-thrown"),
ThrownProjectile {
impact_animation: explosion_animation,
damage: head.damage,
projectile: head.projectile.clone(),
},
Collider::sphere(0.4),
CollisionLayers::new(
LayerMask(GameLayer::Projectile.to_bits()),
LayerMask(state.target_layer.to_bits() | GameLayer::Level.to_bits()),
),
RigidBody::Dynamic,
CollisionEventsEnabled,
Mass(0.01),
LinearVelocity(vel),
Visibility::default(),
Sensor,
Replicated,
ProjectileId(state.trigger_id),
))
.id();
debug!(id=?id, trigger_id = state.trigger_id, "Thrown");
}
fn shot_collision(
mut commands: Commands,
mut collision_message_reader: MessageReader<CollisionStart>,
query_shot: Query<(&ThrownProjectile, &Transform)>,
sensors: Query<(), With<Sensor>>,
) {
for CollisionStart {
collider1: e1,
collider2: e2,
..
} in collision_message_reader.read()
{
if !query_shot.contains(*e1) && !query_shot.contains(*e2) {
continue;
}
if sensors.contains(*e1) && sensors.contains(*e2) {
continue;
}
let shot_entity = if query_shot.contains(*e1) { *e1 } else { *e2 };
let Ok((shot_pos, animation, damage)) =
query_shot.get(shot_entity).map(|(projectile, t)| {
(
t.translation,
projectile.impact_animation,
projectile.damage,
)
})
else {
continue;
};
commands.entity(shot_entity).insert(ExplodingProjectile {
sound: PlaySound::ThrowHit,
damage,
position: shot_pos,
radius: 5.0,
animation,
anim_pixels_per_meter: 32.0,
anim_time: 0.02,
});
}
}

View File

@@ -1,184 +0,0 @@
use crate::{
GameState, character::CharacterAnimations, head::ActiveHead, heads_database::HeadsDatabase,
};
use bevy::{animation::RepeatAnimation, ecs::query::QueryData, prelude::*};
use serde::{Deserialize, Serialize};
use std::time::Duration;
pub fn plugin(app: &mut App) {
app.register_type::<AnimationFlags>();
app.add_systems(
Update,
update_animation.run_if(in_state(GameState::Playing)),
);
}
#[derive(Component, Default, Reflect, PartialEq, Serialize, Deserialize)]
#[reflect(Component)]
#[require(AnimationFlagCache)]
pub struct AnimationFlags {
pub any_direction: bool,
pub jumping: bool,
pub jump_count: u8,
pub shooting: bool,
pub restart_shooting: bool,
pub hit: bool,
}
#[derive(Component, Default, Reflect)]
#[reflect(Component)]
pub struct AnimationFlagCache {
pub jump_count: u8,
}
#[derive(QueryData)]
#[query_data(mutable)]
pub struct AnimationController {
pub transitions: &'static mut AnimationTransitions,
pub player: &'static mut AnimationPlayer,
}
impl AnimationController {
pub fn play_inner(
player: &mut AnimationPlayer,
transitions: &mut AnimationTransitions,
animation: AnimationNodeIndex,
transition: Duration,
repeat: RepeatAnimation,
) {
transitions
.play(player, animation, transition)
.set_repeat(repeat);
}
}
impl AnimationControllerItem<'_, '_> {
pub fn play(
&mut self,
animation: AnimationNodeIndex,
transition: Duration,
repeat: RepeatAnimation,
) {
AnimationController::play_inner(
&mut self.player,
&mut self.transitions,
animation,
transition,
repeat,
);
}
pub fn is_playing(&self, index: AnimationNodeIndex) -> bool {
self.player.is_playing_animation(index)
}
}
const DEFAULT_TRANSITION_DURATION: Duration = Duration::ZERO;
fn update_animation(
mut animated: Query<(AnimationController, &CharacterAnimations)>,
mut character: Query<(&ActiveHead, &mut AnimationFlags, &mut AnimationFlagCache)>,
headdb: Res<HeadsDatabase>,
) {
for (mut controller, anims) in animated.iter_mut() {
let (head, mut flags, mut cache) = character.get_mut(anims.of_character).unwrap();
let head = headdb.head_stats(head.0);
let is_playing_shoot = anims.shoot.is_some()
&& controller.is_playing(anims.shoot.unwrap())
&& !controller
.player
.animation(anims.shoot.unwrap())
.unwrap()
.is_finished();
let is_playing_run_shoot = anims.run_shoot.is_some()
&& controller.is_playing(anims.run_shoot.unwrap())
&& !controller
.player
.animation(anims.run_shoot.unwrap())
.unwrap()
.is_finished();
let wait_for_shoot = !head.interrupt_shoot && (is_playing_shoot || is_playing_run_shoot);
if wait_for_shoot {
return;
} else if flags.shooting && flags.any_direction && anims.run_shoot.is_some() {
if !controller.is_playing(anims.run_shoot.unwrap()) {
controller.play(
anims.run_shoot.unwrap(),
DEFAULT_TRANSITION_DURATION,
RepeatAnimation::Never,
);
}
if controller
.player
.animation(anims.run_shoot.unwrap())
.unwrap()
.is_finished()
|| flags.restart_shooting
{
controller
.player
.animation_mut(anims.run_shoot.unwrap())
.unwrap()
.replay();
flags.restart_shooting = false;
}
} else if flags.shooting && anims.shoot.is_some() {
if !controller.is_playing(anims.shoot.unwrap()) {
controller.play(
anims.shoot.unwrap(),
DEFAULT_TRANSITION_DURATION,
RepeatAnimation::Never,
);
}
if controller
.player
.animation(anims.shoot.unwrap())
.unwrap()
.is_finished()
|| flags.restart_shooting
{
controller
.player
.animation_mut(anims.shoot.unwrap())
.unwrap()
.replay();
flags.restart_shooting = false;
}
} else if flags.hit {
if !controller.is_playing(anims.hit) {
controller.play(
anims.hit,
DEFAULT_TRANSITION_DURATION,
RepeatAnimation::Never,
);
}
} else if flags.jumping {
if !controller.is_playing(anims.jump) || flags.jump_count != cache.jump_count {
controller.play(
anims.jump,
DEFAULT_TRANSITION_DURATION,
RepeatAnimation::Never,
);
cache.jump_count = flags.jump_count;
}
} else if flags.any_direction {
if !controller.player.is_playing_animation(anims.run) {
controller.play(
anims.run,
DEFAULT_TRANSITION_DURATION,
RepeatAnimation::Forever,
);
}
} else if !controller.is_playing(anims.idle) {
controller.play(
anims.idle,
DEFAULT_TRANSITION_DURATION,
RepeatAnimation::Forever,
);
}
}
}

View File

@@ -1,3 +0,0 @@
pub fn main() {
hedz_reloaded::launch();
}

View File

@@ -1,8 +0,0 @@
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)]
pub struct CameraTarget;
#[derive(Component, Reflect, Debug, Serialize, Deserialize, PartialEq)]
pub struct CameraArmRotation;

View File

@@ -1,94 +0,0 @@
use crate::{
GameState, global_observer,
physics_layers::GameLayer,
player::Player,
protocol::{GltfSceneRoot, PlaySound, is_server},
server_observer,
tb_entities::CashSpawn,
};
use avian3d::prelude::*;
use bevy::prelude::*;
use bevy_replicon::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Component, Reflect, Default, Deserialize, Serialize)]
#[reflect(Component)]
#[require(Transform)]
pub struct Cash;
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
struct CashText;
#[derive(Component, Reflect, Default, Serialize, Deserialize, PartialEq)]
pub struct CashInventory {
pub cash: i32,
}
#[derive(EntityEvent)]
pub struct CashCollectEvent {
pub entity: Entity,
}
pub fn plugin(app: &mut App) {
app.add_systems(OnEnter(GameState::Playing), setup.run_if(is_server));
app.add_systems(Update, rotate.run_if(in_state(GameState::Playing)));
server_observer!(app, on_cash_collected);
}
fn setup(mut commands: Commands, query: Query<Entity, With<CashSpawn>>) {
for entity in query.iter() {
commands
.entity(entity)
.insert((
Name::new("cash"),
GltfSceneRoot::Cash,
Cash,
Collider::cuboid(2., 3.0, 2.),
CollisionLayers::new(GameLayer::CollectibleSensors, LayerMask::ALL),
RigidBody::Kinematic,
CollisionEventsEnabled,
Sensor,
Replicated,
))
.observe(on_cash_collision);
}
}
fn on_cash_collected(
trigger: On<CashCollectEvent>,
mut commands: Commands,
mut query_player: Query<&mut CashInventory, With<Player>>,
) {
if let Ok(mut cash) = query_player.get_mut(trigger.entity) {
commands.server_trigger(ToClients {
mode: SendMode::Broadcast,
message: PlaySound::CashCollect,
});
cash.cash += 100;
}
}
fn on_cash_collision(
trigger: On<CollisionStart>,
mut commands: Commands,
query_player: Query<&Player>,
) {
let collectable = trigger.event().collider1;
let collider = trigger.event().collider2;
if query_player.contains(collider) {
commands.trigger(CashCollectEvent { entity: collider });
commands.entity(collectable).despawn();
}
}
fn rotate(time: Res<Time>, mut query: Query<&mut Rotation, With<Cash>>) {
for mut rotation in query.iter_mut() {
rotation.0 = rotation
.0
.mul_quat(Quat::from_rotation_y(time.delta_secs()));
}
}

View File

@@ -1,89 +0,0 @@
use crate::{
cash::CashInventory,
control::CashHealPressed,
hitpoints::Hitpoints,
player::Player,
protocol::{ClientToController, PlaySound},
};
use bevy::prelude::*;
use bevy_replicon::prelude::{FromClient, SendMode, ServerTriggerExt, ToClients};
pub fn plugin(app: &mut App) {
app.add_systems(FixedUpdate, on_heal_trigger);
}
#[derive(Debug, PartialEq, Eq)]
struct HealAction {
cost: i32,
damage_healed: u32,
}
fn on_heal_trigger(
mut commands: Commands,
controllers: ClientToController,
mut query: Query<(&mut Hitpoints, &mut CashInventory), With<Player>>,
mut inputs: MessageReader<FromClient<CashHealPressed>>,
) {
for press in inputs.read() {
let controller = controllers.get_controller(press.client_id);
let (mut hp, mut cash) = query.get_mut(controller).unwrap();
if hp.max() || cash.cash == 0 {
return;
}
let action = heal(cash.cash, hp.get().1 - hp.get().0);
hp.heal(action.damage_healed);
cash.cash = cash.cash.saturating_sub(action.cost);
//TODO: trigger ui cost animation
commands.server_trigger(ToClients {
mode: SendMode::Broadcast,
message: PlaySound::CashHeal,
});
}
}
fn heal(cash: i32, damage: u32) -> HealAction {
let cost = (damage as f32 / 10. * 25.) as i32;
if cash >= cost {
HealAction {
cost,
damage_healed: damage,
}
} else {
let damage_healed = (cash as f32 * 10. / 25.) as u32;
HealAction {
cost: cash,
damage_healed,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_heal() {
assert_eq!(
heal(100, 10),
HealAction {
cost: 25,
damage_healed: 10
}
);
assert_eq!(
heal(100, 90),
HealAction {
cost: 100,
damage_healed: 40
}
);
}
}

Some files were not shown because too many files have changed in this diff Show More