From fcb16857f9c4570a59056018af21a3728115d5fa Mon Sep 17 00:00:00 2001
From: D-AIRY <admin@ds-servers.com>
Date: Wed, 25 Dec 2024 03:20:14 +0300
Subject: [PATCH] func_narrow_passage entity

---
 .../demo_narrow_passage.ent                   |  21 +++
 .../demo_narrow_passage.lvl                   |  24 ++++
 .../demo_narrow_passage/editor/groups.json    |   2 +
 .../demo_narrow_passage/editor/proxies.json   |   2 +
 .../demo_narrow_passage/editor/xcsg.json      |  48 +++++++
 .../levels/demo_narrow_passage/xcsg/0.dse     | Bin 0 -> 3284 bytes
 .../levels/demo_narrow_passage/xcsg/1.dse     | Bin 0 -> 9080 bytes
 build/gamesource/config/entities/defaults.ent |   4 +
 proj/sxgame/vs2013/sxgame.vcxproj             |   4 +
 proj/sxgame/vs2013/sxgame.vcxproj.filters     |  12 ++
 source/game/BaseMover.cpp                     |  14 +-
 source/game/BaseMover.h                       |  12 +-
 source/game/FuncLadder.h                      |   1 -
 source/game/FuncNarrowPassage.cpp             |  15 ++
 source/game/FuncNarrowPassage.h               |  17 +++
 source/game/LadderMovementController.cpp      |   6 +-
 source/game/LadderMovementController.h        |   2 +
 .../game/NarrowPassageMovementController.cpp  | 132 ++++++++++++++++++
 source/game/NarrowPassageMovementController.h |  43 ++++++
 source/game/Player.h                          |   2 +
 20 files changed, 353 insertions(+), 8 deletions(-)
 create mode 100644 build/demos/levels/demo_narrow_passage/demo_narrow_passage.ent
 create mode 100644 build/demos/levels/demo_narrow_passage/demo_narrow_passage.lvl
 create mode 100644 build/demos/levels/demo_narrow_passage/editor/groups.json
 create mode 100644 build/demos/levels/demo_narrow_passage/editor/proxies.json
 create mode 100644 build/demos/levels/demo_narrow_passage/editor/xcsg.json
 create mode 100644 build/demos/levels/demo_narrow_passage/xcsg/0.dse
 create mode 100644 build/demos/levels/demo_narrow_passage/xcsg/1.dse
 create mode 100644 source/game/FuncNarrowPassage.cpp
 create mode 100644 source/game/FuncNarrowPassage.h
 create mode 100644 source/game/NarrowPassageMovementController.cpp
 create mode 100644 source/game/NarrowPassageMovementController.h

diff --git a/build/demos/levels/demo_narrow_passage/demo_narrow_passage.ent b/build/demos/levels/demo_narrow_passage/demo_narrow_passage.ent
new file mode 100644
index 000000000..f360fb1cb
--- /dev/null
+++ b/build/demos/levels/demo_narrow_passage/demo_narrow_passage.ent
@@ -0,0 +1,21 @@
+
+[{D8CE1F61-7941-498F-BD0A-D768A8257ADB}]
+up_point = 3.700012 0.000000 0.000000
+speed = 0.500000
+rotation = 0.000000 -0.707107 0.000000 -0.707107
+parent = 
+origin = 0.000008 0.100000 -1.849973
+name = 
+flags = 0
+classname = func_narrow_passage
+OnPlayerGetOn = 
+OnPlayerGetOff = 
+
+[{DC740742-575B-4B03-873A-1717475B2375}]
+team = -842150451
+rotation = 0.000000 0.000000 0.000000 1.000000
+parent = 
+origin = 0.096405 0.000000 -3.485951
+name = 
+flags = 0
+classname = info_player_spawn
diff --git a/build/demos/levels/demo_narrow_passage/demo_narrow_passage.lvl b/build/demos/levels/demo_narrow_passage/demo_narrow_passage.lvl
new file mode 100644
index 000000000..dfbcb5c3c
--- /dev/null
+++ b/build/demos/levels/demo_narrow_passage/demo_narrow_passage.lvl
@@ -0,0 +1,24 @@
+[level]
+type = indoor
+local_name = demo_narrow_passage
+
+[terrax]
+vp_layout = 0
+grid_step = 4
+grid_show = 1
+cam3_view = 1
+cam3_scale = 0.009128
+cam3_rot = 0.000000 0.000000 0.000000 1.000000
+cam3_pos = -0.616946 0.147523 -1000.000000
+cam2_view = 2
+cam2_scale = 0.008899
+cam2_rot = 0.000000 0.707107 0.000000 0.707107
+cam2_pos = 1000.000000 1.106782 -17.269983
+cam1_view = 0
+cam1_scale = 0.034539
+cam1_rot = -0.707107 0.000000 0.000000 0.707107
+cam1_pos = -5.000654 1000.000000 0.807223
+cam0_view = -1
+cam0_scale = 1.000000
+cam0_rot = 0.219844 0.603690 -0.178066 -0.745332
+cam0_pos = -8.303119 6.760478 -3.441035
diff --git a/build/demos/levels/demo_narrow_passage/editor/groups.json b/build/demos/levels/demo_narrow_passage/editor/groups.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/build/demos/levels/demo_narrow_passage/editor/groups.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/build/demos/levels/demo_narrow_passage/editor/proxies.json b/build/demos/levels/demo_narrow_passage/editor/proxies.json
new file mode 100644
index 000000000..0d4f101c7
--- /dev/null
+++ b/build/demos/levels/demo_narrow_passage/editor/proxies.json
@@ -0,0 +1,2 @@
+[
+]
diff --git a/build/demos/levels/demo_narrow_passage/editor/xcsg.json b/build/demos/levels/demo_narrow_passage/editor/xcsg.json
new file mode 100644
index 000000000..ab68f82be
--- /dev/null
+++ b/build/demos/levels/demo_narrow_passage/editor/xcsg.json
@@ -0,0 +1,48 @@
+{"brushes":[
+	{
+		"brush": [{"v":[[-5,-0.2,5],[-5,-0.2,-5],[5,-0.2,-5],[5,-0.2,5],[-5,0,5],[-5,0,-5],[5,0,-5],[5,0,5]],"e":[[0,1,0],[1,2,0],[2,3,0],[3,0,0],[4,5,0],[5,6,0],[6,7,0],[4,7,0],[0,4,0],[1,5,0],[2,6,0],[3,7,0]],"f":[{"t":{"s":[-0,-0,-1,4.76837e-007,1],"t":[0,-1,0,0,1],"m":0},"e":[8,4,9,0],"n":[-1,-0,-0],"i":0},{"t":{"s":[1,0,0,9.53674e-007,1],"t":[0,-1,0,0,1],"m":0},"e":[9,5,10,1],"n":[0,0,-1],"i":0},{"t":{"s":[-0,0,1,-4.76837e-007,1],"t":[0,-1,0,0,1],"m":0},"e":[10,6,11,2],"n":[1,0,-0],"i":0},{"t":{"s":[-1,0,0,-9.53674e-007,1],"t":[0,-1,0,0,1],"m":0},"e":[11,7,8,3],"n":[0,0,1],"i":0},{"t":{"s":[1,0,-0,9.53674e-007,1],"t":[0,0,1,-4.76837e-007,1],"m":0},"e":[0,1,2,3],"n":[0,-1,0],"i":0},{"t":{"s":[-1,0,0,-9.53674e-007,1],"t":[0,0,1,-4.76837e-007,1],"m":0},"e":[7,6,5,4],"n":[-0,1,-0],"i":0}],"m":["dev_floor"]}]
+		,"guid": "{6A81568F-6D71-402D-AED0-B5F6B29E7C92}"
+		,"color": "0.000000 0.101126 0.607227"
+		,"name": ""
+	}
+	,{
+		"brush": [{"v":[[-1.00002,6.55651e-006,-1.6],[-0.200004,6.07967e-006,-1.6],[-0.200001,-1.19209e-007,1.60005],[-1.00001,3.57628e-007,1.60005],[-1.00001,2.20004,-1.6],[-0.200004,2.20004,-1.6],[-0.2,2.20003,1.60005],[-1.00001,2.20003,1.60005]],"e":[[0,1,0],[1,2,0],[2,3,0],[3,0,0],[4,5,0],[5,6,0],[6,7,0],[4,7,0],[0,4,0],[1,5,0],[2,6,0],[3,7,0]],"f":[{"t":{"s":[1.00001,-2.38715e-007,-1.55717e-006,18.5,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399965,1],"m":1},"e":[8,4,9,0],"n":[-1.55717e-006,1.92885e-006,-1.00001],"i":0},{"t":{"s":[1.55717e-006,-1.92885e-006,1.00001,1.59998,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399965,1],"m":1},"e":[9,5,10,1],"n":[1.00001,-2.38715e-007,-1.55717e-006],"i":0},{"t":{"s":[-1.00001,2.38715e-007,1.55717e-006,-18.5,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399965,1],"m":1},"e":[10,6,11,2],"n":[1.55717e-006,-1.92885e-006,1.00001],"i":0},{"t":{"s":[-1.55717e-006,1.92885e-006,-1.00001,-1.59998,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399965,1],"m":1},"e":[11,7,8,3],"n":[-1.00001,2.38715e-007,1.55717e-006],"i":0},{"t":{"s":[1.55717e-006,-1.92885e-006,1.00001,1.59998,1],"t":[-1.00001,2.38715e-007,1.55717e-006,-18.5,1],"m":1},"e":[0,1,2,3],"n":[-2.38717e-007,-1.00002,-1.92885e-006],"i":0},{"t":{"s":[-1.55717e-006,1.92885e-006,-1.00001,-1.59998,1],"t":[-1.00001,2.38715e-007,1.55717e-006,-18.5,1],"m":1},"e":[7,6,5,4],"n":[2.38717e-007,1.00002,1.92885e-006],"i":0}],"m":["asphalt_01_A","dev_wall"]}]
+		,"guid": "{7B6F5ECE-1D4B-41FF-9553-4EC6989AFA4F}"
+		,"color": "0.000000 0.101126 0.607227"
+		,"name": ""
+	}
+	,{
+		"brush": [{"v":[[0.199986,6.55651e-006,-1.6],[0.999996,6.07967e-006,-1.6],[1,-1.19209e-007,1.60005],[0.19999,3.57628e-007,1.60005],[0.199986,2.20004,-1.59999],[0.999996,2.20004,-1.59999],[1,2.20003,1.60005],[0.19999,2.20003,1.60005]],"e":[[0,1,0],[1,2,0],[2,3,0],[3,0,0],[4,5,0],[5,6,0],[6,7,0],[4,7,0],[0,4,0],[1,5,0],[2,6,0],[3,7,0]],"f":[{"t":{"s":[1.00001,-2.38715e-007,-1.55717e-006,17.7,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399964,1],"m":1},"e":[8,4,9,0],"n":[-1.55717e-006,1.92885e-006,-1.00001],"i":0},{"t":{"s":[1.55717e-006,-1.92885e-006,1.00001,1.59997,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399964,1],"m":1},"e":[9,5,10,1],"n":[1.00001,-2.38715e-007,-1.55717e-006],"i":0},{"t":{"s":[-1.00001,2.38715e-007,1.55717e-006,-17.7,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399964,1],"m":1},"e":[10,6,11,2],"n":[1.55717e-006,-1.92885e-006,1.00001],"i":0},{"t":{"s":[-1.55717e-006,1.92885e-006,-1.00001,-1.59997,1],"t":[-2.38717e-007,-1.00002,-1.92885e-006,-0.399964,1],"m":1},"e":[11,7,8,3],"n":[-1.00001,2.38715e-007,1.55717e-006],"i":0},{"t":{"s":[1.55717e-006,-1.92885e-006,1.00001,1.59997,1],"t":[-1.00001,2.38715e-007,1.55717e-006,-17.7,1],"m":1},"e":[0,1,2,3],"n":[-2.38717e-007,-1.00002,-1.92885e-006],"i":0},{"t":{"s":[-1.55717e-006,1.92885e-006,-1.00001,-1.59997,1],"t":[-1.00001,2.38715e-007,1.55717e-006,-17.7,1],"m":1},"e":[7,6,5,4],"n":[2.38717e-007,1.00002,1.92885e-006],"i":0}],"m":["asphalt_01_A","dev_wall"]}]
+		,"guid": "{FC09E082-D68A-40AE-8BDA-22244DC3B346}"
+		,"color": "0.000000 0.273974 0.827866"
+		,"name": ""
+	}
+	,{
+		"brush": [{"v":[[-5.4,-0.2,5.4],[-5.4,-0.2,5],[5.4,-0.2,5],[5.4,-0.2,5.4],[-5.4,2,5.4],[-5.4,2,5],[5.4,2,5],[5.4,2,5.4]],"e":[[0,1,0],[1,2,0],[2,3,0],[3,0,0],[4,5,0],[5,6,0],[6,7,0],[4,7,0],[0,4,0],[1,5,0],[2,6,0],[3,7,0]],"f":[{"t":{"s":[-0,-0,-1,0,1],"t":[0,-1,0,0,1],"m":0},"e":[8,4,9,0],"n":[-1,-0,-0],"i":0},{"t":{"s":[1,0,0,0,1],"t":[0,-1,0,0,1],"m":0},"e":[9,5,10,1],"n":[0,0,-1],"i":0},{"t":{"s":[-0,0,1,0,1],"t":[0,-1,0,0,1],"m":0},"e":[10,6,11,2],"n":[1,0,-0],"i":0},{"t":{"s":[-1,0,0,0,1],"t":[0,-1,0,0,1],"m":0},"e":[11,7,8,3],"n":[0,0,1],"i":0},{"t":{"s":[1,0,-0,0,1],"t":[0,0,1,0,1],"m":0},"e":[0,1,2,3],"n":[0,-1,0],"i":0},{"t":{"s":[-1,0,0,0,1],"t":[0,0,1,0,1],"m":0},"e":[7,6,5,4],"n":[-0,1,-0],"i":0}],"m":["dev_wall"]}]
+		,"guid": "{9954F53A-4FDD-4A58-BA7C-4BE5A1C0985A}"
+		,"color": "0.000000 0.256697 0.873049"
+		,"name": ""
+	}
+	,{
+		"brush": [{"v":[[-5.4,-0.2,5],[-5.4,-0.2,-5],[-5,-0.2,-5],[-5,-0.2,5],[-5.4,2,5],[-5.4,2,-5],[-5,2,-5],[-5,2,5]],"e":[[0,1,0],[1,2,0],[2,3,0],[3,0,0],[4,5,0],[5,6,0],[6,7,0],[4,7,0],[0,4,0],[1,5,0],[2,6,0],[3,7,0]],"f":[{"t":{"s":[0,0,-1,-5.20001,1],"t":[0,-1,0,5.96046e-008,1],"m":0},"e":[8,4,9,0],"n":[-1,0,0],"i":0},{"t":{"s":[1,0,0,5.2,1],"t":[0,-1,0,5.96046e-008,1],"m":0},"e":[9,5,10,1],"n":[0,0,-1],"i":0},{"t":{"s":[0,0,1,5.20001,1],"t":[0,-1,0,5.96046e-008,1],"m":0},"e":[10,6,11,2],"n":[1,0,0],"i":0},{"t":{"s":[-1,0,0,-5.2,1],"t":[0,-1,0,5.96046e-008,1],"m":0},"e":[11,7,8,3],"n":[0,0,1],"i":0},{"t":{"s":[1,0,0,5.2,1],"t":[0,0,1,5.20001,1],"m":0},"e":[0,1,2,3],"n":[0,-1,0],"i":0},{"t":{"s":[-1,0,0,-5.2,1],"t":[0,0,1,5.20001,1],"m":0},"e":[7,6,5,4],"n":[0,1,0],"i":0}],"m":["dev_wall"]}]
+		,"guid": "{0EB30997-28DE-4FD5-AE94-5117B1A7B4C8}"
+		,"color": "0.000000 0.256697 0.873049"
+		,"name": ""
+	}
+	,{
+		"brush": [{"v":[[-5.4,-0.2,-5],[-5.4,-0.2,-5.4],[5.4,-0.2,-5.4],[5.4,-0.2,-5],[-5.4,2,-5],[-5.4,2,-5.4],[5.4,2,-5.4],[5.4,2,-5]],"e":[[0,1,0],[1,2,0],[2,3,0],[3,0,0],[4,5,0],[5,6,0],[6,7,0],[4,7,0],[0,4,0],[1,5,0],[2,6,0],[3,7,0]],"f":[{"t":{"s":[0,0,-1,-10.4,1],"t":[0,-1,0,0,1],"m":0},"e":[8,4,9,0],"n":[-1,0,0],"i":0},{"t":{"s":[1,0,0,0,1],"t":[0,-1,0,0,1],"m":0},"e":[9,5,10,1],"n":[0,0,-1],"i":0},{"t":{"s":[0,0,1,10.4,1],"t":[0,-1,0,0,1],"m":0},"e":[10,6,11,2],"n":[1,0,0],"i":0},{"t":{"s":[-1,0,0,0,1],"t":[0,-1,0,0,1],"m":0},"e":[11,7,8,3],"n":[0,0,1],"i":0},{"t":{"s":[1,0,0,0,1],"t":[0,0,1,10.4,1],"m":0},"e":[0,1,2,3],"n":[0,-1,0],"i":0},{"t":{"s":[-1,0,0,0,1],"t":[0,0,1,10.4,1],"m":0},"e":[7,6,5,4],"n":[0,1,0],"i":0}],"m":["dev_wall"]}]
+		,"guid": "{69DE4191-351F-4FF1-8781-E915708CE2E3}"
+		,"color": "0.000000 0.256697 0.873049"
+		,"name": ""
+	}
+	,{
+		"brush": [{"v":[[5,-0.2,5],[5,-0.2,-5],[5.4,-0.2,-5],[5.4,-0.2,5],[5,2,5],[5,2,-5],[5.4,2,-5],[5.4,2,5]],"e":[[0,1,0],[1,2,0],[2,3,0],[3,0,0],[4,5,0],[5,6,0],[6,7,0],[4,7,0],[0,4,0],[1,5,0],[2,6,0],[3,7,0]],"f":[{"t":{"s":[0,0,-1,-5.20001,1],"t":[0,-1,0,1.19209e-007,1],"m":0},"e":[8,4,9,0],"n":[-1,0,0],"i":0},{"t":{"s":[1,0,0,-5.2,1],"t":[0,-1,0,1.19209e-007,1],"m":0},"e":[9,5,10,1],"n":[0,0,-1],"i":0},{"t":{"s":[0,0,1,5.20001,1],"t":[0,-1,0,1.19209e-007,1],"m":0},"e":[10,6,11,2],"n":[1,0,0],"i":0},{"t":{"s":[-1,0,0,5.2,1],"t":[0,-1,0,1.19209e-007,1],"m":0},"e":[11,7,8,3],"n":[0,0,1],"i":0},{"t":{"s":[1,0,0,-5.2,1],"t":[0,0,1,5.20001,1],"m":0},"e":[0,1,2,3],"n":[0,-1,0],"i":0},{"t":{"s":[-1,0,0,5.2,1],"t":[0,0,1,5.20001,1],"m":0},"e":[7,6,5,4],"n":[0,1,0],"i":0}],"m":["dev_wall"]}]
+		,"guid": "{28576378-47EB-43D6-A078-BD3C9CA2E5C2}"
+		,"color": "0.000000 0.256697 0.873049"
+		,"name": ""
+	}
+
+]
+,"models":[
+
+]}
diff --git a/build/demos/levels/demo_narrow_passage/xcsg/0.dse b/build/demos/levels/demo_narrow_passage/xcsg/0.dse
new file mode 100644
index 0000000000000000000000000000000000000000..dea4c998fac322c334132b823d35f6eae528f01e
GIT binary patch
literal 3284
zcmZ<>&dpCr&0%0?U|?WWWMp6j(F_a>2N)m##0Rmyuz^UzID)VeqEtK}N)-b`N@`hr
zT26j`5gzp*zpY`0DBu873l5w;<Fk)}VSxh!1BA`Uu;2iQ4-!XagTz2;z}ledL2Q^B
zbT&vm$c{FKhW!i-cg&8QIkW%4S&&_4%+8!SvtI(tX4sN&?u>mbSj_>Gl5=P5YZw~#
zXPo76SaRmf{u5_?_NAZYaoBL?%>D~!eD?XA<#CvB?u>mTL&JXGvpfz{&YiIb*}-wr
z#|~!4uK)G+3=C($_Wc608MefnJ7aHg3Z&+MNyWJ{_6#R|>_B!*Idf+Jq5t*vAUkH9
zIkTUWp}`(xN5{D{_5!DT>_B$Rg4%KF%$frrH=I4Q#sS6#`SIMDH4Y$gWHw9<0|SEt
zG8?7_B#z7msR8-p+?h28&Ybbt$HcI}0mg=e`2w&wG8?7_WEL_TrUoRA%m%3e*>UE~
z8d#hkfU!X|$RD6MM`nZcgZMDBU~CW#QV$YGW`op&>;Tzy5}K}%*ayy^S>pf|M`k;K
z)PT$ag$WWHtOhEM%ywV_B{rgPgFPW-|NsC0Pe{!Nr~-Q18v-qK=xy(g0kT(y2@=rs
z_HV-g+52RG?Dc@w0nlh>kYHe7Pyq)$qz(Y(!~g&1gUSgz25{NT(7?a|#$Z)YF^~{6
ze}U=@kkkSPkUkI#s)i_ipgIJa??Gnn2N6&VF$&@akPzHHP+0}jclHdZYzNgdU^jy7
zB&hEn+<#D|U^`ICiv|V;7$4$4kbNM1FdYyYWG_q|SOi(014sm<58*$M2ngfXhv`3%
z9uOv4A1wZ$VFnHxkO8o8gt@Z;6yG2Ts5_xe9=M;7!xLl{hCYz~|NrNM{fY=P{PuzT
z2h#_QKaklVXW+FDrjH5IUa<pdfMAe&L16>oK}e7o$Q_{e4M=1GsIGzf55xyykQ$u&
zplTO5{Db=sqz8oY>w~!yqz~dhkRA{wTHgVrFa_laSiFG@fniWw><87mAp1b^2TGry
zuz@Im`3qzxL<CC0^f|!vfzl_b`atmq(npFrA@)PU6RHp77Es)SXsAEIszB)gDux<=
zr0N5O3&?LE3<^YO8ikq-whKfc+Xs$!kUmiS5wDK{+;4yu>kJ?oRMdj%Ggbx$22j@l
z#0Jrzwm7J*#L2+Gz{SA80Ahn^ZUzPh9tH*mUIqpRJ}4VR^D{6o2rw`(2r@7*2tnB(
ZT9|=>L4<*UL6m`kK@7?U(c%mY3;_7eUI_pI

literal 0
HcmV?d00001

diff --git a/build/demos/levels/demo_narrow_passage/xcsg/1.dse b/build/demos/levels/demo_narrow_passage/xcsg/1.dse
new file mode 100644
index 0000000000000000000000000000000000000000..d08f2d778bfdc15f7c66b61f7edb5edc575a0abc
GIT binary patch
literal 9080
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
L$_CN>3=9ka<5GNF

literal 0
HcmV?d00001

diff --git a/build/gamesource/config/entities/defaults.ent b/build/gamesource/config/entities/defaults.ent
index 25415f45b..7afee2706 100644
--- a/build/gamesource/config/entities/defaults.ent
+++ b/build/gamesource/config/entities/defaults.ent
@@ -39,3 +39,7 @@ inv_stackable = 0
 
 [base_weapon]
 inv_equip_type = 1
+
+[func_narrow_passage]
+speed = 0.5
+up_point = 2 0 0
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index 352079e14..ed2254be7 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj
+++ b/proj/sxgame/vs2013/sxgame.vcxproj
@@ -202,6 +202,7 @@
     <ClCompile Include="..\..\..\source\game\EntityManager.cpp" />
     <ClCompile Include="..\..\..\source\game\EnvSkybox.cpp" />
     <ClCompile Include="..\..\..\source\game\FuncLadder.cpp" />
+    <ClCompile Include="..\..\..\source\game\FuncNarrowPassage.cpp" />
     <ClCompile Include="..\..\..\source\game\FuncRotating.cpp" />
     <ClCompile Include="..\..\..\source\game\FuncTrain.cpp" />
     <ClCompile Include="..\..\..\source\game\GameData.cpp" />
@@ -220,6 +221,7 @@
     <ClCompile Include="..\..\..\source\game\LogicEntityExists.cpp" />
     <ClCompile Include="..\..\..\source\game\LogicRelay.cpp" />
     <ClCompile Include="..\..\..\source\game\LogicStringbuilder.cpp" />
+    <ClCompile Include="..\..\..\source\game\NarrowPassageMovementController.cpp" />
     <ClCompile Include="..\..\..\source\game\NPCBase.cpp" />
     <ClCompile Include="..\..\..\source\game\NPCZombie.cpp" />
     <ClCompile Include="..\..\..\source\game\PathCorner.cpp" />
@@ -286,6 +288,7 @@
     <ClInclude Include="..\..\..\source\game\EntityPointer.h" />
     <ClInclude Include="..\..\..\source\game\EnvSkybox.h" />
     <ClInclude Include="..\..\..\source\game\FuncLadder.h" />
+    <ClInclude Include="..\..\..\source\game\FuncNarrowPassage.h" />
     <ClInclude Include="..\..\..\source\game\FuncRotating.h" />
     <ClInclude Include="..\..\..\source\game\GameStateManager.h" />
     <ClInclude Include="..\..\..\source\game\CharacterInventory.h" />
@@ -303,6 +306,7 @@
     <ClInclude Include="..\..\..\source\game\LogicEntityExists.h" />
     <ClInclude Include="..\..\..\source\game\LogicRelay.h" />
     <ClInclude Include="..\..\..\source\game\LogicStringbuilder.h" />
+    <ClInclude Include="..\..\..\source\game\NarrowPassageMovementController.h" />
     <ClInclude Include="..\..\..\source\game\physics_util.h" />
     <ClInclude Include="..\..\..\source\game\PointChangelevel.h" />
     <ClInclude Include="..\..\..\source\game\PropBreakable.h" />
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj.filters b/proj/sxgame/vs2013/sxgame.vcxproj.filters
index 1c43d9d0f..a55e52215 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj.filters
+++ b/proj/sxgame/vs2013/sxgame.vcxproj.filters
@@ -378,6 +378,12 @@
     <ClCompile Include="..\..\..\source\game\FuncLadder.cpp">
       <Filter>Source Files\ents\func\mover</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\source\game\FuncNarrowPassage.cpp">
+      <Filter>Source Files\ents\func\mover</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\source\game\NarrowPassageMovementController.cpp">
+      <Filter>Source Files\character_movement</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\source\game\sxgame.h">
@@ -656,6 +662,12 @@
     <ClInclude Include="..\..\..\source\game\FuncLadder.h">
       <Filter>Header Files\ents\func\mover</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\game\FuncNarrowPassage.h">
+      <Filter>Header Files\ents\func\mover</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\source\game\NarrowPassageMovementController.h">
+      <Filter>Header Files\character_movement</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\source\game\sxgame.rc">
diff --git a/source/game/BaseMover.cpp b/source/game/BaseMover.cpp
index 5b6fdf152..d1eed1100 100644
--- a/source/game/BaseMover.cpp
+++ b/source/game/BaseMover.cpp
@@ -5,8 +5,12 @@ TODO("Trigger OnPlayerGetOn/OnPlayerGetOff events");
 TODO("Handle MOVER_NO_AUTOMOUNT flag");
 
 BEGIN_PROPTABLE(CBaseMover)
+	//! Конечная точка
 	DEFINE_FIELD_VECTORFN(m_vUpPoint, PDFF_USE_GIZMO, "up_point", "End point", setUpPoint, EDITOR_POINTCOORD)
 
+	//! Скорость движения
+	DEFINE_FIELD_FLOAT(m_fSpeed, PDFF_NONE, "speed", "Speed", EDITOR_TEXTFIELD)
+
 	//! Игрок присоединился
 	DEFINE_OUTPUT(m_onPlayerGetOn, "OnPlayerGetOn", "On player get on")
 	//! Игрок отсоединился
@@ -19,8 +23,11 @@ BEGIN_PROPTABLE(CBaseMover)
 	//! Переключает состояние
 	DEFINE_INPUT(toggle, "toggle", "Toggle", PDF_NONE)
 
+	
+
 	//! Изначально выключена
 	DEFINE_FLAG(MOVER_INITIALLY_DISABLED, "Start disabled")
+	//! Отключить автомонтирование (требуется нажать кнопку взаимодействия)
 	DEFINE_FLAG(MOVER_NO_AUTOMOUNT, "Disable automount")
 END_PROPTABLE()
 
@@ -365,6 +372,11 @@ float3 CBaseMover::getUpPos()
 	return(getOrient() * m_vUpPoint + getPos());
 }
 
+float CBaseMover::getSpeed()
+{
+	return(m_fSpeed);
+}
+
 void CBaseMover::handleCharacterMount(CBaseEntity *pEntity)
 {
 	if(fstrcmp(pEntity->getClassName(), "player"))
@@ -461,7 +473,7 @@ void CBaseMover::newMovementController(IMovementController **ppOut)
 
 //##########################################################################
 
-void CPhysicsLadderTickEventListener::onEvent(const XEventPhysicsStep *pData)
+void CPhysicsMoverTickEventListener::onEvent(const XEventPhysicsStep *pData)
 {
 	m_pMover->onPhysicsStep();
 }
diff --git a/source/game/BaseMover.h b/source/game/BaseMover.h
index 6b9776be3..6323dd550 100644
--- a/source/game/BaseMover.h
+++ b/source/game/BaseMover.h
@@ -7,10 +7,10 @@
 #define MOVER_NO_AUTOMOUNT ENT_FLAG_1
 
 class CBaseMover;
-class CPhysicsLadderTickEventListener final: public IEventListener<XEventPhysicsStep>
+class CPhysicsMoverTickEventListener final: public IEventListener<XEventPhysicsStep>
 {
 public:
-	CPhysicsLadderTickEventListener(CBaseMover *pMover):
+	CPhysicsMoverTickEventListener(CBaseMover *pMover):
 		m_pMover(pMover)
 	{
 	}
@@ -25,7 +25,7 @@ class CBaseMover: public CPointEntity
 {
 	DECLARE_CLASS(CBaseMover, CPointEntity);
 	DECLARE_PROPTABLE();
-	friend class CPhysicsLadderTickEventListener;
+	friend class CPhysicsMoverTickEventListener;
 public:
 	DECLARE_CONSTRUCTOR();
 	~CBaseMover();
@@ -42,6 +42,8 @@ public:
 
 	float3 getUpPos();
 
+	float getSpeed();
+
 private:
 	void handleCharacterMount(CBaseEntity *pEntity);
 	void createPhysBody();
@@ -70,9 +72,11 @@ private:
 	IXGhostObject *m_pGhostObject = NULL;
 	IXConvexHullShape *m_pCollideShape = NULL;
 	static IEventChannel<XEventPhysicsStep> *m_pTickEventChannel;
-	CPhysicsLadderTickEventListener m_physicsTicker;
+	CPhysicsMoverTickEventListener m_physicsTicker;
 
 	Array<CBaseEntity*> m_aTouchedEntities;
+
+	float m_fSpeed = 3.0f;
 };
 
 #endif
diff --git a/source/game/FuncLadder.h b/source/game/FuncLadder.h
index 9fd556a77..3f6e6b1e7 100644
--- a/source/game/FuncLadder.h
+++ b/source/game/FuncLadder.h
@@ -17,7 +17,6 @@ class CFuncLadder: public CBaseMover
 {
 	DECLARE_CLASS(CFuncLadder, CBaseMover);
 	DECLARE_PROPTABLE();
-	friend class CPhysicsLadderTickEventListener;
 public:
 	DECLARE_TRIVIAL_CONSTRUCTOR();
 	
diff --git a/source/game/FuncNarrowPassage.cpp b/source/game/FuncNarrowPassage.cpp
new file mode 100644
index 000000000..37f562bed
--- /dev/null
+++ b/source/game/FuncNarrowPassage.cpp
@@ -0,0 +1,15 @@
+#include "FuncNarrowPassage.h"
+#include "BaseCharacter.h"
+#include "NarrowPassageMovementController.h"
+
+
+BEGIN_PROPTABLE(CFuncNarrowPassage)
+	// empty
+END_PROPTABLE()
+
+REGISTER_ENTITY(CFuncNarrowPassage, func_narrow_passage);
+
+void CFuncNarrowPassage::newMovementController(IMovementController **ppOut)
+{
+	*ppOut = new CNarrowPassageMovementController(this);
+}
diff --git a/source/game/FuncNarrowPassage.h b/source/game/FuncNarrowPassage.h
new file mode 100644
index 000000000..f5501f54a
--- /dev/null
+++ b/source/game/FuncNarrowPassage.h
@@ -0,0 +1,17 @@
+#ifndef __FUNC_NARROW_PASSAGE_H
+#define __FUNC_NARROW_PASSAGE_H
+
+#include "BaseMover.h"
+
+class CFuncNarrowPassage: public CBaseMover
+{
+	DECLARE_CLASS(CFuncNarrowPassage, CBaseMover);
+	DECLARE_PROPTABLE();
+public:
+	DECLARE_TRIVIAL_CONSTRUCTOR();
+	
+private:
+	void newMovementController(IMovementController **ppOut) override;
+};
+
+#endif
diff --git a/source/game/LadderMovementController.cpp b/source/game/LadderMovementController.cpp
index ef824bf04..3e6b7a10e 100644
--- a/source/game/LadderMovementController.cpp
+++ b/source/game/LadderMovementController.cpp
@@ -9,6 +9,8 @@ CLadderMovementController::CLadderMovementController(CFuncLadder *pLadder)
 	m_vLadderPoint[1] = pLadder->getUpPos();
 
 	m_vLadderDir = SMVector3Normalize(m_vLadderPoint[1] - m_vLadderPoint[0]);
+
+	m_fSpeed = pLadder->getSpeed();
 }
 CLadderMovementController::~CLadderMovementController()
 {
@@ -18,7 +20,7 @@ CLadderMovementController::~CLadderMovementController()
 	}
 }
 
-float3 SMProjectPointOnLine(const float3 &vPos, const float3 &vStart, const float3 &vEnd)
+static float3 SMProjectPointOnLine(const float3 &vPos, const float3 &vStart, const float3 &vEnd)
 {
 	float3 vN = SMVector3Normalize(vEnd - vStart);
 	float fDot0 = SMVector3Dot(vN, vPos - vStart);
@@ -88,7 +90,7 @@ void CLadderMovementController::update(float fDt)
 	{
 		float fDot = SMVector3Dot(m_vLadderDir, m_vMoveDir);
 
-		float3 vSpeed = m_vLadderDir * 3.0f;
+		float3 vSpeed = m_vLadderDir * m_fSpeed;
 		float3 vNewPos;
 
 		if(fDot > /*-SM_PIDIV4*/ -SMToRadian(10.0f))
diff --git a/source/game/LadderMovementController.h b/source/game/LadderMovementController.h
index 0b34f2256..9cdf95cc0 100644
--- a/source/game/LadderMovementController.h
+++ b/source/game/LadderMovementController.h
@@ -26,6 +26,8 @@ private:
 
 	float3_t m_vMoveDir;
 
+	float m_fSpeed = 3.0f;
+
 	struct
 	{
 		bool is = false;
diff --git a/source/game/NarrowPassageMovementController.cpp b/source/game/NarrowPassageMovementController.cpp
new file mode 100644
index 000000000..d4b9cce1e
--- /dev/null
+++ b/source/game/NarrowPassageMovementController.cpp
@@ -0,0 +1,132 @@
+#include "NarrowPassageMovementController.h"
+#include "BaseCharacter.h"
+#include "FuncNarrowPassage.h"
+#include "Player.h"
+
+CNarrowPassageMovementController::CNarrowPassageMovementController(CFuncNarrowPassage *pPassage)
+{
+	m_vLadderPoint[0] = pPassage->getPos();
+	m_vLadderPoint[1] = pPassage->getUpPos();
+
+	m_vLadderDir = SMVector3Normalize(m_vLadderPoint[1] - m_vLadderPoint[0]);
+
+	m_fSpeed = pPassage->getSpeed();
+}
+CNarrowPassageMovementController::~CNarrowPassageMovementController()
+{
+	if(m_pCharacter)
+	{
+		m_pCharacter->getCharacterController()->setGravity(float3(0.0f, -10.0f, 0.0f));
+	}
+}
+
+static float3 SMProjectPointOnLine(const float3 &vPos, const float3 &vStart, const float3 &vEnd)
+{
+	float3 vN = SMVector3Normalize(vEnd - vStart);
+	float fDot0 = SMVector3Dot(vN, vPos - vStart);
+	if(fDot0 <= 0.0f)
+	{
+		return(vStart);
+	}
+
+	float fDot1 = SMVector3Dot(vN, vPos - vEnd);
+	if(fDot1 >= 0.0f)
+	{
+		return(vEnd);
+	}
+
+	return(vStart + vN * fDot0);
+}
+
+void CNarrowPassageMovementController::setCharacter(CBaseCharacter *pCharacter)
+{
+	m_pCharacter = pCharacter;
+
+	IXCharacterController *pCharacterController = pCharacter->getCharacterController();
+
+	pCharacterController->setGravity(float3(0.0f, 0.0f, 0.0f));
+	pCharacterController->setVelocityForTimeInterval(float3(0.0f, 0.0f, 0.0f), 0.0f);
+
+	m_mounting.is = true;
+	m_mounting.fFrac = 0.0f;
+	m_mounting.vStartPos = m_pCharacter->getPos();
+	m_mounting.vTargetPos = SMProjectPointOnLine(m_pCharacter->getPos(), m_vLadderPoint[0], m_vLadderPoint[1]);
+}
+
+void CNarrowPassageMovementController::handleMove(const float3 &vDir)
+{
+	m_vMoveDir = vDir;
+}
+
+void CNarrowPassageMovementController::handleJump()
+{
+	m_bWillDismount = true;
+}
+
+bool CNarrowPassageMovementController::handleUse()
+{
+	m_bWillDismount = true;
+	return(true);
+}
+
+void CNarrowPassageMovementController::update(float fDt)
+{
+	if(m_mounting.is)
+	{
+		m_mounting.fFrac += 7.0f * fDt;
+		if(m_mounting.fFrac > 1.0f)
+		{
+			m_mounting.fFrac = 1.0f;
+			m_mounting.is = false;
+		}
+		m_pCharacter->setPos(vlerp(m_mounting.vStartPos, m_mounting.vTargetPos, m_mounting.fFrac));
+	}
+	else if(m_bWillDismount)
+	{
+		((CPlayer*)m_pCharacter)->m_vCurrentSpeed = m_vMoveDir;
+		m_pCharacter->setMovementController(NULL);
+	}
+	else if(!SMIsZero(SMVector3Length2(m_vMoveDir)))
+	{
+		float fDot = SMVector3Dot(m_vLadderDir, m_vMoveDir);
+
+		float3 vSpeed = m_vLadderDir * m_fSpeed;
+		float3 vNewPos;
+
+		if(fDot > /*-SM_PIDIV4*/ -SMToRadian(10.0f))
+		{
+			if(SMIsZero(SMVector3Length2(m_vLadderPoint[1] - m_pCharacter->getPos())))
+			{
+				m_bWillDismount = true;
+			}
+			else
+			{
+				vNewPos = m_pCharacter->getPos() + vSpeed * fDt;
+				if(SMVector3Length2(vNewPos - m_vLadderPoint[0]) > SMVector3Length2(m_vLadderPoint[1] - m_vLadderPoint[0]))
+				{
+					vNewPos = m_vLadderPoint[1];
+				}
+			}
+		}
+		else
+		{
+			if(SMIsZero(SMVector3Length2(m_vLadderPoint[0] - m_pCharacter->getPos())))
+			{
+				m_bWillDismount = true;
+			}
+			else
+			{
+				vNewPos = m_pCharacter->getPos() - vSpeed * fDt;
+				if(SMVector3Length2(vNewPos - m_vLadderPoint[1]) > SMVector3Length2(m_vLadderPoint[1] - m_vLadderPoint[0]))
+				{
+					vNewPos = m_vLadderPoint[0];
+				}
+			}
+		}
+
+		if(!m_bWillDismount)
+		{
+			m_pCharacter->setPos(vNewPos);
+		}
+	}
+}
diff --git a/source/game/NarrowPassageMovementController.h b/source/game/NarrowPassageMovementController.h
new file mode 100644
index 000000000..86ea5df98
--- /dev/null
+++ b/source/game/NarrowPassageMovementController.h
@@ -0,0 +1,43 @@
+#ifndef __NARROWPASSAGEMOVEMENTCONTROLLER_H
+#define __NARROWPASSAGEMOVEMENTCONTROLLER_H
+
+#include "IMovementController.h"
+
+class CFuncNarrowPassage;
+class CNarrowPassageMovementController: public IXUnknownImplementation<IMovementController>
+{
+public:
+	CNarrowPassageMovementController(CFuncNarrowPassage *pPassage);
+	~CNarrowPassageMovementController();
+
+	void setCharacter(CBaseCharacter *pCharacter) override;
+
+	void handleMove(const float3 &vDir) override;
+	void handleJump() override;
+	bool handleUse() override;
+
+	void update(float fDt) override;
+
+private:
+	CBaseCharacter *m_pCharacter;
+
+	float3_t m_vLadderPoint[2];
+	float3_t m_vLadderDir;
+
+	float3_t m_vMoveDir;
+
+	float m_fSpeed = 3.0f;
+
+	struct
+	{
+		bool is = false;
+		float fFrac = 0.0f;
+		float3_t vStartPos;
+		float3_t vTargetPos;
+	}
+	m_mounting;
+
+	bool m_bWillDismount = false;
+};
+
+#endif
diff --git a/source/game/Player.h b/source/game/Player.h
index 34ab93672..e8416d717 100644
--- a/source/game/Player.h
+++ b/source/game/Player.h
@@ -25,7 +25,9 @@ See the license in LICENSE
 //! Класс игрока  \ingroup cbaseanimating
 class CPlayer: public CBaseCharacter
 {
+	TODO("Fix that");
 	friend class CLadderMovementController;
+	friend class CNarrowPassageMovementController;
 	DECLARE_CLASS(CPlayer, CBaseCharacter);
 	DECLARE_PROPTABLE();
 public:
-- 
GitLab