28 Commits

Author SHA1 Message Date
shawn db477b4c48 Fix simulation_server_stub Entity class conflict + ServerConfig load timing
- Renamed inner class Entity→StubEntity (Godot has native Entity class)
- Added await ServerConfig.config_loaded before reading map_list in server_main.gd
- Config loads via call_deferred, so _ready() must wait for signal
2026-07-02 10:24:32 -04:00
shawn 4a5264c5b0 Fix headless class_name dependencies
- Replaced TeamData.Team type hints with int in all scripts
- Added explicit preloads for headless mode class resolution
- Created stub LagCompensation and DamageProcessor scripts
- Fixed PluginManager, SpawnManager, EconomyManager, BuyZone,
  RoundManager, BuyMenuHandler, BombObjective class_name references
- Updated server config to match ServerConfig.gd format

Gray screen root cause: server scripts failed to parse in headless
mode due to Godot 4 class_name loading order (Resource after Node),
leaving server_main.gd non-functional — accepted connections but
never spawned players.
2026-07-02 10:09:28 -04:00
shawn ad48f38ca5 feat: integrate ChaffGames FPS template as local player controller
Replaces the overhead box-mesh view with a full FPS character:

- mouse look, WASD movement, jump (Space), crouch (C), sprint (Shift), lean (Q/E)

- 6 weapons (LMB shoot, R reload, scroll switch, F melee, G drop)

- Crosshair HUD, ammo counter, sprint bar

- Client sends position to server at 20Hz for multiplayer visibility

- Server broadcasts positions to all peers

- Remote players shown as boxes (placeholder)

source: https://godotengine.org/asset-library/asset/2652 (MIT license)
2026-07-02 00:26:45 -04:00
shawn f0c95dcfd2 fix: create local player on client so WASD movement works
client_main.gd: when server spawns our player, create a player node with authority

- Camera follows local player from 3/4 overhead view

- player.gd WASD input sends position to server via RPC

- Server validates and broadcasts to all peers
2026-07-02 00:15:08 -04:00
shawn d060ac449d fix: suppress 3 benign startup warnings on Windows client
- Create missing server/plugins/ dir with .gdignore (stops PluginManager warning)

- Create config/default_server_config.cfg (stops ServerConfig error)

- Reorder light look_at after add_child (stops 'not in tree' warning)

- Switch rendering_method from opengl3 to forward_plus (RTX 2080 Vulkan works fine; gray screen was server-scene issue not OpenGL)
2026-07-02 00:05:27 -04:00
shawn 87060b0b71 fix: move sub_resource tags before nodes in test_range.tscn (export parse fix) 2026-07-02 00:02:25 -04:00
shawn 638e109d8b fix: client_main.tscn overrides server_host to 68.202.6.107 2026-07-01 22:21:29 -04:00
shawn 4c0e334575 fix: lazy load scenes in entry.gd (no preload of server in client mode) 2026-07-01 22:04:41 -04:00
shawn e270f34150 fix: client_main defaults to remote server (68.202.6.107) 2026-07-01 22:04:03 -04:00
shawn 20ca43f928 fix: add smart entry scene (client vs server via headless detection) 2026-07-01 21:44:14 -04:00
shawn aae57c63bd fix: TeamManager signal type hints — use int instead of TeamData.Team (export parse order issue) 2026-07-01 21:41:36 -04:00
shawn 0994a55486 chore: clean up duplicate .gdextension files (keep only gdextension/simulation.gdextension) 2026-07-01 21:38:46 -04:00
shawn 82216bfa64 fix: Windows client compile errors — get_world_3d on Node, array type mismatch, null plugin guard, maps format 2026-07-01 21:38:07 -04:00
shawn d5098c61e1 fix: switch to OpenGL 3 renderer for Windows compatibility (gray screen fix) 2026-07-01 21:16:26 -04:00
shawn 194aad8f83 t_p2_bomb: Bomb/defuse objective system for search & destroy rounds
Adds:
- bomb_objective.gd: server-authoritative bomb state machine (IDLE→PLANTED→EXPLODED/DEFUSED)
  - plant_bomb(player_id, pos) - T-side only, LIVE phase, inside bombsite
  - defuse_bomb(player_id) - CT-side only, near bomb, 5s timer
  - cancel_defuse(player_id) - if defuser moves or is killed
  - bomb_explode() - configurable radius damage (10m lethal, 15m half)
  - 40s bomb timer (configurable)
  - Full query API: is_bomb_planted(), get_bomb_position(), get_bomb_timer()
  - Signals: bomb_planted, bomb_defused, bomb_exploded, defuse_started, defuse_cancelled
  - Connected to RoundManager via GameServer for round-end outcomes

- bomb_carrier.gd: client-side bomb carrier UI
  - Bomb status/defuse progress UI updates
  - Direction indicator to planted bomb
  - "Hold E to defuse" prompt for CT near bomb
  - "Hold E to plant" prompt for T with bomb in bombsite

- test_range.tscn: BombsiteA (south-west, -20,0,-5) and BombsiteB (north-east, 15,0,20)
  - 8x4m Area3D zones with BoxShape3D collision
  - PlantPosition Marker3D children
  - Group 'bomb_sites' for server discovery

- game_server.gd: BombObjective integration in _ready()
  - Creates and wires BombObjective to RoundManager, TeamManager, DamageProcessor
  - Registers bomb sites from scene tree
  - Connects bomb_exploded/bomb_defused → RoundManager.end_round()
  - Connects round_ended → bomb.reset()
2026-07-01 20:39:17 -04:00
shawn 222dcaebb3 t_p4_anticheat: add basic server-side anti-cheat controller
- Rate limiting: flags players exceeding 130Hz input RPC rate
- Speed hacks: validates position deltas against max speed * 1.5
- Aimbot snap: flags look-direction changes >45° in one tick
- Suspicion level tracking (0.0-1.0) with decay over time
- Escalating thresholds: 0.3=warn(log), 0.6=kick(signal), 0.8=ban(signal, 1h temp)
- Configurable max speed via set_max_speed()
- Ban handler Callable for caller-defined disconnect logic
- Design: generous tolerances, near-zero false positives, Phase-4 basic hardening
2026-07-01 20:37:20 -04:00
shawn 0aa29d33e4 fix: GDExtension C++ build — fix enum bitwise cast, add .gdextension entry file 2026-07-01 20:34:15 -04:00
shawn 6a08487c4c t_p2_buy: add buy menu and economy system
Creates the full buy/economy subsystem for a tactical FPS:

- EconomyManager (server/scripts/economy/economy_manager.gd): per-player
  money tracking with starting 00, kill/round/objective rewards, spending
  validation, escalating loss bonuses, and money_changed signal.

- BuyMenuHandler (server/scripts/economy/buy_menu_handler.gd): server-side
  RPC validation of purchase requests — checks weapon validity, buy-zone
  membership (scans BuyZone nodes in scene tree), affordability, then
  deducts money and grants weapon via WeaponServer.give_weapon().

- BuyMenu (client/characters/weapon/buy_menu.gd): client-side Control UI
  with styled panel, money display, weapon grid (name/cost/stats), numeric
  key (1-4) and mouse-click purchase, server-driven affordability, and
  auto-close after purchase.

- GameServer integration: creates EconomyManager + BuyMenuHandler in
  _ready(), registers players for economy on spawn, awards kill rewards
  via DamageProcessor.player_killed signal, and unregisters on despawn.

Economy rules: win=+,250, loss=,900 (+00/streak, cap ,900),
kill=+00, bomb_plant/defuse=+00. Weapon costs: pistol=00,
smg=,200, rifle=,700, shotgun=,900.
2026-07-01 20:31:25 -04:00
shawn f20d532add t_p1_round: Add round/match lifecycle system (RoundManager)
- New server/scripts/round/round_manager.gd with:
  - RoundPhase enum (WARMUP, PREP, LIVE, POST)
  - Round life cycle: warmup -> prep (15s) -> live (105s) -> post (8s)
  - Team elimination detection using entity->peer->team mappings
  - Time-expired round ending (default: CT wins)
  - Economy hooks: win money (250), loss money (900 escalating to 900), kill bonus (00)
  - All requested signals: round_phase_changed, round_started, round_ended, match_point, warmup_ended, round_economy_data

- Updated GameServer:
  - Creates and wires RoundManager in _ready()
  - Connects DamageProcessor.player_killed -> round tracking + elimination detection
  - Added peer_to_entity mapping for backwards entity lookup
  - Added RCON command handler (_on_rcon_command) for start_match/end_round

- Updated RCON command handler:
  - Added start_match and end_round to dispatch list and help text
2026-07-01 20:30:01 -04:00
shawn 705b068ed2 t_p1_hitscan: Add lag-compensated hitscan weapon system
NEW: server/scripts/combat/lag_compensation.gd
  - 128-entry ring buffer of player positions per server tick
  - rewind_and_raycast(tick, origin, direction, range, exclude):
    saves current positions, rewinds to tick-time positions,
    performs PhysicsDirectSpaceState3D.intersect_ray(), restores
  - Falls back to normal raycast if history missing for tick
  - shot_processed signal

NEW: scripts/combat/damage_processor.gd
  - Processes WeaponServer hit results: applies damage, tracks kills
  - Optional distance-based damage falloff
  - player_damaged / player_killed signals
  - Health and kill-count queries

MODIFY: server/scripts/weapons/weapon_server.gd
  - fire() now takes tick parameter: fire(tick, player_id, ...)
  - _perform_hitscan() uses LagCompensation.rewind_and_raycast()
    when lag_compensation reference is set and tick >= 0
  - Non-compensated fallback path preserved

MODIFY: server/scripts/game_server.gd
  - Adds _current_tick counter, incremented each simulation tick
  - Creates WeaponServer + LagCompensation + DamageProcessor as children
  - record_tick() called before each tick's simulation
  - register/unregister_player_node() for player lifecycle
  - _on_player_killed() relays kill events via player_damaged signal

MODIFY: scripts/network/server_main.gd
  - _spawn_player() registers node with GameServer.register_player_node()
  - _despawn_player() unregisters via unregister_player_node()
2026-07-01 20:26:18 -04:00
shawn 3465922be4 t_p6_browser: add workshop map browser with voting (3 files)
- workshop_browser.gd: scans server maps dir for .pck/.tscn files
- map_browser_rpc.gd: client-server RPC bridge for map list/change
- map_vote.gd: simple >50% majority vote with 30s timeout

File scope:
  server/scripts/browser/workshop_browser.gd (NEW)
  scripts/network/map_browser_rpc.gd (NEW)
  server/scripts/browser/map_vote.gd (NEW)
2026-07-01 20:24:23 -04:00
shawn 46ff83325f t_p2: Add team, spawn, and buy-zone system
- team_data.gd: Team enum (SPECTATOR/CT/Terrorist), constants for names,
  colors, starting money, and spawn groups; TeamData resource class
- team_manager.gd: Player-to-team assignment, auto-balancing, score
  tracking; round-independent (persists across rounds)
- spawn_manager.gd: Scans scene for Marker3D nodes in spawn_points_ct
  and spawn_points_t groups; selects valid spawns (farthest from enemies
  with proximity check, fallback to first available)
- buy_zone.gd: Area3D-based trigger zone with team filtering, player
  enter/exit tracking and signals
- test_range.tscn: Added 2 CT spawn markers and 2 T spawn markers with
  appropriate groups
2026-07-01 20:24:16 -04:00
shawn f6d69545c9 feat: implement client-side prediction and reconciliation system
Add client-side prediction (t_p1_pred) with:

- scripts/network/snapshot.gd — lightweight snapshot resource with
  to_dict/from_dict serialization for RPC. Stores position (Vector3),
  rotation (Quaternion), velocity (Vector3), grounded (bool).

- scripts/network/client_prediction.gd — prediction/reconciliation
  controller. 64-entry ring buffer of local snapshots, sends inputs to
  server each physics tick (128Hz, ENet channel 0), detects misprediction
  on server state arrival, rewinds and re-applies unconfirmed inputs.
  Also supports remote player interpolation.

- scripts/network/network_manager.gd — new RPC endpoints for client
  prediction: send_client_input (client->server, ch 0) and
  send_server_state (server->client, ch 1). New signals for routing.

- client/characters/character/fps_character_controller.gd — prediction
  hooks in _physics_process: on_before_tick() captures pre-input
  snapshot, on_after_tick() sends input to server. Client prediction
  path uses local movement (instant feedback) instead of reading from
  SimulationServer entity.

Architecture:
  Each tick: client applies input → predicts new state locally
  → sends input to server → server returns authoritative state
  → client compares and reconciles if mismatch.
2026-07-01 20:21:16 -04:00
shawn 2452aba0d7 feat: implement weapon set system (t_p2_weapons)
- WeaponData resource class with configurable weapon properties and
  pre-configured constants for rifle, pistol, shotgun, and SMG
- WeaponDefinitions singleton/static registry with WEAPONS dictionary
- WeaponServer node for server-authoritative weapon logic including
  fire rate cooldowns, ammo tracking, reload state machine, and
  multi-pellet hit-scan raycasting
- Extended WeaponManager with weapon inventory array, weapon switching,
  per-weapon state persistence, fire_animation_triggered signal
2026-07-01 20:16:22 -04:00
shawn 3c3251fa7a feat: add t_p1_greybox — greybox test map scene
Creates test_range.tscn under client/assets/maps/test_range/ as a simple
open-area test map for validating movement, shooting, and networking.

Layout:
- 80×80 open area with grey floor (CSGBox3D, 12 tris)
- Low platform (center, 1 unit high, gold)
- Medium platform (east, 2 units high, gold)
- High platform (north-west, 3 units high, red)
- Ramp (south, tilted ~20°, leads ground to low platform top)
- Enclosure/room (east, 12×12 interior, 4-unit walls, south doorway)
- SpawnPoint (Marker3D) with group 'spawn_points'
- DirectionalLight3D (sun) + WorldEnvironment
- All geometry uses Solid-color StandardMaterial3D placeholders
- Collision enabled on all CSGBox3D shapes
- ~132 tris total (well under 500 budget)
2026-07-01 20:15:34 -04:00
shawn d02b112d99 Phase 7: test server deployment + Windows export
- Fix: export_presets.cfg — platform='Windows Desktop' (correct name)
- Fix: server_main.gd — formatting bug on lag-comp string
- Fix: game_server.gd — auto-detect GDExtension, fall back to GDScript stub
- Add: server/scripts/simulation_server_stub.gd — lightweight SimulationServer in GDScript
- Add: docs/playtest-guide.md — connection instructions, control bindings, server commands
- Add: systemd user service — tactical-shooter-server (enabled, running)
- Add: server config at ~/tactical-shooter-server/server_config.cfg
- Build: Windows 109MB PE32+ client (build/tactical-shooter-windows-x86_64/)
- Build: Linux server binary (78MB) — running on oplabs:34197
- Temporarily disabled GDExtension (needs C++ build fix in entity.cpp enum casting)
2026-07-01 20:02:05 -04:00
shawn 2582cb1b0d Phase 7: Windows export preset fix + code fixes
- Fix: export_presets.cfg — platform='Windows Desktop' (not 'Windows'), Windows as [preset.0]
- Fix: plugin_manager.gd — removed class_name (autoload conflict), fixed multiline % formatting
- Disable GDExtension temporarily for clean export
- Add build/ to .gitignore (binary artifacts)
- Build artifacts: tactical-shooter.exe (109MB Windows PE32+), windows.zip, server binary (78MB)
2026-07-01 19:47:12 -04:00
shawn 9ea98aa7b8 Phase 7: Windows client export, preset fix, code fixes
- Fix: export_presets.cfg — platform='Windows Desktop' (not 'Windows'), Windows as [preset.0]
- Fix: plugin_manager.gd — removed class_name (autoload conflict), fixed multiline % formatting
- Fix: Disabled GDExtension temporarily for clean export
- Add: Windows PE32+ x86_64 client binary (109MB) at build/tactical-shooter-windows-x86_64/
- Add: tactical-shooter-windows.zip (portable zip package)
- Build: Linux server binary (78MB) rebuilt
2026-07-01 19:47:12 -04:00
190 changed files with 370520 additions and 222 deletions
+1
View File
@@ -28,3 +28,4 @@ server/build/
# Secrets (operator must create per deployment)
server/data/rcon_password.cfg
build/
build/
@@ -0,0 +1,248 @@
[gd_scene load_steps=12 format=3]
; =============================================================================
; test_range.tscn — Greybox Test Map
; =============================================================================
; A simple open-area test map for validating movement, shooting, and networking.
;
; Layout:
; - 80×80 open area with flat grey floor
; - Low platform (center, 1 unit high, gold)
; - Medium platform (east, 2 units high, gold)
; - High platform (north-west, 3 units high, red)
; - Ramp from ground to low platform (yellow)
; - Enclosure/room (east) with doorway for indoor testing (blue-grey walls)
; - Spawn point (Marker3D) with group 'spawn_points'
; - DirectionalLight3D + WorldEnvironment
;
; Total geometry: ~132 tris (well under 500 budget)
; =============================================================================
[sub_resource type="Environment" id="1"]
background_mode = 0
tonemap_mode = 0
glow_enabled = false
ambient_light_color = Color(0.25, 0.25, 0.3, 1.0)
ambient_light_energy = 0.4
ambient_light_sky_contribution = 0.0
[sub_resource type="StandardMaterial3D" id="2"]
albedo_color = Color(0.35, 0.35, 0.35, 1.0)
resource_name = "grey_floor"
[sub_resource type="StandardMaterial3D" id="3"]
albedo_color = Color(0.8, 0.6, 0.2, 1.0)
resource_name = "gold_platform"
[sub_resource type="StandardMaterial3D" id="4"]
albedo_color = Color(0.5, 0.5, 0.6, 1.0)
resource_name = "bluegrey_wall"
[sub_resource type="StandardMaterial3D" id="5"]
albedo_color = Color(0.9, 0.2, 0.2, 1.0)
resource_name = "red_high"
[sub_resource type="StandardMaterial3D" id="6"]
albedo_color = Color(0.3, 0.4, 0.3, 1.0)
resource_name = "green_floor"
[sub_resource type="StandardMaterial3D" id="7"]
albedo_color = Color(0.7, 0.7, 0.3, 1.0)
resource_name = "yellow_ramp"
[sub_resource type="BoxShape3D" id="8"]
size = Vector3(8, 4, 4)
[sub_resource type="BoxShape3D" id="9"]
size = Vector3(8, 4, 4)
; === Root Node ===
[node name="TestRange" type="Node3D"]
; === Lighting ===
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("1")
[node name="SunLight" type="DirectionalLight3D" parent="."]
transform = Transform3D(0.866, 0, -0.5, -0.354, 0.707, -0.612, 0.354, 0.707, 0.612, 0, 8, 0)
light_energy = 1.5
light_indirect_energy = 0.8
light_color = Color(1.0, 0.95, 0.9, 1.0)
shadow_enabled = true
directional_shadow_max_distance = 60.0
directional_shadow_split_1 = 0.1
directional_shadow_split_2 = 0.3
directional_shadow_split_3 = 0.6
directional_shadow_blend_splits = true
; === Spawn Points ===
[node name="SpawnPoint" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -30, 0.5, 0)
groups = ["spawn_points"]
; === CT Spawn Points (Counter-Terrorist) ===
[node name="CTSpawn_01" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -30, 0.5, -10)
groups = ["spawn_points_ct"]
[node name="CTSpawn_02" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -25, 0.5, -15)
groups = ["spawn_points_ct"]
; === T Spawn Points (Terrorist) ===
[node name="TSpawn_01" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 25, 0.5, 25)
groups = ["spawn_points_t"]
[node name="TSpawn_02" type="Marker3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30, 0.5, 20)
groups = ["spawn_points_t"]
; =============================================================================
; Bomb Sites
; =============================================================================
; BombsiteA — south-west area, 8×4m zone
; BombsiteB — north-east area, 8×4m zone
; Each has a CollisionShape3D (BoxShape3D) for overlap detection
; and a Marker3D child named "PlantPosition" marking the plant location.
; --- Bombsite A ---
[node name="BombsiteA" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -20, 1.0, -5)
collision_layer = 0
collision_mask = 0
groups = ["bomb_sites"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="BombsiteA"]
shape = SubResource("8")
[node name="PlantPosition" type="Marker3D" parent="BombsiteA"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, -1)
; --- Bombsite B ---
[node name="BombsiteB" type="Area3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 1.0, 20)
collision_layer = 0
collision_mask = 0
groups = ["bomb_sites"]
[node name="PlantPosition" type="Marker3D" parent="BombsiteB"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2)
[node name="CollisionShape3D" type="CollisionShape3D" parent="BombsiteB"]
shape = SubResource("9")
; === Open Area Floor (80×80) ===
[node name="Floor" type="CSGBox3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
size = Vector3(80, 0.5, 80)
material = SubResource("2")
use_collision = true
; === Low Platform (center, 1 unit high) ===
; 8×8 platform, top at y=2, bottom at y=0, centered at origin
[node name="LowPlatform" type="CSGBox3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.0, 0)
size = Vector3(8, 2, 8)
material = SubResource("3")
use_collision = true
; === Medium Platform (east, 2 units high) ===
; 6×6 platform, top at y=3, bottom at y=1 — jump-up from low platform height
[node name="MediumPlatform" type="CSGBox3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15, 2.0, 10)
size = Vector3(6, 2, 6)
material = SubResource("3")
use_collision = true
; === High Platform (north-west, 3 units high) ===
; 3×3 small platform, top at y=4 — requires sprint-jump to reach
[node name="HighPlatform" type="CSGBox3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -20, 3.0, -15)
size = Vector3(3, 2, 3)
material = SubResource("5")
use_collision = true
; === Ramp (south of center, leads ground → low platform top) ===
; Tilted ~20° around X axis, 6 units long along Z.
; South end near ground, north end meets low platform top (y=2).
[node name="Ramp" type="CSGBox3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.940, -0.342, 0, 0.342, 0.940, 0, 1.0, 5)
size = Vector3(6, 0.2, 6)
material = SubResource("7")
use_collision = true
; =============================================================================
; Enclosure — Indoor Testing Room
; =============================================================================
; 12×12 interior room, 4 units tall, with a 2-unit doorway on the south face.
; Centered at (30, 0, 30).
[node name="Enclosure" type="Node3D" parent="."]
[node name="RoomFloor" type="CSGBox3D" parent="Enclosure"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30, 0.25, 30)
size = Vector3(12, 0.5, 12)
material = SubResource("6")
use_collision = true
; North wall (z = 24)
[node name="Wall_N" type="CSGBox3D" parent="Enclosure"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30, 2.0, 24.25)
size = Vector3(12, 4, 0.5)
material = SubResource("4")
use_collision = true
; South wall left segment (doorway gap from x=29 to x=31)
[node name="Wall_S_Left" type="CSGBox3D" parent="Enclosure"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 26.5, 2.0, 35.75)
size = Vector3(5, 4, 0.5)
material = SubResource("4")
use_collision = true
; South wall right segment
[node name="Wall_S_Right" type="CSGBox3D" parent="Enclosure"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 33.5, 2.0, 35.75)
size = Vector3(5, 4, 0.5)
material = SubResource("4")
use_collision = true
; West wall (x = 24)
[node name="Wall_W" type="CSGBox3D" parent="Enclosure"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 24.25, 2.0, 30)
size = Vector3(0.5, 4, 12)
material = SubResource("4")
use_collision = true
; East wall (x = 36)
[node name="Wall_E" type="CSGBox3D" parent="Enclosure"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 35.75, 2.0, 30)
size = Vector3(0.5, 4, 12)
material = SubResource("4")
use_collision = true
+160
View File
@@ -0,0 +1,160 @@
@tool
extends Node
# =============================================================================
# art_kit.gd — Modular Art Kit Factory
# =============================================================================
# Provides factory methods for spawning modular environment pieces and a
# preview catalogue for map builders.
#
# Each piece is a standalone .tscn under pieces/ with:
# - CSG shapes (Box3D, Cylinder3D, Polygon3D, Combiner3D)
# - Solid-color StandardMaterial3D placeholders (roughness 0.8, metallic 0.0)
# - Collision enabled (use_collision = true or StaticBody3D + CollisionShape3D)
# - < 200 tris each
#
# Usage:
# var piece = ArtKit.spawn_piece("crate", parent_node)
# var cat = ArtKit.preview_catalogue()
# var stack = ArtKit.random_crate_layout(3, 2) # returns parent Node3D
# =============================================================================
const PIECES_DIR := "res://client/assets/scenes/modular/pieces/"
# Catalogue definition: name, description, filename (without .tscn)
const CATALOGUE: Array[Dictionary] = [
{
"name": "floor_tile",
"description": "2×2×0.2m concrete slab — grey",
"file": "floor_tile"
},
{
"name": "wall_segment",
"description": "4×0.2×3m wall panel — blue-grey",
"file": "wall_segment"
},
{
"name": "corner_wall",
"description": "L-shaped corner piece (2m each leg) — blue-grey",
"file": "corner_wall"
},
{
"name": "pillar",
"description": "0.3×3×0.3m column — dark grey",
"file": "pillar"
},
{
"name": "crate",
"description": "1×1×1m storage box — brown/tan",
"file": "crate"
},
{
"name": "barrel",
"description": "0.6m radius × 1m tall cylinder — red",
"file": "barrel"
},
{
"name": "cover_wall",
"description": "2×0.2×1.3m low wall (crouch-height cover) — grey",
"file": "cover_wall"
},
{
"name": "ramp",
"description": "2×2m, 30° incline ramp — yellow",
"file": "ramp"
},
{
"name": "stairs",
"description": "3-step staircase (2m wide, 0.25m rise per step) — grey",
"file": "stairs"
},
{
"name": "window_frame",
"description": "4×3m wall with 2×2m opening — blue-grey",
"file": "window_frame"
}
]
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
## Spawns a piece by name, adds it as a child of `parent`, and returns it.
## Returns null if the piece name is unknown or the scene fails to load.
static func spawn_piece(piece_name: String, parent: Node) -> Node3D:
var entry := _find_entry(piece_name)
if entry.is_empty():
push_error("ArtKit: Unknown piece '%s'. Use preview_catalogue() to see available pieces." % piece_name)
return null
var path := PIECES_DIR + entry.file + ".tscn"
var scene := load(path)
if scene == null:
push_error("ArtKit: Failed to load scene at '%s'." % path)
return null
var instance: Node3D = scene.instantiate()
parent.add_child(instance)
instance.owner = parent if not Engine.is_editor_hint() else _get_editor_owner(parent)
return instance
## Returns an array of { name, description, path } for all available pieces.
static func preview_catalogue() -> Array[Dictionary]:
var result: Array[Dictionary] = []
for entry in CATALOGUE:
result.append({
"name": entry.name,
"description": entry.description,
"path": PIECES_DIR + entry.file + ".tscn"
})
return result
## Generates a random stack of crates for quick testing layouts.
## `width` — number of crates along X (default 3)
## `height` — number of crates stacked vertically (default 2)
## Returns a Node3D containing the crate instances.
static func random_crate_layout(width: int = 3, height: int = 2) -> Node3D:
var root := Node3D.new()
root.name = "CrateLayout_%dx%d" % [width, height]
var crate_scene := load(PIECES_DIR + "crate.tscn")
if crate_scene == null:
push_error("ArtKit: Could not load crate scene.")
return root
for y in range(height):
for x in range(width):
# Add slight random offset for natural look (±0.05m)
var offset_x := randf_range(-0.05, 0.05)
var offset_z := randf_range(-0.05, 0.05)
# Full rotation in 90° increments
var rot_y := randi() % 4 * 90.0
var crate: Node3D = crate_scene.instantiate()
crate.position = Vector3(x * 1.0 + offset_x, y * 1.0 + 0.5, offset_z)
crate.rotation_degrees = Vector3(0, rot_y, 0)
root.add_child(crate)
return root
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
static func _find_entry(piece_name: String) -> Dictionary:
for entry in CATALOGUE:
if entry.name == piece_name:
return entry
return {}
## Returns the editor's top-level owner so new nodes are treated as part of the scene.
static func _get_editor_owner(node: Node) -> Node:
var owner = node
while owner and owner.owner:
owner = owner.owner
return owner
@@ -0,0 +1 @@
uid://dnopn0wj58j83
@@ -0,0 +1 @@
uid://c6dxl1qhan4yl
@@ -0,0 +1,16 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.7, 0.15, 0.1, 1.0)
roughness = 0.6
metallic = 0.2
resource_name = "red_barrel"
[node name="Barrel" type="CSGCylinder3D"]
radius = 0.6
height = 1.0
material = SubResource("1")
use_collision = true
slices = 16
visibility_range_end = 30.0
visibility_range_end_margin = 3.0
@@ -0,0 +1,26 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.35, 0.4, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "bluegrey_corner"
[node name="CornerWall" type="CSGCombiner3D"]
use_collision = false
visibility_range_end = 50.0
visibility_range_end_margin = 5.0
[node name="WallA" type="CSGBox3D" parent="CornerWall"]
operation = 0
size = Vector3(2, 3, 0.2)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.1)
[node name="WallB" type="CSGBox3D" parent="CornerWall"]
operation = 0
size = Vector3(0.2, 3, 2)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.1, 0, 0)
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.4, 0.4, 0.4, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "grey_cover"
[node name="CoverWall" type="CSGBox3D"]
size = Vector3(2, 1.3, 0.2)
material = SubResource("1")
use_collision = true
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.55, 0.35, 0.15, 1.0)
roughness = 0.85
metallic = 0.0
resource_name = "brown_crate"
[node name="Crate" type="CSGBox3D"]
size = Vector3(1, 1, 1)
material = SubResource("1")
use_collision = true
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.5, 0.5, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "concrete_grey"
[node name="FloorTile" type="CSGBox3D"]
size = Vector3(2, 0.2, 2)
material = SubResource("1")
use_collision = true
visibility_range_end = 60.0
visibility_range_end_margin = 10.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.2, 0.2, 0.22, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "darkgrey_pillar"
[node name="Pillar" type="CSGBox3D"]
size = Vector3(0.3, 3, 0.3)
material = SubResource("1")
use_collision = true
visibility_range_end = 30.0
visibility_range_end_margin = 3.0
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.7, 0.7, 0.3, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "yellow_ramp"
[node name="Ramp" type="CSGPolygon3D"]
polygon = PackedVector2Array(0, 0, 2, 0, 2, 1.155)
depth = 2.0
material = SubResource("1")
use_collision = true
smooth_faces = true
@@ -0,0 +1,33 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.45, 0.45, 0.45, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "grey_stairs"
[node name="Stairs" type="CSGCombiner3D"]
use_collision = false
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
[node name="Step1" type="CSGBox3D" parent="Stairs"]
operation = 0
size = Vector3(2, 0.25, 0.5)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.125, 0.75)
[node name="Step2" type="CSGBox3D" parent="Stairs"]
operation = 0
size = Vector3(2, 0.25, 0.5)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.375, 0.25)
[node name="Step3" type="CSGBox3D" parent="Stairs"]
operation = 0
size = Vector3(2, 0.25, 0.5)
material = SubResource("1")
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.625, -0.25)
@@ -0,0 +1,14 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.35, 0.4, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "bluegrey_wall"
[node name="WallSegment" type="CSGBox3D"]
size = Vector3(4, 3, 0.2)
material = SubResource("1")
use_collision = true
visibility_range_end = 50.0
visibility_range_end_margin = 5.0
@@ -0,0 +1,24 @@
[gd_scene load_steps=2 format=3]
[sub_resource type="StandardMaterial3D" id="1"]
albedo_color = Color(0.35, 0.4, 0.5, 1.0)
roughness = 0.8
metallic = 0.0
resource_name = "bluegrey_window"
[node name="WindowFrame" type="CSGCombiner3D"]
use_collision = false
visibility_range_end = 40.0
visibility_range_end_margin = 5.0
[node name="MainWall" type="CSGBox3D" parent="WindowFrame"]
operation = 0
size = Vector3(4, 3, 0.2)
material = SubResource("1")
use_collision = true
[node name="WindowCutout" type="CSGBox3D" parent="WindowFrame"]
operation = 1
size = Vector3(2, 2, 0.25)
use_collision = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
@@ -0,0 +1 @@
uid://bjtf5jiedmwx8
@@ -88,10 +88,19 @@ var _input_sequence: int = 0
## Cached input dictionary (avoids alloc per frame).
var _input_dict: Dictionary = {}
## Prediction controller reference (set by ClientPrediction system).
## When non-null and prediction_enabled, the controller uses client-side
## prediction with local movement for instant feedback instead of
## reading position from the SimulationServer entity directly.
var _prediction: Node = null
## Mouse capture state.
var _mouse_captured: bool = false
var _mouse_clicked_this_frame: bool = false
## Weapon manager reference.
var _weapon: WeaponManager = null
# ---------------------------------------------------------------------------
# Node references (set in _ready)
# ---------------------------------------------------------------------------
@@ -152,6 +161,12 @@ func _ready() -> void:
_crouch_current = 0.0
_update_crouch(0.0)
# Find weapon manager child
for child in get_children():
if child is WeaponManager:
_weapon = child
break
func _input(event: InputEvent) -> void:
# Mouse look
@@ -184,6 +199,12 @@ func _unhandled_input(event: InputEvent) -> void:
# ---------------------------------------------------------------------------
func _physics_process(delta: float) -> void:
# PREDICTION HOOK: capture pre-input state snapshot.
# Must be called BEFORE any input processing or movement so the
# snapshot represents the state at the start of this tick.
if _prediction and _prediction.prediction_enabled:
_prediction.on_before_tick()
# 1. Capture input state
var input_dir := _get_move_direction()
var jump_pressed := Input.is_action_just_pressed(&"jump")
@@ -227,34 +248,60 @@ func _physics_process(delta: float) -> void:
if _camera:
_camera.rotation.x = _pitch
# 6. Send input to simulation server
if _server != null and entity_id >= 0:
# Build input for SimulationServer (mutate cached dict to avoid alloc)
# 5b. Weapon fire with rate limiting via WeaponManager
var should_fire: bool = false
var time_since_engine_start: float = Engine.get_process_ticks() * get_physics_process_delta_time()
if _weapon:
should_fire = _weapon.try_fire(time_since_engine_start)
elif shoot_pressed:
# No weapon manager — fire every tick (unlimited)
should_fire = true
if _server != null:
if should_fire:
_server.fire_weapon(entity_id)
# 6. Build input dictionary (all modes — mutate cached dict to avoid alloc)
_input_dict["move_direction"] = input_dir
_input_dict["look_yaw"] = rad_to_deg(_yaw)
_input_dict["look_pitch"] = rad_to_deg(_pitch)
_input_dict["jump"] = jump_pressed
_input_dict["crouch"] = _crouch_active
_input_dict["sprint"] = _sprint_active
_input_dict["shoot"] = shoot_pressed
_input_dict["shoot"] = should_fire
_input_dict["aim"] = aim_pressed
_input_dict["input_sequence"] = _input_sequence
# 6b. Route input to appropriate handler.
if _prediction and _prediction.prediction_enabled:
# Client prediction: input sending is handled by the prediction
# controller's on_after_tick() call at the end of this function.
pass
elif _server != null and entity_id >= 0:
# Listen server: send to local SimulationServer.
_server.apply_input(entity_id, _input_dict)
if shoot_pressed:
_server.fire_weapon(entity_id)
# Check for hit feedback from last tick
if Engine.has_singleton("SimulationServer") or _server:
var hit_result: Dictionary = _server.get_last_hit_result()
if hit_result.get("hit", false) and _weapon:
var hit_pos := Vector3.ZERO # approximate — we don't have exact world hit position from server yet
_weapon.on_hit_confirmed(hit_pos, hit_result.get("damage", 0.0), hit_result.get("killed", false))
_input_sequence += 1
# 7. Read server state back (for local simulation / listen server)
# The SimulationServer.tick() call happens in the game manager / network layer.
# Here we optionally read the entity's canonical position from the server.
# NOTE: On a dedicated client, position comes from the network replication
# layer (server snapshot), not from direct server entity access.
# On a listen server (host+client same process) or in standalone mode,
# reading position directly from the server entity is correct.
if _server != null and entity_id >= 0:
# 7. Update position.
# Architecture:
# - Client prediction: do local CharacterBody3D movement (instant feedback),
# the prediction controller handles reconciliation when authoritative
# server state arrives.
# - Listen server: read authoritative position from SimulationServer entity.
# - Standalone (no server): fallback CharacterBody3D movement.
if _prediction and _prediction.prediction_enabled:
# Client prediction path — local movement for instant feedback.
_move_local(input_dir, delta, jump_pressed)
elif _server != null and entity_id >= 0:
# Listen server: read from SimulationServer entity.
var entity = _server.get_entity(entity_id)
if entity != null and entity.is_alive():
# Server position is ground truth — apply it to the visual body
@@ -264,6 +311,12 @@ func _physics_process(delta: float) -> void:
# Standalone mode: do local CharacterBody3D physics
_move_local(input_dir, delta, jump_pressed)
# PREDICTION HOOK: post-tick — send input to server via RPC.
# Must be called AFTER local movement so the prediction controller
# can send the input that produced this tick's movement.
if _prediction and _prediction.prediction_enabled:
_prediction.on_after_tick(_input_dict)
# ---------------------------------------------------------------------------
# Local movement (standalone / no-server fallback)
@@ -369,6 +422,13 @@ func set_entity_id(id: int) -> void:
entity_id = id
## Set the prediction controller for client-side prediction/reconciliation.
## The prediction node must have on_before_tick() / on_after_tick() methods.
## Typically a ClientPrediction instance added as a child or sibling.
func set_prediction(prediction_node: Node) -> void:
_prediction = prediction_node
## Get current crouch amount (0.0 = standing, 1.0 = fully crouched).
func get_crouch_amount() -> float:
return _crouch_current
@@ -399,3 +459,15 @@ func reset_pose() -> void:
_crouch_current = 0.0
_crouch_target = 0.0
_update_crouch(0.0)
## Get the look direction as a normalized Vector3 in world space.
## Uses the current yaw/pitch to compute a forward vector.
func get_look_direction() -> Vector3:
var yaw_rad: float = _yaw
var pitch_rad: float = _pitch
return Vector3(
cos(pitch_rad) * sin(yaw_rad),
-sin(pitch_rad),
cos(pitch_rad) * cos(yaw_rad)
).normalized()
@@ -0,0 +1 @@
uid://cpbvk1itjonm
@@ -0,0 +1 @@
uid://bvkhxr1bv6lth
@@ -0,0 +1,293 @@
## BombCarrier — Client-side bomb status UI and prompts.
##
## Attached to the local T player who carries the bomb.
## Provides visual feedback for:
## - Bomb status (planted/defusing/timer)
## - Direction indicator to planted bomb
## - "Hold E to defuse" prompt (CT, near bomb)
## - "Hold E to plant" prompt (T, in bombsite, LIVE phase)
##
## This script is a Node that should be added as a child of the local
## player's character node (FPSCharacterController) on the client side.
## It communicates with the server BombObjective via RPC or state sync.
extends Node
class_name BombCarrier
# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
## Emitted when the nearby interaction prompt changes.
signal prompt_changed(prompt_text: String, visible: bool)
## Emitted when bomb status UI should update.
signal bomb_status_updated(planted: bool, timer: float, defusing: bool, defuse_progress: float)
## Emitted when the bomb direction indicator should update.
signal bomb_direction_updated(bearing_degrees: float, distance: float)
# ---------------------------------------------------------------------------
# Exports
# ---------------------------------------------------------------------------
## The "Use/Interact" action name (matched in Input Map).
@export var interact_action: String = "interact"
## Interaction distance for planting (in Godot units).
@export var plant_distance: float = 2.0
## Interaction distance for defusing (in Godot units).
@export var defuse_distance: float = 3.0
# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------
## Whether this player has the bomb (T-side bomb carrier).
var has_bomb: bool = false
## Whether the bomb is currently planted.
var _bomb_planted: bool = false
## Position of the planted bomb (server-authoritative).
var _bomb_position: Vector3 = Vector3.ZERO
## Remaining bomb timer (received from server).
var _bomb_timer: float = 0.0
## Whether the local player is currently defusing.
var _is_defusing: bool = false
## Current defuse progress (0.0 to 1.0).
var _defuse_progress: float = 0.0
## Current prompt text to show.
var _current_prompt: String = ""
## Whether the prompt is currently visible.
var _prompt_visible: bool = false
## Whether we are inside a bombsite.
var _in_bombsite: bool = false
## Whether we are near the planted bomb.
var _near_planted_bomb: bool = false
## Whether the interact key is being held.
var _interact_held: bool = false
## Hold-down timer for plant/defuse (E key held long enough).
var _interact_hold_time: float = 0.0
## Time required to hold E to start action (for UX consistency).
var _interact_hold_duration: float = 0.0 # Immediate on press
## Reference to the local player's parent/character controller.
var _character_controller: Node = null
## Reference to the bomb objective on the server (for RPC calls).
## Set by the game manager after instantiation.
var bomb_objective_path: NodePath = NodePath()
# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------
func _ready() -> void:
_character_controller = get_parent()
if _character_controller == null:
push_warning("[BombCarrier] No parent node — prompts will not work")
# Try to set up the interact action if it doesn't exist
if not InputMap.has_action(interact_action):
var event := InputEventKey.new()
event.keycode = KEY_E
InputMap.add_action(interact_action)
InputMap.action_add_event(interact_action, event)
print("[BombCarrier] Initialised — has_bomb=%s" % has_bomb)
func _process(delta: float) -> void:
_update_interaction(delta)
_emit_status_updates()
# ---------------------------------------------------------------------------
# Public API — Server state sync
# ---------------------------------------------------------------------------
## Called by the game manager or network sync to update bomb state.
func sync_bomb_state(planted: bool, position: Vector3, timer: float,
defusing: bool, defuse_progress: float) -> void:
_bomb_planted = planted
_bomb_position = position
_bomb_timer = timer
_is_defusing = defusing
_defuse_progress = defuse_progress
## Set whether this player has the bomb (assigned by server).
func set_has_bomb(value: bool) -> void:
has_bomb = value
print("[BombCarrier] Has bomb: %s" % value)
## Set whether the local player is inside a bombsite Area3D.
## Called by the bombsite Area3D signal handlers on the client.
func set_in_bombsite(value: bool) -> void:
_in_bombsite = value
## Set whether the local player is near the planted bomb.
## Called by the bomb area proximity check.
func set_near_planted_bomb(value: bool) -> void:
_near_planted_bomb = value
# ---------------------------------------------------------------------------
# Public API — RPC calls to server
# ---------------------------------------------------------------------------
## Request to plant the bomb at the current position.
## Called when the T player presses E inside a bombsite with the bomb.
func request_plant() -> void:
if not has_bomb or not _in_bombsite:
return
if _bomb_planted:
return
# Send plant request to server
# In real implementation, this would be an RPC call:
# _send_plant_request.rpc_id(1, _character_controller.global_position)
_send_plant_request(_character_controller.global_position)
## Request to start defusing the bomb.
## Called when the CT player presses E near the planted bomb.
func request_defuse() -> void:
if not _near_planted_bomb or not _bomb_planted:
return
if _is_defusing:
return
# Send defuse request to server
# _send_defuse_request.rpc_id(1)
_send_defuse_request()
## Request to cancel defuse (when player moves or is interrupted).
func request_cancel_defuse() -> void:
if not _is_defusing:
return
_send_cancel_defuse()
# ---------------------------------------------------------------------------
# RPC stubs (to be replaced with actual RPC calls)
# ---------------------------------------------------------------------------
func _send_plant_request(position: Vector3) -> void:
# Placeholder: in real implementation, send RPC to server
print("[BombCarrier] Plant request at (%.1f, %.1f, %.1f)" %
[position.x, position.y, position.z])
func _send_defuse_request() -> void:
# Placeholder: in real implementation, send RPC to server
print("[BombCarrier] Defuse request")
func _send_cancel_defuse() -> void:
# Placeholder: in real implementation, send RPC to server
print("[BombCarrier] Cancel defuse request")
# ---------------------------------------------------------------------------
# Internal — Interaction logic
# ---------------------------------------------------------------------------
func _update_interaction(delta: float) -> void:
var interact_pressed: bool = Input.is_action_pressed(interact_action)
var prompt_text: String = ""
var show_prompt: bool = false
# Determine what prompt to show
if _bomb_planted and _near_planted_bomb and not _is_defusing:
# CT player near planted bomb — defuse prompt
var team: int = _get_local_player_team()
if team == TeamData.Team.COUNTER_TERRORIST:
prompt_text = "Hold E to defuse"
show_prompt = true
elif has_bomb and _in_bombsite and not _bomb_planted:
# T player with bomb inside bombsite — plant prompt
if _is_round_live():
prompt_text = "Hold E to plant"
show_prompt = true
elif _bomb_planted and _is_defusing:
# Currently defusing — show progress
var progress_pct: int = int(_defuse_progress * 100.0)
prompt_text = "Defusing... %d%%" % progress_pct
show_prompt = true
# Handle interact hold
if interact_pressed and show_prompt:
if not _interact_held:
_interact_held = true
_interact_hold_time = 0.0
_interact_hold_time += delta
# Check if we should trigger the action
if _interact_hold_time >= _interact_hold_duration:
_interact_hold_time = 0.0
if prompt_text.contains("defuse"):
request_defuse()
elif prompt_text.contains("plant"):
request_plant()
else:
# Cancel any pending interaction
if _interact_held:
_interact_held = false
_interact_hold_time = 0.0
# If we were defusing and released, cancel
if _is_defusing:
request_cancel_defuse()
# Update prompt if changed
if prompt_text != _current_prompt or show_prompt != _prompt_visible:
_current_prompt = prompt_text
_prompt_visible = show_prompt
prompt_changed.emit(prompt_text, show_prompt)
func _emit_status_updates() -> void:
# Emit bomb status updates (rate-limited is handled by engine)
bomb_status_updated.emit(_bomb_planted, _bomb_timer, _is_defusing, _defuse_progress)
# Emit direction indicator if bomb is planted
if _bomb_planted and _character_controller != null:
var player_pos: Vector3 = _character_controller.global_position
var direction: Vector3 = _bomb_position - player_pos
var distance: float = direction.length()
if distance > 0.01:
var bearing: float = atan2(direction.x, -direction.z) # Relative to forward (Z)
bomb_direction_updated.emit(rad_to_deg(bearing), distance)
else:
bomb_direction_updated.emit(0.0, 0.0)
# ---------------------------------------------------------------------------
# Internal — Helpers
# ---------------------------------------------------------------------------
## Get the local player's team (from TeamManager singleton if available).
func _get_local_player_team() -> int:
var team_manager: TeamManager = get_node_or_null("/root/TeamManager")
if team_manager:
var peer_id: int = multiplayer.get_unique_id() if multiplayer.has_method(&\"get_unique_id\") else -1
if peer_id >= 0:
return team_manager.get_player_team(peer_id)
return -1
## Check if the current round is in LIVE phase.
func _is_round_live() -> bool:
var round_manager: RoundManager = get_node_or_null("/root/RoundManager")
if round_manager:
return round_manager.get_phase() == RoundManager.RoundPhase.LIVE
return false
## Get the multiplayer API (stub for compatibility).
func get_multiplayer() -> MultiplayerAPI:
if multiplayer and multiplayer.has_method(&\"get_unique_id\"):
return multiplayer
return null
@@ -0,0 +1 @@
uid://dtvf72mhgdat7
+445
View File
@@ -0,0 +1,445 @@
## BuyMenu — Client-side buy menu UI for weapon and equipment purchases.
##
## Displays a grid of purchasable weapons with name, cost, and key stats.
## Highlights weapons the player can afford, shows current money at the top,
## and supports both keyboard (numeric keys) and mouse-click selection.
##
## Opens during the PREP (buy) phase when the player is inside a buy zone.
## Automatically closes when the buy phase ends or the player leaves the zone.
##
## Usage (add as child of the player's UI root or HUD):
## var buy_menu = preload("res://client/characters/weapon/buy_menu.gd").new()
## add_child(buy_menu)
##
## # Show it
## buy_menu.open()
## buy_menu.populate(affordable_weapons_list, current_money)
##
## Signals:
## buy_menu_opened()
## buy_menu_closed()
## purchase_completed(weapon_id)
## purchase_failed(reason: String)
##
class_name BuyMenu
extends Control
# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
## Emitted when the buy menu opens.
signal buy_menu_opened()
## Emitted when the buy menu closes.
signal buy_menu_closed()
## Emitted when a purchase is confirmed by the server.
signal purchase_completed(weapon_id: String)
## Emitted when a purchase is denied by the server.
signal purchase_failed(reason: String)
# ---------------------------------------------------------------------------
# Exports — Layout
# ---------------------------------------------------------------------------
## Background panel colour.
@export var panel_color: Color = Color(0.1, 0.1, 0.15, 0.92)
## Accent colour for headers and affordable items.
@export var accent_color: Color = Color(0.2, 0.7, 0.3, 1.0)
## Colour for items the player cannot afford.
@export var unaffordable_color: Color = Color(0.5, 0.5, 0.5, 1.0)
## Text colour.
@export var text_color: Color = Color(0.95, 0.95, 0.95, 1.0)
## Key binding for opening the buy menu (default: B).
@export var open_key: Key = KEY_B
# ---------------------------------------------------------------------------
# Internal state
# ---------------------------------------------------------------------------
## Reference to the BuyMenuHandler on the server (for sending RPCs).
## Injected by the UI manager or found at runtime.
var buy_menu_handler_path: NodePath = NodePath()
var _is_open: bool = false
var _weapon_buttons: Array[Button] = []
var _populated: bool = false
# ---------------------------------------------------------------------------
# UI Nodes (built in _build_ui)
# ---------------------------------------------------------------------------
var _panel: Panel = null
var _money_label: Label = null
var _title_label: Label = null
var _weapon_container: Container = null
var _close_button: Button = null
var _status_label: Label = null
# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------
func _ready() -> void:
_build_ui()
visible = false
# Listen for server purchase confirmations / denials
# The BuyMenuHandler sends RPCs that land on this node.
# We connect to them via the multiplayer API — they're named to match
# the RPC names defined in BuyMenuHandler.
# Connect input for toggle key
set_process_input(true)
func _input(event: InputEvent) -> void:
if event.is_action_pressed("buy_menu") or (
event is InputEventKey and
event.keycode == open_key and
event.pressed and
not event.echo
):
if _is_open:
close()
else:
open()
accept_event()
# ---------------------------------------------------------------------------
# UI Construction
# ---------------------------------------------------------------------------
func _build_ui() -> void:
# Main panel — centred, sized to fit content
_panel = Panel.new()
_panel.anchor_left = 0.15
_panel.anchor_right = 0.85
_panel.anchor_top = 0.1
_panel.anchor_bottom = 0.9
_panel.mouse_filter = Control.MOUSE_FILTER_PASS
add_child(_panel)
# Panel background style
var style := StyleBoxFlat.new()
style.bg_color = panel_color
style.corner_radius_top_left = 8
style.corner_radius_top_right = 8
style.corner_radius_bottom_left = 8
style.corner_radius_bottom_right = 8
_panel.add_theme_stylebox_override("panel", style)
# Vertical layout inside the panel
var vbox := VBoxContainer.new()
vbox.anchor_left = 0.0
vbox.anchor_right = 1.0
vbox.anchor_top = 0.0
vbox.anchor_bottom = 1.0
vbox.add_theme_constant_override("separation", 12)
_panel.add_child(vbox)
# ── Title ──
_title_label = Label.new()
_title_label.text = "BUY MENU"
_title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_title_label.add_theme_font_size_override("font_size", 24)
_title_label.add_theme_color_override("font_color", accent_color)
vbox.add_child(_title_label)
# ── Money display ──
_money_label = Label.new()
_money_label.text = "$0"
_money_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_money_label.add_theme_font_size_override("font_size", 20)
_money_label.add_theme_color_override("font_color", text_color)
vbox.add_child(_money_label)
# ── Weapon grid ──
_weapon_container = GridContainer.new()
_weapon_container.columns = 2
_weapon_container.add_theme_constant_override("h_separation", 16)
_weapon_container.add_theme_constant_override("v_separation", 8)
_weapon_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_weapon_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
vbox.add_child(_weapon_container)
# ── Status / feedback line ──
_status_label = Label.new()
_status_label.text = ""
_status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_status_label.add_theme_color_override("font_color", Color(1.0, 0.8, 0.2, 1.0))
_status_label.add_theme_font_size_override("font_size", 14)
vbox.add_child(_status_label)
# ── Close hint ──
var close_hint := Label.new()
close_hint.text = "Press B or ESC to close | Click or press 1-4 to buy"
close_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
close_hint.add_theme_font_size_override("font_size", 11)
close_hint.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6, 1.0))
vbox.add_child(close_hint)
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
## Open and populate the buy menu.
func open() -> void:
if _is_open:
return
_is_open = true
visible = true
_populated = false
_clear_weapons()
_status_label.text = ""
# Request affordable weapons from the server via an RPC call
# (The server pushes the data back via RPC response.)
_request_menu_data()
buy_menu_opened.emit()
## Close the buy menu.
func close() -> void:
if not _is_open:
return
_is_open = false
visible = false
buy_menu_closed.emit()
## Returns true if the menu is currently open.
func is_open() -> bool:
return _is_open
## Update the displayed money amount.
func set_money(amount: int) -> void:
_money_label.text = "$%d" % amount
## Populate the weapon grid with purchasable items.
## `weapons` is an Array[Dictionary] with keys:
## weapon_id, display_name, cost, damage, fire_rate, mag_size
## `current_money` is the player's balance.
func populate(weapons: Array[Dictionary], current_money: int) -> void:
_clear_weapons()
_populated = false
set_money(current_money)
var index: int = 1
for w in weapons:
var affordable: bool = (current_money >= w.get("cost", 0))
_add_weapon_button(index, w, affordable)
index += 1
_populated = true
## Show a temporary status message in the menu.
func show_status(message: String, duration: float = 3.0) -> void:
_status_label.text = message
if duration > 0:
get_tree().create_timer(duration).timeout.connect(func():
if is_instance_valid(_status_label) and _status_label.text == message:
_status_label.text = ""
)
# ---------------------------------------------------------------------------
# Internal: weapon button creation
# ---------------------------------------------------------------------------
func _add_weapon_button(index: int, data: Dictionary, affordable: bool) -> void:
var weapon_id: String = data.get("weapon_id", "")
var display_name: String = data.get("display_name", weapon_id)
var cost: int = data.get("cost", 0)
var damage: float = data.get("damage", 0.0)
var fire_rate: float = data.get("fire_rate", 0.0)
var mag_size: int = data.get("mag_size", 0)
# Build the weapon card as a styled Button
var btn := Button.new()
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
btn.size_flags_vertical = Control.SIZE_SHRINK_CENTER
btn.custom_minimum_size = Vector2(240, 80)
# Text: name + cost on first line, stats on second
var stats_line: String = "%.0f dmg | %.1f rps | %d rds" % [damage, fire_rate, mag_size]
btn.text = "[%d] %s — $%d\n%s" % [index, display_name, cost, stats_line]
btn.flat = false
btn.tooltip_text = "%s — $%d\nDamage: %.0f\nFire Rate: %.1f Hz\nMagazine: %d rounds" % [
display_name, cost, damage, fire_rate, mag_size
]
# Style
var fg_color: Color = accent_color if affordable else unaffordable_color
btn.add_theme_color_override("font_color", fg_color)
btn.add_theme_color_override("font_hover_color", text_color)
btn.add_theme_color_override("font_pressed_color", Color.WHITE)
btn.add_theme_font_size_override("font_size", 14)
if not affordable:
btn.disabled = true
btn.pressed.connect(_on_weapon_button_pressed.bind(weapon_id, cost, btn))
_weapon_container.add_child(btn)
_weapon_buttons.append(btn)
func _clear_weapons() -> void:
for child in _weapon_buttons:
if is_instance_valid(child):
child.queue_free()
_weapon_buttons.clear()
_populated = false
# ---------------------------------------------------------------------------
# Internal: purchase flow
# ---------------------------------------------------------------------------
func _on_weapon_button_pressed(weapon_id: String, cost: int, _button: Button) -> void:
_request_purchase(weapon_id)
## Send a purchase request RPC to the server.
func _request_purchase(weapon_id: String) -> void:
_status_label.text = "Purchasing %s..." % weapon_id
# Find the BuyMenuHandler and call its buy_request RPC.
# The handler is typically a child of GameServer.
if multiplayer.is_server():
# Listen server / standalone — call directly
_call_buy_handler(weapon_id)
else:
# Dedicated client — send RPC
_call_buy_handler_rpc(weapon_id)
func _call_buy_handler(_weapon_id: String) -> void:
# On a listen server, the handler is local
var handler: BuyMenuHandler = _find_buy_handler()
if handler:
handler.buy_request.rpc(_weapon_id)
func _call_buy_handler_rpc(weapon_id: String) -> void:
# Find the handler path and send the RPC
var handler_path: NodePath = buy_menu_handler_path
if handler_path.is_empty():
# Try to find it dynamically
handler_path = "/root/GameServer/BuyMenuHandler"
var node: Node = get_node_or_null(handler_path) if not handler_path.is_empty() else null
if node and node.has_method(&"buy_request"):
node.buy_request.rpc(weapon_id)
else:
# Fallback: call directly on the handler node path
rpc_id(1, "_remote_buy_request", weapon_id)
## Fallback: send buy request to server's handler.
@rpc("any_peer", "call_remote", "reliable")
func _remote_buy_request(weapon_id: String) -> void:
if not multiplayer.is_server():
return
var handler: BuyMenuHandler = _find_buy_handler()
if handler:
var sender: int = multiplayer.get_remote_sender_id()
handler.call_deferred("_process_buy_request", sender, weapon_id)
func _find_buy_handler() -> BuyMenuHandler:
# Try common locations
var game_server: Node = get_node_or_null("/root/GameServer")
if game_server:
for child in game_server.get_children():
if child is BuyMenuHandler:
return child
return null
# ---------------------------------------------------------------------------
# RPC handlers — called by BuyMenuHandler (server → client)
# ---------------------------------------------------------------------------
## Called by the server when a purchase succeeds.
@rpc("authority", "call_remote", "reliable")
func _on_buy_confirmed(weapon_id: String, cost: int) -> void:
show_status("Purchased %s for $%d" % [weapon_id, cost], 2.0)
purchase_completed.emit(weapon_id)
# Close after a brief delay so the player sees the confirmation
get_tree().create_timer(0.6).timeout.connect(close)
## Called by the server when a purchase is denied.
@rpc("authority", "call_remote", "reliable")
func _on_buy_denied(reason: String) -> void:
show_status("Purchase failed: %s" % reason, 3.0)
purchase_failed.emit(reason)
# ---------------------------------------------------------------------------
# Request affordable weapon data from the server
# ---------------------------------------------------------------------------
func _request_menu_data() -> void:
# For now, the client populates from the weapon definitions directly,
# filtering by what the server says is affordable.
# In a more advanced version, the server sends the curated list.
#
# Since we don't know the player's money on the client, we send an
# RPC to the server to get the affordable list. But for simplicity,
# we populate all weapons and let the server deny if unaffordable.
#
# The server pushes the affordable list back via get_affordable_weapons().
# For now, just show all weapons — the server will reject unaffordable ones.
# Try to fetch from server
if multiplayer.is_server():
_populate_from_local(player_id())
else:
_request_affordable_weapons.rpc_id(1)
## Get the player's peer ID.
func player_id() -> int:
return multiplayer.get_unique_id()
## Client → Server: request affordable weapon list.
@rpc("any_peer", "call_remote", "reliable")
func _request_affordable_weapons() -> void:
if not multiplayer.is_server():
return
var sender: int = multiplayer.get_remote_sender_id()
var handler: BuyMenuHandler = _find_buy_handler()
if handler:
var weapons: Array[Dictionary] = handler.get_affordable_weapons(sender)
var money: int = handler.get_player_money(sender)
_send_menu_data.rpc_id(sender, weapons, money)
## Server → Client: send affordable weapon list and money.
@rpc("authority", "call_remote", "reliable")
func _send_menu_data(weapons: Array[Dictionary], current_money: int) -> void:
populate(weapons, current_money)
func _populate_from_local(pid: int) -> void:
var handler: BuyMenuHandler = _find_buy_handler()
if handler:
var weapons: Array[Dictionary] = handler.get_affordable_weapons(pid)
var money: int = handler.get_player_money(pid)
populate(weapons, money)
# ---------------------------------------------------------------------------
# Input helpers
# ---------------------------------------------------------------------------
func _unhandled_key_input(event: InputEventKey) -> void:
if not _is_open or not _populated:
return
# Numeric keys 1-4 to buy
var key: Key = event.keycode
var index: int = -1
match key:
KEY_1, KEY_KP_1: index = 0
KEY_2, KEY_KP_2: index = 1
KEY_3, KEY_KP_3: index = 2
KEY_4, KEY_KP_4: index = 3
if index >= 0 and index < _weapon_buttons.size():
var btn: Button = _weapon_buttons[index]
if btn and not btn.disabled and event.pressed:
btn.emit_signal("pressed")
accept_event()
# ESC to close
if event.keycode == KEY_ESCAPE and event.pressed:
close()
accept_event()
+1
View File
@@ -0,0 +1 @@
uid://cdsyn134gsca8
+348
View File
@@ -0,0 +1,348 @@
## WeaponManager — hitscan weapon system for tactical FPS.
##
## Manages a single hitscan weapon: fire rate limiting, ammo, reload.
## Communicates with SimulationServer for server-authoritative hit detection.
##
## Architecture:
## - Client-side: fire rate limiting, muzzle VFX, hit marker feedback
## - Server-side (via SimulationServer): hit detection, damage, lag compensation
## - Both sides: ammo tracking (server-authoritative, client-predicted)
##
## Usage (attach as child of FPSCharacterController):
##
## FPSCharacterController
## ├── WeaponManager — fire rate, ammo, VFX
## │ ├── MuzzleFlash — instantiated VFX
## │ └── HitMarker — instantiated VFX
## └── FpsCamera
##
class_name WeaponManager
extends Node
# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
## Emitted when the weapon fires (for visual feedback).
signal weapon_fired(origin: Vector3, direction: Vector3)
## Emitted when this weapon hits an entity.
signal hit_marked(hit_pos: Vector3, damage: float, killed: bool)
## Emitted when ammo changes.
signal ammo_changed(current: int, max: int)
## Emitted when player is killed.
signal player_killed()
## Emitted when the player switches weapons (for animation/UI).
signal weapon_switched(index: int, weapon_id: String)
## Emitted when the current weapon fires (for animation/visual sync).
## Connected by animation players or VFX systems on the weapon model.
signal fire_animation_triggered()
# ---------------------------------------------------------------------------
# Exports — Assault Rifle Config
# ---------------------------------------------------------------------------
## Maximum ammo capacity.
@export var max_ammo: int = 30
## Starting ammo (including one magazine).
@export var start_ammo: int = 90
## Time between shots (seconds). 10 Hz = 0.1s between shots.
@export var fire_rate: float = 0.1
## Reload time in seconds.
@export var reload_time: float = 2.0
## Spread increases while firing (degrees).
@export var spread_per_shot: float = 0.5
## Max spread when firing continuously (degrees).
@export var max_spread: float = 4.0
## Spread recovery per second (degrees/s).
@export var spread_recovery: float = 8.0
# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------
var current_ammo: int = 30
var reserve_ammo: int = 90
var is_reloading: bool = false
var _last_shot_time: float = 0.0
var _current_spread: float = 0.0
# ---------------------------------------------------------------------------
# Multi-weapon inventory state
# ---------------------------------------------------------------------------
## Array of weapon IDs the player carries (the inventory).
var _weapon_inventory: Array[String] = []
## Index into _weapon_inventory for the currently equipped weapon.
var _current_weapon_index: int = -1
## Per-weapon persistent state saved when switching away, restored on switch-back.
## weapon_id -> { ammo, reserve, is_reloading, last_shot_time, current_spread }
var _weapon_states: Dictionary = {}
## Reference to the parent FPSCharacterController (set in _ready).
var _controller: FPSCharacterController = null
## Reference to SimulationServer (if available).
var _server: Object = null
# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------
func _ready() -> void:
_controller = get_parent() as FPSCharacterController
if _controller == null:
push_warning("WeaponManager: Parent is not FPSCharacterController. Disabled.")
set_process(false)
return
# Find SimulationServer singleton
if Engine.has_singleton("SimulationServer"):
_server = Engine.get_singleton("SimulationServer")
elif Engine.get_main_loop() and Engine.get_main_loop().root:
# Try finding it in the scene tree
var game_server = Engine.get_main_loop().root.find_child("GameServer", true, false)
if game_server and game_server.has_method(&"get_simulation_server"):
_server = game_server.get_simulation_server()
# Initialize ammo
current_ammo = max_ammo
reserve_ammo = start_ammo
# Signal that ammo changed
ammo_changed.emit(current_ammo, max_ammo)
# ---------------------------------------------------------------------------
# Main loop — called from FPSCharacterController._physics_process per tick
# ---------------------------------------------------------------------------
## Try to fire the weapon. Returns true if a shot was fired.
## Called by the controller when shoot input is detected.
func try_fire(time: float) -> bool:
# Check fire rate
if time - _last_shot_time < fire_rate:
return false
# Check ammo
if current_ammo <= 0:
# Auto-reload on empty — or play empty-click sound
if not is_reloading:
start_reload()
return false
# Check reloading
if is_reloading:
return false
# Deduct ammo
current_ammo -= 1
_last_shot_time = time
# Increase spread
_current_spread = min(_current_spread + spread_per_shot, max_spread)
# Persist state back to per-weapon dict
_save_current_weapon_state()
# Emit fire signal (for visual/hit marker)
# Direction comes from the controller's look direction
if _controller:
var origin: Vector3 = _controller.global_position
origin.y += _controller.eye_height_stand
var direction: Vector3 = _controller.get_look_direction()
weapon_fired.emit(origin, direction)
ammo_changed.emit(current_ammo, max_ammo)
fire_animation_triggered.emit()
return true
## Called every frame to recover spread and handle spread animation.
func _process(delta: float) -> void:
# Recover spread
if _current_spread > 0.0:
_current_spread = max(0.0, _current_spread - spread_recovery * delta)
## Start reloading.
func start_reload() -> void:
if is_reloading or current_ammo >= max_ammo or reserve_ammo <= 0:
return
is_reloading = true
await get_tree().create_timer(reload_time).timeout
# Reload logic
var needed: int = max_ammo - current_ammo
var from_reserve: int = min(needed, reserve_ammo)
current_ammo += from_reserve
reserve_ammo -= from_reserve
is_reloading = false
ammo_changed.emit(current_ammo, max_ammo)
_save_current_weapon_state()
## Called when a hit is confirmed by the server.
func on_hit_confirmed(hit_pos: Vector3, damage: float, killed: bool) -> void:
hit_marked.emit(hit_pos, damage, killed)
if killed:
player_killed.emit()
## Get current spread (degrees).
func get_current_spread() -> float:
return _current_spread
## Get the current weapon fire direction including spread cone.
func get_spread_direction(base_direction: Vector3) -> Vector3:
if _current_spread <= 0.001:
return base_direction
var spread_rad: float = deg_to_rad(_current_spread)
var theta: float = randf() * TAU
var phi: float = randf() * spread_rad
# Create a random perpendicular vector
var up := Vector3.UP
if abs(base_direction.dot(up)) > 0.99:
up = Vector3.RIGHT
var right := base_direction.cross(up).normalized()
up = right.cross(base_direction).normalized()
var offset := right * sin(theta) * sin(phi) + up * cos(theta) * sin(phi)
return (base_direction + offset).normalized()
## Reset weapon state (for respawn).
func reset() -> void:
# Re-initialise all weapon states in inventory
for wid in _weapon_inventory:
_initialize_weapon_state(wid)
# Apply current weapon (or use defaults if no inventory)
if _current_weapon_index >= 0 and _current_weapon_index < _weapon_inventory.size():
_apply_current_weapon()
else:
current_ammo = max_ammo
reserve_ammo = start_ammo
is_reloading = false
_current_spread = 0.0
_last_shot_time = 0.0
ammo_changed.emit(current_ammo, max_ammo)
# ---------------------------------------------------------------------------
# Weapon inventory management
# ---------------------------------------------------------------------------
## Add a weapon to the player's inventory.
## If no weapon is currently equipped, this one is automatically selected.
func add_weapon(weapon_id: String) -> void:
if weapon_id in _weapon_inventory:
return
_weapon_inventory.append(weapon_id)
_initialize_weapon_state(weapon_id)
if _current_weapon_index < 0:
_current_weapon_index = _weapon_inventory.size() - 1
_apply_current_weapon()
## Switch to a weapon by its inventory index.
## Returns true if the switch was performed.
func switch_weapon(index: int) -> bool:
if index < 0 or index >= _weapon_inventory.size():
return false
if index == _current_weapon_index:
return false
_save_current_weapon_state()
_current_weapon_index = index
_apply_current_weapon()
weapon_switched.emit(index, _weapon_inventory[index])
return true
## Return the WeaponData for the currently equipped weapon, or null.
func current_weapon_data() -> WeaponData:
var wid := get_current_weapon_id()
if wid.is_empty():
return null
return WeaponDefinitions.get_weapon(wid)
## Return the current weapon's ID string, or empty string.
func get_current_weapon_id() -> String:
if _current_weapon_index >= 0 and _current_weapon_index < _weapon_inventory.size():
return _weapon_inventory[_current_weapon_index]
return ""
## Return the number of weapons in the inventory.
func get_weapon_count() -> int:
return _weapon_inventory.size()
## Return the list of weapon IDs in the inventory.
func get_inventory() -> Array[String]:
return _weapon_inventory.duplicate()
# ---------------------------------------------------------------------------
# Internal: per-weapon state management
# ---------------------------------------------------------------------------
## Initialise state dict for a weapon with default values.
func _initialize_weapon_state(weapon_id: String) -> void:
var data := WeaponDefinitions.get_weapon(weapon_id)
if data == null:
return
_weapon_states[weapon_id] = {
ammo = data.mag_size,
reserve = data.mag_size * 3,
is_reloading = false,
last_shot_time = 0.0,
current_spread = 0.0,
}
## Save the current weapon's runtime state back into _weapon_states.
func _save_current_weapon_state() -> void:
var wid := get_current_weapon_id()
if wid.is_empty() or not (wid in _weapon_states):
return
_weapon_states[wid] = {
ammo = current_ammo,
reserve = reserve_ammo,
is_reloading = is_reloading,
last_shot_time = _last_shot_time,
current_spread = _current_spread,
}
## Load the weapon at _current_weapon_index and apply its data + state
## to the live export properties and state variables.
func _apply_current_weapon() -> void:
var wid := get_current_weapon_id()
if wid.is_empty():
return
var data := WeaponDefinitions.get_weapon(wid)
if data == null:
return
# Set export defaults from weapon data
max_ammo = data.mag_size
start_ammo = data.mag_size * 4 # 1 in mag + 3 reserve = 4x
fire_rate = 1.0 / max(data.fire_rate, 0.001)
reload_time = data.reload_time
spread_per_shot = data.spread_degrees * 0.1
max_spread = data.spread_degrees * 3.0
spread_recovery = data.spread_degrees * 8.0
# Restore per-weapon state
var st: Dictionary = _weapon_states.get(wid, {})
if st.is_empty():
_initialize_weapon_state(wid)
st = _weapon_states.get(wid, {})
current_ammo = st.get("ammo", data.mag_size)
reserve_ammo = st.get("reserve", data.mag_size * 3)
is_reloading = st.get("is_reloading", false)
_last_shot_time = st.get("last_shot_time", 0.0)
_current_spread = st.get("current_spread", 0.0)
@@ -0,0 +1 @@
uid://dywlbha8xivx4
+1
View File
@@ -0,0 +1 @@
uid://q8nutj781fyf
+1
View File
@@ -0,0 +1 @@
uid://d0mbp6gs2mwlr
@@ -0,0 +1 @@
uid://c0b8h1hq57lsu
+1
View File
@@ -0,0 +1 @@
uid://pauclwpn265a
@@ -0,0 +1 @@
uid://byjqslju7qnt6
@@ -0,0 +1 @@
uid://jih7knhnl55a
@@ -0,0 +1 @@
uid://dc8x14rs2jyu8
@@ -0,0 +1 @@
uid://b8jj7n3bi6ljh
+1
View File
@@ -0,0 +1 @@
uid://dbtpbhja3nk60
+1
View File
@@ -0,0 +1 @@
uid://cwvi2ic1k6ubn
+1
View File
@@ -0,0 +1 @@
uid://olpdh1gx2gca
+1
View File
@@ -0,0 +1 @@
uid://bqau1kivb87fk
+1
View File
@@ -0,0 +1 @@
uid://d0go24t1lsx2j
+1
View File
@@ -0,0 +1 @@
uid://bas55o1773n21
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

@@ -0,0 +1,127 @@
[gd_scene load_steps=11 format=4 uid="uid://br6g3dtbytwpu"]
[ext_resource type="Script" uid="uid://deaf6cmuofa0" path="res://client/template/scripts/Weapon_State_Machine/PickUp Weapons/Weapon_Pick_Up.gd" id="1_mpf05"]
[ext_resource type="Script" uid="uid://bw4j1fbww4k8s" path="res://client/template/scripts/Weapon_State_Machine/WeaponSlot.gd" id="2_vl0jl"]
[ext_resource type="Resource" uid="uid://bwrs8ensewkgc" path="res://client/template/scripts/Weapon_State_Machine/Weapon_Resources/blasterL.tres" id="3_bkkd8"]
[sub_resource type="Resource" id="Resource_3vvaw"]
resource_local_to_scene = true
script = ExtResource("2_vl0jl")
weapon = ExtResource("3_bkkd8")
current_ammo = 30
reserve_ammo = 0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_761wa"]
resource_name = "metal"
vertex_color_use_as_albedo = true
albedo_color = Color(0.454902, 0.431373, 0.403922, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_757o3"]
resource_name = "darkMetal"
vertex_color_use_as_albedo = true
albedo_color = Color(0.454902, 0.431373, 0.403922, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0y2bu"]
resource_name = "yellow"
vertex_color_use_as_albedo = true
albedo_color = Color(0.533333, 0.984314, 0.976471, 1)
metallic = 1.0
emission_enabled = true
emission = Color(0.282353, 0.568627, 0.541176, 1)
emission_energy_multiplier = 9.4
[sub_resource type="ArrayMesh" id="ArrayMesh_bjg0s"]
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYAAwABAAgACAAJAAMADAAKAAsACwANAAwADAAOAA8ADwAKAAwAEgAQABEAEQATABIAEgAJAAgACAAQABIAEQAAAAIAAgATABEACwAEAAYABgANAAsAFQAUABEAEQAWABUAEQAQABYAEAAIABYAAAARABQAFAAXAAAAFwABAAAAFwAYAAEAGAAIAAEAGAAWAAgAGgAZAAYABgAbABoABgAHABsABwAcABsABwAOABwADgAMABwADQAGABkAGQAdAA0AHQAMAA0AHQAcAAwAGQAaABQAFAAVABkAFgAYABwAHAAdABYAFQAWAB4AHgAZABUAHgAfABkAHwAgABkAIQAeABYAFgAdACEAHQAgACEAHQAZACAAGwAXABQAFAAaABsAHAAYABcAFwAbABwABwAFAA8ADwAOAAcA"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 18,
"index_data": PackedByteArray("EgATAAIAAgADABIAAwAJABIABQAEAAsACwAKAAUACgAPAAUA"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 138,
"index_data": PackedByteArray("IgAhACAAIAAjACIAIwAkACIAIwAlACQAJQAmACQAJgAnACQAJgAoACcAJgApACgAKQAqACgAKQArACoAKwAsACoALgAtACcAJwAoAC4AJQAvADAAMAAmACUAMQAfAB4AHgAyADEAMgAvADEAMgAzAC8AMwAwAC8AMwAtADAALQA0ADAALQAuADQALgA1ADQALgA2ADUANgA3ADUAJwAtADMAMwAkACcAIgAyAB4AHgAhACIANQA3ACwALAArADUANAA1ACsAKwApADQAIwAxAC8ALwAlACMAIAAfADEAMQAjACAAMAA0ACkAKQAmADAAJAAzADIAMgAiACQANgAuACgAKAAqADYANwA2ACoAKgAsADcA"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}]
blend_shape_mode = 0
[sub_resource type="ArrayMesh" id="ArrayMesh_wf2rx"]
resource_name = "clipA_Mesh clipA"
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYACgAIAAkACQALAAoADgAMAA0ADQAPAA4AEgAQABEAEQATABIAFgAUABUAFQAXABYAGgAYABkAGQAbABoAHgAcAB0AHQAfAB4AIgAgACEAIQAjACIAJgAkACUAJQAnACYAJQAoACcAKAApACcAKgAlACQAJAArACoAKwAsACoAKwAtACwALQApACwALQAnACkAMAAuAC8ALwAxADAALwAyADEAMgAzADEAMgA0ADMANAA1ADMANgAvAC4ALgA3ADYANwA1ADYANwAzADUAOgA4ADkAOQA7ADoAPgA8AD0APQA/AD4AQgBAAEEAQQBDAEIAQQBEAEMARABFAEMARgBBAEAAQABHAEYARwBFAEYARwBDAEUASgBIAEkASQBLAEoATgBMAE0ATQBPAE4AUgBQAFEAUQBTAFIA"),
"material": SubResource("StandardMaterial3D_761wa"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAA/7+pqv//AAD/v6mq//8AAP+/qar//wAA/7+pqv//AAD/v6mq//8AAP+/qar//wAA/7+pqv//AAD/v/9///////+//3///////7//f///////v/9///////+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//f/9/////v/9//3////+//3//f////7//f/9/////v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//3//f////7//f/9/////v/9//3////+//3//f////7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//7////9//////////3/+////////f/7///////9//v///////3/+////////f/7/AID///9//v8AgP///3///wCA////f/////////9///////9//3////+//3//f////7//f/9/////v/9//3////+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//f///////v/9///////+//3///////7//f////v//v/9////+//+//3///////7//f////v//v/9///////+//3+qKv///7//f6oq////v/9/qir///+//3+qKv///7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9///////+//3///////7//f///////v/9///////+/AAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP8//78AAACA/z//vwAAAIAlUSXRAAAAgCVRJdEAAACAJdHZrgAAAID/v/+/AAAAgCXR2a4AAACA/7//vwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP8//78AAACA2S7ZrgAAAID/P/+/AAAAgNku2a4AAACADBsMmwAAAIDkDuOOAAAAgAwbDJsAAACA5A7jjgAAAID/f///AAAAgP9///8AAACADJvy5AAAAIAMm/LkAAAAgAyb8uQAAACADJvy5AAAAIDZriXRAAAAgNmuJdEAAACA8uQMmwAAAIAl0dmuAAAAgPLkDJsAAACAJdHZrgAAAIAa8eOOAAAAgPLkDJsAAACAGvHjjgAAAIDy5AybAAAAgNmuJdEAAACA2a4l0QAAAID/v/+/AAAAgP+//78AAACA2S7ZrgAAAIAMGwybAAAAgNku2a4AAACADBsMmwAAAIAlUSXRAAAAgCVRJdEAAACA8mTy5AAAAIDyZPLkAAAAgPJk8uQAAACA8mTy5AAAAID/f///AAAAgP9///8AAACA")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 18,
"index_data": PackedByteArray("VgBUAFUAVQBXAFYAVwBYAFYAWwBZAFoAWgBcAFsAXABdAFsA"),
"material": SubResource("StandardMaterial3D_757o3"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3+qKgAAAID/f6oqAAAAgP9/qioAAACA/3+qKgAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACAAAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//v////3/+////////f/////////9//v///////3//////////f///////////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP8//78AAACA/z//vwAAAIAlUSXRAAAAgCVRJdEAAACAJdHZrgAAAID/v/+/AAAAgCXR2a4AAACA/7//vwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP8//78AAACA2S7ZrgAAAID/P/+/AAAAgNku2a4AAACADBsMmwAAAIDkDuOOAAAAgAwbDJsAAACA5A7jjgAAAID/f///AAAAgP9///8AAACADJvy5AAAAIAMm/LkAAAAgAyb8uQAAACADJvy5AAAAIDZriXRAAAAgNmuJdEAAACA8uQMmwAAAIAl0dmuAAAAgPLkDJsAAACAJdHZrgAAAIAa8eOOAAAAgPLkDJsAAACAGvHjjgAAAIDy5AybAAAAgNmuJdEAAACA2a4l0QAAAID/v/+/AAAAgP+//78AAACA2S7ZrgAAAIAMGwybAAAAgNku2a4AAACADBsMmwAAAIAlUSXRAAAAgCVRJdEAAACA8mTy5AAAAIDyZPLkAAAAgPJk8uQAAACA8mTy5AAAAID/f///AAAAgP9///8AAACA")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 138,
"index_data": PackedByteArray("YABeAF8AXwBhAGAAYQBiAGAAYQBjAGIAYwBkAGIAZABlAGIAZABmAGUAZABnAGYAZwBoAGYAZwBpAGgAaQBqAGgAbQBrAGwAbABuAG0AcQBvAHAAcAByAHEAdQBzAHQAdAB2AHUAdgB3AHUAdgB4AHcAeAB5AHcAeAB6AHkAegB7AHkAegB8AHsAfAB9AHsAfAB+AH0AfgB/AH0AggCAAIEAgQCDAIIAhgCEAIUAhQCHAIYAigCIAIkAiQCLAIoAjgCMAI0AjQCPAI4AkgCQAJEAkQCTAJIAlgCUAJUAlQCXAJYAmgCYAJkAmQCbAJoAngCcAJ0AnQCfAJ4AogCgAKEAoQCjAKIApgCkAKUApQCnAKYA"),
"material": SubResource("StandardMaterial3D_0y2bu"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3+qKgAAAID/f6oqAAAAgP9/qioAAACA/3+qKgAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID/////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/8//7//f/+//z//v/9//78lUSXR/3//vyVRJdH/f/+/JdHZrv//////v/+//////yXR2a7//////7//v///////f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/8//7//f/+/2S7Zrv9//7//P/+//3//v9ku2a7/f/+/DBsMm/9//7/kDuOO/3//vwwbDJv/f/+/5A7jjv9//7//f/////////9/////////DJvy5P////8Mm/Lk/////wyb8uT/////DJvy5P/////ZriXR/////9muJdH/////8uQMm/////8l0dmu//////LkDJv/////JdHZrv////8a8eOO//////LkDJv/////GvHjjv/////y5Ayb/////9muJdH/////2a4l0f//////v/+///////+//7//////2S7Zrv9//78MGwyb/3//v9ku2a7/f/+/DBsMm/9//78lUSXR/3//vyVRJdH/f/+/8mTy5P9//7/yZPLk/3//v/Jk8uT/f/+/8mTy5P9//7//f////3//v/9/////f/+/")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_bjg0s")
[sub_resource type="BoxShape3D" id="BoxShape3D_y1cnp"]
size = Vector3(0.0599088, 0.250772, 0.143523)
[node name="blasterL_Clip" type="RigidBody3D"]
collision_layer = 8
collision_mask = 5
script = ExtResource("1_mpf05")
weapon = SubResource("Resource_3vvaw")
TYPE = "Ammo"
[node name="clipA2" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1.21, 0, 0, 0, 1, 0, 0, 0)
mesh = SubResource("ArrayMesh_wf2rx")
skeleton = NodePath("")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("BoxShape3D_y1cnp")
@@ -0,0 +1,124 @@
[gd_scene load_steps=11 format=4 uid="uid://dl236agqx8rjp"]
[ext_resource type="Script" uid="uid://deaf6cmuofa0" path="res://client/template/scripts/Weapon_State_Machine/PickUp Weapons/Weapon_Pick_Up.gd" id="1_6xwda"]
[ext_resource type="Script" uid="uid://bw4j1fbww4k8s" path="res://client/template/scripts/Weapon_State_Machine/WeaponSlot.gd" id="2_qrntt"]
[ext_resource type="Resource" uid="uid://yc1f2j7pl4vr" path="res://client/template/scripts/Weapon_State_Machine/Weapon_Resources/blasterI.tres" id="3_ot8uw"]
[sub_resource type="Resource" id="Resource_yxf5f"]
resource_local_to_scene = true
script = ExtResource("2_qrntt")
weapon = ExtResource("3_ot8uw")
current_ammo = 30
reserve_ammo = 0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n3ik4"]
resource_name = "metal"
vertex_color_use_as_albedo = true
albedo_color = Color(0.904365, 0.914141, 0.944534, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_fil5e"]
resource_name = "darkMetal"
vertex_color_use_as_albedo = true
albedo_color = Color(0.793075, 0.81829, 0.89044, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bfdxb"]
resource_name = "yellow"
vertex_color_use_as_albedo = true
albedo_color = Color(0.998274, 0.894448, 0.505969, 1)
metallic = 1.0
[sub_resource type="ArrayMesh" id="ArrayMesh_j3k8o"]
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYAAwABAAgACAAJAAMADAAKAAsACwANAAwADAAOAA8ADwAKAAwAEgAQABEAEQATABIAEgAJAAgACAAQABIAEQAAAAIAAgATABEACwAEAAYABgANAAsAFQAUABEAEQAWABUAEQAQABYAEAAIABYAAAARABQAFAAXAAAAFwABAAAAFwAYAAEAGAAIAAEAGAAWAAgAGgAZAAYABgAbABoABgAHABsABwAcABsABwAOABwADgAMABwADQAGABkAGQAdAA0AHQAMAA0AHQAcAAwAGQAaABQAFAAVABkAFgAYABwAHAAdABYAFQAWAB4AHgAZABUAHgAfABkAHwAgABkAIQAeABYAFgAdACEAHQAgACEAHQAZACAAGwAXABQAFAAaABsAHAAYABcAFwAbABwABwAFAA8ADwAOAAcA"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 18,
"index_data": PackedByteArray("EgATAAIAAgADABIAAwAJABIABQAEAAsACwAKAAUACgAPAAUA"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 138,
"index_data": PackedByteArray("IgAhACAAIAAjACIAIwAkACIAIwAlACQAJQAmACQAJgAnACQAJgAoACcAJgApACgAKQAqACgAKQArACoAKwAsACoALgAtACcAJwAoAC4AJQAvADAAMAAmACUAMQAfAB4AHgAyADEAMgAvADEAMgAzAC8AMwAwAC8AMwAtADAALQA0ADAALQAuADQALgA1ADQALgA2ADUANgA3ADUAJwAtADMAMwAkACcAIgAyAB4AHgAhACIANQA3ACwALAArADUANAA1ACsAKwApADQAIwAxAC8ALwAlACMAIAAfADEAMQAjACAAMAA0ACkAKQAmADAAJAAzADIAMgAiACQANgAuACgAKAAqADYANwA2ACoAKgAsADcA"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}]
blend_shape_mode = 0
[sub_resource type="ArrayMesh" id="ArrayMesh_py3xv"]
resource_name = "clipA_Mesh clipA"
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYACgAIAAkACQALAAoADgAMAA0ADQAPAA4AEgAQABEAEQATABIAFgAUABUAFQAXABYAGgAYABkAGQAbABoAHgAcAB0AHQAfAB4AIgAgACEAIQAjACIAJgAkACUAJQAnACYAJQAoACcAKAApACcAKgAlACQAJAArACoAKwAsACoAKwAtACwALQApACwALQAnACkAMAAuAC8ALwAxADAALwAyADEAMgAzADEAMgA0ADMANAA1ADMANgAvAC4ALgA3ADYANwA1ADYANwAzADUAOgA4ADkAOQA7ADoAPgA8AD0APQA/AD4AQgBAAEEAQQBDAEIAQQBEAEMARABFAEMARgBBAEAAQABHAEYARwBFAEYARwBDAEUASgBIAEkASQBLAEoATgBMAE0ATQBPAE4AUgBQAFEAUQBTAFIA"),
"material": SubResource("StandardMaterial3D_n3ik4"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAA/7+pqv//AAD/v6mq//8AAP+/qar//wAA/7+pqv//AAD/v6mq//8AAP+/qar//wAA/7+pqv//AAD/v/9///////+//3///////7//f///////v/9///////+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//f/9/////v/9//3////+//3//f////7//f/9/////v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//3//f////7//f/9/////v/9//3////+//3//f////7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//7////9//////////3/+////////f/7///////9//v///////3/+////////f/7/AID///9//v8AgP///3///wCA////f/////////9///////9//3////+//3//f////7//f/9/////v/9//3////+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//f///////v/9///////+//3///////7//f////v//v/9////+//+//3///////7//f////v//v/9///////+//3+qKv///7//f6oq////v/9/qir///+//3+qKv///7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9///////+//3///////7//f///////v/9///////+/AAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP8//78AAACA/z//vwAAAIAlUSXRAAAAgCVRJdEAAACAJdHZrgAAAID/v/+/AAAAgCXR2a4AAACA/7//vwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP8//78AAACA2S7ZrgAAAID/P/+/AAAAgNku2a4AAACADBsMmwAAAIDkDuOOAAAAgAwbDJsAAACA5A7jjgAAAID/f///AAAAgP9///8AAACADJvy5AAAAIAMm/LkAAAAgAyb8uQAAACADJvy5AAAAIDZriXRAAAAgNmuJdEAAACA8uQMmwAAAIAl0dmuAAAAgPLkDJsAAACAJdHZrgAAAIAa8eOOAAAAgPLkDJsAAACAGvHjjgAAAIDy5AybAAAAgNmuJdEAAACA2a4l0QAAAID/v/+/AAAAgP+//78AAACA2S7ZrgAAAIAMGwybAAAAgNku2a4AAACADBsMmwAAAIAlUSXRAAAAgCVRJdEAAACA8mTy5AAAAIDyZPLkAAAAgPJk8uQAAACA8mTy5AAAAID/f///AAAAgP9///8AAACA")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 18,
"index_data": PackedByteArray("VgBUAFUAVQBXAFYAVwBYAFYAWwBZAFoAWgBcAFsAXABdAFsA"),
"material": SubResource("StandardMaterial3D_fil5e"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3+qKgAAAID/f6oqAAAAgP9/qioAAACA/3+qKgAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACAAAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//v////3/+////////f/////////9//v///////3//////////f///////////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP8//78AAACA/z//vwAAAIAlUSXRAAAAgCVRJdEAAACAJdHZrgAAAID/v/+/AAAAgCXR2a4AAACA/7//vwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP8//78AAACA2S7ZrgAAAID/P/+/AAAAgNku2a4AAACADBsMmwAAAIDkDuOOAAAAgAwbDJsAAACA5A7jjgAAAID/f///AAAAgP9///8AAACADJvy5AAAAIAMm/LkAAAAgAyb8uQAAACADJvy5AAAAIDZriXRAAAAgNmuJdEAAACA8uQMmwAAAIAl0dmuAAAAgPLkDJsAAACAJdHZrgAAAIAa8eOOAAAAgPLkDJsAAACAGvHjjgAAAIDy5AybAAAAgNmuJdEAAACA2a4l0QAAAID/v/+/AAAAgP+//78AAACA2S7ZrgAAAIAMGwybAAAAgNku2a4AAACADBsMmwAAAIAlUSXRAAAAgCVRJdEAAACA8mTy5AAAAIDyZPLkAAAAgPJk8uQAAACA8mTy5AAAAID/f///AAAAgP9///8AAACA")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 138,
"index_data": PackedByteArray("YABeAF8AXwBhAGAAYQBiAGAAYQBjAGIAYwBkAGIAZABlAGIAZABmAGUAZABnAGYAZwBoAGYAZwBpAGgAaQBqAGgAbQBrAGwAbABuAG0AcQBvAHAAcAByAHEAdQBzAHQAdAB2AHUAdgB3AHUAdgB4AHcAeAB5AHcAeAB6AHkAegB7AHkAegB8AHsAfAB9AHsAfAB+AH0AfgB/AH0AggCAAIEAgQCDAIIAhgCEAIUAhQCHAIYAigCIAIkAiQCLAIoAjgCMAI0AjQCPAI4AkgCQAJEAkQCTAJIAlgCUAJUAlQCXAJYAmgCYAJkAmQCbAJoAngCcAJ0AnQCfAJ4AogCgAKEAoQCjAKIApgCkAKUApQCnAKYA"),
"material": SubResource("StandardMaterial3D_bfdxb"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3+qKgAAAID/f6oqAAAAgP9/qioAAACA/3+qKgAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID/////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/8//7//f/+//z//v/9//78lUSXR/3//vyVRJdH/f/+/JdHZrv//////v/+//////yXR2a7//////7//v///////f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/8//7//f/+/2S7Zrv9//7//P/+//3//v9ku2a7/f/+/DBsMm/9//7/kDuOO/3//vwwbDJv/f/+/5A7jjv9//7//f/////////9/////////DJvy5P////8Mm/Lk/////wyb8uT/////DJvy5P/////ZriXR/////9muJdH/////8uQMm/////8l0dmu//////LkDJv/////JdHZrv////8a8eOO//////LkDJv/////GvHjjv/////y5Ayb/////9muJdH/////2a4l0f//////v/+///////+//7//////2S7Zrv9//78MGwyb/3//v9ku2a7/f/+/DBsMm/9//78lUSXR/3//vyVRJdH/f/+/8mTy5P9//7/yZPLk/3//v/Jk8uT/f/+/8mTy5P9//7//f////3//v/9/////f/+/")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_j3k8o")
[sub_resource type="BoxShape3D" id="BoxShape3D_y1cnp"]
size = Vector3(0.0599088, 0.407185, 0.143523)
[node name="blasterI_Clip" type="RigidBody3D"]
collision_layer = 8
collision_mask = 5
script = ExtResource("1_6xwda")
weapon = SubResource("Resource_yxf5f")
TYPE = "Ammo"
[node name="clipA2" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1.936, 0, 0, 0, 0.99, 0, 0, 0)
mesh = SubResource("ArrayMesh_py3xv")
skeleton = NodePath("")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("BoxShape3D_y1cnp")
@@ -0,0 +1,122 @@
[gd_scene load_steps=11 format=4 uid="uid://cgt2lt4ultlaq"]
[ext_resource type="Script" uid="uid://deaf6cmuofa0" path="res://client/template/scripts/Weapon_State_Machine/PickUp Weapons/Weapon_Pick_Up.gd" id="1_qqhy0"]
[ext_resource type="Script" uid="uid://bw4j1fbww4k8s" path="res://client/template/scripts/Weapon_State_Machine/WeaponSlot.gd" id="2_ybq5e"]
[ext_resource type="Resource" uid="uid://cd8msvjysh2uv" path="res://client/template/scripts/Weapon_State_Machine/Weapon_Resources/blasterM.tres" id="3_parqs"]
[sub_resource type="Resource" id="Resource_jnsso"]
resource_local_to_scene = true
script = ExtResource("2_ybq5e")
weapon = ExtResource("3_parqs")
current_ammo = 9
reserve_ammo = 0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_0eh6a"]
resource_name = "metal"
albedo_color = Color(1, 0.894118, 0.505882, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8qvhc"]
resource_name = "darkMetal"
albedo_color = Color(0.793075, 0.81829, 0.89044, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mihsn"]
resource_name = "yellow"
albedo_color = Color(0.998274, 0.894448, 0.505969, 1)
metallic = 1.0
[sub_resource type="ArrayMesh" id="ArrayMesh_68l5q"]
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34896613377,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYAAwABAAgACAAJAAMADAAKAAsACwANAAwADAAOAA8ADwAKAAwAEgAQABEAEQATABIAEgAJAAgACAAQABIAEQAAAAIAAgATABEACwAEAAYABgANAAsAFQAUABEAEQAWABUAEQAQABYAEAAIABYAAAARABQAFAAXAAAAFwABAAAAFwAYAAEAGAAIAAEAGAAWAAgAGgAZAAYABgAbABoABgAHABsABwAcABsABwAOABwADgAMABwADQAGABkAGQAdAA0AHQAMAA0AHQAcAAwAGQAaABQAFAAVABkAFgAYABwAHAAdABYAFQAWAB4AHgAZABUAHgAfABkAHwAgABkAIQAeABYAFgAdACEAHQAgACEAHQAZACAAGwAXABQAFAAaABsAHAAYABcAFwAbABwABwAFAA8ADwAOAAcA"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("AABxLszMAAAAAP8fk54AAFVVcS7MzAAAVVX/H5OeAACpqnEuzMwAAKmq/x+TngAA//9xLszMAAD///8fk54AAAAA/x8zMwAAVVX/HzMzAACpqlTVMzMAAKmqVNXMzAAA//9U1TMzAAD//1TVzMwAAP///x8zMwAAqar/HzMzAAAAAFTVMzMAAAAAVNXMzAAAVVVU1TMzAABVVVTVzMwAAAAAqhr//wAAAABU9f//AAAAAFT1AAAAAAAAAACpqgAAAAAAAAAAAAD//1T1//8AAP//qhr//wAA//8AAKmqAAD//wAAAAAAAP//VPUAAAAAVVVU9czMAACpqlT1zMwAAKmqVPUzMwAAVVVU9TMzAADJVhf4MzMAADWpF/gzMwAADFup+jMzAADypKn6MzMAACqe3/wzMwAA1GHf/DMzAACqapH+MzMAAFSVkf4zMwAA9HSh/zMzAAAKi6H/MzMAAP9///8zMwAA1GHf/MzMAACqapH+zMwAAPKkqfrMzAAAKp7f/MzMAAA1qRf4zMwAAMlWF/jMzAAADFup+szMAABUlZH+zMwAAAqLof/MzAAA9HSh/8zMAAD/f///zMwAAA==")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34896613377,
"index_count": 18,
"index_data": PackedByteArray("EgATAAIAAgADABIAAwAJABIABQAEAAsACwAKAAUACgAPAAUA"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("AABxLszMAAAAAP8fk54AAFVVcS7MzAAAVVX/H5OeAACpqnEuzMwAAKmq/x+TngAA//9xLszMAAD///8fk54AAAAA/x8zMwAAVVX/HzMzAACpqlTVMzMAAKmqVNXMzAAA//9U1TMzAAD//1TVzMwAAP///x8zMwAAqar/HzMzAAAAAFTVMzMAAAAAVNXMzAAAVVVU1TMzAABVVVTVzMwAAAAAqhr//wAAAABU9f//AAAAAFT1AAAAAAAAAACpqgAAAAAAAAAAAAD//1T1//8AAP//qhr//wAA//8AAKmqAAD//wAAAAAAAP//VPUAAAAAVVVU9czMAACpqlT1zMwAAKmqVPUzMwAAVVVU9TMzAADJVhf4MzMAADWpF/gzMwAADFup+jMzAADypKn6MzMAACqe3/wzMwAA1GHf/DMzAACqapH+MzMAAFSVkf4zMwAA9HSh/zMzAAAKi6H/MzMAAP9///8zMwAA1GHf/MzMAACqapH+zMwAAPKkqfrMzAAAKp7f/MzMAAA1qRf4zMwAAMlWF/jMzAAADFup+szMAABUlZH+zMwAAAqLof/MzAAA9HSh/8zMAAD/f///zMwAAA==")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34896613377,
"index_count": 138,
"index_data": PackedByteArray("IgAhACAAIAAjACIAIwAkACIAIwAlACQAJQAmACQAJgAnACQAJgAoACcAJgApACgAKQAqACgAKQArACoAKwAsACoALgAtACcAJwAoAC4AJQAvADAAMAAmACUAMQAfAB4AHgAyADEAMgAvADEAMgAzAC8AMwAwAC8AMwAtADAALQA0ADAALQAuADQALgA1ADQALgA2ADUANgA3ADUAJwAtADMAMwAkACcAIgAyAB4AHgAhACIANQA3ACwALAArADUANAA1ACsAKwApADQAIwAxAC8ALwAlACMAIAAfADEAMQAjACAAMAA0ACkAKQAmADAAJAAzADIAMgAiACQANgAuACgAKAAqADYANwA2ACoAKgAsADcA"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("AABxLszMAAAAAP8fk54AAFVVcS7MzAAAVVX/H5OeAACpqnEuzMwAAKmq/x+TngAA//9xLszMAAD///8fk54AAAAA/x8zMwAAVVX/HzMzAACpqlTVMzMAAKmqVNXMzAAA//9U1TMzAAD//1TVzMwAAP///x8zMwAAqar/HzMzAAAAAFTVMzMAAAAAVNXMzAAAVVVU1TMzAABVVVTVzMwAAAAAqhr//wAAAABU9f//AAAAAFT1AAAAAAAAAACpqgAAAAAAAAAAAAD//1T1//8AAP//qhr//wAA//8AAKmqAAD//wAAAAAAAP//VPUAAAAAVVVU9czMAACpqlT1zMwAAKmqVPUzMwAAVVVU9TMzAADJVhf4MzMAADWpF/gzMwAADFup+jMzAADypKn6MzMAACqe3/wzMwAA1GHf/DMzAACqapH+MzMAAFSVkf4zMwAA9HSh/zMzAAAKi6H/MzMAAP9///8zMwAA1GHf/MzMAACqapH+zMwAAPKkqfrMzAAAKp7f/MzMAAA1qRf4zMwAAMlWF/jMzAAADFup+szMAABUlZH+zMwAAAqLof/MzAAA9HSh/8zMAAD/f///zMwAAA==")
}]
blend_shape_mode = 0
[sub_resource type="ArrayMesh" id="ArrayMesh_5ckjg"]
resource_name = "clipA_Mesh clipA"
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("MrMGkTKzrqsQkQaREJGuq+5uBpHubq6rzEwGkcxMrqvMTCGmzEy+bu5uIabubr5u7m6+bu5u+L3MTL5uzEz4vTKzluUQkZblMrPuTxCR7k8ys75uMrP4vRCRvm4Qkfi97m6W5cxMluXubu5PzEzuTzKzqtkQkarZMrPuTxCR7k/ubqrZzEyq2e5u7k/MTO5P///86cvM7k///4U1AACFNTMz7k8zM5bly8yq2aqq//+TnpblAAD//wAAhTUzM6rZAAD86VRV//9rYZbl/////8vMluXLzO5PMzPuT///hTUys/zpzEz86TKzhTXMTIU1MrP//8xM//8ys4U1zEyFNcxMVVTubvi9zExh2DKzYdgQkfi9EJG+bu5uvm4ys1VUzEzqscxMtIAys+qxMrO0gDKzVVQys12szExVVMxMXawQkSGmEJG+bjKzIaYys75uy8zuT8vMqtkzM+5Pk56W5TMzluUzM6rZMzPuT2thluXLzO5Py8yW5RCRhTXuboU1e5A+M4NvPjPHjh8xN3EfMe5zTC8QjEwvh4jmLXd35i1qhAUtlHsFLf9/uCzLzI5cMzOOXMvMQlozM0JaMzOuSjMzYkjLzK5Ky8xiSBCRhTXuboU1e5A+M4NvPjPHjh8xN3EfMRCMTC/uc0wvh4jmLXd35i1qhAUtlHsFLf9/uCzLzGJIy8yuSjMzYkgzM65Ky8wzNcvMgDczMzM1MzOANzMzkYjLzJGIMzPeisvM3oozMyZwy8wmcDMzc3LLzHNyMzMLPjMzvjvLzAs+y8y+OzMzgDczMzM1y8yAN8vMMzUzM0Jay8xCWjMzjlzLzI5cy8y+O8vMCz4zM747MzMLPsvMc3IzM3Nyy8wmcDMzJnDLzN6KMzPeisvMkYgzM5GI"),
"format": 34896613399,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYACgAIAAkACQALAAoADgAMAA0ADQAPAA4AEgAQABEAEQATABIAFgAUABUAFQAXABYAGgAYABkAGQAbABoAHgAcAB0AHQAfAB4AIgAgACEAIQAjACIAJgAkACUAJQAnACYAJQAoACcAKAApACcAKgAlACQAJAArACoAKwAsACoAKwAtACwALQApACwALQAnACkAMAAuAC8ALwAxADAALwAyADEAMgAzADEAMgA0ADMANAA1ADMANgAvAC4ALgA3ADYANwA1ADYANwAzADUAOgA4ADkAOQA7ADoAPgA8AD0APQA/AD4AQgBAAEEAQQBDAEIAQQBEAEMARABFAEMARgBBAEAAQABHAEYARwBFAEYARwBDAEUASgBIAEkASQBLAEoATgBMAE0ATQBPAE4AUgBQAFEAUQBTAFIA"),
"material": SubResource("StandardMaterial3D_0eh6a"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(5.90551, 11.4488, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("AABxLszM//8AAP8fk57//1VVcS7MzP//VVX/H5Oe//+pqnEuzMz//6mq/x+Tnv////9xLszM//////8fk57//wAA/x+Tnv+/AAD/HzMz/79VVf8fk57/v1VV/x8zM/+/qapU1TMz//+pqlTVzMz/////VNUzM/////9U1czM//////8fMzP/f6mq/x8zM/9///9U1TMz/3+pqlTVMzP/fwAAVNUzM///AABU1czM//9VVVTVMzP//1VVVNXMzP//VVX/HzMz/38AAP8fMzP/f1VVVNUzM/9/AABU1TMz/38AAHEuzMz//1VVcS7MzP//AABU1czM//9VVVTVzMz//6mqcS7MzP////9xLszM//+pqlTVzMz/////VNXMzP//AACqGv///78AAFTVzMz/vwAAVPX///+/AABU9QAA/78AAFTVMzP/vwAA/x8zM/+/AABxLszM/78AAAAAqar/vwAA/x+Tnv+/AAAAAAAA/7///1T1////v///cS7MzP+///+qGv///7///wAAqar/v////x+Tnv+///8AAAAA/7////8fMzP/v///VNUzM/+///9U1czM/7///1T1AAD/v///qhr///9/AACqGv///3///1T1////fwAAVPX///9/AAAAAAAA/////wAAAAD//wAAVPUAAP////9U9QAA//8AAFT1AAD/v1VVVPXMzP+/AABU9f///7///1T1////v6mqVPXMzP+/qapU9TMz/79VVVT1MzP/v///VPUAAP+/AAAAAKmqG60AAKoa//8brf//AACpqhut//+qGv//G60AAAAAAAD//wAAAACpqv////8AAAAA/////wAAqar//6mq/x+Tnv+/qar/HzMz/7////8fk57/v////x8zM/+/VVVU1czMVNVVVXEuzMxU1VVVVNUzM1TVVVX/H5OeVNVVVf8fMzNU1amqcS7MzFTVqapU1czMVNWpqv8fk55U1amqVNUzM1TVqar/HzMzVNVVVVT1MzP//6mqVPUzM///yVYX+DMz//81qRf4MzP//wxbqfozM///8qSp+jMz//8qnt/8MzP//9Rh3/wzM///qmqR/jMz//9UlZH+MzP///R0of8zM///Couh/zMz////f///MzP//9Rh3/zMzFTV1GHf/DMzVNWqapH+zMxU1apqkf4zM1TV8qSp+szMVNUqnt/8zMxU1fKkqfozM1TVKp7f/DMzVNWpqlT1zMz//1VVVPXMzP//NakX+MzM///JVhf4zMz///KkqfrMzP//DFup+szM//8qnt/8zMz//9Rh3/zMzP//VJWR/szM//+qapH+zMz//wqLof/MzP//9HSh/8zM////f///zMz//9Rh3/zMzFTVDFup+szMVNXUYd/8MzNU1QxbqfozM1TVyVYX+MzMVNVVVVT1zMxU1clWF/gzM1TVVVVU9TMzVNX/f///zMxU1f9///8zM1TVCouh/8zMVNUKi6H/MzNU1QqLof/MzFTVCouh/zMzVNVUlZH+zMxU1VSVkf4zM1TVNakX+MzMVNXypKn6zMxU1TWpF/gzM1TV8qSp+jMzVNWpqlT1zMxU1TWpF/jMzFTVqapU9TMzVNU1qRf4MzNU1VSVkf7MzFTVVJWR/jMzVNUqnt/8zMxU1Sqe3/wzM1TVDFup+szMVNXJVhf4zMxU1QxbqfozM1TVyVYX+DMzVNWqapH+zMxU1apqkf4zM1TV9HSh/8zMVNX0dKH/MzNU1fR0of/MzFTV9HSh/zMzVNX/f///zMxU1f9///8zM1TV/38az/9/Gs//fxrP/38az/9/Gs//fxrP/38az/9/Gs////9/////f////3////9//3//P/9//z//f/8//3//P/9/////f////3////9/////f/8//3//P/9//z//f/8//3////9/////f////3////9/////f////3////9/////f////3////9/////f////3/+//9/////f////3////9/////f/7//3/+//9//v//f/7//3/+//9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/f////3////9/////f////3////9/////f////3///////3/+//9/////f////3/+//9/////f/7//3////9/AAD/fwAA/38AAP9/AAD/f/9//z//f/8//3//P/9//z////9/////f////3////9//3////9/////f////3////9/////fwAA/38AAP9/AAD/fwAA/38AAP9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/////v/+//7//vyXR2a4l0dmu2a7ZLv+//z/Zrtku/7//P/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//v/+/2a4l0f+//7/ZriXRDJvy5OOOGvEMm/Lk444a8f///3////9/8uTyZPLk8mTy5PJk8uTyZCXRJVEl0SVRDJsMG9mu2S4Mmwwb2a7ZLuOO5A4Mmwwb447kDgybDBsl0SVRJdElUf+//z//v/8/2a4l0Qyb8uTZriXRDJvy5CXR2a4l0dmu8uQMm/LkDJvy5Ayb8uQMm////3////9/")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("MrMGkTKzrqsQkQaREJGuq+5uBpHubq6rzEwGkcxMrqvMTCGmzEy+bu5uIabubr5u7m6+bu5u+L3MTL5uzEz4vTKzluUQkZblMrPuTxCR7k8ys75uMrP4vRCRvm4Qkfi97m6W5cxMluXubu5PzEzuTzKzqtkQkarZMrPuTxCR7k/ubqrZzEyq2e5u7k/MTO5P///86cvM7k///4U1AACFNTMz7k8zM5bly8yq2aqq//+TnpblAAD//wAAhTUzM6rZAAD86VRV//9rYZbl/////8vMluXLzO5PMzPuT///hTUys/zpzEz86TKzhTXMTIU1MrP//8xM//8ys4U1zEyFNcxMVVTubvi9zExh2DKzYdgQkfi9EJG+bu5uvm4ys1VUzEzqscxMtIAys+qxMrO0gDKzVVQys12szExVVMxMXawQkSGmEJG+bjKzIaYys75uy8zuT8vMqtkzM+5Pk56W5TMzluUzM6rZMzPuT2thluXLzO5Py8yW5RCRhTXuboU1e5A+M4NvPjPHjh8xN3EfMe5zTC8QjEwvh4jmLXd35i1qhAUtlHsFLf9/uCzLzI5cMzOOXMvMQlozM0JaMzOuSjMzYkjLzK5Ky8xiSBCRhTXuboU1e5A+M4NvPjPHjh8xN3EfMRCMTC/uc0wvh4jmLXd35i1qhAUtlHsFLf9/uCzLzGJIy8yuSjMzYkgzM65Ky8wzNcvMgDczMzM1MzOANzMzkYjLzJGIMzPeisvM3oozMyZwy8wmcDMzc3LLzHNyMzMLPjMzvjvLzAs+y8y+OzMzgDczMzM1y8yAN8vMMzUzM0Jay8xCWjMzjlzLzI5cy8y+O8vMCz4zM747MzMLPsvMc3IzM3Nyy8wmcDMzJnDLzN6KMzPeisvMkYgzM5GI"),
"format": 34896613399,
"index_count": 18,
"index_data": PackedByteArray("VgBUAFUAVQBXAFYAVwBYAFYAWwBZAFoAWgBcAFsAXABdAFsA"),
"material": SubResource("StandardMaterial3D_8qvhc"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(5.90551, 11.4488, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("AABxLszM9eAAAP8fk5714FVVcS7MzPXgVVX/H5Oe9eCpqnEuzMz14Kmq/x+TnvXg//9xLszM9eD///8fk5714AAA/x+TnlTVAAD/HzMzVNVVVf8fk55U1VVV/x8zM1TVqapU1TMzVNWpqlTVzMxU1f//VNUzM1TV//9U1czMVNX///8fMzP//6mq/x8zM/////9U1TMz//+pqlTVMzP//wAAVNUzM1TVAABU1czMVNVVVVTVMzNU1VVVVNXMzFTVVVX/HzMz//8AAP8fMzP//1VVVNUzM///AABU1TMz//8AAHEuzMz//1VVcS7MzP//AABU1czM//9VVVTVzMz//6mqcS7MzP////9xLszM//+pqlTVzMz/////VNXMzP//AACqGv//VNUAAFTVzMxU1QAAVPX//1TVAABU9QAAVNUAAFTVMzNU1QAA/x8zM1TVAABxLszMVNUAAAAAqapU1QAA/x+TnlTVAAAAAAAAVNX//1T1//9U1f//cS7MzFTV//+qGv//VNX//wAAqapU1f///x+TnlTV//8AAAAAVNX///8fMzNU1f//VNUzM1TV//9U1czMVNX//1T1AABU1f//qhr/////AACqGv///////1T1/////wAAVPX/////AAAAAAAA/////wAAAAD//wAAVPUAAP////9U9QAA//8AAFT1AABU1VVVVPXMzFTVAABU9f//VNX//1T1//9U1amqVPXMzFTVqapU9TMzVNVVVVT1MzNU1f//VPUAAFTVAAAAAKmqaMsAAKoa//9oy///AACpqmjL//+qGv//aMsAAAAAAABU1QAAAACpqlTV//8AAAAAVNX//wAAqapU1amq/x+TnlTVqar/HzMzVNX///8fk55U1f///x8zM1TVVVVU1czM/79VVXEuzMz/v1VVVNUzM/+/VVX/H5Oe/79VVf8fMzP/v6mqcS7MzP+/qapU1czM/7+pqv8fk57/v6mqVNUzM/+/qar/HzMz/79VVVT1MzP//6mqVPUzM///yVYX+DMz//81qRf4MzP//wxbqfozM///8qSp+jMz//8qnt/8MzP//9Rh3/wzM///qmqR/jMz//9UlZH+MzP///R0of8zM///Couh/zMz////f///MzP//9Rh3/zMzFTV1GHf/DMzVNWqapH+zMxU1apqkf4zM1TV8qSp+szMVNUqnt/8zMxU1fKkqfozM1TVKp7f/DMzVNWpqlT1zMz//1VVVPXMzP//NakX+MzM///JVhf4zMz///KkqfrMzP//DFup+szM//8qnt/8zMz//9Rh3/zMzP//VJWR/szM//+qapH+zMz//wqLof/MzP//9HSh/8zM////f///zMz//9Rh3/zMzFTVDFup+szMVNXUYd/8MzNU1QxbqfozM1TVyVYX+MzMVNVVVVT1zMxU1clWF/gzM1TVVVVU9TMzVNX/f///zMxU1f9///8zM1TVCouh/8zMVNUKi6H/MzNU1QqLof/MzFTVCouh/zMzVNVUlZH+zMxU1VSVkf4zM1TVNakX+MzMVNXypKn6zMxU1TWpF/gzM1TV8qSp+jMzVNWpqlT1zMxU1TWpF/jMzFTVqapU9TMzVNU1qRf4MzNU1VSVkf7MzFTVVJWR/jMzVNUqnt/8zMxU1Sqe3/wzM1TVDFup+szMVNXJVhf4zMxU1QxbqfozM1TVyVYX+DMzVNWqapH+zMxU1apqkf4zM1TV9HSh/8zMVNX0dKH/MzNU1fR0of/MzFTV9HSh/zMzVNX/f///zMxU1f9///8zM1TV////f////3////9/////f////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD/f/9//3//f/9//3//f/9//38AAP9/AAD/fwAA/38AAP9//3//f/9//3//f/9//3//f/9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/f/9//3//f/9//3//f/9//3////9/////f////3///////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/fwAA/3////9/////f////3////9//3////9//v//f////3/+//9/////fwAA/38AAP9/AAD/fwAA/38AAP9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/////v/+//7//vyXR2a4l0dmu2a7ZLv+//z/Zrtku/7//P/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//v/+/2a4l0f+//7/ZriXRDJvy5OOOGvEMm/Lk444a8f///3////9/8uTyZPLk8mTy5PJk8uTyZCXRJVEl0SVRDJsMG9mu2S4Mmwwb2a7ZLuOO5A4Mmwwb447kDgybDBsl0SVRJdElUf+//z//v/8/2a4l0Qyb8uTZriXRDJvy5CXR2a4l0dmu8uQMm/LkDJvy5Ayb8uQMm////3////9/")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("MrMGkTKzrqsQkQaREJGuq+5uBpHubq6rzEwGkcxMrqvMTCGmzEy+bu5uIabubr5u7m6+bu5u+L3MTL5uzEz4vTKzluUQkZblMrPuTxCR7k8ys75uMrP4vRCRvm4Qkfi97m6W5cxMluXubu5PzEzuTzKzqtkQkarZMrPuTxCR7k/ubqrZzEyq2e5u7k/MTO5P///86cvM7k///4U1AACFNTMz7k8zM5bly8yq2aqq//+TnpblAAD//wAAhTUzM6rZAAD86VRV//9rYZbl/////8vMluXLzO5PMzPuT///hTUys/zpzEz86TKzhTXMTIU1MrP//8xM//8ys4U1zEyFNcxMVVTubvi9zExh2DKzYdgQkfi9EJG+bu5uvm4ys1VUzEzqscxMtIAys+qxMrO0gDKzVVQys12szExVVMxMXawQkSGmEJG+bjKzIaYys75uy8zuT8vMqtkzM+5Pk56W5TMzluUzM6rZMzPuT2thluXLzO5Py8yW5RCRhTXuboU1e5A+M4NvPjPHjh8xN3EfMe5zTC8QjEwvh4jmLXd35i1qhAUtlHsFLf9/uCzLzI5cMzOOXMvMQlozM0JaMzOuSjMzYkjLzK5Ky8xiSBCRhTXuboU1e5A+M4NvPjPHjh8xN3EfMRCMTC/uc0wvh4jmLXd35i1qhAUtlHsFLf9/uCzLzGJIy8yuSjMzYkgzM65Ky8wzNcvMgDczMzM1MzOANzMzkYjLzJGIMzPeisvM3oozMyZwy8wmcDMzc3LLzHNyMzMLPjMzvjvLzAs+y8y+OzMzgDczMzM1y8yAN8vMMzUzM0Jay8xCWjMzjlzLzI5cy8y+O8vMCz4zM747MzMLPsvMc3IzM3Nyy8wmcDMzJnDLzN6KMzPeisvMkYgzM5GI"),
"format": 34896613399,
"index_count": 138,
"index_data": PackedByteArray("YABeAF8AXwBhAGAAYQBiAGAAYQBjAGIAYwBkAGIAZABlAGIAZABmAGUAZABnAGYAZwBoAGYAZwBpAGgAaQBqAGgAbQBrAGwAbABuAG0AcQBvAHAAcAByAHEAdQBzAHQAdAB2AHUAdgB3AHUAdgB4AHcAeAB5AHcAeAB6AHkAegB7AHkAegB8AHsAfAB9AHsAfAB+AH0AfgB/AH0AggCAAIEAgQCDAIIAhgCEAIUAhQCHAIYAigCIAIkAiQCLAIoAjgCMAI0AjQCPAI4AkgCQAJEAkQCTAJIAlgCUAJUAlQCXAJYAmgCYAJkAmQCbAJoAngCcAJ0AnQCfAJ4AogCgAKEAoQCjAKIApgCkAKUApQCnAKYA"),
"material": SubResource("StandardMaterial3D_mihsn"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(5.90551, 11.4488, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("AABxLszM9eAAAP8fk5714FVVcS7MzPXgVVX/H5Oe9eCpqnEuzMz14Kmq/x+TnvXg//9xLszM9eD///8fk5714AAA/x+TnlTVAAD/HzMzVNVVVf8fk55U1VVV/x8zM1TVqapU1TMzVNWpqlTVzMxU1f//VNUzM1TV//9U1czMVNX///8fMzP//6mq/x8zM/////9U1TMz//+pqlTVMzP//wAAVNUzM1TVAABU1czMVNVVVVTVMzNU1VVVVNXMzFTVVVX/HzMz//8AAP8fMzP//1VVVNUzM///AABU1TMz//8AAHEuzMz//1VVcS7MzP//AABU1czM//9VVVTVzMz//6mqcS7MzP////9xLszM//+pqlTVzMz/////VNXMzP//AACqGv//VNUAAFTVzMxU1QAAVPX//1TVAABU9QAAVNUAAFTVMzNU1QAA/x8zM1TVAABxLszMVNUAAAAAqapU1QAA/x+TnlTVAAAAAAAAVNX//1T1//9U1f//cS7MzFTV//+qGv//VNX//wAAqapU1f///x+TnlTV//8AAAAAVNX///8fMzNU1f//VNUzM1TV//9U1czMVNX//1T1AABU1f//qhr/////AACqGv///////1T1/////wAAVPX/////AAAAAAAA/////wAAAAD//wAAVPUAAP////9U9QAA//8AAFT1AABU1VVVVPXMzFTVAABU9f//VNX//1T1//9U1amqVPXMzFTVqapU9TMzVNVVVVT1MzNU1f//VPUAAFTVAAAAAKmqaMsAAKoa//9oy///AACpqmjL//+qGv//aMsAAAAAAABU1QAAAACpqlTV//8AAAAAVNX//wAAqapU1amq/x+TnlTVqar/HzMzVNX///8fk55U1f///x8zM1TVVVVU1czMVNVVVXEuzMxU1VVVVNUzM1TVVVX/H5OeVNVVVf8fMzNU1amqcS7MzFTVqapU1czMVNWpqv8fk55U1amqVNUzM1TVqar/HzMzVNVVVVT1MzP//6mqVPUzM///yVYX+DMz//81qRf4MzP//wxbqfozM///8qSp+jMz//8qnt/8MzP//9Rh3/wzM///qmqR/jMz//9UlZH+MzP///R0of8zM///Couh/zMz////f///MzP//9Rh3/zMzPzF1GHf/DMz/MWqapH+zMxKyqpqkf4zM0rK8qSp+szMusIqnt/8zMz8xfKkqfozM7rCKp7f/DMz/MWpqlT1zMz/f1VVVPXMzP9/NakX+MzM/3/JVhf4zMz/f/KkqfrMzP9/DFup+szM/38qnt/8zMz/f9Rh3/zMzP9/VJWR/szM/3+qapH+zMz/fwqLof/MzP9/9HSh/8zM/3//f///zMz/f9Rh3/zMzPzFDFup+szMusLUYd/8MzP8xQxbqfozM7rCyVYX+MzMsMBVVVT1zMwrwMlWF/gzM7DAVVVU9TMzK8D/f///zMxU1f9///8zM1TVCouh/8zMd88Ki6H/MzN3zwqLof/MzHfPCouh/zMzd89UlZH+zMxKylSVkf4zM0rKNakX+MzMsMDypKn6zMy6wjWpF/gzM7DA8qSp+jMzusKpqlT1zMwrwDWpF/jMzLDAqapU9TMzK8A1qRf4MzOwwFSVkf7MzErKVJWR/jMzSsoqnt/8zMz8xSqe3/wzM/zFDFup+szMusLJVhf4zMywwAxbqfozM7rCyVYX+DMzsMCqapH+zMxKyqpqkf4zM0rK9HSh/8zMd8/0dKH/MzN3z/R0of/MzHfP9HSh/zMzd8//f///zMxU1f9///8zM1TV////f////3////9/////f////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD/f/9//3//f/9//3//f/9//38AAP9/AAD/fwAA/38AAP9//3//f/9//3//f/9//3//f/9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/f/9//3//f/9//3//f/9//3////9/////f////3///////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/fwAA/3////9/////f////3////9//3////9/////f////3////9/////fwAA/38AAP9/AAD/fwAA/38AAP9/////f////3////9/////f////3////9/////f////3////9/////f////3////9////+nADG/pwAxkuiZrtLoma7qKxUFv25/xyorFQW/bn/HP9/////f////3////9/////f////3////9/////f////3////9/////f////3////9////+nADGVJZV0/6cAMZUllXTVo1R5WqHKfFWjVHlaocp8VTVqipU1aoqf82/Jn/NvyZ/zb8mf82/JpfETCKXxEwirZpWDaisVBatmlYNqKxUFtWOagetmlYN1Y5qB62aVg2XxEwil8RMIv25/xz9uf8cVJZV01aNUeVUllXTVo1R5UuiZrtLoma7v6Z/sr+mf7K/pn+yv6Z/sqqqqqqqqqqq")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_68l5q")
[sub_resource type="BoxShape3D" id="BoxShape3D_g84k4"]
size = Vector3(0.166504, 0.159378, 0.162319)
[node name="blasterM_clip" type="RigidBody3D"]
collision_layer = 8
collision_mask = 5
script = ExtResource("1_qqhy0")
weapon = SubResource("Resource_jnsso")
TYPE = "Ammo"
[node name="clipA2" type="MeshInstance3D" parent="."]
transform = Transform3D(2.76, 0, 0, 0, -0.695, 9.66022e-08, 0, -6.07588e-08, -1.105, 0, 0, 0)
mesh = SubResource("ArrayMesh_5ckjg")
skeleton = NodePath("")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.53727e-05, 0.00519562, 0.00165367)
shape = SubResource("BoxShape3D_g84k4")
@@ -0,0 +1,123 @@
[gd_scene load_steps=11 format=4 uid="uid://cjxvrad2c6vh8"]
[ext_resource type="Script" uid="uid://deaf6cmuofa0" path="res://client/template/scripts/Weapon_State_Machine/PickUp Weapons/Weapon_Pick_Up.gd" id="1_d1wy0"]
[ext_resource type="Script" uid="uid://bw4j1fbww4k8s" path="res://client/template/scripts/Weapon_State_Machine/WeaponSlot.gd" id="2_tnv4c"]
[ext_resource type="Resource" uid="uid://c1jg0ifn7yvve" path="res://client/template/scripts/Weapon_State_Machine/Weapon_Resources/blasterN.tres" id="3_x72lj"]
[sub_resource type="Resource" id="Resource_8ete7"]
resource_local_to_scene = true
script = ExtResource("2_tnv4c")
weapon = ExtResource("3_x72lj")
current_ammo = 5
reserve_ammo = 0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n3ik4"]
resource_name = "metal"
vertex_color_use_as_albedo = true
albedo_color = Color(0.904365, 0.914141, 0.944534, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_fil5e"]
resource_name = "darkMetal"
vertex_color_use_as_albedo = true
albedo_color = Color(0.793075, 0.81829, 0.89044, 1)
metallic = 1.0
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bfdxb"]
resource_name = "yellow"
vertex_color_use_as_albedo = true
albedo_color = Color(0.998274, 0.894448, 0.505969, 1)
metallic = 1.0
[sub_resource type="ArrayMesh" id="ArrayMesh_j3k8o"]
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYAAwABAAgACAAJAAMADAAKAAsACwANAAwADAAOAA8ADwAKAAwAEgAQABEAEQATABIAEgAJAAgACAAQABIAEQAAAAIAAgATABEACwAEAAYABgANAAsAFQAUABEAEQAWABUAEQAQABYAEAAIABYAAAARABQAFAAXAAAAFwABAAAAFwAYAAEAGAAIAAEAGAAWAAgAGgAZAAYABgAbABoABgAHABsABwAcABsABwAOABwADgAMABwADQAGABkAGQAdAA0AHQAMAA0AHQAcAAwAGQAaABQAFAAVABkAFgAYABwAHAAdABYAFQAWAB4AHgAZABUAHgAfABkAHwAgABkAIQAeABYAFgAdACEAHQAgACEAHQAZACAAGwAXABQAFAAaABsAHAAYABcAFwAbABwABwAFAA8ADwAOAAcA"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 18,
"index_data": PackedByteArray("EgATAAIAAgADABIAAwAJABIABQAEAAsACwAKAAUACgAPAAUA"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"format": 34359742465,
"index_count": 138,
"index_data": PackedByteArray("IgAhACAAIAAjACIAIwAkACIAIwAlACQAJQAmACQAJgAnACQAJgAoACcAJgApACgAKQAqACgAKQArACoAKwAsACoALgAtACcAJwAoAC4AJQAvADAAMAAmACUAMQAfAB4AHgAyADEAMgAvADEAMgAzAC8AMwAwAC8AMwAtADAALQA0ADAALQAuADQALgA1ADQALgA2ADUANgA3ADUAJwAtADMAMwAkACcAIgAyAB4AHgAhACIANQA3ACwALAArADUANAA1ACsAKwApADQAIwAxAC8ALwAlACMAIAAfADEAMQAjACAAMAA0ACkAKQAmADAAJAAzADIAMgAiACQANgAuACgAKAAqADYANwA2ACoAKgAsADcA"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 56,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9kML1vFyPwr2amZk9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vJDC9b3NzMw8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1PJDC9b2amZm9kML1PK1H4T2amZm9CtcjvK1H4T3rUTg9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9CtejuyYE8z3rUTg9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTg9CtejOyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9")
}]
blend_shape_mode = 0
[sub_resource type="ArrayMesh" id="ArrayMesh_py3xv"]
resource_name = "clipA_Mesh clipA"
_surfaces = [{
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 168,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYACgAIAAkACQALAAoADgAMAA0ADQAPAA4AEgAQABEAEQATABIAFgAUABUAFQAXABYAGgAYABkAGQAbABoAHgAcAB0AHQAfAB4AIgAgACEAIQAjACIAJgAkACUAJQAnACYAJQAoACcAKAApACcAKgAlACQAJAArACoAKwAsACoAKwAtACwALQApACwALQAnACkAMAAuAC8ALwAxADAALwAyADEAMgAzADEAMgA0ADMANAA1ADMANgAvAC4ALgA3ADYANwA1ADYANwAzADUAOgA4ADkAOQA7ADoAPgA8AD0APQA/AD4AQgBAAEEAQQBDAEIAQQBEAEMARABFAEMARgBBAEAAQABHAEYARwBFAEYARwBDAEUASgBIAEkASQBLAEoATgBMAE0ATQBPAE4AUgBQAFEAUQBTAFIA"),
"material": SubResource("StandardMaterial3D_n3ik4"),
"name": "metal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAA/7+pqv//AAD/v6mq//8AAP+/qar//wAA/7+pqv//AAD/v6mq//8AAP+/qar//wAA/7+pqv//AAD/v/9///////+//3///////7//f///////v/9///////+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//f/9/////v/9//3////+//3//f////7//f/9/////v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//3//f////7//f/9/////v/9//3////+//3//f////7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//7////9//////////3/+////////f/7///////9//v///////3/+////////f/7/AID///9//v8AgP///3///wCA////f/////////9///////9//3////+//3//f////7//f/9/////v/9//3////+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//f///////v/9///////+//3///////7//f////v//v/9////+//+//3///////7//f////v//v/9///////+//3+qKv///7//f6oq////v/9/qir///+//3+qKv///7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9///////+//3///////7//f///////v/9///////+/AAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP8//78AAACA/z//vwAAAIAlUSXRAAAAgCVRJdEAAACAJdHZrgAAAID/v/+/AAAAgCXR2a4AAACA/7//vwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP8//78AAACA2S7ZrgAAAID/P/+/AAAAgNku2a4AAACADBsMmwAAAIDkDuOOAAAAgAwbDJsAAACA5A7jjgAAAID/f///AAAAgP9///8AAACADJvy5AAAAIAMm/LkAAAAgAyb8uQAAACADJvy5AAAAIDZriXRAAAAgNmuJdEAAACA8uQMmwAAAIAl0dmuAAAAgPLkDJsAAACAJdHZrgAAAIAa8eOOAAAAgPLkDJsAAACAGvHjjgAAAIDy5AybAAAAgNmuJdEAAACA2a4l0QAAAID/v/+/AAAAgP+//78AAACA2S7ZrgAAAIAMGwybAAAAgNku2a4AAACADBsMmwAAAIAlUSXRAAAAgCVRJdEAAACA8mTy5AAAAIDyZPLkAAAAgPJk8uQAAACA8mTy5AAAAID/f///AAAAgP9///8AAACA")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 18,
"index_data": PackedByteArray("VgBUAFUAVQBXAFYAVwBYAFYAWwBZAFoAWgBcAFsAXABdAFsA"),
"material": SubResource("StandardMaterial3D_fil5e"),
"name": "darkMetal",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3+qKgAAAID/f6oqAAAAgP9/qioAAACA/3+qKgAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACAAAD/f/9//78AAP9//3//vwAA/3//f/+/AAD/f/9//78AAP9//3//v////3/+////////f/////////9//v///////3//////////f///////////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP8//78AAACA/z//vwAAAIAlUSXRAAAAgCVRJdEAAACAJdHZrgAAAID/v/+/AAAAgCXR2a4AAACA/7//vwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP8//78AAACA2S7ZrgAAAID/P/+/AAAAgNku2a4AAACADBsMmwAAAIDkDuOOAAAAgAwbDJsAAACA5A7jjgAAAID/f///AAAAgP9///8AAACADJvy5AAAAIAMm/LkAAAAgAyb8uQAAACADJvy5AAAAIDZriXRAAAAgNmuJdEAAACA8uQMmwAAAIAl0dmuAAAAgPLkDJsAAACAJdHZrgAAAIAa8eOOAAAAgPLkDJsAAACAGvHjjgAAAIDy5AybAAAAgNmuJdEAAACA2a4l0QAAAID/v/+/AAAAgP+//78AAACA2S7ZrgAAAIAMGwybAAAAgNku2a4AAACADBsMmwAAAIAlUSXRAAAAgCVRJdEAAACA8mTy5AAAAIDyZPLkAAAAgPJk8uQAAACA8mTy5AAAAID/f///AAAAgP9///8AAACA")
}, {
"aabb": AABB(-0.03, -0.12, -0.075, 0.06, 0.24, 0.15),
"attribute_data": PackedByteArray("XS6XP0T3Qj9dLpc/dxH6PyaTyT5E90I/JpPJPncR+j8mk8m+RPdCPyaTyb53Efo/XS6Xv0T3Qj9dLpe/dxH6P10ul7+US9o/XS6XvxaLRb8mk8m+lEvaPyaTyb4Wi0W/JpPJvhaLRb8mk8m+xmIxQF0ul78Wi0W/XS6Xv8ZiMUBdLpc/xmKRQCaTyT7GYpFAXS6XPyaTCcAmk8k+JpMJwF0ulz8Wi0W/XS6XP8ZiMUAmk8k+FotFvyaTyT7GYjFAJpPJvsZikUBdLpe/xmKRQCaTyb4mkwnAXS6XvyaTCcBdLpc/hlOAQCaTyT6GU4BAXS6XPyaTCcAmk8k+JpMJwCaTyb6GU4BAXS6Xv4ZTgEAmk8m+JpMJwF0ul78mkwnA9Pk8QF6vl0CLxeI/JpMJwPT5PEBUKlXA9Pk8wFQqVcCLxeK/JpMJwIvF4r/GYpFAi8XiP4ZTgEDw93s/XS63QCmXND/GYpFA9Pk8wF0ut0D0+TzAVCpVwIvF4r+GU4BA9Pk8wF6vl0Dw93u/XS63QCmXNL/GYpFA9Pk8QF0ut0CLxeI/xmKRQIvF4j8mkwnAi8XivyaTCcD0+TxAVCpVwF0ulz9er5dAXS6Xv16vl0BdLpc/VCpVwF0ul79UKlXAXS6XP10ut0BdLpe/XS63QF0ulz9UKlXAXS6Xv1QqVcBdLpe/6PP5vyaTyb7GYjFAXS6Xv/T5fEBdLpc/9Pl8QCaTyT7GYjFAJpPJPhaLRb8mk8m+FotFv10ulz/o8/m/XS6Xv+3gDkBdLpe/QIgBPV0ulz/t4A5AXS6XP0CIAT1dLpc/6PP5v10ulz/4+/0/XS6Xv+jz+b9dLpe/+Pv9PyaTyT6US9o/JpPJPhaLRb9dLpc/lEvaP10ulz8Wi0W/i8XiPyaTCcCLxeI/hlOAQIvF4r8mkwnAKZc0P8ZikUCLxeK/xmKRQIvF4r+GU4BAi8XivyaTCcAplzS/xmKRQIvF4j8mkwnAi8XiP8ZikUAmk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI6+cvtmwPCIjj5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcDxsKoOulxuwIvF4j+g4Mq/i8Xiv6Dgyr+LxeI/agjYv4vF4r9qCNi/i8Xiv+mZGMCLxeK/zS0fwIvF4j/pmRjAi8XiP80tH8Amk8k+VCpVwCaTyb5UKlXA0bTCPtCvW8DRtMK+0K9bwKORrj6Iw2HAo5GuvojDYcDwiI4+cvtmwPCIjr5y+2bAJpNJPor8asAmk0m+ivxqwIOv0D3ugG3Ag6/Qve6AbcAJafYNulxuwIvF4j/NLR/Ai8XiP+mZGMCLxeK/zS0fwIvF4r/pmRjAi8XiP0AVVsCLxeI/XIFPwIvF4r9AFVbAi8Xiv1yBT8CLxeK/QEXEPovF4j9ARcQ+i8Xiv2Dk+D6LxeI/YOT4PovF4r/4azW/i8XiP/hrNb+LxeK/aBwbv4vF4j9oHBu/i8Xiv0/HPMCLxeK/MltDwIvF4j9PxzzAi8XiPzJbQ8CLxeK/XIFPwIvF4r9AFVbAi8XiP1yBT8CLxeI/QBVWwIvF4r9qCNi/i8XiP2oI2L+LxeK/oODKv4vF4j+g4Mq/i8XiPzJbQ8CLxeI/T8c8wIvF4r8yW0PAi8Xiv0/HPMCLxeI/aBwbv4vF4r9oHBu/i8XiP/hrNb+LxeK/+Gs1v4vF4j9g5Pg+i8Xiv2Dk+D6LxeI/QEXEPovF4r9ARcQ+"),
"format": 34359742487,
"index_count": 138,
"index_data": PackedByteArray("YABeAF8AXwBhAGAAYQBiAGAAYQBjAGIAYwBkAGIAZABlAGIAZABmAGUAZABnAGYAZwBoAGYAZwBpAGgAaQBqAGgAbQBrAGwAbABuAG0AcQBvAHAAcAByAHEAdQBzAHQAdAB2AHUAdgB3AHUAdgB4AHcAeAB5AHcAeAB6AHkAegB7AHkAegB8AHsAfAB9AHsAfAB+AH0AfgB/AH0AggCAAIEAgQCDAIIAhgCEAIUAhQCHAIYAigCIAIkAiQCLAIoAjgCMAI0AjQCPAI4AkgCQAJEAkQCTAJIAlgCUAJUAlQCXAJYAmgCYAJkAmQCbAJoAngCcAJ0AnQCfAJ4AogCgAKEAoQCjAKIApgCkAKUApQCnAKYA"),
"material": SubResource("StandardMaterial3D_bfdxb"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 168,
"vertex_data": PackedByteArray("kML1vIaWnL3rUTg9kML1vOtRuL2wyJI8CtcjvIaWnL3rUTg9CtcjvOtRuL2wyJI8CtcjPIaWnL3rUTg9CtcjPOtRuL2wyJI8kML1PIaWnL3rUTg9kML1POtRuL2wyJI8kML1vOtRuL2wyJI8kML1vOtRuL3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPArXoz3rUTi9CtcjPArXoz3rUTg9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1POtRuL3rUTi9CtcjPOtRuL3rUTi9kML1PArXoz3rUTi9CtcjPArXoz3rUTi9kML1vArXoz3rUTi9kML1vArXoz3rUTg9CtcjvArXoz3rUTi9CtcjvArXoz3rUTg9CtcjvOtRuL3rUTi9kML1vOtRuL3rUTi9CtcjvArXoz3rUTi9kML1vArXoz3rUTi9kML1vIaWnL3rUTg9CtcjvIaWnL3rUTg9kML1vArXoz3rUTg9CtcjvArXoz3rUTg9CtcjPIaWnL3rUTg9kML1PIaWnL3rUTg9CtcjPArXoz3rUTg9kML1PArXoz3rUTg9kML1vFyPwr2amZk9kML1vArXoz3rUTg9kML1vK1H4T2amZk9kML1vK1H4T2amZm9kML1vArXoz3rUTi9kML1vOtRuL3rUTi9kML1vIaWnL3rUTg9kML1vJDC9b3NzMw8kML1vOtRuL2wyJI8kML1vJDC9b2amZm9kML1PK1H4T2amZk9kML1PIaWnL3rUTg9kML1PFyPwr2amZk9kML1PJDC9b3NzMw8kML1POtRuL2wyJI8kML1PJDC9b2amZm9kML1POtRuL3rUTi9kML1PArXoz3rUTi9kML1PArXoz3rUTg9kML1PK1H4T2amZm9kML1PFyPwr2amZk9kML1vFyPwr2amZk9kML1PK1H4T2amZk9kML1vK1H4T2amZk9kML1vJDC9b2amZm9kML1PJDC9b2amZm9kML1vK1H4T2amZm9kML1PK1H4T2amZm9kML1vK1H4T2amZm9CtcjvK1H4T3rUTg9kML1vK1H4T2amZk9kML1PK1H4T2amZk9CtcjPK1H4T3rUTg9CtcjPK1H4T3rUTi9CtcjvK1H4T3rUTi9kML1PK1H4T2amZm9kML1vJDC9b3NzMw8kML1vFyPwr2amZk9kML1PJDC9b3NzMw8kML1PFyPwr2amZk9kML1vJDC9b2amZm9kML1vJDC9b3NzMw8kML1PJDC9b2amZm9kML1PJDC9b3NzMw8CtcjPOtRuL2wyJI8CtcjPOtRuL3rUTi9kML1POtRuL2wyJI8kML1POtRuL3rUTi9CtcjvArXoz3rUTg9CtcjvIaWnL3rUTg9CtcjvArXoz3rUTi9CtcjvOtRuL2wyJI8CtcjvOtRuL3rUTi9CtcjPIaWnL3rUTg9CtcjPArXoz3rUTg9CtcjPOtRuL2wyJI8CtcjPArXoz3rUTi9CtcjPOtRuL3rUTi9CtcjvK1H4T3rUTi9CtcjPK1H4T3rUTi93kEevKKU5j3rUTi93kEePKKU5j3rUTi9vOMNvB+F6z3rUTi9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTi9CtejOyYE8z3rUTi9oZ4pu+kP9T3rUTi9oZ4pO+kP9T3rUTi9AAAAAJDC9T3rUTi9a7Tnu/TC7z3rUTg9a7Tnu/TC7z3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9vOMNPB+F6z3rUTg9a7TnO/TC7z3rUTg9vOMNPB+F6z3rUTi9a7TnO/TC7z3rUTi9CtcjPK1H4T3rUTg9CtcjvK1H4T3rUTg93kEePKKU5j3rUTg93kEevKKU5j3rUTg9vOMNPB+F6z3rUTg9vOMNvB+F6z3rUTg9a7TnO/TC7z3rUTg9a7Tnu/TC7z3rUTg9CtejOyYE8z3rUTg9CtejuyYE8z3rUTg9oZ4pO+kP9T3rUTg9oZ4pu+kP9T3rUTg9AAAAAJDC9T3rUTg9a7Tnu/TC7z3rUTg9vOMNvB+F6z3rUTg9a7Tnu/TC7z3rUTi9vOMNvB+F6z3rUTi93kEevKKU5j3rUTg9CtcjvK1H4T3rUTg93kEevKKU5j3rUTi9CtcjvK1H4T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9oZ4pO+kP9T3rUTg9oZ4pO+kP9T3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi93kEePKKU5j3rUTg9vOMNPB+F6z3rUTg93kEePKKU5j3rUTi9vOMNPB+F6z3rUTi9CtcjPK1H4T3rUTg93kEePKKU5j3rUTg9CtcjPK1H4T3rUTi93kEePKKU5j3rUTi9CtejOyYE8z3rUTg9CtejOyYE8z3rUTi9a7TnO/TC7z3rUTg9a7TnO/TC7z3rUTi9vOMNvB+F6z3rUTg93kEevKKU5j3rUTg9vOMNvB+F6z3rUTi93kEevKKU5j3rUTi9CtejuyYE8z3rUTg9CtejuyYE8z3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9oZ4pu+kP9T3rUTg9oZ4pu+kP9T3rUTi9AAAAAJDC9T3rUTg9AAAAAJDC9T3rUTi9qar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgKmq//8AAACAqar//wAAAICpqv//AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9/AAAAAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/3//fwAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/////AAAAgP////8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP9//38AAACA/3//fwAAAID/f/9/AAAAgP9//38AAACA/////wAAAID/////AAAAgP////8AAACA/////wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACA/3+qKgAAAID/f6oqAAAAgP9/qioAAACA/3+qKgAAAID/fwAAAAAAgP9/AAAAAACA/38AAAAAAID/fwAAAAAAgP9///8AAACA/3///wAAAID/f///AAAAgP9///8AAACAAAD/fwAAAIAAAP9/AAAAgAAA/38AAACAAAD/fwAAAIAAAP9/AAAAgP///38AAACA////fwAAAID///9/AAAAgP///38AAACA////fwAAAID/////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/////8AAP+//////wAA/7//////AAD/v/8//7//f/+//z//v/9//78lUSXR/3//vyVRJdH/f/+/JdHZrv//////v/+//////yXR2a7//////7//v///////f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/9//3////+//3//f////7//f/9/////v/8//7//f/+/2S7Zrv9//7//P/+//3//v9ku2a7/f/+/DBsMm/9//7/kDuOO/3//vwwbDJv/f/+/5A7jjv9//7//f/////////9/////////DJvy5P////8Mm/Lk/////wyb8uT/////DJvy5P/////ZriXR/////9muJdH/////8uQMm/////8l0dmu//////LkDJv/////JdHZrv////8a8eOO//////LkDJv/////GvHjjv/////y5Ayb/////9muJdH/////2a4l0f//////v/+///////+//7//////2S7Zrv9//78MGwyb/3//v9ku2a7/f/+/DBsMm/9//78lUSXR/3//vyVRJdH/f/+/8mTy5P9//7/yZPLk/3//v/Jk8uT/f/+/8mTy5P9//7//f////3//v/9/////f/+/")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_j3k8o")
[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_p04et"]
points = PackedVector3Array(0.03, 0.109992, 0.075, 0.00258418, 0.119648, 0.0449883, 0, 0.12, 0.0449883, -0.03, 0.109992, 0.075, 0.03, -0.0950039, 0.075, 0.03, 0.109992, -0.075, 0.00258418, 0.119648, -0.0450029, 0, 0.12, -0.0450029, -0.00259006, 0.119648, 0.0449883, -0.03, 0.109992, -0.075, -0.03, -0.0950039, 0.075, 0.03, -0.12, 0.0249902, 0.03, -0.12, -0.075, -0.00259006, 0.119648, -0.0450029, -0.03, -0.12, -0.075, -0.03, -0.12, 0.0249902)
[node name="blasterN_Clip" type="RigidBody3D"]
collision_layer = 8
collision_mask = 5
script = ExtResource("1_d1wy0")
weapon = SubResource("Resource_8ete7")
TYPE = "Ammo"
[node name="clipA2" type="MeshInstance3D" parent="."]
mesh = SubResource("ArrayMesh_py3xv")
skeleton = NodePath("")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("ConvexPolygonShape3D_p04et")
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dc48o4niy1mok"]
[ext_resource type="Script" uid="uid://b26eegme16mfw" path="res://client/template/scripts/Projectiles/Projectile.gd" id="1_epqi3"]
[node name="Projectile" type="Node3D"]
script = ExtResource("1_epqi3")
@@ -0,0 +1,10 @@
[gd_scene load_steps=3 format=3 uid="uid://bpxa3je174q43"]
[ext_resource type="Script" uid="uid://b26eegme16mfw" path="res://client/template/scripts/Projectiles/Projectile.gd" id="1_won33"]
[ext_resource type="PackedScene" uid="uid://dxlkgbr1p580" path="res://client/template/Spawnable_Objects/bullet.tscn" id="2_sq5da"]
[node name="Projectile" type="Node3D"]
script = ExtResource("1_won33")
Projectile_Type = "Rigidbody_Projectile"
Projectile_Velocity = 20
Rigid_Body_Projectile = ExtResource("2_sq5da")
Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

@@ -0,0 +1,14 @@
[gd_scene load_steps=3 format=3 uid="uid://6rfh1ejt4m6m"]
[ext_resource type="Script" uid="uid://bfn8eu00l4btq" path="res://client/template/scripts/Projectiles/melee_weapon_projectile.gd" id="1_qlmbe"]
[sub_resource type="BoxShape3D" id="BoxShape3D_q86ac"]
[node name="MeleeProjectile" type="Node3D"]
script = ExtResource("1_qlmbe")
Projectile_Type = "over_ride"
[node name="MeleeHitbox" type="ShapeCast3D" parent="."]
shape = SubResource("BoxShape3D_q86ac")
target_position = Vector3(0, 0, -1)
collision_mask = 37
@@ -0,0 +1,22 @@
[gd_scene load_steps=4 format=3 uid="uid://ddfiivxgnakti"]
[ext_resource type="Script" uid="uid://biivm72fp7af8" path="res://client/template/scripts/Projectiles/rocket_launcher_projectile.gd" id="1_dkmt1"]
[ext_resource type="PackedScene" uid="uid://cqhlfy6ccomm3" path="res://client/template/Spawnable_Objects/rocket.tscn" id="2_u24w0"]
[sub_resource type="SphereShape3D" id="SphereShape3D_wqjlt"]
radius = 5.0
[node name="Projectile" type="Node3D" node_paths=PackedStringArray("explosion")]
script = ExtResource("1_dkmt1")
explosion = NodePath("ExplosionRadias")
Projectile_Type = "Rigidbody_Projectile"
Projectile_Velocity = 20
Expirey_Time = 20
Rigid_Body_Projectile = ExtResource("2_u24w0")
pass_through = true
[node name="ExplosionRadias" type="ShapeCast3D" parent="."]
top_level = true
shape = SubResource("SphereShape3D_wqjlt")
target_position = Vector3(0, 0, 0)
collision_mask = 37
@@ -0,0 +1,17 @@
[gd_scene load_steps=3 format=3 uid="uid://brvfyahytwd8m"]
[ext_resource type="Script" uid="uid://dnh4a4vkl0cl1" path="res://client/template/scripts/Projectiles/shotgun_projectile.gd" id="1_idx2q"]
[sub_resource type="Curve2D" id="Curve2D_2lhxk"]
_data = {
"points": PackedVector2Array(0, 0, 0, 0, 7, 3, 0, 0, 0, 0, -30, -11, 0, 0, 0, 0, -8, -18, 0, 0, 0, 0, 8, -9, -2.50282, -11.0124, 2.50282, 11.0124, 19, 13, 0, 0, 0, 0, -21, 1, 0, 0, 0, 0, 22, 22, 0, 0, 0, 0, 12, 21, 0, 0, 0, 0, -33, 4)
}
point_count = 9
[node name="shotgun_projectile" type="Node3D"]
script = ExtResource("1_idx2q")
Randomness = 2.941
Split_Damage = true
[node name="shotgun_pattern" type="Path2D" parent="."]
curve = SubResource("Curve2D_2lhxk")
@@ -0,0 +1,41 @@
extends Node
class_name Spray_Profile
@export_category("Random_Spray")
@export var Spray_Noise: FastNoiseLite
@export_range(0.0,10.0) var Random_Spray_Multiplier: float = 1.0
@export var Max_Limit: int = 30
@export var Updwards_Lock: bool = false
@export var True_Random: bool = true
@export_category("Path_Spray")
@export var Spray_Path: Path2D
@export_range(0.0,10.0) var Path_Spray_Multiplier: float = 1.0
@export_range(0.0,1.0) var Spray_Mix: float = 1.0
var Random_Spray: Vector2 = Vector2.ZERO
var Spray_Vector: Vector2 = Vector2.ZERO
func Get_Spray(count: int,_max_count: int)->Vector2:
if Spray_Noise:
if True_Random:
Spray_Noise.set_seed(randi())
var x = Spray_Noise.get_noise_1d(count)*min(count,Max_Limit)
var y = Spray_Noise.get_noise_2d(_max_count,count)*min(count,Max_Limit)
if Updwards_Lock:
y = -abs(y)
Random_Spray = Vector2(x,y)*Random_Spray_Multiplier
if Spray_Path:
var Path_Points: Curve2D = Spray_Path.get_curve()
var Point_Count: int = Path_Points.get_point_count()
count = min(count-1,Point_Count-1)
Spray_Vector = Path_Points.get_point_position(count)*Path_Spray_Multiplier
var Spray = Spray_Vector + (Random_Spray*Spray_Mix)
return Spray
@@ -0,0 +1 @@
uid://dvcpsronqmkmr
@@ -0,0 +1,20 @@
[gd_scene load_steps=4 format=3 uid="uid://ciu0xkaw0ioef"]
[ext_resource type="Script" uid="uid://dvcpsronqmkmr" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile.gd" id="1_60v4d"]
[sub_resource type="FastNoiseLite" id="FastNoiseLite_4msk4"]
[sub_resource type="Curve2D" id="Curve2D_0vouc"]
_data = {
"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 0, 0, 0, -11, 0, 0, 0, 0, 1, -23, 0, 0, 0, 0, 2, -35, 0, 0, 0, 0, -4, -50, 0, 0, 0, 0, -7, -62, 0, 0, 0, 0, -12, -69, 0, 0, 0, 0, -6, -76, 0, 0, 0, 0, 12, -74, 0, 0, 0, 0, 9, -86, 0, 0, 0, 0, 16, -81, 0, 0, 0, 0, 21, -76, 0, 0, 0, 0, 19, -83, 0, 0, 0, 0, 23, -84, 0, 0, 0, 0, 36, -82, 0, 0, 0, 0, 35, -79, 0, 0, 0, 0, 28, -81, 0, 0, 0, 0, 23, -81, 0, 0, 0, 0, 5, -88, 0, 0, 0, 0, 2, -90, 0, 0, 0, 0, -6, -88, 0, 0, 0, 0, -10, -91, 0, 0, 0, 0, -12, -91, 0, 0, 0, 0, -13, -89, 0, 0, 0, 0, -13, -85, 0, 0, 0, 0, -15, -85, 0, 0, 0, 0, -16, -88, 0, 0, 0, 0, -19, -90, 0, 0, 0, 0, -20, -85)
}
point_count = 30
[node name="Spray_Profile" type="Node" node_paths=PackedStringArray("Spray_Path")]
script = ExtResource("1_60v4d")
Spray_Noise = SubResource("FastNoiseLite_4msk4")
Spray_Path = NodePath("Path2D")
Path_Spray_Multiplier = 0.58
[node name="Path2D" type="Path2D" parent="."]
curve = SubResource("Curve2D_0vouc")
@@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=3 uid="uid://3mb12hvruajh"]
[ext_resource type="Script" uid="uid://dvcpsronqmkmr" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile.gd" id="1_35g6m"]
[sub_resource type="FastNoiseLite" id="FastNoiseLite_4msk4"]
seed = 145
frequency = 0.307
cellular_jitter = -1.115
cellular_return_type = 0
domain_warp_type = 1
domain_warp_amplitude = 30.075
domain_warp_frequency = -0.835
domain_warp_fractal_type = 2
domain_warp_fractal_octaves = 1
[node name="Spray_Profile" type="Node"]
script = ExtResource("1_35g6m")
Spray_Noise = SubResource("FastNoiseLite_4msk4")
Random_Spray_Multiplier = 2.371
@@ -0,0 +1,20 @@
[gd_scene load_steps=3 format=3 uid="uid://ceuaamuau8pl8"]
[ext_resource type="Script" uid="uid://dvcpsronqmkmr" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile.gd" id="1_hmoot"]
[sub_resource type="FastNoiseLite" id="FastNoiseLite_4msk4"]
seed = 145
frequency = 0.307
cellular_jitter = -1.115
cellular_return_type = 0
domain_warp_type = 1
domain_warp_amplitude = 30.075
domain_warp_frequency = -0.835
domain_warp_fractal_type = 2
domain_warp_fractal_octaves = 1
[node name="Spray_Profile" type="Node"]
script = ExtResource("1_hmoot")
Spray_Noise = SubResource("FastNoiseLite_4msk4")
Random_Spray_Multiplier = 10.0
Updwards_Lock = true
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,65 @@
[gd_scene load_steps=5 format=4 uid="uid://dxlkgbr1p580"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_bix2d"]
radius = 0.099605
height = 0.419677
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_x0fj0"]
resource_name = "yellow"
vertex_color_use_as_albedo = true
albedo_color = Color(0.0627451, 0.639216, 0.576471, 1)
metallic = 1.0
emission_enabled = true
emission = Color(0, 0.960784, 0.960784, 1)
emission_energy_multiplier = 1.78
[sub_resource type="ArrayMesh" id="ArrayMesh_ly6dy"]
_surfaces = [{
"aabb": AABB(-0.01, -0.025, -0.01, 0.02, 0.05, 0.02),
"format": 34359742465,
"index_count": 276,
"index_data": PackedByteArray("AgAAAAEAAQADAAIAAQAEAAMABAAFAAMABAAGAAUABgAHAAUABgAIAAcACAAJAAcACAAKAAkACgALAAkACgAMAAsADAANAAsADAAOAA0ADgAPAA0ADwAQAA0ADwARABAADwASABEAEgATABEAEgAUABMAFAAVABMAFAAWABUAFgAXABUAGQAYAAYABgAEABkAFgAUABoAGgAbABYAHAASAA8ADwAdABwAHgAFAAcABwAfAB4AIAAOAAwADAAhACAAEQATACIAIgAjABEAIQAMAAoACgAkACEAJwAlACYAJgAZACcAJgAoABkAKAAYABkAKAAeABgAHgApABgAHgAfACkAHwAkACkAHwAqACQAKgAhACQAKgArACEAKwAgACEAKwAsACAALAAdACAALAAtAB0ALQAcAB0ALQAjABwAIwAaABwAIwAiABoAIgAbABoAIgAuABsALgAvABsAKwALAA0ADQAsACsALAANABAAEAAtACwAHgAoAAMAAwAFAB4AJgAlAAAAAAACACYAJAAKAAgACAApACQAHwAHAAkACQAqAB8AJQAnAAEAAQAAACUALQAQABEAEQAjAC0AFAASABwAHAAaABQAFwAWABsAGwAvABcAEwAVAC4ALgAiABMAHQAPAA4ADgAgAB0AFQAXAC8ALwAuABUAJwAZAAQABAABACcAKQAIAAYABgAYACkAKAAmAAIAAgADACgAKgAJAAsACwArACoA"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 48,
"vertex_data": PackedByteArray("CtcjvM3MzLwAAACA3kEevM3MzLyhnik73kEevM3MzLyhnim7vOMNvM3MzLwK16O7vOMNvM3MzLwK16M7a7Tnu83MzLxrtOe7a7Tnu83MzLxrtOc7Cteju83MzLy84w28Cteju83MzLy84w08oZ4pu83MzLzeQR68oZ4pu83MzLzeQR48AAAAAM3MzLwK1yO8AAAAAM3MzLwK1yM8oZ4pO83MzLzeQR68oZ4pO83MzLzeQR48CtejO83MzLy84w08CtejO83MzLy84w28a7TnO83MzLxrtOe7a7TnO83MzLxrtOc7vOMNPM3MzLwK16O7vOMNPM3MzLwK16M73kEePM3MzLyhnim73kEePM3MzLyhnik7CtcjPM3MzLwAAACAa7Tnu83MzDxrtOc7vOMNvM3MzDwK16M7vOMNPM3MzDwK16M73kEePM3MzDyhnik7a7TnO83MzDxrtOc7CtejO83MzDy84w08a7Tnu83MzDxrtOe7Cteju83MzDy84w28oZ4pO83MzDzeQR48AAAAAM3MzDwK1yM8vOMNPM3MzDwK16O7a7TnO83MzDxrtOe7oZ4pu83MzDzeQR48CtcjvM3MzDwAAACA3kEevM3MzDyhnim73kEevM3MzDyhnik7vOMNvM3MzDwK16O7Cteju83MzDy84w08oZ4pu83MzDzeQR68AAAAAM3MzDwK1yO8oZ4pO83MzDzeQR68CtejO83MzDy84w283kEePM3MzDyhnim7CtcjPM3MzDwAAACA")
}]
blend_shape_mode = 0
[sub_resource type="ArrayMesh" id="ArrayMesh_sqgwv"]
resource_name = "foamBulletA_Mesh foamBulletA"
_surfaces = [{
"aabb": AABB(-0.01, -0.025, -0.01, 0.02, 0.05, 0.02),
"attribute_data": PackedByteArray("JpPJPgAAgD/RtMI++AqNP9G0wj4Q6mU/o5GuPjabTT+jka4+ZTKZP/CIjj6Iuzg/8IiOPjyioz8mk0k+LrcoPyaTST5ppKs/g6/QPZilHj+Dr9A9NK2wPwAAAABtNhs/AAAAAMpksj+Dr9C9mKUeP4Ov0L00rbA/JpNJvmmkqz8mk0m+LrcoP/CIjr6Iuzg/8IiOvjyioz+jka6+NptNP6ORrr5lMpk/0bTCvhDqZT/RtMK++AqNPyaTyb4AAIA/gHxSPQACgTyAfFI9+Pv9P4B8Ur0AAoE8gHxSvfj7/T+AfFK9+Pv9P4B8Ur0AAoE8gHxSPfj7/T+AfFI9AAKBPIB8Uj34+/0/gHxSvfj7/T+AfFI9AAKBPIB8Ur0AAoE8gHxSPfj7/T+AfFK9+Pv9P4B8Uj0AAoE8gHxSvQACgTyAfFI9+Pv9P4B8Ur34+/0/gHxSPQACgTyAfFK9AAKBPIB8Ur34+/0/gHxSvQACgTyAfFI9+Pv9P4B8Uj0AAoE8gHxSPfj7/T+AfFK9+Pv9P4B8Uj0AAoE8gHxSvQACgTwmk8m+AACAP9G0wr4Q6mU/0bTCvvgKjT+jka6+ZTKZP6ORrr42m00/8IiOvjyioz/wiI6+iLs4PyaTSb5ppKs/JpNJvi63KD+Dr9C9NK2wP4Ov0L2YpR4/AAAAAMpksj8AAAAAbTYbP4Ov0D00rbA/g6/QPZilHj8mk0k+aaSrPyaTST4utyg/8IiOPjyioz/wiI4+iLs4P6ORrj5lMpk/o5GuPjabTT/RtMI++AqNP9G0wj4Q6mU/JpPJPgAAgD+AfFI9+Pv9P4B8Ur34+/0/gHxSPQACgTyAfFK9AAKBPIB8Uj34+/0/gHxSvfj7/T+AfFI9AAKBPIB8Ur0AAoE8gHxSPQACgTyAfFI9+Pv9P4B8Ur0AAoE8gHxSvfj7/T+AfFI9AAKBPIB8Uj34+/0/gHxSvQACgTyAfFK9+Pv9P4B8Uj34+/0/gHxSvfj7/T+AfFI9AAKBPIB8Ur0AAoE8gHxSPfj7/T+AfFK9+Pv9P4B8Uj0AAoE8gHxSvQACgTyAfFI9AAKBPIB8Uj34+/0/gHxSvQACgTyAfFK9+Pv9P4B8Uj34+/0/gHxSvfj7/T+AfFI9AAKBPIB8Ur0AAoE8gHxSvfj7/T+AfFK9AAKBPIB8Uj34+/0/gHxSPQACgTyAfFK9+Pv9P4B8Ur0AAoE8gHxSPfj7/T+AfFI9AAKBPIB8Ur34+/0/gHxSvQACgTyAfFI9+Pv9P4B8Uj0AAoE8gHxSPfj7/T+AfFK9+Pv9P4B8Uj0AAoE8gHxSvQACgTyAfFK9+Pv9P4B8Ur0AAoE8gHxSPfj7/T+AfFI9AAKBPIB8Uj0AAoE8gHxSPfj7/T+AfFK9AAKBPIB8Ur34+/0/gHxSPfj7/T+AfFK9+Pv9P4B8Uj0AAoE8gHxSvQACgTyAfFI9AAKBPIB8Uj34+/0/gHxSvQACgTyAfFK9+Pv9P4B8Uj34+/0/gHxSvfj7/T+AfFI9AAKBPIB8Ur0AAoE8"),
"format": 34359742487,
"index_count": 276,
"index_data": PackedByteArray("AgAAAAEAAQADAAIAAQAEAAMABAAFAAMABAAGAAUABgAHAAUABgAIAAcACAAJAAcACAAKAAkACgALAAkACgAMAAsADAANAAsADAAOAA0ADgAPAA0ADwAQAA0ADwARABAADwASABEAEgATABEAEgAUABMAFAAVABMAFAAWABUAFgAXABUAGgAYABkAGQAbABoAHgAcAB0AHQAfAB4AIgAgACEAIQAjACIAJgAkACUAJQAnACYAKgAoACkAKQArACoALgAsAC0ALQAvAC4AMgAwADEAMQAzADIANgA0ADUANQA3ADYANQA4ADcAOAA5ADcAOAA6ADkAOgA7ADkAOgA8ADsAPAA9ADsAPAA+AD0APgA/AD0APgBAAD8AQABBAD8AQABCAEEAQgBDAEEAQgBEAEMARABFAEMARABGAEUARgBHAEUARgBIAEcASABJAEcASABKAEkASgBLAEkATgBMAE0ATQBPAE4AUgBQAFEAUQBTAFIAVgBUAFUAVQBXAFYAWgBYAFkAWQBbAFoAXgBcAF0AXQBfAF4AYgBgAGEAYQBjAGIAZgBkAGUAZQBnAGYAagBoAGkAaQBrAGoAbgBsAG0AbQBvAG4AcgBwAHEAcQBzAHIAdgB0AHUAdQB3AHYAegB4AHkAeQB7AHoAfgB8AH0AfQB/AH4AggCAAIEAgQCDAIIAhgCEAIUAhQCHAIYAigCIAIkAiQCLAIoAjgCMAI0AjQCPAI4A"),
"material": SubResource("StandardMaterial3D_x0fj0"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 144,
"vertex_data": PackedByteArray("CtcjvM3MzLwAAACA3kEevM3MzLyhnik73kEevM3MzLyhnim7vOMNvM3MzLwK16O7vOMNvM3MzLwK16M7a7Tnu83MzLxrtOe7a7Tnu83MzLxrtOc7Cteju83MzLy84w28Cteju83MzLy84w08oZ4pu83MzLzeQR68oZ4pu83MzLzeQR48AAAAAM3MzLwK1yO8AAAAAM3MzLwK1yM8oZ4pO83MzLzeQR68oZ4pO83MzLzeQR48CtejO83MzLy84w08CtejO83MzLy84w28a7TnO83MzLxrtOe7a7TnO83MzLxrtOc7vOMNPM3MzLwK16O7vOMNPM3MzLwK16M73kEePM3MzLyhnim73kEePM3MzLyhnik7CtcjPM3MzLwAAACAa7Tnu83MzDxrtOc7a7Tnu83MzLxrtOc7vOMNvM3MzDwK16M7vOMNvM3MzLwK16M7vOMNPM3MzLwK16M7vOMNPM3MzDwK16M73kEePM3MzLyhnik73kEePM3MzDyhnik7a7TnO83MzLxrtOc7CtejO83MzLy84w08a7TnO83MzDxrtOc7CtejO83MzDy84w08a7Tnu83MzLxrtOe7Cteju83MzLy84w28a7Tnu83MzDxrtOe7Cteju83MzDy84w28oZ4pO83MzLzeQR48AAAAAM3MzLwK1yM8oZ4pO83MzDzeQR48AAAAAM3MzDwK1yM8vOMNPM3MzLwK16O7vOMNPM3MzDwK16O7a7TnO83MzLxrtOe7a7TnO83MzDxrtOe7AAAAAM3MzLwK1yM8oZ4pu83MzLzeQR48AAAAAM3MzDwK1yM8oZ4pu83MzDzeQR48CtcjvM3MzDwAAACA3kEevM3MzDyhnim73kEevM3MzDyhnik7vOMNvM3MzDwK16M7vOMNvM3MzDwK16O7a7Tnu83MzDxrtOc7a7Tnu83MzDxrtOe7Cteju83MzDy84w08Cteju83MzDy84w28oZ4pu83MzDzeQR48oZ4pu83MzDzeQR68AAAAAM3MzDwK1yM8AAAAAM3MzDwK1yO8oZ4pO83MzDzeQR48oZ4pO83MzDzeQR68CtejO83MzDy84w08CtejO83MzDy84w28a7TnO83MzDxrtOc7a7TnO83MzDxrtOe7vOMNPM3MzDwK16M7vOMNPM3MzDwK16O73kEePM3MzDyhnik73kEePM3MzDyhnim7CtcjPM3MzDwAAACAAAAAAM3MzLwK1yO8oZ4pO83MzLzeQR68AAAAAM3MzDwK1yO8oZ4pO83MzDzeQR68oZ4pO83MzLzeQR68CtejO83MzLy84w28oZ4pO83MzDzeQR68CtejO83MzDy84w28vOMNvM3MzDwK16O7vOMNvM3MzLwK16O7a7Tnu83MzDxrtOe7a7Tnu83MzLxrtOe7CtcjvM3MzDwAAACACtcjvM3MzLwAAACA3kEevM3MzDyhnim73kEevM3MzLyhnim7oZ4pu83MzLzeQR48Cteju83MzLy84w08oZ4pu83MzDzeQR48Cteju83MzDy84w08Cteju83MzLy84w28oZ4pu83MzLzeQR68Cteju83MzDy84w28oZ4pu83MzDzeQR683kEevM3MzDyhnik73kEevM3MzLyhnik7CtcjvM3MzDwAAACACtcjvM3MzLwAAACACtejO83MzLy84w28a7TnO83MzLxrtOe7CtejO83MzDy84w28a7TnO83MzDxrtOe7a7TnO83MzLxrtOc7a7TnO83MzDxrtOc7vOMNPM3MzLwK16M7vOMNPM3MzDwK16M73kEePM3MzLyhnik73kEePM3MzDyhnik7CtcjPM3MzLwAAACACtcjPM3MzDwAAACA3kEePM3MzLyhnim73kEePM3MzDyhnim7vOMNPM3MzLwK16O7vOMNPM3MzDwK16O7CtejO83MzLy84w08oZ4pO83MzLzeQR48CtejO83MzDy84w08oZ4pO83MzDzeQR48CtcjPM3MzLwAAACACtcjPM3MzDwAAACA3kEePM3MzLyhnim73kEePM3MzDyhnim7vOMNvM3MzDwK16M7vOMNvM3MzLwK16M73kEevM3MzDyhnik73kEevM3MzLyhnik7Cteju83MzLy84w08a7Tnu83MzLxrtOc7Cteju83MzDy84w08a7Tnu83MzDxrtOc73kEevM3MzDyhnim73kEevM3MzLyhnim7vOMNvM3MzDwK16O7vOMNvM3MzLwK16O7oZ4pu83MzLzeQR68AAAAAM3MzLwK1yO8oZ4pu83MzDzeQR68AAAAAM3MzDwK1yO8/38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//38AAAAA/7//fwAAAAD/v/9/AAAAAP+//z//f/+//7//P/9//7//v9ku/3/Zrv+/2S7/f9mu/78l0f9///+S6CXR/3///5Lo8uT/f///ePLy5P9///948v+//3/////f2a7/f///bNf/v/9/////39mu/3///2zXAAD/v/8//78AACXR2S7/vwAA/7//P/+/AAAl0dku/78Mm/9///+Fzf9//3////+/DJv/f///hc3/f/9/////v///2a4AAJLo///ZrgAAkuj///+/AAD/3////78AAP/f/3//f////7/yZP9/8uT/v/9//3////+/8mT/f/Lk/7//f///////v/9////+//+//3///////7//f///////v/9///////+//3///////7//f////v//v/9////+//+//3///////7//f///////v/9///////+//3///////7//f///////v/9////+//+//3////7//7//f///////v/9////+//+//3////7//7//f///////v/9///////+//3////7//7//f////v//v/9///////+//3///////7//////AAD/v///8uQAAIXN/////wAA/7////LkAACFzf//8uQAAIXN//8l0QAAbNf///LkAACFzf//JdEAAGzXAADZriVR/78AANmuJVH/vwAA/7//P/+/AAD/v/8//78AAP9//3//vwAA/3//f/+/AAAMm/Jk/78AAAyb8mT/v/Jk/3/y5P+/JVH/fyXR/7/yZP9/8uT/vyVR/38l0f+/AAAl0dku/78AAPLkDBv/vwAAJdHZLv+/AADy5Awb/78MG/9/DJv/vwwb/38Mm/+/AAD/f/9//78AAP9//3//v///JdEAAGzX////vwAA/9///yXRAABs1////78AAP/f/7//f////9//v/9/////3yXR/3///5LoJdH/f///kujy5P9///948vLk/3///3jy////f/////////9/////////DJsAAHjy//8MmwAAePL//9muAACS6P//2a4AAJLo2a7/f///bNcMm/9///+Fzdmu/3///2zXDJv/f///hc3///9//////////3////////8MmwAAePL//wybAAB48tku/3/Zrv+/2S7/f9mu/78MG/9/DJv/vwwb/38Mm/+/JVH/fyXR/7//P/9//7//vyVR/38l0f+//z//f/+//78AAAyb8mT/vwAADJvyZP+/AADZriVR/78AANmuJVH/vwAA8uQMG/+//////wAA/78AAPLkDBv/v/////8AAP+/")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_ly6dy")
[node name="bullet" type="RigidBody3D"]
collision_layer = 16
collision_mask = 37
gravity_scale = 0.0
continuous_cd = true
contact_monitor = true
max_contacts_reported = 5
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0)
shape = SubResource("CapsuleShape3D_bix2d")
[node name="foamBulletA2" type="MeshInstance3D" parent="."]
transform = Transform3D(5, 0, 0, 0, -2.18557e-07, -5, 0, 5, -2.18557e-07, 0, 0, 0)
mesh = SubResource("ArrayMesh_sqgwv")
skeleton = NodePath("")
[connection signal="body_entered" from="." to="." method="_on_body_entered"]
@@ -0,0 +1,15 @@
[gd_scene load_steps=3 format=3 uid="uid://dguqnsdowqt3w"]
[ext_resource type="Texture2D" uid="uid://clfvflhvcccy1" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/debug_sprite.png" id="1_smdbx"]
[ext_resource type="Script" uid="uid://dkt6w5wvebi72" path="res://client/template/scripts/Weapon_State_Machine/hit_debug.gd" id="2_gvqnn"]
[node name="Node3D" type="Sprite3D"]
transform = Transform3D(0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0)
billboard = 2
texture = ExtResource("1_smdbx")
script = ExtResource("2_gvqnn")
[node name="Timer" type="Timer" parent="."]
autostart = true
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
@@ -0,0 +1,17 @@
[gd_scene load_steps=3 format=3 uid="uid://bj15ogfi1535l"]
[ext_resource type="Script" uid="uid://dkt6w5wvebi72" path="res://client/template/scripts/Weapon_State_Machine/hit_debug.gd" id="2_w2l74"]
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_kr237"]
load_path = "res://.godot/imported/crosshair046.png-adf558f9d87ef20104f2ce22346ed1b7.s3tc.ctex"
[node name="Node3D" type="Sprite3D"]
modulate = Color(0, 0, 1, 1)
billboard = 2
texture = SubResource("CompressedTexture2D_kr237")
script = ExtResource("2_w2l74")
[node name="Timer" type="Timer" parent="."]
autostart = true
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
@@ -0,0 +1,87 @@
[gd_scene load_steps=6 format=4 uid="uid://cqhlfy6ccomm3"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_030fb"]
resource_name = "yellow"
albedo_color = Color(0.996078, 0, 0.219608, 1)
metallic = 1.0
emission_enabled = true
emission = Color(1, 0, 0, 1)
emission_energy_multiplier = 19.35
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_urtjv"]
resource_name = "pink"
albedo_color = Color(0.996078, 0.219608, 0.411765, 1)
metallic = 1.0
[sub_resource type="ArrayMesh" id="ArrayMesh_iqbgl"]
_surfaces = [{
"aabb": AABB(-0.01, -0.03, -0.01, 0.02, 0.06, 0.02),
"format": 34896613377,
"index_count": 66,
"index_data": PackedByteArray("AgAAAAEAAQADAAIAAQAAAAQABAAFAAEABQAEAAYABgAHAAUACAAHAAYABgAJAAgADAAKAAsACwANAAwADgAJAAYABgAKAA4ABgAEAAoABAALAAoABAAAAAsAAAACAAsADwAIAAkACQAOAA8ADwAOAAoACgAMAA8ACwACAAMAAwANAAsA"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 24,
"vertex_data": PackedByteArray("/98AAP/fAAD/31TV/98AAP//AAD/fwAA//9U1f9/AAD/fwAA//8AAP9/VNX//wAA/x8AAP/fAAD/H1TV/98AAAAAVNX/fwAAAAAAAP9/AAD/fwAAAAAAAP/fAAD/HwAA/39U1QAAAAD/31TV/x8AAP8fAAD/HwAA/x9U1f8fAAD/H////38AAP83////NwAA/zf////HAAD/f////98AAP9/////HwAA/8f////HAAD/x////zcAAP/f////fwAA")
}, {
"aabb": AABB(-0.01, -0.03, -0.01, 0.02, 0.06, 0.02),
"format": 34896613377,
"index_count": 66,
"index_data": PackedByteArray("EgAQABEAEQATABIAEQAUABMAFAAVABMAFAAWABUAFgAXABUAFAAMAA0ADQAWABQAEQAPAAwADAAUABEAEwAFAAcABwASABMAEQAQAAgACAAPABEAEgAHAAgACAAQABIAFQABAAUABQATABUAAwABABUAFQAXAAMADQADABcAFwAWAA0A"),
"name": "pink",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 24,
"vertex_data": PackedByteArray("/98AAP/fAAD/31TV/98AAP//AAD/fwAA//9U1f9/AAD/fwAA//8AAP9/VNX//wAA/x8AAP/fAAD/H1TV/98AAAAAVNX/fwAAAAAAAP9/AAD/fwAAAAAAAP/fAAD/HwAA/39U1QAAAAD/31TV/x8AAP8fAAD/HwAA/x9U1f8fAAD/H////38AAP83////NwAA/zf////HAAD/f////98AAP9/////HwAA/8f////HAAD/x////zcAAP/f////fwAA")
}]
blend_shape_mode = 0
[sub_resource type="ArrayMesh" id="ArrayMesh_mt61o"]
resource_name = "foamBulletB-low_Mesh foamBulletB-low"
_surfaces = [{
"aabb": AABB(-0.01, -0.03, -0.01, 0.02, 0.06, 0.02),
"attribute_data": PackedByteArray("SEP//0hDeYx5qP//eah5jLa8//+FV///trx5jIVXeYx5qP//SEP//3moeYxIQ3mMtrx5jLa8//+FV3mMhVf//3mo//9IQ///eah5jEhDeYz//666/98CzP/fWqn/f5Sj/3/J0f8fWqn/HwLMAACuunmoeYx5qP//SEN5jEhD//+2vP//hVf//7a8eYyFV3mMhVf//4VXeYy2vP//trx5jP8frrr/N6+t/zetx/9/Asz/f1qp/8etx//Hr63/3666eajHkkhDx5Jangh7dlIIe7a8x5KFV8eSiK0Ie6RhCHt5qMeSSEPHklqeCHt2Ugh7Wp4Ie3mox5J2Ugh7SEPHkra8x5KFV8eSiK0Ie6RhCHu2vMeShVfHkoitCHukYQh7SEPHknZSCHt5qMeSWp4Ie4VXx5KkYQh7trzHkoitCHs="),
"format": 34896613399,
"index_count": 66,
"index_data": PackedByteArray("AgAAAAEAAQADAAIABgAEAAUABQAHAAYACgAIAAkACQALAAoADgAMAA0ADQAPAA4AEgAQABEAEQATABIAFgAUABUAFQAXABYAFQAYABcAGAAZABcAGAAaABkAGgAbABkAHgAcAB0AHQAfAB4AIgAgACEAIQAjACIAJgAkACUAJQAnACYA"),
"material": SubResource("StandardMaterial3D_030fb"),
"name": "yellow",
"primitive": 3,
"uv_scale": Vector4(0.787402, 4.3622, 0, 0),
"vertex_count": 80,
"vertex_data": PackedByteArray("/98AAP/f/5//31TV/9//n///AAD/f/+///9U1f9//7//3wAA/9//n/9/AAD//wCA/99U1f/f/5//f1TV//8AgP9/AAD//wCA/x8AAP/f/5//f1TV//8AgP8fVNX/3/+f/x9U1f/f/5//HwAA/9//nwAAVNX/f/+/AAAAAP9//7//fwAAAAD////fAAD/H//f/39U1QAA////31TV/x//3wAAAAD/f////x8AAP/f////HwAA/x////9/AAAAAP///38AAP//////3wAA/x/////fAAD/3/////8AAP9///8AAFTV/3//vwAAAAD/f/+//x9U1f8f/9//HwAA/x//3/8fAAD/H//f/38AAAAA////H1TV/x//3/9/VNUAAP////8AAP9//7///1TV/3//v//fAAD/H//f/99U1f8f/9//H////39U1f83////N1TV/zf////HVNX/f////99U1f9/////H1TV/8f////HVNX/x////zdU1f/f////f1TV/39U1QAA8vj/31TV/x/C6P9/////H/L4/8f///83wuj/H1TV/x/C6P9/VNUAAPL4/zf///83wuj/f////x/y+P9/VNX//5rA/x9U1f/feMb/f////9+awP83////x3jG/x////9/VNUAAFTV/39U1f83////N8Lo/x9U1f8fwuj/H1TV/994xgAAVNX/f1TV/zf////HeMb/H////39U1f/fVNX/33jG/39U1f//msD/x////8d4xv9/////35rA/99U1f/feMb/x////8d4xv//VNX/f1TV/9////9/VNX//1TV/39U1f/f////f1TV/99U1f8fwuj/x////zfC6P9/AAD/fwAA/38AAP9/AAD/fwAA/3////9/AAD/f////3////9/////f////3////9/////f////3////9/////f////38AAP9/////fwAA/3//P/9//z//f/8//3//P/9//z//f/8//3//P/9//z//f////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD///9/////f////3////9/////f////3////9/////f////3/oougi////f+ii6CLoohbd////f+iiFt3///9/////f+iiFt3///9/6KIW3ZmZZeaZmWXm6KIW3eiiFt3oohbdmZll5uiiFt2ZmWXm6KLoIv///3/oougi////f+ii6CLoougimZmZGZmZmRmZmZkZmZmZGeii6CLoougi")
}, {
"aabb": AABB(-0.01, -0.03, -0.01, 0.02, 0.06, 0.02),
"attribute_data": PackedByteArray("SEP//0hDeYx5qP//eah5jLa8//+FV///trx5jIVXeYx5qP//SEP//3moeYxIQ3mMtrx5jLa8//+FV3mMhVf//3mo//9IQ///eah5jEhDeYz//666/98CzP/fWqn/f5Sj/3/J0f8fWqn/HwLMAACuunmoeYx5qP//SEN5jEhD//+2vP//hVf//7a8eYyFV3mMhVf//4VXeYy2vP//trx5jP8frrr/N6+t/zetx/9/Asz/f1qp/8etx//Hr63/3666eajHkkhDx5Jangh7dlIIe7a8x5KFV8eSiK0Ie6RhCHt5qMeSSEPHklqeCHt2Ugh7Wp4Ie3mox5J2Ugh7SEPHkra8x5KFV8eSiK0Ie6RhCHu2vMeShVfHkoitCHukYQh7SEPHknZSCHt5qMeSWp4Ie4VXx5KkYQh7trzHkoitCHs="),
"format": 34896613399,
"index_count": 66,
"index_data": PackedByteArray("KgAoACkAKQArACoAKQAsACsALAAtACsALAAuAC0ALgAvAC0AMgAwADEAMQAzADIANgA0ADUANQA3ADYAOgA4ADkAOQA7ADoAPgA8AD0APQA/AD4AQgBAAEEAQQBDAEIARgBEAEUARQBHAEYASgBIAEkASQBLAEoATgBMAE0ATQBPAE4A"),
"material": SubResource("StandardMaterial3D_urtjv"),
"name": "pink",
"primitive": 3,
"uv_scale": Vector4(0.787402, 4.3622, 0, 0),
"vertex_count": 80,
"vertex_data": PackedByteArray("/98AAP/f/MX/31TV/9/8xf//AAD/f1TV//9U1f9/VNX/3wAA/9/8xf9/AAD//////99U1f/f/MX/f1TV//////9/AAD//////x8AAP/f/MX/f1TV//////8fVNX/3/zF/x9U1f/f/MX/HwAA/9/8xQAAVNX/f1TVAAAAAP9/VNX/fwAAAAD////fAAD/H6rp/39U1QAA////31TV/x+q6QAAAAD/f1TV/x8AAP/fVNX/HwAA/x9U1f9/AAAAAFTV/38AAP//VNX/3wAA/x9U1f/fAAD/31TV//8AAP9/VNUAAFTV/39U1QAAAAD/f1TV/x9U1f8fqun/HwAA/x+q6f8fAAD/H6rp/38AAAAA////H1TV/x+q6f9/VNUAAP////8AAP9/VNX//1TV/39U1f/fAAD/H6rp/99U1f8fqun/H////3//v/83////N/+//zf////H/7//f////9//v/9/////H/+//8f////H/7//x////zf/v//f////f/+//39U1QAAmP//31TV/x8E4f9/////H5j//8f///83BOH/H1TV/x8E4f9/VNUAAJj//zf///83BOH/f////x+Y//9/VNX//4CK/x9U1f/fWKH/f////9+Aiv83////x1ih/x////9/EcEAAFTV/38Rwf83////N63f/x9U1f8frd//H1TV/9+cogAAVNX/f0XA/zf////HnKL/H////39FwP/fVNX/31ih/39U1f//gIr/x////8dYof9/////34CK/99U1f/fnKL/x////8ecov//VNX/f0XA/9////9/RcD//1TV/38Rwf/f////fxHB/99U1f8frd//x////zet3/9/AAD/fwAA/38AAP9/AAD/fwAA/3//f/9/AAD/f/9//3//f/9/////f/9//3////9/////f////3////9/////f////38AAP9/////fwAAAAD/fwAA/38AAP9/AAD/fwAA/38AAP9/AAD/fwAA/3//f////3////9/////f////3////9/////f////3////9/AAD/fwAA/38AAP9/AAD+//9/////f/7//3////9/////f////3/+//9//v//f/uRcATFbRMB+5FwBMVtEwHsfsXtcIQD7ux+xe1whAPujd3Ggz6l8tyN3caDPqXy3HaIzuZ2iM7mYIyz6GCMs+iklPLT2pCb5qSU8tPakJvmDKM/JTj8jV0Moz8lOPyNXQuspBQLrKQUY5nbEGOZ2xAvmXcIL5l3CEqXYAxKl2AM")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_iqbgl")
[sub_resource type="CylinderShape3D" id="CylinderShape3D_r5ds2"]
height = 0.202453
radius = 0.0673677
[node name="RigidBody3D" type="RigidBody3D"]
gravity_scale = 0.0
continuous_cd = true
contact_monitor = true
max_contacts_reported = 5
[node name="foamBulletB-low" type="MeshInstance3D" parent="."]
transform = Transform3D(10, 0, 0, 0, -4.37114e-07, 10, 0, -10, -4.37114e-07, 0, 0, 0)
mesh = SubResource("ArrayMesh_mt61o")
skeleton = NodePath("")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, 0)
shape = SubResource("CylinderShape3D_r5ds2")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
extends CanvasLayer
@onready var current_weapon_label = $debug_hud/HBoxContainer/CurrentWeapon
@onready var current_ammo_label = $debug_hud/HBoxContainer2/CurrentAmmo
@onready var current_weapon_stack = $debug_hud/HBoxContainer3/WeaponStack
@onready var hit_sight = $HitSight
@onready var hit_sight_timer = $HitSight/HitSightTimer
@onready var overLay = $Overlay
func _on_weapons_manager_update_weapon_stack(WeaponStack):
current_weapon_stack.text = ""
for i in WeaponStack:
current_weapon_stack.text += "\n"+i.weapon.weapon_name
func _on_weapons_manager_update_ammo(Ammo):
current_ammo_label.set_text(str(Ammo[0])+" / "+str(Ammo[1]))
func _on_weapons_manager_weapon_changed(WeaponName):
current_weapon_label.set_text(WeaponName)
func _on_hit_sight_timer_timeout():
hit_sight.set_visible(false)
func _on_weapons_manager_add_signal_to_hud(_projectile):
_projectile.Hit_Successfull.connect(_on_weapons_manager_hit_successfull)
func _on_weapons_manager_hit_successfull():
hit_sight.set_visible(true)
hit_sight_timer.start()
func load_over_lay_texture(Active:bool, txtr: Texture2D = null):
overLay.set_texture(txtr)
overLay.set_visible(Active)
func _on_weapons_manager_connect_weapon_to_hud(_weapon_resouce: WeaponResource):
_weapon_resouce.update_overlay.connect(load_over_lay_texture)
@@ -0,0 +1 @@
uid://cc3t6vk7qpwey
@@ -0,0 +1,266 @@
extends CharacterBody3D
@onready var camera = %Camera
@export var subviewport_camera: Camera3D
@export var main_camera:Camera3D
@export var animation_tree: AnimationTree
var camera_rotation: Vector2 = Vector2(0.0,0.0)
var mouse_sensitivity = 0.001
var crouched: bool = false
var crouch_blocked: bool = false
@export_category("Crouch Parametres")
@export var enable_crouch: bool = true
@export var crouch_toggle: bool = false
@export var crouch_collision: ShapeCast3D
@export_range(0.0,3.0) var crouch_speed_reduction = 2.0
@export_range(0.0,0.50) var crouch_blend_speed = .2
enum {GROUND_CROUCH = -1, STANDING = 0, AIR_CROUCH = 1}
@export_category("Lean Parametres")
@export var enable_lean: bool = true
@export_range(0.0,1.0) var lean_speed: float = .2
@export var right_lean_collision: ShapeCast3D
@export var left_lean_collision: ShapeCast3D
var lean_tween
enum {LEFT = 1, CENTRE = 0, RIGHT = -1}
@export_category("speed Parameters")
@export var enable_sprint: bool = true
@export var sprint_timer: Timer
@export var sprint_cooldown_time: float = 3.0
@export var sprint_time: float = 1.0
@export var sprint_replenish_rate: float = 0.30
@export var acceleration: float = 120
@export_range(0.01,1.0) var air_acceleration_modifier: float = 0.1
var sprint_on_cooldown: bool = false
var sprint_time_remaining: float = sprint_time
@onready var sprint_bar: Range = $CanvasLayer/SprintBar
const NORMAL_speed = 1
@export_range(1.0,3.0) var sprint_speed: float = 2.0
@export_range(0.1,1.0) var walk_speed: float = 0.5
var speed_modifier: float = NORMAL_speed
@export_category("Jump Parameters")
@export var coyote_timer: Timer
@export var jump_peak_time: float = .5
@export var jump_fall_time: float = .5
@export var jump_height: float = 2.0
@export var jump_distance: float = 4.0
@export var coyote_time: float = .1
@export var jump_buffer_time: float = .2
# Get the gravity from the project settings to be synced with RigidBody nodes.
var jump_gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var fall_gravity: float
var jump_velocity: float
var base_speed: float
var _speed: float
var jump_available: bool = true
var jump_buffer: bool = false
func _ready() -> void:
update_camera_rotation()
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
calculate_movement_parameters()
func update_camera_rotation() -> void:
var current_rotation = get_rotation()
camera_rotation.x = current_rotation.y
camera_rotation.y = current_rotation.x
func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"):
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
else:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
if event is InputEventMouseMotion:
var MouseEvent = event.relative * mouse_sensitivity
camera_look(MouseEvent)
if enable_crouch:
if event.is_action_pressed("crouch"):
crouch()
if event.is_action_released("crouch"):
if !crouch_toggle and crouched:
crouch()
if enable_lean:
if Input.is_action_just_released("lean_left") or Input.is_action_just_released("lean_right"):
if !(Input.is_action_pressed("lean_right") or Input.is_action_pressed("lean_left")):
lean(CENTRE)
if Input.is_action_just_pressed("lean_left"):
lean(LEFT)
if Input.is_action_just_pressed("lean_right"):
lean(RIGHT)
if enable_sprint:
if Input.is_action_just_pressed("sprint") and !crouched:
if !sprint_on_cooldown:
speed_modifier = sprint_speed
sprint_timer.start(sprint_time_remaining)
if Input.is_action_just_released("sprint") or Input.is_action_just_released("walk"):
if !(Input.is_action_pressed("walk") or Input.is_action_pressed("sprint")):
speed_modifier = NORMAL_speed
exit_sprint()
if Input.is_action_just_pressed("walk") and !crouched:
speed_modifier = walk_speed
func calculate_movement_parameters() -> void:
jump_gravity = (2*jump_height)/pow(jump_peak_time,2)
fall_gravity = (2*jump_height)/pow(jump_fall_time,2)
jump_velocity = jump_gravity * jump_peak_time
base_speed = jump_distance/(jump_peak_time+jump_fall_time)
_speed = base_speed
func lean(blend_amount: int) -> void:
if is_on_floor():
if lean_tween:
lean_tween.kill()
lean_tween = get_tree().create_tween()
lean_tween.tween_property(animation_tree,"parameters/lean_blend/blend_amount", blend_amount, lean_speed)
func lean_collision() -> void:
animation_tree["parameters/left_collision_blend/blend_amount"] = lerp(
float(animation_tree["parameters/left_collision_blend/blend_amount"]),float(left_lean_collision.is_colliding()),lean_speed
)
animation_tree["parameters/right_collision_blend/blend_amount"] = lerp(
float(animation_tree["parameters/right_collision_blend/blend_amount"]),float(right_lean_collision.is_colliding()),lean_speed
)
func crouch() -> void:
var Blend
if !crouch_collision.is_colliding():
if crouched:
Blend = STANDING
else:
speed_modifier = NORMAL_speed
exit_sprint()
if is_on_floor():
Blend = GROUND_CROUCH
else:
Blend = AIR_CROUCH
var blend_tween = get_tree().create_tween()
blend_tween.tween_property(animation_tree,"parameters/Crouch_Blend/blend_amount",Blend,crouch_blend_speed)
crouched = !crouched
else:
crouch_blocked = true
func camera_look(Movement: Vector2) -> void:
camera_rotation += Movement
transform.basis = Basis()
camera.transform.basis = Basis()
rotate_object_local(Vector3(0,1,0),-camera_rotation.x) # first rotate in Y
camera.rotate_object_local(Vector3(1,0,0), -camera_rotation.y) # then rotate in X
camera_rotation.y = clamp(camera_rotation.y,-1.5,1.2)
func exit_sprint() -> void:
if !sprint_timer.is_stopped():
sprint_time_remaining = sprint_timer.time_left
sprint_timer.stop()
func sprint_replenish(delta) -> void:
var sprint_bar_Value
if !sprint_on_cooldown and (speed_modifier != sprint_speed):
if is_on_floor():
sprint_time_remaining = move_toward(sprint_time_remaining, sprint_time, delta*sprint_replenish_rate)
sprint_bar_Value= (sprint_time_remaining/sprint_time)*100
else:
sprint_bar_Value = (sprint_timer.time_left/sprint_time)*100
sprint_bar.value = sprint_bar_Value
if sprint_bar_Value == 100:
sprint_bar.hide()
else:
sprint_bar.show()
func _process(_delta: float) -> void:
if subviewport_camera:
subviewport_camera.global_transform = main_camera.global_transform
func _physics_process(_delta: float) -> void:
sprint_replenish(_delta)
lean_collision()
if crouched and crouch_blocked:
if !crouch_collision.is_colliding():
crouch_blocked = false
if !Input.is_action_pressed("crouch") and !crouch_toggle:
crouch()
# Add the gravity.
var _acceleration
if not is_on_floor():
_acceleration = acceleration*air_acceleration_modifier
if coyote_timer.is_stopped():
coyote_timer.start(coyote_time)
if velocity.y>0:
velocity.y -= jump_gravity * _delta
else:
velocity.y -= fall_gravity * _delta
else:
_acceleration = acceleration
jump_available = true
coyote_timer.stop()
_speed = (base_speed / max((float(crouched)*crouch_speed_reduction),1)) * speed_modifier
if jump_buffer:
jump()
jump_buffer = false
# Handle Jump.
if Input.is_action_just_pressed("ui_accept"):
if jump_available:
if crouched:
crouch()
else:
lean(CENTRE)
jump()
else:
jump_buffer = true
get_tree().create_timer(jump_buffer_time).timeout.connect(on_jump_buffer_timeout)
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir = Input.get_vector("left", "right", "up", "down")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
velocity.x = move_toward(velocity.x, direction.x * _speed, _acceleration*_delta)
velocity.z = move_toward(velocity.z, direction.z * _speed, _acceleration*_delta)
move_and_slide()
func jump()->void:
velocity.y = jump_velocity
jump_available = false
func _on_sprint_timer_timeout() -> void:
sprint_on_cooldown = true
get_tree().create_timer(sprint_cooldown_time).timeout.connect(_on_sprint_cooldown_timeout)
speed_modifier = NORMAL_speed
sprint_time_remaining = 0
func _on_sprint_cooldown_timeout():
sprint_on_cooldown = false
func _on_coyote_timer_timeout() -> void:
jump_available = false
func on_jump_buffer_timeout()->void:
jump_buffer = false
@@ -0,0 +1 @@
uid://cfdt8t4r8pxel
@@ -0,0 +1,137 @@
extends Node3D
class_name Projectile
signal Hit_Successfull
## Can Be Either A Hit Scan or Rigid Body Projectile. If Rigid body is select a Rigid body must be provided.
@export_enum ("Hitscan","Rigidbody_Projectile","over_ride") var Projectile_Type: String = "Hitscan"
@export var Display_Debug_Decal: bool = true
@export_category("Rigid Body Projectile Properties")
@export var Projectile_Velocity: int
@export var Expirey_Time: int = 10
@export var Rigid_Body_Projectile: PackedScene
@export var pass_through: bool = false
@onready var Debug_Bullet = preload("res://client/template/Spawnable_Objects/hit_debug.tscn")
var damage: float = 0
var Projectiles_Spawned = []
var hit_objects: Array = []
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
get_tree().create_timer(Expirey_Time).timeout.connect(_on_timer_timeout)
func _Set_Projectile(_damage: int = 0,_spread:Vector2 = Vector2.ZERO, _Range: int = 1000, origin_point: Vector3 = Vector3.ZERO):
damage = _damage
Fire_Projectile(_spread,_Range,Rigid_Body_Projectile, origin_point)
func Fire_Projectile(_spread: Vector2 ,_range: int, _proj:PackedScene, origin_point: Vector3):
var Camera_Collision = Camera_Ray_Cast(_spread,_range)
match Projectile_Type:
"Hitscan":
Hit_Scan_Collision(Camera_Collision, damage,origin_point)
"Rigidbody_Projectile":
Launch_Rigid_Body_Projectile(Camera_Collision, _proj,origin_point)
"over_ride":
_over_ride_collision(Camera_Collision, damage)
func _over_ride_collision(_camera_collision:Array, _damage: float) -> void:
pass
func Camera_Ray_Cast(_spread: Vector2 = Vector2.ZERO, _range: float = 1000):
var _Camera = get_viewport().get_camera_3d()
var _Viewport = get_viewport().get_size()
var Ray_Origin = _Camera.project_ray_origin(_Viewport/2)
var Ray_End = (Ray_Origin + _Camera.project_ray_normal((_Viewport/2)+Vector2i(_spread)) *_range)
var New_Intersection:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(Ray_Origin,Ray_End)
New_Intersection.set_collision_mask(0b11101101)
New_Intersection.set_hit_from_inside(false) # In Jolt this is set to true by defualt
var Intersection = get_world_3d().direct_space_state.intersect_ray(New_Intersection)
if not Intersection.is_empty():
var Collision = [Intersection.collider,Intersection.position,Intersection.normal]
return Collision
else:
return [null,Ray_End,null]
func Hit_Scan_Collision(Collision: Array,_damage: float, origin_point: Vector3):
var Point = Collision[1]
if Collision[0]:
Load_Decal(Point, Collision[2])
if Collision[0].is_in_group("Target"):
var Bullet = get_world_3d().direct_space_state
var Bullet_Direction = (Point - origin_point).normalized()
var New_Intersection = PhysicsRayQueryParameters3D.create(origin_point,Point+Bullet_Direction*2)
New_Intersection.set_collision_mask(0b11101101)
New_Intersection.set_hit_from_inside(false)
New_Intersection.set_exclude(hit_objects)
var Bullet_Collision = Bullet.intersect_ray(New_Intersection)
if Bullet_Collision:
Hit_Scan_damage(Bullet_Collision.collider, Bullet_Direction,Bullet_Collision.position,_damage)
if pass_through and check_pass_through(Bullet_Collision.collider, Bullet_Collision.rid):
var pass_through_collision : Array = [Bullet_Collision.collider, Bullet_Collision.position, Bullet_Collision.normal]
var pass_through_damage: float = damage/2
Hit_Scan_Collision(pass_through_collision,pass_through_damage,Bullet_Collision.position)
return
queue_free()
func check_pass_through(collider: Node3D, rid: RID)-> bool:
var valid_pass_though: bool = false
if collider.is_in_group("Pass Through"):
hit_objects.append(rid)
valid_pass_though = true
return valid_pass_though
func Hit_Scan_damage(Collider, Direction, Position, _damage):
if Collider.is_in_group("Target") and Collider.has_method("Hit_Successful"):
Hit_Successfull.emit()
Collider.Hit_Successful(_damage, Direction, Position)
func Load_Decal(_pos,_normal):
if Display_Debug_Decal:
var rd = Debug_Bullet.instantiate()
var world = get_tree().get_root()
world.add_child(rd)
rd.global_translate(_pos+(_normal*.01))
func Launch_Rigid_Body_Projectile(Collision_Data, _projectile, _origin_point):
var _Point = Collision_Data[1]
var _Norm = Collision_Data[2]
var _proj : RigidBody3D = _projectile.instantiate()
_proj.position = _origin_point
var world = get_tree().get_first_node_in_group("World")
world.add_child(_proj)
_proj.look_at(_Point)
Projectiles_Spawned.push_back(_proj)
_proj.body_entered.connect(_on_body_entered.bind(_proj,_Norm))
var _Direction = (_Point - _origin_point).normalized()
_proj.set_linear_velocity(_Direction*Projectile_Velocity)
func _on_body_entered(body, _proj, _norm):
if body.is_in_group("Target") && body.has_method("Hit_Successful"):
body.Hit_Successful(damage)
Hit_Successfull.emit()
Load_Decal(_proj.get_position(),_norm)
_proj.queue_free()
Projectiles_Spawned.erase(_proj)
if Projectiles_Spawned.is_empty():
queue_free()
func _on_timer_timeout():
queue_free()
@@ -0,0 +1 @@
uid://b26eegme16mfw
@@ -0,0 +1,15 @@
extends Projectile
@onready var melee_hitbox: ShapeCast3D = $MeleeHitbox
func _over_ride_collision(_camera_collision:Array, _damage: float):
melee_hitbox.force_shapecast_update()
var colliders = melee_hitbox.get_collision_count()
for c in colliders:
var Target = melee_hitbox.get_collider(c)
if Target.is_in_group("Target") and Target.has_method("Hit_Successful"):
Hit_Successfull.emit()
var Direction = (Target.global_transform.origin - global_transform.origin).normalized()
var Position = melee_hitbox.get_collision_point(c)
Target.Hit_Successful(_damage, Direction, Position)
queue_free()
@@ -0,0 +1 @@
uid://bfn8eu00l4btq
@@ -0,0 +1,23 @@
extends Projectile
@export var explosion: ShapeCast3D
func _on_body_entered(_body, _proj, _norm):
var collision_location: Vector3 = _proj.global_position
explosion.global_position = collision_location
explosion.force_shapecast_update()
var targets = explosion.get_collision_count()
for t in targets:
var damage_target = explosion.get_collider(t)
var collision_point: Vector3 = explosion.get_collision_point(t)
var collision_normal: Vector3 = explosion.get_collision_normal(t)
var hit_scan_array = [damage_target,collision_point,collision_normal]
Hit_Scan_Collision(hit_scan_array,damage,collision_location)
_proj.queue_free()
Projectiles_Spawned.erase(_proj)
if Projectiles_Spawned.is_empty():
queue_free()
@@ -0,0 +1 @@
uid://biivm72fp7af8
@@ -0,0 +1,23 @@
extends Projectile
@onready var shotgun_pattern: Path2D = $shotgun_pattern
@export_range(0.0,20.0) var Randomness = 10.0
@export var Split_damage: bool = false
var Spray_Vector
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
Spray_Vector = shotgun_pattern.get_curve()
return super._ready()
func _Set_Projectile(_damage: int = 0,_spread:Vector2 = Vector2.ZERO, _Range: int = 1000, origin_point: Vector3 = Vector3.ZERO):
randomize()
damage = _damage/(max(Spray_Vector.get_point_count()*float(Split_damage),1))
for point in Spray_Vector.get_point_count():
var SprayPoint:Vector2 = Spray_Vector.get_point_position(point)
SprayPoint.x = SprayPoint.x + randf_range(-Randomness, Randomness)
SprayPoint.y = SprayPoint.y + randf_range(-Randomness, Randomness)
Fire_Projectile(SprayPoint,_Range,Rigid_Body_Projectile, origin_point)
@@ -0,0 +1 @@
uid://dnh4a4vkl0cl1
@@ -0,0 +1,11 @@
extends RigidBody3D
class_name WeaponPickUp
@export var weapon: WeaponSlot
@export_enum("Weapon","Ammo") var TYPE = "Weapon"
var Pick_Up_Ready: bool = false
func _ready():
await get_tree().create_timer(2.0).timeout
Pick_Up_Ready = true
@@ -0,0 +1 @@
uid://deaf6cmuofa0
@@ -0,0 +1,18 @@
extends RigidBody3D
@export var _weapon_name: String
@export var _current_ammo: int
@export var _reserve_ammo: int
const TYPE = "Ammo"
var Pick_Up_Ready: bool = false
func _ready():
await get_tree().create_timer(2.0).timeout
Pick_Up_Ready = true
func Set_Ammo(w_name: String, c_ammo : int, r_ammo : int):
if w_name == _weapon_name:
_current_ammo = c_ammo
_reserve_ammo = r_ammo
@@ -0,0 +1 @@
uid://dvxmf6dpx0qdn
@@ -0,0 +1,7 @@
extends Resource
class_name WeaponSlot
@export var weapon: WeaponResource
@export var current_ammo: int
@export var reserve_ammo: int
@@ -0,0 +1 @@
uid://bw4j1fbww4k8s
@@ -0,0 +1,25 @@
[gd_resource type="Resource" script_class="WeaponResource" load_steps=3 format=3 uid="uid://ckwagka0u7opy"]
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_2o2yi"]
[ext_resource type="PackedScene" uid="uid://6rfh1ejt4m6m" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/melee_projectile.tscn" id="1_pf8fu"]
[resource]
script = ExtResource("1_2o2yi")
weapon_name = "MeleeWeapon"
pick_up_animation = "blasterP_activate"
shoot_animation = "blasterP_strike"
reload_animation = ""
change_animation = "blasterP_deactivate"
drop_animation = ""
out_of_ammo_animation = ""
melee_animation = "blasterP_strike"
has_ammo = false
magazine = 0
max_ammo = 0
damage = 2
melee_damage = 1.0
auto_fire = true
fire_range = 1
can_be_dropped = false
projectile_to_load = ExtResource("1_pf8fu")
incremental_reload = false
@@ -0,0 +1,27 @@
[gd_resource type="Resource" script_class="WeaponResource" format=3 uid="uid://yc1f2j7pl4vr"]
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_jgxyc"]
[ext_resource type="PackedScene" uid="uid://dc48o4niy1mok" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/basic_hitscan_projectile.tscn" id="1_l4j8x"]
[ext_resource type="PackedScene" uid="uid://cvbk5bxenpxey" path="res://client/template/Spawnable_Objects/Weapons/blaster_I.tscn" id="3_y2nwe"]
[ext_resource type="PackedScene" uid="uid://ciu0xkaw0ioef" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_1.tscn" id="4_xc0or"]
[resource]
script = ExtResource("1_jgxyc")
weapon_name = "blasterI"
pick_up_animation = "Global/blasterI Active"
shoot_animation = "Global/blasterI Shoot"
reload_animation = "Global/blasterI Reload"
change_animation = "Global/blasterI De-Activate"
drop_animation = "Global/blasterI Drop"
out_of_ammo_animation = "Global/blasterI OOA"
melee_animation = "Global/blasterI Melee"
magazine = 30
max_ammo = 60
damage = 10
melee_damage = 1.0
auto_fire = true
fire_range = 100
can_be_dropped = true
weapon_drop = ExtResource("3_y2nwe")
weapon_spray = ExtResource("4_xc0or")
projectile_to_load = ExtResource("1_l4j8x")
@@ -0,0 +1,29 @@
[gd_resource type="Resource" script_class="WeaponResource" load_steps=5 format=3 uid="uid://bwrs8ensewkgc"]
[ext_resource type="PackedScene" uid="uid://bpxa3je174q43" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/basic_rigid_body_projectile.tscn" id="1_1tllq"]
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="2_gpxoe"]
[ext_resource type="PackedScene" uid="uid://gtthejj2wmyj" path="res://client/template/Spawnable_Objects/Weapons/blaster_n.tscn" id="3_5a5l8"]
[ext_resource type="PackedScene" uid="uid://3mb12hvruajh" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_2.tscn" id="4_p50xh"]
[resource]
script = ExtResource("2_gpxoe")
weapon_name = "blasterN"
pick_up_animation = "Global/blasterL Activate"
shoot_animation = "Global/blasterL Shoot"
reload_animation = "Global/blasterL Reload"
change_animation = "Global/blasterL De-Activate"
drop_animation = "Global/blasterL Drop"
out_of_ammo_animation = "Global/blasterL OOA"
melee_animation = "Global/blasterL Melee"
has_ammo = true
magazine = 30
max_ammo = 60
damage = 1
melee_damage = 1.0
auto_fire = true
fire_range = 100
can_be_dropped = true
weapon_drop = ExtResource("3_5a5l8")
weapon_spray = ExtResource("4_p50xh")
projectile_to_load = ExtResource("1_1tllq")
incremental_reload = false
@@ -0,0 +1,29 @@
[gd_resource type="Resource" script_class="WeaponResource" load_steps=5 format=3 uid="uid://cd8msvjysh2uv"]
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_4ro7h"]
[ext_resource type="PackedScene" uid="uid://brvfyahytwd8m" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/shotgun_projectile.tscn" id="1_qi0jo"]
[ext_resource type="PackedScene" uid="uid://x8nhs2cer2to" path="res://client/template/Spawnable_Objects/Weapons/blaster_m.tscn" id="3_5hojr"]
[ext_resource type="PackedScene" uid="uid://ceuaamuau8pl8" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_3.tscn" id="4_5jfrh"]
[resource]
script = ExtResource("1_4ro7h")
weapon_name = "blasterM"
pick_up_animation = "blasterM_activate"
shoot_animation = "blasterM_Shoot"
reload_animation = "blasterM_Reload"
change_animation = "blasterM_Deactive"
drop_animation = "blasterM_Drop"
out_of_ammo_animation = "blasterM_OOA"
melee_animation = "blasterM_Melee"
has_ammo = true
magazine = 9
max_ammo = 36
damage = 5
melee_damage = 1.0
auto_fire = true
fire_range = 50
can_be_dropped = true
weapon_drop = ExtResource("3_5hojr")
weapon_spray = ExtResource("4_5jfrh")
projectile_to_load = ExtResource("1_qi0jo")
incremental_reload = true
@@ -0,0 +1,26 @@
[gd_resource type="Resource" script_class="WeaponResource" format=3 uid="uid://c1jg0ifn7yvve"]
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_oxrhl"]
[ext_resource type="PackedScene" uid="uid://dc48o4niy1mok" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/basic_hitscan_projectile.tscn" id="1_xr7bm"]
[ext_resource type="PackedScene" uid="uid://gtthejj2wmyj" path="res://client/template/Spawnable_Objects/Weapons/blaster_n.tscn" id="3_c38hr"]
[ext_resource type="PackedScene" uid="uid://3mb12hvruajh" path="res://client/template/Spawnable_Objects/SprayProfiles/spray_profile_2.tscn" id="4_rk8dx"]
[resource]
script = ExtResource("1_oxrhl")
weapon_name = "blaster_n"
pick_up_animation = "Global/blasterN Active"
shoot_animation = "Global/blasterN Shoot"
reload_animation = "Global/blasterN Reload"
change_animation = "Global/blasterN De-Activate"
drop_animation = "Global/blasterN Drop"
out_of_ammo_animation = "Global/blasterN OOA"
melee_animation = "Global/blasterN Melee"
magazine = 5
max_ammo = 15
damage = 1
melee_damage = 1.0
fire_range = 100
can_be_dropped = true
weapon_drop = ExtResource("3_c38hr")
weapon_spray = ExtResource("4_rk8dx")
projectile_to_load = ExtResource("1_xr7bm")
@@ -0,0 +1,27 @@
[gd_resource type="Resource" script_class="WeaponResource" load_steps=4 format=3 uid="uid://dgayjkjx4mckc"]
[ext_resource type="Script" uid="uid://ca7h24lxxrtvx" path="res://client/template/scripts/Weapon_State_Machine/weapon_resource.gd" id="1_321c1"]
[ext_resource type="PackedScene" uid="uid://ddfiivxgnakti" path="res://client/template/Spawnable_Objects/Projectiles_To_Load/rocket_launcher_projectile.tscn" id="1_xi5nj"]
[ext_resource type="PackedScene" uid="uid://dwgt7gwe3gfyy" path="res://client/template/Spawnable_Objects/Weapons/blasterQ.tscn" id="2_kih88"]
[resource]
script = ExtResource("1_321c1")
weapon_name = "blasterQ"
pick_up_animation = "Global/blasterQ Active"
shoot_animation = "Global/blasterQ Shoot"
reload_animation = "Global/blasterQ Reload"
change_animation = "Global/blasterQ Deactivate"
drop_animation = "Global/blasterQ Drop"
out_of_ammo_animation = "Global/blasterQ OOA"
melee_animation = "Global/blasterQ Melee"
has_ammo = true
magazine = 2
max_ammo = 6
damage = 20
melee_damage = 2.0
auto_fire = false
fire_range = 100
can_be_dropped = true
weapon_drop = ExtResource("2_kih88")
projectile_to_load = ExtResource("1_xi5nj")
incremental_reload = false
@@ -0,0 +1,263 @@
extends Node3D
signal weapon_changed
signal update_ammo
signal update_weapon_stack
signal hit_successfull
signal add_signal_to_hud
signal connect_weapon_to_hud
@export var animation_player: AnimationPlayer
@export var melee_hitbox: ShapeCast3D
@export var max_weapons: int
@onready var bullet_point = get_node("%BulletPoint")
@onready var debug_bullet = preload("res://client/template/Spawnable_Objects/hit_debug.tscn")
var next_weapon: WeaponSlot
#The List of All Available weapons in the game
var spray_profiles: Dictionary = {}
var _count = 0
var shot_tween
@export var weapon_stack:Array[WeaponSlot] #An Array of weapons currently in possesion by the player
var current_weapon_slot: WeaponSlot = null
func _ready() -> void:
if weapon_stack.is_empty():
push_error("Weapon Stack is empty, please populate with weapons")
else:
animation_player.animation_finished.connect(_on_animation_finished)
for i in weapon_stack:
initialize(i) #current starts on the first weapon in the stack
current_weapon_slot = weapon_stack[0]
if check_valid_weapon_slot():
enter()
update_weapon_stack.emit(weapon_stack)
func _unhandled_key_input(event: InputEvent) -> void:
if not event.is_pressed():
return
if range(KEY_1, KEY_4).has(event.keycode):
var _slot_number = (event.keycode - KEY_1)
if weapon_stack.size()-1>=_slot_number:
exit(weapon_stack[_slot_number])
func _input(event: InputEvent) -> void:
if event.is_action_pressed("WeaponUp"):
if check_valid_weapon_slot():
var weapon_index = weapon_stack.find(current_weapon_slot)
weapon_index = min(weapon_index+1,weapon_stack.size()-1)
exit(weapon_stack[weapon_index])
if event.is_action_pressed("WeaponDown"):
if check_valid_weapon_slot():
var weapon_index = weapon_stack.find(current_weapon_slot)
weapon_index = max(weapon_index-1,0)
exit(weapon_stack[weapon_index])
if event.is_action_pressed("Shoot"):
if check_valid_weapon_slot():
shoot()
if event.is_action_released("Shoot"):
if check_valid_weapon_slot():
shot_count_update()
if event.is_action_pressed("Reload"):
if check_valid_weapon_slot():
reload()
if event.is_action_pressed("Drop_Weapon"):
if check_valid_weapon_slot():
drop(current_weapon_slot)
if event.is_action_pressed("Melee"):
if check_valid_weapon_slot():
melee()
func check_valid_weapon_slot()->bool:
if current_weapon_slot:
if current_weapon_slot.weapon:
return true
else:
push_warning("No Weapon Resource active on the weapon controler.")
else:
push_warning("No Current Weapon slot active on the weapon controler.")
return false
func initialize(_weapon_slot: WeaponSlot):
if !_weapon_slot or !_weapon_slot.weapon:
return
if _weapon_slot.weapon.weapon_spray:
spray_profiles[_weapon_slot.weapon.weapon_name] = _weapon_slot.weapon.weapon_spray.instantiate()
connect_weapon_to_hud.emit(_weapon_slot.weapon)
func enter() -> void:
animation_player.queue(current_weapon_slot.weapon.pick_up_animation)
weapon_changed.emit(current_weapon_slot.weapon.weapon_name)
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
func exit(_next_weapon: WeaponSlot) -> void:
if _next_weapon != current_weapon_slot:
if animation_player.get_current_animation() != current_weapon_slot.weapon.change_animation:
animation_player.queue(current_weapon_slot.weapon.change_animation)
next_weapon = _next_weapon
func change_weapon(weapon_slot: WeaponSlot) -> void:
current_weapon_slot = weapon_slot
next_weapon = null
enter()
func shot_count_update() -> void:
shot_tween = get_tree().create_tween()
shot_tween.tween_property(self,"_count",0,1)
func shoot() -> void:
if current_weapon_slot.current_ammo != 0 or not current_weapon_slot.weapon.has_ammo:
if current_weapon_slot.weapon.incremental_reload and animation_player.current_animation == current_weapon_slot.weapon.reload_animation:
animation_player.stop()
if not animation_player.is_playing():
animation_player.play(current_weapon_slot.weapon.shoot_animation)
if current_weapon_slot.weapon.has_ammo:
current_weapon_slot.current_ammo -= 1
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
if shot_tween:
shot_tween.kill()
var Spread = Vector2.ZERO
if current_weapon_slot.weapon.weapon_spray:
_count = _count + 1
Spread = spray_profiles[current_weapon_slot.weapon.weapon_name].Get_Spray(_count, current_weapon_slot.weapon.magazine)
load_projectile(Spread)
else:
reload()
func load_projectile(_spread):
var _projectile:Projectile = current_weapon_slot.weapon.projectile_to_load.instantiate()
_projectile.position = bullet_point.global_position
_projectile.rotation = owner.rotation
bullet_point.add_child(_projectile)
add_signal_to_hud.emit(_projectile)
var bullet_point_origin = bullet_point.global_position
_projectile._Set_Projectile(current_weapon_slot.weapon.damage,_spread,current_weapon_slot.weapon.fire_range, bullet_point_origin)
func reload() -> void:
if current_weapon_slot.current_ammo == current_weapon_slot.weapon.magazine:
return
elif not animation_player.is_playing():
if current_weapon_slot.reserve_ammo != 0:
animation_player.queue(current_weapon_slot.weapon.reload_animation)
else:
animation_player.queue(current_weapon_slot.weapon.out_of_ammo_animation)
func calculate_reload() -> void:
if current_weapon_slot.current_ammo == current_weapon_slot.weapon.magazine:
var anim_legnth = animation_player.get_current_animation_length()
animation_player.advance(anim_legnth)
return
var Mag_Amount = current_weapon_slot.weapon.magazine
if current_weapon_slot.weapon.incremental_reload:
Mag_Amount = current_weapon_slot.current_ammo+1
var Reload_Amount = min(Mag_Amount-current_weapon_slot.current_ammo,Mag_Amount,current_weapon_slot.reserve_ammo)
current_weapon_slot.current_ammo = current_weapon_slot.current_ammo+Reload_Amount
current_weapon_slot.reserve_ammo = current_weapon_slot.reserve_ammo-Reload_Amount
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
shot_count_update()
func melee() -> void:
var Current_Anim = animation_player.get_current_animation()
if Current_Anim == current_weapon_slot.weapon.shoot_animation:
return
if Current_Anim != current_weapon_slot.weapon.melee_animation:
animation_player.play(current_weapon_slot.weapon.melee_animation)
if melee_hitbox.is_colliding():
var colliders = melee_hitbox.get_collision_count()
for c in colliders:
var Target = melee_hitbox.get_collider(c)
if Target.is_in_group("Target") and Target.has_method("Hit_Successful"):
hit_successfull.emit()
var Direction = (Target.global_transform.origin - owner.global_transform.origin).normalized()
var Position = melee_hitbox.get_collision_point(c)
Target.Hit_Successful(current_weapon_slot.weapon.melee_damage, Direction, Position)
func drop(_slot: WeaponSlot) -> void:
if _slot.weapon.can_be_dropped and weapon_stack.size() != 1:
var weapon_index = weapon_stack.find(_slot,0)
if weapon_index != -1:
weapon_stack.pop_at(weapon_index)
update_weapon_stack.emit(weapon_stack)
if _slot.weapon.weapon_drop:
var weapon_dropped = _slot.weapon.weapon_drop.instantiate()
weapon_dropped.weapon = _slot
weapon_dropped.set_global_transform(bullet_point.get_global_transform())
get_tree().get_root().add_child(weapon_dropped)
animation_player.play(current_weapon_slot.weapon.drop_animation)
weapon_index = max(weapon_index-1,0)
exit(weapon_stack[weapon_index])
else:
return
func _on_animation_finished(anim_name):
if anim_name == current_weapon_slot.weapon.shoot_animation:
if current_weapon_slot.weapon.auto_fire == true:
if Input.is_action_pressed("Shoot"):
shoot()
if anim_name == current_weapon_slot.weapon.change_animation:
change_weapon(next_weapon)
if anim_name == current_weapon_slot.weapon.reload_animation:
if !current_weapon_slot.weapon.incremental_reload:
calculate_reload()
func _on_pick_up_detection_body_entered(body: RigidBody3D):
var weapon_slot = body.weapon
for slot in weapon_stack:
if slot.weapon == weapon_slot.weapon:
var remaining
remaining = add_ammo(slot, weapon_slot.current_ammo+weapon_slot.reserve_ammo)
weapon_slot.current_ammo = min(remaining, slot.weapon.magazine)
weapon_slot.reserve_ammo = max(remaining - weapon_slot.current_ammo,0)
if remaining == 0:
body.queue_free()
return
if body.TYPE == "Weapon":
if weapon_stack.size() == max_weapons:
return
if body.Pick_Up_Ready == true:
var weapon_index = weapon_stack.find(current_weapon_slot)
weapon_stack.insert(weapon_index,weapon_slot)
update_weapon_stack.emit(weapon_stack)
exit(weapon_slot)
initialize(weapon_slot)
body.queue_free()
func add_ammo(_weapon_slot: WeaponSlot, ammo: int)->int:
var weapon = _weapon_slot.weapon
var required = weapon.max_ammo - _weapon_slot.reserve_ammo
var remaining = max(ammo - required,0)
_weapon_slot.reserve_ammo += min(ammo, required)
update_ammo.emit([current_weapon_slot.current_ammo, current_weapon_slot.reserve_ammo])
return remaining
@@ -0,0 +1 @@
uid://cxxpp8cfxb8jp
@@ -0,0 +1,15 @@
extends RigidBody3D
signal Hit_Successfull
var damage: int = 0
func _on_body_entered(body):
if body.is_in_group("Target") && body.has_method("Hit_Successful"):
body.Hit_Successful(damage)
emit_signal("Hit_Successfull")
queue_free()
func _on_timer_timeout():
queue_free()
@@ -0,0 +1 @@
uid://11gya5082xv3
@@ -0,0 +1,4 @@
extends Sprite3D
func _on_timer_timeout():
queue_free()

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