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)
This commit is contained in:
2026-07-02 00:26:45 -04:00
parent f0c95dcfd2
commit ad48f38ca5
70 changed files with 5297 additions and 52 deletions
@@ -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()
@@ -0,0 +1 @@
uid://dkt6w5wvebi72
@@ -0,0 +1,56 @@
@icon("res://client/template/scripts/Weapon_State_Machine/weapon_resource_icon.svg")
extends Resource
class_name WeaponResource
@warning_ignore("unused_signal")
signal update_overlay
@warning_ignore("unused_signal")
signal Zoom
@export_group("Weapon Animations")
##The Reference for the active weapons and pick ups
@export var weapon_name: String
## The Animation to play when the weapon was picked up
@export var pick_up_animation: String
## The Animation to play when the weapon is shot
@export var shoot_animation: String
## The Animation to play when the weapon reload
@export var reload_animation: String
## The Animation to play when the weapon is Changed
@export var change_animation: String
## The Animation to play when the weapon is dropped
@export var drop_animation: String
## The Animation to play when the weapon is out of ammo
@export var out_of_ammo_animation: String
## The Animation to play when you do the melee strike
@export var melee_animation: String
@export_group("Weapon Stats")
## If Uncheck Shoot Function Will skip ammo check
@export var has_ammo: bool = true
## The Maximum amount that you will reload if zero
@export var magazine: int
## The Maximum ammo that can be held in reserve.
@export var max_ammo: int
## The damage that a weapon will do
@export var damage: int
## The Melee damage that a weapon will do.
@export var melee_damage: float
## If Auto Fire is set to true the weapon will continuously fire until fire trigger is released
@export var auto_fire: bool
## The Range that a weapon will fire. Beyong this number no hit will trigger.
@export var fire_range: int
@export_group("Weapon Behaviour")
##If Checked the weapon drop scene MUST be provided
@export var can_be_dropped: bool
## The Rigid body to spawn for the weapon. It should be a Rigid Body of type Weapon_Pick_Up and have a matching weapon_name.
@export var weapon_drop: PackedScene
## The Spray_Profile to use when firing the weapon. It should be of Type Spray_Profile. This handles the spray calculations and passes back the information to the Projectile to load
@export var weapon_spray: PackedScene
## The Projectile that will be loaded. Not a Rigid body but class that handles the ray cast processing and can be either hitscan or rigid body. Should be of Type Projectile
@export var projectile_to_load: PackedScene
## Incremental Reload is for shotgun or sigle item loaded weapons where you can interupt the reload process. If true the Calculate_Reload function on the weapon_state_machine must be called indepently.
## For Example: at each step of a shotgun reload the function is called via the animation player.
@export var incremental_reload: bool = false
@@ -0,0 +1 @@
uid://ca7h24lxxrtvx
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="120"
height="120"
viewBox="0 0 120 120"
version="1.1"
id="svg5"
xml:space="preserve"
inkscape:export-filename="Secondary/Pistol/Pistol_Icon.png"
inkscape:export-xdpi="51.200001"
inkscape:export-ydpi="51.200001"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="weapon_resource_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="5.6568543"
inkscape:cx="80.786949"
inkscape:cy="57.275649"
inkscape:window-width="1920"
inkscape:window-height="1027"
inkscape:window-x="1920"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="layer3"
showguides="false"><inkscape:grid
type="xygrid"
id="grid1007" /></sodipodi:namedview><defs
id="defs2" /><g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Pistol"><path
style="fill:#ffffff;stroke:#fdfdfd;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="M 38.383179,85.989654 H 22.544682 l 0.222115,-1.575826 1.386247,-0.721408 0.05719,-0.847949 -3.175572,-0.262031 -2.445375,-1.472439 -0.110658,-2.665305 2.949314,-7.791327 6.077368,-9.78273 2.91565,-6.907743 0.608662,-4.254202 -0.828252,-2.540647 -1.58005,-1.418767 -2.673229,-0.498449 -1.314259,-0.812357 0.201485,-1.760648 1.061372,-1.444114 v -7.969685 l 3.708858,0.113304 1.021667,-1.839682 h 1.253556 v 1.707812 h 63.130061 l 0.373363,-1.305175 h 1.081786 l 0.566864,1.261097 h 1.459724 v 2.15445 h 1.244851 v 5.163391 h -1.069317 v 6.063658 l -1.496296,1.496296 H 72.741608 l -1.470697,1.683184 -0.364041,1.454612 -0.04419,1.387817 v 2.164182 l 0.423966,1.778557 0.827681,2.201972 H 54.552451 l -2.623079,-0.958804 -2.411172,1.16365 -1.828793,4.83828 0.731728,2.413014 -2.39789,1.427434 -0.93345,2.930928 0.556376,2.135795 c 0,0 -2.498093,1.397347 -2.587302,1.586483 -0.08921,0.189136 -1.241396,3.928682 -1.241396,3.928682 l 0.08274,3.424204 -0.543105,1.129123 -2.746189,0.08859 0.0032,1.06841 h 1.282991 v 2.070652 z"
id="path1040"
inkscape:label="path1040" /></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB