From f3185ec6bcc05fdf766e84f63dc16f0b047d5201 Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Mon, 7 May 2018 08:03:37 +0300
Subject: [PATCH] Shooting

---
 build/gamesource/config/entities/classes.ent  |  37 +-
 build/gamesource/config/entities/defaults.ent |   4 +-
 proj/sxgame/vs2013/sxgame.vcxproj             |   6 +
 proj/sxgame/vs2013/sxgame.vcxproj.filters     |  18 +
 sdks/bullet3                                  |   2 +-
 source/anim/animated.cpp                      |   6 +-
 source/anim/animated.h                        |   2 +-
 source/anim/sxanim.h                          |   3 +-
 source/common                                 |   2 +-
 source/decals/DecalManager.cpp                |  19 +-
 source/decals/DecalManager.h                  |   6 +-
 source/game/BaseAmmo.cpp                      | 395 +++++++++++++++++-
 source/game/BaseAmmo.h                        |  41 +-
 source/game/BaseAnimating.h                   |   4 +-
 source/game/BaseCharacter.cpp                 | 146 ++++++-
 source/game/BaseCharacter.h                   |  26 ++
 source/game/BaseEntity.cpp                    |  40 +-
 source/game/BaseEntity.h                      |  11 +
 source/game/BaseItem.cpp                      |   2 +-
 source/game/BaseMag.cpp                       |   4 +
 source/game/BaseSupply.h                      |   5 +
 source/game/BaseTool.cpp                      |  55 ++-
 source/game/BaseTool.h                        |  14 +
 source/game/BaseTrigger.cpp                   |   4 +-
 source/game/BaseWeapon.cpp                    | 251 +++++++++--
 source/game/BaseWeapon.h                      |  34 +-
 source/game/CharacterInventory.cpp            | 163 ++++++++
 source/game/CharacterInventory.h              |  32 ++
 source/game/GameData.cpp                      |   7 +
 source/game/NPCBase.cpp                       |  30 +-
 source/game/NPCBase.h                         |  16 +-
 source/game/NPCZombie.cpp                     |   4 +-
 source/game/Player.cpp                        |  16 +-
 source/game/Random.h                          |  21 +
 source/game/TakeDamageInfo.h                  |  34 ++
 source/game/Tracer.cpp                        | 129 ++++++
 source/game/Tracer.h                          |  46 ++
 source/mtllight/material.cpp                  |   5 +
 source/physics/PhyWorld.cpp                   |  56 ++-
 source/physics/PhyWorld.h                     |   7 +-
 source/physics/sxphysics.h                    |  33 ++
 source/physics/sxphysics_dll.cpp              |  15 +-
 source/skyxengine.cpp                         |   5 +
 43 files changed, 1601 insertions(+), 155 deletions(-)
 create mode 100644 source/game/CharacterInventory.cpp
 create mode 100644 source/game/CharacterInventory.h
 create mode 100644 source/game/Random.h
 create mode 100644 source/game/TakeDamageInfo.h
 create mode 100644 source/game/Tracer.cpp
 create mode 100644 source/game/Tracer.h

diff --git a/build/gamesource/config/entities/classes.ent b/build/gamesource/config/entities/classes.ent
index 8b2422ed1..ef9771264 100644
--- a/build/gamesource/config/entities/classes.ent
+++ b/build/gamesource/config/entities/classes.ent
@@ -1,12 +1,19 @@
 [ammo_5.45x39ps]
 base_class = base_ammo
 show_in_listing = 1
-model = "models/ammo/ammo_5.45x39.dse"
+inv_stackable = 1
+inv_stack_max = 300
+; model = "models/ammo/ammo_5.45x39.dse"
 inv_name = "5.45x39ps"
 ;начальная скорость пули, ее масса, бронепробиваемость, останавливающее действие
 start_speed = 900 ; м/с
 bullet_mass = 3.4 ; г
 armor_piercing = 500 ; ед/дж
+is_expansive = 0 ; экспансивная
+is_bursting = 0 ; разрывная
+; коэффициент формы 
+; наличие/отсутствие сердечника
+; вращение об/с - зависит от шачальной скорости, шага нареза
 
 [ammobox_5.45x39ps]
 base_class = base_ammobox
@@ -44,17 +51,23 @@ addon_handlers = ""
 zoom_time = 0.125
 zoomable = 1
 
-fire_modes = "single,burst"; single/burst/cutoff
-; cutoff_size = 3
-; cutoff_speed = 100
-single_speed = 40; выст/мин
-burst_speed = 100; выст/мин
+aiming_range = 200 ; м, дистанция пристрелки
+
+fire_modes = "single,burst,cutoff"; single/burst/cutoff
+cutoff_size = 3
+single_rate = 400; выст/мин
+burst_rate = 600; выст/мин
+cutoff_rate = 600; выст/мин
 
 effective_distance = 650
 
 ;разброс
 spread_base       	= 0.33    ;угол (в градусах) базовой дисперсии оружия (оружия, зажатого в тисках)
 
+; тип нарезки ствола: 0 - гладкоствольное; -1 - левая; 1 - правая
+rifle_type = 1 ; 
+rifle_step = 200 ; шаг нарезки, мм
+
 ; Боевая скорострельность (одиночными): 40 выст/мин
 ; Боевая скорострельность (очередями): 100 выст/мин
 ; Дальность, до которой сохраняется убойное действие пули: 1350 м
@@ -71,12 +84,12 @@ spread_base       	= 0.33    ;угол (в градусах) базовой ди
 
 
 ; звуки
-snd_draw         =  ; Достать
-snd_holster      =  ; Убрать
-snd_shoot        = "ak74_shoot2.ogg" ; Стрелять
-snd_empty        =  ; Пустой
-snd_reload       =  ; Перезарядка
-snd_switch       =  ; Переключение режима
+snd_draw         = "wpn/ak74_draw.ogg" ; Достать
+snd_holster      = "wpn/generic_holster.ogg" ; Убрать
+snd_shoot        = "wpn/ak74_shoot2.ogg" ; Стрелять
+snd_empty        = "wpn/gen_empty.ogg" ; Пустой
+snd_reload       = "wpn/ak74_reload.ogg" ; Перезарядка
+snd_switch       = "wpn/generic_close.ogg" ; Переключение режима
 
 ;прочность
 durability_max = 350000
diff --git a/build/gamesource/config/entities/defaults.ent b/build/gamesource/config/entities/defaults.ent
index b61b09092..70fb6cda8 100644
--- a/build/gamesource/config/entities/defaults.ent
+++ b/build/gamesource/config/entities/defaults.ent
@@ -1,3 +1,5 @@
 
 [npc_zombie]
-model = "models/zombie/zombie.dse"
+; model = "models/zombie/zombie.dse"
+; model = "models/zombie/zombie_hb.dse"
+model = "models/stalker_zombi/stalker_zombi_a.dse"
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index 5d98a16f4..f4c7b56df 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj
+++ b/proj/sxgame/vs2013/sxgame.vcxproj
@@ -99,6 +99,7 @@
     <ClCompile Include="..\..\..\source\game\BaseWeaponAddon.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseTrigger.cpp" />
     <ClCompile Include="..\..\..\source\game\BaseCharacter.cpp" />
+    <ClCompile Include="..\..\..\source\game\CharacterInventory.cpp" />
     <ClCompile Include="..\..\..\source\game\crosshair.cpp" />
     <ClCompile Include="..\..\..\source\game\CrosshairManager.cpp" />
     <ClCompile Include="..\..\..\source\game\EntityFactory.cpp" />
@@ -126,6 +127,7 @@
     <ClCompile Include="..\..\..\source\game\PlayerSpawn.cpp" />
     <ClCompile Include="..\..\..\source\game\PointCamera.cpp" />
     <ClCompile Include="..\..\..\source\game\PointEntity.cpp" />
+    <ClCompile Include="..\..\..\source\game\Tracer.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\common\AAString.h" />
@@ -136,6 +138,8 @@
     <ClInclude Include="..\..\..\source\game\BaseWeaponAddon.h" />
     <ClInclude Include="..\..\..\source\game\BaseTrigger.h" />
     <ClInclude Include="..\..\..\source\game\BaseCharacter.h" />
+    <ClInclude Include="..\..\..\source\game\CharacterInventory.h" />
+    <ClInclude Include="..\..\..\source\game\Random.h" />
     <ClInclude Include="..\..\..\source\game\crosshair.h" />
     <ClInclude Include="..\..\..\source\game\CrosshairManager.h" />
     <ClInclude Include="..\..\..\source\game\EntityFactory.h" />
@@ -165,6 +169,8 @@
     <ClInclude Include="..\..\..\source\game\PlayerSpawn.h" />
     <ClInclude Include="..\..\..\source\game\PointCamera.h" />
     <ClInclude Include="..\..\..\source\game\PointEntity.h" />
+    <ClInclude Include="..\..\..\source\game\TakeDamageInfo.h" />
+    <ClInclude Include="..\..\..\source\game\Tracer.h" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj.filters b/proj/sxgame/vs2013/sxgame.vcxproj.filters
index 1fd6f5807..1a4129b7a 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj.filters
+++ b/proj/sxgame/vs2013/sxgame.vcxproj.filters
@@ -168,6 +168,12 @@
     <ClCompile Include="..\..\..\source\game\BaseAmmoBox.cpp">
       <Filter>Source Files\ents\items</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\game\Tracer.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\game\CharacterInventory.cpp">
+      <Filter>Source Files\ents</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\game\sxgame.h">
@@ -281,5 +287,17 @@
     <ClInclude Include="..\..\..\source\game\BasePistol.h">
       <Filter>Header Files\ents\items\tools\weapons</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\game\Random.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\game\Tracer.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\game\TakeDamageInfo.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\game\CharacterInventory.h">
+      <Filter>Header Files\ents</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/sdks/bullet3 b/sdks/bullet3
index 6c9124b10..c04199762 160000
--- a/sdks/bullet3
+++ b/sdks/bullet3
@@ -1 +1 @@
-Subproject commit 6c9124b1059924bac1f7bd347fabd3ab2ecd76e7
+Subproject commit c04199762e5e6e2161585ade2a22be2d355bf6ce
diff --git a/source/anim/animated.cpp b/source/anim/animated.cpp
index 47b08aa5b..505be3e7e 100644
--- a/source/anim/animated.cpp
+++ b/source/anim/animated.cpp
@@ -1075,7 +1075,7 @@ Animation::~Animation()
 	m_pMgr->unreg(myId);
 }
 
-SMMATRIX Animation::getBoneTransform(UINT _id)
+SMMATRIX Animation::getBoneTransform(UINT _id, bool bWithScale)
 {
 	//id *= 2;
 	int id = m_FinalBones[_id].pid;
@@ -1084,6 +1084,10 @@ SMMATRIX Animation::getBoneTransform(UINT _id)
 		return(SMMatrixIdentity());
 	}
 	float3 pos = m_pBoneMatrixRender[id].position/* * m_fScale*/;
+	if(bWithScale)
+	{
+		pos *= m_fScale;
+	}
 	SMQuaternion q = m_pBoneMatrixRender[id].orient;
 	return(q.GetMatrix() * SMMatrixTranslation(pos));
 }
diff --git a/source/anim/animated.h b/source/anim/animated.h
index 22a50c8cf..41df6124b 100644
--- a/source/anim/animated.h
+++ b/source/anim/animated.h
@@ -150,7 +150,7 @@ public:
 
 	void setBoneController(const String & name, float value, MODEL_BONE_CTL what);
 
-	SMMATRIX getBoneTransform(UINT id);
+	SMMATRIX getBoneTransform(UINT id, bool bWithScale = false);
 	float3 getBoneTransformPos(UINT id);
 	SMQuaternion getBoneTransformRot(UINT id);
 	UINT getBone(const char * str);
diff --git a/source/anim/sxanim.h b/source/anim/sxanim.h
index ee094dae3..92aca21b1 100644
--- a/source/anim/sxanim.h
+++ b/source/anim/sxanim.h
@@ -166,9 +166,10 @@ public:
 
 	/*! Возвращает трансформацию указанной кости
 		@param[in] id Номер кости
+		@param[in] bWithScale Учитывать ли масштабирование
 		@return Матрица трансформации кости
 	*/
-	virtual SMMATRIX getBoneTransform(UINT id) = 0;
+	virtual SMMATRIX getBoneTransform(UINT id, bool bWithScale = false) = 0;
 
 	/*! Возвращает идентификатор указанной кости
 		@param[in] str Имя кости
diff --git a/source/common b/source/common
index 6d30eb4c6..1ad20f60b 160000
--- a/source/common
+++ b/source/common
@@ -1 +1 @@
-Subproject commit 6d30eb4c6e806568c952dffbd90d3ae59ff01a43
+Subproject commit 1ad20f60bbac0b2d9496c64d3ef35cea0fa51b88
diff --git a/source/decals/DecalManager.cpp b/source/decals/DecalManager.cpp
index d5e3833bc..281fdd004 100644
--- a/source/decals/DecalManager.cpp
+++ b/source/decals/DecalManager.cpp
@@ -446,7 +446,7 @@ void DecalManager::shootDecal(DECAL_TYPE type, const float3 & position, ID iMate
 					btTriangleMeshShape * shape = (btTriangleMeshShape*)part->m_hitCollisionObject->getCollisionShape();
 					btStridingMeshInterface * iface = shape->getMeshInterface();
 
-					float3_t * verices;
+					float3 * verices;
 					int numverts;
 					PHY_ScalarType type = PHY_INTEGER;
 					int stride = 0;
@@ -526,6 +526,7 @@ void DecalManager::shootDecal(DECAL_TYPE type, const float3 & position, ID iMate
 							vClippedVerts[ii + 1].z = nn; // fix normal
 							//Transform to World
 							vDecalVerts.push_back(vert0);
+							//m_dbgRender.push_back(vert0.pos);
 
 							vert.pos = mBasis * vClippedVerts[ii];
 							vert.normal = n;
@@ -533,11 +534,14 @@ void DecalManager::shootDecal(DECAL_TYPE type, const float3 & position, ID iMate
 							vert.tex = (float2)(vert.tex * float2((float)(rng.xmax - rng.xmin) / (float)_info.Width, (float)(rng.ymax - rng.ymin) / (float)_info.Height) + float2((float)rng.xmin / (float)_info.Width, (float)rng.ymin / (float)_info.Height));
 
 							vDecalVerts.push_back(vert);
+							//m_dbgRender.push_back(vert.pos);
 
 							vert.pos = mBasis * vClippedVerts[ii + 1];
 							vert.tex = float2((vClippedVerts[ii + 1].x - sBound.x) / (sBound.y - sBound.x), (vClippedVerts[ii + 1].y - tBound.x) / (tBound.y - tBound.x));
 							vert.tex = (float2)(vert.tex * float2((float)(rng.xmax - rng.xmin) / (float)_info.Width, (float)(rng.ymax - rng.ymin) / (float)_info.Height) + float2((float)rng.xmin / (float)_info.Width, (float)rng.ymin / (float)_info.Height));
 							vDecalVerts.push_back(vert);
+							//m_dbgRender.push_back(vert.pos);
+
 							//m_dbgRender.push_back(mBasis * vClippedVerts[ii]);
 							//m_dbgRender.push_back(mBasis * vClippedVerts[(ii + 1) % len]);
 
@@ -560,12 +564,12 @@ void DecalManager::shootDecal(DECAL_TYPE type, const float3 & position, ID iMate
 
 void DecalManager::render()
 {
-	/*for(int i = 0, l = g_dbgDraw.size(); i < l; i += 3)
+	/*for(int i = 0, l = m_dbgRender.size(); i < l; i += 3)
 	{
-		SXPhysics_GetDynWorld()->getDebugDrawer()->drawTriangle(F3_BTVEC(g_dbgDraw[i]), F3_BTVEC(g_dbgDraw[i + 1]), F3_BTVEC(g_dbgDraw[i + 2]), btVector3(1.0f, 1.0f, 1.0f), 1.0f);
-	}
-	SXPhysics_GetDynWorld()->getDebugDrawer()->drawSphere(F3_BTVEC(spherePos), spherePos.w, btVector3(1, 1, 1));
-	*/
+		SXPhysics_GetDynWorld()->getDebugDrawer()->drawTriangle(F3_BTVEC(m_dbgRender[i]), F3_BTVEC(m_dbgRender[i + 1]), F3_BTVEC(m_dbgRender[i + 2]), btVector3(1.0f, 1.0f, 1.0f), 1.0f);
+	}*/
+	//SXPhysics_GetDynWorld()->getDebugDrawer()->drawSphere(F3_BTVEC(spherePos), spherePos.w, btVector3(1, 1, 1));
+	
 	updateBuffer();
 
 	if(!m_pVertexBuffer)
@@ -573,10 +577,7 @@ void DecalManager::render()
 		return;
 	}
 
-
 	dev->SetFVF(D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1);
-	
-	//SkyXEngine::Core::Data::Device->SetTexture(0, SkyXEngine::Core::Data::LoadedTextures->GetTexture(3));
 
 	dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
 
diff --git a/source/decals/DecalManager.h b/source/decals/DecalManager.h
index f150cc1da..ccce0c0ea 100644
--- a/source/decals/DecalManager.h
+++ b/source/decals/DecalManager.h
@@ -181,7 +181,7 @@ public:
 		}
 
 		// floor/ceiling?
-		if(fabs(surfaceNormal.z) > SIN_45_DEGREES)
+		if(fabs(surfaceNormal.y) > SIN_45_DEGREES)
 		{
 			textureSpaceBasis[0].x = 1.0f;
 			textureSpaceBasis[0].y = 0.0f;
@@ -197,8 +197,8 @@ public:
 		else
 		{
 			textureSpaceBasis[1].x = 0.0f;
-			textureSpaceBasis[1].y = 0.0f;
-			textureSpaceBasis[1].z = -1.0f;
+			textureSpaceBasis[1].y = -1.0f;
+			textureSpaceBasis[1].z = 0.0f;
 
 			// S = N cross T
 			textureSpaceBasis[0] = SMVector3Cross(textureSpaceBasis[2], textureSpaceBasis[1]);
diff --git a/source/game/BaseAmmo.cpp b/source/game/BaseAmmo.cpp
index aaa93334c..41440c58a 100644
--- a/source/game/BaseAmmo.cpp
+++ b/source/game/BaseAmmo.cpp
@@ -5,6 +5,11 @@ See the license in LICENSE
 ***********************************************************/
 
 #include "BaseAmmo.h"
+#include "Tracer.h"
+#include "BaseTool.h"
+#include <particles/sxparticles.h>
+#include <decals/sxdecals.h>
+#include <BulletCollision/NarrowPhaseCollision/btRaycastCallback.h>
 
 /*! \skydocent base_ammo
 Базовый класс для патронов
@@ -17,16 +22,404 @@ BEGIN_PROPTABLE(CBaseAmmo)
 	DEFINE_FIELD_FLOAT(m_fBulletMass, PDFF_NOEDIT | PDFF_NOEXPORT, "bullet_mass", "", EDITOR_NONE)
 	//! Бронебойность
 	DEFINE_FIELD_FLOAT(m_fArmorPiercing, PDFF_NOEDIT | PDFF_NOEXPORT, "armor_piercing", "", EDITOR_NONE)
+	//! Экспансивная?
+	DEFINE_FIELD_BOOL(m_isExpansive, PDFF_NOEDIT | PDFF_NOEXPORT, "is_expansive", "", EDITOR_NONE)
+	//! Разрывная?
+	DEFINE_FIELD_BOOL(m_isBursting, PDFF_NOEDIT | PDFF_NOEXPORT, "is_bursting", "", EDITOR_NONE)
 END_PROPTABLE()
 
 REGISTER_ENTITY_NOLISTING(CBaseAmmo, base_ammo);
 
+struct	AllHitsNotMeRayResultCallback: public btCollisionWorld::AllHitsRayResultCallback
+{
+	AllHitsNotMeRayResultCallback(btCollisionObject* me, const btVector3&	rayFromWorld, const btVector3&	rayToWorld):
+		AllHitsRayResultCallback(rayFromWorld, rayToWorld)
+	{
+		m_me = me;
+	}
+
+	virtual	btScalar	addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
+	{
+		//printf("%f\n", rayResult.m_hitFraction);
+		if(rayResult.m_collisionObject == m_me)
+		{
+			return 1.0;
+		}
+		if(rayResult.m_localShapeInfo)
+		{
+			m_shapeInfos.push_back(*rayResult.m_localShapeInfo);
+		}
+		else
+		{
+			m_shapeInfos.push_back({-1, -1});
+		}
+		return(AllHitsRayResultCallback::addSingleResult(rayResult, normalInWorldSpace));
+	}
+	Array<btCollisionWorld::LocalShapeInfo> m_shapeInfos;
+protected:
+	btCollisionObject* m_me;
+};
+
 CBaseAmmo::CBaseAmmo(CEntityManager * pMgr):
 	BaseClass(pMgr),
 	m_fStartSpeed(0.0f),
 	m_fBulletMass(0.0f),
-	m_fArmorPiercing(0.0f)
+	m_fArmorPiercing(0.0f),
+	m_fNextBarrierDepth(0.0f)
 {
 	m_bPickable = false;
 	m_bInvStackable = false;
 }
+
+void CBaseAmmo::fire(const float3 &vStart, const float3 &vDir, CBaseCharacter *pAttacker)
+{
+	extern CTracer *g_pTracer;
+
+	fire(vStart, vDir, pAttacker, m_fStartSpeed);
+}
+
+void CBaseAmmo::fire(const float3 &_vStart, const float3 &_vDir, CBaseCharacter *pAttacker, float fSpeed)
+{
+	extern CTracer *g_pTracer;
+
+	int iJump = 0;
+
+	float3 vStart = _vStart;
+	float3 vDir = _vDir;
+	float fMaxDistance = ((CBaseTool*)getParent())->getMaxDistance();
+
+	float3 end;
+	float t0 = 0.0f;
+	float fY0;
+	bool isY0set = false;
+	float fSpeedY0;
+	g_pTracer->begin(vStart);
+
+	while(iJump < BULLET_MAX_JUMPS && fSpeed > 0.5f && fMaxDistance > 0.0f)
+	{
+		end = vStart + SMVector3Normalize(vDir) * min(fMaxDistance, BULLET_STEP_DISTANCE);
+		if(!isY0set)
+		{
+			fY0 = end.y;
+			isY0set = true;
+			fSpeedY0 = fSpeed * (vDir.y / SMVector3Length(vDir));
+		}
+		AllHitsNotMeRayResultCallback cb(pAttacker ? pAttacker->getBtCollisionObject() : NULL, F3_BTVEC(vStart), F3_BTVEC(end));
+		cb.m_collisionFilterGroup = CG_BULLETFIRE;
+		cb.m_collisionFilterMask = CG_ALL & ~(CG_DEBRIS | CG_TRIGGER | CG_CHARACTER);
+		cb.m_flags |= btTriangleRaycastCallback::kF_FilterBackfaces;
+		SXPhysics_GetDynWorld()->rayTest(F3_BTVEC(vStart), F3_BTVEC(end), cb);
+
+		g_pTracer->lineTo(vStart, 0.0f);
+		//g_pTracer->begin(vStart);
+		if(cb.hasHit())
+		{
+			AllHitsNotMeRayResultCallback cbBack(pAttacker ? pAttacker->getBtCollisionObject() : NULL, F3_BTVEC(end), F3_BTVEC(vStart));
+			cbBack.m_flags |= btTriangleRaycastCallback::kF_FilterBackfaces;
+			SXPhysics_GetDynWorld()->rayTest(F3_BTVEC(end), F3_BTVEC(vStart), cbBack);
+
+			Array<HitPoint> aHitPoints;
+			aHitPoints.reserve(cb.m_hitFractions.size() + cbBack.m_hitFractions.size());
+
+			for(int i = 0, l = cb.m_hitFractions.size(); i < l; ++i)
+			{
+				aHitPoints.push_back({BTVEC_F3(cb.m_hitPointWorld[i]), BTVEC_F3(cb.m_hitNormalWorld[i]), cb.m_collisionObjects[i], cb.m_hitFractions[i], false, cb.m_shapeInfos[i]});
+			}
+			for(int i = 0, l = cbBack.m_hitFractions.size(); i < l; ++i)
+			{
+				aHitPoints.push_back({BTVEC_F3(cbBack.m_hitPointWorld[i]), BTVEC_F3(cbBack.m_hitNormalWorld[i]), cbBack.m_collisionObjects[i], 1.0f - cbBack.m_hitFractions[i], true, cbBack.m_shapeInfos[i]});
+			}
+
+			aHitPoints.quickSort([](const HitPoint &a, const HitPoint &b)
+			{
+				return(a.fFraction < b.fFraction);
+			});
+			for(int i = 0, l = aHitPoints.size(); i < l; ++i)
+			{
+
+				ID idMtl = SXPhysics_GetMtlID(aHitPoints[i].pCollisionObject, &aHitPoints[i].shapeInfo);
+				if(ID_VALID(idMtl) && !aHitPoints[i].isExit)
+				{
+					float fHitChance = SML_MtlGetHitChance(idMtl);
+					if(fHitChance < randf(0.0f, 1.0f))
+					{
+						int c = 0;
+						for(int j = i + 1; j < l; ++j)
+						{
+							if(aHitPoints[j].isExit)
+							{
+								if(c == 0)
+								{
+									aHitPoints.erase(j);
+									--l;
+									break;
+								}
+								--c;
+							}
+							else
+							{
+								++c;
+							}
+						}
+						aHitPoints.erase(i--);
+						--l;
+						//skip that
+						continue;
+					}
+				}
+				g_pTracer->lineTo(aHitPoints[i].vPosition, aHitPoints[i].isExit ? 0.0f : 1.0f);
+				shootDecal(aHitPoints[i].vPosition, aHitPoints[i].vNormal, idMtl);
+				if(!aHitPoints[i].isExit)
+				{
+					m_fNextBarrierDepth = 0.0f;
+					// find next exit
+					// calculate can we hole 
+					for(int j = i + 1; j < l; ++j)
+					{
+						if(aHitPoints[j].isExit)
+						{
+							m_fNextBarrierDepth = SMVector3Length(aHitPoints[j].vPosition - aHitPoints[i].vPosition);
+							break;
+						}
+					}
+					float3 vNewDir;
+					float fNewSpeed = fSpeed;
+					if(shouldRecochet(aHitPoints[i].vPosition, aHitPoints[i].vNormal, vDir, idMtl, fSpeed, &vNewDir, &fNewSpeed))
+					{
+						// @TODO: play recochet sound
+					}
+					
+					float fEnergyDelta = m_fBulletMass * 0.001f * 0.5f * (fSpeed * fSpeed - fNewSpeed * fNewSpeed);
+					
+					if(aHitPoints[i].pCollisionObject->getUserPointer())
+					{
+						CTakeDamageInfo takeDamageInfo(pAttacker, fEnergyDelta);
+						takeDamageInfo.m_pInflictor = getParent();
+
+						((CBaseEntity*)aHitPoints[i].pCollisionObject->getUserPointer())->dispatchDamage(takeDamageInfo);
+					}
+					
+
+					// restart fire with new dir and speed
+
+					float3 vStart2 = aHitPoints[i].vPosition + SMVector3Normalize(vDir) * 0.001f;
+					vDir = vNewDir;
+					fSpeed = fNewSpeed;
+
+					fMaxDistance -= SMVector3Length(vStart - vStart2);
+					vStart = vStart2;
+
+					t0 = 0.0f;
+					isY0set = false;
+					++iJump;
+					//fire(aHitPoints[i].vPosition + SMVector3Normalize(vDir) * 0.001f, vNewDir, pAttacker, fNewSpeed, iJump + 1);
+					break;
+				}
+
+			}
+
+
+
+			//shoot decal
+			//SXDecals_ShootDecal(DECAL_TYPE_CONCRETE, BTVEC_F3(cb.m_hitPointWorld[0]), BTVEC_F3(cb.m_hitNormalWorld[0]));
+			//SPE_EffectPlayByName("fire", &BTVEC_F3(cb.m_hitPointWorld), &BTVEC_F3(cb.m_hitNormalWorld));
+			/*if(!cb.m_collisionObject->isStaticOrKinematicObject())
+			{
+			((btRigidBody*)cb.m_collisionObject)->applyCentralImpulse(F3_BTVEC(vDir * 10.0f));
+			cb.m_collisionObject->activate();
+			}*/
+			//g_pTracer->lineTo(BTVEC_F3(cb.m_hitPointWorld[0]) + SMVector3Normalize(BTVEC_F3(cb.m_hitNormalWorld[0])) * 0.1f, 0.0f);
+		}
+		else
+		{
+			g_pTracer->lineTo(end, 1.0f);
+			fMaxDistance -= min(fMaxDistance, BULLET_STEP_DISTANCE);
+			float t1 = t0 + (min(fMaxDistance, BULLET_STEP_DISTANCE) / fSpeed);
+			float3 vNewStart = end;
+			// apply derivation
+			// and...
+			// apply wind
+			// end...
+
+			// apply gravity
+			//end.y += (fSpeed * (vDir.y / SMVector3Length(vDir)) * (t1 - t0) - 10.0f * 0.5f * (t1 * t1 - t0 * t0)); // 10.0f - gravity
+			end.y = fY0 + fSpeedY0 * t0 - 10.0f * 0.5f * t0 * t0;
+
+			vDir = SMVector3Normalize(end - vStart);
+
+			vStart = vNewStart;
+			t0 = t1;
+		}
+	}
+	g_pTracer->end();
+}
+
+void CBaseAmmo::shootDecal(const float3 &vPos, const float3 &vNormal, ID idMtl)
+{
+	if(ID_VALID(idMtl))
+	{
+		MTLTYPE_PHYSIC type = SML_MtlGetPhysicMaterial(idMtl);
+		DECAL_TYPE decalType = DECAL_TYPE_CONCRETE;
+		switch(type)
+		{
+		case MTLTYPE_PHYSIC_METAL:
+			decalType = DECAL_TYPE_METAL;
+			break;
+		case MTLTYPE_PHYSIC_FLESH:
+			decalType = DECAL_TYPE_FLESH;
+			break;
+		case MTLTYPE_PHYSIC_GROUD_SAND:
+			decalType = DECAL_TYPE_EARTH;
+			break;
+		case MTLTYPE_PHYSIC_PLASTIC:
+			decalType = DECAL_TYPE_PLASTIC;
+			break;
+		case MTLTYPE_PHYSIC_TREE:
+			decalType = DECAL_TYPE_WOOD;
+			break;
+		}
+		SXDecals_ShootDecal(decalType, vPos, vNormal);
+
+		//SPE_EffectPlayByName("create_decal_test", &aHitPoints[i].vPosition, &aHitPoints[i].vNormal);
+	}
+}
+
+bool CBaseAmmo::shouldRecochet(const float3 &vPos, const float3 &vNormal, const float3 &vDir, ID idMtl, float fSpeed, float3 *pvNewDir, float *pfNewSpeed)
+{
+	//m_fArmorPiercing ^ => chance ^
+	//fDurability ^ => chance ^
+	//angle(-dir, normal) > 87 && m_isBursting => chance = 0
+	//if(angle(-dir, normal) -> 90)
+	//{
+	//	fDensity ^ => xAngle ^
+	//}
+	//else if(angle(-dir, normal) -> 0)
+	//{
+	//	fDensity ^ => chance ^
+	//	if(canHole())
+	//	{
+	//		chance = 0
+	//	}
+	//	else
+	//	{
+	//		chance ^
+	//	}
+	//}
+	//m_isExpansive => chance = 0
+	//
+	float fDurability = 10;
+	float fDensity = 1000;
+	bool isNewSpeedSet = false;
+	if(ID_VALID(idMtl))
+	{
+		fDurability = SML_MtlGetPenetration(idMtl); // прочность
+		fDensity = SML_MtlGetDensity(idMtl); // плотность
+		if(fDurability <= 0)
+		{
+			fDurability = 10;
+		}
+	}
+	float fChance = 0.05f;
+	float fXangle = 0.0f;
+	float fAngleDirNormal = SMToAngle(acosf(SMVector3Dot(-vDir, vNormal)));
+
+	if(m_isBursting && fAngleDirNormal > 87.0f)
+	{
+		fChance = 0.0f;
+	}
+	else
+	{
+		fChance += (1.0f - pow(0.9f, m_fArmorPiercing)) * 0.4f + (1.0f - pow(0.9f, fDurability)) * 0.4f;
+	}
+
+	if(fAngleDirNormal > 80.0f)
+	{
+		fXangle = (1.0f - pow(0.99f, fDensity)) * 45.0f * (true /* RIFLE_TYPE == RIFLE_TYPE_RIGHT */ ? 1.0f : -1.0f); // RIFLE_TYPE_UNRIFLED : 0.0f
+		// @FIXME: consider m_fRifleStep too
+	}
+	//else if(fAngleDirNormal < 10.0f)
+	{
+		fChance += (1.0f - pow(0.9f, fDensity)) * 0.5f;
+		if(canHole(fDurability, fSpeed, pfNewSpeed))
+		{
+			fChance = 0.0f;
+			isNewSpeedSet = true;
+		}
+		else
+		{
+			fChance += 0.05f;
+		}
+	}
+
+	if(m_isExpansive)
+	{
+		fChance = 0.0f;
+	}
+
+	fChance *= 0.1f;
+
+	bool bRecochet = randf(0.0f, 1.0f) < fChance;
+
+	if(bRecochet)
+	{
+		*pvNewDir = SMQuaternion(SMVector3Cross(vDir, vNormal), SMToRadian(fAngleDirNormal)) * SMQuaternion(vNormal, fXangle) * vNormal;
+	}
+	else
+	{
+		*pvNewDir = vDir;
+	}
+
+	if(!isNewSpeedSet)
+	{
+		if(bRecochet)
+		{
+			*pfNewSpeed = fSpeed;
+			//if(fXangle > 0.0f)
+			{
+				*pfNewSpeed *= 0.9f;
+			}
+		}
+		else
+		{
+			*pfNewSpeed = 0.0f;
+		}
+	}
+
+	//pvNewDir
+	//pfNewSpeed
+
+	return(bRecochet);
+}
+
+bool CBaseAmmo::canHole(float fDurability, float fCurrentSpeed, float *pfNewSpeed)
+{
+	// based on m_fNextBarrierDepth
+
+	if(m_fNextBarrierDepth < SIMD_EPSILON)
+	{
+		//*pfNewSpeed = fCurrentSpeed;
+		//return(true);
+		m_fNextBarrierDepth = 0.001f;
+	}
+
+	float fCurrentEnergy = m_fBulletMass * 0.001f * fCurrentSpeed * fCurrentSpeed / 2.0f;
+
+	float fRequiredEnergy = fDurability * m_fNextBarrierDepth * 100.0f;
+
+
+	fCurrentEnergy -= fRequiredEnergy;
+
+	if(fCurrentEnergy >= 0.0f)
+	{
+		*pfNewSpeed = sqrtf(fCurrentEnergy * 2.0f / (m_fBulletMass * 0.001f));
+		return(true);
+	}
+
+	*pfNewSpeed = 0.0f;
+	return(false);
+}
+
+float CBaseAmmo::getStartSpeed() const
+{
+	return(m_fStartSpeed);
+}
\ No newline at end of file
diff --git a/source/game/BaseAmmo.h b/source/game/BaseAmmo.h
index cb706c3bb..c7524e22f 100644
--- a/source/game/BaseAmmo.h
+++ b/source/game/BaseAmmo.h
@@ -13,6 +13,10 @@ See the license in LICENSE
 #define __BASE_AMMO_H
 
 #include "BaseSupply.h"
+#include "BaseCharacter.h"
+
+#define BULLET_MAX_JUMPS 32
+#define BULLET_STEP_DISTANCE 5.0f /* 5 метров */
 
 /*! Базовый класс патронов
 \ingroup cbaseitem
@@ -25,7 +29,14 @@ public:
 	DECLARE_CONSTRUCTOR();
 
 	//! Стреляет пулю
-	void fire(const float3 &vStart, const float3 &vEnd);
+	void fire(const float3 &vStart, const float3 &vDir, CBaseCharacter *pAttacker=NULL);
+
+	bool isAmmo() const
+	{
+		return(true);
+	}
+
+	float getStartSpeed() const;
 
 protected:
 
@@ -35,6 +46,34 @@ protected:
 	float m_fBulletMass;
 	//! Бронебойность
 	float m_fArmorPiercing;
+	//! Экспансивная?
+	bool m_isExpansive;
+	//! Разрывная?
+	bool m_isBursting;
+
+	struct HitPoint
+	{
+		float3_t vPosition;
+		float3_t vNormal;
+		const btCollisionObject *pCollisionObject;
+		float fFraction;
+		bool isExit;
+		btCollisionWorld::LocalShapeInfo shapeInfo;
+	};
+
+	//! Рисует декаль в точке попадания
+	void shootDecal(const float3 &vPos, const float3 &vNormal, ID idMtl);
+
+	//! Определяет, был ли рикошет
+	bool shouldRecochet(const float3 &vPos, const float3 &vNormal, const float3 &vDir, ID idMtl, float fSpeed, float3 *pvNewDir, float *pfNewSpeed);
+
+	//! Определяет, может ли пуля пробить препятствие
+	bool canHole(float fDurability, float fCurrentSpeed, float *pfNewSpeed);
+
+	//! Хранит толщину препятствия во время расчета выстрела. Изменяется во время полета от препятствия к препятствию
+	float m_fNextBarrierDepth;
+
+	void fire(const float3 &vStart, const float3 &vDir, CBaseCharacter *pAttacker, float fSpeed);
 };
 
 #endif
diff --git a/source/game/BaseAnimating.h b/source/game/BaseAnimating.h
index 289d4e76e..9ed6eb6e9 100644
--- a/source/game/BaseAnimating.h
+++ b/source/game/BaseAnimating.h
@@ -48,7 +48,7 @@ public:
 	bool playingAnimations(const char* name);
 	void setPos(const float3 & pos);
 	void setOrient(const SMQuaternion & q);
-
+	
 protected:
 	IAnimPlayer * m_pAnimPlayer;
 	const char * m_szModelFile;
@@ -56,7 +56,7 @@ protected:
 
 	virtual void initPhysics();
 	virtual void createPhysBody();
-	void releasePhysics();
+	virtual void releasePhysics();
 	virtual void removePhysBody();
 
 	btCollisionShape * m_pCollideShape;
diff --git a/source/game/BaseCharacter.cpp b/source/game/BaseCharacter.cpp
index 3508cbb84..85c53176d 100644
--- a/source/game/BaseCharacter.cpp
+++ b/source/game/BaseCharacter.cpp
@@ -48,7 +48,8 @@ CBaseCharacter::CBaseCharacter(CEntityManager * pMgr):
 	m_uMoveDir(PM_OBSERVER),
 	m_vPitchYawRoll(float3_t(0, 0, 0)),
 	m_pActiveTool(NULL),
-	m_fCurrentSpread(0.0f)
+	m_fCurrentSpread(0.0f),
+	m_pHitboxBodies(NULL)
 {
 	btTransform startTransform;
 	startTransform.setIdentity();
@@ -73,9 +74,9 @@ CBaseCharacter::CBaseCharacter(CEntityManager * pMgr):
 	m_pCharacter->setFallSpeed(300.0f);
 	//m_pCharacter->setFallSpeed(30.0f);
 
-	SXPhysics_GetDynWorld()->addCollisionObject(m_pGhostObject, btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::AllFilter & ~btBroadphaseProxy::DebrisFilter);
+	SXPhysics_GetDynWorld()->addCollisionObject(m_pGhostObject, CG_CHARACTER, CG_ALL & ~(CG_DEBRIS | CG_HITBOX | CG_WATER));
 
-	m_pGhostObject->setCollisionFlags(m_pGhostObject->getCollisionFlags() | btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT);
+	//m_pGhostObject->setCollisionFlags(m_pGhostObject->getCollisionFlags() | btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT);
 
 	SXPhysics_GetDynWorld()->addAction(m_pCharacter);
 
@@ -90,14 +91,18 @@ CBaseCharacter::CBaseCharacter(CEntityManager * pMgr):
 	m_flashlight->setColor(float3(3.5, 3.5, 3.5));
 	//m_flashlight->setShadowType(-1);
 	m_flashlight->setShadowType(1);
+	m_flashlight->setEnable(false);
 
 	m_idTaskSpread = SET_INTERVAL(updateSpread, 1.0f / 30.0f);
+
+	m_pInventory = new CCharacterInventory(this);
 }
 
 CBaseCharacter::~CBaseCharacter()
 {
 	CLEAR_INTERVAL(m_idTaskSpread);
 	REMOVE_ENTITY(m_flashlight);
+	mem_delete(m_pInventory);
 }
 
 
@@ -148,10 +153,9 @@ void CBaseCharacter::nextFireMode()
 	{
 		return;
 	}
-	if(m_pActiveTool)
+	if(m_pActiveTool && m_pActiveTool->isWeapon())
 	{
-		//@FIXME: Add correct call
-		//m_pActiveTool->nextFireMode();
+		((CBaseWeapon*)m_pActiveTool)->nextFireMode();
 	}
 }
 
@@ -171,7 +175,7 @@ void CBaseCharacter::playFootstepsSound()
 			btKinematicClosestNotMeRayResultCallback cb(m_pGhostObject, F3_BTVEC(start), F3_BTVEC(end));
 			SXPhysics_GetDynWorld()->rayTest(F3_BTVEC(start), F3_BTVEC(end), cb);
 
-			if(cb.hasHit() && cb.m_shapeInfo.m_shapePart == 0 && cb.m_shapeInfo.m_triangleIndex >= 0)
+			if(cb.hasHit()/* && cb.m_shapeInfo.m_shapePart == 0 && cb.m_shapeInfo.m_triangleIndex >= 0*/)
 			{
 				MTLTYPE_PHYSIC type = (MTLTYPE_PHYSIC)SXPhysics_GetMtlType(cb.m_collisionObject, &cb.m_shapeInfo);
 				g_pGameData->playFootstepSound(type, BTVEC_F3(cb.m_hitPointWorld));
@@ -249,3 +253,131 @@ float CBaseCharacter::getCurrentSpread()
 {
 	return(m_fCurrentSpread);
 }
+
+void CBaseCharacter::initHitboxes()
+{
+	if(!m_pAnimPlayer)
+	{
+		return;
+	}
+
+	int l = m_pAnimPlayer->getHitboxCount();
+	m_pHitboxBodies = new btRigidBody*[l];
+
+	const ModelHitbox * hb;
+	for(int i = 0; i < l; ++i)
+	{
+		hb = m_pAnimPlayer->getHitbox(i);
+		btCollisionShape *pShape;
+		switch(hb->type)
+		{
+		case HT_BOX:
+			pShape = new btBoxShape(F3_BTVEC(hb->lwh * 0.5f * m_fBaseScale));
+			break;
+		case HT_CAPSULE:
+			pShape = new btCapsuleShape(hb->lwh.y * 0.5f * m_fBaseScale, hb->lwh.z * m_fBaseScale);
+			break;
+		case HT_CYLINDER:
+			pShape = new btCylinderShape(F3_BTVEC(hb->lwh * 0.5f * m_fBaseScale));
+			break;
+		case HT_ELIPSOID:
+			// @FIXME: Add actual elipsoid shape
+			pShape = new btSphereShape(hb->lwh.x);
+			break;
+		case HT_CONVEX:
+			assert(false && "Not supported here");
+		}
+		btVector3 vInertia;
+		const float fMass = 1.0f;
+		pShape->calculateLocalInertia(fMass, vInertia);
+
+		btDefaultMotionState * motionState = new btDefaultMotionState();
+
+		btRigidBody::btRigidBodyConstructionInfo rigidBodyCI(
+			fMass,                  // mass
+			motionState,        // initial position
+			pShape,    // collision shape of body
+			vInertia  // local inertia
+			);
+		btRigidBody * pRigidBody = new btRigidBody(rigidBodyCI);
+		pRigidBody->setUserPointer(this);
+
+		pRigidBody->setAngularFactor(0.0f);
+		pRigidBody->setLinearFactor(btVector3(0.0f, 0.0f, 0.0f));
+
+		SXPhysics_AddShapeEx(pRigidBody, CG_HITBOX, CG_BULLETFIRE);
+		m_pHitboxBodies[i] = pRigidBody;
+	}
+
+	updateHitboxes();
+}
+
+void CBaseCharacter::updateHitboxes()
+{
+	if(!m_pAnimPlayer || !m_pHitboxBodies)
+	{
+		return;
+	}
+
+	const ModelHitbox * hb;
+	for(int i = 0, l = m_pAnimPlayer->getHitboxCount(); i < l; ++i)
+	{
+		hb = m_pAnimPlayer->getHitbox(i);
+		
+		//SMMATRIX mBone = m_pAnimPlayer->getBoneTransformPos(hb->bone_id);
+
+		m_pHitboxBodies[i]->getWorldTransform().setFromOpenGLMatrix((btScalar*)&(SMMatrixRotationX(hb->rot.x)
+			* SMMatrixRotationY(hb->rot.y)
+			* SMMatrixRotationZ(hb->rot.z)
+			* SMMatrixTranslation(hb->pos * m_fBaseScale)
+			* m_pAnimPlayer->getBoneTransform(hb->bone_id, true)
+			* getWorldTM()
+			));
+	}
+}
+
+void CBaseCharacter::releaseHitboxes()
+{
+	if(!m_pAnimPlayer || !m_pHitboxBodies)
+	{
+		return;
+	}
+
+	for(int i = 0, l = m_pAnimPlayer->getHitboxCount(); i < l; ++i)
+	{
+		SXPhysics_RemoveShape(m_pHitboxBodies[i]);
+
+		btMotionState * motionState = m_pHitboxBodies[i]->getMotionState();
+
+		mem_delete(motionState);
+
+		btCollisionShape * pShape = m_pHitboxBodies[i]->getCollisionShape();
+		mem_delete(pShape);
+		mem_delete(m_pHitboxBodies[i]);
+	}
+
+	mem_delete_a(m_pHitboxBodies);
+}
+
+void CBaseCharacter::onSync()
+{
+	BaseClass::onSync();
+
+	updateHitboxes();
+}
+
+void CBaseCharacter::initPhysics()
+{
+	initHitboxes();
+}
+
+void CBaseCharacter::releasePhysics()
+{
+	releaseHitboxes();
+}
+
+/*void CBaseCharacter::dispatchDamage(CTakeDamageInfo &takeDamageInfo)
+{
+	// adjust damage by bodypart
+	BaseClass::dispatchDamage(takeDamageInfo);
+}*/
diff --git a/source/game/BaseCharacter.h b/source/game/BaseCharacter.h
index 75009b222..9b096eb2f 100644
--- a/source/game/BaseCharacter.h
+++ b/source/game/BaseCharacter.h
@@ -18,6 +18,7 @@ See the license in LICENSE
 
 #include "BaseAnimating.h"
 #include "LightDirectional.h"
+#include "CharacterInventory.h"
 
 class CBaseTool;
 
@@ -68,6 +69,26 @@ public:
 	//! �������� ������������ ��� �������� ������ ����������� �������� (� ������ �������� ��������)
 	float getCurrentSpread();
 
+	btCollisionObject *getBtCollisionObject() const
+	{
+		return(m_pGhostObject);
+	}
+	void initHitboxes();
+	void releaseHitboxes();
+	void updateHitboxes();
+
+	void onSync();
+
+	void initPhysics();
+	void releasePhysics();
+
+	//void dispatchDamage(CTakeDamageInfo &takeDamageInfo);
+
+	CCharacterInventory * getInventory()
+	{
+		return(m_pInventory);
+	}
+
 protected:
 	//! �������
 	CLightDirectional* m_flashlight;
@@ -83,6 +104,7 @@ protected:
 	btRigidBody * m_pRigidBody;
 	btPairCachingGhostObject * m_pGhostObject;
 	btKinematicCharacterController * m_pCharacter;
+	btRigidBody ** m_pHitboxBodies;
 	//! @}
 
 	//! ���� �������� ������
@@ -99,6 +121,10 @@ protected:
 
 	//! ����������� �������� ��������
 	float m_fCurrentSpread;
+
+	CCharacterInventory * m_pInventory;
+
+
 };
 
 #endif
diff --git a/source/game/BaseEntity.cpp b/source/game/BaseEntity.cpp
index 40ddce8f2..1101d3ab0 100644
--- a/source/game/BaseEntity.cpp
+++ b/source/game/BaseEntity.cpp
@@ -27,6 +27,8 @@ BEGIN_PROPTABLE_NOBASE(CBaseEntity)
 	DEFINE_FIELD_FLAGS(m_iFlags, 0, "flags", "Flags", EDITOR_FLAGS)
 	//! Объект-владелец
 	DEFINE_FIELD_ENTITY(m_pOwner, PDFF_NOEXPORT | PDFF_NOEDIT, "owner", "", EDITOR_NONE)
+	//! Здоровье
+	DEFINE_FIELD_FLOAT(m_fHealth, PDFF_NOEXPORT | PDFF_NOEDIT, "health", "", EDITOR_NONE)
 
 	//DEFINE_FIELD_STRING(m_szName, 0, "some opt", "Option", EDITOR_COMBOBOX)
 	//	COMBO_OPTION("Option 1", "value 1")
@@ -62,7 +64,8 @@ CBaseEntity::CBaseEntity(CEntityManager * pWorld):
 	m_szName(NULL),
 	m_pParent(NULL),
 	m_iParentAttachment(-1),
-	m_pOwner(NULL)/*,
+	m_pOwner(NULL),
+	m_fHealth(100.0f)/*,
 	m_vDiscreteLinearVelocity(float3_t(0.0f, 0.0f, 0.0f))*/
 {
 	m_iId = pWorld->reg(this);
@@ -644,4 +647,37 @@ void CBaseEntity::updateOutputs()
 		}
 		pt = pt->pBaseProptable;
 	}
-}
\ No newline at end of file
+}
+
+void CBaseEntity::dispatchDamage(CTakeDamageInfo &takeDamageInfo)
+{
+	float fHealth = takeDamageInfo.m_fDamage * 0.1f;
+	if(takeDamageInfo.m_pInflictor)
+	{
+		LibReport(REPORT_MSG_LEVEL_NOTICE, "%s damaged (" COLOR_LRED "%.2f" COLOR_RESET ") by " COLOR_YELLOW "%s\n", getClassName(), fHealth, takeDamageInfo.m_pInflictor->getClassName());
+	}
+	else
+	{
+		LibReport(REPORT_MSG_LEVEL_NOTICE, "%s damaged (" COLOR_LRED "%.2f" COLOR_RESET ")\n", getClassName(), fHealth);
+	}
+	takeHealth(fHealth);
+}
+
+void CBaseEntity::takeHealth(float fVal)
+{
+	if(m_fHealth <= 0.0f)
+	{
+		return;
+	}
+	m_fHealth -= fVal;
+	if(m_fHealth <= 0.0f)
+	{
+		onDeath();
+	}
+}
+
+void CBaseEntity::onDeath()
+{
+	LibReport(REPORT_MSG_LEVEL_NOTICE, "Entity %s died!\n", getClassName());
+	// do nothing
+}
diff --git a/source/game/BaseEntity.h b/source/game/BaseEntity.h
index 30a24e916..24195cf66 100644
--- a/source/game/BaseEntity.h
+++ b/source/game/BaseEntity.h
@@ -27,6 +27,8 @@ See the license in LICENSE
 
 #include "proptable.h"
 
+#include "TakeDamageInfo.h"
+
 #pragma pointers_to_members(full_generality, virtual_inheritance)
 
 #pragma warning(push)
@@ -99,6 +101,10 @@ public:
 	//void updateDiscreteLinearVelocity(int step, float dt);
 	//const float3_t & getDiscreteLinearVelocity() const;
 
+	virtual void dispatchDamage(CTakeDamageInfo &takeDamageInfo);
+
+	virtual void onDeath();
+
 private:
 	void setClassName(const char * name);
 	void setDefaults();
@@ -162,6 +168,11 @@ protected:
 	\note только для внутреннего использования
 	*/
 	void updateOutputs();
+	
+	//! здоровье [0,+inf]
+	float m_fHealth;
+
+	void takeHealth(float fVal);
 };
 
 #pragma warning(pop)
diff --git a/source/game/BaseItem.cpp b/source/game/BaseItem.cpp
index 572fc8414..b79eb9ff1 100644
--- a/source/game/BaseItem.cpp
+++ b/source/game/BaseItem.cpp
@@ -31,7 +31,7 @@ CBaseItem::CBaseItem(CEntityManager * pMgr):
 	BaseClass(pMgr),
 	m_bInvStackable(true),
 	m_iInvStackCurSize(0),
-	m_iInvStackMaxSize(0),
+	m_iInvStackMaxSize(1),
 	m_iInvWeight(0.0f),
 	m_bPickable(true)
 {
diff --git a/source/game/BaseMag.cpp b/source/game/BaseMag.cpp
index 89a969c8f..a78317531 100644
--- a/source/game/BaseMag.cpp
+++ b/source/game/BaseMag.cpp
@@ -40,4 +40,8 @@ int CBaseMag::getCapacity()
 void CBaseMag::load(int count)
 {
 	m_iCurrentLoad += count;
+	if(m_iCurrentLoad > m_iCapacity)
+	{
+		m_iCurrentLoad = m_iCapacity;
+	}
 }
diff --git a/source/game/BaseSupply.h b/source/game/BaseSupply.h
index e95af1a0a..290b24843 100644
--- a/source/game/BaseSupply.h
+++ b/source/game/BaseSupply.h
@@ -23,6 +23,11 @@ class CBaseSupply: public CBaseItem
 	DECLARE_PROPTABLE();
 public:
 	DECLARE_TRIVIAL_CONSTRUCTOR();
+
+	virtual bool isAmmo() const
+	{
+		return(false);
+	}
 };
 
 #endif
diff --git a/source/game/BaseTool.cpp b/source/game/BaseTool.cpp
index 13eab5f92..af21ecfa2 100644
--- a/source/game/BaseTool.cpp
+++ b/source/game/BaseTool.cpp
@@ -46,6 +46,8 @@ BEGIN_PROPTABLE(CBaseTool)
 	DEFINE_FIELD_FLOAT(m_fMaxDistance, PDFF_NOEDIT | PDFF_NOEXPORT, "max_distance", "", EDITOR_NONE)
 	//! Подходящие типы припасов, классы, через запятую
 	DEFINE_FIELD_STRING(m_szUsableAmmos, PDFF_NOEDIT | PDFF_NOEXPORT, "ammos", "", EDITOR_NONE)
+	//! Класс заряженного в данный момент припаса
+	DEFINE_FIELD_STRING(m_szLoadedAmmo, PDFF_NOEDIT | PDFF_NOEXPORT, "loaded_ammo", "", EDITOR_NONE)
 
 END_PROPTABLE()
 
@@ -66,7 +68,8 @@ CBaseTool::CBaseTool(CEntityManager * pMgr):
 	m_iMuzzleFlash(-1),
 	m_iMuzzleFlash2(-1),
 	m_fMaxDistance(1000.0f),
-	m_bIsWeapon(false)
+	m_bIsWeapon(false),
+	m_pLoadedAmmo(NULL)
 {
 	m_bInvStackable = false;
 
@@ -107,7 +110,7 @@ void CBaseTool::setNextUse(float time)
 }
 bool CBaseTool::canUse()
 {
-	return(m_bCanUse);
+	return(m_bCanUse && !m_bInPrimaryAction);
 }
 
 void CBaseTool::primaryAction(BOOL st)
@@ -284,3 +287,51 @@ float CBaseTool::getCondition() const
 {
 	return(1.0f);
 }
+
+CBaseSupply *CBaseTool::getAmmo() const
+{
+	return(m_pLoadedAmmo);
+}
+
+void CBaseTool::chargeAmmo(CBaseSupply *pAmmo)
+{
+	if(!pAmmo || !isValidAmmo(pAmmo))
+	{
+		return;
+	}
+	if(getAmmo())
+	{
+		uncharge();
+	}
+	m_pLoadedAmmo = pAmmo;
+	m_pLoadedAmmo->setParent(this);
+	_setStrVal(&m_szLoadedAmmo, m_pLoadedAmmo->getClassName());
+}
+
+void CBaseTool::uncharge()
+{
+	if(!getAmmo())
+	{
+		return;
+	}
+	_setStrVal(&m_szLoadedAmmo, "");
+	m_pLoadedAmmo->setParent(NULL);
+	//getOwner()->getInventory()->put(getAmmo());
+	m_pLoadedAmmo = NULL;
+}
+
+bool CBaseTool::isValidAmmo(CBaseSupply *pAmmo)
+{
+	const char * str = strstr(m_szUsableAmmos, pAmmo->getClassName());
+	if(str)
+	{
+		char chr = str[strlen(pAmmo->getClassName())];
+		return(chr == ',' || chr == 0);
+	}
+	return(false);
+}
+
+float CBaseTool::getMaxDistance() const
+{
+	return(m_fMaxDistance);
+}
diff --git a/source/game/BaseTool.h b/source/game/BaseTool.h
index 18b2e0cf6..d5c9edc4a 100644
--- a/source/game/BaseTool.h
+++ b/source/game/BaseTool.h
@@ -13,6 +13,7 @@ See the license in LICENSE
 #define __BASE_TOOL_H
 
 #include "BaseItem.h"
+#include "BaseSupply.h"
 #include <score/sxscore.h>
 
 enum
@@ -65,8 +66,19 @@ public:
 	//! Состояние: 1 - целое; 0 - сломанное
 	float getCondition() const;
 
+	//! Объект заряженного припаса
+	CBaseSupply *getAmmo() const;
+	//! Зарядить
+	void chargeAmmo(CBaseSupply *pAmmo);
+	//! Разрядить
+	void uncharge();
+
+	float getMaxDistance() const;
+
 protected:
 
+	bool isValidAmmo(CBaseSupply *pAmmo);
+
 	bool m_bInPrimaryAction;
 	bool m_bInSecondaryAction;
 
@@ -110,6 +122,8 @@ protected:
 	const char * m_szSecondaryActionMuzzleflash;
 
 	const char * m_szUsableAmmos;
+	const char * m_szLoadedAmmo;
+	CBaseSupply * m_pLoadedAmmo;
 
 	float m_fMaxDistance;
 
diff --git a/source/game/BaseTrigger.cpp b/source/game/BaseTrigger.cpp
index b73b78ddb..84b5316c7 100644
--- a/source/game/BaseTrigger.cpp
+++ b/source/game/BaseTrigger.cpp
@@ -64,7 +64,7 @@ void CBaseTrigger::enable()
 		m_idUpdateInterval = SET_INTERVAL(update, 0);
 		if(m_pGhostObject)
 		{
-			SXPhysics_GetDynWorld()->addCollisionObject(m_pGhostObject, btBroadphaseProxy::SensorTrigger, btBroadphaseProxy::CharacterFilter);
+			SXPhysics_GetDynWorld()->addCollisionObject(m_pGhostObject, CG_TRIGGER, CG_CHARACTER);
 		}
 	}
 }
@@ -115,7 +115,7 @@ void CBaseTrigger::createPhysBody()
 		m_pGhostObject->setCollisionShape(m_pCollideShape);
 		m_pGhostObject->setCollisionFlags(m_pGhostObject->getCollisionFlags() ^ btCollisionObject::CF_NO_CONTACT_RESPONSE);
 
-		SXPhysics_GetDynWorld()->addCollisionObject(m_pGhostObject, btBroadphaseProxy::SensorTrigger, btBroadphaseProxy::CharacterFilter);
+		SXPhysics_GetDynWorld()->addCollisionObject(m_pGhostObject, CG_TRIGGER, CG_CHARACTER);
 	}
 }
 
diff --git a/source/game/BaseWeapon.cpp b/source/game/BaseWeapon.cpp
index 9dc7dd01e..cd5fcca5c 100644
--- a/source/game/BaseWeapon.cpp
+++ b/source/game/BaseWeapon.cpp
@@ -7,6 +7,8 @@ See the license in LICENSE
 #include <particles/sxparticles.h>
 #include "BaseWeapon.h"
 #include "Player.h"
+#include "BaseAmmo.h"
+#include "Random.h"
 
 /*! \skydocent base_weapon
 Базовый класс для оружия
@@ -26,11 +28,11 @@ BEGIN_PROPTABLE(CBaseWeapon)
 	//! Доступные режимы стрельбы
 	DEFINE_FIELD_STRING(m_szFireModes, PDFF_NOEDIT | PDFF_NOEXPORT, "fire_modes", "", EDITOR_NONE)
 	//! Скорострельность одиночными
-	DEFINE_FIELD_INT(m_iSingleSpeed, PDFF_NOEDIT | PDFF_NOEXPORT, "single_speed", "", EDITOR_NONE)
+	DEFINE_FIELD_INT(m_iSingleRate, PDFF_NOEDIT | PDFF_NOEXPORT, "single_rate", "", EDITOR_NONE)
 	//! Скорострельность в автоматическом режиме
-	DEFINE_FIELD_INT(m_iBurstSpeed, PDFF_NOEDIT | PDFF_NOEXPORT, "burst_speed", "", EDITOR_NONE)
+	DEFINE_FIELD_INT(m_iBurstRate, PDFF_NOEDIT | PDFF_NOEXPORT, "burst_rate", "", EDITOR_NONE)
 	//! Скорострельность отсечками
-	DEFINE_FIELD_INT(m_iCutoffSpeed, PDFF_NOEDIT | PDFF_NOEXPORT, "cutoff_speed", "", EDITOR_NONE)
+	DEFINE_FIELD_INT(m_iCutoffRate, PDFF_NOEDIT | PDFF_NOEXPORT, "cutoff_rate", "", EDITOR_NONE)
 	//! Патронов в отсечке
 	DEFINE_FIELD_INT(m_iCutoffSize, PDFF_NOEDIT | PDFF_NOEXPORT, "cutoff_size", "", EDITOR_NONE)
 	//! Текущий режим стрельбы
@@ -78,12 +80,23 @@ BEGIN_PROPTABLE(CBaseWeapon)
 	DEFINE_FIELD_FLOAT(m_fSpreadArm, PDFF_NOEDIT | PDFF_NOEXPORT, "spread_arm", "", EDITOR_NONE)
 	//! коэффициент разброса в прицеливании
 	DEFINE_FIELD_FLOAT(m_fSpreadIronSight, PDFF_NOEDIT | PDFF_NOEXPORT, "spread_ironsight", "", EDITOR_NONE)
+
+	//! Дальность пристрелки
+	DEFINE_FIELD_FLOAT(m_fAimingRange, PDFF_NOEDIT | PDFF_NOEXPORT, "aiming_range", "", EDITOR_NONE)
+
+	//! тип нарезки ствола: 0 - гладкоствольное; -1 - левая; 1 - правая
+	DEFINE_FIELD_INT(m_rifleType, PDFF_NOEDIT | PDFF_NOEXPORT, "rifle_type", "", EDITOR_NONE)
+	//! шаг нарезки ствола (мм)
+	DEFINE_FIELD_FLOAT(m_fRifleStep, PDFF_NOEDIT | PDFF_NOEXPORT, "rifle_step", "", EDITOR_NONE)
 END_PROPTABLE()
 
 REGISTER_ENTITY_NOLISTING(CBaseWeapon, base_weapon);
 
 CBaseWeapon::CBaseWeapon(CEntityManager * pMgr):
 	BaseClass(pMgr),
+
+	m_idTaskShoot(-1),
+
 	m_pSilencer(NULL),
 	m_pScope(NULL),
 	m_pHandle(NULL),
@@ -110,7 +123,9 @@ CBaseWeapon::CBaseWeapon(CEntityManager * pMgr):
 	m_fSpreadAirborne(5.0f),
 	m_fSpreadCondition(3.0f),
 	m_fSpreadArm(3.0f),
-	m_fSpreadIronSight(-0.8f)
+	m_fSpreadIronSight(-0.8f),
+
+	m_fAimingRange(100.f)
 {
 	m_bIsWeapon = true;
 }
@@ -189,38 +204,39 @@ bool CBaseWeapon::setKV(const char * name, const char * value)
 
 void CBaseWeapon::primaryAction(BOOL st)
 {
-	m_bInPrimaryAction = st != FALSE;
+	//m_bInPrimaryAction = st != FALSE;
+	
 	if(st)
 	{
-		playAnimation("shoot1");
-		if(ID_VALID(m_iMuzzleFlash))
+		if(canUse())
 		{
-			SPE_EffectEnableSet(m_iMuzzleFlash, true);
-		}
-		if(ID_VALID(m_iSoundAction1))
-		{
-			SSCore_SndInstancePlay3d(m_iSoundAction1, &getPos());
+			m_bInPrimaryAction = true;
+			switch(m_fireMode)
+			{
+			case FIRE_MODE_BURST:
+				m_idTaskShoot = SET_INTERVAL(taskShoot, 60.0f / (float)m_iBurstRate);
+				break;
+			case FIRE_MODE_CUTOFF:
+				m_iCutoffCurrent = 0;
+				m_idTaskShoot = SET_INTERVAL(taskShoot, 60.0f / (float)m_iCutoffRate);
+				break;
+			case FIRE_MODE_SINGLE:
+				setNextUse(60.0f / (float)m_iSingleRate);
+				break;
+			}
+			taskShoot(0.0f);
 		}
-
-		//((CPlayer*)m_pOwner)->is
-
-		//trace line
-		float3 start = getPos();
-		float3 dir = m_pParent->getOrient() * float3(0.0f, 0.0f, 1.0f);
-		float3 end = start + dir * m_fMaxDistance;
-		btCollisionWorld::ClosestRayResultCallback cb(F3_BTVEC(start), F3_BTVEC(end));
-		SXPhysics_GetDynWorld()->rayTest(F3_BTVEC(start), F3_BTVEC(end), cb);
-
-		if(cb.hasHit())
+	}
+	else
+	{
+		if(m_fireMode != FIRE_MODE_CUTOFF)
 		{
-			//shoot decal
-			//SXDecals_ShootDecal(DECAL_TYPE_CONCRETE, BTVEC_F3(cb.m_hitPointWorld), BTVEC_F3(cb.m_hitNormalWorld));
-			SPE_EffectPlayByName("fire", &BTVEC_F3(cb.m_hitPointWorld), &BTVEC_F3(cb.m_hitNormalWorld));
-			if(!cb.m_collisionObject->isStaticOrKinematicObject())
+			if(ID_VALID(m_idTaskShoot))
 			{
-				((btRigidBody*)cb.m_collisionObject)->applyCentralImpulse(F3_BTVEC(dir * 10.0f));
-				cb.m_collisionObject->activate();
+				CLEAR_INTERVAL(m_idTaskShoot);
+				m_idTaskShoot = -1;
 			}
+			m_bInPrimaryAction = false;
 		}
 	}
 }
@@ -248,27 +264,53 @@ void CBaseWeapon::reload()
 	}
 	if(canUse())
 	{
-		//int count = m_pOwner->getInventory()->consumeItems("ammo_5.45x39ps", m_pMag->getCapacity() - m_pMag->getLoad() + m_iCapacity - m_iCurrentLoad);
-		//count += m_iCurrentLoad;
-		//m_iCurrentLoad = min(count, m_iCapacity);
-		//count -= m_iCurrentLoad;
-		//m_pMag->load(count);
-
-		setNextUse(m_fReloadTime);
-		playAnimation("reload");
-		if(ID_VALID(m_idSndReload))
+		int iWantLoad = m_pMag->getCapacity() - m_pMag->getLoad()/* + m_iCapacity - m_iCurrentLoad */;
+		if(iWantLoad <= 0)
+		{
+			printf(COLOR_MAGENTA "Mag full!\n" COLOR_RESET);
+			return;
+		}
+		int count = ((CBaseCharacter*)m_pOwner)->getInventory()->consumeItems(m_szLoadedAmmo, iWantLoad);
+		if(count)
+		{
+			bool isFast = m_iCapacity == m_iCurrentLoad;
+
+			count += m_iCurrentLoad;
+			m_iCurrentLoad = min(count, m_iCapacity);
+			count -= m_iCurrentLoad;
+			m_pMag->load(count);
+
+			setNextUse(m_fReloadTime);
+			playAnimation(isFast ? "reload_fast" : "reload");
+			if(isFast)
+			{
+				/*if(ID_VALID(m_idSndReloadFast))
+				{
+					SSCore_SndInstancePlay3d(m_idSndReloadFast, &getPos());
+				}*/
+			}
+			else
+			{
+				if(ID_VALID(m_idSndReload))
+				{
+					SSCore_SndInstancePlay3d(m_idSndReload, &getPos());
+				}
+			}
+		}
+		else
 		{
-			SSCore_SndInstancePlay3d(m_idSndReload, &getPos());
+			printf(COLOR_MAGENTA "No more bullets!\n" COLOR_RESET);
+			return;
 		}
 	}
 }
 
 void CBaseWeapon::setFireMode(FIRE_MODE mode)
 {
-	if(!(m_iFireModes & mode))
+	if((m_iFireModes & mode) && canUse())
 	{
 		m_fireMode = mode;
-		if(ID_VALID(m_idSndReload))
+		if(ID_VALID(m_idSndSwitch))
 		{
 			SSCore_SndInstancePlay3d(m_idSndSwitch, &getPos());
 		}
@@ -286,7 +328,7 @@ void CBaseWeapon::nextFireMode()
 	while(newMode != cur && !(m_iFireModes & (1 << newMode)));
 	if(newMode != cur)
 	{
-		setFireMode((FIRE_MODE)newMode);
+		setFireMode((FIRE_MODE)(1 << newMode));
 	}
 }
 
@@ -340,3 +382,128 @@ float CBaseWeapon::getSpreadCoeff(SPREAD_COEFF what) const
 	}
 	return(1.0f);
 }
+
+float3 CBaseWeapon::applySpread(const float3 &vDir, float fSpread)
+{
+	float3 vRight, vUp;
+
+	if(vDir.x == 0.0f && vDir.y == 0.0f)
+	{
+		vRight = float3(0.0f, -1.0f, 0.0f);
+		vUp = float3(-vDir.z, 0.0f, 0.0f);
+	}
+	else
+	{
+		vRight = SMVector3Normalize(SMVector3Cross(vDir, float3((0.0f, 0.0f, 1.0f))));
+		vUp = SMVector2Normalize(SMVector3Cross(vRight, vDir));
+	}
+
+
+
+	float x, y, z;
+	const float flatness = 0.5f;
+	CRandom random;
+
+	do
+	{
+		x = random.getRandomFloat(-1, 1) * flatness + random.getRandomFloat(-1, 1) * (1 - flatness);
+		y = random.getRandomFloat(-1, 1) * flatness + random.getRandomFloat(-1, 1) * (1 - flatness);
+
+		z = x*x + y*y;
+	}
+	while(z > 1);
+
+	fSpread = tanf(fSpread);
+
+	return(vDir + x * fSpread * vRight + y * fSpread * vUp);
+}
+
+void CBaseWeapon::taskShoot(float dt)
+{
+	if(m_fireMode == FIRE_MODE_CUTOFF)
+	{
+		++m_iCutoffCurrent;
+		if(m_iCutoffCurrent > m_iCutoffSize)
+		{
+			if(ID_VALID(m_idTaskShoot))
+			{
+				CLEAR_INTERVAL(m_idTaskShoot);
+				m_idTaskShoot = -1;
+			}
+			m_bInPrimaryAction = false;
+			return;
+		}
+	}
+
+	CBaseAmmo *pAmmo = NULL;
+	if(canShoot() && getAmmo() && getAmmo()->isAmmo())
+	{
+		pAmmo = (CBaseAmmo*)getAmmo();
+	}
+	else
+	{
+		if(ID_VALID(m_idSndEmpty))
+		{
+			SSCore_SndInstancePlay3d(m_idSndEmpty, &getPos());
+		}
+		if(ID_VALID(m_idTaskShoot))
+		{
+			CLEAR_INTERVAL(m_idTaskShoot);
+			m_idTaskShoot = -1;
+		}
+		m_bInPrimaryAction = false;
+		return;
+	}
+
+	playAnimation("shoot1");
+	if(ID_VALID(m_iMuzzleFlash))
+	{
+		SPE_EffectEnableSet(m_iMuzzleFlash, true);
+	}
+	if(ID_VALID(m_idSndShoot))
+	{
+		SSCore_SndInstancePlay3d(m_idSndShoot, &getPos());
+	}
+
+	//((CPlayer*)m_pOwner)->is
+
+	//trace line
+	float3 start = getPos();
+	float3 dir = m_pParent->getOrient() * float3(0.0f, 0.0f, 1.0f);
+
+	dir = SMQuaternion(m_pParent->getOrient() * float3(1.0f, 0.0f, 0.0f), asinf(m_fAimingRange * 10.0f / (pAmmo->getStartSpeed() * pAmmo->getStartSpeed())) * 0.5f) * dir;
+
+
+	dir = applySpread(dir, SMToRadian(((CBaseCharacter*)getOwner())->getCurrentSpread()));
+
+	pAmmo->fire(start, dir, (CBaseCharacter*)getOwner());
+
+	if(m_pMag && m_pMag->getLoad() > 0)
+	{
+		m_pMag->load(-1);
+	}
+	else
+	{
+		--m_iCurrentLoad;
+	}
+}
+
+void CBaseWeapon::attachMag(CBaseMag * pMag)
+{
+	m_pMag = pMag;
+
+	int iNeedLoad = m_iCapacity - m_iCurrentLoad;
+	if(iNeedLoad > 0)
+	{
+		if(m_pMag->getLoad() >= iNeedLoad)
+		{
+			m_iCurrentLoad += iNeedLoad;
+			m_pMag->load(-iNeedLoad);
+		}
+		else
+		{
+			m_iCurrentLoad += m_pMag->getLoad();
+			m_pMag->load(-m_pMag->getLoad());
+		}
+	}
+}
diff --git a/source/game/BaseWeapon.h b/source/game/BaseWeapon.h
index 0092c51a2..4400c1011 100644
--- a/source/game/BaseWeapon.h
+++ b/source/game/BaseWeapon.h
@@ -43,6 +43,16 @@ enum SPREAD_COEFF
 	SPREAD_COEFF_IRONSIGHT, //!< в прицеливании
 };
 
+//! Тип нарезки ствола
+enum RIFLE_TYPE
+{
+	RIFLE_TYPE_LEFT = -1, //!< Левая
+	RIFLE_TYPE_UNRIFLED = 0, //!< Гладкоствольное
+	RIFLE_TYPE_RIGHT = 1, //!< Правая
+
+	RIFLE_TYPE_FORCE_DWORD = 0x7fffffff  /* force 32-bit size enum */
+};
+
 /*! Оружие
 \ingroup cbaseitem
 */
@@ -78,8 +88,21 @@ public:
 	//! Коэффициент разброса
 	float getSpreadCoeff(SPREAD_COEFF what) const;
 
+	//! Тип нарезки ствола
+	RIFLE_TYPE getRifleType() const {return(m_rifleType);}
+
+	//! Подсоединить магазин
+	void attachMag(CBaseMag * pMag);
 protected:
 
+	//! Распределение гаусса вектора vDir в телесном угле fSpread
+	float3 applySpread(const float3 &vDir, float fSpread);
+
+	//! Задача стрельбы
+	void taskShoot(float dt);
+
+	ID m_idTaskShoot;
+
 	// Compatible addons
 	const char * m_szAddonScopes;
 	const char * m_szAddonSilencers;
@@ -96,11 +119,13 @@ protected:
 	FIRE_MODE m_fireMode;
 	int m_iFireModes;
 	const char * m_szFireModes;
-	int m_iSingleSpeed;
-	int m_iBurstSpeed;
-	int m_iCutoffSpeed;
+	int m_iSingleRate;
+	int m_iBurstRate;
+	int m_iCutoffRate;
 	int m_iCutoffSize;
 
+	int m_iCutoffCurrent;
+
 	// Sounds
 	const char * m_szSndDraw;
 	const char * m_szSndHolster;
@@ -118,6 +143,9 @@ protected:
 
 	// Shooting
 	float m_fEffectiveDistance;
+	RIFLE_TYPE m_rifleType;
+	float m_fRifleStep;
+	float m_fAimingRange;
 
 	// Without mag
 	int m_iCapacity;
diff --git a/source/game/CharacterInventory.cpp b/source/game/CharacterInventory.cpp
new file mode 100644
index 000000000..af0a6c856
--- /dev/null
+++ b/source/game/CharacterInventory.cpp
@@ -0,0 +1,163 @@
+#include "CharacterInventory.h"
+
+#include "BaseCharacter.h"
+
+CCharacterInventory::CCharacterInventory(CBaseCharacter * pOwner, int iSlotCount):
+	m_pOwner(pOwner),
+	m_iSlotCount(iSlotCount)
+{
+	assert(iSlotCount > 0);
+
+	m_ppSlots = new CBaseItem*[iSlotCount];
+	memset(m_ppSlots, 0, sizeof(CBaseItem*) * iSlotCount);
+}
+
+CCharacterInventory::~CCharacterInventory()
+{
+	for(int i = 0; i < m_iSlotCount; ++i)
+	{
+		if(m_ppSlots[i])
+		{
+			REMOVE_ENTITY(m_ppSlots[i]);
+		}
+	}
+	mem_delete_a(m_ppSlots);
+}
+
+bool CCharacterInventory::hasItems(const char * szClassName, int iCount)
+{
+	assert(iCount > 0);
+
+	for(int i = 0; i < m_iSlotCount; ++i)
+	{
+		if(m_ppSlots[i] && !fstrcmp(m_ppSlots[i]->getClassName(), szClassName))
+		{
+			iCount -= m_ppSlots[i]->m_iInvStackCurSize;
+			if(iCount <= 0)
+			{
+				return(true);
+			}
+		}
+	}
+	return(iCount <= 0);
+}
+
+int CCharacterInventory::consumeItems(const char *szClassName, int iCount)
+{
+	assert(iCount > 0);
+
+	int iConsumed = 0;
+
+	for(int i = 0; i < m_iSlotCount; ++i)
+	{
+		if(m_ppSlots[i] && !fstrcmp(m_ppSlots[i]->getClassName(), szClassName))
+		{
+			if(m_ppSlots[i]->m_iInvStackCurSize >= iCount)
+			{
+				iConsumed += iCount;
+				m_ppSlots[i]->m_iInvStackCurSize -= iCount;
+				if(m_ppSlots[i]->m_iInvStackCurSize <= 0)
+				{
+					REMOVE_ENTITY(m_ppSlots[i]);
+					m_ppSlots[i] = 0;
+				}
+				break;
+			}
+			else
+			{
+				iConsumed += m_ppSlots[i]->m_iInvStackCurSize;
+				iCount -= m_ppSlots[i]->m_iInvStackCurSize;
+				
+				REMOVE_ENTITY(m_ppSlots[i]);
+				m_ppSlots[i] = 0;
+			}
+		}
+	}
+	return(iConsumed);
+}
+
+void CCharacterInventory::putItems(const char *szClassName, int iCount)
+{
+	assert(iCount > 0);
+	for(int i = 0; i < m_iSlotCount; ++i)
+	{
+		if(m_ppSlots[i] && !fstrcmp(m_ppSlots[i]->getClassName(), szClassName) && m_ppSlots[i]->m_bInvStackable)
+		{
+			int iCanAdd = m_ppSlots[i]->m_iInvStackMaxSize - m_ppSlots[i]->m_iInvStackCurSize;
+			if(iCanAdd > 0)
+			{
+				if(iCanAdd >= iCount)
+				{
+					m_ppSlots[i]->m_iInvStackCurSize += iCount;
+					iCount = 0;
+					break;
+				}
+				else
+				{
+					iCount -= iCanAdd;
+					m_ppSlots[i]->m_iInvStackCurSize += iCanAdd;
+				}
+			}
+		}
+	}
+
+	if(iCount > 0)
+	{
+		for(int i = 0; i < m_iSlotCount; ++i)
+		{
+			if(!m_ppSlots[i])
+			{
+				if((m_ppSlots[i] = (CBaseItem*)CREATE_ENTITY(szClassName, m_pOwner->getManager())))
+				{
+					int iCanAdd = m_ppSlots[i]->m_iInvStackMaxSize - m_ppSlots[i]->m_iInvStackCurSize;
+					if(iCanAdd > 0)
+					{
+						if(iCanAdd >= iCount)
+						{
+							m_ppSlots[i]->m_iInvStackCurSize += iCount;
+							iCount = 0;
+							break;
+						}
+						else
+						{
+							iCount -= iCanAdd;
+							m_ppSlots[i]->m_iInvStackCurSize += iCanAdd;
+						}
+					}
+				}
+				else
+				{
+					break;
+				}
+			}
+		}
+	}
+}
+
+int CCharacterInventory::getSlotCount() const
+{
+	return(m_iSlotCount);
+}
+
+CBaseItem * CCharacterInventory::getSlot(int i) const
+{
+	assert(i >= 0 && i < m_iSlotCount);
+
+	return(m_ppSlots[i]);
+}
+
+float CCharacterInventory::getTotalWeight() const
+{
+	float fTotal = 0.0f;
+
+	for(int i = 0; i < m_iSlotCount; ++i)
+	{
+		if(m_ppSlots[i])
+		{
+			fTotal += m_ppSlots[i]->getWeight() * m_ppSlots[i]->m_iInvStackCurSize;
+		}
+	}
+
+	return(fTotal);
+}
+
diff --git a/source/game/CharacterInventory.h b/source/game/CharacterInventory.h
new file mode 100644
index 000000000..02ba8ae98
--- /dev/null
+++ b/source/game/CharacterInventory.h
@@ -0,0 +1,32 @@
+#ifndef __CHARACTER_INVENTORY_H
+#define __CHARACTER_INVENTORY_H
+
+#include "BaseItem.h"
+
+class CBaseCharacter;
+
+class CCharacterInventory
+{
+public: 
+	CCharacterInventory(CBaseCharacter * pOwner, int iSlotCount = 8 * 16);
+	~CCharacterInventory();
+
+	bool hasItems(const char * szClassName, int iCount = 1);
+	int consumeItems(const char *szClassName, int iCount = 1);
+
+	void putItems(const char *szClassName, int iCount = 1);
+	//void putItems(CBaseItem *pItem);
+	
+	int getSlotCount() const;
+	CBaseItem * getSlot(int i) const;
+
+	float getTotalWeight() const;
+
+protected:
+
+	CBaseCharacter * m_pOwner;
+	CBaseItem ** m_ppSlots;
+	int m_iSlotCount;
+};
+
+#endif
diff --git a/source/game/GameData.cpp b/source/game/GameData.cpp
index 7e8544891..05a35686e 100644
--- a/source/game/GameData.cpp
+++ b/source/game/GameData.cpp
@@ -11,6 +11,8 @@ See the license in LICENSE
 
 #include <score/sxscore.h>
 
+#include "Tracer.h"
+
 
 CPlayer * GameData::m_pPlayer;
 CPointCamera * GameData::m_pActiveCamera;
@@ -19,6 +21,8 @@ CEntityManager * GameData::m_pMgr;
 CRagdoll * g_pRagdoll;
 IAnimPlayer * pl;
 
+CTracer *g_pTracer;
+
 GameData::GameData()
 {
 	loadFoostepsSounds();
@@ -80,6 +84,7 @@ GameData::GameData()
 	Core_0RegisterConcmdClsArg("+debug_slot_move", m_pPlayer, (SXCONCMDCLSARG)&CPlayer::_ccmd_slot_on);
 	Core_0RegisterConcmdCls("-debug_slot_move", m_pPlayer, (SXCONCMDCLS)&CPlayer::_ccmd_slot_off);
 
+	g_pTracer = new CTracer(5000);
 
 	//m_pPlayer->setModel("models/stalker_zombi/stalker_zombi_a.dse");
 	//m_pPlayer->playAnimation("reload");
@@ -93,6 +98,7 @@ GameData::~GameData()
 {
 	//mem_delete(g_pRagdoll);
 
+	mem_delete(g_pTracer);
 	mem_delete(m_pMgr);
 
 	for(int i = 0; i < MPT_COUNT; ++i)
@@ -108,6 +114,7 @@ void GameData::update()
 }
 void GameData::render()
 {
+	//g_pTracer->render();
 }
 void GameData::renderHUD()
 {
diff --git a/source/game/NPCBase.cpp b/source/game/NPCBase.cpp
index 52090fdbe..e52e8bfca 100644
--- a/source/game/NPCBase.cpp
+++ b/source/game/NPCBase.cpp
@@ -17,10 +17,9 @@ END_PROPTABLE()
 
 REGISTER_ENTITY_NOLISTING(CNPCBase, npc_base);
 
-CNPCBase::CNPCBase(CEntityManager * pMgr) :
+CNPCBase::CNPCBase(CEntityManager * pMgr):
 	BaseClass(pMgr)
 {
-	m_fHealth = 1.f;
 	m_fSpeedWalk = 0.07f;
 	m_fSpeedRun = 0.12f;
 	m_idCurrQuaidInPath = -1;
@@ -44,29 +43,6 @@ CNPCBase::~CNPCBase()
 
 }
 
-void CNPCBase::initPhysics()
-{
-	btTransform startTransform;
-	startTransform.setIdentity();
-	startTransform.setOrigin(F3_BTVEC(m_vPosition));
-	
-	m_pGhostObject = new btPairCachingGhostObject();
-	m_pGhostObject->setWorldTransform(startTransform);
-	m_pCollideShape = new btCapsuleShape(0.3f, 1.4f);
-	m_pGhostObject->setCollisionShape(m_pCollideShape);
-	m_pGhostObject->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
-	m_pGhostObject->setUserPointer(this);
-
-	m_pCharacter = new btKinematicCharacterController(m_pGhostObject, (btConvexShape*)m_pCollideShape, m_fStepHeight, btVector3(0.0f, 1.0f, 0.0f));
-	m_pCharacter->setMaxJumpHeight(0.60f);
-	m_pCharacter->setJumpSpeed(4.50f);
-	m_pCharacter->setGravity(btVector3(0, -10.0f, 0));
-	m_pCharacter->setFallSpeed(300.0f);
-
-	SXPhysics_GetDynWorld()->addCollisionObject(m_pGhostObject, btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::AllFilter & ~btBroadphaseProxy::DebrisFilter);
-	SXPhysics_GetDynWorld()->addAction(m_pCharacter);
-}
-
 void CNPCBase::setPos(const float3 &pos)
 {
 	float3 tpos = pos;
@@ -131,7 +107,7 @@ ID CNPCBase::getAIQuad()
 
 bool CNPCBase::pathFind(ID endq)
 {
-	if (m_idCurrAiQuad >= 0 && SAIG_GridFindPath(m_idCurrAiQuad, endq))
+	if(m_idCurrAiQuad >= 0 && SAIG_GridFindPath(m_idCurrAiQuad, endq))
 	{
 		if (m_aPathQuads.size() > 0)
 			SAIG_GridSetColorArr(&(m_aPathQuads[0]), 0, m_aPathQuads.size());
@@ -142,7 +118,7 @@ bool CNPCBase::pathFind(ID endq)
 		m_vLastPathPos = m_vPosition;
 		return true;
 	}
-
+	
 	m_statePath = NPC_STATE_PATH_NOTFOUND;
 	return false;
 }
diff --git a/source/game/NPCBase.h b/source/game/NPCBase.h
index 965376810..303daa135 100644
--- a/source/game/NPCBase.h
+++ b/source/game/NPCBase.h
@@ -16,7 +16,7 @@ See the license in LICENSE
 #ifndef __NPCBASE_H
 #define __NPCBASE_H
 
-#include "BaseAnimating.h"
+#include "BaseCharacter.h"
 #include <aigrid/sxaigrid.h>
 
 //! базовое направление для нпс
@@ -58,9 +58,9 @@ enum NPC_STATE_PATH
 };
 
 //! Базовый класс npc
-class CNPCBase: public CBaseAnimating
+class CNPCBase: public CBaseCharacter
 {
-	DECLARE_CLASS(CNPCBase, CBaseAnimating);
+	DECLARE_CLASS(CNPCBase, CBaseCharacter);
 	DECLARE_PROPTABLE();
 
 public:
@@ -79,10 +79,8 @@ public:
 
 protected:
 
-	virtual void initPhysics();
-
-	btPairCachingGhostObject * m_pGhostObject;
-	btKinematicCharacterController * m_pCharacter;
+	//btPairCachingGhostObject * m_pGhostObject;
+	//btKinematicCharacterController * m_pCharacter;
 
 	bool pathFind(ID endq);	//!< поиск пути от текущего (на котором стоит нпс) до endq
 	void pathWalk();		//!< хождение по пути
@@ -91,9 +89,7 @@ protected:
 	//! ориентаци нпс на точку pos, ttime время в млсек за которое нпс будет повернут в/на точку
 	void orientAtPoint(const float3 *pos, DWORD ttime);	
 	void updateOrientLerp();//!< плавная интерполяция поворотов
-
-	float m_fHealth;	//!< здоровье [0,1]
-
+	
 	float m_fSpeedWalk;	//!< скорость движения при ходьбе
 	float m_fSpeedRun;	//!< скорость движения при беге
 
diff --git a/source/game/NPCZombie.cpp b/source/game/NPCZombie.cpp
index d4ccd413c..560ff74aa 100644
--- a/source/game/NPCZombie.cpp
+++ b/source/game/NPCZombie.cpp
@@ -106,9 +106,9 @@ void CNPCZombie::randWalk()
 		m_stateMove = NPC_STATE_MOVE_WALK;
 	else if (m_stateMove != NPC_STATE_MOVE_IDLE)
 	{
-		if (!playingAnimations("walk0"))
+		if (!playingAnimations("reload"))
 		{
-			playAnimation("walk0");
+			playAnimation("reload");
 			SSCore_SndPlay(m_idSndIdle2);
 		}
 
diff --git a/source/game/Player.cpp b/source/game/Player.cpp
index f34cf7be3..f85ee7c91 100644
--- a/source/game/Player.cpp
+++ b/source/game/Player.cpp
@@ -10,6 +10,9 @@ See the license in LICENSE
 #include "LightDirectional.h"
 #include "BaseTool.h"
 #include <aigrid/sxaigrid.h>
+#include "BaseAmmo.h"
+
+#include "BaseWeapon.h"
 
 #include "GameData.h"
 
@@ -37,7 +40,6 @@ CPlayer::CPlayer(CEntityManager * pMgr):
 
 	m_iUpdIval = SET_INTERVAL(updateInput, 0);
 
-
 	m_pActiveTool = (CBaseTool*)CREATE_ENTITY("weapon_ak74", m_pMgr);
 	m_pActiveTool->setOwner(this);
 	m_pActiveTool->attachHands();
@@ -46,9 +48,21 @@ CPlayer::CPlayer(CEntityManager * pMgr):
 	m_pActiveTool->setOrient(getOrient());
 	m_pActiveTool->setParent(this);
 
+	CBaseAmmo *pAmmo = (CBaseAmmo*)CREATE_ENTITY("ammo_5.45x39ps", m_pMgr);
+	m_pActiveTool->chargeAmmo(pAmmo);
+
+	CBaseMag *pMag = (CBaseMag*)CREATE_ENTITY("mag_ak74_30", m_pMgr);
+	pMag->load(30);
+	((CBaseWeapon*)m_pActiveTool)->attachMag(pMag);
+
+	getInventory()->putItems("ammo_5.45x39ps", 60);
+
+
 	m_idQuadCurr = -1;
 
 	m_pCrosshair = new CCrosshair();
+
+	m_pGhostObject->setCollisionFlags(m_pGhostObject->getCollisionFlags() | btCollisionObject::CF_DISABLE_VISUALIZE_OBJECT);
 }
 
 CPlayer::~CPlayer()
diff --git a/source/game/Random.h b/source/game/Random.h
new file mode 100644
index 000000000..7fab77889
--- /dev/null
+++ b/source/game/Random.h
@@ -0,0 +1,21 @@
+#ifndef _RANDOM_H_
+#define _RANDOM_H_
+
+#include <common/sxmath.h>
+
+/*!
+	��������� ��������� �����
+*/
+class CRandom
+{
+public:
+	CRandom(int iSeed=0)
+	{
+	}
+	float getRandomFloat(float fMinVal = 0.0f, float fMaxVal = 1.0f)
+	{
+		return(randf(fMinVal, fMaxVal));
+	}
+};
+
+#endif
diff --git a/source/game/TakeDamageInfo.h b/source/game/TakeDamageInfo.h
new file mode 100644
index 000000000..563bc1afc
--- /dev/null
+++ b/source/game/TakeDamageInfo.h
@@ -0,0 +1,34 @@
+#ifndef __TAKEDAMAGEINFO_H
+#define __TAKEDAMAGEINFO_H
+
+#include <anim/ModelFile.h>
+
+class CBaseEntity;
+
+enum DAMAGE_TYPE
+{
+	DAMAGE_GENERIC = 0,
+	DAMAGE_FIRE,
+	// ... add more
+};
+
+class CTakeDamageInfo
+{
+public:
+	CTakeDamageInfo(CBaseEntity *pAttacker, float fDamage, DAMAGE_TYPE damageType = DAMAGE_GENERIC):
+		m_bodyPart(HBP_DEFAULT),
+		m_pInflictor(NULL),
+		m_pAttacker(pAttacker),
+		m_fDamage(fDamage),
+		m_damageType(damageType)
+	{
+	}
+
+	CBaseEntity *m_pInflictor;
+	CBaseEntity *m_pAttacker;
+	HITBOX_BODYPART m_bodyPart;
+	float m_fDamage;
+	DAMAGE_TYPE m_damageType;
+};
+
+#endif
diff --git a/source/game/Tracer.cpp b/source/game/Tracer.cpp
new file mode 100644
index 000000000..f78c23817
--- /dev/null
+++ b/source/game/Tracer.cpp
@@ -0,0 +1,129 @@
+#include "Tracer.h"
+#include <gcore/sxgcore.h>
+
+CTracer::CTracer(int iTracePoolSize):
+	m_isTracing(false),
+	m_iCurTrace(0),
+	m_iPoolSize(iTracePoolSize),
+	m_iLineCount(0)
+{
+	m_vpPoints = new Array<Point>[iTracePoolSize];
+}
+
+CTracer::~CTracer()
+{
+	mem_delete_a(m_vpPoints);
+}
+
+//! �������� ����� ����� �� ����� vPoint � ������ fFracColor (0-1)
+void CTracer::begin(const float3 &vPoint, float fFracColor)
+{
+	if(m_isTracing)
+	{
+		end();
+	}
+	m_isTracing = true;
+
+	if(m_iLineCount < m_iCurTrace + 1)
+	{
+		m_iLineCount = m_iCurTrace + 1;
+	}
+
+	m_vpPoints[m_iCurTrace].clearFast();
+
+	putPoint(vPoint, fFracColor);
+}
+
+//! ����������� �������� �����
+void CTracer::end()
+{
+	if(m_isTracing)
+	{
+		m_isTracing = false;
+
+		++m_iCurTrace;
+		m_iCurTrace %= m_iPoolSize;
+	}
+}
+
+//! ������� ��� �������
+void CTracer::clear()
+{
+	end();
+	m_iCurTrace = 0;
+	m_iLineCount = 0;
+
+	for(int i = 0; i < m_iPoolSize; ++i)
+	{
+		m_vpPoints[i].clearFast();
+	}
+}
+
+//! �������� ����� �� ��������� �����
+void CTracer::lineTo(const float3 &vPoint, float fFracColor)
+{
+	putPoint(vPoint, fFracColor);
+}
+
+void CTracer::render()
+{
+	if(!m_iLineCount)
+	{
+		return;
+	}
+
+
+	SGCore_ShaderUnBind();
+
+	SMMATRIX mView, mProj;
+	Core_RMatrixGet(G_RI_MATRIX_VIEW, &mView);
+	Core_RMatrixGet(G_RI_MATRIX_PROJECTION, &mProj);
+
+	SGCore_GetDXDevice()->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&SMMatrixIdentity());
+	SGCore_GetDXDevice()->SetTransform(D3DTS_VIEW, (D3DMATRIX*)&mView);
+	SGCore_GetDXDevice()->SetTransform(D3DTS_PROJECTION, (D3DMATRIX*)&mProj);
+
+	SGCore_GetDXDevice()->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
+	SGCore_GetDXDevice()->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
+	SGCore_GetDXDevice()->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
+
+	SGCore_GetDXDevice()->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
+
+	SGCore_GetDXDevice()->SetTexture(0, 0);
+
+	// render m_iLineCount traces
+	for(int i = 0; i < m_iLineCount; ++i)
+	{
+		SGCore_GetDXDevice()->DrawPrimitiveUP(D3DPT_LINESTRIP, m_vpPoints[i].size() - 1, &(m_vpPoints[i][0]), sizeof(Point));
+	}
+}
+
+void CTracer::putPoint(const float3 &vPoint, float fFracColor)
+{
+	m_vpPoints[m_iCurTrace].push_back({vPoint, getColor(fFracColor)});
+}
+
+unsigned int CTracer::getColor(float fFrac)
+{
+	bool isSecond = fFrac > 0.5f;
+	unsigned int iStartColor = isSecond ? 0xFFFFFF00 : 0xFFFF0000;
+	unsigned int iEndColor = isSecond ? 0xFF00FF00 : 0xFFFFFF00;
+
+	unsigned int iResult = 0;
+
+	if(isSecond)
+	{
+		fFrac -= 0.5f;
+	}
+	fFrac *= 2.0f;
+
+	iResult = (int)lerpf((float)((iStartColor >> 24) & 0xFF), (float)((iEndColor >> 24) & 0xFF), fFrac);
+	iResult <<= 8;
+	iResult |= (int)lerpf((float)((iStartColor >> 16) & 0xFF), (float)((iEndColor >> 16) & 0xFF), fFrac);
+	iResult <<= 8;
+	iResult |= (int)lerpf((float)((iStartColor >> 8) & 0xFF), (float)((iEndColor >> 8) & 0xFF), fFrac);
+	iResult <<= 8;
+	iResult |= (int)lerpf((float)(iStartColor & 0xFF), (float)(iEndColor & 0xFF), fFrac);
+
+	return(iResult);
+}
diff --git a/source/game/Tracer.h b/source/game/Tracer.h
new file mode 100644
index 000000000..f9e155671
--- /dev/null
+++ b/source/game/Tracer.h
@@ -0,0 +1,46 @@
+#ifndef _TRACER_H_
+#define _TRACER_H_
+
+#include <common/sxmath.h>
+#include <common/Array.h>
+
+//! �������� ������ � ������������, ��� ������
+class CTracer
+{
+public:
+	CTracer(int iTracePoolSize = 10);
+	~CTracer();
+
+	//! �������� ����� ����� �� ����� vPoint � ������ fFracColor (0-1)
+	void begin(const float3 &vPoint, float fFracColor = 0.0f);
+
+	//! ����������� �������� �����
+	void end();
+
+	//! ������� ��� �������
+	void clear();
+
+	//! �������� ����� �� ��������� �����
+	void lineTo(const float3 &vPoint, float fFracColor=0.0f);
+
+	void render();
+
+protected:
+	bool m_isTracing;
+	int m_iCurTrace;
+	int m_iPoolSize;
+	int m_iLineCount;
+
+	void putPoint(const float3 &vPoint, float fFracColor);
+	unsigned int getColor(float fFrac);
+
+	struct Point
+	{
+		float3_t vPos;
+		unsigned int color;
+	};
+
+	Array<Point> *m_vpPoints;
+};
+
+#endif
diff --git a/source/mtllight/material.cpp b/source/mtllight/material.cpp
index 22b28f6da..ed373db66 100644
--- a/source/mtllight/material.cpp
+++ b/source/mtllight/material.cpp
@@ -146,6 +146,11 @@ Materials::Material::Material()
 	MainTexture - 1;
 	PreShaderVS - 1;
 	PreShaderPS - 1;
+
+	HitChance = 1.0f;
+	Penetration = 100.0f;
+	Density = 1000.0f;
+
 }
 
 void Materials::Material::Nulling()
diff --git a/source/physics/PhyWorld.cpp b/source/physics/PhyWorld.cpp
index 96673b970..325de7d19 100644
--- a/source/physics/PhyWorld.cpp
+++ b/source/physics/PhyWorld.cpp
@@ -17,7 +17,7 @@ PhyWorld::PhyWorld():
 	m_pGeomStaticCollideMesh(NULL),
 	m_pGeomStaticCollideShape(NULL),
 	m_pGeomStaticRigidBody(NULL),
-	m_pGeomMtlTypes(0),
+	m_pGeomMtlIDs(0),
 	m_iGeomFacesCount(0),
 	m_iGeomModelCount(0),
 	m_ppGreenStaticCollideShape(NULL),
@@ -36,7 +36,7 @@ PhyWorld::PhyWorld():
 	
 	Core_0RegisterCVarString("phy_world_gravity", "0 -10 0", "World gravity (x y z)");
 	m_pDynamicsWorld->setGravity(btVector3(0, -10, 0));
-		
+			
 	m_pDebugDrawer = new DebugDrawer();
 	m_pDebugDrawer->setDebugMode(btIDebugDraw::DBG_DrawWireframe);
 	//m_pDebugDrawer->setDebugMode(btIDebugDraw::DBG_FastWireframe);
@@ -93,6 +93,11 @@ void PhyWorld::AddShape(btRigidBody * pBody)
 	m_pDynamicsWorld->addRigidBody(pBody);
 }
 
+void PhyWorld::addShape(btRigidBody * pBody, int group, int mask)
+{
+	m_pDynamicsWorld->addRigidBody(pBody, group, mask);
+}
+
 void PhyWorld::RemoveShape(btRigidBody * pBody)
 {
 	if(pBody)
@@ -124,7 +129,7 @@ void PhyWorld::LoadGeom(const char * file)
 			m_iGeomFacesCount += pIndexCount[tc] / 3;
 		}
 
-		m_pGeomMtlTypes = new MTLTYPE_PHYSIC[m_iGeomFacesCount];
+		m_pGeomMtlIDs = new ID[m_iGeomFacesCount];
 		m_iGeomModelCount = iModelCount;
 		m_pGeomStaticCollideMesh = new btTriangleMesh(true, false);
 
@@ -151,7 +156,8 @@ void PhyWorld::LoadGeom(const char * file)
 			}
 			for(int i = 0; i < pIndexCount[tc]; i += 3)
 			{
-				m_pGeomMtlTypes[iFace++] = SML_MtlGetPhysicMaterial(ppMtls[tc][i]);
+				//m_pGeomMtlTypes[iFace++] = SML_MtlGetPhysicMaterial(ppMtls[tc][i]);
+				m_pGeomMtlIDs[iFace++] = ppMtls[tc][i];
 				m_pGeomStaticCollideMesh->addTriangleIndices(ppIndices[tc][i] + VC, ppIndices[tc][i + 1] + VC, ppIndices[tc][i + 2] + VC);
 			}
 			IC += pIndexCount[tc];
@@ -182,14 +188,14 @@ void PhyWorld::LoadGeom(const char * file)
 
 
 
-	float3_t** green_arr_vertex;
-	int32_t* green_arr_count_vertex;
-	uint32_t** green_arr_index;
-	ID** green_arr_mtl;
-	int32_t* green_arr_count_index;
-	CGreenDataVertex** green_arr_transform;
-	int32_t* green_arr_count_transform;
-	int32_t green_arr_count_green;
+	float3_t** green_arr_vertex = 0;
+	int32_t* green_arr_count_vertex = 0;
+	uint32_t** green_arr_index = 0;
+	ID** green_arr_mtl = 0;
+	int32_t* green_arr_count_index = 0;
+	CGreenDataVertex** green_arr_transform = 0;
+	int32_t* green_arr_count_transform = 0;
+	int32_t green_arr_count_green = 0;
 
 	SGeom_GreenGetNavMeshAndTransform(&green_arr_vertex, &green_arr_count_vertex, &green_arr_index, &green_arr_mtl, &green_arr_count_index, &green_arr_transform, &green_arr_count_transform, &green_arr_count_green);
 
@@ -414,7 +420,7 @@ void PhyWorld::UnloadGeom()
 
 	m_iGeomModelCount = 0;
 	m_iGeomFacesCount = 0;
-	mem_delete_a(m_pGeomMtlTypes);
+	mem_delete_a(m_pGeomMtlIDs);
 }
 
 bool PhyWorld::ImportGeom(const char * file)
@@ -484,8 +490,8 @@ bool PhyWorld::ImportGeom(const char * file)
 				if(pmf.i64Magick == PHY_MAT_FILE_MAGICK)
 				{
 					m_iGeomFacesCount = pmf.uiGeomFaceCount;
-					m_pGeomMtlTypes = new MTLTYPE_PHYSIC[m_iGeomFacesCount];
-					fread(m_pGeomMtlTypes, sizeof(MTLTYPE_PHYSIC), m_iGeomFacesCount, pF);
+					m_pGeomMtlIDs = new ID[m_iGeomFacesCount];
+					fread(m_pGeomMtlIDs, sizeof(ID), m_iGeomFacesCount, pF);
 				}
 				else
 				{
@@ -565,7 +571,7 @@ bool PhyWorld::ExportGeom(const char * _file)
 		PhyMatFile pmf;
 		pmf.uiGeomFaceCount = m_iGeomFacesCount;
 		fwrite(&pmf, sizeof(pmf), 1, file);
-		fwrite(m_pGeomMtlTypes, sizeof(MTLTYPE_PHYSIC), m_iGeomFacesCount, file);
+		fwrite(m_pGeomMtlIDs, sizeof(ID), m_iGeomFacesCount, file);
 		fclose(file);
 	}
 	return(ret);
@@ -573,13 +579,23 @@ bool PhyWorld::ExportGeom(const char * _file)
 
 MTLTYPE_PHYSIC PhyWorld::GetMtlType(const btCollisionObject *pBody, const btCollisionWorld::LocalShapeInfo *pShapeInfo)
 {
-	if(pBody == m_pGeomStaticRigidBody && m_iGeomFacesCount > pShapeInfo->m_triangleIndex)
+	ID idMtl = GetMtlID(pBody, pShapeInfo);
+	if(ID_VALID(idMtl))
 	{
-		return(m_pGeomMtlTypes[pShapeInfo->m_triangleIndex]);
+		return(SML_MtlGetPhysicMaterial(idMtl));
 	}
 	return(MTLTYPE_PHYSIC_DEFAULT);
 }
 
+ID PhyWorld::GetMtlID(const btCollisionObject *pBody, const btCollisionWorld::LocalShapeInfo *pShapeInfo)
+{
+	if(pBody == m_pGeomStaticRigidBody && m_iGeomFacesCount > pShapeInfo->m_triangleIndex)
+	{
+		return(m_pGeomMtlIDs[pShapeInfo->m_triangleIndex]);
+	}
+	return(-1);
+}
+
 //##############################################################
 
 void PhyWorld::DebugDrawer::drawLine(const btVector3 & from, const btVector3 & to, const btVector3 & color)
@@ -646,8 +662,8 @@ void PhyWorld::DebugDrawer::render()
 	SGCore_GetDXDevice()->SetTransform(D3DTS_PROJECTION, (D3DMATRIX*)&mProj);
 
 	SGCore_GetDXDevice()->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
-	SGCore_GetDXDevice()->SetRenderState(D3DRS_LIGHTING, FALSE);
-	SGCore_GetDXDevice()->SetRenderState(D3DRS_COLORWRITEENABLE, 0xFF);
+	//SGCore_GetDXDevice()->SetRenderState(D3DRS_LIGHTING, FALSE);
+	//SGCore_GetDXDevice()->SetRenderState(D3DRS_COLORWRITEENABLE, 0xFF);
 
 	SGCore_GetDXDevice()->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
 
diff --git a/source/physics/PhyWorld.h b/source/physics/PhyWorld.h
index 435608ca7..d35ba69de 100644
--- a/source/physics/PhyWorld.h
+++ b/source/physics/PhyWorld.h
@@ -47,8 +47,10 @@ public:
 	void sync();
 
 	void AddShape(btRigidBody * pBody);
+	void addShape(btRigidBody * pBody, int group, int mask);
 	void RemoveShape(btRigidBody * pBody);
 
+
 	void LoadGeom(const char * file=NULL);
 	void UnloadGeom();
 
@@ -58,6 +60,8 @@ public:
 	void render();
 
 	MTLTYPE_PHYSIC GetMtlType(const btCollisionObject *pBody, const btCollisionWorld::LocalShapeInfo *pShapeInfo);
+	
+	ID GetMtlID(const btCollisionObject *pBody, const btCollisionWorld::LocalShapeInfo *pShapeInfo);
 
 	btDiscreteDynamicsWorld * GetBtWorld()
 	{
@@ -104,7 +108,8 @@ protected:
 	btTriangleMesh * m_pGeomStaticCollideMesh;
 	btCollisionShape * m_pGeomStaticCollideShape;
 	btRigidBody * m_pGeomStaticRigidBody;
-	MTLTYPE_PHYSIC *m_pGeomMtlTypes;
+	//MTLTYPE_PHYSIC *m_pGeomMtlTypes;
+	ID *m_pGeomMtlIDs;
 	int m_iGeomFacesCount;
 	int m_iGeomModelCount;
 
diff --git a/source/physics/sxphysics.h b/source/physics/sxphysics.h
index 4c2ee3e85..bc6aae970 100644
--- a/source/physics/sxphysics.h
+++ b/source/physics/sxphysics.h
@@ -35,6 +35,36 @@ See the license in LICENSE
 #include <BulletCollision/CollisionDispatch/btGhostObject.h>
 #include <BulletDynamics/Character/btKinematicCharacterController.h>
 
+#define BIT(n) (1 << n)
+enum COLLISION_GROUP
+{
+	CG_NONE = 0,
+	// BEGIN --- Do not change ---
+	CG_DEFAULT = BIT(0),
+	CG_STATIC = BIT(1),
+	CG_KINEMATIC = BIT(2),
+	CG_DEBRIS = BIT(3),
+	CG_TRIGGER = BIT(4),
+	CG_CHARACTER = BIT(5),
+	// END --- Do not change ---
+
+	CG_WATER = BIT(6),
+	CG_HITBOX = BIT(7),
+	CG_BULLETFIRE = BIT(8),
+
+	CG_ALL = 0xFFFFFFFF
+};
+
+//! Описатель физических свойств поверхности
+struct SurfaceInfo
+{
+	ID idMtl; //!< ID материала
+	float fHitChance; //!< Шанс столкновения пули
+	float fStrength; //!< Прочность
+	float fDensity; //!< Плотность
+	int mat_type; //!< Тип материала
+};
+
 /*! Инициализирует библиотеку
 */
 SX_LIB_API void SXPhysics_0Create();
@@ -83,6 +113,7 @@ SX_LIB_API void SXPhysics_DebugRender();
 /*! Добавляет объект в симуляцию
 */
 SX_LIB_API void SXPhysics_AddShape(btRigidBody * pBody);
+SX_LIB_API void SXPhysics_AddShapeEx(btRigidBody * pBody, int group, int mask);
 
 /*! Удаляет объект из симуляции
 */
@@ -90,6 +121,8 @@ SX_LIB_API void SXPhysics_RemoveShape(btRigidBody * pBody);
 
 SX_LIB_API int SXPhysics_GetMtlType(const btCollisionObject *pBody, const btCollisionWorld::LocalShapeInfo *pShapeInfo);
 
+SX_LIB_API ID SXPhysics_GetMtlID(const btCollisionObject *pBody, const btCollisionWorld::LocalShapeInfo *pShapeInfo);
+
 SX_LIB_API btDiscreteDynamicsWorld * SXPhysics_GetDynWorld();
 
 #endif
diff --git a/source/physics/sxphysics_dll.cpp b/source/physics/sxphysics_dll.cpp
index 3ec2b29dc..96b67a644 100644
--- a/source/physics/sxphysics_dll.cpp
+++ b/source/physics/sxphysics_dll.cpp
@@ -117,6 +117,12 @@ SX_LIB_API void SXPhysics_AddShape(btRigidBody * pBody)
 	g_pWorld->AddShape(pBody);
 }
 
+SX_LIB_API void SXPhysics_AddShapeEx(btRigidBody * pBody, int group, int mask)
+{
+	SP_PRECOND(_VOID);
+	g_pWorld->addShape(pBody, group, mask);
+}
+
 SX_LIB_API void SXPhysics_RemoveShape(btRigidBody * pBody)
 {
 	SP_PRECOND(_VOID);
@@ -146,4 +152,11 @@ SX_LIB_API int SXPhysics_GetMtlType(const btCollisionObject *pBody, const btColl
 	SP_PRECOND(MTLTYPE_PHYSIC_DEFAULT);
 
 	return(g_pWorld->GetMtlType(pBody, pShapeInfo));
-}
\ No newline at end of file
+}
+
+SX_LIB_API ID SXPhysics_GetMtlID(const btCollisionObject *pBody, const btCollisionWorld::LocalShapeInfo *pShapeInfo)
+{
+	SP_PRECOND(-1);
+
+	return(g_pWorld->GetMtlID(pBody, pShapeInfo));
+}
diff --git a/source/skyxengine.cpp b/source/skyxengine.cpp
index b2e1b62e1..d806ac1c4 100644
--- a/source/skyxengine.cpp
+++ b/source/skyxengine.cpp
@@ -435,6 +435,7 @@ void SkyXEngine_CreateLoadCVar()
 	Core_0ConsoleExecCmd("exec ../sysconfig.cfg");
 	Core_0ConsoleExecCmd("exec ../userconfig.cfg");
 
+	Core_0ConsoleUpdate();
 	Core_0ConsoleUpdate();
 
 	Core_0ConsoleExecCmd("exec ../sysconfig.cfg");
@@ -769,6 +770,10 @@ void SkyXEngine_Frame(DWORD timeDelta)
 	SXPhysics_DebugRender();
 #endif
 
+#if defined(SX_GAME) || defined(SX_LEVEL_EDITOR)
+	SXGame_Render();
+#endif
+
 #if defined(SX_LEVEL_EDITOR)
 	SXLevelEditor::LevelEditorUpdate(timeDelta);
 #endif
-- 
GitLab