summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <smcv@debian.org>2023-12-10 21:44:36 +0000
committerSimon McVittie <smcv@debian.org>2023-12-10 21:44:36 +0000
commit3c1973ef48293fee8e59ce5726b41e68dfdfdd75 (patch)
tree3419fcc35610a869a4df8d933805214838f0aea9
parent118da2438115ef24902639bde5a96a75c07a1344 (diff)
parentcb121885d5d0fe9aa7d20d2bcfd543297974577a (diff)
Update upstream source from tag 'upstream/8.30+dfsg'
Update to upstream version '8.30+dfsg' with Debian dir fce7054568f4c72a2f4619bc78a341736a7d2ff7
-rw-r--r--rogue/.gitignore2
-rw-r--r--rogue/CHANGELOG135
-rw-r--r--rogue/CMakeLists.txt151
-rw-r--r--rogue/LICENSE340
-rw-r--r--rogue/Makefile318
-rw-r--r--rogue/README41
-rw-r--r--rogue/src/dm/ball.c841
-rw-r--r--rogue/src/dm/tag.c356
-rw-r--r--rogue/src/g_ai.c1731
-rw-r--r--rogue/src/g_chase.c244
-rw-r--r--rogue/src/g_cmds.c1925
-rw-r--r--rogue/src/g_combat.c1158
-rw-r--r--rogue/src/g_func.c3976
-rw-r--r--rogue/src/g_items.c3684
-rw-r--r--rogue/src/g_main.c488
-rw-r--r--rogue/src/g_misc.c2728
-rw-r--r--rogue/src/g_monster.c1307
-rw-r--r--rogue/src/g_newai.c1793
-rw-r--r--rogue/src/g_newdm.c448
-rw-r--r--rogue/src/g_newfnc.c464
-rw-r--r--rogue/src/g_newtarg.c431
-rw-r--r--rogue/src/g_newtrig.c249
-rw-r--r--rogue/src/g_newweap.c1927
-rw-r--r--rogue/src/g_phys.c1425
-rw-r--r--rogue/src/g_spawn.c1809
-rw-r--r--rogue/src/g_sphere.c862
-rw-r--r--rogue/src/g_svcmds.c334
-rw-r--r--rogue/src/g_target.c1200
-rw-r--r--rogue/src/g_trigger.c911
-rw-r--r--rogue/src/g_turret.c814
-rw-r--r--rogue/src/g_utils.c875
-rw-r--r--rogue/src/g_weapon.c1158
-rw-r--r--rogue/src/header/game.h215
-rw-r--r--rogue/src/header/local.h1365
-rw-r--r--rogue/src/header/shared.h1081
-rw-r--r--rogue/src/monster/berserker/berserker.c715
-rw-r--r--rogue/src/monster/berserker/berserker.h261
-rw-r--r--rogue/src/monster/boss2/boss2.c880
-rw-r--r--rogue/src/monster/boss2/boss2.h189
-rw-r--r--rogue/src/monster/boss3/boss3.c73
-rw-r--r--rogue/src/monster/boss3/boss31.c904
-rw-r--r--rogue/src/monster/boss3/boss31.h196
-rw-r--r--rogue/src/monster/boss3/boss32.c1252
-rw-r--r--rogue/src/monster/boss3/boss32.h499
-rw-r--r--rogue/src/monster/brain/brain.c776
-rw-r--r--rogue/src/monster/brain/brain.h231
-rw-r--r--rogue/src/monster/carrier/carrier.c1590
-rw-r--r--rogue/src/monster/carrier/carrier.h87
-rw-r--r--rogue/src/monster/chick/chick.c1083
-rw-r--r--rogue/src/monster/chick/chick.h296
-rw-r--r--rogue/src/monster/flipper/flipper.c532
-rw-r--r--rogue/src/monster/flipper/flipper.h168
-rw-r--r--rogue/src/monster/float/float.c875
-rw-r--r--rogue/src/monster/float/float.h256
-rw-r--r--rogue/src/monster/flyer/flyer.c1120
-rw-r--r--rogue/src/monster/flyer/flyer.h164
-rw-r--r--rogue/src/monster/gladiator/gladiator.c548
-rw-r--r--rogue/src/monster/gladiator/gladiator.h98
-rw-r--r--rogue/src/monster/gunner/gunner.c1270
-rw-r--r--rogue/src/monster/gunner/gunner.h227
-rw-r--r--rogue/src/monster/hover/hover.c851
-rw-r--r--rogue/src/monster/hover/hover.h213
-rw-r--r--rogue/src/monster/infantry/infantry.c977
-rw-r--r--rogue/src/monster/infantry/infantry.h225
-rw-r--r--rogue/src/monster/insane/insane.c934
-rw-r--r--rogue/src/monster/insane/insane.h290
-rw-r--r--rogue/src/monster/medic/medic.c1995
-rw-r--r--rogue/src/monster/medic/medic.h245
-rw-r--r--rogue/src/monster/misc/move.c855
-rw-r--r--rogue/src/monster/misc/player.h207
-rw-r--r--rogue/src/monster/mutant/mutant.c980
-rw-r--r--rogue/src/monster/mutant/mutant.h162
-rw-r--r--rogue/src/monster/parasite/parasite.c998
-rw-r--r--rogue/src/monster/parasite/parasite.h134
-rw-r--r--rogue/src/monster/soldier/soldier.c1887
-rw-r--r--rogue/src/monster/soldier/soldier.h483
-rw-r--r--rogue/src/monster/stalker/stalker.c1496
-rw-r--r--rogue/src/monster/stalker/stalker.h101
-rw-r--r--rogue/src/monster/supertank/supertank.c901
-rw-r--r--rogue/src/monster/supertank/supertank.h262
-rw-r--r--rogue/src/monster/tank/tank.c1242
-rw-r--r--rogue/src/monster/tank/tank.h302
-rw-r--r--rogue/src/monster/turret/turret.c1266
-rw-r--r--rogue/src/monster/turret/turret.h24
-rw-r--r--rogue/src/monster/widow/widow.c1910
-rw-r--r--rogue/src/monster/widow/widow.h177
-rw-r--r--rogue/src/monster/widow/widow2.c2272
-rw-r--r--rogue/src/monster/widow/widow2.h134
-rw-r--r--rogue/src/player/client.c2518
-rw-r--r--rogue/src/player/hud.c657
-rw-r--r--rogue/src/player/trail.c156
-rw-r--r--rogue/src/player/view.c1482
-rw-r--r--rogue/src/player/weapon.c2654
-rw-r--r--rogue/src/savegame/savegame.c1189
-rw-r--r--rogue/src/savegame/tables/clientfields.h15
-rw-r--r--rogue/src/savegame/tables/fields.h102
-rw-r--r--rogue/src/savegame/tables/gamefunc_decs.h1491
-rw-r--r--rogue/src/savegame/tables/gamefunc_list.h1493
-rw-r--r--rogue/src/savegame/tables/gamemmove_decs.h364
-rw-r--r--rogue/src/savegame/tables/gamemmove_list.h365
-rw-r--r--rogue/src/savegame/tables/levelfields.h15
-rw-r--r--rogue/src/shared/flash.c463
-rw-r--r--rogue/src/shared/rand.c97
-rw-r--r--rogue/src/shared/shared.c1347
-rw-r--r--rogue/stuff/mapfixes/rammo1.ent6123
-rw-r--r--rogue/stuff/mapfixes/rbase1.ent5423
-rw-r--r--rogue/stuff/mapfixes/rhangar2.ent7436
-rw-r--r--rogue/stuff/mapfixes/rmine1.ent7107
-rw-r--r--rogue/stuff/mapfixes/rsewer1.ent5282
-rw-r--r--rogue/stuff/mapfixes/rsewer2.ent6805
-rw-r--r--rogue/stuff/mapfixes/rware2.ent8317
-rw-r--r--xatrix/.gitignore2
-rw-r--r--xatrix/CHANGELOG129
-rw-r--r--xatrix/CMakeLists.txt139
-rw-r--r--xatrix/LICENSE340
-rw-r--r--xatrix/Makefile308
-rw-r--r--xatrix/README50
-rw-r--r--xatrix/src/g_ai.c1286
-rw-r--r--xatrix/src/g_chase.c246
-rw-r--r--xatrix/src/g_cmds.c1908
-rw-r--r--xatrix/src/g_combat.c750
-rw-r--r--xatrix/src/g_func.c3214
-rw-r--r--xatrix/src/g_items.c3045
-rw-r--r--xatrix/src/g_main.c467
-rw-r--r--xatrix/src/g_misc.c2982
-rw-r--r--xatrix/src/g_monster.c1260
-rw-r--r--xatrix/src/g_phys.c1300
-rw-r--r--xatrix/src/g_spawn.c1113
-rw-r--r--xatrix/src/g_svcmds.c337
-rw-r--r--xatrix/src/g_target.c1329
-rw-r--r--xatrix/src/g_trigger.c905
-rw-r--r--xatrix/src/g_turret.c589
-rw-r--r--xatrix/src/g_utils.c664
-rw-r--r--xatrix/src/g_weapon.c1848
-rw-r--r--xatrix/src/header/game.h224
-rw-r--r--xatrix/src/header/local.h1120
-rw-r--r--xatrix/src/header/shared.h1082
-rw-r--r--xatrix/src/monster/berserker/berserker.c559
-rw-r--r--xatrix/src/monster/berserker/berserker.h253
-rw-r--r--xatrix/src/monster/boss2/boss2.c803
-rw-r--r--xatrix/src/monster/boss2/boss2.h190
-rw-r--r--xatrix/src/monster/boss3/boss3.c74
-rw-r--r--xatrix/src/monster/boss3/boss31.c910
-rw-r--r--xatrix/src/monster/boss3/boss31.h197
-rw-r--r--xatrix/src/monster/boss3/boss32.c1252
-rw-r--r--xatrix/src/monster/boss3/boss32.h500
-rw-r--r--xatrix/src/monster/boss5/boss5.c886
-rw-r--r--xatrix/src/monster/brain/brain.c1117
-rw-r--r--xatrix/src/monster/brain/brain.h232
-rw-r--r--xatrix/src/monster/chick/chick.c959
-rw-r--r--xatrix/src/monster/chick/chick.h297
-rw-r--r--xatrix/src/monster/fixbot/fixbot.c1682
-rw-r--r--xatrix/src/monster/fixbot/fixbot.h221
-rw-r--r--xatrix/src/monster/flipper/flipper.c536
-rw-r--r--xatrix/src/monster/flipper/flipper.h169
-rw-r--r--xatrix/src/monster/float/float.c816
-rw-r--r--xatrix/src/monster/float/float.h257
-rw-r--r--xatrix/src/monster/flyer/flyer.c830
-rw-r--r--xatrix/src/monster/flyer/flyer.h166
-rw-r--r--xatrix/src/monster/gekk/gekk.c2012
-rw-r--r--xatrix/src/monster/gekk/gekk.h359
-rw-r--r--xatrix/src/monster/gladiator/gladb.c540
-rw-r--r--xatrix/src/monster/gladiator/gladiator.c537
-rw-r--r--xatrix/src/monster/gladiator/gladiator.h99
-rw-r--r--xatrix/src/monster/gunner/gunner.c866
-rw-r--r--xatrix/src/monster/gunner/gunner.h218
-rw-r--r--xatrix/src/monster/hover/hover.c799
-rw-r--r--xatrix/src/monster/hover/hover.h214
-rw-r--r--xatrix/src/monster/infantry/infantry.c812
-rw-r--r--xatrix/src/monster/infantry/infantry.h216
-rw-r--r--xatrix/src/monster/insane/insane.c934
-rw-r--r--xatrix/src/monster/insane/insane.h291
-rw-r--r--xatrix/src/monster/medic/medic.c1016
-rw-r--r--xatrix/src/monster/medic/medic.h246
-rw-r--r--xatrix/src/monster/misc/move.c708
-rw-r--r--xatrix/src/monster/misc/player.h207
-rw-r--r--xatrix/src/monster/mutant/mutant.c869
-rw-r--r--xatrix/src/monster/mutant/mutant.h158
-rw-r--r--xatrix/src/monster/parasite/parasite.c755
-rw-r--r--xatrix/src/monster/parasite/parasite.h127
-rw-r--r--xatrix/src/monster/soldier/soldier.c3357
-rw-r--r--xatrix/src/monster/soldier/soldier.h484
-rw-r--r--xatrix/src/monster/soldier/soldierh.h485
-rw-r--r--xatrix/src/monster/supertank/supertank.c886
-rw-r--r--xatrix/src/monster/supertank/supertank.h263
-rw-r--r--xatrix/src/monster/tank/tank.c1097
-rw-r--r--xatrix/src/monster/tank/tank.h303
-rw-r--r--xatrix/src/player/client.c2274
-rw-r--r--xatrix/src/player/hud.c625
-rw-r--r--xatrix/src/player/trail.c156
-rw-r--r--xatrix/src/player/view.c1431
-rw-r--r--xatrix/src/player/weapon.c2426
-rw-r--r--xatrix/src/savegame/savegame.c1174
-rw-r--r--xatrix/src/savegame/tables/clientfields.h14
-rw-r--r--xatrix/src/savegame/tables/fields.h89
-rw-r--r--xatrix/src/savegame/tables/gamefunc_decs.h1231
-rw-r--r--xatrix/src/savegame/tables/gamefunc_list.h1232
-rw-r--r--xatrix/src/savegame/tables/gamemmove_decs.h377
-rw-r--r--xatrix/src/savegame/tables/gamemmove_list.h378
-rw-r--r--xatrix/src/savegame/tables/levelfields.h14
-rw-r--r--xatrix/src/shared/flash.c463
-rw-r--r--xatrix/src/shared/rand.c97
-rw-r--r--xatrix/src/shared/shared.c1347
-rw-r--r--xatrix/stuff/mapfixes/industry.ent6042
-rw-r--r--xatrix/stuff/mapfixes/w_treat.ent4170
-rw-r--r--xatrix/stuff/mapfixes/xintell.ent2482
206 files changed, 217923 insertions, 0 deletions
diff --git a/rogue/.gitignore b/rogue/.gitignore
new file mode 100644
index 0000000..cadaacd
--- /dev/null
+++ b/rogue/.gitignore
@@ -0,0 +1,2 @@
+/build/
+/release/ \ No newline at end of file
diff --git a/rogue/CHANGELOG b/rogue/CHANGELOG
new file mode 100644
index 0000000..7dfa51e
--- /dev/null
+++ b/rogue/CHANGELOG
@@ -0,0 +1,135 @@
+Ground Zero 2.10 to 2.11
+- Relicense under GPL2.
+- Fix Entity used itself in rbase1 and other minor fixes. (by
+ BjossiAlfreds)
+- Some fixes for rsewer1. (by BjossiAlfreds)
+- Fix minor AI glitches with turrets (by BjossiAlfreds)
+- Fixed nagging help message in rhangar2. (by BjossiAlfreds)
+- Fixed gunner grenade duck code running twice. (by BjossiAlfreds)
+- Fixed wrong Tank muzzle flash. (by BjossiAlfreds)
+- Implement `g_swap_speed`. This allows to skip frames of "putting down
+ weapon" and "raising weapon" animations, speeding them up. (by Jaime
+ Moreira)
+- Several fixes to makron (by BjossiAlfreds)
+- Fixed stand-ground gladiators not attacking at certain range. (by
+ BjossiAlfreds)
+- Fixed monsters seeing players during intermissions. (by BjossiAlfreds)
+
+Ground Zero 2.09 to 2.10
+- Implement faster weapon switching with the new 'cycleweap' command.
+ (by protocultor).
+- Fixes pusher delta yaw manipulation. This fixes the infamous bug were
+ a player standing on a blocked elevator gets turned around (by
+ skuller).
+- Fix several coop related bugs with the powercubes. (by BjossiAlfreds)
+- A way better fix for dead bodies obstructing elevators or falling
+ through the worldmodel. (by BjossiAlfreds)
+- Fix items already in water playing a splash sound at level start. (by
+ BjossiAlfreds)
+
+Ground Zero 2.08 to 2.09
+- Refine the 'g_footstep' cvar to match Quake II itself.
+- Implement 'g_machinegun_norecoil'. The cvar is cheat protected. (by
+ De-Seppe)
+- Update the entity files for rmine1, rsewer2 and rware2, fixing some
+ smaller map bugs. (by BjossiAlfreds and Dremor8484)
+- Fix soldiers never showing their pain skins as long as they're alive.
+ (by BjossiAlfreds)
+- Implement the 'prefweap' command to select a weapon by priority. (by
+ Jaime Moreira)
+
+Ground Zero 2.07 to 2.08
+- Fix wrong sound for some items when activated. (by BjossiAlfreds)
+- Port the 'aimfix' cvar. (by Mitchell Richters)
+- Port the 'coop_pickup_weapons' and 'coop_elevator_delay' cvars.
+- Fix a long standing crash occuring when exploding projectiles like
+ grenates or rockets generate sound targets and a least one monster
+ starts moving to one of that targets.
+- Add a cvar `g_footsteps` to control the generation of footstep sound.
+- Move several hard coded map fixes to entity files. Add newly
+ discovered mapfixes to the entity files. (by BjossiAlfreds)
+- Fix several subtile gameplay and entity handling bug. (by
+ BjossiAlfreds)
+
+Ground Zero 2.06 to 2.07
+- Several fixes for subtile bugs (by BjossiAlfreds)
+
+Ground Zero 2.05 to 2.06
+- New commands: 'listentities' allows listing of entities. 'teleport'
+ teleports the player to the given coordinates.
+- A lot of fixes for subtle, long standing AI and game play bugs. (by
+ BjossiAlfreds)
+- Fix problem found by PVS studio. (analysis by demoth and fixes by
+ BjossiAlfreds)
+- Add back Ground Zero specific monster behaviors, lost during initial
+ merge in 2008. (by BjossiAlfreds)
+
+Ground Zero 2.04 to 2.05
+- Fix the parasite not attacking the player in some situations.
+- Small bugfixes and better support the the current version of the
+ Windows build environment.
+
+Ground Zero 2.03 to 2.04
+- Fix a lot of potential crashes. (reported by Maarakate)
+- Fix monsters running in place.
+- Fix monsters not recognizing the player under some
+ circumstances.
+- Fix monsters getting stuck when resurrected by a medic.
+
+Ground Zero 2.02 to 2.03
+- Make gibs and debris SOLID_BBOX so they move on entities.
+- Switch from an arch whitelist to an "all archs are supported"
+ approach.
+
+Ground Zero 2.01 to 2.02
+- Added CMake as an optional build system.
+- Fix bug with high velocities in vents in 32bit builds.
+
+Ground Zero 2.00 to 2.01
+- Coop bugfixes
+
+Ground Zero 1.07 to 2.00
+- Cleanup of the whole source, nearly every line
+ was audited and touched.
+- Add sanity checks to all function.
+- Fix all known bugs.
+- Merge all missing changes from baseq2.
+
+Ground Zero 1.06 to 1.07
+- Port 'Ground Zero' to Mac OS X.
+
+Ground Zero 1.05 to 1.06
+- Port 'Ground Zero' to Windows.
+- Use randk() instead of rand(), a better PNRG.
+- Fix some potential problems found by scan-build.
+
+Ground Zero 1.04 to 1.05
+- Port new savegame system from baseq2
+- Reorder files to reflect the new structure of baseq2
+
+Ground Zero 1.03 to 1.04:
+- Fix a crash when a proxy mine is attached to a flying enemy
+- Fix a crash in coop when multible rockets are in flight
+ (by caedes)
+- Reformat the console output
+
+Ground Zero 1.03RC3 to 1.03:
+- Just version number change
+
+Ground Zero 1.03RC2 to 1.03RC3:
+- Saner CFLAGS
+- Do not show the gun symbol when fov is bigger than 91 and
+ cl_gun is set to 2
+
+Ground Zero 1.03RC to 1.03RC2:
+- Slightly better performance (~10 FPS)
+
+Ground Zero 1.02 to 1.03RC:
+- Fix a rare crash with the Proxy Mine Launcher (reported by
+ E. Müller)
+- Tesla Coils explode when touching lava or slime (reported
+ by ogrish_freak [at] gmail [dot] com)
+
+Ground Zero 1.01 to 1.02:
+- Added License
+- Added Readme
diff --git a/rogue/CMakeLists.txt b/rogue/CMakeLists.txt
new file mode 100644
index 0000000..91a19b5
--- /dev/null
+++ b/rogue/CMakeLists.txt
@@ -0,0 +1,151 @@
+cmake_minimum_required(VERSION 3.0)
+
+# Print a message that using the Makefiles is recommended.
+message(NOTICE: " The CMakeLists.txt is unmaintained. Use the Makefile if possible.")
+
+# Enforce "Debug" as standard build type
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+endif(NOT CMAKE_BUILD_TYPE)
+
+# CMake project configuration
+project(yquake2-rogue)
+
+# Enforce compiler flags (GCC / Clang compatible, yquake2
+# won't build with another compiler anyways)
+# -Wall -> More warnings
+# -fno-strict-aliasing -> Quake 2 is far away from strict aliasing
+# -fwrapv -> Make signed integer overflows defined
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fno-strict-aliasing -fwrapv")
+
+# Use -O2 as maximum optimization level. -O3 has it's problems with yquake2.
+string(REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
+
+# Operating system
+add_definitions(-DYQ2OSTYPE="${CMAKE_SYSTEM_NAME}")
+
+# Architecture string
+string(REGEX REPLACE "amd64" "x86_64" YQ2_ARCH "${CMAKE_SYSTEM_PROCESSOR}")
+string(REGEX REPLACE "i.86" "i386" YQ2_ARCH "${YQ2_ARCH}")
+string(REGEX REPLACE "^arm.*" "arm" YQ2_ARCH "${YQ2_ARCH}")
+add_definitions(-DYQ2ARCH="${YQ2_ARCH}")
+
+# Linker Flags
+if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
+ list(APPEND RogueLinkerFlags "-lm")
+else()
+ list(APPEND RogueLinkerFlags "-lm -rdynamic")
+endif()
+
+set(Rogue-Source
+ src/dm/ball.c
+ src/dm/tag.c
+ src/monster/berserker/berserker.c
+ src/monster/boss2/boss2.c
+ src/monster/boss3/boss3.c
+ src/monster/boss3/boss31.c
+ src/monster/boss3/boss32.c
+ src/monster/brain/brain.c
+ src/monster/carrier/carrier.c
+ src/monster/chick/chick.c
+ src/monster/flipper/flipper.c
+ src/monster/float/float.c
+ src/monster/flyer/flyer.c
+ src/monster/gladiator/gladiator.c
+ src/monster/gunner/gunner.c
+ src/monster/hover/hover.c
+ src/monster/infantry/infantry.c
+ src/monster/insane/insane.c
+ src/monster/medic/medic.c
+ src/monster/misc/move.c
+ src/monster/mutant/mutant.c
+ src/monster/parasite/parasite.c
+ src/monster/soldier/soldier.c
+ src/monster/stalker/stalker.c
+ src/monster/supertank/supertank.c
+ src/monster/tank/tank.c
+ src/monster/turret/turret.c
+ src/monster/widow/widow.c
+ src/monster/widow/widow2.c
+ src/player/client.c
+ src/player/hud.c
+ src/player/trail.c
+ src/player/view.c
+ src/player/weapon.c
+ src/savegame/savegame.c
+ src/shared/flash.c
+ src/shared/rand.c
+ src/shared/shared.c
+ src/g_ai.c
+ src/g_chase.c
+ src/g_cmds.c
+ src/g_combat.c
+ src/g_func.c
+ src/g_items.c
+ src/g_main.c
+ src/g_misc.c
+ src/g_monster.c
+ src/g_newai.c
+ src/g_newdm.c
+ src/g_newfnc.c
+ src/g_newtarg.c
+ src/g_newtrig.c
+ src/g_newweap.c
+ src/g_phys.c
+ src/g_spawn.c
+ src/g_sphere.c
+ src/g_svcmds.c
+ src/g_target.c
+ src/g_trigger.c
+ src/g_turret.c
+ src/g_utils.c
+ src/g_weapon.c
+ )
+
+set(Rogue-Header
+ src/header/game.h
+ src/header/local.h
+ src/header/shared.h
+ src/monster/berserker/berserker.h
+ src/monster/boss2/boss2.h
+ src/monster/boss3/boss31.h
+ src/monster/boss3/boss32.h
+ src/monster/brain/brain.h
+ src/monster/carrier/carrier.h
+ src/monster/chick/chick.h
+ src/monster/flipper/flipper.h
+ src/monster/float/float.h
+ src/monster/flyer/flyer.h
+ src/monster/gladiator/gladiator.h
+ src/monster/gunner/gunner.h
+ src/monster/hover/hover.h
+ src/monster/infantry/infantry.h
+ src/monster/insane/insane.h
+ src/monster/medic/medic.h
+ src/monster/misc/player.h
+ src/monster/mutant/mutant.h
+ src/monster/parasite/parasite.h
+ src/monster/soldier/soldier.h
+ src/monster/stalker/stalker.h
+ src/monster/supertank/supertank.h
+ src/monster/tank/tank.h
+ src/monster/turret/turret.h
+ src/monster/widow/widow.h
+ src/monster/widow/widow2.h
+ src/savegame/tables/clientfields.h
+ src/savegame/tables/fields.h
+ src/savegame/tables/gamefunc_decs.h
+ src/savegame/tables/gamefunc_list.h
+ src/savegame/tables/gamemmove_decs.h
+ src/savegame/tables/gamemmove_list.h
+ src/savegame/tables/levelfields.h
+ )
+
+# Build the rogue dynamic library
+add_library(game SHARED ${Rogue-Source} ${Rogue-Header})
+set_target_properties(game PROPERTIES
+ PREFIX ""
+ LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/Debug
+ LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/Release
+)
+target_link_libraries(game ${RogueLinkerFlags})
diff --git a/rogue/LICENSE b/rogue/LICENSE
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/rogue/LICENSE
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/rogue/Makefile b/rogue/Makefile
new file mode 100644
index 0000000..e44acdc
--- /dev/null
+++ b/rogue/Makefile
@@ -0,0 +1,318 @@
+# ----------------------------------------------------- #
+# Makefile for the rogue game module for Quake II #
+# #
+# Just type "make" to compile the #
+# - Ground Zero Game (game.so) #
+# #
+# Dependencies: #
+# - None, but you need a Quake II to play. #
+# While in theory every client should work #
+# Yamagi Quake II is recommended. #
+# #
+# Platforms: #
+# - FreeBSD #
+# - Linux #
+# - Mac OS X #
+# - OpenBSD #
+# - Windows #
+# ----------------------------------------------------- #
+
+# Detect the OS
+ifdef SystemRoot
+YQ2_OSTYPE ?= Windows
+else
+YQ2_OSTYPE ?= $(shell uname -s)
+endif
+
+# Special case for MinGW
+ifneq (,$(findstring MINGW,$(YQ2_OSTYPE)))
+YQ2_OSTYPE := Windows
+endif
+
+# Detect the architecture
+ifeq ($(YQ2_OSTYPE), Windows)
+ifdef MINGW_CHOST
+ifeq ($(MINGW_CHOST), x86_64-w64-mingw32)
+YQ2_ARCH ?= x86_64
+else # i686-w64-mingw32
+YQ2_ARCH ?= i386
+endif
+else # windows, but MINGW_CHOST not defined
+ifdef PROCESSOR_ARCHITEW6432
+# 64 bit Windows
+YQ2_ARCH ?= $(PROCESSOR_ARCHITEW6432)
+else
+# 32 bit Windows
+YQ2_ARCH ?= $(PROCESSOR_ARCHITECTURE)
+endif
+endif # windows but MINGW_CHOST not defined
+else
+ifneq ($(YQ2_OSTYPE), Darwin)
+# Normalize some abiguous YQ2_ARCH strings
+YQ2_ARCH ?= $(shell uname -m | sed -e 's/i.86/i386/' -e 's/amd64/x86_64/' -e 's/arm64/aarch64/' -e 's/^arm.*/arm/')
+else
+YQ2_ARCH ?= $(shell uname -m)
+endif
+endif
+
+# On Windows / MinGW $(CC) is undefined by default.
+ifeq ($(YQ2_OSTYPE),Windows)
+CC ?= gcc
+endif
+
+# Detect the compiler
+ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1)
+COMPILER := clang
+COMPILERVER := $(shell $(CC) -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/')
+else ifeq ($(shell $(CC) -v 2>&1 | grep -c -E "(gcc version|gcc-Version)"), 1)
+COMPILER := gcc
+COMPILERVER := $(shell $(CC) -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/')
+else
+COMPILER := unknown
+endif
+
+# ----------
+
+# Base CFLAGS. These may be overridden by the environment.
+# Highest supported optimizations are -O2, higher levels
+# will likely break this crappy code.
+ifdef DEBUG
+CFLAGS ?= -O0 -g -Wall -pipe
+else
+CFLAGS ?= -O2 -Wall -pipe -fomit-frame-pointer
+endif
+
+# Always needed are:
+# -fno-strict-aliasing since the source doesn't comply
+# with strict aliasing rules and it's next to impossible
+# to get it there...
+# -fwrapv for defined integer wrapping. MSVC6 did this
+# and the game code requires it.
+override CFLAGS += -std=gnu99 -fno-strict-aliasing -fwrapv
+
+# -MMD to generate header dependencies. Unsupported by
+# the Clang shipped with OS X.
+ifneq ($(YQ2_OSTYPE), Darwin)
+override CFLAGS += -MMD
+endif
+
+# OS X architecture.
+ifeq ($(YQ2_OSTYPE), Darwin)
+override CFLAGS += -arch $(YQ2_ARCH)
+endif
+
+# ----------
+
+# Switch of some annoying warnings.
+ifeq ($(COMPILER), clang)
+ # -Wno-missing-braces because otherwise clang complains
+ # about totally valid 'vec3_t bla = {0}' constructs.
+ CFLAGS += -Wno-missing-braces
+else ifeq ($(COMPILER), gcc)
+ # GCC 8.0 or higher.
+ ifeq ($(shell test $(COMPILERVER) -ge 80000; echo $$?),0)
+ # -Wno-format-truncation and -Wno-format-overflow
+ # because GCC spams about 50 false positives.
+ CFLAGS += -Wno-format-truncation -Wno-format-overflow
+ endif
+endif
+
+# ----------
+
+# Defines the operating system and architecture
+override CFLAGS += -DYQ2OSTYPE=\"$(YQ2_OSTYPE)\" -DYQ2ARCH=\"$(YQ2_ARCH)\"
+
+# ----------
+
+# For reproduceable builds, look here for details:
+# https://reproducible-builds.org/specs/source-date-epoch/
+ifdef SOURCE_DATE_EPOCH
+CFLAGS += -DBUILD_DATE=\"$(shell date --utc --date="@${SOURCE_DATE_EPOCH}" +"%b %_d %Y" | sed -e 's/ /\\ /g')\"
+endif
+
+# ----------
+
+# Using the default x87 float math on 32bit x86 causes rounding trouble
+# -ffloat-store could work around that, but the better solution is to
+# just enforce SSE - every x86 CPU since Pentium3 supports that
+# and this should even improve the performance on old CPUs
+ifeq ($(YQ2_ARCH), i386)
+override CFLAGS += -msse -mfpmath=sse
+endif
+
+# Force SSE math on x86_64. All sane compilers should do this
+# anyway, just to protect us from broken Linux distros.
+ifeq ($(YQ2_ARCH), x86_64)
+override CFLAGS += -mfpmath=sse
+endif
+
+# ----------
+
+# Base LDFLAGS.
+LDFLAGS ?=
+
+# It's a shared library.
+override LDFLAGS += -shared
+
+# Required libaries
+ifeq ($(YQ2_OSTYPE), Darwin)
+override LDFLAGS += -arch $(YQ2_ARCH)
+else ifeq ($(YQ2_OSTYPE), Windows)
+override LDFLAGS += -static-libgcc
+else
+override LDFLAGS += -lm
+endif
+
+# ----------
+
+# Builds everything
+all: rogue
+
+# ----------
+
+# When make is invoked by "make VERBOSE=1" print
+# the compiler and linker commands.
+
+ifdef VERBOSE
+Q :=
+else
+Q := @
+endif
+
+# ----------
+
+# Phony targets
+.PHONY : all clean rogue
+
+# ----------
+
+# Cleanup
+clean:
+ @echo "===> CLEAN"
+ ${Q}rm -Rf build release
+
+# ----------
+
+# The rogue game
+ifeq ($(YQ2_OSTYPE), Windows)
+rogue:
+ @echo "===> Building game.dll"
+ ${Q}mkdir -p release
+ $(MAKE) release/game.dll
+else ifeq ($(YQ2_OSTYPE), Darwin)
+rogue:
+ @echo "===> Building game.dylib"
+ ${Q}mkdir -p release
+ $(MAKE) release/game.dylib
+else
+rogue:
+ @echo "===> Building game.so"
+ ${Q}mkdir -p release
+ $(MAKE) release/game.so
+
+release/game.so : CFLAGS += -fPIC
+endif
+
+build/%.o: %.c
+ @echo "===> CC $<"
+ ${Q}mkdir -p $(@D)
+ ${Q}$(CC) -c $(CFLAGS) -o $@ $<
+
+# ----------
+
+ROGUE_OBJS_ = \
+ src/g_ai.o \
+ src/g_chase.o \
+ src/g_cmds.o \
+ src/g_combat.o \
+ src/g_func.o \
+ src/g_items.o \
+ src/g_main.o \
+ src/g_misc.o \
+ src/g_monster.o \
+ src/g_newai.o \
+ src/g_newdm.o \
+ src/g_newfnc.o \
+ src/g_newtarg.o \
+ src/g_newtrig.o \
+ src/g_newweap.o \
+ src/g_phys.o \
+ src/g_spawn.o \
+ src/g_sphere.o \
+ src/g_svcmds.o \
+ src/g_target.o \
+ src/g_trigger.o \
+ src/g_turret.o \
+ src/g_utils.o \
+ src/g_weapon.o \
+ src/dm/ball.o \
+ src/dm/tag.o \
+ src/monster/berserker/berserker.o \
+ src/monster/boss2/boss2.o \
+ src/monster/boss3/boss3.o \
+ src/monster/boss3/boss31.o \
+ src/monster/boss3/boss32.o \
+ src/monster/brain/brain.o \
+ src/monster/carrier/carrier.o \
+ src/monster/chick/chick.o \
+ src/monster/flipper/flipper.o \
+ src/monster/float/float.o \
+ src/monster/flyer/flyer.o \
+ src/monster/gladiator/gladiator.o \
+ src/monster/gunner/gunner.o \
+ src/monster/hover/hover.o \
+ src/monster/infantry/infantry.o \
+ src/monster/insane/insane.o \
+ src/monster/medic/medic.o \
+ src/monster/misc/move.o \
+ src/monster/mutant/mutant.o \
+ src/monster/parasite/parasite.o \
+ src/monster/soldier/soldier.o \
+ src/monster/stalker/stalker.o \
+ src/monster/supertank/supertank.o \
+ src/monster/tank/tank.o \
+ src/monster/turret/turret.o \
+ src/monster/widow/widow.o \
+ src/monster/widow/widow2.o \
+ src/player/client.o \
+ src/player/hud.o \
+ src/player/trail.o \
+ src/player/view.o \
+ src/player/weapon.o \
+ src/savegame/savegame.o \
+ src/shared/flash.o \
+ src/shared/rand.o \
+ src/shared/shared.o
+
+# ----------
+
+# Rewrite paths to our object directory
+ROGUE_OBJS = $(patsubst %,build/%,$(ROGUE_OBJS_))
+
+# ----------
+
+# Generate header dependencies
+ROGUE_DEPS= $(ROGUE_OBJS:.o=.d)
+
+# ----------
+
+# Suck header dependencies in
+-include $(ROGUE_DEPS)
+
+# ----------
+
+ifeq ($(YQ2_OSTYPE), Windows)
+release/game.dll : $(ROGUE_OBJS)
+ @echo "===> LD $@"
+ ${Q}$(CC) -o $@ $(ROGUE_OBJS) $(LDFLAGS)
+else ifeq ($(YQ2_OSTYPE), Darwin)
+release/game.dylib : $(ROGUE_OBJS)
+ @echo "===> LD $@"
+ ${Q}$(CC) -o $@ $(ROGUE_OBJS) $(LDFLAGS)
+else
+release/game.so : $(ROGUE_OBJS)
+ @echo "===> LD $@"
+ ${Q}$(CC) -o $@ $(ROGUE_OBJS) $(LDFLAGS)
+endif
+
+# ----------
diff --git a/rogue/README b/rogue/README
new file mode 100644
index 0000000..a80653c
--- /dev/null
+++ b/rogue/README
@@ -0,0 +1,41 @@
+This is a bugfixed version of id Software's Quake II missionpack
+"Ground Zero", developed by Rogue Software. Hundreds of bugs were
+fixed, this version should run much more stable than the old
+SDK version. While compatible with any Quake II client that uses
+the original unaltered mod API, the "Yamagi Quake II Client" is
+highly recommended to play the addon. For more information visit
+http://www.yamagi.org/quake2.
+
+Installation for FreeBSD, Linux and OpenBSD:
+--------------------------------------------
+1. Type "make" or "gmake" to compile the game.so.
+2. Create a subdirectory rogue/ in your quake2 directory.
+3. Copy pak0.pak and videos/ from the "Ground Zero" CD to
+ the newly created directory rogue/.
+4. Copy release/game.so to rogue/.
+5. Start the game with "./quake2 +set game rogue"
+
+Installation for OS X:
+----------------------
+1. Create a subdirectory rogue/ in your quake2 directory.
+2. Copy pak0.pak and videos/ from the "Ground Zero" CD to
+ the newly created directory rogue/.
+3. Copy game.dynlib from the zip-archive to rogue/.
+4. Start the game with "quake2 +set game rogue"
+
+If you want to compile 'rogue' for OS X from source, please take a
+look at the "Installation" section of the README of the Yamagi Quake II
+client. In the same file the integration into an app-bundle is
+explained.
+
+Installation for Windows:
+-------------------------
+1. Create a subdirectory rogue\ in your quake2 directory.
+2. Copy pak0.pak and videos\ from the "Ground Zero" CD to
+ the newly created directory rogue\.
+3. Copy game.dll from the zip-archive to rogue/.
+4. Start the game with "quake2.exe +set game rogue"
+
+If you want to compile 'rogue' for Windows from source, please take a
+look at the "Installation" section of the README of the Yamagi Quake II
+client. There's descripted how to setup the build environment.
diff --git a/rogue/src/dm/ball.c b/rogue/src/dm/ball.c
new file mode 100644
index 0000000..5877984
--- /dev/null
+++ b/rogue/src/dm/ball.c
@@ -0,0 +1,841 @@
+/* =======================================================================
+ *
+ * Deathmatch ball.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+
+#define DBALL_GOAL_TEAM1 0x0001
+#define DBALL_GOAL_TEAM2 0x0002
+#define DBALL_SPEED_ONEWAY 1
+
+edict_t *dball_ball_entity = NULL;
+int dball_ball_startpt_count;
+int dball_team1_goalscore;
+int dball_team2_goalscore;
+
+cvar_t *dball_team1_skin;
+cvar_t *dball_team2_skin;
+cvar_t *goallimit;
+
+extern void EndDMLevel(void);
+extern void ClientUserinfoChanged(edict_t *ent, char *userinfo);
+extern void SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles);
+extern float PlayersRangeFromSpot(edict_t *spot);
+
+void DBall_BallDie(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+void DBall_BallRespawn(edict_t *self);
+
+int
+DBall_CheckDMRules(void)
+{
+ if (goallimit && goallimit->value)
+ {
+ if (dball_team1_goalscore >= goallimit->value)
+ {
+ gi.bprintf(PRINT_HIGH, "Team 1 Wins.\n");
+ }
+ else if (dball_team2_goalscore >= goallimit->value)
+ {
+ gi.bprintf(PRINT_HIGH, "Team 2 Wins.\n");
+ }
+ else
+ {
+ return 0;
+ }
+
+ EndDMLevel();
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+DBall_ClientBegin(edict_t *ent)
+{
+ int team1, team2, unassigned;
+ edict_t *other;
+ char *p;
+ static char value[512];
+ int j;
+
+ team1 = 0;
+ team2 = 0;
+ unassigned = 0;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ for (j = 1; j <= game.maxclients; j++)
+ {
+ other = &g_edicts[j];
+
+ if (!other->inuse)
+ {
+ continue;
+ }
+
+ if (!other->client)
+ {
+ continue;
+ }
+
+ if (other == ent) /* don't count the new player */
+ {
+ continue;
+ }
+
+ strcpy(value, Info_ValueForKey(other->client->pers.userinfo, "skin"));
+ p = strchr(value, '/');
+
+ if (p)
+ {
+ if (!strcmp(dball_team1_skin->string, value))
+ {
+ team1++;
+ }
+ else if (!strcmp(dball_team2_skin->string, value))
+ {
+ team2++;
+ }
+ else
+ {
+ unassigned++;
+ }
+ }
+ else
+ {
+ unassigned++;
+ }
+ }
+
+ if (team1 > team2)
+ {
+ gi.dprintf("assigned to team 2\n");
+ Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string);
+ }
+ else
+ {
+ gi.dprintf("assigned to team 1\n");
+ Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string);
+ }
+
+ ClientUserinfoChanged(ent, ent->client->pers.userinfo);
+
+ if (unassigned)
+ {
+ gi.dprintf("%d unassigned players present!\n", unassigned);
+ }
+}
+
+void
+DBall_SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles)
+{
+ edict_t *bestspot;
+ float bestdistance, bestplayerdistance;
+ edict_t *spot;
+ char *spottype;
+ char skin[512];
+
+ if (!ent)
+ {
+ return;
+ }
+
+ strcpy(skin, Info_ValueForKey(ent->client->pers.userinfo, "skin"));
+
+ if (!strcmp(dball_team1_skin->string, skin))
+ {
+ spottype = "dm_dball_team1_start";
+ }
+ else if (!strcmp(dball_team2_skin->string, skin))
+ {
+ spottype = "dm_dball_team2_start";
+ }
+ else
+ {
+ spottype = "info_player_deathmatch";
+ }
+
+ spot = NULL;
+ bestspot = NULL;
+ bestdistance = 0;
+
+ while ((spot = G_Find(spot, FOFS(classname), spottype)) != NULL)
+ {
+ bestplayerdistance = PlayersRangeFromSpot(spot);
+
+ if (bestplayerdistance > bestdistance)
+ {
+ bestspot = spot;
+ bestdistance = bestplayerdistance;
+ }
+ }
+
+ if (bestspot)
+ {
+ VectorCopy(bestspot->s.origin, origin);
+ origin[2] += 9;
+ VectorCopy(bestspot->s.angles, angles);
+ return;
+ }
+
+ /* if we didn't find an appropriate spawnpoint,
+ just call the standard one. */
+ SelectSpawnPoint(ent, origin, angles);
+}
+
+void
+DBall_GameInit(void)
+{
+ sv_stopspeed->value = 0;
+ dball_team1_goalscore = 0;
+ dball_team2_goalscore = 0;
+
+ dmflags->value = (int)dmflags->value | DF_NO_MINES | DF_NO_NUKES |
+ DF_NO_STACK_DOUBLE | DF_NO_FRIENDLY_FIRE | DF_SKINTEAMS;
+
+ dball_team1_skin = gi.cvar("dball_team1_skin", "male/ctf_r", 0);
+ dball_team2_skin = gi.cvar("dball_team2_skin", "male/ctf_b", 0);
+ goallimit = gi.cvar("goallimit", "0", 0);
+}
+
+void
+DBall_PostInitSetup(void)
+{
+ edict_t *e;
+
+ e = NULL;
+
+ /* turn teleporter destinations nonsolid. */
+ while ((e = (G_Find(e, FOFS(classname), "misc_teleporter_dest"))))
+ {
+ e->solid = SOLID_NOT;
+ gi.linkentity(e);
+ }
+
+ /* count the ball start points */
+ dball_ball_startpt_count = 0;
+ e = NULL;
+
+ while ((e = (G_Find(e, FOFS(classname), "dm_dball_ball_start"))))
+ {
+ dball_ball_startpt_count++;
+ }
+
+ if (dball_ball_startpt_count == 0)
+ {
+ gi.dprintf("No Deathball start points!\n");
+ }
+}
+
+int
+DBall_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, int mod)
+{
+ if (!targ || !attacker)
+ {
+ return 0;
+ }
+
+ /* cut player -> ball damage to 1 */
+ if (targ == dball_ball_entity)
+ {
+ return 1;
+ }
+
+ /* damage player -> player is halved */
+ if (attacker != dball_ball_entity)
+ {
+ return damage / 2;
+ }
+
+ return damage;
+}
+
+int
+DBall_ChangeKnockback(edict_t *targ, edict_t *attacker, int knockback, int mod)
+{
+ if (!targ || !attacker)
+ {
+ return 0;
+ }
+
+ if (targ != dball_ball_entity)
+ {
+ return knockback;
+ }
+
+ if (knockback < 1)
+ {
+ if (mod == MOD_ROCKET) /* rocket */
+ {
+ knockback = 70;
+ }
+ else if (mod == MOD_BFG_EFFECT) /* bfg */
+ {
+ knockback = 90;
+ }
+ else
+ {
+ gi.dprintf("zero knockback, mod %d\n", mod);
+ }
+ }
+ else
+ {
+ switch (mod)
+ {
+ case MOD_BLASTER:
+ knockback *= 3;
+ break;
+ case MOD_SHOTGUN:
+ knockback = (knockback * 3) / 8;
+ break;
+ case MOD_SSHOTGUN:
+ knockback = knockback / 3;
+ break;
+ case MOD_HYPERBLASTER:
+ knockback *= 4;
+ break;
+ case MOD_GRENADE:
+ case MOD_HANDGRENADE:
+ case MOD_PROX:
+ case MOD_G_SPLASH:
+ case MOD_HG_SPLASH:
+ case MOD_HELD_GRENADE:
+ case MOD_TRACKER:
+ case MOD_DISINTEGRATOR:
+ knockback /= 2;
+ break;
+ case MOD_R_SPLASH:
+ case MOD_MACHINEGUN:
+ knockback = (knockback * 3) / 2;
+ break;
+ case MOD_RAILGUN:
+ case MOD_HEATBEAM:
+ knockback /= 3;
+ break;
+ }
+ }
+
+ return knockback;
+}
+
+void
+DBall_GoalTouch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ int team_score;
+ int scorechange;
+ int j;
+ char value[512];
+ char *p;
+ edict_t *ent;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other != dball_ball_entity)
+ {
+ return;
+ }
+
+ self->health = self->max_health;
+
+ /* determine which team scored, and bump the team score */
+ if (self->spawnflags & DBALL_GOAL_TEAM1)
+ {
+ dball_team1_goalscore += self->wait;
+ team_score = 1;
+ }
+ else
+ {
+ dball_team2_goalscore += self->wait;
+ team_score = 2;
+ }
+
+ /* bump the score for everyone on the correct team. */
+ for (j = 1; j <= game.maxclients; j++)
+ {
+ ent = &g_edicts[j];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (ent == other->enemy)
+ {
+ scorechange = self->wait + 5;
+ }
+ else
+ {
+ scorechange = self->wait;
+ }
+
+ strcpy(value, Info_ValueForKey(ent->client->pers.userinfo, "skin"));
+ p = strchr(value, '/');
+
+ if (p)
+ {
+ if (!strcmp(dball_team1_skin->string, value))
+ {
+ if (team_score == 1)
+ {
+ ent->client->resp.score += scorechange;
+ }
+ else if (other->enemy == ent)
+ {
+ ent->client->resp.score -= scorechange;
+ }
+ }
+ else if (!strcmp(dball_team2_skin->string, value))
+ {
+ if (team_score == 2)
+ {
+ ent->client->resp.score += scorechange;
+ }
+ else if (other->enemy == ent)
+ {
+ ent->client->resp.score -= scorechange;
+ }
+ }
+ else
+ {
+ gi.dprintf("unassigned player!!!!\n");
+ }
+ }
+ }
+
+ if (other->enemy)
+ {
+ gi.dprintf("score for team %d by %s\n", team_score, other->enemy->client->pers.netname);
+ }
+ else
+ {
+ gi.dprintf("score for team %d by someone\n", team_score);
+ }
+
+ DBall_BallDie(other, other->enemy, other->enemy, 0, vec3_origin);
+ G_UseTargets(self, other);
+}
+
+edict_t *
+PickBallStart(edict_t *ent)
+{
+ int which, current;
+ edict_t *e;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ which = ceil(random() * dball_ball_startpt_count);
+ e = NULL;
+ current = 0;
+
+ while ((e = (G_Find(e, FOFS(classname), "dm_dball_ball_start"))))
+ {
+ current++;
+
+ if (current == which)
+ {
+ return e;
+ }
+ }
+
+ if (current == 0)
+ {
+ gi.dprintf("No ball start points found!\n");
+ }
+
+ return G_Find(NULL, FOFS(classname), "dm_dball_ball_start");
+}
+
+void
+DBall_BallTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t dir;
+ float dot;
+ float speed;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (other->takedamage == DAMAGE_NO)
+ {
+ return;
+ }
+
+ /* hit a player */
+ if (other->client)
+ {
+ if (ent->velocity[0] || ent->velocity[1] || ent->velocity[2])
+ {
+ speed = VectorLength(ent->velocity);
+
+ VectorSubtract(ent->s.origin, other->s.origin, dir);
+ dot = DotProduct(dir, ent->velocity);
+
+ if (dot > 0.7)
+ {
+ T_Damage(other, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
+ speed / 10, speed / 10, 0, MOD_DBALL_CRUSH);
+ }
+ }
+ }
+}
+
+void
+DBall_BallPain(edict_t *self, edict_t *other, float kick, int damage)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ self->enemy = other;
+ self->health = self->max_health;
+}
+
+void
+DBall_BallDie(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* do the splash effect */
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_DBALL_GOAL);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ VectorClear(self->s.angles);
+ VectorClear(self->velocity);
+ VectorClear(self->avelocity);
+
+ /* make it invisible and desolid until respawn time */
+ self->solid = SOLID_NOT;
+ self->think = DBall_BallRespawn;
+ self->nextthink = level.time + 2;
+ gi.linkentity(self);
+}
+
+void
+DBall_BallRespawn(edict_t *self)
+{
+ edict_t *start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* do the splash effect */
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_DBALL_GOAL);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ /* move the ball and stop it */
+ start = PickBallStart(self);
+
+ if (start)
+ {
+ VectorCopy(start->s.origin, self->s.origin);
+ VectorCopy(start->s.origin, self->s.old_origin);
+ }
+
+ VectorClear(self->s.angles);
+ VectorClear(self->velocity);
+ VectorClear(self->avelocity);
+
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2");
+ self->s.event = EV_PLAYER_TELEPORT;
+ self->groundentity = NULL;
+
+ /* kill anything at the destination */
+ KillBox(self);
+
+ gi.linkentity(self);
+}
+
+void
+DBall_SpeedTouch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ float dot;
+ vec3_t vel;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other != dball_ball_entity)
+ {
+ return;
+ }
+
+ if (self->timestamp >= level.time)
+ {
+ return;
+ }
+
+ if (VectorLength(other->velocity) < 1)
+ {
+ return;
+ }
+
+ if (self->spawnflags & DBALL_SPEED_ONEWAY)
+ {
+ VectorCopy(other->velocity, vel);
+ VectorNormalize(vel);
+ dot = DotProduct(vel, self->movedir);
+
+ if (dot < 0.8)
+ {
+ return;
+ }
+ }
+
+ self->timestamp = level.time + self->delay;
+ VectorScale(other->velocity, self->speed, other->velocity);
+}
+
+void
+SP_dm_dball_ball(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(deathmatch->value))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (gamerules && (gamerules->value != RDM_DEATHBALL))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ dball_ball_entity = self;
+
+ self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2");
+ VectorSet(self->mins, -32, -32, -32);
+ VectorSet(self->maxs, 32, 32, 32);
+ self->solid = SOLID_BBOX;
+ self->movetype = MOVETYPE_NEWTOSS;
+ self->clipmask = MASK_MONSTERSOLID;
+
+ self->takedamage = DAMAGE_YES;
+ self->mass = 50;
+ self->health = 50000;
+ self->max_health = 50000;
+ self->pain = DBall_BallPain;
+ self->die = DBall_BallDie;
+ self->touch = DBall_BallTouch;
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32)
+ * Deathball team 1 start point
+ */
+void
+SP_dm_dball_team1_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (gamerules && (gamerules->value != RDM_DEATHBALL))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+}
+
+/*
+ * QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32)
+ * Deathball team 2 start point
+ */
+void
+SP_dm_dball_team2_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (gamerules && (gamerules->value != RDM_DEATHBALL))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+}
+
+/*
+ * QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48)
+ * Deathball ball start point
+ */
+void
+SP_dm_dball_ball_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (gamerules && (gamerules->value != RDM_DEATHBALL))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+}
+
+/*
+ * QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY
+ * Deathball ball speed changing field.
+ *
+ * speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double)
+ * angle: used with ONEWAY so speed change is only one way.
+ * delay: time between speed changes (default: 0.2 sec)
+ */
+void
+SP_dm_dball_speed_change(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (gamerules && (gamerules->value != RDM_DEATHBALL))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 2;
+ }
+
+ if (!self->delay)
+ {
+ self->delay = 0.2;
+ }
+
+ self->touch = DBall_SpeedTouch;
+ self->solid = SOLID_TRIGGER;
+ self->movetype = MOVETYPE_NONE;
+ self->svflags |= SVF_NOCLIENT;
+
+ if (!VectorCompare(self->s.angles, vec3_origin))
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+ else
+ {
+ VectorSet(self->movedir, 1, 0, 0);
+ }
+
+ gi.setmodel(self, self->model);
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2
+ * Deathball goal
+ *
+ * Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score.
+ *
+ * "wait": score to be given for this goal (default 10) player gets score+5.
+ */
+void
+SP_dm_dball_goal(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(deathmatch->value))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (gamerules && (gamerules->value != RDM_DEATHBALL))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (!self->wait)
+ {
+ self->wait = 10;
+ }
+
+ self->touch = DBall_GoalTouch;
+ self->solid = SOLID_TRIGGER;
+ self->movetype = MOVETYPE_NONE;
+ self->svflags |= SVF_NOCLIENT;
+
+ if (!VectorCompare(self->s.angles, vec3_origin))
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+
+ gi.setmodel(self, self->model);
+ gi.linkentity(self);
+}
diff --git a/rogue/src/dm/tag.c b/rogue/src/dm/tag.c
new file mode 100644
index 0000000..adbfb7c
--- /dev/null
+++ b/rogue/src/dm/tag.c
@@ -0,0 +1,356 @@
+/* =======================================================================
+ *
+ * Deathmatch tag.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+
+extern edict_t *SelectFarthestDeathmatchSpawnPoint(void);
+extern void SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles);
+void SP_dm_tag_token(edict_t *self);
+
+edict_t *tag_token;
+edict_t *tag_owner;
+int tag_count;
+
+void
+Tag_PlayerDeath(edict_t *targ, edict_t *inflictor /* unused */, edict_t *attacker /* unused */)
+{
+ if (tag_token && targ && (targ == tag_owner))
+ {
+ Tag_DropToken(targ, FindItem("Tag Token"));
+ tag_owner = NULL;
+ tag_count = 0;
+ }
+}
+
+void
+Tag_KillItBonus(edict_t *self)
+{
+ edict_t *armor;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if the player is hurt, boost them up to max. */
+ if (self->health < self->max_health)
+ {
+ self->health += 200;
+
+ if (self->health > self->max_health)
+ {
+ self->health = self->max_health;
+ }
+ }
+
+ /* give the player a body armor */
+ armor = G_Spawn();
+ armor->spawnflags |= DROPPED_ITEM;
+ armor->item = FindItem("Body Armor");
+ Touch_Item(armor, self, NULL, NULL);
+
+ if (armor->inuse)
+ {
+ G_FreeEdict(armor);
+ }
+}
+
+void
+Tag_PlayerDisconnect(edict_t *self)
+{
+ if (tag_token && self && (self == tag_owner))
+ {
+ Tag_DropToken(self, FindItem("Tag Token"));
+ tag_owner = NULL;
+ tag_count = 0;
+ }
+}
+
+void
+Tag_Score(edict_t *attacker, edict_t *victim, int scoreChange)
+{
+ gitem_t *quad;
+ int mod;
+
+ if (!attacker || !victim)
+ {
+ return;
+ }
+
+ mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+
+ if (tag_token && tag_owner)
+ {
+ /* owner killed somone else */
+ if ((scoreChange > 0) && (tag_owner == attacker))
+ {
+ scoreChange = 3;
+ tag_count++;
+
+ if (tag_count == 5)
+ {
+ quad = FindItem("Quad Damage");
+ attacker->client->pers.inventory[ITEM_INDEX(quad)]++;
+ quad->use(attacker, quad);
+ tag_count = 0;
+ }
+ }
+ /* owner got killed. 5 points and switch owners */
+ else if ((tag_owner == victim) && (tag_owner != attacker))
+ {
+ scoreChange = 5;
+
+ if ((mod == MOD_HUNTER_SPHERE) || (mod == MOD_DOPPLE_EXPLODE) ||
+ (mod == MOD_DOPPLE_VENGEANCE) || (mod == MOD_DOPPLE_HUNTER) ||
+ (attacker->health <= 0))
+ {
+ Tag_DropToken(tag_owner, FindItem("Tag Token"));
+ tag_owner = NULL;
+ tag_count = 0;
+ }
+ else
+ {
+ Tag_KillItBonus(attacker);
+ tag_owner = attacker;
+ tag_count = 0;
+ }
+ }
+ }
+
+ attacker->client->resp.score += scoreChange;
+}
+
+qboolean
+Tag_PickupToken(edict_t *ent, edict_t *other)
+{
+ if (gamerules && (gamerules->value != 2))
+ {
+ return false;
+ }
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ /* sanity checking is good. */
+ if (tag_token != ent)
+ {
+ tag_token = ent;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ tag_owner = other;
+ tag_count = 0;
+
+ Tag_KillItBonus(other);
+
+ return true;
+}
+
+void
+Tag_Respawn(edict_t *ent)
+{
+ edict_t *spot;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ spot = SelectFarthestDeathmatchSpawnPoint();
+
+ if (spot == NULL)
+ {
+ ent->nextthink = level.time + 1;
+ return;
+ }
+
+ VectorCopy(spot->s.origin, ent->s.origin);
+ gi.linkentity(ent);
+}
+
+void
+Tag_MakeTouchable(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->touch = Touch_Item;
+
+ tag_token->think = Tag_Respawn;
+
+ /* check here to see if it's in lava or slime. if so, do a respawn sooner */
+ if (gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA | CONTENTS_SLIME))
+ {
+ tag_token->nextthink = level.time + 3;
+ }
+ else
+ {
+ tag_token->nextthink = level.time + 30;
+ }
+}
+
+void
+Tag_DropToken(edict_t *ent, gitem_t *item)
+{
+ trace_t trace;
+ vec3_t forward, right;
+ vec3_t offset;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ /* reset the score count for next player */
+ tag_count = 0;
+ tag_owner = NULL;
+
+ tag_token = G_Spawn();
+
+ tag_token->classname = item->classname;
+ tag_token->item = item;
+ tag_token->spawnflags = DROPPED_ITEM;
+ tag_token->s.effects = EF_ROTATE | EF_TAGTRAIL;
+ tag_token->s.renderfx = RF_GLOW;
+ VectorSet(tag_token->mins, -15, -15, -15);
+ VectorSet(tag_token->maxs, 15, 15, 15);
+ gi.setmodel(tag_token, tag_token->item->world_model);
+ tag_token->solid = SOLID_TRIGGER;
+ tag_token->movetype = MOVETYPE_TOSS;
+ tag_token->touch = NULL;
+ tag_token->owner = ent;
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ VectorSet(offset, 24, 0, -16);
+ G_ProjectSource(ent->s.origin, offset, forward, right, tag_token->s.origin);
+ trace = gi.trace(ent->s.origin, tag_token->mins, tag_token->maxs, tag_token->s.origin, ent, CONTENTS_SOLID);
+ VectorCopy(trace.endpos, tag_token->s.origin);
+
+ VectorScale(forward, 100, tag_token->velocity);
+ tag_token->velocity[2] = 300;
+
+ tag_token->think = Tag_MakeTouchable;
+ tag_token->nextthink = level.time + 1;
+
+ gi.linkentity(tag_token);
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+}
+
+void
+Tag_PlayerEffects(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent == tag_owner)
+ {
+ ent->s.effects |= EF_TAGTRAIL;
+ }
+}
+
+void
+Tag_DogTag(edict_t *ent, edict_t *killer /* unused */, char **pic)
+{
+ if (!ent || !pic)
+ {
+ return;
+ }
+
+ if (ent == tag_owner)
+ {
+ (*pic) = "tag3";
+ }
+}
+
+int
+Tag_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, int mod)
+{
+ if (!targ || !attacker)
+ {
+ return 0;
+ }
+
+ if ((targ != tag_owner) && (attacker != tag_owner))
+ {
+ return damage * 3 / 4;
+ }
+
+ return damage;
+}
+
+void
+Tag_GameInit(void)
+{
+ tag_token = NULL;
+ tag_owner = NULL;
+ tag_count = 0;
+}
+
+void
+Tag_PostInitSetup(void)
+{
+ edict_t *e;
+ vec3_t origin, angles;
+
+ /* automatic spawning of tag token if one is not present on map. */
+ e = G_Find(NULL, FOFS(classname), "dm_tag_token");
+
+ if (e == NULL)
+ {
+ e = G_Spawn();
+ e->classname = "dm_tag_token";
+
+ SelectSpawnPoint(e, origin, angles);
+ VectorCopy(origin, e->s.origin);
+ VectorCopy(origin, e->s.old_origin);
+ VectorCopy(angles, e->s.angles);
+ SP_dm_tag_token(e);
+ }
+}
+
+/*
+ * QUAKED dm_tag_token (.3 .3 1) (-16 -16 -16) (16 16 16)
+ * The tag token for deathmatch tag games.
+ */
+void
+SP_dm_tag_token(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(deathmatch->value))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (gamerules && (gamerules->value != 2))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ /* store the tag token edict pointer for later use. */
+ tag_token = self;
+ tag_count = 0;
+
+ self->classname = "dm_tag_token";
+ self->model = "models/items/tagtoken/tris.md2";
+ self->count = 1;
+ SpawnItem(self, FindItem("Tag Token"));
+}
diff --git a/rogue/src/g_ai.c b/rogue/src/g_ai.c
new file mode 100644
index 0000000..f7b4f4d
--- /dev/null
+++ b/rogue/src/g_ai.c
@@ -0,0 +1,1731 @@
+/* =======================================================================
+ *
+ * The basic AI functions like enemy detection, attacking and so on.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+extern cvar_t *maxclients;
+int enemy_range;
+float enemy_yaw;
+qboolean ai_checkattack(edict_t *self, float dist);
+qboolean enemy_infront;
+qboolean enemy_vis;
+qboolean FindTarget(edict_t *self);
+
+/* ========================================================================== */
+
+/*
+ * Called once each frame to set level.sight_client
+ * to the player to be checked for in findtarget.
+ * If all clients are either dead or in notarget,
+ * sight_client will be null.
+ * In coop games, sight_client will cycle
+ * between the clients.
+ */
+void
+AI_SetSightClient(void)
+{
+ edict_t *ent;
+ int start, check;
+
+ if (level.sight_client == NULL)
+ {
+ start = 1;
+ }
+ else
+ {
+ start = level.sight_client - g_edicts;
+ }
+
+ check = start;
+
+ while (1)
+ {
+ check++;
+
+ if (check > game.maxclients)
+ {
+ check = 1;
+ }
+
+ ent = &g_edicts[check];
+
+ if (ent->inuse && (ent->health > 0) &&
+ !(ent->flags & (FL_NOTARGET | FL_DISGUISED)))
+ {
+ level.sight_client = ent;
+ return; /* got one */
+ }
+
+ if (check == start)
+ {
+ level.sight_client = NULL;
+ return; /* nobody to see */
+ }
+ }
+}
+
+/*
+ * Move the specified distance at current facing.
+ */
+void
+ai_move(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ M_walkmove(self, self->s.angles[YAW], dist);
+}
+
+/*
+ *
+ * Used for standing around and looking
+ * for players Distance is for slight
+ * position adjustments needed by the
+ * animations
+ */
+void
+ai_stand(edict_t *self, float dist)
+{
+ vec3_t v;
+ qboolean retval;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (dist)
+ {
+ M_walkmove(self, self->s.angles[YAW], dist);
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ if (self->enemy)
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, v);
+ self->ideal_yaw = vectoyaw(v);
+
+ if ((self->s.angles[YAW] != self->ideal_yaw) &&
+ self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+ {
+ self->monsterinfo.aiflags &=
+ ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+ self->monsterinfo.run(self);
+ }
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ M_ChangeYaw(self);
+ }
+
+ /* find out if we're going to be shooting */
+ retval = ai_checkattack(self, 0);
+
+ /* record sightings of player */
+ if ((self->enemy) && (self->enemy->inuse) &&
+ (visible(self, self->enemy)))
+ {
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+ self->monsterinfo.trail_time = level.time;
+ self->monsterinfo.blind_fire_delay = 0;
+ }
+ /* check retval to make sure we're not blindfiring */
+ else if (!retval)
+ {
+ FindTarget(self);
+ return;
+ }
+ }
+ else
+ {
+ FindTarget(self);
+ }
+
+ return;
+ }
+
+ if (FindTarget(self))
+ {
+ return;
+ }
+
+ if (level.time > self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.walk(self);
+ return;
+ }
+
+ if (!(self->spawnflags & 1) && (self->monsterinfo.idle) &&
+ (level.time > self->monsterinfo.idle_time))
+ {
+ if (self->monsterinfo.idle_time)
+ {
+ self->monsterinfo.idle(self);
+ self->monsterinfo.idle_time = level.time + 15 + random() * 15;
+ }
+ else
+ {
+ self->monsterinfo.idle_time = level.time + random() * 15;
+ }
+ }
+}
+
+/*
+ * The monster is walking it's beat
+ */
+void
+ai_walk(edict_t *self, float dist)
+{
+ M_MoveToGoal(self, dist);
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for noticing a player */
+ if (FindTarget(self))
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.search) &&
+ (level.time > self->monsterinfo.idle_time))
+ {
+ if (self->monsterinfo.idle_time)
+ {
+ self->monsterinfo.search(self);
+ self->monsterinfo.idle_time = level.time + 15 + random() * 15;
+ }
+ else
+ {
+ self->monsterinfo.idle_time = level.time + random() * 15;
+ }
+ }
+}
+
+/*
+ * Turns towards target and advances
+ * Use this call with a distnace of 0
+ * to replace ai_face
+ */
+void
+ai_charge(edict_t *self, float dist)
+{
+ vec3_t v;
+ float ofs;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+ }
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, v);
+ self->ideal_yaw = vectoyaw(v);
+ }
+
+ M_ChangeYaw(self);
+
+ if (dist)
+ {
+ if (self->monsterinfo.aiflags & AI_CHARGING)
+ {
+ M_MoveToGoal(self, dist);
+ return;
+ }
+
+ /* circle strafe support */
+ if (self->monsterinfo.attack_state == AS_SLIDING)
+ {
+ /* if we're fighting a tesla, NEVER circle strafe */
+ if ((self->enemy) && (self->enemy->classname) &&
+ (!strcmp(self->enemy->classname, "tesla")))
+ {
+ ofs = 0;
+ }
+ else if (self->monsterinfo.lefty)
+ {
+ ofs = 90;
+ }
+ else
+ {
+ ofs = -90;
+ }
+
+ if (M_walkmove(self, self->ideal_yaw + ofs, dist))
+ {
+ return;
+ }
+
+ self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+ M_walkmove(self, self->ideal_yaw - ofs, dist);
+ }
+ else
+ {
+ M_walkmove(self, self->s.angles[YAW], dist);
+ }
+ }
+}
+
+/*
+ * Don't move, but turn towards
+ * ideal_yaw. Distance is for
+ * slight position adjustments
+ * needed by the animations
+ */
+void
+ai_turn(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (dist)
+ {
+ M_walkmove(self, self->s.angles[YAW], dist);
+ }
+
+ if (FindTarget(self))
+ {
+ return;
+ }
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ M_ChangeYaw(self);
+ }
+}
+
+/* ========================================================================== */
+
+/*
+ * .enemy
+ * Will be world if not currently angry at anyone.
+ *
+ * .movetarget
+ * The next path spot to walk toward. If .enemy, ignore .movetarget.
+ * When an enemy is killed, the monster will try to return to it's path.
+ *
+ * .hunt_time
+ * Set to time + something when the player is in sight, but movement straight for
+ * him is blocked. This causes the monster to use wall following code for
+ * movement direction instead of sighting on the player.
+ *
+ * .ideal_yaw
+ * A yaw angle of the intended direction, which will be turned towards at up
+ * to 45 deg / state. If the enemy is in view and hunt_time is not active,
+ * this will be the exact line towards the enemy.
+ *
+ * .pausetime
+ * A monster will leave it's stand state and head towards it's .movetarget when
+ * time > .pausetime.
+ */
+
+/* ========================================================================== */
+
+/*
+ * returns the range catagorization of an entity reletive to self
+ * 0 melee range, will become hostile even if back is turned
+ * 1 visibility and infront, or visibility and show hostile
+ * 2 infront and show hostile
+ * 3 only triggered by damage
+ */
+int
+range(edict_t *self, edict_t *other)
+{
+ vec3_t v;
+ float len;
+
+ if (!self || !other)
+ {
+ return 0;
+ }
+
+ VectorSubtract(self->s.origin, other->s.origin, v);
+ len = VectorLength(v);
+
+ if (len < MELEE_DISTANCE)
+ {
+ return RANGE_MELEE;
+ }
+
+ if (len < 500)
+ {
+ return RANGE_NEAR;
+ }
+
+ if (len < 1000)
+ {
+ return RANGE_MID;
+ }
+
+ return RANGE_FAR;
+}
+
+/*
+ * returns 1 if the entity is visible
+ * to self, even if not infront
+ */
+qboolean
+visible(edict_t *self, edict_t *other)
+{
+ vec3_t spot1;
+ vec3_t spot2;
+ trace_t trace;
+
+ if (!self || !other)
+ {
+ return false;
+ }
+
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(other->s.origin, spot2);
+ spot2[2] += other->viewheight;
+ trace = gi.trace(spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
+
+ if ((trace.fraction == 1.0) || (trace.ent == other))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * returns 1 if the entity is in
+ * front (in sight) of self
+ */
+qboolean
+infront(edict_t *self, edict_t *other)
+{
+ vec3_t vec;
+ float dot;
+ vec3_t forward;
+
+ if (!self || !other)
+ {
+ return false;
+ }
+
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+
+ VectorSubtract(other->s.origin, self->s.origin, vec);
+ VectorNormalize(vec);
+ dot = DotProduct(vec, forward);
+
+ if (dot > 0.3)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/* ============================================================================ */
+
+void
+HuntTarget(edict_t *self)
+{
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->goalentity = self->enemy;
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.stand(self);
+ }
+ else
+ if (self->monsterinfo.run)
+ {
+ self->monsterinfo.run(self);
+ }
+
+ if(visible(self, self->enemy))
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ }
+
+ self->ideal_yaw = vectoyaw(vec);
+
+ /* wait a while before first attack */
+ if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+ {
+ AttackFinished(self, 1);
+ }
+}
+
+void
+FoundTarget(edict_t *self)
+{
+ if (!self|| !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ /* let other monsters see this monster for a while */
+ if (self->enemy->client)
+ {
+ if (self->enemy->flags & FL_DISGUISED)
+ {
+ self->enemy->flags &= ~FL_DISGUISED;
+ }
+
+ level.sight_entity = self;
+ level.sight_entity_framenum = level.framenum;
+ level.sight_entity->light_level = 128;
+ }
+
+ self->show_hostile = level.time + 1; /* wake up other monsters */
+
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.trail_time = level.time;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+ self->monsterinfo.blind_fire_delay = 0;
+
+ if (!self->combattarget)
+ {
+ HuntTarget(self);
+ return;
+ }
+
+ self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
+
+ if (!self->movetarget)
+ {
+ self->goalentity = self->movetarget = self->enemy;
+ HuntTarget(self);
+ gi.dprintf("%s at %s, combattarget %s not found\n",
+ self->classname, vtos(self->s.origin),
+ self->combattarget);
+ return;
+ }
+
+ /* clear out our combattarget, these are a one shot deal */
+ self->combattarget = NULL;
+ self->monsterinfo.aiflags |= AI_COMBAT_POINT;
+
+ /* clear the targetname, that point is ours! */
+ self->movetarget->targetname = NULL;
+ self->monsterinfo.pausetime = 0;
+
+ /* run for it */
+ self->monsterinfo.run(self);
+}
+
+/*
+ * Self is currently not attacking anything,
+ * so try to find a target
+ *
+ * Returns TRUE if an enemy was sighted
+ *
+ * When a player fires a missile, the point
+ * of impact becomes a fakeplayer so that
+ * monsters that see the impact will respond
+ * as if they had seen the player.
+ *
+ * To avoid spending too much time, only
+ * a single client (or fakeclient) is
+ * checked each frame. This means multi
+ * player games will have slightly
+ * slower noticing monsters.
+ */
+qboolean
+FindTarget(edict_t *self)
+{
+ edict_t *client;
+ qboolean heardit;
+ int r;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_GOOD_GUY)
+ {
+ return false;
+ }
+
+ /* if we're going to a combat point, just proceed */
+ if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+ {
+ return false;
+ }
+
+ /* if the first spawnflag bit is set, the monster
+ will only wake up on really seeing the player,
+ not another monster getting angry or hearing
+ something */
+
+ heardit = false;
+
+ if ((level.sight_entity_framenum >= (level.framenum - 1)) &&
+ !(self->spawnflags & 1))
+ {
+ client = level.sight_entity;
+
+ if (client->enemy == self->enemy)
+ {
+ return false;
+ }
+ }
+ else if (level.disguise_violation_framenum > level.framenum)
+ {
+ client = level.disguise_violator;
+ }
+ else if (level.sound_entity_framenum >= (level.framenum - 1))
+ {
+ client = level.sound_entity;
+ heardit = true;
+ }
+ else if (!(self->enemy) &&
+ (level.sound2_entity_framenum >= (level.framenum - 1)) &&
+ !(self->spawnflags & 1))
+ {
+ client = level.sound2_entity;
+ heardit = true;
+ }
+ else
+ {
+ client = level.sight_client;
+ }
+
+ /* if the entity went away, forget it */
+ if (!client || !client->inuse ||
+ (client->client && level.intermissiontime))
+ {
+ return false;
+ }
+
+ if (client == self->enemy)
+ {
+ return true;
+ }
+
+ if ((self->monsterinfo.aiflags & AI_HINT_PATH) && (coop) && (coop->value))
+ {
+ heardit = false;
+ }
+
+ if (client->client)
+ {
+ if (client->flags & FL_NOTARGET)
+ {
+ return false;
+ }
+ }
+ else if (client->svflags & SVF_MONSTER)
+ {
+ if (!client->enemy)
+ {
+ return false;
+ }
+
+ if (client->enemy->flags & FL_NOTARGET)
+ {
+ return false;
+ }
+ }
+ else if (heardit)
+ {
+ if ((client->owner) && (client->owner->flags & FL_NOTARGET))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ if (!heardit)
+ {
+ r = range(self, client);
+
+ if (r == RANGE_FAR)
+ {
+ return false;
+ }
+
+ /* is client in an spot too dark to be seen? */
+ if (client->light_level <= 5)
+ {
+ return false;
+ }
+
+ if (!visible(self, client))
+ {
+ return false;
+ }
+
+ if (r == RANGE_NEAR)
+ {
+ if ((client->show_hostile < level.time) && !infront(self, client))
+ {
+ return false;
+ }
+ }
+ else if (r == RANGE_MID)
+ {
+ if (!infront(self, client))
+ {
+ return false;
+ }
+ }
+
+ self->enemy = client;
+
+ if (strcmp(self->enemy->classname, "player_noise") != 0)
+ {
+ self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+ if (!self->enemy->client)
+ {
+ self->enemy = self->enemy->enemy;
+
+ if (!self->enemy->client)
+ {
+ self->enemy = NULL;
+ return false;
+ }
+ }
+ }
+ }
+ else /* heardit */
+ {
+ vec3_t temp;
+
+ if (self->spawnflags & 1)
+ {
+ if (!visible(self, client))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!gi.inPHS(self->s.origin, client->s.origin))
+ {
+ return false;
+ }
+ }
+
+ VectorSubtract(client->s.origin, self->s.origin, temp);
+
+ if (VectorLength(temp) > 1000) /* too far to hear */
+ {
+ return false;
+ }
+
+ /* check area portals - if they are different
+ and not connected then we can't hear it */
+ if (client->areanum != self->areanum)
+ {
+ if (!gi.AreasConnected(self->areanum, client->areanum))
+ {
+ return false;
+ }
+ }
+
+ self->ideal_yaw = vectoyaw(temp);
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ M_ChangeYaw(self);
+ }
+
+ /* hunt the sound for a bit; hopefully find the real player */
+ self->monsterinfo.aiflags |= AI_SOUND_TARGET;
+ self->enemy = client;
+ }
+
+ /* if we got an enemy, we need to bail out of
+ hint paths, so take over here */
+ if (self->monsterinfo.aiflags & AI_HINT_PATH)
+ {
+ /* this calls foundtarget for us */
+ hintpath_stop(self);
+ }
+ else
+ {
+ FoundTarget(self);
+ }
+
+ if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) &&
+ (self->monsterinfo.sight))
+ {
+ self->monsterinfo.sight(self, self->enemy);
+ }
+
+ return true;
+}
+
+/* ============================================================================= */
+
+qboolean
+FacingIdeal(edict_t *self)
+{
+ float delta;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
+
+ if ((delta > 45) && (delta < 315))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+/* ============================================================================= */
+
+qboolean
+M_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ float chance;
+ trace_t tr;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA |
+ CONTENTS_WINDOW);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ /* we want them to go ahead and shoot at info_notnulls if they can. */
+ if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0))
+ {
+ /* if we can't see our target, and we're not
+ blocked by a monster, go into blind fire
+ if available */
+ if ((!(tr.ent->svflags & SVF_MONSTER)) &&
+ (!visible(self, self->enemy)))
+ {
+ if ((self->monsterinfo.blindfire) &&
+ (self->monsterinfo.blind_fire_delay <= 20.0))
+ {
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (level.time <
+ (self->monsterinfo.trail_time +
+ self->monsterinfo.blind_fire_delay))
+ {
+ /* wait for our time */
+ return false;
+ }
+ else
+ {
+ /* make sure we're not going to shoot a monster */
+ tr = gi.trace(spot1, NULL, NULL,
+ self->monsterinfo.blind_fire_target,
+ self, CONTENTS_MONSTER);
+
+ if (tr.allsolid || tr.startsolid ||
+ ((tr.fraction < 1.0) &&
+ (tr.ent != self->enemy)))
+ {
+ return false;
+ }
+
+ self->monsterinfo.attack_state = AS_BLIND;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ /* don't always melee in easy mode */
+ if ((skill->value == SKILL_EASY) && (rand() & 3))
+ {
+ /* fix for melee only monsters & strafing */
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ return false;
+ }
+
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ /* fix for melee only monsters & strafing */
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.1;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.02;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ chance *= 0.5;
+ }
+ else if (skill->value >= SKILL_HARD)
+ {
+ chance *= 2;
+ }
+
+ /* go ahead and shoot every time if it's a info_notnull */
+ if ((random() < chance) || (self->enemy->solid == SOLID_NOT))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ /* daedalus should strafe more.. this can be done
+ here or in a customized check_attack code for
+ the hover. */
+ if (self->flags & FL_FLY)
+ {
+ /* originally, just 0.3 */
+ float strafe_chance;
+
+ if (!(strcmp(self->classname, "monster_daedalus")))
+ {
+ strafe_chance = 0.8;
+ }
+ else
+ {
+ strafe_chance = 0.6;
+ }
+
+ /* if enemy is tesla, never strafe */
+ if ((self->enemy->classname) &&
+ (!strcmp(self->enemy->classname, "tesla")))
+ {
+ strafe_chance = 0;
+ }
+
+ if (random() < strafe_chance)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+ else
+ {
+ /* do we want the monsters strafing? */
+ if (random() < 0.4)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Turn and close until within an
+ * angle to launch a melee attack
+ */
+void
+ai_run_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->ideal_yaw = enemy_yaw;
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ M_ChangeYaw(self);
+ }
+
+ if (FacingIdeal(self))
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.melee(self);
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+}
+
+/*
+ * Turn in place until within an
+ * angle to launch a missile attack
+ */
+void
+ai_run_missile(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->ideal_yaw = enemy_yaw;
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ M_ChangeYaw(self);
+ }
+
+ if (FacingIdeal(self))
+ {
+ if (self->monsterinfo.attack)
+ {
+ self->monsterinfo.attack(self);
+
+ if ((self->monsterinfo.attack_state == AS_MISSILE) ||
+ (self->monsterinfo.attack_state == AS_BLIND)) {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+ }
+}
+
+/*
+ * Strafe sideways, but stay at
+ * aproximately the same range
+ */
+void
+ai_run_slide(edict_t *self, float distance)
+{
+ float ofs;
+ float angle;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->ideal_yaw = enemy_yaw;
+ angle = 90;
+
+ if (self->monsterinfo.lefty)
+ {
+ ofs = angle;
+ }
+ else
+ {
+ ofs = -angle;
+ }
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ M_ChangeYaw(self);
+ }
+
+ /* clamp maximum sideways move for non flyers to make them look less jerky */
+ if (!(self->flags & FL_FLY))
+ {
+ distance = min(distance, 8.0);
+ }
+
+ if (M_walkmove(self, self->ideal_yaw + ofs, distance))
+ {
+ return;
+ }
+
+ /* if we're dodging, give up on it and go straight */
+ if (self->monsterinfo.aiflags & AI_DODGING)
+ {
+ monster_done_dodge(self);
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ return;
+ }
+
+ self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+
+ if (M_walkmove(self, self->ideal_yaw - ofs, distance))
+ {
+ return;
+ }
+
+ /* if we're dodging, give up on it and go straight */
+ if (self->monsterinfo.aiflags & AI_DODGING)
+ {
+ monster_done_dodge(self);
+ }
+
+ /* the move failed, so signal the caller (ai_run) to try going straight */
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+}
+
+/*
+ * Decides if we're going to attack
+ * or do something else used by
+ * ai_run and ai_stand
+ */
+static qboolean
+hesDeadJim(const edict_t *self)
+{
+ const edict_t *enemy = self->enemy;
+
+ if (!enemy || !enemy->inuse)
+ {
+ return true;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ return (enemy->health > 0);
+ }
+
+ if (enemy->client && level.intermissiontime)
+ {
+ return true;
+ }
+
+ if (self->monsterinfo.aiflags & AI_BRUTAL)
+ {
+ return (enemy->health <= -80);
+ }
+
+ return (enemy->health <= 0);
+}
+
+qboolean
+ai_checkattack(edict_t *self, float dist)
+{
+ vec3_t temp;
+ qboolean retval;
+
+ if (!self)
+ {
+ enemy_vis = false;
+
+ return false;
+ }
+
+ /* this causes monsters to run blindly
+ to the combat point w/o firing */
+ if (self->goalentity)
+ {
+ if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+ {
+ return false;
+ }
+
+ if ((self->monsterinfo.aiflags & AI_SOUND_TARGET) && !visible(self, self->goalentity))
+ {
+ if ((level.time - self->enemy->last_sound_time) > 5.0)
+ {
+ if (self->goalentity == self->enemy)
+ {
+ if (self->movetarget)
+ {
+ self->goalentity = self->movetarget;
+ }
+ else
+ {
+ self->goalentity = NULL;
+ }
+ }
+
+ self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+ if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+ {
+ self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+ }
+ }
+ else
+ {
+ self->show_hostile = level.time + 1;
+ return false;
+ }
+ }
+ }
+
+ enemy_vis = false;
+
+ /* see if the enemy is dead */
+ if (hesDeadJim(self))
+ {
+ self->enemy = NULL;
+ self->monsterinfo.aiflags &= ~AI_MEDIC;
+
+ if (self->oldenemy && (self->oldenemy->health > 0))
+ {
+ self->enemy = self->oldenemy;
+ self->oldenemy = NULL;
+ HuntTarget(self);
+ }
+ else if (self->monsterinfo.last_player_enemy &&
+ (self->monsterinfo.last_player_enemy->health > 0))
+ {
+ self->enemy = self->monsterinfo.last_player_enemy;
+ self->oldenemy = NULL;
+ self->monsterinfo.last_player_enemy = NULL;
+ HuntTarget(self);
+ }
+ else
+ {
+ if (self->movetarget)
+ {
+ self->goalentity = self->movetarget;
+ self->monsterinfo.walk(self);
+ }
+ else
+ {
+ /* we need the pausetime otherwise the stand code
+ will just revert to walking with no target and
+ the monsters will wonder around aimlessly trying
+ to hunt the world entity */
+ self->monsterinfo.pausetime = level.time + 100000000;
+ self->monsterinfo.stand(self);
+ }
+
+ return true;
+ }
+ }
+
+ self->show_hostile = level.time + 1; /* wake up other monsters */
+
+ /* check knowledge of enemy */
+ enemy_vis = visible(self, self->enemy);
+
+ if (enemy_vis)
+ {
+ self->monsterinfo.search_time = level.time + 5;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ self->monsterinfo.trail_time = level.time;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+ self->monsterinfo.blind_fire_delay = 0;
+ }
+
+ if (coop && coop->value && (self->monsterinfo.search_time < level.time))
+ {
+ if (FindTarget(self))
+ {
+ return true;
+ }
+ }
+
+ if (self->enemy)
+ {
+ enemy_infront = infront(self, self->enemy);
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+ }
+
+ retval = self->monsterinfo.checkattack(self);
+
+ if (retval)
+ {
+ if (self->monsterinfo.attack_state == AS_MISSILE)
+ {
+ ai_run_missile(self);
+ return true;
+ }
+
+ if (self->monsterinfo.attack_state == AS_MELEE)
+ {
+ ai_run_melee(self);
+ return true;
+ }
+
+ /* added so monsters can shoot blind */
+ if (self->monsterinfo.attack_state == AS_BLIND)
+ {
+ ai_run_missile(self);
+ return true;
+ }
+
+ /* if enemy is not currently visible,
+ we will never attack */
+ if (!enemy_vis)
+ {
+ return false;
+ }
+ }
+
+ return retval;
+}
+
+/*
+ * The monster has an enemy
+ * it is trying to kill
+ */
+void
+ai_run(edict_t *self, float dist)
+{
+ vec3_t v;
+ edict_t *tempgoal;
+ edict_t *save;
+ qboolean new;
+ edict_t *marker;
+ float d1, d2;
+ trace_t tr;
+ vec3_t v_forward, v_right;
+ float left, center, right;
+ vec3_t left_target, right_target;
+ qboolean retval;
+ qboolean alreadyMoved = false;
+ qboolean gotcha = false;
+ edict_t *realEnemy;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we're going to a combat point, just proceed */
+ if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+ {
+ M_MoveToGoal(self, dist);
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ }
+
+ if (self->maxs[2] != self->monsterinfo.base_height)
+ {
+ monster_duck_up(self);
+ }
+
+ /* if we're currently looking for a hint path */
+ if (self->monsterinfo.aiflags & AI_HINT_PATH)
+ {
+ M_MoveToGoal(self, dist);
+
+ if (!self->inuse)
+ {
+ return;
+ }
+
+ /* first off, make sure we're looking for
+ the player, not a noise he made */
+ if (self->enemy)
+ {
+ if (self->enemy->inuse)
+ {
+ if (strcmp(self->enemy->classname, "player_noise") != 0)
+ {
+ realEnemy = self->enemy;
+ }
+ else if (self->enemy->owner)
+ {
+ realEnemy = self->enemy->owner;
+ }
+ else /* uh oh, can't figure out enemy, bail */
+ {
+ self->enemy = NULL;
+ hintpath_stop(self);
+ return;
+ }
+ }
+ else
+ {
+ self->enemy = NULL;
+ hintpath_stop(self);
+ return;
+ }
+ }
+ else
+ {
+ hintpath_stop(self);
+ return;
+ }
+
+ if (visible(self, realEnemy))
+ {
+ gotcha = true;
+ }
+ else if (coop->value)
+ {
+ FindTarget(self);
+ }
+
+ /* if we see the player, stop following hintpaths. */
+ if (gotcha)
+ {
+ /* disconnect from hintpaths and start looking normally for players. */
+ hintpath_stop(self);
+ }
+
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+ {
+ /* paranoia checking */
+ if (self->enemy)
+ {
+ VectorSubtract(self->s.origin, self->enemy->s.origin, v);
+ }
+
+ if ((!self->enemy) || (VectorLength(v) < 64))
+ {
+ self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+ self->monsterinfo.stand(self);
+ return;
+ }
+
+ M_MoveToGoal(self, dist);
+ /* prevent double moves for sound_targets */
+ alreadyMoved = true;
+
+ if (!self->inuse)
+ {
+ return;
+ }
+
+ if (!FindTarget(self))
+ {
+ return;
+ }
+ }
+
+ retval = ai_checkattack(self, dist);
+
+ /* don't strafe if we can't see our enemy */
+ if ((!enemy_vis) && (self->monsterinfo.attack_state == AS_SLIDING))
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+
+ /* unless we're dodging (dodging out of view looks smart) */
+ if (self->monsterinfo.aiflags & AI_DODGING)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+
+ if (self->monsterinfo.attack_state == AS_SLIDING)
+ {
+ /* protect against double moves */
+ if (!alreadyMoved)
+ {
+ ai_run_slide(self, dist);
+ }
+
+ /* we're using attack_state as the return value out of
+ ai_run_slide to indicate whether or not the move
+ succeeded. If the move succeeded, and we're still
+ sliding, we're done in here (since we've had our
+ chance to shoot in ai_checkattack, and have moved).
+ if the move failed, our state is as_straight, and
+ it will be taken care of below */
+ if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING))
+ {
+ return;
+ }
+ }
+ else if (self->monsterinfo.aiflags & AI_CHARGING)
+ {
+ self->ideal_yaw = enemy_yaw;
+
+ if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+ {
+ M_ChangeYaw(self);
+ }
+ }
+
+ if (retval)
+ {
+ if ((dist != 0) && (!alreadyMoved) &&
+ (self->monsterinfo.attack_state == AS_STRAIGHT) &&
+ (!(self->monsterinfo.aiflags & AI_STAND_GROUND)))
+ {
+ M_MoveToGoal(self, dist);
+ }
+
+ if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
+ {
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.trail_time = level.time;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+ self->monsterinfo.blind_fire_delay = 0;
+ }
+
+ return;
+ }
+
+ if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
+ {
+ /* check for alreadyMoved */
+ if (!alreadyMoved)
+ {
+ M_MoveToGoal(self, dist);
+ }
+
+ if (!self->inuse)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.trail_time = level.time;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+ self->monsterinfo.blind_fire_delay = 0;
+
+ return;
+ }
+
+ if ((self->monsterinfo.trail_time + 5) <= level.time)
+ {
+ /* and we haven't checked for valid hint paths in the last 10 seconds */
+ if ((self->monsterinfo.last_hint_time + 10) <= level.time)
+ {
+ /* check for hint_paths. */
+ self->monsterinfo.last_hint_time = level.time;
+
+ if (monsterlost_checkhint(self))
+ {
+ return;
+ }
+ }
+ }
+
+ if ((self->monsterinfo.search_time) &&
+ (level.time > (self->monsterinfo.search_time + 20)))
+ {
+ /* double move protection */
+ if (!alreadyMoved)
+ {
+ M_MoveToGoal(self, dist);
+ }
+
+ self->monsterinfo.search_time = 0;
+ return;
+ }
+
+ tempgoal = G_SpawnOptional();
+
+ if (!tempgoal)
+ {
+ M_MoveToGoal(self, dist);
+ return;
+ }
+
+ save = self->goalentity;
+ self->goalentity = tempgoal;
+
+ new = false;
+
+ if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
+ {
+ /* just lost sight of the player, decide where to go first */
+ self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
+ self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
+ new = true;
+ }
+
+ if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
+ {
+ self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
+
+ /* give ourself more time since we got this far */
+ self->monsterinfo.search_time = level.time + 5;
+
+ if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
+ {
+ self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
+ marker = NULL;
+ VectorCopy(self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
+ new = true;
+ }
+ else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
+ {
+ self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
+ marker = PlayerTrail_PickFirst(self);
+ }
+ else
+ {
+ marker = PlayerTrail_PickNext(self);
+ }
+
+ if (marker)
+ {
+ VectorCopy(marker->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.trail_time = marker->timestamp;
+ self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
+ new = true;
+ }
+ }
+
+ VectorSubtract(self->s.origin, self->monsterinfo.last_sighting, v);
+ d1 = VectorLength(v);
+
+ if (d1 <= dist)
+ {
+ self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
+ dist = d1;
+ }
+
+ VectorCopy(self->monsterinfo.last_sighting, self->goalentity->s.origin);
+
+ if (new)
+ {
+ tr = gi.trace(self->s.origin, self->mins, self->maxs,
+ self->monsterinfo.last_sighting, self,
+ MASK_PLAYERSOLID);
+
+ if (tr.fraction < 1)
+ {
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ d1 = VectorLength(v);
+ center = tr.fraction;
+ d2 = d1 * ((center + 1) / 2);
+ self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+ AngleVectors(self->s.angles, v_forward, v_right, NULL);
+
+ VectorSet(v, d2, -16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward, v_right, left_target);
+ tr = gi.trace(self->s.origin, self->mins, self->maxs,
+ left_target, self, MASK_PLAYERSOLID);
+ left = tr.fraction;
+
+ VectorSet(v, d2, 16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward, v_right, right_target);
+ tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target,
+ self, MASK_PLAYERSOLID);
+ right = tr.fraction;
+
+ center = (d1 * center) / d2;
+
+ if ((left >= center) && (left > right))
+ {
+ if (left < 1)
+ {
+ VectorSet(v, d2 * left * 0.5, -16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward,
+ v_right, left_target);
+ }
+
+ VectorCopy(self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+ self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+ VectorCopy(left_target, self->goalentity->s.origin);
+ VectorCopy(left_target, self->monsterinfo.last_sighting);
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+ }
+ else if ((right >= center) && (right > left))
+ {
+ if (right < 1)
+ {
+ VectorSet(v, d2 * right * 0.5, 16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward, v_right,
+ right_target);
+ }
+
+ VectorCopy(self->monsterinfo.last_sighting,
+ self->monsterinfo.saved_goal);
+ self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+ VectorCopy(right_target, self->goalentity->s.origin);
+ VectorCopy(right_target, self->monsterinfo.last_sighting);
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+ }
+ }
+ }
+
+ M_MoveToGoal(self, dist);
+
+ if (!self->inuse)
+ {
+ return;
+ }
+
+ G_FreeEdict(tempgoal);
+
+ self->goalentity = save;
+}
diff --git a/rogue/src/g_chase.c b/rogue/src/g_chase.c
new file mode 100644
index 0000000..566fa8f
--- /dev/null
+++ b/rogue/src/g_chase.c
@@ -0,0 +1,244 @@
+/*
+ * =======================================================================
+ *
+ * Chase cam. Only used in multiplayer mode.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+void
+UpdateChaseCam(edict_t *ent)
+{
+ vec3_t o, ownerv, goal;
+ edict_t *targ;
+ vec3_t forward, right;
+ trace_t trace;
+ int i;
+ vec3_t angles;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* is our chase target gone? */
+ if (!ent->client->chase_target->inuse ||
+ ent->client->chase_target->client->resp.spectator)
+ {
+ edict_t *old = ent->client->chase_target;
+ ChaseNext(ent);
+
+ if (ent->client->chase_target == old)
+ {
+ ent->client->chase_target = NULL;
+ ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+ return;
+ }
+ }
+
+ targ = ent->client->chase_target;
+
+ VectorCopy(targ->s.origin, ownerv);
+ ownerv[2] += targ->viewheight;
+
+ VectorCopy(targ->client->v_angle, angles);
+
+ if (angles[PITCH] > 56)
+ {
+ angles[PITCH] = 56;
+ }
+
+ AngleVectors(angles, forward, right, NULL);
+ VectorNormalize(forward);
+ VectorMA(ownerv, -30, forward, o);
+
+ if (o[2] < targ->s.origin[2] + 20)
+ {
+ o[2] = targ->s.origin[2] + 20;
+ }
+
+ /* jump animation lifts */
+ if (!targ->groundentity)
+ {
+ o[2] += 16;
+ }
+
+ trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+ VectorCopy(trace.endpos, goal);
+
+ VectorMA(goal, 2, forward, goal);
+
+ /* pad for floors and ceilings */
+ VectorCopy(goal, o);
+ o[2] += 6;
+ trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+ if (trace.fraction < 1)
+ {
+ VectorCopy(trace.endpos, goal);
+ goal[2] -= 6;
+ }
+
+ VectorCopy(goal, o);
+ o[2] -= 6;
+ trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+ if (trace.fraction < 1)
+ {
+ VectorCopy(trace.endpos, goal);
+ goal[2] += 6;
+ }
+
+ if (targ->deadflag)
+ {
+ ent->client->ps.pmove.pm_type = PM_DEAD;
+ }
+ else
+ {
+ ent->client->ps.pmove.pm_type = PM_FREEZE;
+ }
+
+ VectorCopy(goal, ent->s.origin);
+
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
+ targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
+ }
+
+ if (targ->deadflag)
+ {
+ ent->client->ps.viewangles[ROLL] = 40;
+ ent->client->ps.viewangles[PITCH] = -15;
+ ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
+ }
+ else
+ {
+ VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
+ VectorCopy(targ->client->v_angle, ent->client->v_angle);
+ }
+
+ ent->viewheight = 0;
+ ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
+ gi.linkentity(ent);
+}
+
+void
+ChaseNext(edict_t *ent)
+{
+ int i;
+ edict_t *e;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client->chase_target)
+ {
+ return;
+ }
+
+ i = ent->client->chase_target - g_edicts;
+
+ do
+ {
+ i++;
+
+ if (i > maxclients->value)
+ {
+ i = 1;
+ }
+
+ e = g_edicts + i;
+
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->client->resp.spectator)
+ {
+ break;
+ }
+ }
+ while (e != ent->client->chase_target);
+
+ ent->client->chase_target = e;
+ ent->client->update_chase = true;
+}
+
+void
+ChasePrev(edict_t *ent)
+{
+ int i;
+ edict_t *e;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client->chase_target)
+ {
+ return;
+ }
+
+ i = ent->client->chase_target - g_edicts;
+
+ do
+ {
+ i--;
+
+ if (i < 1)
+ {
+ i = maxclients->value;
+ }
+
+ e = g_edicts + i;
+
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->client->resp.spectator)
+ {
+ break;
+ }
+ }
+ while (e != ent->client->chase_target);
+
+ ent->client->chase_target = e;
+ ent->client->update_chase = true;
+}
+
+void
+GetChaseTarget(edict_t *ent)
+{
+ int i;
+ edict_t *other;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ for (i = 1; i <= maxclients->value; i++)
+ {
+ other = g_edicts + i;
+
+ if (other->inuse && !other->client->resp.spectator)
+ {
+ ent->client->chase_target = other;
+ ent->client->update_chase = true;
+ UpdateChaseCam(ent);
+ return;
+ }
+ }
+
+ gi.centerprintf(ent, "No other players to chase.");
+}
diff --git a/rogue/src/g_cmds.c b/rogue/src/g_cmds.c
new file mode 100644
index 0000000..94401d9
--- /dev/null
+++ b/rogue/src/g_cmds.c
@@ -0,0 +1,1925 @@
+/* =======================================================================
+ *
+ * Game command processing.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+#include "monster/misc/player.h"
+
+static char *
+ClientTeam(edict_t *ent, char* value)
+{
+ char *p;
+
+ value[0] = 0;
+
+ if (!ent)
+ {
+ return value;
+ }
+
+ if (!ent->client)
+ {
+ return value;
+ }
+
+ strcpy(value, Info_ValueForKey(ent->client->pers.userinfo, "skin"));
+ p = strchr(value, '/');
+
+ if (!p)
+ {
+ return value;
+ }
+
+ if ((int)(dmflags->value) & DF_MODELTEAMS)
+ {
+ *p = 0;
+ return value;
+ }
+
+ return ++p;
+}
+
+qboolean
+OnSameTeam(edict_t *ent1, edict_t *ent2)
+{
+ char ent1Team[512];
+ char ent2Team[512];
+
+ if (!ent1 || !ent2)
+ {
+ return false;
+ }
+
+ if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+ {
+ return false;
+ }
+
+ ClientTeam(ent1, ent1Team);
+ ClientTeam(ent2, ent2Team);
+
+ if (ent1Team[0] != '\0' && strcmp(ent1Team, ent2Team) == 0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+SelectNextItem(edict_t *ent, int itflags)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (cl->chase_target)
+ {
+ ChaseNext(ent);
+ return;
+ }
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ index = (cl->pers.selected_item + i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & itflags))
+ {
+ continue;
+ }
+
+ cl->pers.selected_item = index;
+ return;
+ }
+
+ cl->pers.selected_item = -1;
+}
+
+void
+SelectPrevItem(edict_t *ent, int itflags)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (cl->chase_target)
+ {
+ ChasePrev(ent);
+ return;
+ }
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ index = (cl->pers.selected_item + MAX_ITEMS - i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & itflags))
+ {
+ continue;
+ }
+
+ cl->pers.selected_item = index;
+ return;
+ }
+
+ cl->pers.selected_item = -1;
+}
+
+void
+ValidateSelectedItem(edict_t *ent)
+{
+ gclient_t *cl;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (cl->pers.inventory[cl->pers.selected_item])
+ {
+ return; /* valid */
+ }
+
+ SelectNextItem(ent, -1);
+}
+
+/* ================================================================================= */
+
+/*
+ * Give items to a client
+ */
+void
+Cmd_Give_f(edict_t *ent)
+{
+ char *name;
+ gitem_t *it;
+ int index;
+ int i;
+ qboolean give_all;
+ edict_t *it_ent;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ name = gi.args();
+
+ if (Q_stricmp(name, "all") == 0)
+ {
+ give_all = true;
+ }
+ else
+ {
+ give_all = false;
+ }
+
+ if (give_all || (Q_stricmp(gi.argv(1), "health") == 0))
+ {
+ if (gi.argc() == 3)
+ {
+ ent->health = atoi(gi.argv(2));
+ ent->health = ent->health < 1 ? 1 : ent->health;
+ }
+ else
+ {
+ ent->health = ent->max_health;
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "weapons") == 0))
+ {
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = itemlist + i;
+
+ if (!it->pickup)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ continue;
+ }
+
+ ent->client->pers.inventory[i] += 1;
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "ammo") == 0))
+ {
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = itemlist + i;
+
+ if (!it->pickup)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_AMMO))
+ {
+ continue;
+ }
+
+ Add_Ammo(ent, it, 1000);
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "armor") == 0))
+ {
+ gitem_armor_t *info;
+
+ it = FindItem("Jacket Armor");
+ ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+ it = FindItem("Combat Armor");
+ ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+ it = FindItem("Body Armor");
+ info = (gitem_armor_t *)it->info;
+ ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count;
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "Power Shield") == 0))
+ {
+ it = FindItem("Power Shield");
+ it_ent = G_Spawn();
+ it_ent->classname = it->classname;
+ SpawnItem(it_ent, it);
+ Touch_Item(it_ent, ent, NULL, NULL);
+
+ if (it_ent->inuse)
+ {
+ G_FreeEdict(it_ent);
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all)
+ {
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = itemlist + i;
+
+ if (!it->pickup)
+ {
+ continue;
+ }
+
+ if (it->flags & IT_NOT_GIVEABLE)
+ {
+ continue;
+ }
+
+ if (it->flags & (IT_ARMOR | IT_WEAPON | IT_AMMO))
+ {
+ continue;
+ }
+
+ ent->client->pers.inventory[i] = 1;
+ }
+
+ return;
+ }
+
+ it = FindItem(name);
+
+ if (!it)
+ {
+ name = gi.argv(1);
+ it = FindItem(name);
+
+ if (!it)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "unknown item\n");
+ return;
+ }
+ }
+
+ if (!it->pickup)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "non-pickup item\n");
+ return;
+ }
+
+ if (it->flags & IT_NOT_GIVEABLE)
+ {
+ gi.dprintf("item cannot be given\n");
+ return;
+ }
+
+ index = ITEM_INDEX(it);
+
+ if (it->flags & IT_AMMO)
+ {
+ if (gi.argc() == 3)
+ {
+ ent->client->pers.inventory[index] = atoi(gi.argv(2));
+ }
+ else
+ {
+ ent->client->pers.inventory[index] += it->quantity;
+ }
+ }
+ else
+ {
+ it_ent = G_Spawn();
+ it_ent->classname = it->classname;
+ SpawnItem(it_ent, it);
+
+ /* since some items don't actually spawn when you say to .. */
+ if (!it_ent->inuse)
+ {
+ return;
+ }
+
+ Touch_Item(it_ent, ent, NULL, NULL);
+
+ if (it_ent->inuse)
+ {
+ G_FreeEdict(it_ent);
+ }
+ }
+}
+
+/*
+ * Sets client to godmode
+ */
+void
+Cmd_God_f(edict_t *ent)
+{
+ char *msg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ ent->flags ^= FL_GODMODE;
+
+ if (!(ent->flags & FL_GODMODE))
+ {
+ msg = "godmode OFF\n";
+ }
+ else
+ {
+ msg = "godmode ON\n";
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, msg);
+}
+
+/*
+ * Sets client to notarget
+ */
+void
+Cmd_Notarget_f(edict_t *ent)
+{
+ char *msg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ ent->flags ^= FL_NOTARGET;
+
+ if (!(ent->flags & FL_NOTARGET))
+ {
+ msg = "notarget OFF\n";
+ }
+ else
+ {
+ msg = "notarget ON\n";
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, msg);
+}
+
+void
+Cmd_Noclip_f(edict_t *ent)
+{
+ char *msg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ if (ent->movetype == MOVETYPE_NOCLIP)
+ {
+ ent->movetype = MOVETYPE_WALK;
+ msg = "noclip OFF\n";
+ }
+ else
+ {
+ ent->movetype = MOVETYPE_NOCLIP;
+ msg = "noclip ON\n";
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, msg);
+}
+
+/*
+ * Use an inventory item
+ */
+void
+Cmd_Use_f(edict_t *ent)
+{
+ int index;
+ gitem_t *it;
+ char *s;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ s = gi.args();
+ it = FindItem(s);
+
+ if (!it)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "unknown item: %s\n", s);
+ return;
+ }
+
+ if (!it->use)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not usable.\n");
+ return;
+ }
+
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+
+ it->use(ent, it);
+}
+
+/*
+ * Drop an inventory item
+ */
+void
+Cmd_Drop_f(edict_t *ent)
+{
+ int index;
+ gitem_t *it;
+ char *s;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ s = gi.args();
+ it = FindItem(s);
+
+ if (!it)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "unknown item: %s\n", s);
+ return;
+ }
+
+ if (!it->drop)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not dropable.\n");
+ return;
+ }
+
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+
+ it->drop(ent, it);
+}
+
+void
+Cmd_Score_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->showinventory = false;
+ ent->client->showhelp = false;
+
+ if (!deathmatch->value && !coop->value)
+ {
+ return;
+ }
+
+ if (ent->client->showscores)
+ {
+ ent->client->showscores = false;
+ return;
+ }
+
+ ent->client->showscores = true;
+ DeathmatchScoreboardMessage(ent, ent->enemy);
+ gi.unicast(ent, true);
+}
+
+void
+Cmd_Help_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* this is for backwards compatability */
+ if (deathmatch->value)
+ {
+ Cmd_Score_f(ent);
+ return;
+ }
+
+ ent->client->showinventory = false;
+ ent->client->showscores = false;
+
+ if (ent->client->showhelp)
+ {
+ ent->client->showhelp = false;
+ return;
+ }
+
+ ent->client->showhelp = true;
+ ent->client->pers.helpchanged = 0;
+ HelpComputerMessage(ent);
+ gi.unicast(ent, true);
+}
+
+void
+Cmd_Inven_f(edict_t *ent)
+{
+ gclient_t *cl;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ cl->showscores = false;
+ cl->showhelp = false;
+
+ if (cl->showinventory)
+ {
+ cl->showinventory = false;
+ return;
+ }
+
+ cl->showinventory = true;
+
+ InventoryMessage(ent);
+ gi.unicast(ent, true);
+}
+
+void
+Cmd_InvUse_f(edict_t *ent)
+{
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ValidateSelectedItem(ent);
+
+ if (ent->client->pers.selected_item == -1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No item to use.\n");
+ return;
+ }
+
+ it = &itemlist[ent->client->pers.selected_item];
+
+ if (!it->use)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not usable.\n");
+ return;
+ }
+
+ it->use(ent, it);
+}
+
+void
+Cmd_WeapPrev_f(edict_t *ent)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+ int selected_weapon;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (!cl->pers.weapon)
+ {
+ return;
+ }
+
+ selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ /* prevent scrolling through ALL weapons */
+ index = (selected_weapon + MAX_ITEMS - i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ continue;
+ }
+
+ it->use(ent, it);
+
+ /* prevent scrolling through ALL weapons */
+ if (cl->newweapon == it)
+ {
+ return;
+ }
+ }
+}
+
+void
+Cmd_WeapNext_f(edict_t *ent)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+ int selected_weapon;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (!cl->pers.weapon)
+ {
+ return;
+ }
+
+ selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ /* prevent scrolling through ALL weapons */
+ index = (selected_weapon + i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ continue;
+ }
+
+ it->use(ent, it);
+
+ /* prevent scrolling through ALL weapons */
+ if (cl->newweapon == it)
+ {
+ return;
+ }
+ }
+}
+
+void
+Cmd_WeapLast_f(edict_t *ent)
+{
+ gclient_t *cl;
+ int index;
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (!cl->pers.weapon || !cl->pers.lastweapon)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(cl->pers.lastweapon);
+
+ if (!cl->pers.inventory[index])
+ {
+ return;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ return;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ return;
+ }
+
+ it->use(ent, it);
+}
+
+void
+Cmd_InvDrop_f(edict_t *ent)
+{
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ValidateSelectedItem(ent);
+
+ if (ent->client->pers.selected_item == -1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No item to drop.\n");
+ return;
+ }
+
+ it = &itemlist[ent->client->pers.selected_item];
+
+ if (!it->drop)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not dropable.\n");
+ return;
+ }
+
+ it->drop(ent, it);
+}
+
+void
+Cmd_Kill_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((level.time - ent->client->respawn_time) < 5)
+ {
+ return;
+ }
+
+ ent->flags &= ~FL_GODMODE;
+ ent->health = 0;
+ meansOfDeath = MOD_SUICIDE;
+
+ /* make sure no trackers are still hurting us. */
+ if (ent->client->tracker_pain_framenum)
+ {
+ RemoveAttackingPainDaemons(ent);
+ }
+
+ if (ent->client->owned_sphere)
+ {
+ G_FreeEdict(ent->client->owned_sphere);
+ ent->client->owned_sphere = NULL;
+ }
+
+ player_die(ent, ent, ent, 100000, vec3_origin);
+}
+
+void
+Cmd_PutAway_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->showscores = false;
+ ent->client->showhelp = false;
+ ent->client->showinventory = false;
+}
+
+int
+PlayerSort(void const *a, void const *b)
+{
+ int anum, bnum;
+
+ if (!a || !b)
+ {
+ return 0;
+ }
+
+ anum = *(int *)a;
+ bnum = *(int *)b;
+
+ anum = game.clients[anum].ps.stats[STAT_FRAGS];
+ bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
+
+ if (anum < bnum)
+ {
+ return -1;
+ }
+
+ if (anum > bnum)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+Cmd_Players_f(edict_t *ent)
+{
+ int i;
+ int count;
+ char small[64];
+ char large[1280];
+ int index[256];
+
+ if (!ent)
+ {
+ return;
+ }
+
+ count = 0;
+
+ for (i = 0; i < maxclients->value; i++)
+ {
+ if (game.clients[i].pers.connected)
+ {
+ index[count] = i;
+ count++;
+ }
+ }
+
+ /* sort by frags */
+ qsort(index, count, sizeof(index[0]), PlayerSort);
+
+ /* print information */
+ large[0] = 0;
+
+ for (i = 0; i < count; i++)
+ {
+ Com_sprintf(small, sizeof(small), "%3i %s\n",
+ game.clients[index[i]].ps.stats[STAT_FRAGS],
+ game.clients[index[i]].pers.netname);
+
+ if (strlen(small) + strlen(large) > sizeof(large) - 100)
+ {
+ /* can't print all of them in one packet */
+ strcat(large, "...\n");
+ break;
+ }
+
+ strcat(large, small);
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, "%s\n%i players\n", large, count);
+}
+
+void
+Cmd_Wave_f(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ i = atoi(gi.argv(1));
+
+ /* can't wave when ducked */
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ return;
+ }
+
+ if (ent->client->anim_priority > ANIM_WAVE)
+ {
+ return;
+ }
+
+ ent->client->anim_priority = ANIM_WAVE;
+
+ switch (i)
+ {
+ case 0:
+ gi.cprintf(ent, PRINT_HIGH, "flipoff\n");
+ ent->s.frame = FRAME_flip01 - 1;
+ ent->client->anim_end = FRAME_flip12;
+ break;
+ case 1:
+ gi.cprintf(ent, PRINT_HIGH, "salute\n");
+ ent->s.frame = FRAME_salute01 - 1;
+ ent->client->anim_end = FRAME_salute11;
+ break;
+ case 2:
+ gi.cprintf(ent, PRINT_HIGH, "taunt\n");
+ ent->s.frame = FRAME_taunt01 - 1;
+ ent->client->anim_end = FRAME_taunt17;
+ break;
+ case 3:
+ gi.cprintf(ent, PRINT_HIGH, "wave\n");
+ ent->s.frame = FRAME_wave01 - 1;
+ ent->client->anim_end = FRAME_wave11;
+ break;
+ case 4:
+ default:
+ gi.cprintf(ent, PRINT_HIGH, "point\n");
+ ent->s.frame = FRAME_point01 - 1;
+ ent->client->anim_end = FRAME_point12;
+ break;
+ }
+}
+
+static qboolean
+flooded(edict_t *ent)
+{
+ gclient_t *cl;
+ int i;
+ int num_msgs;
+ int mx;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!deathmatch->value && !coop->value)
+ {
+ return false;
+ }
+
+ num_msgs = flood_msgs->value;
+ if (num_msgs <= 0)
+ {
+ return false;
+ }
+
+ cl = ent->client;
+ mx = sizeof(cl->flood_when) / sizeof(cl->flood_when[0]);
+
+ if (num_msgs > mx)
+ {
+ gi.dprintf("flood_msgs lowered to max: 10\n");
+
+ num_msgs = mx;
+ gi.cvar_forceset("flood_msgs", "10");
+ }
+
+ if (level.time < cl->flood_locktill)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n",
+ (int)(cl->flood_locktill - level.time));
+
+ return true;
+ }
+
+ i = (cl->flood_whenhead - num_msgs) + 1;
+
+ if (i < 0)
+ {
+ i += mx;
+ }
+
+ if (cl->flood_when[i] &&
+ (level.time - cl->flood_when[i]) < flood_persecond->value)
+ {
+ cl->flood_locktill = level.time + flood_waitdelay->value;
+
+ gi.cprintf(ent, PRINT_CHAT,
+ "Flood protection: You can't talk for %d seconds.\n",
+ (int)flood_waitdelay->value);
+
+ return true;
+ }
+
+ cl->flood_whenhead = (cl->flood_whenhead + 1) % mx;
+ cl->flood_when[cl->flood_whenhead] = level.time;
+
+ return false;
+}
+
+void
+Cmd_Say_f(edict_t *ent, qboolean team, qboolean arg0)
+{
+ int j;
+ edict_t *other;
+ char *p;
+ char text[2048];
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((gi.argc() < 2) && !arg0)
+ {
+ return;
+ }
+
+ if (flooded(ent))
+ {
+ return;
+ }
+
+ if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+ {
+ team = false;
+ }
+
+ if (team)
+ {
+ Com_sprintf(text, sizeof(text), "(%s): ", ent->client->pers.netname);
+ }
+ else
+ {
+ Com_sprintf(text, sizeof(text), "%s: ", ent->client->pers.netname);
+ }
+
+ if (arg0)
+ {
+ strcat(text, gi.argv(0));
+ strcat(text, " ");
+ strcat(text, gi.args());
+ }
+ else
+ {
+ p = gi.args();
+
+ if (*p == '"')
+ {
+ p++;
+ p[strlen(p) - 1] = 0;
+ }
+
+ strcat(text, p);
+ }
+
+ /* don't let text be too long for malicious reasons */
+ if (strlen(text) > 150)
+ {
+ text[150] = 0;
+ }
+
+ strcat(text, "\n");
+
+ if (dedicated->value)
+ {
+ gi.cprintf(NULL, PRINT_CHAT, "%s", text);
+ }
+
+ for (j = 1; j <= game.maxclients; j++)
+ {
+ other = &g_edicts[j];
+
+ if (!other->inuse)
+ {
+ continue;
+ }
+
+ if (!other->client)
+ {
+ continue;
+ }
+
+ if (team)
+ {
+ if (!OnSameTeam(ent, other))
+ {
+ continue;
+ }
+ }
+
+ gi.cprintf(other, PRINT_CHAT, "%s", text);
+ }
+}
+
+void
+Cmd_Ent_Count_f(edict_t *ent)
+{
+ int x;
+ edict_t *e;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ x = 0;
+
+ for (e = g_edicts; e < &g_edicts[globals.num_edicts]; e++)
+ {
+ if (e->inuse)
+ {
+ x++;
+ }
+ }
+
+ gi.dprintf("%d entites active\n", x);
+}
+
+void
+Cmd_PlayerList_f(edict_t *ent)
+{
+ int i, text_len;
+ char st[80];
+ char text[1400];
+ edict_t *e2;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* connect time, ping, score, name */
+ *text = '\0';
+
+ for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++)
+ {
+ if (!e2->inuse)
+ {
+ continue;
+ }
+
+ Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n",
+ (level.framenum - e2->client->resp.enterframe) / 600,
+ ((level.framenum - e2->client->resp.enterframe) % 600) / 10,
+ e2->client->ping, e2->client->resp.score,
+ e2->client->pers.netname,
+ e2->client->resp.spectator ? " (spectator)" : "");
+
+ text_len = strlen(text);
+
+ if ((text_len + strlen(st)) > (sizeof(text) - 50))
+ {
+ snprintf(text + text_len, sizeof(text) - text_len, "And more...\n");
+ gi.cprintf(ent, PRINT_HIGH, "%s", text);
+ return;
+ }
+
+ strcat(text, st);
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, "%s", text);
+}
+
+void
+Cmd_Teleport_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ if (gi.argc() != 4)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: teleport x y z\n");
+ return;
+ }
+
+ /* Unlink it to prevent unwanted interactions with
+ other entities. This works because linkentity()
+ uses the first available slot and the player is
+ always at postion 0. */
+ gi.unlinkentity(ent);
+
+ /* Set new position */
+ ent->s.origin[0] = atof(gi.argv(1));
+ ent->s.origin[1] = atof(gi.argv(2));
+ ent->s.origin[2] = atof(gi.argv(3)) + 10.0;
+
+ /* Remove velocity and keep the entity briefly in place
+ to give the server and clients time to catch up. */
+ VectorClear(ent->velocity);
+ ent->client->ps.pmove.pm_time = 20;
+ ent->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+ /* Remove viewangles. They'll be recalculated
+ by the client at the next frame. */
+ VectorClear(ent->s.angles);
+ VectorClear(ent->client->ps.viewangles);
+ VectorClear(ent->client->v_angle);
+
+ /* Telefrag everything that's in the target location. */
+ KillBox(ent);
+
+ /* And link it back in. */
+ gi.linkentity(ent);
+}
+
+void
+Cmd_ListEntities_f(edict_t *ent)
+{
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ if (gi.argc() < 2)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: listentities <all|ammo|items|keys|monsters|weapons>\n");
+ return;
+ }
+
+ /* What to print? */
+ qboolean all = false;
+ qboolean ammo = false;
+ qboolean items = false;
+ qboolean keys = false;
+ qboolean monsters = false;
+ qboolean weapons = false;
+
+ for (int i = 1; i < gi.argc(); i++)
+ {
+ const char *arg = gi.argv(i);
+
+ if (Q_stricmp(arg, "all") == 0)
+ {
+ all = true;
+ }
+ else if (Q_stricmp(arg, "ammo") == 0)
+ {
+ ammo = true;
+ }
+ else if (Q_stricmp(arg, "items") == 0)
+ {
+ items = true;
+ }
+ else if (Q_stricmp(arg, "keys") == 0)
+ {
+ keys = true;
+ }
+ else if (Q_stricmp(arg, "monsters") == 0)
+ {
+ monsters = true;
+ }
+ else if (Q_stricmp(arg, "weapons") == 0)
+ {
+ weapons = true;
+ }
+ else
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: listentities <all|ammo|items|keys|monsters|weapons>\n");
+ }
+ }
+
+ /* Print what's requested. */
+ for (int i = 0; i < globals.num_edicts; i++)
+ {
+ edict_t *cur = &g_edicts[i];
+ qboolean print = false;
+
+ /* Ensure that the entity is valid. */
+ if (!cur->classname)
+ {
+ continue;
+ }
+
+ if (all)
+ {
+ print = true;
+ }
+ else
+ {
+ if (ammo)
+ {
+ if (strncmp(cur->classname, "ammo_", 5) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (items)
+ {
+ if (strncmp(cur->classname, "item_", 5) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (keys)
+ {
+ if (strncmp(cur->classname, "key_", 4) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (monsters)
+ {
+ if (strncmp(cur->classname, "monster_", 8) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (weapons)
+ {
+ if (strncmp(cur->classname, "weapon_", 7) == 0)
+ {
+ print = true;
+ }
+ }
+ }
+
+ if (print)
+ {
+ /* We use dprintf() because cprintf() may flood the server... */
+ gi.dprintf("%s: %f %f %f\n", cur->classname, cur->s.origin[0], cur->s.origin[1], cur->s.origin[2]);
+ }
+ }
+}
+
+static int
+get_ammo_usage(gitem_t *weap)
+{
+ if (!weap)
+ {
+ return 0;
+ }
+
+ /* handles grenades and tesla which only use 1 ammo per shot */
+ /* have to check this because they don't store their ammo usage in weap->quantity */
+ if (weap->flags & IT_AMMO)
+ {
+ return 1;
+ }
+
+ /* weapons store their ammo usage in the quantity field */
+ return weap->quantity;
+}
+
+static gitem_t *
+cycle_weapon(edict_t *ent)
+{
+ gclient_t *cl;
+ gitem_t *noammo_fallback;
+ gitem_t *noweap_fallback;
+ gitem_t *weap;
+ gitem_t *ammo;
+ int i;
+ int start;
+ int num_weaps;
+ const char *weapname = NULL;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ cl = ent->client;
+
+ if (!cl)
+ {
+ return NULL;
+ }
+
+ num_weaps = gi.argc();
+
+ /* find where we want to start the search for the next eligible weapon */
+ if (cl->newweapon)
+ {
+ weapname = cl->newweapon->classname;
+ }
+ else if (cl->pers.weapon)
+ {
+ weapname = cl->pers.weapon->classname;
+ }
+
+ if (weapname)
+ {
+ for (i = 1; i < num_weaps; i++)
+ {
+ if (Q_stricmp(weapname, gi.argv(i)) == 0)
+ {
+ break;
+ }
+ }
+
+ i++;
+
+ if (i >= num_weaps)
+ {
+ i = 1;
+ }
+ }
+ else
+ {
+ i = 1;
+ }
+
+ start = i;
+ noammo_fallback = NULL;
+ noweap_fallback = NULL;
+
+ /* find the first eligible weapon in the list we can switch to */
+ do
+ {
+ weap = FindItemByClassname(gi.argv(i));
+
+ if (weap && weap != cl->pers.weapon && (weap->flags & IT_WEAPON) && weap->use)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(weap)] > 0)
+ {
+ if (weap->ammo)
+ {
+ ammo = FindItem(weap->ammo);
+ if (ammo)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(ammo)] >= get_ammo_usage(weap))
+ {
+ return weap;
+ }
+
+ if (!noammo_fallback)
+ {
+ noammo_fallback = weap;
+ }
+ }
+ }
+ else
+ {
+ return weap;
+ }
+ }
+ else if (!noweap_fallback)
+ {
+ noweap_fallback = weap;
+ }
+ }
+
+ i++;
+
+ if (i >= num_weaps)
+ {
+ i = 1;
+ }
+ } while (i != start);
+
+ /* if no weapon was found, the fallbacks will be used for
+ printing the appropriate error message to the console
+ */
+
+ if (noammo_fallback)
+ {
+ return noammo_fallback;
+ }
+
+ return noweap_fallback;
+}
+
+void
+Cmd_CycleWeap_f(edict_t *ent)
+{
+ gitem_t *weap;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (gi.argc() <= 1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: cycleweap classname1 classname2 .. classnameN\n");
+ return;
+ }
+
+ weap = cycle_weapon(ent);
+ if (weap)
+ {
+ if (ent->client->pers.inventory[ITEM_INDEX(weap)] <= 0)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", weap->pickup_name);
+ }
+ else
+ {
+ weap->use(ent, weap);
+ }
+ }
+}
+
+static gitem_t *
+preferred_weapon(edict_t *ent)
+{
+ gclient_t *cl;
+ gitem_t *noammo_fallback;
+ gitem_t *noweap_fallback;
+ gitem_t *weap;
+ gitem_t *ammo;
+ int i;
+ int num_weaps;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ cl = ent->client;
+
+ if (!cl)
+ {
+ return NULL;
+ }
+
+ num_weaps = gi.argc();
+ noammo_fallback = NULL;
+ noweap_fallback = NULL;
+
+ /* find the first eligible weapon in the list we can switch to */
+ for (i = 1; i < num_weaps; i++)
+ {
+ weap = FindItemByClassname(gi.argv(i));
+
+ if (weap && (weap->flags & IT_WEAPON) && weap->use)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(weap)] > 0)
+ {
+ if (weap->ammo)
+ {
+ ammo = FindItem(weap->ammo);
+ if (ammo)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(ammo)] >= get_ammo_usage(weap))
+ {
+ return weap;
+ }
+
+ if (!noammo_fallback)
+ {
+ noammo_fallback = weap;
+ }
+ }
+ }
+ else
+ {
+ return weap;
+ }
+ }
+ else if (!noweap_fallback)
+ {
+ noweap_fallback = weap;
+ }
+ }
+ }
+
+ /* if no weapon was found, the fallbacks will be used for
+ printing the appropriate error message to the console
+ */
+
+ if (noammo_fallback)
+ {
+ return noammo_fallback;
+ }
+
+ return noweap_fallback;
+}
+
+void
+Cmd_PrefWeap_f(edict_t *ent)
+{
+ gitem_t *weap;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (gi.argc() <= 1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: prefweap classname1 classname2 .. classnameN\n");
+ return;
+ }
+
+ weap = preferred_weapon(ent);
+ if (weap)
+ {
+ if (ent->client->pers.inventory[ITEM_INDEX(weap)] <= 0)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", weap->pickup_name);
+ }
+ else
+ {
+ weap->use(ent, weap);
+ }
+ }
+}
+
+void
+ClientCommand(edict_t *ent)
+{
+ char *cmd;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client)
+ {
+ return; /* not fully in game yet */
+ }
+
+ cmd = gi.argv(0);
+
+ if (Q_stricmp(cmd, "players") == 0)
+ {
+ Cmd_Players_f(ent);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "say") == 0)
+ {
+ Cmd_Say_f(ent, false, false);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "say_team") == 0)
+ {
+ Cmd_Say_f(ent, true, false);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "score") == 0)
+ {
+ Cmd_Score_f(ent);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "help") == 0)
+ {
+ Cmd_Help_f(ent);
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return;
+ }
+
+ if (Q_stricmp(cmd, "use") == 0)
+ {
+ Cmd_Use_f(ent);
+ }
+ else if (Q_stricmp(cmd, "drop") == 0)
+ {
+ Cmd_Drop_f(ent);
+ }
+ else if (Q_stricmp(cmd, "give") == 0)
+ {
+ Cmd_Give_f(ent);
+ }
+ else if (Q_stricmp(cmd, "god") == 0)
+ {
+ Cmd_God_f(ent);
+ }
+ else if (Q_stricmp(cmd, "notarget") == 0)
+ {
+ Cmd_Notarget_f(ent);
+ }
+ else if (Q_stricmp(cmd, "noclip") == 0)
+ {
+ Cmd_Noclip_f(ent);
+ }
+ else if (Q_stricmp(cmd, "inven") == 0)
+ {
+ Cmd_Inven_f(ent);
+ }
+ else if (Q_stricmp(cmd, "invnext") == 0)
+ {
+ SelectNextItem(ent, -1);
+ }
+ else if (Q_stricmp(cmd, "invprev") == 0)
+ {
+ SelectPrevItem(ent, -1);
+ }
+ else if (Q_stricmp(cmd, "invnextw") == 0)
+ {
+ SelectNextItem(ent, IT_WEAPON);
+ }
+ else if (Q_stricmp(cmd, "invprevw") == 0)
+ {
+ SelectPrevItem(ent, IT_WEAPON);
+ }
+ else if (Q_stricmp(cmd, "invnextp") == 0)
+ {
+ SelectNextItem(ent, IT_POWERUP);
+ }
+ else if (Q_stricmp(cmd, "invprevp") == 0)
+ {
+ SelectPrevItem(ent, IT_POWERUP);
+ }
+ else if (Q_stricmp(cmd, "invuse") == 0)
+ {
+ Cmd_InvUse_f(ent);
+ }
+ else if (Q_stricmp(cmd, "invdrop") == 0)
+ {
+ Cmd_InvDrop_f(ent);
+ }
+ else if (Q_stricmp(cmd, "weapprev") == 0)
+ {
+ Cmd_WeapPrev_f(ent);
+ }
+ else if (Q_stricmp(cmd, "weapnext") == 0)
+ {
+ Cmd_WeapNext_f(ent);
+ }
+ else if (Q_stricmp(cmd, "weaplast") == 0)
+ {
+ Cmd_WeapLast_f(ent);
+ }
+ else if (Q_stricmp(cmd, "kill") == 0)
+ {
+ Cmd_Kill_f(ent);
+ }
+ else if (Q_stricmp(cmd, "putaway") == 0)
+ {
+ Cmd_PutAway_f(ent);
+ }
+ else if (Q_stricmp(cmd, "wave") == 0)
+ {
+ Cmd_Wave_f(ent);
+ }
+ else if (Q_stricmp(cmd, "playerlist") == 0)
+ {
+ Cmd_PlayerList_f(ent);
+ }
+ else if (Q_stricmp(cmd, "entcount") == 0)
+ {
+ Cmd_Ent_Count_f(ent);
+ }
+ else if (Q_stricmp(cmd, "disguise") == 0)
+ {
+ ent->flags |= FL_DISGUISED;
+ }
+ else if (Q_stricmp(cmd, "teleport") == 0)
+ {
+ Cmd_Teleport_f(ent);
+ }
+ else if (Q_stricmp(cmd, "listentities") == 0)
+ {
+ Cmd_ListEntities_f(ent);
+ }
+ else if (Q_stricmp(cmd, "cycleweap") == 0)
+ {
+ Cmd_CycleWeap_f(ent);
+ }
+ else if (Q_stricmp(cmd, "prefweap") == 0)
+ {
+ Cmd_PrefWeap_f(ent);
+ }
+ else /* anything that doesn't match a command will be a chat */
+ {
+ Cmd_Say_f(ent, false, true);
+ }
+}
+
diff --git a/rogue/src/g_combat.c b/rogue/src/g_combat.c
new file mode 100644
index 0000000..12a2e32
--- /dev/null
+++ b/rogue/src/g_combat.c
@@ -0,0 +1,1158 @@
+/*
+ * =======================================================================
+ *
+ * Combat code like damage, death and so on.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+void M_SetEffects(edict_t *self);
+
+/*
+ * clean up heal targets for medic
+ */
+void
+cleanupHealTarget(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->monsterinfo.healer = NULL;
+ ent->takedamage = DAMAGE_YES;
+ ent->monsterinfo.aiflags &= ~AI_RESURRECTING;
+ M_SetEffects(ent);
+}
+
+/*
+ * Returns true if the inflictor can directly damage the
+ * target. Used for explosions and melee attacks.
+ */
+qboolean
+CanDamage(edict_t *targ, edict_t *inflictor)
+{
+ vec3_t dest;
+ trace_t trace;
+
+ if (!targ || !inflictor)
+ {
+ return false;
+ }
+
+ /* bmodels need special checking because their origin is 0,0,0 */
+ if (targ->movetype == MOVETYPE_PUSH)
+ {
+ VectorAdd(targ->absmin, targ->absmax, dest);
+ VectorScale(dest, 0.5, dest);
+ trace = gi.trace(inflictor->s.origin, vec3_origin,
+ vec3_origin, dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ if (trace.ent == targ)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ trace = gi.trace(inflictor->s.origin, vec3_origin,
+ vec3_origin, targ->s.origin, inflictor,
+ MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] += 15.0;
+ dest[1] += 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin,
+ vec3_origin, dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] += 15.0;
+ dest[1] -= 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] -= 15.0;
+ dest[1] += 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] -= 15.0;
+ dest[1] -= 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point)
+{
+ if (!targ || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ /* Reset AI flag for being ducked. This fixes a corner case
+ were the monster is ressurected by a medic and get's stuck
+ in the next frame for mmove_t not matching the AI state. */
+ if (targ->monsterinfo.aiflags & AI_DUCKED)
+ {
+ targ->monsterinfo.aiflags &= ~AI_DUCKED;
+ }
+
+ if (targ->monsterinfo.aiflags & AI_MEDIC)
+ {
+ if (targ->enemy)
+ {
+ cleanupHealTarget(targ->enemy);
+ }
+
+ /* clean up self */
+ targ->monsterinfo.aiflags &= ~AI_MEDIC;
+ targ->enemy = attacker;
+ }
+ else
+ {
+ targ->enemy = attacker;
+ }
+
+ if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+ {
+ /* free up slot for spawned monster if it's spawned */
+ if (targ->monsterinfo.aiflags & AI_SPAWNED_CARRIER)
+ {
+ if (targ->monsterinfo.commander &&
+ targ->monsterinfo.commander->inuse &&
+ !strcmp(targ->monsterinfo.commander->classname, "monster_carrier"))
+ {
+ targ->monsterinfo.commander->monsterinfo.monster_slots++;
+ }
+ }
+
+ if (targ->monsterinfo.aiflags & AI_SPAWNED_MEDIC_C)
+ {
+ if (targ->monsterinfo.commander)
+ {
+ if (targ->monsterinfo.commander->inuse &&
+ !strcmp(targ->monsterinfo.commander->classname, "monster_medic_commander"))
+ {
+ targ->monsterinfo.commander->monsterinfo.monster_slots++;
+ }
+ }
+ }
+
+ if (targ->monsterinfo.aiflags & AI_SPAWNED_WIDOW)
+ {
+ /* need to check this because we can
+ have variable numbers of coop players */
+ if (targ->monsterinfo.commander &&
+ targ->monsterinfo.commander->inuse &&
+ !strncmp(targ->monsterinfo.commander->classname, "monster_widow", 13))
+ {
+ if (targ->monsterinfo.commander->monsterinfo.monster_used > 0)
+ {
+ targ->monsterinfo.commander->monsterinfo.monster_used--;
+ }
+ }
+ }
+
+ if ((!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) &&
+ (!(targ->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
+ {
+ level.killed_monsters++;
+
+ if (coop->value && attacker->client)
+ {
+ attacker->client->resp.score++;
+ }
+ }
+ }
+
+ if ((targ->movetype == MOVETYPE_PUSH) ||
+ (targ->movetype == MOVETYPE_STOP) || (targ->movetype == MOVETYPE_NONE))
+ {
+ targ->die(targ, inflictor, attacker, damage, point);
+ return;
+ }
+
+ if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+ {
+ targ->touch = NULL;
+ monster_death_use(targ);
+ }
+
+ targ->die(targ, inflictor, attacker, damage, point);
+}
+
+void
+SpawnDamage(int type, vec3_t origin, vec3_t normal, int damage)
+{
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(type);
+ gi.WritePosition(origin);
+ gi.WriteDir(normal);
+ gi.multicast(origin, MULTICAST_PVS);
+}
+
+/*
+ * ============
+ * T_Damage
+ *
+ * targ entity that is being damaged
+ * inflictor entity that is causing the damage
+ * attacker entity that caused the inflictor to damage targ
+ * example: targ=monster, inflictor=rocket, attacker=player
+ *
+ * dir direction of the attack
+ * point point at which the damage is being inflicted
+ * normal normal vector from that point
+ * damage amount of damage being inflicted
+ * knockback force to be applied against targ as a result of the damage
+ *
+ * dflags these flags are used to control how T_Damage works
+ * DAMAGE_RADIUS damage was indirect (from a nearby explosion)
+ * DAMAGE_NO_ARMOR armor does not protect from this damage
+ * DAMAGE_ENERGY damage is from an energy based weapon
+ * DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
+ * DAMAGE_BULLET damage is from a bullet (used for ricochets)
+ * DAMAGE_NO_PROTECTION kills godmode, armor, everything
+ * ============
+ */
+int
+CheckPowerArmor(edict_t *ent, vec3_t point, vec3_t normal,
+ int damage, int dflags)
+{
+ gclient_t *client;
+ int save;
+ int power_armor_type;
+ int index = 0;
+ int damagePerCell;
+ int pa_te_type;
+ int power = 0;
+ int power_used;
+
+ if (!ent)
+ {
+ return 0;
+ }
+
+ if (!damage)
+ {
+ return 0;
+ }
+
+ client = ent->client;
+
+ if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_POWER_ARMOR))
+ {
+ return 0;
+ }
+
+ if (client)
+ {
+ power_armor_type = PowerArmorType(ent);
+
+ if (power_armor_type != POWER_ARMOR_NONE)
+ {
+ index = ITEM_INDEX(FindItem("Cells"));
+ power = client->pers.inventory[index];
+ }
+ }
+ else if (ent->svflags & SVF_MONSTER)
+ {
+ power_armor_type = ent->monsterinfo.power_armor_type;
+ power = ent->monsterinfo.power_armor_power;
+ }
+ else
+ {
+ return 0;
+ }
+
+ if (power_armor_type == POWER_ARMOR_NONE)
+ {
+ return 0;
+ }
+
+ if (!power)
+ {
+ return 0;
+ }
+
+ if (power_armor_type == POWER_ARMOR_SCREEN)
+ {
+ vec3_t vec;
+ float dot;
+ vec3_t forward;
+
+ /* only works if damage point is in front */
+ AngleVectors(ent->s.angles, forward, NULL, NULL);
+ VectorSubtract(point, ent->s.origin, vec);
+ VectorNormalize(vec);
+ dot = DotProduct(vec, forward);
+
+ if (dot <= 0.3)
+ {
+ return 0;
+ }
+
+ damagePerCell = 1;
+ pa_te_type = TE_SCREEN_SPARKS;
+ damage = damage / 3;
+ }
+ else
+ {
+ damagePerCell = 2;
+ pa_te_type = TE_SHIELD_SPARKS;
+ damage = (2 * damage) / 3;
+ }
+
+ /* etf rifle */
+ if (dflags & DAMAGE_NO_REG_ARMOR)
+ {
+ save = (power * damagePerCell) / 2;
+ }
+ else
+ {
+ save = power * damagePerCell;
+ }
+
+ if (!save)
+ {
+ return 0;
+ }
+
+ if (save > damage)
+ {
+ save = damage;
+ }
+
+ SpawnDamage(pa_te_type, point, normal, save);
+ ent->powerarmor_time = level.time + 0.2;
+
+ if (dflags & DAMAGE_NO_REG_ARMOR)
+ {
+ power_used = (save / damagePerCell) * 2;
+ }
+ else
+ {
+ power_used = save / damagePerCell;
+ }
+
+ if (client)
+ {
+ client->pers.inventory[index] -= power_used;
+ }
+ else
+ {
+ ent->monsterinfo.power_armor_power -= power_used;
+ }
+
+ return save;
+}
+
+int
+CheckArmor(edict_t *ent, vec3_t point, vec3_t normal,
+ int damage, int te_sparks, int dflags)
+{
+ gclient_t *client;
+ int save;
+ int index;
+ gitem_t *armor;
+
+ if (!ent)
+ {
+ return 0;
+ }
+
+ if (!damage)
+ {
+ return 0;
+ }
+
+ client = ent->client;
+
+ if (!client)
+ {
+ return 0;
+ }
+
+ if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_ARMOR))
+ {
+ return 0;
+ }
+
+ index = ArmorIndex(ent);
+
+ if (!index)
+ {
+ return 0;
+ }
+
+ armor = GetItemByIndex(index);
+
+ if (dflags & DAMAGE_ENERGY)
+ {
+ save = ceil(((gitem_armor_t *)armor->info)->energy_protection * damage);
+ }
+ else
+ {
+ save = ceil(((gitem_armor_t *)armor->info)->normal_protection * damage);
+ }
+
+ if (save >= client->pers.inventory[index])
+ {
+ save = client->pers.inventory[index];
+ }
+
+ if (!save)
+ {
+ return 0;
+ }
+
+ client->pers.inventory[index] -= save;
+ SpawnDamage(te_sparks, point, normal, save);
+
+ return save;
+}
+
+void
+M_ReactToDamage(edict_t *targ, edict_t *attacker, edict_t *inflictor)
+{
+ qboolean new_tesla;
+
+ if (!targ || !attacker || !inflictor)
+ {
+ return;
+ }
+
+ if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
+ {
+ return;
+ }
+
+ /* logic for tesla - if you are hit by a tesla,
+ and can't see who you should be mad at (attacker)
+ attack the tesla also, target the tesla if it's
+ a "new" tesla */
+ if (!strcmp(inflictor->classname, "tesla"))
+ {
+ new_tesla = MarkTeslaArea(targ, inflictor);
+
+ if (new_tesla || !targ->enemy)
+ {
+ TargetTesla(targ, inflictor);
+ }
+
+ return;
+ }
+
+ if ((attacker == targ) || (attacker == targ->enemy))
+ {
+ return;
+ }
+
+ /* if we are a good guy monster and
+ our attacker is a player or another
+ good guy, do not get mad at them */
+ if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
+ {
+ if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ return;
+ }
+ }
+
+ /* if we're currently mad at something
+ a target_anger made us mad at, ignore
+ damage */
+ if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER)
+ {
+ float percentHealth;
+
+ /* make sure whatever we were pissed at is still around. */
+ if (targ->enemy->inuse)
+ {
+ percentHealth = (float)(targ->health) / (float)(targ->max_health);
+
+ if (percentHealth > 0.33)
+ {
+ return;
+ }
+ }
+
+ /* remove the target anger flag */
+ targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
+ }
+
+ /* if we're healing someone, do like above and try to stay with them */
+ if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC))
+ {
+ float percentHealth;
+
+ percentHealth = (float)(targ->health) / (float)(targ->max_health);
+
+ /* ignore it some of the time */
+ if (targ->enemy->inuse && (percentHealth > 0.25))
+ {
+ return;
+ }
+
+ /* remove the medic flag */
+ targ->monsterinfo.aiflags &= ~AI_MEDIC;
+ cleanupHealTarget(targ->enemy);
+ }
+
+ /* if attacker is a client, get mad at them
+ because he's good and we're not */
+ if (attacker->client)
+ {
+ targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+ /* this can only happen in coop (both new and
+ old enemies are clients) only switch if can't
+ see the current enemy */
+ if (targ->enemy && targ->enemy->client)
+ {
+ if (visible(targ, targ->enemy))
+ {
+ targ->oldenemy = attacker;
+ return;
+ }
+
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+
+ return;
+ }
+
+ if (((targ->flags & (FL_FLY | FL_SWIM)) ==
+ (attacker->flags & (FL_FLY | FL_SWIM))) &&
+ (strcmp(targ->classname, attacker->classname) != 0) &&
+ !(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) &&
+ !(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS))
+ {
+ if (targ->enemy && targ->enemy->client)
+ {
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+ }
+ /* if they *meant* to shoot us, then shoot back */
+ else if (attacker->enemy == targ)
+ {
+ if (targ->enemy && targ->enemy->client)
+ {
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+ }
+ /* otherwise get mad at whoever they are mad at (help our buddy) unless it is us! */
+ else if (attacker->enemy)
+ {
+ if (targ->enemy && targ->enemy->client)
+ {
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker->enemy;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+ }
+}
+
+qboolean
+CheckTeamDamage(edict_t *targ, edict_t *attacker)
+{
+ return false;
+}
+
+static void
+apply_knockback(edict_t *targ, vec3_t dir, float knockback, float scale)
+{
+ vec3_t kvel;
+ float mass;
+
+ if (!knockback)
+ {
+ return;
+ }
+
+ mass = (targ->mass < 50) ? 50.0f : (float)targ->mass;
+
+ VectorNormalize2(dir, kvel);
+ VectorScale(kvel, scale * (knockback / mass), kvel);
+ VectorAdd(targ->velocity, kvel, targ->velocity);
+}
+
+void
+T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir,
+ vec3_t point, vec3_t normal, int damage, int knockback, int dflags,
+ int mod)
+{
+ gclient_t *client;
+ int take;
+ int save;
+ int asave;
+ int psave;
+ int te_sparks;
+ int sphere_notified;
+
+ if (!targ || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ if (!targ->takedamage)
+ {
+ return;
+ }
+
+ sphere_notified = false;
+
+ /* friendly fire avoidance. If enabled you can't
+ hurt teammates (but you can hurt yourself)
+ knockback still occurs */
+ if ((targ != attacker) && ((deathmatch->value &&
+ ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) ||
+ coop->value))
+ {
+ if (OnSameTeam(targ, attacker))
+ {
+ /* nukes kill everyone */
+ if (((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) &&
+ (mod != MOD_NUKE))
+ {
+ damage = 0;
+ }
+ else
+ {
+ mod |= MOD_FRIENDLY_FIRE;
+ }
+ }
+ }
+
+ meansOfDeath = mod;
+
+ /* allow the deathmatch game to change values */
+ if (deathmatch->value && gamerules && gamerules->value)
+ {
+ if (DMGame.ChangeDamage)
+ {
+ damage = DMGame.ChangeDamage(targ, attacker, damage, mod);
+ }
+
+ if (DMGame.ChangeKnockback)
+ {
+ knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod);
+ }
+
+ if (!damage)
+ {
+ return;
+ }
+ }
+
+ /* easy mode takes half damage */
+ if ((skill->value == SKILL_EASY) && (deathmatch->value == 0) && targ->client)
+ {
+ damage *= 0.5;
+
+ if (!damage)
+ {
+ damage = 1;
+ }
+ }
+
+ client = targ->client;
+
+ /* defender sphere takes half damage */
+ if ((client) && (client->owned_sphere) &&
+ (client->owned_sphere->spawnflags == 1))
+ {
+ damage *= 0.5;
+
+ if (!damage)
+ {
+ damage = 1;
+ }
+ }
+
+ if (dflags & DAMAGE_BULLET)
+ {
+ te_sparks = TE_BULLET_SPARKS;
+ }
+ else
+ {
+ te_sparks = TE_SPARKS;
+ }
+
+ /* bonus damage for suprising a monster */
+ if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) &&
+ (attacker->client) && (!targ->enemy) && (targ->health > 0))
+ {
+ damage *= 2;
+ }
+
+ if (targ->flags & FL_NO_KNOCKBACK)
+ {
+ knockback = 0;
+ }
+
+ /* figure momentum add */
+ if (!(dflags & DAMAGE_NO_KNOCKBACK) &&
+ (targ->movetype != MOVETYPE_NONE) &&
+ (targ->movetype != MOVETYPE_BOUNCE) &&
+ (targ->movetype != MOVETYPE_PUSH) &&
+ (targ->movetype != MOVETYPE_STOP))
+ {
+ apply_knockback (targ, dir, knockback,
+ ((client && attacker == targ) ? 1600.0f : 500.0f));
+ }
+
+ take = damage;
+ save = 0;
+
+ /* check for godmode */
+ if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION))
+ {
+ take = 0;
+ save = damage;
+ SpawnDamage(te_sparks, point, normal, save);
+ }
+
+ /* check for invincibility */
+ if ((client &&
+ (client->invincible_framenum > level.framenum)) &&
+ !(dflags & DAMAGE_NO_PROTECTION))
+ {
+ if (targ->pain_debounce_time < level.time)
+ {
+ gi.sound(targ, CHAN_ITEM, gi.soundindex( "items/protect4.wav"), 1, ATTN_NORM, 0);
+ targ->pain_debounce_time = level.time + 2;
+ }
+
+ take = 0;
+ save = damage;
+ }
+
+ /* check for monster invincibility */
+ if (((targ->svflags & SVF_MONSTER) &&
+ (targ->monsterinfo.invincible_framenum > level.framenum)) &&
+ !(dflags & DAMAGE_NO_PROTECTION))
+ {
+ if (targ->pain_debounce_time < level.time)
+ {
+ gi.sound(targ, CHAN_ITEM, gi.soundindex( "items/protect4.wav"), 1, ATTN_NORM, 0);
+ targ->pain_debounce_time = level.time + 2;
+ }
+
+ take = 0;
+ save = damage;
+ }
+
+ psave = CheckPowerArmor(targ, point, normal, take, dflags);
+ take -= psave;
+
+ asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
+ take -= asave;
+
+ /* treat cheat/powerup savings the same as armor */
+ asave += save;
+
+ /* this option will do damage both to the armor
+ and person. originally for DPU rounds */
+ if (dflags & DAMAGE_DESTROY_ARMOR)
+ {
+ if (!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) &&
+ !(client && (client->invincible_framenum > level.framenum)))
+ {
+ take = damage;
+ }
+ }
+
+ /* do the damage */
+ if (take)
+ {
+ /* need more blood for chainfist. */
+ if (targ->flags & FL_MECHANICAL)
+ {
+ SpawnDamage(TE_ELECTRIC_SPARKS, point, normal, take);
+ }
+ else if ((targ->svflags & SVF_MONSTER) || (client))
+ {
+ if (mod == MOD_CHAINFIST)
+ {
+ SpawnDamage(TE_MOREBLOOD, point, normal, 255);
+ }
+ else
+ {
+ SpawnDamage(TE_BLOOD, point, normal, take);
+ }
+ }
+ else
+ {
+ SpawnDamage(te_sparks, point, normal, take);
+ }
+
+ targ->health = targ->health - take;
+
+ /* spheres need to know who to shoot at */
+ if (client && client->owned_sphere)
+ {
+ sphere_notified = true;
+
+ if (client->owned_sphere->pain)
+ {
+ client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0);
+ }
+ }
+
+ if (targ->health <= 0)
+ {
+ if ((targ->svflags & SVF_MONSTER) || (client))
+ {
+ targ->flags |= FL_NO_KNOCKBACK;
+ }
+
+ Killed(targ, inflictor, attacker, take, point);
+ return;
+ }
+ }
+
+ /* spheres need to know who to shoot at */
+ if (!sphere_notified)
+ {
+ if (client && client->owned_sphere)
+ {
+ if (client->owned_sphere->pain)
+ {
+ client->owned_sphere->pain(client->owned_sphere, attacker, 0,
+ 0);
+ }
+ }
+ }
+
+ if (targ->svflags & SVF_MONSTER)
+ {
+ M_ReactToDamage(targ, attacker, inflictor);
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
+ {
+ targ->pain(targ, attacker, knockback, take);
+
+ /* nightmare mode monsters don't go into pain frames often */
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ targ->pain_debounce_time = level.time + 5;
+ }
+ }
+ }
+ else if (client)
+ {
+ if (!(targ->flags & FL_GODMODE) && (take))
+ {
+ targ->pain(targ, attacker, knockback, take);
+ }
+ }
+ else if (take)
+ {
+ if (targ->pain)
+ {
+ targ->pain(targ, attacker, knockback, take);
+ }
+ }
+
+ /* add to the damage inflicted on a player this frame
+ the total will be turned into screen blends and view angle kicks
+ at the end of the frame */
+ if (client)
+ {
+ client->damage_parmor += psave;
+ client->damage_armor += asave;
+ client->damage_blood += take;
+ client->damage_knockback += knockback;
+ VectorCopy(point, client->damage_from);
+ }
+}
+
+void
+T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage,
+ edict_t *ignore, float radius, int mod)
+{
+ float points;
+ edict_t *ent = NULL;
+ vec3_t v;
+ vec3_t dir;
+
+ if (!inflictor || !attacker)
+ {
+ return;
+ }
+
+ while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
+ {
+ if (ent == ignore)
+ {
+ continue;
+ }
+
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ VectorAdd(ent->mins, ent->maxs, v);
+ VectorMA(ent->s.origin, 0.5, v, v);
+ VectorSubtract(inflictor->s.origin, v, v);
+ points = damage - 0.5 * VectorLength(v);
+
+ if (ent == attacker)
+ {
+ points = points * 0.5;
+ }
+
+ if (points > 0)
+ {
+ if (CanDamage(ent, inflictor))
+ {
+ VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
+ T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin,
+ (int)points, (int)points, DAMAGE_RADIUS, mod);
+ }
+ }
+ }
+}
+
+void
+T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage,
+ edict_t *ignore, float radius, int mod)
+{
+ float points;
+ edict_t *ent = NULL;
+ vec3_t v;
+ vec3_t dir;
+ float len;
+ float killzone, killzone2;
+ trace_t tr;
+ float dist;
+
+ killzone = radius;
+ killzone2 = radius * 2.0;
+
+ if (!inflictor || !attacker || !ignore)
+ {
+ return;
+ }
+
+ while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != NULL)
+ {
+ /* ignore nobody */
+ if (ent == ignore)
+ {
+ continue;
+ }
+
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!(ent->client || (ent->svflags & SVF_MONSTER) ||
+ (ent->svflags & SVF_DAMAGEABLE)))
+ {
+ continue;
+ }
+
+ VectorAdd(ent->mins, ent->maxs, v);
+ VectorMA(ent->s.origin, 0.5, v, v);
+ VectorSubtract(inflictor->s.origin, v, v);
+ len = VectorLength(v);
+
+ if (len <= killzone)
+ {
+ if (ent->client)
+ {
+ ent->flags |= FL_NOGIB;
+ }
+
+ points = 10000;
+ }
+ else if (len <= killzone2)
+ {
+ points = (damage / killzone) * (killzone2 - len);
+ }
+ else
+ {
+ points = 0;
+ }
+
+ if (points > 0)
+ {
+ if (ent->client)
+ {
+ ent->client->nuke_framenum = level.framenum + 20;
+ }
+
+ VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
+ T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
+ vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
+ mod);
+ }
+ }
+
+ /* skip the worldspawn */
+ ent = g_edicts + 1;
+
+ /* cycle through players */
+ while (ent)
+ {
+ if ((ent->client) &&
+ (ent->client->nuke_framenum != level.framenum + 20) && (ent->inuse))
+ {
+ tr = gi.trace(inflictor->s.origin, NULL, NULL, ent->s.origin,
+ inflictor, MASK_SOLID);
+
+ if (tr.fraction == 1.0)
+ {
+ ent->client->nuke_framenum = level.framenum + 20;
+ }
+ else
+ {
+ dist = realrange(ent, inflictor);
+
+ if (dist < 2048)
+ {
+ ent->client->nuke_framenum = max(ent->client->nuke_framenum,
+ level.framenum + 15);
+ }
+ else
+ {
+ ent->client->nuke_framenum = max(ent->client->nuke_framenum,
+ level.framenum + 10);
+ }
+ }
+
+ ent++;
+ }
+ else
+ {
+ ent = NULL;
+ }
+ }
+}
+
+/*
+ * Like T_RadiusDamage, but ignores
+ * anything with classname=ignoreClass
+ */
+void
+T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage,
+ char *ignoreClass, float radius, int mod)
+{
+ float points;
+ edict_t *ent = NULL;
+ vec3_t v;
+ vec3_t dir;
+
+ if (!inflictor || !attacker || !ignoreClass)
+ {
+ return;
+ }
+
+ while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
+ {
+ if (ent->classname && !strcmp(ent->classname, ignoreClass))
+ {
+ continue;
+ }
+
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ VectorAdd(ent->mins, ent->maxs, v);
+ VectorMA(ent->s.origin, 0.5, v, v);
+ VectorSubtract(inflictor->s.origin, v, v);
+ points = damage - 0.5 * VectorLength(v);
+
+ if (ent == attacker)
+ {
+ points = points * 0.5;
+ }
+
+ if (points > 0)
+ {
+ if (CanDamage(ent, inflictor))
+ {
+ VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
+ T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
+ vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
+ mod);
+ }
+ }
+ }
+}
diff --git a/rogue/src/g_func.c b/rogue/src/g_func.c
new file mode 100644
index 0000000..6d2f32c
--- /dev/null
+++ b/rogue/src/g_func.c
@@ -0,0 +1,3976 @@
+/*
+ * =======================================================================
+ *
+ * Level functions. Platforms, buttons, dooors and so on.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define PLAT_LOW_TRIGGER 1
+#define PLAT2_TOGGLE 2
+#define PLAT2_TOP 4
+#define PLAT2_TRIGGER_TOP 8
+#define PLAT2_TRIGGER_BOTTOM 16
+#define PLAT2_BOX_LIFT 32
+
+#define STATE_TOP 0
+#define STATE_BOTTOM 1
+#define STATE_UP 2
+#define STATE_DOWN 3
+
+#define DOOR_START_OPEN 1
+#define DOOR_REVERSE 2
+#define DOOR_CRUSHER 4
+#define DOOR_NOMONSTER 8
+#define DOOR_TOGGLE 32
+#define DOOR_X_AXIS 64
+#define DOOR_Y_AXIS 128
+#define DOOR_INACTIVE 8192
+
+#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2)
+
+#define PLAT2_CALLED 1
+#define PLAT2_MOVING 2
+#define PLAT2_WAITING 4
+
+#define TRAIN_START_ON 1
+#define TRAIN_TOGGLE 2
+#define TRAIN_BLOCK_STOPS 4
+
+#define SECRET_ALWAYS_SHOOT 1
+#define SECRET_1ST_LEFT 2
+#define SECRET_1ST_DOWN 4
+
+void door_secret_move1(edict_t *self);
+void door_secret_move2(edict_t *self);
+void door_secret_move3(edict_t *self);
+void door_secret_move4(edict_t *self);
+void door_secret_move5(edict_t *self);
+void door_secret_move6(edict_t *self);
+void door_secret_done(edict_t *self);
+
+void train_next(edict_t *self);
+void door_go_down(edict_t *self);
+void plat2_go_down(edict_t *ent);
+void plat2_go_up(edict_t *ent);
+void plat2_spawn_danger_area(edict_t *ent);
+void plat2_kill_danger_area(edict_t *ent);
+void Think_AccelMove(edict_t *ent);
+void plat_go_down(edict_t *ent);
+
+/*
+ * =========================================================
+ *
+ * PLATS
+ *
+ * movement options:
+ *
+ * linear
+ * smooth start, hard stop
+ * smooth start, smooth stop
+ *
+ * start
+ * end
+ * acceleration
+ * speed
+ * deceleration
+ * begin sound
+ * end sound
+ * target fired when reaching end
+ * wait at end
+ *
+ * object characteristics that use move segments
+ * ---------------------------------------------
+ * movetype_push, or movetype_stop
+ * action when touched
+ * action when blocked
+ * action when used
+ * disabled?
+ * auto trigger spawning
+ *
+ *
+ * =========================================================
+ */
+
+/* Support routines for movement (changes in origin using velocity) */
+
+void
+Move_Done(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->velocity);
+ ent->moveinfo.endfunc(ent);
+}
+
+void
+Move_Final(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->moveinfo.remaining_distance == 0)
+ {
+ Move_Done(ent);
+ return;
+ }
+
+ VectorScale(ent->moveinfo.dir,
+ ent->moveinfo.remaining_distance / FRAMETIME,
+ ent->velocity);
+
+ ent->think = Move_Done;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+Move_Begin(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ float frames;
+
+ if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
+ {
+ Move_Final(ent);
+ return;
+ }
+
+ VectorScale(ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
+ frames = floor( (ent->moveinfo.remaining_distance /
+ ent->moveinfo.speed) / FRAMETIME);
+ ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
+ ent->nextthink = level.time + (frames * FRAMETIME);
+ ent->think = Move_Final;
+}
+
+void
+Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *))
+{
+ if (!ent || !func)
+ {
+ return;
+ }
+
+ VectorClear(ent->velocity);
+ VectorSubtract(dest, ent->s.origin, ent->moveinfo.dir);
+ ent->moveinfo.remaining_distance = VectorNormalize(ent->moveinfo.dir);
+ ent->moveinfo.endfunc = func;
+
+ if ((ent->moveinfo.speed == ent->moveinfo.accel) &&
+ (ent->moveinfo.speed == ent->moveinfo.decel))
+ {
+ if (level.current_entity ==
+ ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+ {
+ Move_Begin(ent);
+ }
+ else
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = Move_Begin;
+ }
+ }
+ else
+ {
+ /* accelerative */
+ ent->moveinfo.current_speed = 0;
+ ent->think = Think_AccelMove;
+ ent->nextthink = level.time + FRAMETIME;
+ }
+}
+
+/* Support routines for angular movement
+ (changes in angle using avelocity) */
+void
+AngleMove_Done(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->avelocity);
+ ent->moveinfo.endfunc(ent);
+}
+
+void
+AngleMove_Final(edict_t *ent)
+{
+ vec3_t move;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->moveinfo.state == STATE_UP)
+ {
+ VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, move);
+ }
+ else
+ {
+ VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, move);
+ }
+
+ if (VectorCompare(move, vec3_origin))
+ {
+ AngleMove_Done(ent);
+ return;
+ }
+
+ VectorScale(move, 1.0 / FRAMETIME, ent->avelocity);
+
+ ent->think = AngleMove_Done;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+AngleMove_Begin(edict_t *ent)
+{
+ vec3_t destdelta;
+ float len;
+ float traveltime;
+ float frames;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* accelerate as needed */
+ if (ent->moveinfo.speed < ent->speed)
+ {
+ ent->moveinfo.speed += ent->accel;
+
+ if (ent->moveinfo.speed > ent->speed)
+ {
+ ent->moveinfo.speed = ent->speed;
+ }
+ }
+
+ /* set destdelta to the vector needed to move */
+ if (ent->moveinfo.state == STATE_UP)
+ {
+ VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, destdelta);
+ }
+ else
+ {
+ VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, destdelta);
+ }
+
+ /* calculate length of vector */
+ len = VectorLength(destdelta);
+
+ /* divide by speed to get time to reach dest */
+ traveltime = len / ent->moveinfo.speed;
+
+ if (traveltime < FRAMETIME)
+ {
+ AngleMove_Final(ent);
+ return;
+ }
+
+ frames = floor(traveltime / FRAMETIME);
+
+ /* scale the destdelta vector by the time spent traveling to get velocity */
+ VectorScale(destdelta, 1.0 / traveltime, ent->avelocity);
+
+ /* if we're done accelerating, act as a normal rotation */
+ if (ent->moveinfo.speed >= ent->speed)
+ {
+ /* set nextthink to trigger a think when dest is reached */
+ ent->nextthink = level.time + frames * FRAMETIME;
+ ent->think = AngleMove_Final;
+ }
+ else
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = AngleMove_Begin;
+ }
+}
+
+void
+AngleMove_Calc(edict_t *ent, void (*func)(edict_t *))
+{
+ if (!ent || !func)
+ {
+ return;
+ }
+
+ VectorClear(ent->avelocity);
+ ent->moveinfo.endfunc = func;
+
+ /* if we're supposed to accelerate, this will
+ tell anglemove_begin to do so */
+ if (ent->accel != ent->speed)
+ {
+ ent->moveinfo.speed = 0;
+ }
+
+ if (level.current_entity ==
+ ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+ {
+ AngleMove_Begin(ent);
+ }
+ else
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = AngleMove_Begin;
+ }
+}
+
+/*
+ * The team has completed a frame of movement, so
+ * change the speed for the next frame
+ */
+
+void
+plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
+{
+ float accel_dist;
+ float decel_dist;
+
+ if (!moveinfo)
+ {
+ return;
+ }
+
+ moveinfo->move_speed = moveinfo->speed;
+
+ if (moveinfo->remaining_distance < moveinfo->accel)
+ {
+ moveinfo->current_speed = moveinfo->remaining_distance;
+ return;
+ }
+
+ accel_dist = AccelerationDistance(moveinfo->speed, moveinfo->accel);
+ decel_dist = AccelerationDistance(moveinfo->speed, moveinfo->decel);
+
+ if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
+ {
+ float f;
+
+ f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
+ moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
+ decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel);
+ }
+
+ moveinfo->decel_distance = decel_dist;
+}
+
+void
+plat_Accelerate(moveinfo_t *moveinfo)
+{
+ if (!moveinfo)
+ {
+ return;
+ }
+
+ /* are we decelerating? */
+ if (moveinfo->remaining_distance <= moveinfo->decel_distance)
+ {
+ if (moveinfo->remaining_distance < moveinfo->decel_distance)
+ {
+ if (moveinfo->next_speed)
+ {
+ moveinfo->current_speed = moveinfo->next_speed;
+ moveinfo->next_speed = 0;
+ return;
+ }
+
+ if (moveinfo->current_speed > moveinfo->decel)
+ {
+ moveinfo->current_speed -= moveinfo->decel;
+ }
+ }
+
+ return;
+ }
+
+ /* are we at full speed and need to start decelerating during this move? */
+ if (moveinfo->current_speed == moveinfo->move_speed)
+ {
+ if ((moveinfo->remaining_distance - moveinfo->current_speed) <
+ moveinfo->decel_distance)
+ {
+ float p1_distance;
+ float p2_distance;
+ float distance;
+
+ p1_distance = moveinfo->remaining_distance -
+ moveinfo->decel_distance;
+ p2_distance = moveinfo->move_speed *
+ (1.0 - (p1_distance / moveinfo->move_speed));
+ distance = p1_distance + p2_distance;
+ moveinfo->current_speed = moveinfo->move_speed;
+ moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel *
+ (p2_distance / distance);
+ return;
+ }
+ }
+
+ /* are we accelerating? */
+ if (moveinfo->current_speed < moveinfo->speed)
+ {
+ float old_speed;
+ float p1_distance;
+ float p1_speed;
+ float p2_distance;
+ float distance;
+
+ old_speed = moveinfo->current_speed;
+
+ /* figure simple acceleration up to move_speed */
+ moveinfo->current_speed += moveinfo->accel;
+
+ if (moveinfo->current_speed > moveinfo->speed)
+ {
+ moveinfo->current_speed = moveinfo->speed;
+ }
+
+ /* are we accelerating throughout this entire move? */
+ if ((moveinfo->remaining_distance - moveinfo->current_speed) >=
+ moveinfo->decel_distance)
+ {
+ return;
+ }
+
+ /* during this move we will accelrate from current_speed to move_speed
+ and cross over the decel_distance; figure the average speed for the
+ entire move */
+ p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+ p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
+ p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
+ distance = p1_distance + p2_distance;
+ moveinfo->current_speed = (p1_speed * (p1_distance /
+ distance)) + (moveinfo->move_speed * (p2_distance / distance));
+ moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel *
+ (p2_distance / distance);
+ return;
+ }
+
+ /* we are at constant velocity (move_speed) */
+ return;
+}
+
+void
+Think_AccelMove(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
+ plat_CalcAcceleratedMove(&ent->moveinfo);
+ plat_Accelerate(&ent->moveinfo);
+
+ /* will the entire move complete on next frame? */
+ if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
+ {
+ Move_Final(ent);
+ return;
+ }
+
+ VectorScale(ent->moveinfo.dir, ent->moveinfo.current_speed * 10,
+ ent->velocity);
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = Think_AccelMove;
+}
+
+void
+plat_hit_top(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_end)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end,
+ 1, ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = 0;
+ }
+
+ ent->moveinfo.state = STATE_TOP;
+
+ ent->think = plat_go_down;
+ ent->nextthink = level.time + 3;
+}
+
+void
+plat_hit_bottom(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_end)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = 0;
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+
+ plat2_kill_danger_area(ent);
+}
+
+void
+plat_go_down(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_start)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = ent->moveinfo.sound_middle;
+ }
+
+ ent->moveinfo.state = STATE_DOWN;
+ Move_Calc(ent, ent->moveinfo.end_origin, plat_hit_bottom);
+}
+
+void
+plat_go_up(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_start)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = ent->moveinfo.sound_middle;
+ }
+
+ ent->moveinfo.state = STATE_UP;
+ Move_Calc(ent, ent->moveinfo.start_origin, plat_hit_top);
+
+ plat2_spawn_danger_area(ent);
+}
+
+void
+plat_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entity without it's origin near the model */
+ VectorMA(other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ /* gib dead things */
+ if (other->health < 1)
+ {
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100, 1, 0, MOD_CRUSH);
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+ if (self->moveinfo.state == STATE_UP)
+ {
+ plat_go_down(self);
+ }
+ else if (self->moveinfo.state == STATE_DOWN)
+ {
+ plat_go_up(self);
+ }
+}
+
+void
+Use_Plat(edict_t *ent, edict_t *other, edict_t *activator /* unused */)
+{
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ /* if a monster is using us, then allow the activity when stopped. */
+ if (other->svflags & SVF_MONSTER)
+ {
+ if (ent->moveinfo.state == STATE_TOP)
+ {
+ plat_go_down(ent);
+ }
+ else if (ent->moveinfo.state == STATE_BOTTOM)
+ {
+ plat_go_up(ent);
+ }
+
+ return;
+ }
+
+ if (ent->think)
+ {
+ return; /* already down */
+ }
+
+ plat_go_down(ent);
+}
+
+void
+wait_and_change_think(edict_t* ent)
+{
+ void (*afterwaitfunc)(edict_t *) = ent->moveinfo.endfunc;
+ ent->moveinfo.endfunc = NULL;
+ afterwaitfunc(ent);
+}
+
+/*
+ * In coop mode, this waits for coop_elevator_delay seconds
+ * before calling afterwaitfunc(ent); otherwise it just calls
+ * afterwaitfunc(ent);
+ */
+static void
+wait_and_change(edict_t* ent, void (*afterwaitfunc)(edict_t *))
+{
+ float waittime = coop_elevator_delay->value;
+ if (coop->value && waittime > 0.0f)
+ {
+ if(ent->nextthink == 0)
+ {
+ ent->moveinfo.endfunc = afterwaitfunc;
+ ent->think = wait_and_change_think;
+ ent->nextthink = level.time + waittime;
+ }
+ }
+ else
+ {
+ afterwaitfunc(ent);
+
+ }
+}
+
+void
+Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unsed */,
+ csurface_t *surf /* unused */)
+{
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ ent = ent->enemy; /* now point at the plat, not the trigger */
+
+ if (ent->moveinfo.state == STATE_BOTTOM)
+ {
+ wait_and_change(ent, plat_go_up);
+ }
+ else if (ent->moveinfo.state == STATE_TOP)
+ {
+ ent->nextthink = level.time + 1; /* the player is still on the plat, so delay going down */
+ }
+}
+
+edict_t *
+plat_spawn_inside_trigger(edict_t *ent)
+{
+ edict_t *trigger;
+ vec3_t tmin, tmax;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ /* middle trigger */
+ trigger = G_Spawn();
+ trigger->touch = Touch_Plat_Center;
+ trigger->movetype = MOVETYPE_NONE;
+ trigger->solid = SOLID_TRIGGER;
+ trigger->enemy = ent;
+
+ tmin[0] = ent->mins[0] + 25;
+ tmin[1] = ent->mins[1] + 25;
+
+ tmax[0] = ent->maxs[0] - 25;
+ tmax[1] = ent->maxs[1] - 25;
+ tmax[2] = ent->maxs[2] + 8;
+
+ tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
+
+ if (ent->spawnflags & PLAT_LOW_TRIGGER)
+ {
+ tmax[2] = tmin[2] + 8;
+ }
+
+ if (tmax[0] - tmin[0] <= 0)
+ {
+ tmin[0] = (ent->mins[0] + ent->maxs[0]) * 0.5;
+ tmax[0] = tmin[0] + 1;
+ }
+
+ if (tmax[1] - tmin[1] <= 0)
+ {
+ tmin[1] = (ent->mins[1] + ent->maxs[1]) * 0.5;
+ tmax[1] = tmin[1] + 1;
+ }
+
+ VectorCopy(tmin, trigger->mins);
+ VectorCopy(tmax, trigger->maxs);
+
+ gi.linkentity(trigger);
+
+ return trigger;
+}
+
+/*
+ * QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
+ *
+ * speed -> default 150
+ *
+ * Plats are always drawn in the extended position,
+ * so they will light correctly.
+ *
+ * If the plat is the target of another trigger or button,
+ * it will start out disabled in the extended position until
+ * it is trigger, when it will lower and become a normal plat.
+ *
+ * "speed" overrides default 200.
+ * "accel" overrides default 500
+ * "lip" overrides default 8 pixel lip
+ *
+ * If the "height" key is set, that will determine the amount
+ * the plat moves, instead of being implicitly determoveinfoned
+ * by the model's height.
+ *
+ * Set "sounds" to one of the following:
+ * 1) base fast
+ * 2) chain slow
+ */
+void
+SP_func_plat(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->s.angles);
+ ent->solid = SOLID_BSP;
+ ent->movetype = MOVETYPE_PUSH;
+
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = plat_blocked;
+
+ if (!ent->speed)
+ {
+ ent->speed = 20;
+ }
+ else
+ {
+ ent->speed *= 0.1;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = 5;
+ }
+ else
+ {
+ ent->accel *= 0.1;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = 5;
+ }
+ else
+ {
+ ent->decel *= 0.1;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ if (!st.lip)
+ {
+ st.lip = 8;
+ }
+
+ /* pos1 is the top position, pos2 is the bottom */
+ VectorCopy(ent->s.origin, ent->pos1);
+ VectorCopy(ent->s.origin, ent->pos2);
+
+ if (st.height)
+ {
+ ent->pos2[2] -= st.height;
+ }
+ else
+ {
+ ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
+ }
+
+ ent->use = Use_Plat;
+
+ plat_spawn_inside_trigger(ent); /* the "start moving" trigger */
+
+ if (ent->targetname)
+ {
+ ent->moveinfo.state = STATE_UP;
+ }
+ else
+ {
+ VectorCopy(ent->pos2, ent->s.origin);
+ gi.linkentity(ent);
+ ent->moveinfo.state = STATE_BOTTOM;
+ }
+
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->pos1, ent->moveinfo.start_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
+ VectorCopy(ent->pos2, ent->moveinfo.end_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
+
+ ent->moveinfo.sound_start = gi.soundindex("plats/pt1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("plats/pt1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("plats/pt1_end.wav");
+}
+
+void
+plat2_spawn_danger_area(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ vec3_t mins, maxs;
+
+ VectorCopy(ent->mins, mins);
+ VectorCopy(ent->maxs, maxs);
+ maxs[2] = ent->mins[2] + 64;
+
+ SpawnBadArea(mins, maxs, 0, ent);
+}
+
+void
+plat2_kill_danger_area(edict_t *ent)
+{
+ edict_t *t;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ t = NULL;
+
+ while ((t = G_Find(t, FOFS(classname), "bad_area")))
+ {
+ if (t->owner == ent)
+ {
+ G_FreeEdict(t);
+ }
+ }
+}
+
+void
+plat2_hit_top(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_end)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end,
+ 1, ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = 0;
+ }
+
+ ent->moveinfo.state = STATE_TOP;
+
+ if (ent->plat2flags & PLAT2_CALLED)
+ {
+ ent->plat2flags = PLAT2_WAITING;
+
+ if (!(ent->spawnflags & PLAT2_TOGGLE))
+ {
+ ent->think = plat2_go_down;
+ ent->nextthink = level.time + 5.0;
+ }
+
+ if (deathmatch->value)
+ {
+ ent->last_move_time = level.time - 1.0;
+ }
+ else
+ {
+ ent->last_move_time = level.time - 2.0;
+ }
+ }
+ else if (!(ent->spawnflags & PLAT2_TOP) &&
+ !(ent->spawnflags & PLAT2_TOGGLE))
+ {
+ ent->plat2flags = 0;
+ ent->think = plat2_go_down;
+ ent->nextthink = level.time + 2.0;
+ ent->last_move_time = level.time;
+ }
+ else
+ {
+ ent->plat2flags = 0;
+ ent->last_move_time = level.time;
+ }
+
+ G_UseTargets(ent, ent);
+}
+
+void
+plat2_hit_bottom(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_end)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_end, 1,
+ ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = 0;
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+
+ if (ent->plat2flags & PLAT2_CALLED)
+ {
+ ent->plat2flags = PLAT2_WAITING;
+
+ if (!(ent->spawnflags & PLAT2_TOGGLE))
+ {
+ ent->think = plat2_go_up;
+ ent->nextthink = level.time + 5.0;
+ }
+
+ if (deathmatch->value)
+ {
+ ent->last_move_time = level.time - 1.0;
+ }
+ else
+ {
+ ent->last_move_time = level.time - 2.0;
+ }
+ }
+ else if ((ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE))
+ {
+ ent->plat2flags = 0;
+ ent->think = plat2_go_up;
+ ent->nextthink = level.time + 2.0;
+ ent->last_move_time = level.time;
+ }
+ else
+ {
+ ent->plat2flags = 0;
+ ent->last_move_time = level.time;
+ }
+
+ plat2_kill_danger_area(ent);
+ G_UseTargets(ent, ent);
+}
+
+void
+plat2_go_down(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_start)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = ent->moveinfo.sound_middle;
+ }
+
+ ent->moveinfo.state = STATE_DOWN;
+ ent->plat2flags |= PLAT2_MOVING;
+
+ Move_Calc(ent, ent->moveinfo.end_origin, plat2_hit_bottom);
+}
+
+void
+plat2_go_up(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_start)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = ent->moveinfo.sound_middle;
+ }
+
+ ent->moveinfo.state = STATE_UP;
+ ent->plat2flags |= PLAT2_MOVING;
+
+ plat2_spawn_danger_area(ent);
+
+ Move_Calc(ent, ent->moveinfo.start_origin, plat2_hit_top);
+}
+
+void
+plat2_operate(edict_t *ent, edict_t *other)
+{
+ int otherState;
+ float pauseTime;
+ float platCenter;
+ edict_t *trigger;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ trigger = ent;
+ ent = ent->enemy; /* now point at the plat, not the trigger */
+
+ if (ent->plat2flags & PLAT2_MOVING)
+ {
+ return;
+ }
+
+ if ((ent->last_move_time + 2) > level.time)
+ {
+ return;
+ }
+
+ platCenter = (trigger->absmin[2] + trigger->absmax[2]) / 2;
+
+ if (ent->moveinfo.state == STATE_TOP)
+ {
+ otherState = STATE_TOP;
+
+ if (ent->spawnflags & PLAT2_BOX_LIFT)
+ {
+ if (platCenter > other->s.origin[2])
+ {
+ otherState = STATE_BOTTOM;
+ }
+ }
+ else
+ {
+ if (trigger->absmax[2] > other->s.origin[2])
+ {
+ otherState = STATE_BOTTOM;
+ }
+ }
+ }
+ else
+ {
+ otherState = STATE_BOTTOM;
+
+ if (other->s.origin[2] > platCenter)
+ {
+ otherState = STATE_TOP;
+ }
+ }
+
+ ent->plat2flags = PLAT2_MOVING;
+
+ if (deathmatch->value)
+ {
+ pauseTime = 0.3;
+ }
+ else
+ {
+ pauseTime = 0.5;
+ }
+
+ if (ent->moveinfo.state != otherState)
+ {
+ ent->plat2flags |= PLAT2_CALLED;
+ pauseTime = 0.1;
+ }
+
+ ent->last_move_time = level.time;
+
+ if (ent->moveinfo.state == STATE_BOTTOM)
+ {
+ ent->think = plat2_go_up;
+ ent->nextthink = level.time + pauseTime;
+ }
+ else
+ {
+ ent->think = plat2_go_down;
+ ent->nextthink = level.time + pauseTime;
+ }
+}
+
+void
+Touch_Plat_Center2(edict_t *ent, edict_t *other,
+ cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ /* this requires monsters to actively trigger plats, not just step on them. */
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ /* don't let non-monsters activate plat2s */
+ if ((!(other->svflags & SVF_MONSTER)) && (!other->client))
+ {
+ return;
+ }
+
+ plat2_operate(ent, other);
+}
+
+void
+plat2_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ /* gib dead things */
+ if (other->health < 1)
+ {
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100, 1, 0, MOD_CRUSH);
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+ if (self->moveinfo.state == STATE_UP)
+ {
+ plat2_go_down(self);
+ }
+ else if (self->moveinfo.state == STATE_DOWN)
+ {
+ plat2_go_up(self);
+ }
+}
+
+void
+Use_Plat2(edict_t *ent, edict_t *other /* unused */,
+ edict_t *activator)
+{
+ edict_t *trigger;
+ int i;
+
+ if (!ent || !activator)
+ {
+ return;
+ }
+
+ if (ent->moveinfo.state > STATE_BOTTOM)
+ {
+ return;
+ }
+
+ if ((ent->last_move_time + 2) > level.time)
+ {
+ return;
+ }
+
+ for (i = 1, trigger = g_edicts + 1; i < globals.num_edicts; i++, trigger++)
+ {
+ if (!trigger->inuse)
+ {
+ continue;
+ }
+
+ if (trigger->touch == Touch_Plat_Center2)
+ {
+ if (trigger->enemy == ent)
+ {
+ plat2_operate(trigger, activator);
+ return;
+ }
+ }
+ }
+}
+
+void
+plat2_activate(edict_t *ent, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ edict_t *trigger;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->use = Use_Plat2;
+ trigger = plat_spawn_inside_trigger(ent); /* the "start moving" trigger */
+
+ trigger->maxs[0] += 10;
+ trigger->maxs[1] += 10;
+ trigger->mins[0] -= 10;
+ trigger->mins[1] -= 10;
+
+ gi.linkentity(trigger);
+
+ trigger->touch = Touch_Plat_Center2; /* Override trigger touch function */
+
+ plat2_go_down(ent);
+}
+
+/* QUAKED func_plat2 (0 .5 .8) ? PLAT_LOW_TRIGGER PLAT2_TOGGLE PLAT2_TOP PLAT2_TRIGGER_TOP PLAT2_TRIGGER_BOTTOM BOX_LIFT
+ * speed default 150
+ *
+ * PLAT_LOW_TRIGGER - creates a short trigger field at the bottom
+ * PLAT2_TOGGLE - plat will not return to default position.
+ * PLAT2_TOP - plat's default position will the the top.
+ * PLAT2_TRIGGER_TOP - plat will trigger it's targets each time it hits top
+ * PLAT2_TRIGGER_BOTTOM - plat will trigger it's targets each time it hits bottom
+ * BOX_LIFT - this indicates that the lift is a box, rather than just a platform
+ *
+ * Plats are always drawn in the extended position, so they will light correctly.
+ *
+ * If the plat is the target of another trigger or button, it will start out
+ * disabled in the extended position until it is trigger, when it will lower
+ * and become a normal plat.
+ *
+ * "speed" overrides default 200.
+ * "accel" overrides default 500
+ * "lip" no default
+ *
+ * If the "height" key is set, that will determine the amount the plat moves,
+ * instead of being implicitly determoveinfoned by the model's height.
+ *
+ */
+void
+SP_func_plat2(edict_t *ent)
+{
+ edict_t *trigger;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->s.angles);
+ ent->solid = SOLID_BSP;
+ ent->movetype = MOVETYPE_PUSH;
+
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = plat2_blocked;
+
+ if (!ent->speed)
+ {
+ ent->speed = 20;
+ }
+ else
+ {
+ ent->speed *= 0.1;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = 5;
+ }
+ else
+ {
+ ent->accel *= 0.1;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = 5;
+ }
+ else
+ {
+ ent->decel *= 0.1;
+ }
+
+ if (deathmatch->value)
+ {
+ ent->speed *= 2;
+ ent->accel *= 2;
+ ent->decel *= 2;
+ }
+
+ /* Added to kill things it's being blocked by */
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ /* pos1 is the top position, pos2 is the bottom */
+ VectorCopy(ent->s.origin, ent->pos1);
+ VectorCopy(ent->s.origin, ent->pos2);
+
+ if (st.height)
+ {
+ ent->pos2[2] -= (st.height - st.lip);
+ }
+ else
+ {
+ ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
+ }
+
+ ent->moveinfo.state = STATE_TOP;
+
+ if (ent->targetname)
+ {
+ ent->use = plat2_activate;
+ }
+ else
+ {
+ ent->use = Use_Plat2;
+
+ trigger = plat_spawn_inside_trigger(ent); /* the "start moving" trigger */
+
+ trigger->maxs[0] += 10;
+ trigger->maxs[1] += 10;
+ trigger->mins[0] -= 10;
+ trigger->mins[1] -= 10;
+
+ gi.linkentity(trigger);
+ trigger->touch = Touch_Plat_Center2; /* Override trigger touch function */
+
+ if (!(ent->spawnflags & PLAT2_TOP))
+ {
+ VectorCopy(ent->pos2, ent->s.origin);
+ ent->moveinfo.state = STATE_BOTTOM;
+ }
+ }
+
+ gi.linkentity(ent);
+
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->pos1, ent->moveinfo.start_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
+ VectorCopy(ent->pos2, ent->moveinfo.end_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
+
+ ent->moveinfo.sound_start = gi.soundindex("plats/pt1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("plats/pt1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("plats/pt1_end.wav");
+}
+
+/* ==================================================================== */
+
+/* QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL
+ *
+ * You need to have an origin brush as part of this entity. The center
+ * of that brush will bethe point around which it is rotated. It will
+ * rotate around the Z axis by default. You can check either the
+ * X_AXIS or Y_AXIS box to change that.
+ *
+ * func_rotating will use it's targets when it stops and starts.
+ *
+ * "speed" determines how fast it moves; default value is 100.
+ * "dmg" damage to inflict when blocked (2 default)
+ * "accel" if specified, is how much the rotation speed will increase per .1sec.
+ *
+ * REVERSE will cause the it to rotate in the opposite direction.
+ * STOP mean it will stop moving instead of pushing entities
+ * ACCEL means it will accelerate to it's final speed and decelerate when shutting down.
+ */
+void
+rotating_accel(edict_t *self)
+{
+ float current_speed;
+
+ if (!self)
+ {
+ return;
+ }
+
+ current_speed = VectorLength(self->avelocity);
+
+ if (current_speed >= (self->speed - self->accel)) /* done */
+ {
+ VectorScale(self->movedir, self->speed, self->avelocity);
+ G_UseTargets(self, self);
+ }
+ else
+ {
+ current_speed += self->accel;
+ VectorScale(self->movedir, current_speed, self->avelocity);
+ self->think = rotating_accel;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+rotating_decel(edict_t *self)
+{
+ float current_speed;
+
+ if (!self)
+ {
+ return;
+ }
+
+ current_speed = VectorLength(self->avelocity);
+
+ if (current_speed <= self->decel) /* done */
+ {
+ VectorClear(self->avelocity);
+ G_UseTargets(self, self);
+ self->touch = NULL;
+ }
+ else
+ {
+ current_speed -= self->decel;
+ VectorScale(self->movedir, current_speed, self->avelocity);
+ self->think = rotating_decel;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+rotating_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+rotating_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
+ {
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+ }
+}
+
+void
+rotating_use(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!VectorCompare(self->avelocity, vec3_origin))
+ {
+ self->s.sound = 0;
+
+ if (self->spawnflags & 8192) /* Decelerate */
+ {
+ rotating_decel(self);
+ }
+ else
+ {
+ VectorClear(self->avelocity);
+ G_UseTargets(self, self);
+ self->touch = NULL;
+ }
+ }
+ else
+ {
+ self->s.sound = self->moveinfo.sound_middle;
+
+ if (self->spawnflags & 8192) /* accelerate */
+ {
+ rotating_accel(self);
+ }
+ else
+ {
+ VectorScale(self->movedir, self->speed, self->avelocity);
+ G_UseTargets(self, self);
+ }
+
+ if (self->spawnflags & 16)
+ {
+ self->touch = rotating_touch;
+ }
+ }
+}
+
+void
+SP_func_rotating(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->solid = SOLID_BSP;
+
+ if (ent->spawnflags & 32)
+ {
+ ent->movetype = MOVETYPE_STOP;
+ }
+ else
+ {
+ ent->movetype = MOVETYPE_PUSH;
+ }
+
+ /* set the axis of rotation */
+ VectorClear(ent->movedir);
+
+ if (ent->spawnflags & 4)
+ {
+ ent->movedir[2] = 1.0;
+ }
+ else if (ent->spawnflags & 8)
+ {
+ ent->movedir[0] = 1.0;
+ }
+ else /* Z_AXIS */
+ {
+ ent->movedir[1] = 1.0;
+ }
+
+ /* check for reverse rotation */
+ if (ent->spawnflags & 2)
+ {
+ VectorNegate(ent->movedir, ent->movedir);
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 100;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ ent->use = rotating_use;
+
+ ent->blocked = rotating_blocked;
+
+ if (ent->spawnflags & 1)
+ {
+ ent->use(ent, NULL, NULL);
+ }
+
+ if (ent->spawnflags & 64)
+ {
+ ent->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (ent->spawnflags & 128)
+ {
+ ent->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ if (ent->spawnflags & 8192) /* Accelerate / Decelerate */
+ {
+ if (!ent->accel)
+ {
+ ent->accel = 1;
+ }
+ else if (ent->accel > ent->speed)
+ {
+ ent->accel = ent->speed;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = 1;
+ }
+ else if (ent->decel > ent->speed)
+ {
+ ent->decel = ent->speed;
+ }
+ }
+
+ gi.setmodel(ent, ent->model);
+ gi.linkentity(ent);
+}
+
+/* ==================================================================== */
+
+/* BUTTONS */
+
+/*
+ * QUAKED func_button (0 .5 .8) ?
+ *
+ * When a button is touched, it moves some distance
+ * in the direction of it's angle, triggers all of it's
+ * targets, waits some time, then returns to it's original
+ * position where it can be triggered again.
+ *
+ * "angle" determines the opening direction
+ * "target" all entities with a matching targetname will be used
+ * "speed" override the default 40 speed
+ * "wait" override the default 1 second wait (-1 = never return)
+ * "lip" override the default 4 pixel lip remaining at end of move
+ * "health" if set, the button must be killed instead of touched
+ * "sounds"
+ * 1) silent
+ * 2) steam metal
+ * 3) wooden clunk
+ * 4) metallic click
+ * 5) in-out
+ */
+void
+button_done(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_BOTTOM;
+ self->s.effects &= ~EF_ANIM23;
+ self->s.effects |= EF_ANIM01;
+}
+
+void
+button_return(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_DOWN;
+
+ Move_Calc(self, self->moveinfo.start_origin, button_done);
+
+ self->s.frame = 0;
+
+ if (self->health)
+ {
+ self->takedamage = DAMAGE_YES;
+ }
+}
+
+void
+button_wait(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_TOP;
+ self->s.effects &= ~EF_ANIM01;
+ self->s.effects |= EF_ANIM23;
+
+ G_UseTargets(self, self->activator);
+ self->s.frame = 1;
+
+ if (self->moveinfo.wait >= 0)
+ {
+ self->nextthink = level.time + self->moveinfo.wait;
+ self->think = button_return;
+ }
+}
+
+void
+button_fire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->moveinfo.state == STATE_UP) ||
+ (self->moveinfo.state == STATE_TOP))
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_UP;
+
+ if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start,
+ 1, ATTN_STATIC, 0);
+ }
+
+ Move_Calc(self, self->moveinfo.end_origin, button_wait);
+}
+
+void
+button_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+ button_fire(self);
+}
+
+void
+button_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ self->activator = other;
+ button_fire(self);
+}
+
+void
+button_killed(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker, int damage /* unused */,
+ vec3_t point /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ self->activator = attacker;
+ self->health = self->max_health;
+ self->takedamage = DAMAGE_NO;
+ button_fire(self);
+}
+
+void
+SP_func_button(edict_t *ent)
+{
+ vec3_t abs_movedir;
+ float dist;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ G_SetMovedir(ent->s.angles, ent->movedir);
+ ent->movetype = MOVETYPE_STOP;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ if (ent->sounds != 1)
+ {
+ ent->moveinfo.sound_start = gi.soundindex("switches/butn2.wav");
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 40;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = ent->speed;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = ent->speed;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 3;
+ }
+
+ if (!st.lip)
+ {
+ st.lip = 4;
+ }
+
+ VectorCopy(ent->s.origin, ent->pos1);
+ abs_movedir[0] = fabs(ent->movedir[0]);
+ abs_movedir[1] = fabs(ent->movedir[1]);
+ abs_movedir[2] = fabs(ent->movedir[2]);
+ dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] +
+ abs_movedir[2] * ent->size[2] - st.lip;
+ VectorMA(ent->pos1, dist, ent->movedir, ent->pos2);
+
+ ent->use = button_use;
+ ent->s.effects |= EF_ANIM01;
+
+ if (ent->health)
+ {
+ ent->max_health = ent->health;
+ ent->die = button_killed;
+ ent->takedamage = DAMAGE_YES;
+ }
+ else if (!ent->targetname)
+ {
+ ent->touch = button_touch;
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->pos1, ent->moveinfo.start_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
+ VectorCopy(ent->pos2, ent->moveinfo.end_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
+
+ gi.linkentity(ent);
+}
+
+
+/* ==================================================================== */
+
+/*
+ * DOORS
+ *
+ * spawn a trigger surrounding the entire team
+ * unless it is already targeted by another
+ */
+
+/*
+ * QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
+ *
+ * TOGGLE wait in both the start and end states for a trigger event.
+ * START_OPEN the door to moves to its destination when spawned, and operate in reverse.
+ * It is used to temporarily or permanently close off an area when triggered
+ * (not useful for touch or takedamage doors).
+ * NOMONSTER monsters will not trigger this door
+ *
+ * "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+ * "angle" determines the opening direction
+ * "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+ * "health" if set, door must be shot open
+ * "speed" movement speed (100 default)
+ * "wait" wait before returning (3 default, -1 = never return)
+ * "lip" lip remaining at end of move (8 default)
+ * "dmg" damage to inflict when blocked (2 default)
+ * "sounds"
+ * 1) silent
+ * 2) light
+ * 3) medium
+ * 4) heavy
+ */
+
+void
+door_use_areaportals(edict_t *self, qboolean open)
+{
+ edict_t *t = NULL;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ return;
+ }
+
+ while ((t = G_Find(t, FOFS(targetname), self->target)))
+ {
+ if (Q_stricmp(t->classname, "func_areaportal") == 0)
+ {
+ gi.SetAreaPortalState(t->style, open);
+ }
+ }
+}
+
+void
+door_hit_top(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_end)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_end, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = 0;
+ }
+
+ self->moveinfo.state = STATE_TOP;
+
+ if (self->spawnflags & DOOR_TOGGLE)
+ {
+ return;
+ }
+
+ if (self->moveinfo.wait >= 0)
+ {
+ self->think = door_go_down;
+ self->nextthink = level.time + self->moveinfo.wait;
+ }
+}
+
+void
+door_hit_bottom(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_end)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_end, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = 0;
+ }
+
+ self->moveinfo.state = STATE_BOTTOM;
+ door_use_areaportals(self, false);
+}
+
+void
+door_go_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_start)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start,
+ 1, ATTN_STATIC, 0);
+ }
+
+ self->s.sound = self->moveinfo.sound_middle;
+ }
+
+ if (self->max_health)
+ {
+ self->takedamage = DAMAGE_YES;
+ self->health = self->max_health;
+ }
+
+ self->moveinfo.state = STATE_DOWN;
+
+ if (strcmp(self->classname, "func_door") == 0)
+ {
+ Move_Calc(self, self->moveinfo.start_origin, door_hit_bottom);
+ }
+ else if (strcmp(self->classname, "func_door_rotating") == 0)
+ {
+ AngleMove_Calc(self, door_hit_bottom);
+ }
+}
+
+void
+door_go_up(edict_t *self, edict_t *activator)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->moveinfo.state == STATE_UP)
+ {
+ return; /* already going up */
+ }
+
+ if (self->moveinfo.state == STATE_TOP)
+ {
+ /* reset top wait time */
+ if (self->moveinfo.wait >= 0)
+ {
+ self->nextthink = level.time + self->moveinfo.wait;
+ }
+
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_start)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = self->moveinfo.sound_middle;
+ }
+
+ self->moveinfo.state = STATE_UP;
+
+ if (strcmp(self->classname, "func_door") == 0)
+ {
+ Move_Calc(self, self->moveinfo.end_origin, door_hit_top);
+ }
+ else if (strcmp(self->classname, "func_door_rotating") == 0)
+ {
+ AngleMove_Calc(self, door_hit_top);
+ }
+
+ G_UseTargets(self, activator);
+ door_use_areaportals(self, true);
+}
+
+void
+smart_water_go_up(edict_t *self)
+{
+ float distance;
+ edict_t *lowestPlayer;
+ edict_t *ent;
+ float lowestPlayerPt;
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->moveinfo.state == STATE_TOP)
+ {
+ /* reset top wait time */
+ if (self->moveinfo.wait >= 0)
+ {
+ self->nextthink = level.time + self->moveinfo.wait;
+ }
+
+ return;
+ }
+
+ if (self->health)
+ {
+ if (self->absmax[2] >= self->health)
+ {
+ VectorClear(self->velocity);
+ self->nextthink = 0;
+ self->moveinfo.state = STATE_TOP;
+ return;
+ }
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_start)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = self->moveinfo.sound_middle;
+ }
+
+ /* find the lowest player point. */
+ lowestPlayerPt = 999999;
+ lowestPlayer = NULL;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = &g_edicts[1 + i];
+
+ /* don't count dead or unused player slots */
+ if ((ent->inuse) && (ent->health > 0))
+ {
+ if (ent->absmin[2] < lowestPlayerPt)
+ {
+ lowestPlayerPt = ent->absmin[2];
+ lowestPlayer = ent;
+ }
+ }
+ }
+
+ if (!lowestPlayer)
+ {
+ return;
+ }
+
+ distance = lowestPlayerPt - self->absmax[2];
+
+ /* for the calculations, make sure we
+ intend to go up at least a little. */
+ if (distance < self->accel)
+ {
+ distance = 100;
+ self->moveinfo.speed = 5;
+ }
+ else
+ {
+ self->moveinfo.speed = distance / self->accel;
+ }
+
+ if (self->moveinfo.speed < 5)
+ {
+ self->moveinfo.speed = 5;
+ }
+ else if (self->moveinfo.speed > self->speed)
+ {
+ self->moveinfo.speed = self->speed;
+ }
+
+ /* should this allow any movement other than straight up? */
+ VectorSet(self->moveinfo.dir, 0, 0, 1);
+ VectorScale(self->moveinfo.dir, self->moveinfo.speed, self->velocity);
+ self->moveinfo.remaining_distance = distance;
+
+ if (self->moveinfo.state != STATE_UP)
+ {
+ G_UseTargets(self, lowestPlayer);
+ door_use_areaportals(self, true);
+ self->moveinfo.state = STATE_UP;
+ }
+
+ self->think = smart_water_go_up;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+door_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ edict_t *ent;
+ vec3_t center;
+
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (self->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ if (self->spawnflags & DOOR_TOGGLE)
+ {
+ if ((self->moveinfo.state == STATE_UP) ||
+ (self->moveinfo.state == STATE_TOP))
+ {
+ /* trigger all paired doors */
+ for (ent = self; ent; ent = ent->teamchain)
+ {
+ ent->message = NULL;
+ ent->touch = NULL;
+ door_go_down(ent);
+ }
+
+ return;
+ }
+ }
+
+ /* smart water is different */
+ VectorAdd(self->mins, self->maxs, center);
+ VectorScale(center, 0.5, center);
+
+ if ((gi.pointcontents(center) & MASK_WATER) && self->spawnflags & 2)
+ {
+ self->message = NULL;
+ self->touch = NULL;
+ self->enemy = activator;
+ smart_water_go_up(self);
+ return;
+ }
+
+ /* trigger all paired doors */
+ for (ent = self; ent; ent = ent->teamchain)
+ {
+ ent->message = NULL;
+ ent->touch = NULL;
+ door_go_up(ent, activator);
+ }
+}
+
+void
+Touch_DoorTrigger(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ return;
+ }
+
+ if ((self->owner->spawnflags & DOOR_NOMONSTER) &&
+ (other->svflags & SVF_MONSTER))
+ {
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 1.0;
+
+ door_use(self->owner, other, other);
+}
+
+void
+Think_CalcMoveSpeed(edict_t *self)
+{
+ edict_t *ent;
+ float min;
+ float time;
+ float newspeed;
+ float ratio;
+ float dist;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->flags & FL_TEAMSLAVE)
+ {
+ return; /* only the team master does this */
+ }
+
+ /* find the smallest distance any member of the team will be moving */
+ min = fabs(self->moveinfo.distance);
+
+ for (ent = self->teamchain; ent; ent = ent->teamchain)
+ {
+ dist = fabs(ent->moveinfo.distance);
+
+ if (dist < min)
+ {
+ min = dist;
+ }
+ }
+
+ time = min / self->moveinfo.speed;
+
+ /* adjust speeds so they will all complete at the same time */
+ for (ent = self; ent; ent = ent->teamchain)
+ {
+ newspeed = fabs(ent->moveinfo.distance) / time;
+ ratio = newspeed / ent->moveinfo.speed;
+
+ if (ent->moveinfo.accel == ent->moveinfo.speed)
+ {
+ ent->moveinfo.accel = newspeed;
+ }
+ else
+ {
+ ent->moveinfo.accel *= ratio;
+ }
+
+ if (ent->moveinfo.decel == ent->moveinfo.speed)
+ {
+ ent->moveinfo.decel = newspeed;
+ }
+ else
+ {
+ ent->moveinfo.decel *= ratio;
+ }
+
+ ent->moveinfo.speed = newspeed;
+ }
+}
+
+void
+Think_SpawnDoorTrigger(edict_t *ent)
+{
+ edict_t *other;
+ vec3_t mins, maxs;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->flags & FL_TEAMSLAVE)
+ {
+ return; /* only the team leader spawns a trigger */
+ }
+
+ VectorCopy(ent->absmin, mins);
+ VectorCopy(ent->absmax, maxs);
+
+ for (other = ent->teamchain; other; other = other->teamchain)
+ {
+ AddPointToBounds(other->absmin, mins, maxs);
+ AddPointToBounds(other->absmax, mins, maxs);
+ }
+
+ /* expand */
+ mins[0] -= 60;
+ mins[1] -= 60;
+ maxs[0] += 60;
+ maxs[1] += 60;
+
+ other = G_Spawn();
+ VectorCopy(mins, other->mins);
+ VectorCopy(maxs, other->maxs);
+ other->owner = ent;
+ other->solid = SOLID_TRIGGER;
+ other->movetype = MOVETYPE_NONE;
+ other->touch = Touch_DoorTrigger;
+ gi.linkentity(other);
+
+ if (ent->spawnflags & DOOR_START_OPEN)
+ {
+ door_use_areaportals(ent, true);
+ }
+
+ Think_CalcMoveSpeed(ent);
+}
+
+void
+door_blocked(edict_t *self, edict_t *other)
+{
+ edict_t *ent;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entitiy without their origin near the model */
+ VectorMA(other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+ if (self->spawnflags & DOOR_CRUSHER)
+ {
+ return;
+ }
+
+ /* if a door has a negative wait, it would never come
+ back if blocked, so let it just squash the object
+ to death real fast */
+ if (self->moveinfo.wait >= 0)
+ {
+ if (self->moveinfo.state == STATE_DOWN)
+ {
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ door_go_up(ent, ent->activator);
+ }
+ }
+ else
+ {
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ door_go_down(ent);
+ }
+ }
+ }
+}
+
+void
+door_killed(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker, int damage /* unused */,
+ vec3_t point /* unused */)
+{
+ edict_t *ent;
+
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ ent->health = ent->max_health;
+ ent->takedamage = DAMAGE_NO;
+ }
+
+ door_use(self->teammaster, attacker, attacker);
+}
+
+void
+door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 5.0;
+
+ gi.centerprintf(other, "%s", self->message);
+ gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+SP_func_door(edict_t *ent)
+{
+ vec3_t abs_movedir;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->sounds != 1)
+ {
+ ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
+ }
+
+ G_SetMovedir(ent->s.angles, ent->movedir);
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = door_blocked;
+ ent->use = door_use;
+
+ if (!ent->speed)
+ {
+ ent->speed = 100;
+ }
+
+ if (deathmatch->value)
+ {
+ ent->speed *= 2;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = ent->speed;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = ent->speed;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 3;
+ }
+
+ if (!st.lip)
+ {
+ st.lip = 8;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ /* calculate second position */
+ VectorCopy(ent->s.origin, ent->pos1);
+ abs_movedir[0] = fabs(ent->movedir[0]);
+ abs_movedir[1] = fabs(ent->movedir[1]);
+ abs_movedir[2] = fabs(ent->movedir[2]);
+ ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] *
+ ent->size[1] + abs_movedir[2] * ent->size[2] -
+ st.lip;
+ VectorMA(ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
+
+ /* if it starts open, switch the positions */
+ if (ent->spawnflags & DOOR_START_OPEN)
+ {
+ VectorCopy(ent->pos2, ent->s.origin);
+ VectorCopy(ent->pos1, ent->pos2);
+ VectorCopy(ent->s.origin, ent->pos1);
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+
+ if (ent->health)
+ {
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_killed;
+ ent->max_health = ent->health;
+ }
+ else if (ent->targetname && ent->message)
+ {
+ gi.soundindex("misc/talk.wav");
+ ent->touch = door_touch;
+ }
+
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->pos1, ent->moveinfo.start_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
+ VectorCopy(ent->pos2, ent->moveinfo.end_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
+
+ if (ent->spawnflags & 16)
+ {
+ ent->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (ent->spawnflags & 64)
+ {
+ ent->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ /* to simplify logic elsewhere, make
+ non-teamed doors into a team of one */
+ if (!ent->team)
+ {
+ ent->teammaster = ent;
+ }
+
+ gi.linkentity(ent);
+
+ ent->nextthink = level.time + FRAMETIME;
+
+ if (ent->health || ent->targetname)
+ {
+ ent->think = Think_CalcMoveSpeed;
+ }
+ else
+ {
+ ent->think = Think_SpawnDoorTrigger;
+ }
+}
+
+void
+Door_Activate(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = NULL;
+
+ if (self->health)
+ {
+ self->takedamage = DAMAGE_YES;
+ self->die = door_killed;
+ self->max_health = self->health;
+ }
+
+ if (self->health)
+ {
+ self->think = Think_CalcMoveSpeed;
+ }
+ else
+ {
+ self->think = Think_SpawnDoorTrigger;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+/*
+ * QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS
+ *
+ * TOGGLE causes the door to wait in both the start and end states for a trigger event.
+ * START_OPEN the door to moves to its destination when spawned, and operate in reverse.
+ * It is used to temporarily or permanently close off an area when triggered
+ * (not useful for touch or takedamage doors).
+ * NOMONSTER monsters will not trigger this door
+ *
+ * You need to have an origin brush as part of this entity. The center of that brush will be
+ * the point around which it is rotated. It will rotate around the Z axis by default. You can
+ * check either the X_AXIS or Y_AXIS box to change that.
+ *
+ * "distance" is how many degrees the door will be rotated.
+ * "speed" determines how fast the door moves; default value is 100.
+ *
+ * REVERSE will cause the door to rotate in the opposite direction.
+ *
+ * "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+ * "angle" determines the opening direction
+ * "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+ * "health" if set, door must be shot open
+ * "speed" movement speed (100 default)
+ * "wait" wait before returning (3 default, -1 = never return)
+ * "dmg" damage to inflict when blocked (2 default)
+ * "sounds"
+ * 1) silent
+ * 2) light
+ * 3) medium
+ * 4) heavy
+ */
+void
+SP_func_door_rotating(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->s.angles);
+
+ /* set the axis of rotation */
+ VectorClear(ent->movedir);
+
+ if (ent->spawnflags & DOOR_X_AXIS)
+ {
+ ent->movedir[2] = 1.0;
+ }
+ else if (ent->spawnflags & DOOR_Y_AXIS)
+ {
+ ent->movedir[0] = 1.0;
+ }
+ else /* Z_AXIS */
+ {
+ ent->movedir[1] = 1.0;
+ }
+
+ /* check for reverse rotation */
+ if (ent->spawnflags & DOOR_REVERSE)
+ {
+ VectorNegate(ent->movedir, ent->movedir);
+ }
+
+ if (!st.distance)
+ {
+ gi.dprintf("%s at %s with no distance set\n", ent->classname,
+ vtos(ent->s.origin));
+ st.distance = 90;
+ }
+
+ VectorCopy(ent->s.angles, ent->pos1);
+ VectorMA(ent->s.angles, st.distance, ent->movedir, ent->pos2);
+ ent->moveinfo.distance = st.distance;
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = door_blocked;
+ ent->use = door_use;
+
+ if (!ent->speed)
+ {
+ ent->speed = 100;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = ent->speed;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = ent->speed;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 3;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ if (ent->sounds != 1)
+ {
+ ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
+ }
+
+ /* if it starts open, switch the positions */
+ if (ent->spawnflags & DOOR_START_OPEN)
+ {
+ VectorCopy(ent->pos2, ent->s.angles);
+ VectorCopy(ent->pos1, ent->pos2);
+ VectorCopy(ent->s.angles, ent->pos1);
+ VectorNegate(ent->movedir, ent->movedir);
+ }
+
+ if (ent->health)
+ {
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_killed;
+ ent->max_health = ent->health;
+ }
+
+ if (ent->targetname && ent->message)
+ {
+ gi.soundindex("misc/talk.wav");
+ ent->touch = door_touch;
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->s.origin, ent->moveinfo.start_origin);
+ VectorCopy(ent->pos1, ent->moveinfo.start_angles);
+ VectorCopy(ent->s.origin, ent->moveinfo.end_origin);
+ VectorCopy(ent->pos2, ent->moveinfo.end_angles);
+
+ if (ent->spawnflags & 16)
+ {
+ ent->s.effects |= EF_ANIM_ALL;
+ }
+
+ /* to simplify logic elsewhere, make non-teamed doors into a team of one */
+ if (!ent->team)
+ {
+ ent->teammaster = ent;
+ }
+
+ gi.linkentity(ent);
+
+ ent->nextthink = level.time + FRAMETIME;
+
+ if (ent->health || ent->targetname)
+ {
+ ent->think = Think_CalcMoveSpeed;
+ }
+ else
+ {
+ ent->think = Think_SpawnDoorTrigger;
+ }
+
+ if (ent->spawnflags & DOOR_INACTIVE)
+ {
+ ent->takedamage = DAMAGE_NO;
+ ent->die = NULL;
+ ent->think = NULL;
+ ent->nextthink = 0;
+ ent->use = Door_Activate;
+ }
+}
+
+void
+smart_water_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_LAVA);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100, 1, 0, MOD_LAVA);
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_water (0 .5 .8) ? START_OPEN
+ *
+ * func_water is a moveable water brush. It must be targeted to operate.
+ * Use a non-water texture at your own risk.
+ *
+ * START_OPEN causes the water to move to its destination when spawned
+ * and operate in reverse.
+ *
+ * "angle" determines the opening direction (up or down only)
+ * "speed" movement speed (25 default)
+ * "wait" wait before returning (-1 default, -1 = TOGGLE)
+ * "lip" lip remaining at end of move (0 default)
+ * "sounds" (yes, these need to be changed)
+ * 0) no sound
+ * 1) water
+ * 2) lava
+ */
+void
+SP_func_water(edict_t *self)
+{
+ vec3_t abs_movedir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ G_SetMovedir(self->s.angles, self->movedir);
+ self->movetype = MOVETYPE_PUSH;
+ self->solid = SOLID_BSP;
+ gi.setmodel(self, self->model);
+
+ switch (self->sounds)
+ {
+ default:
+ break;
+
+ case 1: /* water */
+ case 2: /* lava */
+ self->moveinfo.sound_start = gi.soundindex("world/mov_watr.wav");
+ self->moveinfo.sound_end = gi.soundindex("world/stp_watr.wav");
+ break;
+ }
+
+ /* calculate second position */
+ VectorCopy(self->s.origin, self->pos1);
+ abs_movedir[0] = fabs(self->movedir[0]);
+ abs_movedir[1] = fabs(self->movedir[1]);
+ abs_movedir[2] = fabs(self->movedir[2]);
+ self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] *
+ self->size[1] + abs_movedir[2] * self->size[2] -
+ st.lip;
+ VectorMA(self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
+
+ /* if it starts open, switch the positions */
+ if (self->spawnflags & DOOR_START_OPEN)
+ {
+ VectorCopy(self->pos2, self->s.origin);
+ VectorCopy(self->pos1, self->pos2);
+ VectorCopy(self->s.origin, self->pos1);
+ }
+
+ VectorCopy(self->pos1, self->moveinfo.start_origin);
+ VectorCopy(self->s.angles, self->moveinfo.start_angles);
+ VectorCopy(self->pos2, self->moveinfo.end_origin);
+ VectorCopy(self->s.angles, self->moveinfo.end_angles);
+
+ self->moveinfo.state = STATE_BOTTOM;
+
+ if (!self->speed)
+ {
+ self->speed = 25;
+ }
+
+ self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
+
+ if (self->spawnflags & 2) /* smart water */
+ {
+ if (!self->accel)
+ {
+ self->accel = 20;
+ }
+
+ self->blocked = smart_water_blocked;
+ }
+
+ if (!self->wait)
+ {
+ self->wait = -1;
+ }
+
+ self->moveinfo.wait = self->wait;
+
+ self->use = door_use;
+
+ if (self->wait == -1)
+ {
+ self->spawnflags |= DOOR_TOGGLE;
+ }
+
+ self->classname = "func_door";
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
+ *
+ * Trains are moving platforms that players can ride.
+ * The targets origin specifies the min point of the train
+ * at each corner. The train spawns at the first target it
+ * is pointing at. If the train is the target of a button
+ * or trigger, it will not begin moving until activated.
+ *
+ * speed default 100
+ * dmg default 2
+ * noise looping sound to play when the train is in motion
+ *
+ */
+void
+train_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entity without an origin near the model */
+ VectorMA(other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ if (!self->dmg)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 0.5;
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+train_wait(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->target_ent->pathtarget)
+ {
+ char *savetarget;
+ edict_t *ent;
+
+ ent = self->target_ent;
+ savetarget = ent->target;
+ ent->target = ent->pathtarget;
+ G_UseTargets(ent, self->activator);
+ ent->target = savetarget;
+
+ /* make sure we didn't get killed by a killtarget */
+ if (!self->inuse)
+ {
+ return;
+ }
+ }
+
+ if (self->moveinfo.wait)
+ {
+ if (self->moveinfo.wait > 0)
+ {
+ self->nextthink = level.time + self->moveinfo.wait;
+ self->think = train_next;
+ }
+ else if (self->spawnflags & TRAIN_TOGGLE)
+ {
+ self->target_ent = NULL;
+ self->spawnflags &= ~TRAIN_START_ON;
+ VectorClear(self->velocity);
+ self->nextthink = 0;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_end)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_end,
+ 1, ATTN_STATIC, 0);
+ }
+
+ self->s.sound = 0;
+ }
+ }
+ else
+ {
+ train_next(self);
+ }
+}
+
+void
+train_piece_wait(edict_t *self)
+{
+}
+
+void
+train_next(edict_t *self)
+{
+ edict_t *ent;
+ vec3_t dest;
+ qboolean first;
+
+ if (!self)
+ {
+ return;
+ }
+
+ first = true;
+
+again:
+ if (!self->target)
+ {
+ return;
+ }
+
+ ent = G_PickTarget(self->target);
+
+ if (!ent)
+ {
+ gi.dprintf("train_next: bad target %s\n", self->target);
+ return;
+ }
+
+ self->target = ent->target;
+
+ /* check for a teleport path_corner */
+ if (ent->spawnflags & 1)
+ {
+ if (!first)
+ {
+ gi.dprintf("connected teleport path_corners, see %s at %s\n",
+ ent->classname, vtos(ent->s.origin));
+ return;
+ }
+
+ first = false;
+ VectorSubtract(ent->s.origin, self->mins, self->s.origin);
+ VectorCopy(self->s.origin, self->s.old_origin);
+ self->s.event = EV_OTHER_TELEPORT;
+ gi.linkentity(self);
+ goto again;
+ }
+
+ if (ent->speed)
+ {
+ self->speed = ent->speed;
+ self->moveinfo.speed = ent->speed;
+
+ if (ent->accel)
+ {
+ self->moveinfo.accel = ent->accel;
+ }
+ else
+ {
+ self->moveinfo.accel = ent->speed;
+ }
+
+ if (ent->decel)
+ {
+ self->moveinfo.decel = ent->decel;
+ }
+ else
+ {
+ self->moveinfo.decel = ent->speed;
+ }
+
+ self->moveinfo.current_speed = 0;
+ }
+
+ self->moveinfo.wait = ent->wait;
+ self->target_ent = ent;
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_start)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = self->moveinfo.sound_middle;
+ }
+
+ VectorSubtract(ent->s.origin, self->mins, dest);
+ self->moveinfo.state = STATE_TOP;
+ VectorCopy(self->s.origin, self->moveinfo.start_origin);
+ VectorCopy(dest, self->moveinfo.end_origin);
+ Move_Calc(self, dest, train_wait);
+ self->spawnflags |= TRAIN_START_ON;
+
+ if (self->team)
+ {
+ edict_t *e;
+ vec3_t dir, dst;
+
+ VectorSubtract(dest, self->s.origin, dir);
+
+ for (e = self->teamchain; e; e = e->teamchain)
+ {
+ VectorAdd(dir, e->s.origin, dst);
+ VectorCopy(e->s.origin, e->moveinfo.start_origin);
+ VectorCopy(dst, e->moveinfo.end_origin);
+
+ e->moveinfo.state = STATE_TOP;
+ e->speed = self->speed;
+ e->moveinfo.speed = self->moveinfo.speed;
+ e->moveinfo.accel = self->moveinfo.accel;
+ e->moveinfo.decel = self->moveinfo.decel;
+ e->movetype = MOVETYPE_PUSH;
+ Move_Calc(e, dst, train_piece_wait);
+ }
+ }
+}
+
+void
+train_resume(edict_t *self)
+{
+ edict_t *ent;
+ vec3_t dest;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = self->target_ent;
+
+ VectorSubtract(ent->s.origin, self->mins, dest);
+ self->moveinfo.state = STATE_TOP;
+ VectorCopy(self->s.origin, self->moveinfo.start_origin);
+ VectorCopy(dest, self->moveinfo.end_origin);
+ Move_Calc(self, dest, train_wait);
+ self->spawnflags |= TRAIN_START_ON;
+}
+
+void
+func_train_find(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("train_find: no target\n");
+ return;
+ }
+
+ ent = G_PickTarget(self->target);
+
+ if (!ent)
+ {
+ gi.dprintf("train_find: target %s not found\n", self->target);
+ return;
+ }
+
+ self->target = ent->target;
+
+ VectorSubtract(ent->s.origin, self->mins, self->s.origin);
+ gi.linkentity(self);
+
+ /* if not triggered, start immediately */
+ if (!self->targetname)
+ {
+ self->spawnflags |= TRAIN_START_ON;
+ }
+
+ if (self->spawnflags & TRAIN_START_ON)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ self->think = train_next;
+ self->activator = self;
+ }
+}
+
+void
+train_use(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ if (self->spawnflags & TRAIN_START_ON)
+ {
+ if (!(self->spawnflags & TRAIN_TOGGLE))
+ {
+ return;
+ }
+
+ self->spawnflags &= ~TRAIN_START_ON;
+ VectorClear(self->velocity);
+ self->nextthink = 0;
+ }
+ else
+ {
+ if (self->target_ent)
+ {
+ train_resume(self);
+ }
+ else
+ {
+ train_next(self);
+ }
+ }
+}
+
+void
+SP_func_train(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+
+ VectorClear(self->s.angles);
+ self->blocked = train_blocked;
+
+ if (self->spawnflags & TRAIN_BLOCK_STOPS)
+ {
+ self->dmg = 0;
+ }
+ else
+ {
+ if (!self->dmg)
+ {
+ self->dmg = 100;
+ }
+ }
+
+ self->solid = SOLID_BSP;
+ gi.setmodel(self, self->model);
+
+ if (st.noise)
+ {
+ self->moveinfo.sound_middle = gi.soundindex(st.noise);
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 100;
+ }
+
+ self->moveinfo.speed = self->speed;
+ self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
+
+ self->use = train_use;
+
+ gi.linkentity(self);
+
+ if (self->target)
+ {
+ /* start trains on the second frame, to make
+ sure their targets have had a chance to spawn */
+ self->nextthink = level.time + FRAMETIME;
+ self->think = func_train_find;
+ }
+ else
+ {
+ gi.dprintf("func_train without a target at %s\n", vtos(self->absmin));
+ }
+}
+
+/*
+ * QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
+ */
+void
+trigger_elevator_use(edict_t *self, edict_t *other,
+ edict_t *activator /* unused */)
+{
+ edict_t *target;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->movetarget->nextthink)
+ {
+ return;
+ }
+
+ if (!other->pathtarget)
+ {
+ gi.dprintf("elevator used with no pathtarget\n");
+ return;
+ }
+
+ target = G_PickTarget(other->pathtarget);
+
+ if (!target)
+ {
+ gi.dprintf("elevator used with bad pathtarget: %s\n",
+ other->pathtarget);
+ return;
+ }
+
+ self->movetarget->target_ent = target;
+ train_resume(self->movetarget);
+}
+
+void
+trigger_elevator_init(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("trigger_elevator has no target\n");
+ return;
+ }
+
+ self->movetarget = G_PickTarget(self->target);
+
+ if (!self->movetarget)
+ {
+ gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
+ return;
+ }
+
+ if (strcmp(self->movetarget->classname, "func_train") != 0)
+ {
+ gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
+ return;
+ }
+
+ self->use = trigger_elevator_use;
+ self->svflags = SVF_NOCLIENT;
+}
+
+void
+SP_trigger_elevator(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = trigger_elevator_init;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
+ *
+ * "wait" base time between triggering all targets, default is 1
+ * "random" wait variance, default is 0
+ *
+ * so, the basic time between firing is a random time
+ * between (wait - random) and (wait + random)
+ *
+ * "delay" delay before first firing when turned on, default is 0
+ * "pausetime" additional delay used only the very first time
+ * and only if spawned with START_ON
+ *
+ * These can used but not touched.
+ */
+void
+func_timer_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_UseTargets(self, self->activator);
+ self->nextthink = level.time + self->wait + crandom() * self->random;
+}
+
+void
+func_timer_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ /* if on, turn it off */
+ if (self->nextthink)
+ {
+ self->nextthink = 0;
+ return;
+ }
+
+ /* turn it on */
+ if (self->delay)
+ {
+ self->nextthink = level.time + self->delay;
+ }
+ else
+ {
+ func_timer_think(self);
+ }
+}
+
+void
+SP_func_timer(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->wait)
+ {
+ self->wait = 1.0;
+ }
+
+ self->use = func_timer_use;
+ self->think = func_timer_think;
+
+ if (self->random >= self->wait)
+ {
+ self->random = self->wait - FRAMETIME;
+ gi.dprintf("func_timer at %s has random >= wait\n",
+ vtos(self->s.origin));
+ }
+
+ if (self->spawnflags & 1)
+ {
+ self->nextthink = level.time + 1.0 + st.pausetime + self->delay +
+ self->wait + crandom() * self->random;
+ self->activator = self;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
+ *
+ * Conveyors are stationary brushes that move what's on them.
+ * The brush should be have a surface with at least one current
+ * content enabled.
+ *
+ * speed default 100
+ */
+void
+func_conveyor_use(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 1)
+ {
+ self->speed = 0;
+ self->spawnflags &= ~1;
+ }
+ else
+ {
+ self->speed = self->count;
+ self->spawnflags |= 1;
+ }
+
+ if (!(self->spawnflags & 2))
+ {
+ self->count = 0;
+ }
+}
+
+void
+SP_func_conveyor(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 100;
+ }
+
+ if (!(self->spawnflags & 1))
+ {
+ self->count = self->speed;
+ self->speed = 0;
+ }
+
+ self->use = func_conveyor_use;
+
+ gi.setmodel(self, self->model);
+ self->solid = SOLID_BSP;
+ gi.linkentity(self);
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
+ * A secret door. Slide back and then to the side.
+ *
+ * open_once doors never closes
+ * 1st_left 1st move is left of arrow
+ * 1st_down 1st move is down from arrow
+ * always_shoot door is shootebale even if targeted
+ *
+ * "angle" determines the direction
+ * "dmg" damage to inflic when blocked (default 2)
+ * "wait" how long to hold in the open position (default 5, -1 means hold)
+ */
+void
+door_secret_use(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /*unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* make sure we're not already moving */
+ if (!VectorCompare(self->s.origin, vec3_origin))
+ {
+ return;
+ }
+
+ Move_Calc(self, self->pos1, door_secret_move1);
+ door_use_areaportals(self, true);
+}
+
+void
+door_secret_move1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + 1.0;
+ self->think = door_secret_move2;
+}
+
+void
+door_secret_move2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, self->pos2, door_secret_move3);
+}
+
+void
+door_secret_move3(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->wait == -1)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + self->wait;
+ self->think = door_secret_move4;
+}
+
+void
+door_secret_move4(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, self->pos1, door_secret_move5);
+}
+
+void
+door_secret_move5(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + 1.0;
+ self->think = door_secret_move6;
+}
+
+void
+door_secret_move6(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, vec3_origin, door_secret_done);
+}
+
+void
+door_secret_done(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
+ {
+ self->health = 0;
+ self->takedamage = DAMAGE_YES;
+ }
+
+ door_use_areaportals(self, false);
+}
+
+void
+door_secret_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entities without their origin near the model */
+ VectorMA(other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 0.5;
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+door_secret_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker, int damage /* unused */,
+ vec3_t point /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+ door_secret_use(self, attacker, attacker);
+}
+
+void
+SP_func_door_secret(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ vec3_t forward, right, up;
+ float side;
+ float width;
+ float length;
+
+ ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = door_secret_blocked;
+ ent->use = door_secret_use;
+
+ if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
+ {
+ ent->health = 0;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_secret_die;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 5;
+ }
+
+ ent->moveinfo.accel = ent->moveinfo.decel =
+ ent->moveinfo.speed = 50;
+
+ /* calculate positions */
+ AngleVectors(ent->s.angles, forward, right, up);
+ VectorClear(ent->s.angles);
+ side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
+
+ if (ent->spawnflags & SECRET_1ST_DOWN)
+ {
+ width = fabs(DotProduct(up, ent->size));
+ }
+ else
+ {
+ width = fabs(DotProduct(right, ent->size));
+ }
+
+ length = fabs(DotProduct(forward, ent->size));
+
+ if (ent->spawnflags & SECRET_1ST_DOWN)
+ {
+ VectorMA(ent->s.origin, -1 * width, up, ent->pos1);
+ }
+ else
+ {
+ VectorMA(ent->s.origin, side * width, right, ent->pos1);
+ }
+
+ VectorMA(ent->pos1, length, forward, ent->pos2);
+
+ if (ent->health)
+ {
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_killed;
+ ent->max_health = ent->health;
+ }
+ else if (ent->targetname && ent->message)
+ {
+ gi.soundindex("misc/talk.wav");
+ ent->touch = door_touch;
+ }
+
+ ent->classname = "func_door";
+
+ gi.linkentity(ent);
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_killbox (1 0 0) ?
+ *
+ * Kills everything inside when fired,
+ * irrespective of protection.
+ */
+void
+use_killbox(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ KillBox(self);
+
+ /* Hack to make sure that really everything is killed */
+ self->count--;
+
+ if (!self->count)
+ {
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 1;
+ }
+}
+
+void
+SP_func_killbox(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, ent->model);
+ ent->use = use_killbox;
+ ent->svflags = SVF_NOCLIENT;
+}
diff --git a/rogue/src/g_items.c b/rogue/src/g_items.c
new file mode 100644
index 0000000..a13088d
--- /dev/null
+++ b/rogue/src/g_items.c
@@ -0,0 +1,3684 @@
+/*
+ * =======================================================================
+ *
+ * Item handling and item definitions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define HEALTH_IGNORE_MAX 1
+#define HEALTH_TIMED 2
+
+qboolean Pickup_Weapon(edict_t *ent, edict_t *other);
+void Use_Weapon(edict_t *ent, gitem_t *inv);
+void Drop_Weapon(edict_t *ent, gitem_t *inv);
+
+void Weapon_Blaster(edict_t *ent);
+void Weapon_Shotgun(edict_t *ent);
+void Weapon_SuperShotgun(edict_t *ent);
+void Weapon_Machinegun(edict_t *ent);
+void Weapon_Chaingun(edict_t *ent);
+void Weapon_HyperBlaster(edict_t *ent);
+void Weapon_RocketLauncher(edict_t *ent);
+void Weapon_Grenade(edict_t *ent);
+void Weapon_GrenadeLauncher(edict_t *ent);
+void Weapon_Railgun(edict_t *ent);
+void Weapon_BFG(edict_t *ent);
+void Weapon_ChainFist(edict_t *ent);
+void Weapon_Disintegrator(edict_t *ent);
+void Weapon_ETF_Rifle(edict_t *ent);
+void Weapon_Heatbeam(edict_t *ent);
+void Weapon_Prox(edict_t *ent);
+void Weapon_Tesla(edict_t *ent);
+void Weapon_ProxLauncher(edict_t *ent);
+
+gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET};
+gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
+gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};
+
+int jacket_armor_index;
+int combat_armor_index;
+int body_armor_index;
+static int power_screen_index;
+static int power_shield_index;
+
+void Use_Quad(edict_t *ent, gitem_t *item);
+static int quad_drop_timeout_hack;
+
+/* ====================================================================== */
+
+gitem_t *
+GetItemByIndex(int index)
+{
+ if ((index == 0) || (index >= game.num_items))
+ {
+ return NULL;
+ }
+
+ return &itemlist[index];
+}
+
+gitem_t *
+FindItemByClassname(char *classname)
+{
+ int i;
+ gitem_t *it;
+
+ if (!classname)
+ {
+ return NULL;
+ }
+
+ it = itemlist;
+
+ for (i = 0; i < game.num_items; i++, it++)
+ {
+ if (!it->classname)
+ {
+ continue;
+ }
+
+ if (!Q_stricmp(it->classname, classname))
+ {
+ return it;
+ }
+ }
+
+ return NULL;
+}
+
+gitem_t *
+FindItem(char *pickup_name)
+{
+ int i;
+ gitem_t *it;
+
+ if (!pickup_name)
+ {
+ return NULL;
+ }
+
+ it = itemlist;
+
+ for (i = 0; i < game.num_items; i++, it++)
+ {
+ if (!it->pickup_name)
+ {
+ continue;
+ }
+
+ if (!Q_stricmp(it->pickup_name, pickup_name))
+ {
+ return it;
+ }
+ }
+
+ return NULL;
+}
+
+/* ====================================================================== */
+
+void
+DoRespawn(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->team)
+ {
+ edict_t *master;
+ int count;
+ int choice;
+
+ master = ent->teammaster;
+
+ for (count = 0, ent = master; ent; ent = ent->chain, count++)
+ {
+ }
+
+ choice = count ? randk() % count : 0;
+
+ for (count = 0, ent = master; count < choice; ent = ent->chain, count++)
+ {
+ }
+ }
+
+ if (randomrespawn && randomrespawn->value)
+ {
+ edict_t *newEnt;
+
+ newEnt = DoRandomRespawn(ent);
+
+ /* if we've changed entities, then do some sleight
+ * of hand. otherwise, the old entity will respawn */
+ if (newEnt)
+ {
+ G_FreeEdict(ent);
+ ent = newEnt;
+ }
+ }
+
+ ent->svflags &= ~SVF_NOCLIENT;
+ ent->solid = SOLID_TRIGGER;
+ gi.linkentity(ent);
+
+ /* send an effect */
+ ent->s.event = EV_ITEM_RESPAWN;
+}
+
+void
+SetRespawn(edict_t *ent, float delay)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->flags |= FL_RESPAWN;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+ ent->nextthink = level.time + delay;
+ ent->think = DoRespawn;
+ gi.linkentity(ent);
+}
+
+/* ====================================================================== */
+
+qboolean
+Pickup_Powerup(edict_t *ent, edict_t *other)
+{
+ int quantity;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ if (((skill->value == SKILL_MEDIUM) &&
+ (quantity >= 2)) || ((skill->value >= SKILL_HARD) && (quantity >= 1)))
+ {
+ return false;
+ }
+
+ if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ if (deathmatch->value)
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+ }
+
+ return true;
+}
+
+void
+Drop_General(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+ Drop_Item(ent, item);
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+}
+
+/* ====================================================================== */
+
+qboolean
+Pickup_Adrenaline(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (!deathmatch->value)
+ {
+ other->max_health += 1;
+ }
+
+ if (other->health < other->max_health)
+ {
+ other->health = other->max_health;
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_AncientHead(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ other->max_health += 2;
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Bandolier(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ gitem_t *item;
+ int index;
+
+ if (other->client->pers.max_bullets < 250)
+ {
+ other->client->pers.max_bullets = 250;
+ }
+
+ if (other->client->pers.max_shells < 150)
+ {
+ other->client->pers.max_shells = 150;
+ }
+
+ if (other->client->pers.max_cells < 250)
+ {
+ other->client->pers.max_cells = 250;
+ }
+
+ if (other->client->pers.max_slugs < 75)
+ {
+ other->client->pers.max_slugs = 75;
+ }
+
+ if (other->client->pers.max_flechettes < 250)
+ {
+ other->client->pers.max_flechettes = 250;
+ }
+
+ if (g_disruptor->value)
+ {
+ if (other->client->pers.max_rounds < 150)
+ {
+ other->client->pers.max_rounds = 150;
+ }
+ }
+
+ item = FindItem("Bullets");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_bullets)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_bullets;
+ }
+ }
+
+ item = FindItem("Shells");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_shells)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_shells;
+ }
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Pack(edict_t *ent, edict_t *other)
+{
+ gitem_t *item;
+ int index;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (other->client->pers.max_bullets < 300)
+ {
+ other->client->pers.max_bullets = 300;
+ }
+
+ if (other->client->pers.max_shells < 200)
+ {
+ other->client->pers.max_shells = 200;
+ }
+
+ if (other->client->pers.max_rockets < 100)
+ {
+ other->client->pers.max_rockets = 100;
+ }
+
+ if (other->client->pers.max_grenades < 100)
+ {
+ other->client->pers.max_grenades = 100;
+ }
+
+ if (other->client->pers.max_cells < 300)
+ {
+ other->client->pers.max_cells = 300;
+ }
+
+ if (other->client->pers.max_slugs < 100)
+ {
+ other->client->pers.max_slugs = 100;
+ }
+
+ if (other->client->pers.max_flechettes < 300)
+ {
+ other->client->pers.max_flechettes = 300;
+ }
+
+ if (g_disruptor->value)
+ {
+ if (other->client->pers.max_rounds < 200)
+ {
+ other->client->pers.max_rounds = 200;
+ }
+ }
+
+ item = FindItem("Bullets");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_bullets)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_bullets;
+ }
+ }
+
+ item = FindItem("Shells");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_shells)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_shells;
+ }
+ }
+
+ item = FindItem("Cells");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_cells)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_cells;
+ }
+ }
+
+ item = FindItem("Grenades");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_grenades)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_grenades;
+ }
+ }
+
+ item = FindItem("Rockets");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_rockets)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_rockets;
+ }
+ }
+
+ item = FindItem("Slugs");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_slugs)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_slugs;
+ }
+ }
+
+ item = FindItem("Flechettes");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_flechettes)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_flechettes;
+ }
+ }
+
+ item = FindItem("Rounds");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_rounds)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_rounds;
+ }
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Nuke(edict_t *ent, edict_t *other)
+{
+ int quantity;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ if (quantity >= 1)
+ {
+ return false;
+ }
+
+ if ((coop->value) && (ent->item->flags & IT_STAY_COOP))
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ if (deathmatch->value)
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+ }
+
+ return true;
+}
+
+void
+Use_IR(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->ir_framenum > level.framenum)
+ {
+ ent->client->ir_framenum += 600;
+ }
+ else
+ {
+ ent->client->ir_framenum = level.framenum + 600;
+ }
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ir_start.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+Use_Double(edict_t *ent, gitem_t *item)
+{
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->double_framenum > level.framenum)
+ {
+ ent->client->double_framenum += 300;
+ }
+ else
+ {
+ ent->client->double_framenum = level.framenum + 300;
+ }
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage1.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+Use_Compass(edict_t *ent, gitem_t *item)
+{
+ int ang;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ang = (int)(ent->client->v_angle[1]);
+
+ if (ang < 0)
+ {
+ ang += 360;
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, "Origin: %0.0f,%0.0f,%0.0f Dir: %d\n",
+ ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], ang);
+}
+
+void
+Use_Nuke(edict_t *ent, gitem_t *item)
+{
+ vec3_t forward, right, start;
+ float speed;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorCopy(ent->s.origin, start);
+ speed = 100;
+ fire_nuke(ent, start, forward, speed);
+}
+
+void
+Use_Doppleganger(edict_t *ent, gitem_t *item)
+{
+ vec3_t forward, right;
+ vec3_t createPt, spawnPt;
+ vec3_t ang;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ VectorClear(ang);
+ ang[YAW] = ent->client->v_angle[YAW];
+ AngleVectors(ang, forward, right, NULL);
+
+ VectorMA(ent->s.origin, 48, forward, createPt);
+
+ if (!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32))
+ {
+ return;
+ }
+
+ if (!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1))
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ SpawnGrow_Spawn(spawnPt, 0);
+ fire_doppleganger(ent, spawnPt, forward);
+}
+
+qboolean
+Pickup_Doppleganger(edict_t *ent, edict_t *other)
+{
+ int quantity;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (!(deathmatch->value))
+ {
+ return false;
+ }
+
+ quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ if (quantity >= 1)
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Sphere(edict_t *ent, edict_t *other)
+{
+ int quantity;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (other->client && other->client->owned_sphere)
+ {
+ return false;
+ }
+
+ quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ if (((skill->value == SKILL_MEDIUM) &&
+ (quantity >= 2)) || ((skill->value >= SKILL_HARD) && (quantity >= 1)))
+ {
+ return false;
+ }
+
+ if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ if (deathmatch->value)
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+ }
+
+ return true;
+}
+
+void
+Use_Defender(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if (ent->client && ent->client->owned_sphere)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ Defender_Launch(ent);
+}
+
+void
+Use_Hunter(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if (ent->client && ent->client->owned_sphere)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ Hunter_Launch(ent);
+}
+
+void
+Use_Vengeance(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if (ent->client && ent->client->owned_sphere)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ Vengeance_Launch(ent);
+}
+
+/* ====================================================================== */
+
+void
+Use_Quad(edict_t *ent, gitem_t *item)
+{
+ int timeout;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (quad_drop_timeout_hack)
+ {
+ timeout = quad_drop_timeout_hack;
+ quad_drop_timeout_hack = 0;
+ }
+ else
+ {
+ timeout = 300;
+ }
+
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ ent->client->quad_framenum += timeout;
+ }
+ else
+ {
+ ent->client->quad_framenum = level.framenum + timeout;
+ }
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM,
+ 0);
+}
+
+/* ====================================================================== */
+
+void
+Use_Breather(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->breather_framenum > level.framenum)
+ {
+ ent->client->breather_framenum += 300;
+ }
+ else
+ {
+ ent->client->breather_framenum = level.framenum + 300;
+ }
+}
+
+/* ====================================================================== */
+
+void
+Use_Envirosuit(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->enviro_framenum > level.framenum)
+ {
+ ent->client->enviro_framenum += 300;
+ }
+ else
+ {
+ ent->client->enviro_framenum = level.framenum + 300;
+ }
+}
+
+/* ====================================================================== */
+
+void
+Use_Invulnerability(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->invincible_framenum > level.framenum)
+ {
+ ent->client->invincible_framenum += 300;
+ }
+ else
+ {
+ ent->client->invincible_framenum = level.framenum + 300;
+ }
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0);
+}
+
+/* ====================================================================== */
+
+void
+Use_Silencer(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+ ent->client->silencer_shots += 30;
+}
+
+/* ====================================================================== */
+
+qboolean
+Pickup_Key(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (coop->value)
+ {
+ if (strcmp(ent->classname, "key_power_cube") == 0)
+ {
+ if (other->client->pers.power_cubes &
+ ((ent->spawnflags & 0x0000ff00) >> 8))
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+ other->client->pers.power_cubes |=
+ ((ent->spawnflags & 0x0000ff00) >> 8);
+ }
+ else
+ {
+ if (other->client->pers.inventory[ITEM_INDEX(ent->item)])
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1;
+ }
+
+ return true;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+ return true;
+}
+
+/* ====================================================================== */
+
+qboolean
+Add_Ammo(edict_t *ent, gitem_t *item, int count)
+{
+ int index;
+ int max;
+
+ if (!ent || !item)
+ {
+ return false;
+ }
+
+ if (!ent->client)
+ {
+ return false;
+ }
+
+
+ if (item->tag == AMMO_BULLETS)
+ {
+ max = ent->client->pers.max_bullets;
+ }
+ else if (item->tag == AMMO_SHELLS)
+ {
+ max = ent->client->pers.max_shells;
+ }
+ else if (item->tag == AMMO_ROCKETS)
+ {
+ max = ent->client->pers.max_rockets;
+ }
+ else if (item->tag == AMMO_GRENADES)
+ {
+ max = ent->client->pers.max_grenades;
+ }
+ else if (item->tag == AMMO_CELLS)
+ {
+ max = ent->client->pers.max_cells;
+ }
+ else if (item->tag == AMMO_SLUGS)
+ {
+ max = ent->client->pers.max_slugs;
+ }
+ else if (item->tag == AMMO_FLECHETTES)
+ {
+ max = ent->client->pers.max_flechettes;
+ }
+ else if (item->tag == AMMO_PROX)
+ {
+ max = ent->client->pers.max_prox;
+ }
+ else if (item->tag == AMMO_TESLA)
+ {
+ max = ent->client->pers.max_tesla;
+ }
+ else if (item->tag == AMMO_DISRUPTOR)
+ {
+ max = ent->client->pers.max_rounds;
+ }
+ else
+ {
+ gi.dprintf("undefined ammo type\n");
+ return false;
+ }
+
+ index = ITEM_INDEX(item);
+
+ if (ent->client->pers.inventory[index] == max)
+ {
+ return false;
+ }
+
+ ent->client->pers.inventory[index] += count;
+
+ if (ent->client->pers.inventory[index] > max)
+ {
+ ent->client->pers.inventory[index] = max;
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Ammo(edict_t *ent, edict_t *other)
+{
+ int oldcount;
+ int count;
+ qboolean weapon;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ weapon = (ent->item->flags & IT_WEAPON);
+
+ if ((weapon) && ((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ count = 1000;
+ }
+ else if (ent->count)
+ {
+ count = ent->count;
+ }
+ else
+ {
+ count = ent->item->quantity;
+ }
+
+ oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ if (!Add_Ammo(other, ent->item, count))
+ {
+ return false;
+ }
+
+ if (weapon && !oldcount)
+ {
+ /* don't switch to tesla */
+ if ((other->client->pers.weapon != ent->item) &&
+ (!deathmatch->value || (other->client->pers.weapon == FindItem("blaster"))) &&
+ (strcmp(ent->classname, "ammo_tesla")))
+ {
+ other->client->newweapon = ent->item;
+ }
+ }
+
+ if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value))
+ {
+ SetRespawn(ent, 30);
+ }
+
+ return true;
+}
+
+void
+Drop_Ammo(edict_t *ent, gitem_t *item)
+{
+ edict_t *dropped;
+ int index;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(item);
+ dropped = Drop_Item(ent, item);
+
+ if (ent->client->pers.inventory[index] >= item->quantity)
+ {
+ dropped->count = item->quantity;
+ }
+ else
+ {
+ dropped->count = ent->client->pers.inventory[index];
+ }
+
+ if (ent->client->pers.weapon &&
+ (ent->client->pers.weapon->tag == AMMO_GRENADES) &&
+ (item->tag == AMMO_GRENADES) &&
+ (ent->client->pers.inventory[index] - dropped->count <= 0))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Can't drop current weapon\n");
+ G_FreeEdict(dropped);
+ return;
+ }
+
+ ent->client->pers.inventory[index] -= dropped->count;
+ ValidateSelectedItem(ent);
+}
+
+/* ====================================================================== */
+
+void
+MegaHealth_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->owner->health > self->owner->max_health)
+ {
+ self->nextthink = level.time + 1;
+ self->owner->health -= 1;
+ return;
+ }
+
+ if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(self, 20);
+ }
+ else
+ {
+ G_FreeEdict(self);
+ }
+}
+
+qboolean
+Pickup_Health(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (!(ent->style & HEALTH_IGNORE_MAX))
+ {
+ if (other->health >= other->max_health)
+ {
+ return false;
+ }
+ }
+
+ other->health += ent->count;
+
+ if (!(ent->style & HEALTH_IGNORE_MAX))
+ {
+ if (other->health > other->max_health)
+ {
+ other->health = other->max_health;
+ }
+ }
+
+ if (ent->style & HEALTH_TIMED)
+ {
+ ent->think = MegaHealth_think;
+ ent->nextthink = level.time + 5;
+ ent->owner = other;
+ ent->flags |= FL_RESPAWN;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+ }
+ else
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, 30);
+ }
+ }
+
+ return true;
+}
+
+/* ====================================================================== */
+
+int
+ArmorIndex(edict_t *ent)
+{
+ if (!ent)
+ {
+ return 0;
+ }
+
+ if (!ent->client)
+ {
+ return 0;
+ }
+
+ if (ent->client->pers.inventory[jacket_armor_index] > 0)
+ {
+ return jacket_armor_index;
+ }
+
+ if (ent->client->pers.inventory[combat_armor_index] > 0)
+ {
+ return combat_armor_index;
+ }
+
+ if (ent->client->pers.inventory[body_armor_index] > 0)
+ {
+ return body_armor_index;
+ }
+
+ return 0;
+}
+
+qboolean
+Pickup_Armor(edict_t *ent, edict_t *other)
+{
+ int old_armor_index;
+ gitem_armor_t *oldinfo;
+ gitem_armor_t *newinfo;
+ int newcount;
+ float salvage;
+ int salvagecount;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ /* get info on new armor */
+ newinfo = (gitem_armor_t *)ent->item->info;
+
+ old_armor_index = ArmorIndex(other);
+
+ /* handle armor shards specially */
+ if (ent->item->tag == ARMOR_SHARD)
+ {
+ if (!old_armor_index)
+ {
+ other->client->pers.inventory[jacket_armor_index] = 2;
+ }
+ else
+ {
+ other->client->pers.inventory[old_armor_index] += 2;
+ }
+ }
+
+ /* if player has no armor, just use it */
+ else if (!old_armor_index)
+ {
+ other->client->pers.inventory[ITEM_INDEX(ent->item)] =
+ newinfo->base_count;
+ }
+
+ /* use the better armor */
+ else
+ {
+ /* get info on old armor */
+ if (old_armor_index == jacket_armor_index)
+ {
+ oldinfo = &jacketarmor_info;
+ }
+ else if (old_armor_index == combat_armor_index)
+ {
+ oldinfo = &combatarmor_info;
+ }
+ else
+ {
+ oldinfo = &bodyarmor_info;
+ }
+
+ if (newinfo->normal_protection > oldinfo->normal_protection)
+ {
+ /* calc new armor values */
+ salvage = oldinfo->normal_protection / newinfo->normal_protection;
+ salvagecount = salvage * other->client->pers.inventory[old_armor_index];
+ newcount = newinfo->base_count + salvagecount;
+
+ if (newcount > newinfo->max_count)
+ {
+ newcount = newinfo->max_count;
+ }
+
+ /* zero count of old armor so it goes away */
+ other->client->pers.inventory[old_armor_index] = 0;
+
+ /* change armor to new item with computed value */
+ other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount;
+ }
+ else
+ {
+ /* calc new armor values */
+ salvage = newinfo->normal_protection / oldinfo->normal_protection;
+ salvagecount = salvage * newinfo->base_count;
+ newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
+
+ if (newcount > oldinfo->max_count)
+ {
+ newcount = oldinfo->max_count;
+ }
+
+ /* if we're already maxed out then we don't need the new armor */
+ if (other->client->pers.inventory[old_armor_index] >= newcount)
+ {
+ return false;
+ }
+
+ /* update current armor value */
+ other->client->pers.inventory[old_armor_index] = newcount;
+ }
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, 20);
+ }
+
+ return true;
+}
+
+/* ====================================================================== */
+
+int
+PowerArmorType(edict_t *ent)
+{
+ if (!ent)
+ {
+ return POWER_ARMOR_NONE;
+ }
+
+ if (!ent->client)
+ {
+ return POWER_ARMOR_NONE;
+ }
+
+ if (!(ent->flags & FL_POWER_ARMOR))
+ {
+ return POWER_ARMOR_NONE;
+ }
+
+ if (ent->client->pers.inventory[power_shield_index] > 0)
+ {
+ return POWER_ARMOR_SHIELD;
+ }
+
+ if (ent->client->pers.inventory[power_screen_index] > 0)
+ {
+ return POWER_ARMOR_SCREEN;
+ }
+
+ return POWER_ARMOR_NONE;
+}
+
+void
+Use_PowerArmor(edict_t *ent, gitem_t *item)
+{
+ int index;
+
+ if (ent->flags & FL_POWER_ARMOR)
+ {
+ ent->flags &= ~FL_POWER_ARMOR;
+ gi.sound(ent, CHAN_AUTO, gi.soundindex( "misc/power2.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ index = ITEM_INDEX(FindItem("cells"));
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No cells for power armor.\n");
+ return;
+ }
+
+ ent->flags |= FL_POWER_ARMOR;
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);
+ }
+}
+
+qboolean
+Pickup_PowerArmor(edict_t *ent, edict_t *other)
+{
+ int quantity;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ if (deathmatch->value)
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ /* auto-use for DM only if we didn't already have one */
+ if (!quantity)
+ {
+ ent->item->use(other, ent->item);
+ }
+ }
+
+ return true;
+}
+
+void
+Drop_PowerArmor(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if ((ent->flags & FL_POWER_ARMOR) &&
+ (ent->client->pers.inventory[ITEM_INDEX(item)] == 1))
+ {
+ Use_PowerArmor(ent, item);
+ }
+
+ Drop_General(ent, item);
+}
+
+/* ====================================================================== */
+
+void
+Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ qboolean taken;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (other->health < 1)
+ {
+ return; /* dead people can't pickup */
+ }
+
+ if (!ent->item->pickup)
+ {
+ return; /* not a grabbable item? */
+ }
+
+ taken = ent->item->pickup(ent, other);
+
+ if (taken)
+ {
+ /* flash the screen */
+ other->client->bonus_alpha = 0.25;
+
+ /* show icon and name on status bar */
+ other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon);
+ other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS + ITEM_INDEX(ent->item);
+ other->client->pickup_msg_time = level.time + 3.0;
+
+ /* change selected item */
+ if (ent->item->use)
+ {
+ other->client->pers.selected_item =
+ other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item);
+ }
+
+ if (ent->item->pickup == Pickup_Health)
+ {
+ if (ent->count == 2)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->count == 10)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->count == 25)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0);
+ }
+ else /* (ent->count == 100) */
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+ else if (ent->item->pickup_sound)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0);
+ }
+
+ /* activate item instantly if appropriate */
+ /* moved down here so activation sounds override the pickup sound */
+ if (deathmatch->value)
+ {
+ if ((((int)dmflags->value & DF_INSTANT_ITEMS) &&
+ (ent->item->flags & IT_INSTANT_USE)) ||
+ ((ent->item->use == Use_Quad) &&
+ (ent->spawnflags & DROPPED_PLAYER_ITEM)))
+ {
+ if ((ent->item->use == Use_Quad) &&
+ (ent->spawnflags & DROPPED_PLAYER_ITEM))
+ {
+ quad_drop_timeout_hack =
+ (ent->nextthink - level.time) / FRAMETIME;
+ }
+
+ if (ent->item->use)
+ {
+ ent->item->use(other, ent->item);
+ }
+ else
+ {
+ gi.dprintf("Powerup has no use function!\n");
+ }
+ }
+ }
+ }
+
+ if (!(ent->spawnflags & ITEM_TARGETS_USED))
+ {
+ G_UseTargets(ent, other);
+ ent->spawnflags |= ITEM_TARGETS_USED;
+ }
+
+ if (!taken)
+ {
+ return;
+ }
+
+ if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) ||
+ (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
+ {
+ if (ent->flags & FL_RESPAWN)
+ {
+ ent->flags &= ~FL_RESPAWN;
+ }
+ else
+ {
+ G_FreeEdict(ent);
+ }
+ }
+}
+
+/* ====================================================================== */
+
+void
+drop_temp_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (other == ent->owner)
+ {
+ return;
+ }
+
+ Touch_Item(ent, other, plane, surf);
+}
+
+void
+drop_make_touchable(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->touch = Touch_Item;
+
+ if (deathmatch->value)
+ {
+ ent->nextthink = level.time + 29;
+ ent->think = G_FreeEdict;
+ }
+}
+
+edict_t *
+Drop_Item(edict_t *ent, gitem_t *item)
+{
+ edict_t *dropped;
+ vec3_t forward, right;
+ vec3_t offset;
+
+ if (!ent || !item)
+ {
+ return NULL;
+ }
+
+ dropped = G_Spawn();
+
+ dropped->classname = item->classname;
+ dropped->item = item;
+ dropped->spawnflags = DROPPED_ITEM;
+ dropped->s.effects = item->world_model_flags;
+ dropped->s.renderfx = RF_GLOW | RF_IR_VISIBLE;
+ VectorSet(dropped->mins, -15, -15, -15);
+ VectorSet(dropped->maxs, 15, 15, 15);
+ gi.setmodel(dropped, dropped->item->world_model);
+ dropped->solid = SOLID_TRIGGER;
+ dropped->movetype = MOVETYPE_TOSS;
+ dropped->touch = drop_temp_touch;
+ dropped->owner = ent;
+
+ if (ent->client)
+ {
+ trace_t trace;
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ VectorSet(offset, 24, 0, -16);
+ G_ProjectSource(ent->s.origin, offset, forward, right, dropped->s.origin);
+ trace = gi.trace(ent->s.origin, dropped->mins, dropped->maxs,
+ dropped->s.origin, ent, CONTENTS_SOLID);
+ VectorCopy(trace.endpos, dropped->s.origin);
+ }
+ else
+ {
+ AngleVectors(ent->s.angles, forward, right, NULL);
+ VectorCopy(ent->s.origin, dropped->s.origin);
+ }
+
+ VectorScale(forward, 100, dropped->velocity);
+ dropped->velocity[2] = 300;
+
+ dropped->think = drop_make_touchable;
+ dropped->nextthink = level.time + 1;
+
+ gi.linkentity(dropped);
+
+ return dropped;
+}
+
+void
+Use_Item(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->svflags &= ~SVF_NOCLIENT;
+ ent->use = NULL;
+
+ if (ent->spawnflags & ITEM_NO_TOUCH)
+ {
+ ent->solid = SOLID_BBOX;
+ ent->touch = NULL;
+ }
+ else
+ {
+ ent->solid = SOLID_TRIGGER;
+ ent->touch = Touch_Item;
+ }
+
+ gi.linkentity(ent);
+}
+
+/* ====================================================================== */
+
+void
+droptofloor(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ trace_t tr;
+ vec3_t dest;
+ float *v;
+
+ v = tv(-15, -15, -15);
+ VectorCopy(v, ent->mins);
+ v = tv(15, 15, 15);
+ VectorCopy(v, ent->maxs);
+
+ if (ent->model)
+ {
+ gi.setmodel(ent, ent->model);
+ }
+ else if (ent->item->world_model)
+ {
+ gi.setmodel(ent, ent->item->world_model);
+ }
+
+ ent->solid = SOLID_TRIGGER;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->touch = Touch_Item;
+
+ v = tv(0, 0, -128);
+ VectorAdd(ent->s.origin, v, dest);
+
+ tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
+
+ if (tr.startsolid)
+ {
+ gi.dprintf("droptofloor: %s startsolid at %s\n", ent->classname,
+ vtos(ent->s.origin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ VectorCopy(tr.endpos, ent->s.origin);
+
+ if (ent->team)
+ {
+ ent->flags &= ~FL_TEAMSLAVE;
+ ent->chain = ent->teamchain;
+ ent->teamchain = NULL;
+
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+
+ if (ent == ent->teammaster)
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = DoRespawn;
+ }
+ }
+
+ if (ent->spawnflags & ITEM_NO_TOUCH)
+ {
+ ent->solid = SOLID_BBOX;
+ ent->touch = NULL;
+ ent->s.effects &= ~EF_ROTATE;
+ ent->s.renderfx &= ~RF_GLOW;
+ }
+
+ if (ent->spawnflags & ITEM_TRIGGER_SPAWN)
+ {
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+ ent->use = Use_Item;
+ }
+
+ gi.linkentity(ent);
+}
+
+/*
+ * Precaches all data needed for a given item.
+ * This will be called for each item spawned in a level,
+ * and for each item in each client's inventory.
+ */
+void
+PrecacheItem(gitem_t *it)
+{
+ char *s, *start;
+ char data[MAX_QPATH];
+ int len;
+ gitem_t *ammo;
+
+ if (!it)
+ {
+ return;
+ }
+
+ if (it->pickup_sound)
+ {
+ gi.soundindex(it->pickup_sound);
+ }
+
+ if (it->world_model)
+ {
+ gi.modelindex(it->world_model);
+ }
+
+ if (it->view_model)
+ {
+ gi.modelindex(it->view_model);
+ }
+
+ if (it->icon)
+ {
+ gi.imageindex(it->icon);
+ }
+
+ /* parse everything for its ammo */
+ if (it->ammo && it->ammo[0])
+ {
+ ammo = FindItem(it->ammo);
+
+ if (ammo != it)
+ {
+ PrecacheItem(ammo);
+ }
+ }
+
+ /* parse the space seperated precache string for other items */
+ s = it->precaches;
+
+ if (!s || !s[0])
+ {
+ return;
+ }
+
+ while (*s)
+ {
+ start = s;
+
+ while (*s && *s != ' ')
+ {
+ s++;
+ }
+
+ len = s - start;
+
+ if ((len >= MAX_QPATH) || (len < 5))
+ {
+ gi.error("PrecacheItem: %s has bad precache string", it->classname);
+ }
+
+ memcpy(data, start, len);
+ data[len] = 0;
+
+ if (*s)
+ {
+ s++;
+ }
+
+ /* determine type based on extension */
+ if (!strcmp(data + len - 3, "md2"))
+ {
+ gi.modelindex(data);
+ }
+ else if (!strcmp(data + len - 3, "sp2"))
+ {
+ gi.modelindex(data);
+ }
+ else if (!strcmp(data + len - 3, "wav"))
+ {
+ gi.soundindex(data);
+ }
+
+ if (!strcmp(data + len - 3, "pcx"))
+ {
+ gi.imageindex(data);
+ }
+ }
+}
+
+/*
+ * Create the item marked for spawn creation
+ */
+void
+Item_TriggeredSpawn(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = NULL;
+
+ if (strcmp(self->classname, "key_power_cube"))
+ {
+ self->spawnflags = 0;
+ }
+
+ droptofloor(self);
+}
+
+/*
+ * Set up an item to spawn in later.
+ */
+void
+SetTriggeredSpawn(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* don't do anything on key_power_cubes. */
+ if (!strcmp(ent->classname, "key_power_cube"))
+ {
+ return;
+ }
+
+ ent->think = NULL;
+ ent->nextthink = 0;
+ ent->use = Item_TriggeredSpawn;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+}
+
+/*
+ * ============
+ * Sets the clipping size and
+ * plants the object on the floor.
+ *
+ * Items can't be immediately dropped
+ * to floor, because they might be on
+ * an entity that hasn't spawned yet.
+ * ============
+ */
+void
+SpawnItem(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if (!g_disruptor->value)
+ {
+ if ((!strcmp(ent->classname, "ammo_disruptor")) ||
+ (!strcmp(ent->classname, "weapon_disintegrator")))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if (ent->spawnflags > 1)
+ {
+ if (strcmp(ent->classname, "key_power_cube") != 0)
+ {
+ ent->spawnflags = 0;
+ gi.dprintf("%s at %s has invalid spawnflags set\n",
+ ent->classname, vtos(ent->s.origin));
+ }
+ }
+
+ /* some items will be prevented in deathmatch */
+ if (deathmatch->value)
+ {
+ if ((int)dmflags->value & DF_NO_ARMOR)
+ {
+ if ((item->pickup == Pickup_Armor) ||
+ (item->pickup == Pickup_PowerArmor))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_NO_ITEMS)
+ {
+ if (item->pickup == Pickup_Powerup)
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (item->pickup == Pickup_Sphere)
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (item->pickup == Pickup_Doppleganger)
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_NO_HEALTH)
+ {
+ if ((item->pickup == Pickup_Health) ||
+ (item->pickup == Pickup_Adrenaline) ||
+ (item->pickup == Pickup_AncientHead))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_INFINITE_AMMO)
+ {
+ if ((item->flags == IT_AMMO) ||
+ (strcmp(ent->classname, "weapon_bfg") == 0))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_NO_MINES)
+ {
+ if (!strcmp(ent->classname, "ammo_prox") ||
+ !strcmp(ent->classname, "ammo_tesla"))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_NO_NUKES)
+ {
+ if (!strcmp(ent->classname, "ammo_nuke"))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_NO_SPHERES)
+ {
+ if (item->pickup == Pickup_Sphere)
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+ }
+
+ /* DM only items */
+ if (!deathmatch->value)
+ {
+ if ((item->pickup == Pickup_Doppleganger) ||
+ (item->pickup == Pickup_Nuke))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if ((item->use == Use_Vengeance) || (item->use == Use_Hunter))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ PrecacheItem(item);
+
+ if (coop->value && !(ent->spawnflags & ITEM_NO_TOUCH) && (strcmp(ent->classname, "key_power_cube") == 0))
+ {
+ ent->spawnflags |= (1 << (8 + level.power_cubes));
+ level.power_cubes++;
+ }
+
+ /* don't let them drop items that stay in a coop game */
+ if ((coop->value) && (item->flags & IT_STAY_COOP))
+ {
+ item->drop = NULL;
+ }
+
+ ent->item = item;
+ ent->nextthink = level.time + 2 * FRAMETIME; /* items start after other solids */
+ ent->think = droptofloor;
+ ent->s.effects = item->world_model_flags;
+ ent->s.renderfx = RF_GLOW;
+
+ if (ent->model)
+ {
+ gi.modelindex(ent->model);
+ }
+
+ if (ent->spawnflags & 1)
+ {
+ SetTriggeredSpawn(ent);
+ }
+}
+
+/* ====================================================================== */
+
+gitem_t itemlist[] = {
+ {
+ NULL
+ }, /* leave index 0 alone */
+
+ /* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_armor_body",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar1_pkup.wav",
+ "models/items/armor/body/tris.md2", EF_ROTATE,
+ NULL,
+ "i_bodyarmor",
+ "Body Armor",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ &bodyarmor_info,
+ ARMOR_BODY,
+ ""
+ },
+
+ /* QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_armor_combat",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar1_pkup.wav",
+ "models/items/armor/combat/tris.md2", EF_ROTATE,
+ NULL,
+ "i_combatarmor",
+ "Combat Armor",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ &combatarmor_info,
+ ARMOR_COMBAT,
+ ""
+ },
+
+ /* QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_armor_jacket",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar1_pkup.wav",
+ "models/items/armor/jacket/tris.md2", EF_ROTATE,
+ NULL,
+ "i_jacketarmor",
+ "Jacket Armor",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ &jacketarmor_info,
+ ARMOR_JACKET,
+ ""
+ },
+
+ /* QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_armor_shard",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar2_pkup.wav",
+ "models/items/armor/shard/tris.md2", EF_ROTATE,
+ NULL,
+ "i_jacketarmor",
+ "Armor Shard",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ NULL,
+ ARMOR_SHARD,
+ ""
+ },
+
+ /* QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_power_screen",
+ Pickup_PowerArmor,
+ Use_PowerArmor,
+ Drop_PowerArmor,
+ NULL,
+ "misc/ar3_pkup.wav",
+ "models/items/armor/screen/tris.md2", EF_ROTATE,
+ NULL,
+ "i_powerscreen",
+ "Power Screen",
+ 0,
+ 60,
+ NULL,
+ IT_ARMOR,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_power_shield",
+ Pickup_PowerArmor,
+ Use_PowerArmor,
+ Drop_PowerArmor,
+ NULL,
+ "misc/ar3_pkup.wav",
+ "models/items/armor/shield/tris.md2", EF_ROTATE,
+ NULL,
+ "i_powershield",
+ "Power Shield",
+ 0,
+ 60,
+ NULL,
+ IT_ARMOR,
+ 0,
+ NULL,
+ 0,
+ "misc/power2.wav misc/power1.wav"
+ },
+
+ /* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+ always owned, never in the world */
+ {
+ "weapon_blaster",
+ NULL,
+ Use_Weapon,
+ NULL,
+ Weapon_Blaster,
+ "misc/w_pkup.wav",
+ NULL, 0,
+ "models/weapons/v_blast/tris.md2",
+ "w_blaster",
+ "Blaster",
+ 0,
+ 0,
+ NULL,
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_BLASTER,
+ NULL,
+ 0,
+ "weapons/blastf1a.wav misc/lasfly.wav"
+ },
+
+ /* QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_shotgun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Shotgun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_shotg/tris.md2", EF_ROTATE,
+ "models/weapons/v_shotg/tris.md2",
+ "w_shotgun",
+ "Shotgun",
+ 0,
+ 1,
+ "Shells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_SHOTGUN,
+ NULL,
+ 0,
+ "weapons/shotgf1b.wav weapons/shotgr1b.wav"
+ },
+
+ /* QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_supershotgun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_SuperShotgun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_shotg2/tris.md2", EF_ROTATE,
+ "models/weapons/v_shotg2/tris.md2",
+ "w_sshotgun",
+ "Super Shotgun",
+ 0,
+ 2,
+ "Shells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_SUPERSHOTGUN,
+ NULL,
+ 0,
+ "weapons/sshotf1b.wav"
+ },
+
+ /* QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_machinegun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Machinegun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_machn/tris.md2", EF_ROTATE,
+ "models/weapons/v_machn/tris.md2",
+ "w_machinegun",
+ "Machinegun",
+ 0,
+ 1,
+ "Bullets",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_MACHINEGUN,
+ NULL,
+ 0,
+
+ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"
+ },
+
+ /* QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_chaingun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Chaingun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_chain/tris.md2", EF_ROTATE,
+ "models/weapons/v_chain/tris.md2",
+ "w_chaingun",
+ "Chaingun",
+ 0,
+ 1,
+ "Bullets",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_CHAINGUN,
+ NULL,
+ 0,
+
+ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"
+ },
+
+ /* QUAKED weapon_etf_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_etf_rifle",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_ETF_Rifle,
+ "misc/w_pkup.wav",
+ "models/weapons/g_etf_rifle/tris.md2", EF_ROTATE,
+ "models/weapons/v_etf_rifle/tris.md2",
+ "w_etf_rifle",
+ "ETF Rifle",
+ 0,
+ 1,
+ "Flechettes",
+ IT_WEAPON,
+ WEAP_ETFRIFLE,
+ NULL,
+ 0,
+ "weapons/nail1.wav models/proj/flechette/tris.md2",
+ },
+
+ /* QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_grenades",
+ Pickup_Ammo,
+ Use_Weapon,
+ Drop_Ammo,
+ Weapon_Grenade,
+ "misc/am_pkup.wav",
+ "models/items/ammo/grenades/medium/tris.md2", 0,
+ "models/weapons/v_handgr/tris.md2",
+ "a_grenades",
+ "Grenades",
+ 3,
+ 5,
+ "grenades",
+ IT_AMMO | IT_WEAPON,
+ WEAP_GRENADES,
+ NULL,
+ AMMO_GRENADES,
+
+ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "
+ },
+
+ /* QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_grenadelauncher",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_GrenadeLauncher,
+ "misc/w_pkup.wav",
+ "models/weapons/g_launch/tris.md2", EF_ROTATE,
+ "models/weapons/v_launch/tris.md2",
+ "w_glauncher",
+ "Grenade Launcher",
+ 0,
+ 1,
+ "Grenades",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_GRENADELAUNCHER,
+ NULL,
+ 0,
+
+ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"
+ },
+
+ /* QUAKED weapon_proxlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_proxlauncher",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_ProxLauncher,
+ "misc/w_pkup.wav",
+ "models/weapons/g_plaunch/tris.md2", EF_ROTATE,
+ "models/weapons/v_plaunch/tris.md2",
+ "w_proxlaunch",
+ "Prox Launcher",
+ 0,
+ 1,
+ "Prox",
+ IT_WEAPON,
+ WEAP_PROXLAUNCH,
+ NULL,
+ AMMO_PROX,
+ "weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav weapons/proxwarn.wav weapons/proxopen.wav",
+ },
+
+ /* QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_rocketlauncher",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_RocketLauncher,
+ "misc/w_pkup.wav",
+ "models/weapons/g_rocket/tris.md2", EF_ROTATE,
+ "models/weapons/v_rocket/tris.md2",
+ "w_rlauncher",
+ "Rocket Launcher",
+ 0,
+ 1,
+ "Rockets",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_ROCKETLAUNCHER,
+ NULL,
+ 0,
+
+ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2"
+ },
+
+ /* QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_hyperblaster",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_HyperBlaster,
+ "misc/w_pkup.wav",
+ "models/weapons/g_hyperb/tris.md2", EF_ROTATE,
+ "models/weapons/v_hyperb/tris.md2",
+ "w_hyperblaster",
+ "HyperBlaster",
+ 0,
+ 1,
+ "Cells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_HYPERBLASTER,
+ NULL,
+ 0,
+
+ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"
+ },
+
+ /* QUAKED weapon_plasmabeam (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_plasmabeam",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Heatbeam,
+ "misc/w_pkup.wav",
+ "models/weapons/g_beamer/tris.md2", EF_ROTATE,
+ "models/weapons/v_beamer/tris.md2",
+ "w_heatbeam",
+ "Plasma Beam",
+ 0,
+ 2,
+ "Cells",
+ IT_WEAPON,
+ WEAP_PLASMA,
+ NULL,
+ 0,
+ "models/weapons/v_beamer2/tris.md2 weapons/bfg__l1a.wav",
+ },
+
+ /* QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_railgun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Railgun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_rail/tris.md2", EF_ROTATE,
+ "models/weapons/v_rail/tris.md2",
+ "w_railgun",
+ "Railgun",
+ 0,
+ 1,
+ "Slugs",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_RAILGUN,
+ NULL,
+ 0,
+ "weapons/rg_hum.wav"
+ },
+
+ /* QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_bfg",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_BFG,
+ "misc/w_pkup.wav",
+ "models/weapons/g_bfg/tris.md2", EF_ROTATE,
+ "models/weapons/v_bfg/tris.md2",
+ "w_bfg",
+ "BFG10K",
+ 0,
+ 50,
+ "Cells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_BFG,
+ NULL,
+ 0,
+
+ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav"
+ },
+
+ /* QUAKED weapon_chainfist (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_chainfist",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_ChainFist,
+ "misc/w_pkup.wav",
+ "models/weapons/g_chainf/tris.md2", EF_ROTATE,
+ "models/weapons/v_chainf/tris.md2",
+ "w_chainfist",
+ "Chainfist",
+ 0,
+ 0,
+ NULL,
+ IT_WEAPON | IT_MELEE,
+ WEAP_CHAINFIST,
+ NULL,
+ 1,
+ "weapons/sawidle.wav weapons/sawhit.wav",
+ },
+
+ /* QUAKED weapon_disintegrator (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "weapon_disintegrator",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Disintegrator,
+ "misc/w_pkup.wav",
+ "models/weapons/g_dist/tris.md2", EF_ROTATE,
+ "models/weapons/v_dist/tris.md2",
+ "w_disintegrator",
+ "Disruptor",
+ 0,
+ 1,
+ "Rounds",
+ IT_WEAPON,
+ WEAP_DISRUPTOR,
+ NULL,
+ 1,
+ "models/items/spawngro/tris.md2 models/proj/disintegrator/tris.md2 weapons/disrupt.wav weapons/disint2.wav weapons/disrupthit.wav",
+ },
+
+ /* QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_shells",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/shells/medium/tris.md2", 0,
+ NULL,
+ "a_shells",
+ "Shells",
+ 3,
+ 10,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_SHELLS,
+ ""
+ },
+
+ /* QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_bullets",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/bullets/medium/tris.md2", 0,
+ NULL,
+ "a_bullets",
+ "Bullets",
+ 3,
+ 50,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_BULLETS,
+ ""
+ },
+
+ /* QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_cells",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/cells/medium/tris.md2", 0,
+ NULL,
+ "a_cells",
+ "Cells",
+ 3,
+ 50,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_CELLS,
+ ""
+ },
+
+ /* QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_rockets",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/rockets/medium/tris.md2", 0,
+ NULL,
+ "a_rockets",
+ "Rockets",
+ 3,
+ 5,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_ROCKETS,
+ ""
+ },
+
+ /* QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_slugs",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/slugs/medium/tris.md2", 0,
+ NULL,
+ "a_slugs",
+ "Slugs",
+ 3,
+ 10,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_SLUGS,
+ ""
+ },
+
+ /* QUAKED ammo_flechettes (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_flechettes",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/ammo/am_flechette/tris.md2", 0,
+ NULL,
+ "a_flechettes",
+ "Flechettes",
+ 3,
+ 50,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_FLECHETTES
+ },
+
+ /* QUAKED ammo_prox (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_prox",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/ammo/am_prox/tris.md2", 0,
+ NULL,
+ "a_prox",
+ "Prox",
+ 3,
+ 5,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_PROX,
+ "models/weapons/g_prox/tris.md2 weapons/proxwarn.wav"
+ },
+
+ /* QUAKED ammo_tesla (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_tesla",
+ Pickup_Ammo,
+ Use_Weapon,
+ Drop_Ammo,
+ Weapon_Tesla,
+ "misc/am_pkup.wav",
+ "models/ammo/am_tesl/tris.md2", 0,
+ "models/weapons/v_tesla/tris.md2",
+ "a_tesla",
+ "Tesla",
+ 3,
+ 5,
+ "Tesla",
+ IT_AMMO | IT_WEAPON,
+ 0,
+ NULL,
+ AMMO_TESLA,
+ "models/weapons/v_tesla2/tris.md2 weapons/teslaopen.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/weapons/g_tesla/tris.md2"
+ },
+
+ /* QUAKED ammo_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_nuke",
+ Pickup_Nuke,
+ Use_Nuke,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/weapons/g_nuke/tris.md2", EF_ROTATE,
+ NULL,
+ "p_nuke",
+ "A-M Bomb",
+ 3,
+ 300,
+ "A-M Bomb",
+ IT_POWERUP,
+ 0,
+ NULL,
+ 0,
+ "weapons/nukewarn2.wav world/rumble.wav"
+ },
+
+ /* QUAKED ammo_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "ammo_disruptor",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/ammo/am_disr/tris.md2", 0,
+ NULL,
+ "a_disruptor",
+ "Rounds",
+ 3,
+ 15,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_DISRUPTOR
+ },
+
+ /* QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_quad",
+ Pickup_Powerup,
+ Use_Quad,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/quaddama/tris.md2", EF_ROTATE,
+ NULL,
+ "p_quad",
+ "Quad Damage",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP|IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/damage.wav items/damage2.wav items/damage3.wav"
+ },
+
+ /* QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_invulnerability",
+ Pickup_Powerup,
+ Use_Invulnerability,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/invulner/tris.md2", EF_ROTATE,
+ NULL,
+ "p_invulnerability",
+ "Invulnerability",
+ 2,
+ 300,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/protect.wav items/protect2.wav items/protect4.wav"
+ },
+
+ /* QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_silencer",
+ Pickup_Powerup,
+ Use_Silencer,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/silencer/tris.md2", EF_ROTATE,
+ NULL,
+ "p_silencer",
+ "Silencer",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_breather",
+ Pickup_Powerup,
+ Use_Breather,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/breather/tris.md2", EF_ROTATE,
+ NULL,
+ "p_rebreather",
+ "Rebreather",
+ 2,
+ 60,
+ NULL,
+ IT_STAY_COOP | IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/airout.wav"
+ },
+
+ /* QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_enviro",
+ Pickup_Powerup,
+ Use_Envirosuit,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/enviro/tris.md2", EF_ROTATE,
+ NULL,
+ "p_envirosuit",
+ "Environment Suit",
+ 2,
+ 60,
+ NULL,
+ IT_STAY_COOP | IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/airout.wav"
+ },
+
+ /* QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ Special item that gives +2 to maximum health */
+ {
+ "item_ancient_head",
+ Pickup_AncientHead,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/c_head/tris.md2", EF_ROTATE,
+ NULL,
+ "i_fixme",
+ "Ancient Head",
+ 2,
+ 60,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ gives +1 to maximum health */
+ {
+ "item_adrenaline",
+ Pickup_Adrenaline,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/adrenal/tris.md2", EF_ROTATE,
+ NULL,
+ "p_adrenaline",
+ "Adrenaline",
+ 2,
+ 60,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_bandolier",
+ Pickup_Bandolier,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/band/tris.md2", EF_ROTATE,
+ NULL,
+ "p_bandolier",
+ "Bandolier",
+ 2,
+ 60,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_pack",
+ Pickup_Pack,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/pack/tris.md2", EF_ROTATE,
+ NULL,
+ "i_pack",
+ "Ammo Pack",
+ 2,
+ 180,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED item_ir_goggles (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_ir_goggles",
+ Pickup_Powerup,
+ Use_IR,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/goggles/tris.md2", EF_ROTATE,
+ NULL,
+ "p_ir",
+ "IR Goggles",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "misc/ir_start.wav"
+ },
+
+ /* QUAKED item_double (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_double",
+ Pickup_Powerup,
+ Use_Double,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/ddamage/tris.md2", EF_ROTATE,
+ NULL,
+ "p_double",
+ "Double Damage",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "misc/ddamage1.wav misc/ddamage2.wav misc/ddamage3.wav"
+ },
+
+ /* QUAKED item_compass (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_compass",
+ Pickup_Powerup,
+ Use_Compass,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/objects/fire/tris.md2", EF_ROTATE,
+ NULL,
+ "p_compass",
+ "compass",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP,
+ 0,
+ NULL,
+ 0,
+ },
+
+ /* QUAKED item_sphere_vengeance (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_sphere_vengeance",
+ Pickup_Sphere,
+ Use_Vengeance,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/vengnce/tris.md2", EF_ROTATE,
+ NULL,
+ "p_vengeance",
+ "vengeance sphere",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "spheres/v_idle.wav"
+ },
+
+ /* QUAKED item_sphere_hunter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_sphere_hunter",
+ Pickup_Sphere,
+ Use_Hunter,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/hunter/tris.md2", EF_ROTATE,
+ NULL,
+ "p_hunter",
+ "hunter sphere",
+ 2,
+ 120,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "spheres/h_idle.wav spheres/h_active.wav spheres/h_lurk.wav"
+ },
+
+ /* QUAKED item_sphere_defender (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_sphere_defender",
+ Pickup_Sphere,
+ Use_Defender,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/defender/tris.md2", EF_ROTATE,
+ NULL,
+ "p_defender",
+ "defender sphere",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "models/proj/laser2/tris.md2 models/items/shell/tris.md2 spheres/d_idle.wav"
+ },
+
+ /* QUAKED item_doppleganger (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "item_doppleganger",
+ Pickup_Doppleganger,
+ Use_Doppleganger,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/dopple/tris.md2",
+ EF_ROTATE,
+ NULL,
+ "p_doppleganger",
+ "Doppleganger",
+ 0,
+ 90,
+ NULL,
+ IT_POWERUP,
+ 0,
+ NULL,
+ 0,
+ "models/objects/dopplebase/tris.md2 models/items/spawngro2/tris.md2 models/items/hunter/tris.md2 models/items/vengnce/tris.md2",
+ },
+
+ {
+ NULL,
+ Tag_PickupToken,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/tagtoken/tris.md2",
+ EF_ROTATE | EF_TAGTRAIL,
+ NULL,
+ "i_tagtoken",
+ "Tag Token",
+ 0,
+ 0,
+ NULL,
+ IT_POWERUP | IT_NOT_GIVEABLE,
+ 0,
+ NULL,
+ 1,
+ NULL,
+ },
+
+ /* QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ key for computer centers */
+ {
+ "key_data_cd",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/data_cd/tris.md2", EF_ROTATE,
+ NULL,
+ "k_datacd",
+ "Data CD",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH
+ warehouse circuits */
+ {
+ "key_power_cube",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/power/tris.md2", EF_ROTATE,
+ NULL,
+ "k_powercube",
+ "Power Cube",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ key for the entrance of jail3 */
+ {
+ "key_pyramid",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/pyramid/tris.md2", EF_ROTATE,
+ NULL,
+ "k_pyramid",
+ "Pyramid Key",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ key for the city computer */
+ {
+ "key_data_spinner",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/spinner/tris.md2", EF_ROTATE,
+ NULL,
+ "k_dataspin",
+ "Data Spinner",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ security pass for the security level */
+ {
+ "key_pass",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/pass/tris.md2", EF_ROTATE,
+ NULL,
+ "k_security",
+ "Security Pass",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ normal door key - blue */
+ {
+ "key_blue_key",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/key/tris.md2", EF_ROTATE,
+ NULL,
+ "k_bluekey",
+ "Blue Key",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ normal door key - red */
+ {
+ "key_red_key",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/red_key/tris.md2", EF_ROTATE,
+ NULL,
+ "k_redkey",
+ "Red Key",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ tank commander's head */
+ {
+ "key_commander_head",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/monsters/commandr/head/tris.md2", EF_GIB,
+ NULL,
+ "k_comhead",
+ "Commander's Head",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ tank commander's head */
+ {
+ "key_airstrike_target",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/target/tris.md2", EF_ROTATE,
+ NULL,
+ "i_airstrike",
+ "Airstrike Marker",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /* QUAKED key_nuke_container (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "key_nuke_container",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/weapons/g_nuke/tris.md2",
+ EF_ROTATE,
+ NULL,
+ "i_contain",
+ "Antimatter Pod",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ },
+
+ /* QUAKED key_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */
+ {
+ "key_nuke",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/weapons/g_nuke/tris.md2",
+ EF_ROTATE,
+ NULL,
+ "i_nuke",
+ "Antimatter Bomb",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ },
+
+ {
+ NULL,
+ Pickup_Health,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ NULL, 0,
+ NULL,
+ "i_health",
+ "Health",
+ 3,
+ 0,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+
+ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"
+ },
+
+ /* end of list marker */
+ {NULL}
+};
+
+/*
+ * QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ */
+void
+SP_item_health(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/healing/medium/tris.md2";
+ self->count = 10;
+ SpawnItem(self, FindItem("Health"));
+ gi.soundindex("items/n_health.wav");
+}
+
+/*
+ * QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ */
+void
+SP_item_health_small(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/healing/stimpack/tris.md2";
+ self->count = 2;
+ SpawnItem(self, FindItem("Health"));
+ self->style = HEALTH_IGNORE_MAX;
+ gi.soundindex("items/s_health.wav");
+}
+
+/*
+ * QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ */
+void
+SP_item_health_large(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/healing/large/tris.md2";
+ self->count = 25;
+ SpawnItem(self, FindItem("Health"));
+ gi.soundindex("items/l_health.wav");
+}
+
+/*
+ * QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+ */
+void
+SP_item_health_mega(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/mega_h/tris.md2";
+ self->count = 100;
+ SpawnItem(self, FindItem("Health"));
+ gi.soundindex("items/m_health.wav");
+ self->style = HEALTH_IGNORE_MAX | HEALTH_TIMED;
+}
+
+void
+InitItems(void)
+{
+ game.num_items = sizeof(itemlist) / sizeof(itemlist[0]) - 1;
+}
+
+/*
+ * Called by worldspawn
+ */
+void
+SetItemNames(void)
+{
+ int i;
+ gitem_t *it;
+
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = &itemlist[i];
+ gi.configstring(CS_ITEMS + i, it->pickup_name);
+ }
+
+ jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
+ combat_armor_index = ITEM_INDEX(FindItem("Combat Armor"));
+ body_armor_index = ITEM_INDEX(FindItem("Body Armor"));
+ power_screen_index = ITEM_INDEX(FindItem("Power Screen"));
+ power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
+}
+
+void
+SP_xatrix_item(edict_t *self)
+{
+ gitem_t *item;
+ int i;
+ char *spawnClass = NULL;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->classname)
+ {
+ return;
+ }
+
+ if (!strcmp(self->classname, "ammo_magslug"))
+ {
+ spawnClass = "ammo_flechettes";
+ }
+ else if (!strcmp(self->classname, "ammo_trap"))
+ {
+ spawnClass = "weapon_proxlauncher";
+ }
+ else if (!strcmp(self->classname, "item_quadfire"))
+ {
+ float chance;
+
+ chance = random();
+
+ if (chance < 0.2)
+ {
+ spawnClass = "item_sphere_hunter";
+ }
+ else if (chance < 0.6)
+ {
+ spawnClass = "item_sphere_vengeance";
+ }
+ else
+ {
+ spawnClass = "item_sphere_defender";
+ }
+ }
+ else if (!strcmp(self->classname, "weapon_boomer"))
+ {
+ spawnClass = "weapon_etf_rifle";
+ }
+ else if (!strcmp(self->classname, "weapon_phalanx"))
+ {
+ spawnClass = "weapon_plasmabeam";
+ }
+
+ /* check item spawn functions */
+ for (i = 0, item = itemlist; i < game.num_items; i++, item++)
+ {
+ if (!item->classname)
+ {
+ continue;
+ }
+
+ if (!strcmp(item->classname, spawnClass))
+ {
+ /* found it */
+ SpawnItem(self, item);
+ return;
+ }
+ }
+}
diff --git a/rogue/src/g_main.c b/rogue/src/g_main.c
new file mode 100644
index 0000000..de6f000
--- /dev/null
+++ b/rogue/src/g_main.c
@@ -0,0 +1,488 @@
+/*
+ * =======================================================================
+ *
+ * Jump in into the game.so and support functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+game_locals_t game;
+level_locals_t level;
+game_import_t gi;
+game_export_t globals;
+spawn_temp_t st;
+
+int sm_meat_index;
+int snd_fry;
+int meansOfDeath;
+
+edict_t *g_edicts;
+
+cvar_t *deathmatch;
+cvar_t *coop;
+cvar_t *coop_baseq2; /* treat spawnflags according to baseq2 rules */
+cvar_t *coop_elevator_delay;
+cvar_t *coop_pickup_weapons;
+cvar_t *dmflags;
+cvar_t *skill;
+cvar_t *fraglimit;
+cvar_t *timelimit;
+cvar_t *password;
+cvar_t *spectator_password;
+cvar_t *needpass;
+cvar_t *maxclients;
+cvar_t *maxspectators;
+cvar_t *maxentities;
+cvar_t *g_select_empty;
+cvar_t *dedicated;
+cvar_t *g_footsteps;
+cvar_t *g_fix_triggered;
+
+cvar_t *filterban;
+
+cvar_t *sv_maxvelocity;
+cvar_t *sv_gravity;
+
+cvar_t *sv_rollspeed;
+cvar_t *sv_rollangle;
+cvar_t *gun_x;
+cvar_t *gun_y;
+cvar_t *gun_z;
+
+cvar_t *run_pitch;
+cvar_t *run_roll;
+cvar_t *bob_up;
+cvar_t *bob_pitch;
+cvar_t *bob_roll;
+
+cvar_t *sv_cheats;
+
+cvar_t *flood_msgs;
+cvar_t *flood_persecond;
+cvar_t *flood_waitdelay;
+
+cvar_t *sv_maplist;
+cvar_t *sv_stopspeed;
+
+cvar_t *g_showlogic;
+cvar_t *gamerules;
+cvar_t *huntercam;
+cvar_t *strong_mines;
+cvar_t *randomrespawn;
+
+cvar_t *g_disruptor;
+
+cvar_t *aimfix;
+cvar_t *g_machinegun_norecoil;
+cvar_t *g_swap_speed;
+
+void SpawnEntities(char *mapname, char *entities, char *spawnpoint);
+void ClientThink(edict_t *ent, usercmd_t *cmd);
+qboolean ClientConnect(edict_t *ent, char *userinfo);
+void ClientUserinfoChanged(edict_t *ent, char *userinfo);
+void ClientDisconnect(edict_t *ent);
+void ClientBegin(edict_t *ent);
+void ClientCommand(edict_t *ent);
+void RunEntity(edict_t *ent);
+void WriteGame(char *filename, qboolean autosave);
+void ReadGame(char *filename);
+void WriteLevel(char *filename);
+void ReadLevel(char *filename);
+void InitGame(void);
+void G_RunFrame(void);
+
+/* =================================================================== */
+
+void
+ShutdownGame(void)
+{
+ gi.dprintf("==== ShutdownGame ====\n");
+
+ gi.FreeTags(TAG_LEVEL);
+ gi.FreeTags(TAG_GAME);
+}
+
+/*
+ * Returns a pointer to the structure with
+ * all entry points and global variables
+ */
+game_export_t *
+GetGameAPI(game_import_t *import)
+{
+ gi = *import;
+
+ globals.apiversion = GAME_API_VERSION;
+ globals.Init = InitGame;
+ globals.Shutdown = ShutdownGame;
+ globals.SpawnEntities = SpawnEntities;
+
+ globals.WriteGame = WriteGame;
+ globals.ReadGame = ReadGame;
+ globals.WriteLevel = WriteLevel;
+ globals.ReadLevel = ReadLevel;
+
+ globals.ClientThink = ClientThink;
+ globals.ClientConnect = ClientConnect;
+ globals.ClientUserinfoChanged = ClientUserinfoChanged;
+ globals.ClientDisconnect = ClientDisconnect;
+ globals.ClientBegin = ClientBegin;
+ globals.ClientCommand = ClientCommand;
+
+ globals.RunFrame = G_RunFrame;
+
+ globals.ServerCommand = ServerCommand;
+
+ globals.edict_size = sizeof(edict_t);
+
+ /* Seed the PRNG */
+ randk_seed();
+
+ return &globals;
+}
+
+/*
+ * this is only here so the functions in
+ * q_shared.c and q_shwin.c can link
+ */
+void
+Sys_Error(char *error, ...)
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start(argptr, error);
+ vsprintf(text, error, argptr);
+ va_end(argptr);
+
+ gi.error("%s", text);
+}
+
+void
+Com_Printf(char *msg, ...)
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start(argptr, msg);
+ vsprintf(text, msg, argptr);
+ va_end(argptr);
+
+ gi.dprintf("%s", text);
+}
+
+/* ====================================================================== */
+
+void
+ClientEndServerFrames(void)
+{
+ int i;
+ edict_t *ent;
+
+ /* calc the player views now that all
+ pushing and damage has been added */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ ent = g_edicts + 1 + i;
+
+ if (!ent->inuse || !ent->client)
+ {
+ continue;
+ }
+
+ ClientEndServerFrame(ent);
+ }
+}
+
+/*
+ * Returns the created target changelevel
+ */
+edict_t *
+CreateTargetChangeLevel(char *map)
+{
+ edict_t *ent;
+
+ if (!map)
+ {
+ return NULL;
+ }
+
+ ent = G_Spawn();
+ ent->classname = "target_changelevel";
+ Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map);
+ ent->map = level.nextmap;
+ return ent;
+}
+
+/*
+ * The timelimit or fraglimit has been exceeded
+ */
+void
+EndDMLevel(void)
+{
+ edict_t *ent;
+ char *s, *t, *f;
+ static const char *seps = " ,\n\r";
+
+ /* stay on same level flag */
+ if ((int)dmflags->value & DF_SAME_LEVEL)
+ {
+ BeginIntermission(CreateTargetChangeLevel(level.mapname));
+ return;
+ }
+
+ /* see if it's in the map list */
+ if (*sv_maplist->string)
+ {
+ s = strdup(sv_maplist->string);
+ f = NULL;
+ t = strtok(s, seps);
+
+ while (t != NULL)
+ {
+ if (Q_stricmp(t, level.mapname) == 0)
+ {
+ /* it's in the list, go to the next one */
+ t = strtok(NULL, seps);
+
+ if (t == NULL) /* end of list, go to first one */
+ {
+ if (f == NULL) /* there isn't a first one, same level */
+ {
+ BeginIntermission(CreateTargetChangeLevel(level.mapname));
+ }
+ else
+ {
+ BeginIntermission(CreateTargetChangeLevel(f));
+ }
+ }
+ else
+ {
+ BeginIntermission(CreateTargetChangeLevel(t));
+ }
+
+ free(s);
+ return;
+ }
+
+ if (!f)
+ {
+ f = t;
+ }
+
+ t = strtok(NULL, seps);
+ }
+
+ free(s);
+ }
+
+ if (level.nextmap[0]) /* go to a specific map */
+ {
+ BeginIntermission(CreateTargetChangeLevel(level.nextmap));
+ }
+ else /* search for a changelevel */
+ {
+ ent = G_Find(NULL, FOFS(classname), "target_changelevel");
+
+ if (!ent)
+ {
+ /* the map designer didn't include a changelevel,
+ so create a fake ent that goes back to the same
+ level */
+ BeginIntermission(CreateTargetChangeLevel(level.mapname));
+ return;
+ }
+
+ BeginIntermission(ent);
+ }
+}
+
+void
+CheckNeedPass(void)
+{
+ int need;
+
+ /* if password or spectator_password has
+ changed, update needpass as needed */
+ if (password->modified || spectator_password->modified)
+ {
+ password->modified = spectator_password->modified = false;
+
+ need = 0;
+
+ if (*password->string && Q_stricmp(password->string, "none"))
+ {
+ need |= 1;
+ }
+
+ if (*spectator_password->string &&
+ Q_stricmp(spectator_password->string, "none"))
+ {
+ need |= 2;
+ }
+
+ gi.cvar_set("needpass", va("%d", need));
+ }
+}
+
+void
+CheckDMRules(void)
+{
+ int i;
+ gclient_t *cl;
+
+ if (level.intermissiontime)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ return;
+ }
+
+ if (gamerules && gamerules->value && DMGame.CheckDMRules)
+ {
+ if (DMGame.CheckDMRules())
+ {
+ return;
+ }
+ }
+
+ if (timelimit->value)
+ {
+ if (level.time >= timelimit->value * 60)
+ {
+ gi.bprintf(PRINT_HIGH, "Timelimit hit.\n");
+ EndDMLevel();
+ return;
+ }
+ }
+
+ if (fraglimit->value)
+ {
+ for (i = 0; i < maxclients->value; i++)
+ {
+ cl = game.clients + i;
+
+ if (!g_edicts[i + 1].inuse)
+ {
+ continue;
+ }
+
+ if (cl->resp.score >= fraglimit->value)
+ {
+ gi.bprintf(PRINT_HIGH, "Fraglimit hit.\n");
+ EndDMLevel();
+ return;
+ }
+ }
+ }
+}
+
+void
+ExitLevel(void)
+{
+ int i;
+ edict_t *ent;
+ char command[256];
+
+ Com_sprintf(command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
+ gi.AddCommandString(command);
+ level.changemap = NULL;
+ level.exitintermission = 0;
+ level.intermissiontime = 0;
+ ClientEndServerFrames();
+
+ /* clear some things before going to next level */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ ent = g_edicts + 1 + i;
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (ent->health > ent->max_health)
+ {
+ ent->health = ent->max_health;
+ }
+ }
+
+ debristhisframe = 0;
+ gibsthisframe = 0;
+}
+
+/*
+ * Advances the world by 0.1 seconds
+ */
+void
+G_RunFrame(void)
+{
+ int i;
+ edict_t *ent;
+
+ level.framenum++;
+ level.time = level.framenum * FRAMETIME;
+
+ debristhisframe = 0;
+ gibsthisframe = 0;
+
+ /* choose a client for monsters to target this frame */
+ AI_SetSightClient();
+
+ /* exit intermissions */
+ if (level.exitintermission)
+ {
+ ExitLevel();
+ return;
+ }
+
+ /* treat each object in turn even the
+ world gets a chance to think */
+ ent = &g_edicts[0];
+
+ for (i = 0; i < globals.num_edicts; i++, ent++)
+ {
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ level.current_entity = ent;
+
+ VectorCopy(ent->s.origin, ent->s.old_origin);
+
+ /* if the ground entity moved, make sure we are still on it */
+ if ((ent->groundentity) &&
+ (ent->groundentity->linkcount != ent->groundentity_linkcount))
+ {
+ ent->groundentity = NULL;
+
+ if (!(ent->flags & (FL_SWIM | FL_FLY)) &&
+ (ent->svflags & SVF_MONSTER))
+ {
+ M_CheckGround(ent);
+ }
+ }
+
+ if ((i > 0) && (i <= maxclients->value))
+ {
+ ClientBeginServerFrame(ent);
+ continue;
+ }
+
+ G_RunEntity(ent);
+ }
+
+ /* see if it is time to end a deathmatch */
+ CheckDMRules();
+
+ /* see if needpass needs updated */
+ CheckNeedPass();
+
+ /* build the playerstate_t structures for all players */
+ ClientEndServerFrames();
+}
diff --git a/rogue/src/g_misc.c b/rogue/src/g_misc.c
new file mode 100644
index 0000000..ea64ead
--- /dev/null
+++ b/rogue/src/g_misc.c
@@ -0,0 +1,2728 @@
+/*
+ * =======================================================================
+ *
+ * Miscellaneos entities, functs and functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+int debristhisframe;
+int gibsthisframe;
+
+extern void M_WorldEffects(edict_t *ent);
+
+/* ===================================================== */
+
+void
+Use_Areaportal(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->count ^= 1; /* toggle state */
+ gi.SetAreaPortalState(ent->style, ent->count);
+}
+
+/*
+ * QUAKED func_areaportal (0 0 0) ?
+ *
+ * This is a non-visible object that divides the world into
+ * areas that are seperated when this portal is not activated.
+ * Usually enclosed in the middle of a door.
+ */
+void
+SP_func_areaportal(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->use = Use_Areaportal;
+ ent->count = 0; /* always start closed; */
+}
+
+/* ===================================================== */
+
+void
+VelocityForDamage(int damage, vec3_t v)
+{
+ v[0] = 100.0 * crandom();
+ v[1] = 100.0 * crandom();
+ v[2] = 200.0 + 100.0 * random();
+
+ if (damage < 50)
+ {
+ VectorScale(v, 0.7, v);
+ }
+ else
+ {
+ VectorScale(v, 1.2, v);
+ }
+}
+
+void
+ClipGibVelocity(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->velocity[0] < -300)
+ {
+ ent->velocity[0] = -300;
+ }
+ else if (ent->velocity[0] > 300)
+ {
+ ent->velocity[0] = 300;
+ }
+
+ if (ent->velocity[1] < -300)
+ {
+ ent->velocity[1] = -300;
+ }
+ else if (ent->velocity[1] > 300)
+ {
+ ent->velocity[1] = 300;
+ }
+
+ if (ent->velocity[2] < 200)
+ {
+ ent->velocity[2] = 200; /* always some upwards */
+ }
+ else if (ent->velocity[2] > 500)
+ {
+ ent->velocity[2] = 500;
+ }
+}
+
+void
+gib_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.frame++;
+ self->nextthink = level.time + FRAMETIME;
+
+ if (self->s.frame == 10)
+ {
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 8 + random() * 10;
+ }
+}
+
+void
+gib_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane, csurface_t *surf /* unused */)
+{
+ vec3_t normal_angles, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->groundentity)
+ {
+ return;
+ }
+
+ self->touch = NULL;
+
+ if (plane)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0);
+
+ vectoangles(plane->normal, normal_angles);
+ AngleVectors(normal_angles, NULL, right, NULL);
+ vectoangles(right, self->s.angles);
+
+ if (self->s.modelindex == sm_meat_index)
+ {
+ self->s.frame++;
+ self->think = gib_think;
+ self->nextthink = level.time + FRAMETIME;
+ }
+ }
+}
+
+void
+gib_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+ThrowGib(edict_t *self, char *gibname, int damage, int type)
+{
+ edict_t *gib;
+ vec3_t vd;
+ vec3_t origin;
+ vec3_t size;
+ float vscale;
+
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ if (gibsthisframe > MAX_GIBS)
+ {
+ return;
+ }
+
+ gib = G_SpawnOptional();
+
+ if (!gib)
+ {
+ return;
+ }
+
+ gibsthisframe++;
+
+ VectorScale(self->size, 0.5, size);
+ VectorAdd(self->absmin, size, origin);
+ gib->s.origin[0] = origin[0] + crandom() * size[0];
+ gib->s.origin[1] = origin[1] + crandom() * size[1];
+ gib->s.origin[2] = origin[2] + crandom() * size[2];
+
+ gi.setmodel(gib, gibname);
+ gib->solid = SOLID_BBOX;
+ gib->svflags = SVF_DEADMONSTER;
+ gib->s.effects |= EF_GIB;
+ gib->flags |= FL_NO_KNOCKBACK;
+ gib->takedamage = DAMAGE_YES;
+ gib->die = gib_die;
+ gib->health = 250;
+
+ if (type == GIB_ORGANIC)
+ {
+ gib->movetype = MOVETYPE_TOSS;
+ gib->touch = gib_touch;
+ vscale = 0.5;
+ }
+ else
+ {
+ gib->movetype = MOVETYPE_BOUNCE;
+ vscale = 1.0;
+ }
+
+ VelocityForDamage(damage, vd);
+ VectorMA(self->velocity, vscale, vd, gib->velocity);
+ ClipGibVelocity(gib);
+ gib->avelocity[0] = random() * 600;
+ gib->avelocity[1] = random() * 600;
+ gib->avelocity[2] = random() * 600;
+
+ gib->think = G_FreeEdict;
+ gib->nextthink = level.time + 10 + random() * 10;
+ gib->s.renderfx |= RF_IR_VISIBLE;
+
+ gi.linkentity(gib);
+}
+
+void
+ThrowHead(edict_t *self, char *gibname, int damage, int type)
+{
+ vec3_t vd;
+ float vscale;
+
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ self->s.skinnum = 0;
+ self->s.frame = 0;
+ VectorClear(self->mins);
+ VectorClear(self->maxs);
+
+ self->s.modelindex2 = 0;
+ gi.setmodel(self, gibname);
+ self->solid = SOLID_BBOX;
+ self->s.effects |= EF_GIB;
+ self->s.effects &= ~EF_FLIES;
+ self->s.sound = 0;
+ self->flags |= FL_NO_KNOCKBACK;
+ self->svflags &= ~SVF_MONSTER;
+ self->takedamage = DAMAGE_YES;
+ self->targetname = NULL;
+ self->die = gib_die;
+
+ // The entity still has the monsters clipmaks.
+ // Reset it to MASK_SHOT to be on the save side.
+ self->clipmask = MASK_SHOT;
+
+ if (type == GIB_ORGANIC)
+ {
+ self->movetype = MOVETYPE_TOSS;
+ self->touch = gib_touch;
+ vscale = 0.5;
+ }
+ else
+ {
+ self->movetype = MOVETYPE_BOUNCE;
+ vscale = 1.0;
+ }
+
+ VelocityForDamage(damage, vd);
+ VectorMA(self->velocity, vscale, vd, self->velocity);
+ ClipGibVelocity(self);
+
+ self->avelocity[YAW] = crandom() * 600;
+
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 10 + random() * 10;
+
+ gi.linkentity(self);
+}
+
+void
+ThrowClientHead(edict_t *self, int damage)
+{
+ vec3_t vd;
+ char *gibname;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (rand() & 1)
+ {
+ gibname = "models/objects/gibs/head2/tris.md2";
+ self->s.skinnum = 1; /* second skin is player */
+ }
+ else
+ {
+ gibname = "models/objects/gibs/skull/tris.md2";
+ self->s.skinnum = 0;
+ }
+
+ self->s.origin[2] += 32;
+ self->s.frame = 0;
+ gi.setmodel(self, gibname);
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 16);
+
+ self->takedamage = DAMAGE_NO;
+ self->solid = SOLID_BBOX;
+ self->s.effects = EF_GIB;
+ self->s.sound = 0;
+ self->flags |= FL_NO_KNOCKBACK;
+
+ // The entity still has the monsters clipmaks.
+ // Reset it to MASK_SHOT to be on the save side.
+ self->clipmask = MASK_SHOT;
+
+ self->movetype = MOVETYPE_BOUNCE;
+ VelocityForDamage(damage, vd);
+ VectorAdd(self->velocity, vd, self->velocity);
+
+ if (self->client) /* bodies in the queue don't have a client anymore */
+ {
+ self->client->anim_priority = ANIM_DEATH;
+ self->client->anim_end = self->s.frame;
+ }
+ else
+ {
+ self->think = NULL;
+ self->nextthink = 0;
+ }
+
+ gi.linkentity(self);
+}
+
+void
+debris_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin)
+{
+ edict_t *chunk;
+ vec3_t v;
+
+ if (!self || !modelname)
+ {
+ return;
+ }
+
+ if (debristhisframe > MAX_DEBRIS)
+ {
+ return;
+ }
+
+ chunk = G_SpawnOptional();
+
+ if (!chunk)
+ {
+ return;
+ }
+
+ debristhisframe++;
+
+ VectorCopy(origin, chunk->s.origin);
+ gi.setmodel(chunk, modelname);
+ v[0] = 100 * crandom();
+ v[1] = 100 * crandom();
+ v[2] = 100 + 100 * crandom();
+ VectorMA(self->velocity, speed, v, chunk->velocity);
+ chunk->movetype = MOVETYPE_BOUNCE;
+ chunk->solid = SOLID_NOT;
+ chunk->avelocity[0] = random() * 600;
+ chunk->avelocity[1] = random() * 600;
+ chunk->avelocity[2] = random() * 600;
+ chunk->think = G_FreeEdict;
+ chunk->nextthink = level.time + 5 + random() * 5;
+ chunk->s.frame = 0;
+ chunk->flags = 0;
+ chunk->classname = "debris";
+ chunk->takedamage = DAMAGE_YES;
+ chunk->die = debris_die;
+ chunk->health = 250;
+ gi.linkentity(chunk);
+}
+
+void
+BecomeExplosion1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(self);
+}
+
+void
+BecomeExplosion2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION2);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(self);
+}
+
+/*
+ * QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
+ *
+ * Target: next path corner
+ * Pathtarget: gets used when an entity that has
+ * this path_corner targeted touches it
+ */
+void
+path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ vec3_t v;
+ edict_t *next;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->movetarget != self)
+ {
+ return;
+ }
+
+ if (other->enemy)
+ {
+ return;
+ }
+
+ if (self->pathtarget)
+ {
+ char *savetarget;
+
+ savetarget = self->target;
+ self->target = self->pathtarget;
+ G_UseTargets(self, other);
+ self->target = savetarget;
+ }
+
+ if (self->target)
+ {
+ next = G_PickTarget(self->target);
+ }
+ else
+ {
+ next = NULL;
+ }
+
+ if ((next) && (next->spawnflags & 1))
+ {
+ VectorCopy(next->s.origin, v);
+ v[2] += next->mins[2];
+ v[2] -= other->mins[2];
+ VectorCopy(v, other->s.origin);
+ next = G_PickTarget(next->target);
+ other->s.event = EV_OTHER_TELEPORT;
+ }
+
+ other->goalentity = other->movetarget = next;
+
+ if (self->wait)
+ {
+ other->monsterinfo.pausetime = level.time + self->wait;
+ other->monsterinfo.stand(other);
+ return;
+ }
+
+ if (!other->movetarget)
+ {
+ other->monsterinfo.pausetime = level.time + 100000000;
+ other->monsterinfo.stand(other);
+ }
+ else
+ {
+ VectorSubtract(other->goalentity->s.origin, other->s.origin, v);
+ other->ideal_yaw = vectoyaw(v);
+ }
+}
+
+void
+SP_path_corner(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->targetname)
+ {
+ gi.dprintf("path_corner with no targetname at %s\n", vtos(
+ self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->touch = path_corner_touch;
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+ self->svflags |= SVF_NOCLIENT;
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
+ *
+ * Makes this the target of a monster and it will head here
+ * when first activated before going after the activator. If
+ * hold is selected, it will stay here.
+ */
+void
+point_combat_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ edict_t *activator;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->movetarget != self)
+ {
+ return;
+ }
+
+ if (self->target)
+ {
+ other->target = self->target;
+ other->goalentity = other->movetarget = G_PickTarget(other->target);
+
+ if (!other->goalentity)
+ {
+ gi.dprintf("%s at %s target %s does not exist\n", self->classname,
+ vtos(self->s.origin), self->target);
+ other->movetarget = self;
+ }
+
+ self->target = NULL;
+ }
+ else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM | FL_FLY)))
+ {
+ other->monsterinfo.pausetime = level.time + 100000000;
+ other->monsterinfo.aiflags |= AI_STAND_GROUND;
+ other->monsterinfo.stand(other);
+ }
+
+ if (other->movetarget == self)
+ {
+ other->target = NULL;
+ other->movetarget = NULL;
+ other->goalentity = other->enemy;
+ other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
+ }
+
+ if (self->pathtarget)
+ {
+ char *savetarget;
+
+ savetarget = self->target;
+ self->target = self->pathtarget;
+
+ if (other->enemy && other->enemy->client)
+ {
+ activator = other->enemy;
+ }
+ else if (other->oldenemy && other->oldenemy->client)
+ {
+ activator = other->oldenemy;
+ }
+ else if (other->activator && other->activator->client)
+ {
+ activator = other->activator;
+ }
+ else
+ {
+ activator = other;
+ }
+
+ G_UseTargets(self, activator);
+ self->target = savetarget;
+ }
+}
+
+void
+SP_point_combat(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->touch = point_combat_touch;
+ VectorSet(self->mins, -8, -8, -16);
+ VectorSet(self->maxs, 8, 8, 16);
+ self->svflags = SVF_NOCLIENT;
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8)
+ *
+ * Just for the debugging level. Don't use
+ */
+void
+TH_viewthing(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.frame = (ent->s.frame + 1) % 7;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+SP_viewthing(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.dprintf("viewthing spawned\n");
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.renderfx = RF_FRAMELERP;
+ VectorSet(ent->mins, -16, -16, -24);
+ VectorSet(ent->maxs, 16, 16, 32);
+ ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
+ gi.linkentity(ent);
+ ent->nextthink = level.time + 0.5;
+ ent->think = TH_viewthing;
+ return;
+}
+
+/*
+ * QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
+ *
+ * Used as a positional target for spotlights, etc.
+ */
+void
+SP_info_null(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_FreeEdict(self);
+}
+
+/*
+ * QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
+ *
+ * Used as a positional target for lighting.
+ */
+void
+SP_info_notnull(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->s.origin, self->absmin);
+ VectorCopy(self->s.origin, self->absmax);
+}
+
+#define START_OFF 1
+
+/*
+ * QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
+ *
+ * Non-displayed light.
+ * Default light value is 300.
+ * Default style is 0.
+ * If targeted, will toggle between on and off.
+ * Default _cone value is 10 (used to set size of light for spotlights)
+ */
+void
+light_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & START_OFF)
+ {
+ gi.configstring(CS_LIGHTS + self->style, "m");
+ self->spawnflags &= ~START_OFF;
+ }
+ else
+ {
+ gi.configstring(CS_LIGHTS + self->style, "a");
+ self->spawnflags |= START_OFF;
+ }
+}
+
+void
+SP_light(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* no targeted lights in deathmatch, because they cause global messages */
+ if (!self->targetname || deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->style >= 32)
+ {
+ self->use = light_use;
+
+ if (self->spawnflags & START_OFF)
+ {
+ gi.configstring(CS_LIGHTS + self->style, "a");
+ }
+ else
+ {
+ gi.configstring(CS_LIGHTS + self->style, "m");
+ }
+ }
+}
+
+/*
+ * QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
+ *
+ * This is just a solid wall if not inhibited
+ * TRIGGER_SPAWN the wall will not be present until triggered
+ * it will then blink in to existance; it will
+ * kill anything that was in it's way
+ *
+ * TOGGLE only valid for TRIGGER_SPAWN walls
+ * this allows the wall to be turned on and off
+ *
+ * START_ON only valid for TRIGGER_SPAWN walls
+ * the wall will initially be present
+ */
+
+void
+func_wall_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->solid == SOLID_NOT)
+ {
+ self->solid = SOLID_BSP;
+ self->svflags &= ~SVF_NOCLIENT;
+ KillBox(self);
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ self->svflags |= SVF_NOCLIENT;
+ }
+
+ gi.linkentity(self);
+
+ if (!(self->spawnflags & 2))
+ {
+ self->use = NULL;
+ }
+}
+
+void
+SP_func_wall(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+
+ if (self->spawnflags & 8)
+ {
+ self->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (self->spawnflags & 16)
+ {
+ self->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ /* just a wall */
+ if ((self->spawnflags & 7) == 0)
+ {
+ self->solid = SOLID_BSP;
+ gi.linkentity(self);
+ return;
+ }
+
+ /* it must be TRIGGER_SPAWN */
+ if (!(self->spawnflags & 1))
+ {
+ self->spawnflags |= 1;
+ }
+
+ /* yell if the spawnflags are odd */
+ if (self->spawnflags & 4)
+ {
+ if (!(self->spawnflags & 2))
+ {
+ gi.dprintf("func_wall START_ON without TOGGLE\n");
+ self->spawnflags |= 2;
+ }
+ }
+
+ self->use = func_wall_use;
+
+ if (self->spawnflags & 4)
+ {
+ self->solid = SOLID_BSP;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ self->svflags |= SVF_NOCLIENT;
+ }
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
+ *
+ * This is solid bmodel that will fall if it's support it removed.
+ */
+
+void
+func_object_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ /* only squash thing we fall on top of */
+ if (plane && plane->normal[2] < 1.0)
+ {
+ return;
+ }
+
+ if (other->takedamage == DAMAGE_NO)
+ {
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, self->s.origin, vec3_origin,
+ self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+func_object_release(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_TOSS;
+ self->touch = func_object_touch;
+}
+
+void
+func_object_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BSP;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = NULL;
+ KillBox(self);
+ func_object_release(self);
+}
+
+void
+SP_func_object(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.setmodel(self, self->model);
+
+ self->mins[0] += 1;
+ self->mins[1] += 1;
+ self->mins[2] += 1;
+ self->maxs[0] -= 1;
+ self->maxs[1] -= 1;
+ self->maxs[2] -= 1;
+
+ if (!self->dmg)
+ {
+ self->dmg = 100;
+ }
+
+ if (self->spawnflags == 0)
+ {
+ self->solid = SOLID_BSP;
+ self->movetype = MOVETYPE_PUSH;
+ self->think = func_object_release;
+ self->nextthink = level.time + 2 * FRAMETIME;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ self->movetype = MOVETYPE_PUSH;
+ self->use = func_object_use;
+ self->svflags |= SVF_NOCLIENT;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (self->spawnflags & 4)
+ {
+ self->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ self->clipmask = MASK_MONSTERSOLID;
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE
+ *
+ * Any brush that you want to explode or break apart. If you want an
+ * ex0plosion, set dmg and it will do a radius explosion of that amount
+ * at the center of the bursh.
+ *
+ * If targeted it will not be shootable.
+ *
+ * INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must
+ * target the entity you want to trigger it. This is the only entity approved to activate it.
+ *
+ * health defaults to 100.
+ *
+ * mass defaults to 75. This determines how much debris is emitted when
+ * it explodes. You get one large chunk per 100 of mass (up to 8) and
+ * one small chunk per 25 of mass (up to 16). So 800 gives the most.
+ */
+void
+func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ vec3_t origin;
+ vec3_t chunkorigin;
+ vec3_t size;
+ int count;
+ int mass;
+ edict_t *master;
+
+ if (!self || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ /* bmodel origins are (0 0 0), we need to adjust that here */
+ VectorScale(self->size, 0.5, size);
+ VectorAdd(self->absmin, size, origin);
+ VectorCopy(origin, self->s.origin);
+
+ self->takedamage = DAMAGE_NO;
+
+ if (self->dmg)
+ {
+ T_RadiusDamage(self, attacker, self->dmg, NULL,
+ self->dmg + 40, MOD_EXPLOSIVE);
+ }
+
+ VectorSubtract(self->s.origin, inflictor->s.origin, self->velocity);
+ VectorNormalize(self->velocity);
+ VectorScale(self->velocity, 150, self->velocity);
+
+ /* start chunks towards the center */
+ VectorScale(size, 0.5, size);
+
+ mass = self->mass;
+
+ if (!mass)
+ {
+ mass = 75;
+ }
+
+ /* big chunks */
+ if (mass >= 100)
+ {
+ count = mass / 100;
+
+ if (count > 8)
+ {
+ count = 8;
+ }
+
+ while (count--)
+ {
+ chunkorigin[0] = origin[0] + crandom() * size[0];
+ chunkorigin[1] = origin[1] + crandom() * size[1];
+ chunkorigin[2] = origin[2] + crandom() * size[2];
+ ThrowDebris(self, "models/objects/debris1/tris.md2", 1, chunkorigin);
+ }
+ }
+
+ /* small chunks */
+ count = mass / 25;
+
+ if (count > 16)
+ {
+ count = 16;
+ }
+
+ while (count--)
+ {
+ chunkorigin[0] = origin[0] + crandom() * size[0];
+ chunkorigin[1] = origin[1] + crandom() * size[1];
+ chunkorigin[2] = origin[2] + crandom() * size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", 2, chunkorigin);
+ }
+
+ if (self->flags & FL_TEAMSLAVE)
+ {
+ master = self->teammaster;
+
+ /* because mappers (other than jim (usually)) are stupid.... */
+ while (master)
+ {
+ if (master->teamchain == self)
+ {
+ master->teamchain = self->teamchain;
+ break;
+ }
+
+ master = master->teamchain;
+ }
+ }
+
+ G_UseTargets(self, attacker);
+
+ if (self->dmg)
+ {
+ BecomeExplosion1(self);
+ }
+ else
+ {
+ G_FreeEdict(self);
+ }
+}
+
+void
+func_explosive_use(edict_t *self, edict_t *other, edict_t *activator /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ func_explosive_explode(self, self, other, self->health, vec3_origin);
+}
+
+void
+func_explosive_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = func_explosive_use;
+
+ if (!self->health)
+ {
+ self->health = 100;
+ }
+
+ self->die = func_explosive_explode;
+ self->takedamage = DAMAGE_YES;
+}
+
+void
+func_explosive_spawn(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BSP;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = NULL;
+ KillBox(self);
+ gi.linkentity(self);
+}
+
+void
+SP_func_explosive(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+
+ gi.modelindex("models/objects/debris1/tris.md2");
+ gi.modelindex("models/objects/debris2/tris.md2");
+
+ gi.setmodel(self, self->model);
+
+ if (self->spawnflags & 1)
+ {
+ self->svflags |= SVF_NOCLIENT;
+ self->solid = SOLID_NOT;
+ self->use = func_explosive_spawn;
+ }
+ else if (self->spawnflags & 8)
+ {
+ self->solid = SOLID_BSP;
+
+ if (self->targetname)
+ {
+ self->use = func_explosive_activate;
+ }
+ }
+ else
+ {
+ self->solid = SOLID_BSP;
+
+ if (self->targetname)
+ {
+ self->use = func_explosive_use;
+ }
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (self->spawnflags & 4)
+ {
+ self->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ if ((self->use != func_explosive_use) &&
+ (self->use != func_explosive_activate))
+ {
+ if (!self->health)
+ {
+ self->health = 100;
+ }
+
+ self->die = func_explosive_explode;
+ self->takedamage = DAMAGE_YES;
+ }
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40)
+ *
+ * Large exploding box. You can override its mass (100),
+ * health (80), and dmg (150).
+ */
+
+void
+barrel_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+
+{
+ float ratio;
+ vec3_t v;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if ((!other->groundentity) || (other->groundentity == self))
+ {
+ return;
+ }
+
+ ratio = (float)other->mass / (float)self->mass;
+ VectorSubtract(self->s.origin, other->s.origin, v);
+ M_walkmove(self, vectoyaw(v), 20 * ratio * FRAMETIME);
+}
+
+void
+barrel_explode(edict_t *self)
+{
+ vec3_t org;
+ float spd;
+ vec3_t save;
+
+ if (!self)
+ {
+ return;
+ }
+
+ T_RadiusDamage(self, self->activator, self->dmg,
+ NULL, self->dmg + 40, MOD_BARREL);
+
+ VectorCopy(self->s.origin, save);
+ VectorMA(self->absmin, 0.5, self->size, self->s.origin);
+
+ /* a few big chunks */
+ spd = 1.5 * (float)self->dmg / 200.0;
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);
+
+ /* bottom corners */
+ spd = 1.75 * (float)self->dmg / 200.0;
+ VectorCopy(self->absmin, org);
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+ VectorCopy(self->absmin, org);
+ org[0] += self->size[0];
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+ VectorCopy(self->absmin, org);
+ org[1] += self->size[1];
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+ VectorCopy(self->absmin, org);
+ org[0] += self->size[0];
+ org[1] += self->size[1];
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+
+ /* a bunch of little chunks */
+ spd = 2.0 * (float)self->dmg / 200.0;
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+
+ VectorCopy(save, self->s.origin);
+
+ if (self->groundentity)
+ {
+ BecomeExplosion2(self);
+ }
+ else
+ {
+ BecomeExplosion1(self);
+ }
+}
+
+void
+barrel_delay(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+ self->nextthink = level.time + 2 * FRAMETIME;
+ self->think = barrel_explode;
+ self->activator = attacker;
+}
+
+void
+barrel_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* the think needs to be first since later stuff may override. */
+ self->think = barrel_think;
+ self->nextthink = level.time + FRAMETIME;
+
+ M_CatagorizePosition(self);
+ self->flags |= FL_IMMUNE_SLIME;
+ self->air_finished = level.time + 100;
+ M_WorldEffects(self);
+}
+
+void
+barrel_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ M_droptofloor(self);
+ self->think = barrel_think;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+SP_misc_explobox(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ { /* auto-remove for deathmatch */
+ G_FreeEdict(self);
+ return;
+ }
+
+ gi.modelindex("models/objects/debris1/tris.md2");
+ gi.modelindex("models/objects/debris2/tris.md2");
+ gi.modelindex("models/objects/debris3/tris.md2");
+
+ self->solid = SOLID_BBOX;
+ self->movetype = MOVETYPE_STEP;
+
+ self->model = "models/objects/barrels/tris.md2";
+ self->s.modelindex = gi.modelindex(self->model);
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 40);
+
+ if (!self->mass)
+ {
+ self->mass = 400;
+ }
+
+ if (!self->health)
+ {
+ self->health = 10;
+ }
+
+ if (!self->dmg)
+ {
+ self->dmg = 150;
+ }
+
+ self->die = barrel_delay;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.aiflags = AI_NOSTEP;
+
+ self->touch = barrel_touch;
+ self->think = barrel_start;
+ self->nextthink = level.time + 2 * FRAMETIME;
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8)
+ */
+void
+misc_blackhole_use(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ G_FreeEdict(ent);
+}
+
+void
+misc_blackhole_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 19)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 0;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+SP_misc_blackhole(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_NOT;
+ VectorSet(ent->mins, -64, -64, 0);
+ VectorSet(ent->maxs, 64, 64, 8);
+ ent->s.modelindex = gi.modelindex("models/objects/black/tris.md2");
+ ent->s.renderfx = RF_TRANSLUCENT;
+ ent->use = misc_blackhole_use;
+ ent->think = misc_blackhole_think;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
+ */
+void
+misc_eastertank_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 293)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 254;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+SP_misc_eastertank(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, -16);
+ VectorSet(ent->maxs, 32, 32, 32);
+ ent->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
+ ent->s.frame = 254;
+ ent->think = misc_eastertank_think;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
+ */
+void
+misc_easterchick_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 247)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 208;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+SP_misc_easterchick(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, 0);
+ VectorSet(ent->maxs, 32, 32, 32);
+ ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
+ ent->s.frame = 208;
+ ent->think = misc_easterchick_think;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
+ */
+void
+misc_easterchick2_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 287)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 248;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+SP_misc_easterchick2(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, 0);
+ VectorSet(ent->maxs, 32, 32, 32);
+ ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
+ ent->s.frame = 248;
+ ent->think = misc_easterchick2_think;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48)
+ *
+ * Not really a monster, this is the Tank Commander's decapitated body.
+ * There should be a item_commander_head that has this as it's target.
+ */
+void
+commander_body_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 24)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->nextthink = 0;
+ }
+
+ if (self->s.frame == 22)
+ {
+ gi.sound(self, CHAN_BODY, gi.soundindex("tank/thud.wav"), 1, ATTN_NORM, 0);
+ }
+}
+
+void
+commander_body_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = commander_body_think;
+ self->nextthink = level.time + FRAMETIME;
+ gi.sound(self, CHAN_BODY, gi.soundindex("tank/pain.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+commander_body_drop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_TOSS;
+ self->s.origin[2] += 2;
+}
+
+void
+SP_monster_commander_body(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_BBOX;
+ self->model = "models/monsters/commandr/tris.md2";
+ self->s.modelindex = gi.modelindex(self->model);
+ VectorSet(self->mins, -32, -32, 0);
+ VectorSet(self->maxs, 32, 32, 48);
+ self->use = commander_body_use;
+ self->takedamage = DAMAGE_YES;
+ self->flags = FL_GODMODE;
+ self->s.renderfx |= RF_FRAMELERP;
+ gi.linkentity(self);
+
+ gi.soundindex("tank/thud.wav");
+ gi.soundindex("tank/pain.wav");
+
+ self->think = commander_body_drop;
+ self->nextthink = level.time + 5 * FRAMETIME;
+}
+
+/*
+ * QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4)
+ *
+ * The origin is the bottom of the banner.
+ * The banner is 128 tall.
+ */
+void
+misc_banner_think(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.frame = (ent->s.frame + 1) % 16;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+SP_misc_banner(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
+ ent->s.frame = rand() % 16;
+ gi.linkentity(ent);
+
+ ent->think = misc_banner_think;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+/*
+ * QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED
+ *
+ * This is the dead player model. Comes in 6 exciting different poses!
+ */
+void
+misc_deadsoldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health > -30)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM,
+ 0);
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+}
+
+void
+SP_misc_deadsoldier(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.modelindex = gi.modelindex("models/deadbods/dude/tris.md2");
+
+ /* Defaults to frame 0 */
+ if (ent->spawnflags & 2)
+ {
+ ent->s.frame = 1;
+ }
+ else if (ent->spawnflags & 4)
+ {
+ ent->s.frame = 2;
+ }
+ else if (ent->spawnflags & 8)
+ {
+ ent->s.frame = 3;
+ }
+ else if (ent->spawnflags & 16)
+ {
+ ent->s.frame = 4;
+ }
+ else if (ent->spawnflags & 32)
+ {
+ ent->s.frame = 5;
+ }
+ else
+ {
+ ent->s.frame = 0;
+ }
+
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 16);
+ ent->deadflag = DEAD_DEAD;
+ ent->takedamage = DAMAGE_YES;
+ ent->svflags |= SVF_MONSTER | SVF_DEADMONSTER;
+ ent->die = misc_deadsoldier_die;
+ ent->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+ gi.linkentity(ent);
+}
+
+extern void train_use(edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find(edict_t *self);
+
+/*
+ * QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32)
+ *
+ * This is the Viper for the flyby bombing.
+ * It is trigger_spawned, so you must have something use it for it to show up.
+ * There must be a path for it to follow once it is activated.
+ *
+ * "speed" How fast the Viper should fly
+ */
+void
+misc_viper_use(edict_t *self, edict_t *other, edict_t *activator)
+{
+ if (!self || !other || !activator)
+ {
+ return;
+ }
+
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = train_use;
+ train_use(self, other, activator);
+}
+
+void
+SP_misc_viper(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->target)
+ {
+ gi.dprintf("misc_viper without a target at %s\n", vtos(ent->absmin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 300;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/ships/viper/tris.md2");
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 32);
+
+ ent->think = func_train_find;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->use = misc_viper_use;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72)
+ *
+ * This is a large stationary viper as seen in Paul's intro
+ */
+void
+SP_misc_bigviper(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -176, -120, -24);
+ VectorSet(ent->maxs, 176, 120, 72);
+ ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2");
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8)
+ *
+ * "dmg" how much boom should the bomb make?
+ */
+void
+misc_viper_bomb_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_UseTargets(self, self->activator);
+
+ self->s.origin[2] = self->absmin[2] + 1;
+ T_RadiusDamage(self, self, self->dmg, NULL, self->dmg + 40, MOD_BOMB);
+ BecomeExplosion2(self);
+}
+
+void
+misc_viper_bomb_prethink(edict_t *self)
+{
+ vec3_t v;
+ float diff;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->groundentity = NULL;
+
+ diff = self->timestamp - level.time;
+
+ if (diff < -1.0)
+ {
+ diff = -1.0;
+ }
+
+ VectorScale(self->moveinfo.dir, 1.0 + diff, v);
+ v[2] = diff;
+
+ diff = self->s.angles[2];
+ vectoangles(v, self->s.angles);
+ self->s.angles[2] = diff + 10;
+}
+
+void
+misc_viper_bomb_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ edict_t *viper;
+
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BBOX;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->s.effects |= EF_ROCKET;
+ self->use = NULL;
+ self->movetype = MOVETYPE_TOSS;
+ self->prethink = misc_viper_bomb_prethink;
+ self->touch = misc_viper_bomb_touch;
+ self->activator = activator;
+
+ viper = G_Find(NULL, FOFS(classname), "misc_viper");
+ VectorScale(viper->moveinfo.dir, viper->moveinfo.speed, self->velocity);
+
+ self->timestamp = level.time;
+ VectorCopy(viper->moveinfo.dir, self->moveinfo.dir);
+}
+
+void
+SP_misc_viper_bomb(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_NOT;
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+
+ self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2");
+
+ if (!self->dmg)
+ {
+ self->dmg = 1000;
+ }
+
+ self->use = misc_viper_bomb_use;
+ self->svflags |= SVF_NOCLIENT;
+
+ gi.linkentity(self);
+}
+
+extern void train_use(edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find(edict_t *self);
+
+/*
+ * QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32)
+ *
+ * This is a Storgg ship for the flybys.
+ * It is trigger_spawned, so you must have something use it for it to show up.
+ * There must be a path for it to follow once it is activated.
+ *
+ * "speed" How fast it should fly
+ */
+void
+misc_strogg_ship_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = train_use;
+ train_use(self, other, activator);
+}
+
+void
+SP_misc_strogg_ship(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->target)
+ {
+ gi.dprintf("%s without a target at %s\n", ent->classname,
+ vtos(ent->absmin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 300;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2");
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 32);
+
+ ent->think = func_train_find;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->use = misc_strogg_ship_use;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
+ */
+void
+misc_satellite_dish_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.frame++;
+
+ if (self->s.frame < 38)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+misc_satellite_dish_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.frame = 0;
+ self->think = misc_satellite_dish_think;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+SP_misc_satellite_dish(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -64, -64, 0);
+ VectorSet(ent->maxs, 64, 64, 128);
+ ent->s.modelindex = gi.modelindex("models/objects/satellite/tris.md2");
+ ent->use = misc_satellite_dish_use;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
+ */
+void
+SP_light_mine1(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.modelindex =
+ gi.modelindex("models/objects/minelite/light1/tris.md2");
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
+ */
+void
+SP_light_mine2(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.modelindex =
+ gi.modelindex("models/objects/minelite/light2/tris.md2");
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8)
+ *
+ * Intended for use with the target_spawner
+ */
+void
+SP_misc_gib_arm(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/gibs/arm/tris.md2");
+ ent->solid = SOLID_BBOX;
+ ent->s.effects |= EF_GIB;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = gib_die;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->svflags |= SVF_MONSTER;
+ ent->deadflag = DEAD_DEAD;
+ ent->avelocity[0] = random() * 200;
+ ent->avelocity[1] = random() * 200;
+ ent->avelocity[2] = random() * 200;
+ ent->think = G_FreeEdict;
+ ent->nextthink = level.time + 30;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8)
+ * Intended for use with the target_spawner
+ */
+void
+SP_misc_gib_leg(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/gibs/leg/tris.md2");
+ ent->solid = SOLID_BBOX;
+ ent->s.effects |= EF_GIB;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = gib_die;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->svflags |= SVF_MONSTER;
+ ent->deadflag = DEAD_DEAD;
+ ent->avelocity[0] = random() * 200;
+ ent->avelocity[1] = random() * 200;
+ ent->avelocity[2] = random() * 200;
+ ent->think = G_FreeEdict;
+ ent->nextthink = level.time + 30;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8)
+ * Intended for use with the target_spawner
+ */
+void
+SP_misc_gib_head(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/gibs/head/tris.md2");
+ ent->solid = SOLID_BBOX;
+ ent->s.effects |= EF_GIB;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = gib_die;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->svflags |= SVF_MONSTER;
+ ent->deadflag = DEAD_DEAD;
+ ent->avelocity[0] = random() * 200;
+ ent->avelocity[1] = random() * 200;
+ ent->avelocity[2] = random() * 200;
+ ent->think = G_FreeEdict;
+ ent->nextthink = level.time + 30;
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED target_character (0 0 1) ?
+ *
+ * used with target_string (must be on same "team")
+ * "count" is position in the string (starts at 1)
+ */
+
+void
+SP_target_character(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+ self->solid = SOLID_BSP;
+ self->s.frame = 12;
+ gi.linkentity(self);
+ return;
+}
+
+/*
+ * QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
+ */
+void
+target_string_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ edict_t *e;
+ int n, l;
+ char c;
+
+ if (!self)
+ {
+ return;
+ }
+
+ l = strlen(self->message);
+
+ for (e = self->teammaster; e; e = e->teamchain)
+ {
+ if (!e->count)
+ {
+ continue;
+ }
+
+ n = e->count - 1;
+
+ if (n > l)
+ {
+ e->s.frame = 12;
+ continue;
+ }
+
+ c = self->message[n];
+
+ if ((c >= '0') && (c <= '9'))
+ {
+ e->s.frame = c - '0';
+ }
+ else if (c == '-')
+ {
+ e->s.frame = 10;
+ }
+ else if (c == ':')
+ {
+ e->s.frame = 11;
+ }
+ else
+ {
+ e->s.frame = 12;
+ }
+ }
+}
+
+void
+SP_target_string(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->message)
+ {
+ self->message = "";
+ }
+
+ self->use = target_string_use;
+}
+
+#define CLOCK_MESSAGE_SIZE 16
+
+/*
+ * QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE
+ *
+ * target a target_string with this
+ *
+ * The default is to be a time of day clock
+ *
+ * TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget"
+ * If START_OFF, this entity must be used before it starts
+ *
+ * "style" 0 "xx"
+ * 1 "xx:xx"
+ * 2 "xx:xx:xx"
+ */
+void
+func_clock_reset(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->activator = NULL;
+
+ if (self->spawnflags & 1)
+ {
+ self->health = 0;
+ self->wait = self->count;
+ }
+ else if (self->spawnflags & 2)
+ {
+ self->health = self->count;
+ self->wait = 0;
+ }
+}
+
+void
+func_clock_format_countdown(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->style == 0)
+ {
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health);
+ return;
+ }
+
+ if (self->style == 1)
+ {
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i",
+ self->health / 60, self->health % 60);
+
+ if (self->message[3] == ' ')
+ {
+ self->message[3] = '0';
+ }
+
+ return;
+ }
+
+ if (self->style == 2)
+ {
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i",
+ self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60,
+ self->health % 60);
+
+ if (self->message[3] == ' ')
+ {
+ self->message[3] = '0';
+ }
+
+ if (self->message[6] == ' ')
+ {
+ self->message[6] = '0';
+ }
+
+ return;
+ }
+}
+
+void
+func_clock_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!self->enemy)
+ {
+ return;
+ }
+ }
+
+ if (self->spawnflags & 1)
+ {
+ func_clock_format_countdown(self);
+ self->health++;
+ }
+ else if (self->spawnflags & 2)
+ {
+ func_clock_format_countdown(self);
+ self->health--;
+ }
+ else
+ {
+ struct tm *ltime;
+ time_t gmtime;
+
+ time(&gmtime);
+ ltime = localtime(&gmtime);
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i",
+ ltime->tm_hour, ltime->tm_min, ltime->tm_sec);
+
+ if (self->message[3] == ' ')
+ {
+ self->message[3] = '0';
+ }
+
+ if (self->message[6] == ' ')
+ {
+ self->message[6] = '0';
+ }
+ }
+
+ self->enemy->message = self->message;
+ self->enemy->use(self->enemy, self, self);
+
+ if (((self->spawnflags & 1) && (self->health > self->wait)) ||
+ ((self->spawnflags & 2) && (self->health < self->wait)))
+ {
+ if (self->pathtarget)
+ {
+ char *savetarget;
+ char *savemessage;
+
+ savetarget = self->target;
+ savemessage = self->message;
+ self->target = self->pathtarget;
+ self->message = NULL;
+ G_UseTargets(self, self->activator);
+ self->target = savetarget;
+ self->message = savemessage;
+ }
+
+ if (!(self->spawnflags & 8))
+ {
+ return;
+ }
+
+ func_clock_reset(self);
+
+ if (self->spawnflags & 4)
+ {
+ return;
+ }
+ }
+
+ self->nextthink = level.time + 1;
+}
+
+void
+func_clock_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (!(self->spawnflags & 8))
+ {
+ self->use = NULL;
+ }
+
+ if (self->activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+ self->think(self);
+}
+
+void
+SP_func_clock(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("%s with no target at %s\n", self->classname,
+ vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ if ((self->spawnflags & 2) && (!self->count))
+ {
+ gi.dprintf("%s with no count at %s\n", self->classname,
+ vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ if ((self->spawnflags & 1) && (!self->count))
+ {
+ self->count = 60 * 60;
+ }
+
+ func_clock_reset(self);
+
+ self->message = gi.TagMalloc(CLOCK_MESSAGE_SIZE, TAG_LEVEL);
+
+ self->think = func_clock_think;
+
+ if (self->spawnflags & 4)
+ {
+ self->use = func_clock_use;
+ }
+ else
+ {
+ self->nextthink = level.time + 1;
+ }
+}
+
+/* ================================================================================= */
+
+void
+teleporter_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ edict_t *dest;
+ int i;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ dest = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!dest)
+ {
+ gi.dprintf("Couldn't find destination\n");
+ return;
+ }
+
+ /* unlink to make sure it can't possibly interfere with KillBox */
+ gi.unlinkentity(other);
+
+ VectorCopy(dest->s.origin, other->s.origin);
+ VectorCopy(dest->s.origin, other->s.old_origin);
+ other->s.origin[2] += 10;
+
+ /* clear the velocity and hold them in place briefly */
+ VectorClear(other->velocity);
+ other->client->ps.pmove.pm_time = 160 >> 3; /* hold time */
+ other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+ /* draw the teleport splash at source and on the player */
+ self->owner->s.event = EV_PLAYER_TELEPORT;
+ other->s.event = EV_PLAYER_TELEPORT;
+
+ /* set angles */
+ for (i = 0; i < 3; i++)
+ {
+ other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
+ dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+ }
+
+ VectorClear(other->s.angles);
+ VectorClear(other->client->ps.viewangles);
+ VectorClear(other->client->v_angle);
+
+ /* kill anything at the destination */
+ KillBox(other);
+
+ gi.linkentity(other);
+}
+
+/*
+ * QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
+ *
+ * Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
+ */
+void
+SP_misc_teleporter(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ edict_t *trig;
+
+ if (!ent->target)
+ {
+ gi.dprintf("teleporter without a target.\n");
+ G_FreeEdict(ent);
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/dmspot/tris.md2");
+ ent->s.skinnum = 1;
+ ent->s.effects = EF_TELEPORTER;
+ ent->s.sound = gi.soundindex("world/amb10.wav");
+ ent->solid = SOLID_BBOX;
+
+ VectorSet(ent->mins, -32, -32, -24);
+ VectorSet(ent->maxs, 32, 32, -16);
+ gi.linkentity(ent);
+
+ trig = G_Spawn();
+ trig->touch = teleporter_touch;
+ trig->solid = SOLID_TRIGGER;
+ trig->target = ent->target;
+ trig->owner = ent;
+ VectorCopy(ent->s.origin, trig->s.origin);
+ VectorSet(trig->mins, -8, -8, 8);
+ VectorSet(trig->maxs, 8, 8, 24);
+ gi.linkentity(trig);
+}
+
+/*
+ * QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
+ *
+ * Point teleporters at these.
+ */
+void
+SP_misc_teleporter_dest(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/dmspot/tris.md2");
+ ent->s.skinnum = 0;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, -24);
+ VectorSet(ent->maxs, 32, 32, -16);
+ gi.linkentity(ent);
+}
+
+void
+misc_nuke_core_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->svflags & SVF_NOCLIENT)
+ {
+ self->svflags &= ~SVF_NOCLIENT;
+ }
+ else
+ {
+ self->svflags |= SVF_NOCLIENT;
+ }
+}
+
+/*
+ * QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16)
+ *
+ * toggles visible/not visible. starts visible.
+ */
+void
+SP_misc_nuke_core(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/core/tris.md2");
+ gi.linkentity(ent);
+
+ ent->use = misc_nuke_core_use;
+}
diff --git a/rogue/src/g_monster.c b/rogue/src/g_monster.c
new file mode 100644
index 0000000..d7c82b8
--- /dev/null
+++ b/rogue/src/g_monster.c
@@ -0,0 +1,1307 @@
+/*
+ * =======================================================================
+ *
+ * Monster utility functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+void
+monster_fire_bullet(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int kick, int hspread, int vspread, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int count, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_shotgun(self, start, aimdir, damage, kick, hspread, vspread,
+ count, MOD_UNKNOWN);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed,
+ int flashtype, int effect)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_blaster(self, start, dir, damage, speed, effect, false);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_blaster2(self, start, dir, damage, speed, effect, false);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_tracker(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, edict_t *enemy, int flashtype)
+{
+ if (!self || !enemy)
+ {
+ return;
+ }
+
+ fire_tracker(self, start, dir, damage, speed, enemy);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, vec3_t offset,
+ int damage, int kick, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_heat(self, start, dir, offset, damage, kick, true);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir,
+ int damage, int speed, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_grenade(self, start, aimdir, damage, speed, 2.5, damage + 40);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir,
+ int damage, int speed, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_rocket(self, start, dir, damage, speed, damage + 20, damage);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir,
+ int damage, int kick, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(gi.pointcontents(start) & MASK_SOLID))
+ {
+ fire_rail(self, start, aimdir, damage, kick);
+ }
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir,
+ int damage, int speed, int kick, float damage_radius,
+ int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_bfg(self, start, aimdir, damage, speed, damage_radius);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+M_FliesOff(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.effects &= ~EF_FLIES;
+ self->s.sound = 0;
+}
+
+void
+M_FliesOn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ return;
+ }
+
+ self->s.effects |= EF_FLIES;
+ self->s.sound = gi.soundindex("infantry/inflies1.wav");
+ self->think = M_FliesOff;
+ self->nextthink = level.time + 60;
+}
+
+void
+M_FlyCheck(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ return;
+ }
+
+ if (random() > 0.5)
+ {
+ return;
+ }
+
+ self->think = M_FliesOn;
+ self->nextthink = level.time + 5 + 10 * random();
+}
+
+void
+AttackFinished(edict_t *self, float time)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.attack_finished = level.time + time;
+}
+
+void
+M_CheckGround(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ vec3_t point;
+ trace_t trace;
+
+ if (ent->flags & (FL_SWIM | FL_FLY))
+ {
+ return;
+ }
+
+ if ((ent->velocity[2] * ent->gravityVector[2]) < -100)
+ {
+ ent->groundentity = NULL;
+ return;
+ }
+
+ /* if the hull point one-quarter unit down is solid the entity is on ground */
+ point[0] = ent->s.origin[0];
+ point[1] = ent->s.origin[1];
+ point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]);
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ point, ent, MASK_MONSTERSOLID);
+
+ /* check steepness */
+ if (ent->gravityVector[2] < 0) /* normal gravity */
+ {
+ if ((trace.plane.normal[2] < 0.7) && !trace.startsolid)
+ {
+ ent->groundentity = NULL;
+ return;
+ }
+ }
+ else /* inverted gravity */
+ {
+ if ((trace.plane.normal[2] > -0.7) && !trace.startsolid)
+ {
+ ent->groundentity = NULL;
+ return;
+ }
+ }
+
+ if (!trace.startsolid && !trace.allsolid)
+ {
+ VectorCopy(trace.endpos, ent->s.origin);
+ ent->groundentity = trace.ent;
+ ent->groundentity_linkcount = trace.ent->linkcount;
+ ent->velocity[2] = 0;
+ }
+}
+
+void
+M_CatagorizePosition(edict_t *ent)
+{
+ vec3_t point;
+ int cont;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* get waterlevel */
+ point[0] = (ent->absmax[0] + ent->absmin[0])/2;
+ point[1] = (ent->absmax[1] + ent->absmin[1])/2;
+ point[2] = ent->absmin[2] + 2;
+ cont = gi.pointcontents(point);
+
+ if (!(cont & MASK_WATER))
+ {
+ ent->waterlevel = 0;
+ ent->watertype = 0;
+ return;
+ }
+
+ ent->watertype = cont;
+ ent->waterlevel = 1;
+ point[2] += 26;
+ cont = gi.pointcontents(point);
+
+ if (!(cont & MASK_WATER))
+ {
+ return;
+ }
+
+ ent->waterlevel = 2;
+ point[2] += 22;
+ cont = gi.pointcontents(point);
+
+ if (cont & MASK_WATER)
+ {
+ ent->waterlevel = 3;
+ }
+}
+
+void
+M_WorldEffects(edict_t *ent)
+{
+ int dmg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->health > 0)
+ {
+ if (!(ent->flags & FL_SWIM))
+ {
+ if (ent->waterlevel < 3)
+ {
+ ent->air_finished = level.time + 12;
+ }
+ else if (ent->air_finished < level.time)
+ {
+ /* drown! */
+ if (ent->pain_debounce_time < level.time)
+ {
+ dmg = 2 + 2 * floor(level.time - ent->air_finished);
+
+ if (dmg > 15)
+ {
+ dmg = 15;
+ }
+
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin,
+ vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+ ent->pain_debounce_time = level.time + 1;
+ }
+ }
+ }
+ else
+ {
+ if (ent->waterlevel > 0)
+ {
+ ent->air_finished = level.time + 9;
+ }
+ else if (ent->air_finished < level.time)
+ {
+ /* suffocate! */
+ if (ent->pain_debounce_time < level.time)
+ {
+ dmg = 2 + 2 * floor(level.time - ent->air_finished);
+
+ if (dmg > 15)
+ {
+ dmg = 15;
+ }
+
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin,
+ vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+ ent->pain_debounce_time = level.time + 1;
+ }
+ }
+ }
+ }
+
+ if (ent->waterlevel == 0)
+ {
+ if (ent->flags & FL_INWATER)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+ ent->flags &= ~FL_INWATER;
+ }
+
+ return;
+ }
+
+ if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
+ {
+ if (ent->damage_debounce_time < level.time)
+ {
+ ent->damage_debounce_time = level.time + 0.2;
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin,
+ 10 * ent->waterlevel, 0, 0, MOD_LAVA);
+ }
+ }
+
+ if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
+ {
+ if (ent->damage_debounce_time < level.time)
+ {
+ ent->damage_debounce_time = level.time + 1;
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin,
+ vec3_origin, 4 * ent->waterlevel, 0, 0, MOD_SLIME);
+ }
+ }
+
+ if (!(ent->flags & FL_INWATER))
+ {
+ if (!(ent->svflags & SVF_DEADMONSTER))
+ {
+ if (ent->watertype & CONTENTS_LAVA)
+ {
+ if (random() <= 0.5)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+ else if (ent->watertype & CONTENTS_SLIME)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->watertype & CONTENTS_WATER)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+
+ ent->flags |= FL_INWATER;
+ ent->damage_debounce_time = 0;
+ }
+}
+
+void
+M_droptofloor(edict_t *ent)
+{
+ vec3_t end;
+ trace_t trace;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->gravityVector[2] < 0)
+ {
+ ent->s.origin[2] += 1;
+ VectorCopy(ent->s.origin, end);
+ end[2] -= 256;
+ }
+ else
+ {
+ ent->s.origin[2] -= 1;
+ VectorCopy(ent->s.origin, end);
+ end[2] += 256;
+ }
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ end, ent, MASK_MONSTERSOLID);
+
+ if ((trace.fraction == 1) || trace.allsolid)
+ {
+ return;
+ }
+
+ VectorCopy(trace.endpos, ent->s.origin);
+
+ gi.linkentity(ent);
+ M_CheckGround(ent);
+ M_CatagorizePosition(ent);
+}
+
+void
+M_SetEffects(edict_t *ent)
+{
+ int remaining;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN | EF_DOUBLE | EF_QUAD | EF_PENT);
+ ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE | RF_SHELL_DOUBLE);
+
+ if (ent->monsterinfo.aiflags & AI_RESURRECTING)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= RF_SHELL_RED;
+ }
+
+ if (ent->health <= 0)
+ {
+ return;
+ }
+
+ if (ent->powerarmor_time > level.time)
+ {
+ if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
+ {
+ ent->s.effects |= EF_POWERSCREEN;
+ }
+ else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= RF_SHELL_GREEN;
+ }
+ }
+
+ if (ent->monsterinfo.quad_framenum > level.framenum)
+ {
+ remaining = ent->monsterinfo.quad_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_QUAD;
+ }
+ }
+ else
+ {
+ ent->s.effects &= ~EF_QUAD;
+ }
+
+ if (ent->monsterinfo.double_framenum > level.framenum)
+ {
+ remaining = ent->monsterinfo.double_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_DOUBLE;
+ }
+ }
+ else
+ {
+ ent->s.effects &= ~EF_DOUBLE;
+ }
+
+ if (ent->monsterinfo.invincible_framenum > level.framenum)
+ {
+ remaining = ent->monsterinfo.invincible_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_PENT;
+ }
+ }
+ else
+ {
+ ent->s.effects &= ~EF_PENT;
+ }
+}
+
+void
+M_MoveFrame(edict_t *self)
+{
+ mmove_t *move;
+ int index;
+
+ if (!self)
+ {
+ return;
+ }
+
+ move = self->monsterinfo.currentmove;
+ self->nextthink = level.time + FRAMETIME;
+
+ if ((self->monsterinfo.nextframe) &&
+ (self->monsterinfo.nextframe >= move->firstframe) &&
+ (self->monsterinfo.nextframe <= move->lastframe))
+ {
+ if (self->s.frame != self->monsterinfo.nextframe)
+ {
+ self->s.frame = self->monsterinfo.nextframe;
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+
+ self->monsterinfo.nextframe = 0;
+ }
+ else
+ {
+ /* prevent nextframe from leaking into a future move */
+ self->monsterinfo.nextframe = 0;
+
+ if (self->s.frame == move->lastframe)
+ {
+ if (move->endfunc)
+ {
+ move->endfunc(self);
+
+ /* regrab move, endfunc is very likely to change it */
+ move = self->monsterinfo.currentmove;
+
+ /* check for death */
+ if (self->svflags & SVF_DEADMONSTER)
+ {
+ return;
+ }
+ }
+ }
+
+ if ((self->s.frame < move->firstframe) ||
+ (self->s.frame > move->lastframe))
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ self->s.frame = move->firstframe;
+ }
+ else
+ {
+ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+ {
+ self->s.frame++;
+
+ if (self->s.frame > move->lastframe)
+ {
+ self->s.frame = move->firstframe;
+ }
+ }
+ }
+ }
+
+ index = self->s.frame - move->firstframe;
+
+ if (move->frame[index].aifunc)
+ {
+ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+ {
+ move->frame[index].aifunc(self, move->frame[index].dist * self->monsterinfo.scale);
+ }
+ else
+ {
+ move->frame[index].aifunc(self, 0);
+ }
+ }
+
+ if (move->frame[index].thinkfunc)
+ {
+ move->frame[index].thinkfunc(self);
+ }
+}
+
+void
+monster_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ M_MoveFrame(self);
+
+ if (self->linkcount != self->monsterinfo.linkcount)
+ {
+ self->monsterinfo.linkcount = self->linkcount;
+ M_CheckGround(self);
+ }
+
+ M_CatagorizePosition(self);
+ M_WorldEffects(self);
+ M_SetEffects(self);
+}
+
+/*
+ * Using a monster makes it angry at the current activator
+ */
+void
+monster_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ if (self->health <= 0)
+ {
+ return;
+ }
+
+ if (activator->flags & FL_NOTARGET)
+ {
+ return;
+ }
+
+ if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ return;
+ }
+
+ if (activator->flags & FL_DISGUISED)
+ {
+ return;
+ }
+
+ /* delay reaction so if the monster is teleported,
+ its sound is still heard */
+ self->enemy = activator;
+ FoundTarget(self);
+}
+
+void monster_start_go(edict_t *self);
+
+void
+monster_triggered_spawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.origin[2] += 1;
+ KillBox(self);
+
+ self->solid = SOLID_BBOX;
+ self->movetype = MOVETYPE_STEP;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->air_finished = level.time + 12;
+ gi.linkentity(self);
+
+ monster_start_go(self);
+
+ if (self->enemy && !(self->spawnflags & 1) &&
+ !(self->enemy->flags & FL_NOTARGET))
+ {
+ if (!(self->enemy->flags & FL_DISGUISED))
+ {
+ FoundTarget(self);
+ }
+ else
+ {
+ self->enemy = NULL;
+ }
+ }
+ else
+ {
+ self->enemy = NULL;
+ }
+}
+
+void
+monster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ /* we have a one frame delay here so we don't
+ telefrag the guy who activated us */
+ self->think = monster_triggered_spawn;
+ self->nextthink = level.time + FRAMETIME;
+
+ if (activator->client)
+ {
+ self->enemy = activator;
+ }
+
+ self->use = monster_use;
+}
+
+void
+monster_triggered_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_NOT;
+ self->movetype = MOVETYPE_NONE;
+ self->svflags |= SVF_NOCLIENT;
+ self->nextthink = 0;
+ self->use = monster_triggered_spawn_use;
+}
+
+/*
+ * When a monster dies, it fires all of its
+ * targets with the current enemy as activator.
+ */
+void
+monster_death_use(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags &= ~(FL_FLY | FL_SWIM);
+ self->monsterinfo.aiflags &= AI_GOOD_GUY;
+
+ if (self->item)
+ {
+ Drop_Item(self, self->item);
+ self->item = NULL;
+ }
+
+ if (self->deathtarget)
+ {
+ self->target = self->deathtarget;
+ }
+
+ if (!self->target)
+ {
+ return;
+ }
+
+ G_UseTargets(self, self->enemy);
+}
+
+/* ============================================================================ */
+
+qboolean
+monster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return false;
+ }
+
+ if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ self->spawnflags &= ~4;
+ self->spawnflags |= 1;
+ }
+
+ if ((self->spawnflags & 2) && !self->targetname)
+ {
+ if (g_fix_triggered->value)
+ {
+ self->spawnflags &= ~2;
+ }
+
+ gi.dprintf ("triggered %s at %s has no targetname\n", self->classname, vtos (self->s.origin));
+ }
+
+ if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) &&
+ (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
+ {
+ level.total_monsters++;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+ self->svflags |= SVF_MONSTER;
+ self->s.renderfx |= RF_FRAMELERP;
+ self->takedamage = DAMAGE_AIM;
+ self->air_finished = level.time + 12;
+ self->use = monster_use;
+
+ if(!self->max_health)
+ {
+ self->max_health = self->health;
+ }
+
+ self->clipmask = MASK_MONSTERSOLID;
+
+ self->s.skinnum = 0;
+ self->deadflag = DEAD_NO;
+ self->svflags &= ~SVF_DEADMONSTER;
+
+ if (!self->monsterinfo.checkattack)
+ {
+ self->monsterinfo.checkattack = M_CheckAttack;
+ }
+
+ VectorCopy(self->s.origin, self->s.old_origin);
+
+ if (st.item)
+ {
+ self->item = FindItemByClassname(st.item);
+
+ if (!self->item)
+ {
+ gi.dprintf("%s at %s has bad item: %s\n", self->classname,
+ vtos(self->s.origin), st.item);
+ }
+ }
+
+ /* randomize what frame they start on */
+ if (self->monsterinfo.currentmove)
+ {
+ self->s.frame = self->monsterinfo.currentmove->firstframe +
+ (rand() % (self->monsterinfo.currentmove->lastframe -
+ self->monsterinfo.currentmove->firstframe + 1));
+ }
+
+ self->monsterinfo.base_height = self->maxs[2];
+ self->monsterinfo.quad_framenum = 0;
+ self->monsterinfo.double_framenum = 0;
+ self->monsterinfo.invincible_framenum = 0;
+
+ return true;
+}
+
+void
+monster_start_go(edict_t *self)
+{
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= 0)
+ {
+ return;
+ }
+
+ /* check for target to combat_point and change to combattarget */
+ if (self->target)
+ {
+ qboolean notcombat;
+ qboolean fixup;
+ edict_t *target;
+
+ target = NULL;
+ notcombat = false;
+ fixup = false; while ((target = G_Find(target, FOFS(targetname), self->target)) != NULL)
+ {
+ if (strcmp(target->classname, "point_combat") == 0)
+ {
+ self->combattarget = self->target;
+ fixup = true;
+ }
+ else
+ {
+ notcombat = true;
+ }
+ }
+
+ if (notcombat && self->combattarget)
+ {
+ gi.dprintf("%s at %s has target with mixed types\n",
+ self->classname, vtos(self->s.origin));
+ }
+
+ if (fixup)
+ {
+ self->target = NULL;
+ }
+ }
+
+ /* validate combattarget */
+ if (self->combattarget)
+ {
+ edict_t *target;
+
+ target = NULL;
+
+ while ((target = G_Find(target, FOFS(targetname),
+ self->combattarget)) != NULL)
+ {
+ if (strcmp(target->classname, "point_combat") != 0)
+ {
+ gi.dprintf( "%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
+ self->classname, (int)self->s.origin[0], (int)self->s.origin[1],
+ (int)self->s.origin[2], self->combattarget, target->classname,
+ (int)target->s.origin[0], (int)target->s.origin[1], (int)target->s.origin[2]);
+ }
+ }
+ }
+
+ if (self->target)
+ {
+ self->goalentity = self->movetarget = G_PickTarget(self->target);
+
+ if (!self->movetarget)
+ {
+ gi.dprintf("%s can't find target %s at %s\n", self->classname,
+ self->target, vtos(self->s.origin));
+ self->target = NULL;
+ self->monsterinfo.pausetime = 100000000;
+ self->monsterinfo.stand(self);
+ }
+ else if (strcmp(self->movetarget->classname, "path_corner") == 0)
+ {
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
+ self->monsterinfo.walk(self);
+ self->target = NULL;
+ }
+ else
+ {
+ self->goalentity = self->movetarget = NULL;
+ self->monsterinfo.pausetime = 100000000;
+ self->monsterinfo.stand(self);
+ }
+ }
+ else
+ {
+ self->monsterinfo.pausetime = 100000000;
+ self->monsterinfo.stand(self);
+ }
+
+ self->think = monster_think;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+walkmonster_start_go(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->spawnflags & 2) && (level.time < 1))
+ {
+ M_droptofloor(self);
+
+ if (self->groundentity)
+ {
+ if (!M_walkmove(self, 0, 0))
+ {
+ gi.dprintf("%s in solid at %s\n", self->classname,
+ vtos(self->s.origin));
+ }
+ }
+ }
+
+ if (!self->yaw_speed)
+ {
+ self->yaw_speed = 20;
+ }
+
+ if (!self->viewheight)
+ {
+ self->viewheight = 25;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ monster_triggered_start(self);
+ }
+ else
+ {
+ monster_start_go(self);
+ }
+}
+
+void
+walkmonster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = walkmonster_start_go;
+ monster_start(self);
+}
+
+void
+flymonster_start_go(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!M_walkmove(self, 0, 0))
+ {
+ gi.dprintf("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+ }
+
+ if (!self->yaw_speed)
+ {
+ self->yaw_speed = 10;
+ }
+
+ if (!self->viewheight)
+ {
+ self->viewheight = 25;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ monster_triggered_start(self);
+ }
+ else
+ {
+ monster_start_go(self);
+ }
+}
+
+void
+flymonster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags |= FL_FLY;
+ self->think = flymonster_start_go;
+ monster_start(self);
+}
+
+void
+swimmonster_start_go(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->yaw_speed)
+ {
+ self->yaw_speed = 10;
+ }
+
+ if (!self->viewheight)
+ {
+ self->viewheight = 10;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ monster_triggered_start(self);
+ }
+ else
+ {
+ monster_start_go(self);
+ }
+}
+
+void
+swimmonster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags |= FL_SWIM;
+ self->think = swimmonster_start_go;
+ monster_start(self);
+}
+
+void stationarymonster_start_go(edict_t *self);
+
+void
+stationarymonster_triggered_spawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ KillBox(self);
+
+ self->solid = SOLID_BBOX;
+ self->movetype = MOVETYPE_NONE;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->air_finished = level.time + 12;
+ gi.linkentity(self);
+
+ monster_start_go(self);
+
+ if (self->enemy && !(self->spawnflags & 1) &&
+ !(self->enemy->flags & FL_NOTARGET))
+ {
+ if (!(self->enemy->flags & FL_DISGUISED))
+ {
+ FoundTarget(self);
+ }
+ else
+ {
+ self->enemy = NULL;
+ }
+ }
+ else
+ {
+ self->enemy = NULL;
+ }
+}
+
+void
+stationarymonster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ /* we have a one frame delay here so we don't telefrag the guy who activated us */
+ self->think = stationarymonster_triggered_spawn;
+ self->nextthink = level.time + FRAMETIME;
+
+ if (activator->client)
+ {
+ self->enemy = activator;
+ }
+
+ self->use = monster_use;
+}
+
+void
+stationarymonster_triggered_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_NOT;
+ self->movetype = MOVETYPE_NONE;
+ self->svflags |= SVF_NOCLIENT;
+ self->nextthink = 0;
+ self->use = stationarymonster_triggered_spawn_use;
+}
+
+void
+stationarymonster_start_go(edict_t *self)
+{
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->yaw_speed)
+ {
+ self->yaw_speed = 20;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ stationarymonster_triggered_start(self);
+ }
+ else
+ {
+ monster_start_go(self);
+ }
+}
+
+void
+stationarymonster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = stationarymonster_start_go;
+ monster_start(self);
+}
+
+void
+monster_done_dodge(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DODGING;
+}
diff --git a/rogue/src/g_newai.c b/rogue/src/g_newai.c
new file mode 100644
index 0000000..f832937
--- /dev/null
+++ b/rogue/src/g_newai.c
@@ -0,0 +1,1793 @@
+/*
+ * =======================================================================
+ *
+ * Rogue specific AI code
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define STATE_TOP 0
+#define STATE_BOTTOM 1
+#define STATE_UP 2
+#define STATE_DOWN 3
+
+#define HINT_ENDPOINT 0x0001
+#define MAX_HINT_CHAINS 100
+
+#define TESLA_DAMAGE_RADIUS 128
+
+edict_t *hint_path_start[MAX_HINT_CHAINS];
+int hint_paths_present;
+int num_hint_paths;
+
+qboolean face_wall(edict_t *self);
+qboolean monsterlost_checkhint2(edict_t *self);
+void HuntTarget(edict_t *self);
+
+qboolean
+blocked_checkplat(edict_t *self, float dist)
+{
+ int playerPosition;
+ trace_t trace;
+ vec3_t pt1, pt2;
+ vec3_t forward;
+ edict_t *plat;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ /* check player's relative altitude */
+ if (self->enemy->absmin[2] >= self->absmax[2])
+ {
+ playerPosition = 1;
+ }
+ else if (self->enemy->absmax[2] <= self->absmin[2])
+ {
+ playerPosition = -1;
+ }
+ else
+ {
+ playerPosition = 0;
+ }
+
+ /* if we're close to the same position, don't bother trying plats. */
+ if (playerPosition == 0)
+ {
+ return false;
+ }
+
+ plat = NULL;
+
+ /* see if we're already standing on a plat. */
+ if (self->groundentity && (self->groundentity != world))
+ {
+ if (!strncmp(self->groundentity->classname, "func_plat", 8))
+ {
+ plat = self->groundentity;
+ }
+ }
+
+ /* if we're not, check to see if we'll step onto one with this move */
+ if (!plat)
+ {
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ VectorMA(self->s.origin, dist, forward, pt1);
+ VectorCopy(pt1, pt2);
+ pt2[2] -= 384;
+
+ trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2,
+ self, MASK_MONSTERSOLID);
+
+ if ((trace.fraction < 1) && !trace.allsolid && !trace.startsolid)
+ {
+ if (!strncmp(trace.ent->classname, "func_plat", 8))
+ {
+ plat = trace.ent;
+ }
+ }
+ }
+
+ /* if we've found a plat, trigger it. */
+ if (plat && plat->use)
+ {
+ if (playerPosition == 1)
+ {
+ if (((self->groundentity == plat) &&
+ (plat->moveinfo.state == STATE_BOTTOM)) ||
+ ((self->groundentity != plat) &&
+ (plat->moveinfo.state == STATE_TOP)))
+ {
+ plat->use(plat, self, self);
+ return true;
+ }
+ }
+ else if (playerPosition == -1)
+ {
+ if (((self->groundentity == plat) &&
+ (plat->moveinfo.state == STATE_TOP)) ||
+ ((self->groundentity != plat) &&
+ (plat->moveinfo.state == STATE_BOTTOM)))
+ {
+ plat->use(plat, self, self);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+qboolean
+blocked_checkjump(edict_t *self, float dist, float maxDown, float maxUp)
+{
+ int playerPosition;
+ trace_t trace;
+ vec3_t pt1, pt2;
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+
+ if (self->enemy->absmin[2] > (self->absmin[2] + 16))
+ {
+ playerPosition = 1;
+ }
+ else if (self->enemy->absmin[2] < (self->absmin[2] - 16))
+ {
+ playerPosition = -1;
+ }
+ else
+ {
+ playerPosition = 0;
+ }
+
+ if ((playerPosition == -1) && maxDown)
+ {
+ /* check to make sure we can even get to the spot we're going to "fall" from */
+ VectorMA(self->s.origin, 48, forward, pt1);
+ trace = gi.trace(self->s.origin, self->mins, self->maxs, pt1,
+ self, MASK_MONSTERSOLID);
+
+ if (trace.fraction < 1)
+ {
+ return false;
+ }
+
+ VectorCopy(pt1, pt2);
+ pt2[2] = self->absmin[2] - maxDown - 1;
+
+ trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self,
+ MASK_MONSTERSOLID | MASK_WATER);
+
+ if ((trace.fraction < 1) && !trace.allsolid && !trace.startsolid)
+ {
+ if (((self->absmin[2] - trace.endpos[2]) >=
+ 24) && (trace.contents & MASK_SOLID))
+ {
+ if ((self->enemy->absmin[2] - trace.endpos[2]) > 32)
+ {
+ return false;
+ }
+
+ if (trace.plane.normal[2] < 0.9)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+ else if ((playerPosition == 1) && maxUp)
+ {
+ VectorMA(self->s.origin, 48, forward, pt1);
+ VectorCopy(pt1, pt2);
+ pt1[2] = self->absmax[2] + maxUp;
+
+ trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self,
+ MASK_MONSTERSOLID | MASK_WATER);
+
+ if ((trace.fraction < 1) && !trace.allsolid && !trace.startsolid)
+ {
+ if (((trace.endpos[2] - self->absmin[2]) <= maxUp) &&
+ trace.contents & MASK_SOLID)
+ {
+ face_wall(self);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+qboolean
+blocked_checknewenemy(edict_t *self)
+{
+ return false;
+}
+
+edict_t *
+hintpath_findstart(edict_t *ent)
+{
+ edict_t *e;
+ edict_t *last;
+ int field;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ if (ent->target) /* starting point */
+ {
+ last = world;
+ field = FOFS(targetname);
+ e = G_Find(NULL, field, ent->target);
+
+ while (e)
+ {
+ last = e;
+
+ if (!e->target)
+ {
+ break;
+ }
+
+ e = G_Find(NULL, field, e->target);
+ }
+ }
+ else /* end point */
+ {
+ last = world;
+ field = FOFS(target);
+ e = G_Find(NULL, field, ent->targetname);
+
+ while (e)
+ {
+ last = e;
+
+ if (!e->targetname)
+ {
+ break;
+ }
+
+ e = G_Find(NULL, field, e->targetname);
+ }
+ }
+
+ if (!(last->spawnflags & HINT_ENDPOINT))
+ {
+ return NULL;
+ }
+
+ if (last == world)
+ {
+ last = NULL;
+ }
+
+ return last;
+}
+
+edict_t *
+hintpath_other_end(edict_t *ent)
+{
+ edict_t *e;
+ edict_t *last;
+ int field;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ if (ent->target) /* starting point */
+ {
+ last = world;
+ field = FOFS(targetname);
+ e = G_Find(NULL, field, ent->target);
+
+ while (e)
+ {
+ last = e;
+
+ if (!e->target)
+ {
+ break;
+ }
+
+ e = G_Find(NULL, field, e->target);
+ }
+ }
+ else /* end point */
+ {
+ last = world;
+ field = FOFS(target);
+ e = G_Find(NULL, field, ent->targetname);
+
+ while (e)
+ {
+ last = e;
+
+ if (!e->targetname)
+ {
+ break;
+ }
+
+ e = G_Find(NULL, field, e->targetname);
+ }
+ }
+
+ if (!(last->spawnflags & HINT_ENDPOINT))
+ {
+ return NULL;
+ }
+
+ if (last == world)
+ {
+ last = NULL;
+ }
+
+ return last;
+}
+
+void
+hintpath_go(edict_t *self, edict_t *point)
+{
+ vec3_t dir;
+ vec3_t angles;
+
+ if (!self || !point)
+ {
+ return;
+ }
+
+ VectorSubtract(point->s.origin, self->s.origin, dir);
+ vectoangles2(dir, angles);
+
+ self->ideal_yaw = angles[YAW];
+ self->goalentity = self->movetarget = point;
+ self->monsterinfo.pausetime = 0;
+ self->monsterinfo.aiflags |= AI_HINT_PATH;
+ self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_PURSUIT_LAST_SEEN | AI_PURSUE_NEXT | AI_PURSUE_TEMP);
+
+ /* run for it */
+ self->monsterinfo.search_time = level.time;
+ self->monsterinfo.run(self);
+}
+
+void
+hintpath_stop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->goalentity = NULL;
+ self->movetarget = NULL;
+ self->monsterinfo.last_hint_time = level.time;
+ self->monsterinfo.goal_hint = NULL;
+ self->monsterinfo.aiflags &= ~AI_HINT_PATH;
+
+ if (has_valid_enemy(self))
+ {
+ /* if we can see our target, go nuts */
+ if (visible(self, self->enemy))
+ {
+ FoundTarget(self);
+ return;
+ }
+
+ /* otherwise, keep chasing */
+ HuntTarget(self);
+ return;
+ }
+
+ /* if our enemy is no longer valid, forget about our enemy and go into stand */
+ self->enemy = NULL;
+
+ /* we need the pausetime otherwise the stand code
+ will just revert to walking with no target and
+ the monsters will wonder around aimlessly trying
+ to hunt the world entity */
+ self->monsterinfo.pausetime = level.time + 100000000;
+ self->monsterinfo.stand(self);
+}
+
+qboolean
+monsterlost_checkhint(edict_t *self)
+{
+ edict_t *e, *monster_pathchain, *target_pathchain;
+ edict_t *checkpoint = NULL;
+ edict_t *closest;
+ float closest_range = 1000000;
+ edict_t *start, *destination;
+ int count1 = 0, count2 = 0, count4 = 0, count5 = 0;
+ float r;
+ int i;
+ qboolean hint_path_represented[MAX_HINT_CHAINS];
+
+ if (!self)
+ {
+ return false;
+ }
+
+ /* if there are no hint paths on this map, exit immediately. */
+ if (!hint_paths_present)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ return false;
+ }
+
+ if (!strcmp(self->classname, "monster_turret"))
+ {
+ return false;
+ }
+
+ monster_pathchain = NULL;
+
+ /* find all the hint_paths. */
+ for (i = 0; i < num_hint_paths; i++)
+ {
+ e = hint_path_start[i];
+
+ while (e)
+ {
+ count1++;
+
+ if (e->monster_hint_chain)
+ {
+ e->monster_hint_chain = NULL;
+ }
+
+ if (monster_pathchain)
+ {
+ checkpoint->monster_hint_chain = e;
+ checkpoint = e;
+ }
+ else
+ {
+ monster_pathchain = e;
+ checkpoint = e;
+ }
+
+ e = e->hint_chain;
+ }
+ }
+
+ /* filter them by distance and visibility to the monster */
+ e = monster_pathchain;
+ checkpoint = NULL;
+
+ while (e)
+ {
+ r = realrange(self, e);
+
+ if (r > 512)
+ {
+ count2++;
+
+ if (checkpoint)
+ {
+ checkpoint->monster_hint_chain = e->monster_hint_chain;
+ e->monster_hint_chain = NULL;
+ e = checkpoint->monster_hint_chain;
+ continue;
+ }
+ else
+ {
+ /* use checkpoint as temp pointer */
+ checkpoint = e;
+ e = e->monster_hint_chain;
+ checkpoint->monster_hint_chain = NULL;
+
+ /* and clear it again */
+ checkpoint = NULL;
+
+ /* since we have yet to find a valid one (or else
+ checkpoint would be set) move the start of
+ monster_pathchain */
+ monster_pathchain = e;
+ continue;
+ }
+ }
+
+ if (!visible(self, e))
+ {
+ count4++;
+
+ if (checkpoint)
+ {
+ checkpoint->monster_hint_chain = e->monster_hint_chain;
+ e->monster_hint_chain = NULL;
+ e = checkpoint->monster_hint_chain;
+ continue;
+ }
+ else
+ {
+ /* use checkpoint as temp pointer */
+ checkpoint = e;
+ e = e->monster_hint_chain;
+ checkpoint->monster_hint_chain = NULL;
+
+ /* and clear it again */
+
+ checkpoint = NULL;
+ /* since we have yet to find a valid one (or else
+ checkpoint would be set) move the start of
+ monster_pathchain */
+ monster_pathchain = e;
+ continue;
+ }
+ }
+
+ /* if it passes all the tests, it's a keeper */
+ count5++;
+ checkpoint = e;
+ e = e->monster_hint_chain;
+ }
+
+ /* at this point, we have a list of all of the eligible
+ hint nodes for the monster we now take them, figure out
+ what hint chains they're on, and traverse down those
+ chains, seeing whether any can see the player. first,
+ we figure out which hint chains we have represented
+ in monster_pathchain */
+ if (count5 == 0)
+ {
+ return false;
+ }
+
+ for (i = 0; i < num_hint_paths; i++)
+ {
+ hint_path_represented[i] = false;
+ }
+
+ e = monster_pathchain;
+ checkpoint = NULL;
+
+ while (e)
+ {
+ if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths))
+ {
+ return false;
+ }
+
+ hint_path_represented[e->hint_chain_id] = true;
+ e = e->monster_hint_chain;
+ }
+
+ count2 = 0;
+ count4 = 0;
+ count5 = 0;
+
+ /* now, build the target_pathchain which contains all of
+ the hint_path nodes we need to check for validity
+ (within range, visibility) */
+ target_pathchain = NULL;
+ checkpoint = NULL;
+
+ for (i = 0; i < num_hint_paths; i++)
+ {
+ /* if this hint chain is represented in the
+ monster_hint_chain, add all of it's nodes
+ to the target_pathchain for validity checking */
+ if (hint_path_represented[i])
+ {
+ e = hint_path_start[i];
+
+ while (e)
+ {
+ if (target_pathchain)
+ {
+ checkpoint->target_hint_chain = e;
+ checkpoint = e;
+ }
+ else
+ {
+ target_pathchain = e;
+ checkpoint = e;
+ }
+
+ e = e->hint_chain;
+ }
+ }
+ }
+
+ /* target_pathchain is a list of all of the hint_path nodes
+ we need to check for validity relative to the target */
+ e = target_pathchain;
+ checkpoint = NULL;
+
+ while (e)
+ {
+ r = realrange(self->enemy, e);
+
+ if (r > 512)
+ {
+ count2++;
+
+ if (checkpoint)
+ {
+ checkpoint->target_hint_chain = e->target_hint_chain;
+ e->target_hint_chain = NULL;
+ e = checkpoint->target_hint_chain;
+ continue;
+ }
+ else
+ {
+ /* use checkpoint as temp pointer */
+ checkpoint = e;
+ e = e->target_hint_chain;
+ checkpoint->target_hint_chain = NULL;
+
+ /* and clear it again */
+ checkpoint = NULL;
+ target_pathchain = e;
+ continue;
+ }
+ }
+
+ if (!visible(self->enemy, e))
+ {
+ count4++;
+
+ if (checkpoint)
+ {
+ checkpoint->target_hint_chain = e->target_hint_chain;
+ e->target_hint_chain = NULL;
+ e = checkpoint->target_hint_chain;
+ continue;
+ }
+ else
+ {
+ /* use checkpoint as temp pointer */
+ checkpoint = e;
+ e = e->target_hint_chain;
+ checkpoint->target_hint_chain = NULL;
+
+ /* and clear it again */
+ checkpoint = NULL;
+ target_pathchain = e;
+ continue;
+ }
+ }
+
+ /* if it passes all the tests, it's a keeper */
+ count5++;
+ checkpoint = e;
+ e = e->target_hint_chain;
+ }
+
+ /* at this point we should have:
+ - monster_pathchain - a list of "monster valid" hint_path nodes linked
+ together by monster_hint_chain
+ - target_pathcain - a list of "target valid" hint_path nodes linked
+ together by target_hint_chain. these are filtered
+ such that only nodes which are on the same chain
+ as "monster valid" nodes
+
+ Now, we figure out which "monster valid" node we want to use. To do this, we
+ first off make sure we have some target nodes. If we don't, there are no
+ valid hint_path nodes for us to take. If we have some, we filter all of our
+ "monster valid" nodes by which ones have "target valid" nodes on them. Once
+ this filter is finished, we select the closest "monster valid" node, and go to it. */
+
+ if (count5 == 0)
+ {
+ return false;
+ }
+
+ /* reuse the hint_chain_represented array, this time
+ to see which chains are represented by the target */
+ for (i = 0; i < num_hint_paths; i++)
+ {
+ hint_path_represented[i] = false;
+ }
+
+ e = target_pathchain;
+ checkpoint = NULL;
+
+ while (e)
+ {
+ if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths))
+ {
+ return false;
+ }
+
+ hint_path_represented[e->hint_chain_id] = true;
+ e = e->target_hint_chain;
+ }
+
+ /* traverse the monster_pathchain - if the hint_node isn't represented
+ in the "target valid" chain list, remove it. if it is on the list,
+ check it for range from the monster. If the range is the closest, keep it */
+ closest = NULL;
+ e = monster_pathchain;
+
+ while (e)
+ {
+ if (!(hint_path_represented[e->hint_chain_id]))
+ {
+ checkpoint = e->monster_hint_chain;
+ e->monster_hint_chain = NULL;
+ e = checkpoint;
+ continue;
+ }
+
+ r = realrange(self, e);
+
+ if (r < closest_range)
+ {
+ closest = e;
+ }
+
+ e = e->monster_hint_chain;
+ }
+
+ if (!closest)
+ {
+ return false;
+ }
+
+ start = closest;
+
+ /* now we know which one is the closest to the monster..
+ this is the one the monster will go to. we need to
+ finally determine what the DESTINATION node is for the
+ monster. walk down the hint_chain, and find the closest one
+ to the player */
+ closest = NULL;
+ closest_range = 10000000;
+ e = target_pathchain;
+
+ while (e)
+ {
+ if (start->hint_chain_id == e->hint_chain_id)
+ {
+ r = realrange(self, e);
+
+ if (r < closest_range)
+ {
+ closest = e;
+ }
+ }
+
+ e = e->target_hint_chain;
+ }
+
+ if (!closest)
+ {
+ return false;
+ }
+
+ destination = closest;
+
+ self->monsterinfo.goal_hint = destination;
+ hintpath_go(self, start);
+
+ return true;
+}
+
+void
+hint_path_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ edict_t *e, *goal;
+ edict_t *next = NULL;
+ qboolean goalFound = false;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ /* make sure we're the target of it's obsession */
+ if (other->movetarget == self)
+ {
+ goal = other->monsterinfo.goal_hint;
+
+ /* if the monster is where he wants to be */
+ if (goal == self)
+ {
+ hintpath_stop(other);
+ return;
+ }
+ else
+ {
+ /* if we aren't, figure out which way we want to go */
+ e = hint_path_start[self->hint_chain_id];
+
+ while (e)
+ {
+ /* if we get up to ourselves on the hint chain, we're going down it */
+ if (e == self)
+ {
+ next = e->hint_chain;
+ break;
+ }
+
+ if (e == goal)
+ {
+ goalFound = true;
+ }
+
+ /* if we get to where the next link on the chain is this hint_path
+ and have found the goal on the way we're going upstream, so
+ remember who the previous link is */
+ if ((e->hint_chain == self) && goalFound)
+ {
+ next = e;
+ break;
+ }
+
+ e = e->hint_chain;
+ }
+ }
+
+ /* if we couldn't find it, have the monster go back to normal hunting. */
+ if (!next)
+ {
+ hintpath_stop(other);
+ return;
+ }
+
+ /* set the last_hint entry to this hint_path,
+ and send him on his way */
+ hintpath_go(other, next);
+
+ /* have the monster freeze if the hint path we
+ just touched has a wait time on it, for e
+ xample, when riding a plat. */
+ if (self->wait)
+ {
+ other->nextthink = level.time + self->wait;
+ }
+ }
+}
+
+/*
+ * QUAKED hint_path (.5 .3 0) (-8 -8 -8) (8 8 8) END
+ *
+ * Target: next hint path
+ *
+ * END - set this flag on the endpoints of each hintpath.
+ * "wait" - set this if you want the monster to freeze when they touch this hintpath
+ */
+void
+SP_hint_path(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (!self->targetname && !self->target)
+ {
+ gi.dprintf("unlinked hint_path at %s\n", vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->touch = hint_path_touch;
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+ self->svflags |= SVF_NOCLIENT;
+ gi.linkentity(self);
+}
+
+void
+InitHintPaths(void)
+{
+ edict_t *e, *current;
+ int field, i, count2;
+
+ hint_paths_present = 0;
+
+ /* check all the hint_paths. */
+ field = FOFS(classname);
+ e = G_Find(NULL, field, "hint_path");
+
+ if (e)
+ {
+ hint_paths_present = 1;
+ }
+ else
+ {
+ return;
+ }
+
+ memset(hint_path_start, 0, MAX_HINT_CHAINS * sizeof(edict_t *));
+ num_hint_paths = 0;
+
+ while (e)
+ {
+ if (e->spawnflags & HINT_ENDPOINT)
+ {
+ if (e->target) /* start point */
+ {
+ if (e->targetname) /* this is a bad end, ignore it */
+ {
+ gi.dprintf("Hint path at %s marked as endpoint with both target (%s) and targetname (%s)\n",
+ vtos(e->s.origin), e->target, e->targetname);
+ }
+ else
+ {
+ if (num_hint_paths >= MAX_HINT_CHAINS)
+ {
+ break;
+ }
+
+ hint_path_start[num_hint_paths++] = e;
+ }
+ }
+ }
+
+ e = G_Find(e, field, "hint_path");
+ }
+
+ field = FOFS(targetname);
+
+ for (i = 0; i < num_hint_paths; i++)
+ {
+ count2 = 1;
+ current = hint_path_start[i];
+ current->hint_chain_id = i;
+ e = G_Find(NULL, field, current->target);
+
+ if (G_Find(e, field, current->target))
+ {
+ gi.dprintf("\nForked hint path at %s detected for chain %d, target %s\n",
+ vtos(current->s.origin), num_hint_paths, current->target);
+ hint_path_start[i]->hint_chain = NULL;
+ continue;
+ }
+
+ while (e)
+ {
+ if (e->hint_chain)
+ {
+ gi.dprintf("\nCircular hint path at %s detected for chain %d, targetname %s\n",
+ vtos(e->s.origin), num_hint_paths, e->targetname);
+ hint_path_start[i]->hint_chain = NULL;
+ break;
+ }
+
+ count2++;
+ current->hint_chain = e;
+ current = e;
+ current->hint_chain_id = i;
+
+ if (!current->target)
+ {
+ break;
+ }
+
+ e = G_Find(NULL, field, current->target);
+
+ if (G_Find(e, field, current->target))
+ {
+ gi.dprintf("\nForked hint path at %s detected for chain %d, target %s\n",
+ vtos(current->s.origin), num_hint_paths, current->target);
+ hint_path_start[i]->hint_chain = NULL;
+ break;
+ }
+ }
+ }
+}
+
+qboolean
+inback(edict_t *self, edict_t *other)
+{
+ vec3_t vec;
+ float dot;
+ vec3_t forward;
+
+ if (!self || !other)
+ {
+ return false;
+ }
+
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ VectorSubtract(other->s.origin, self->s.origin, vec);
+ VectorNormalize(vec);
+ dot = DotProduct(vec, forward);
+
+ if (dot < -0.3)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+float
+realrange(edict_t *self, edict_t *other)
+{
+ vec3_t dir;
+
+ if (!self || !other)
+ {
+ return 0;
+ }
+
+ VectorSubtract(self->s.origin, other->s.origin, dir);
+
+ return VectorLength(dir);
+}
+
+qboolean
+face_wall(edict_t *self)
+{
+ vec3_t pt;
+ vec3_t forward;
+ vec3_t ang;
+ trace_t tr;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ VectorMA(self->s.origin, 64, forward, pt);
+ tr = gi.trace(self->s.origin, vec3_origin, vec3_origin,
+ pt, self, MASK_MONSTERSOLID);
+
+ if ((tr.fraction < 1) && !tr.allsolid && !tr.startsolid)
+ {
+ vectoangles2(tr.plane.normal, ang);
+ self->ideal_yaw = ang[YAW] + 180;
+
+ if (self->ideal_yaw > 360)
+ {
+ self->ideal_yaw -= 360;
+ }
+
+ M_ChangeYaw(self);
+ return true;
+ }
+
+ return false;
+}
+
+void
+badarea_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+}
+
+edict_t *
+SpawnBadArea(vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner)
+{
+ edict_t *badarea;
+ vec3_t origin;
+
+ if (!owner)
+ {
+ return NULL;
+ }
+
+ VectorAdd(mins, maxs, origin);
+ VectorScale(origin, 0.5, origin);
+
+ VectorSubtract(maxs, origin, maxs);
+ VectorSubtract(mins, origin, mins);
+
+ badarea = G_Spawn();
+ VectorCopy(origin, badarea->s.origin);
+ VectorCopy(maxs, badarea->maxs);
+ VectorCopy(mins, badarea->mins);
+ badarea->touch = badarea_touch;
+ badarea->movetype = MOVETYPE_NONE;
+ badarea->solid = SOLID_TRIGGER;
+ badarea->classname = "bad_area";
+ gi.linkentity(badarea);
+
+ if (lifespan)
+ {
+ badarea->think = G_FreeEdict;
+ badarea->nextthink = level.time + lifespan;
+ }
+
+ badarea->owner = owner;
+
+ return badarea;
+}
+
+edict_t *
+CheckForBadArea(edict_t *ent)
+{
+ int i, num;
+ edict_t *touch[MAX_EDICTS], *hit;
+ vec3_t mins, maxs;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ VectorAdd(ent->s.origin, ent->mins, mins);
+ VectorAdd(ent->s.origin, ent->maxs, maxs);
+
+ num = gi.BoxEdicts(mins, maxs, touch, MAX_EDICTS, AREA_TRIGGERS);
+
+ /* be careful, it is possible to have an entity in this
+ list removed before we get to it (killtriggered) */
+ for (i = 0; i < num; i++)
+ {
+ hit = touch[i];
+
+ if (!hit->inuse)
+ {
+ continue;
+ }
+
+ if (hit->touch == badarea_touch)
+ {
+ return hit;
+ }
+ }
+
+ return NULL;
+}
+
+qboolean
+MarkTeslaArea(edict_t *self, edict_t *tesla)
+{
+ vec3_t mins, maxs;
+ edict_t *e;
+ edict_t *tail;
+ edict_t *area;
+
+ if (!tesla || !self)
+ {
+ return false;
+ }
+
+ area = NULL;
+
+ /* make sure this tesla doesn't have a bad area around it already... */
+ e = tesla->teamchain;
+ tail = tesla;
+
+ while (e)
+ {
+ tail = tail->teamchain;
+
+ if (!strcmp(e->classname, "bad_area"))
+ {
+ return false;
+ }
+
+ e = e->teamchain;
+ }
+
+ /* see if we can grab the trigger directly */
+ if (tesla->teamchain && tesla->teamchain->inuse)
+ {
+ edict_t *trigger;
+
+ trigger = tesla->teamchain;
+
+ VectorCopy(trigger->absmin, mins);
+ VectorCopy(trigger->absmax, maxs);
+
+ if (tesla->air_finished)
+ {
+ area = SpawnBadArea(mins, maxs, tesla->air_finished, tesla);
+ }
+ else
+ {
+ area = SpawnBadArea(mins, maxs, tesla->nextthink, tesla);
+ }
+ }
+ /* otherwise we just guess at how long it'll last. */
+ else
+ {
+ VectorSet(mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS,
+ tesla->mins[2]);
+ VectorSet(maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS);
+
+ area = SpawnBadArea(mins, maxs, 30, tesla);
+ }
+
+ /* if we spawned a bad area, then link it to the tesla */
+ if (area)
+ {
+ tail->teamchain = area;
+ }
+
+ return true;
+}
+
+void
+PredictAim(edict_t *target, vec3_t start, float bolt_speed, qboolean eye_height,
+ float offset, vec3_t aimdir, vec3_t aimpoint)
+{
+ vec3_t dir, vec;
+ float dist, time;
+
+ if (!target || !target->inuse)
+ {
+ VectorCopy(vec3_origin, aimdir);
+ return;
+ }
+
+ VectorSubtract(target->s.origin, start, dir);
+
+ if (eye_height)
+ {
+ dir[2] += target->viewheight;
+ }
+
+ dist = VectorLength(dir);
+ time = dist / bolt_speed;
+
+ VectorMA(target->s.origin, time - offset, target->velocity, vec);
+
+ if (eye_height)
+ {
+ vec[2] += target->viewheight;
+ }
+
+ if (aimdir)
+ {
+ VectorSubtract(vec, start, aimdir);
+ VectorNormalize(aimdir);
+ }
+
+ if (aimpoint)
+ {
+ VectorCopy(vec, aimpoint);
+ }
+}
+
+qboolean
+below(edict_t *self, edict_t *other)
+{
+ vec3_t vec;
+ float dot;
+ vec3_t down;
+
+ if (!self || !other)
+ {
+ return false;
+ }
+
+ VectorSubtract(other->s.origin, self->s.origin, vec);
+ VectorNormalize(vec);
+ VectorSet(down, 0, 0, -1);
+ dot = DotProduct(vec, down);
+
+ if (dot > 0.95) /* 18 degree arc below */
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+drawbbox(edict_t *self)
+{
+ int lines[4][3] = {
+ {1, 2, 4},
+ {1, 2, 7},
+ {1, 4, 5},
+ {2, 4, 7}
+ };
+
+ if (!self)
+ {
+ return;
+ }
+
+ int starts[4] = {0, 3, 5, 6};
+
+ vec3_t pt[8];
+ int i, j, k;
+ vec3_t coords[2];
+ vec3_t newbox;
+ vec3_t f, r, u, dir;
+
+ VectorCopy(self->absmin, coords[0]);
+ VectorCopy(self->absmax, coords[1]);
+
+ for (i = 0; i <= 1; i++)
+ {
+ for (j = 0; j <= 1; j++)
+ {
+ for (k = 0; k <= 1; k++)
+ {
+ pt[4 * i + 2 * j + k][0] = coords[i][0];
+ pt[4 * i + 2 * j + k][1] = coords[j][1];
+ pt[4 * i + 2 * j + k][2] = coords[k][2];
+ }
+ }
+ }
+
+ for (i = 0; i <= 3; i++)
+ {
+ for (j = 0; j <= 2; j++)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_DEBUGTRAIL);
+ gi.WritePosition(pt[starts[i]]);
+ gi.WritePosition(pt[lines[i][j]]);
+ gi.multicast(pt[starts[i]], MULTICAST_ALL);
+ }
+ }
+
+ vectoangles2(self->s.angles, dir);
+ AngleVectors(dir, f, r, u);
+
+ VectorMA(self->s.origin, 50, f, newbox);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_DEBUGTRAIL);
+ gi.WritePosition(self->s.origin);
+ gi.WritePosition(newbox);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ VectorClear(newbox);
+
+ VectorMA(self->s.origin, 50, r, newbox);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_DEBUGTRAIL);
+ gi.WritePosition(self->s.origin);
+ gi.WritePosition(newbox);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ VectorClear(newbox);
+
+ VectorMA(self->s.origin, 50, u, newbox);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_DEBUGTRAIL);
+ gi.WritePosition(self->s.origin);
+ gi.WritePosition(newbox);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ VectorClear(newbox);
+}
+
+void
+M_MonsterDodge(edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+ float r = random();
+ float height;
+ qboolean ducker = false, dodger = false;
+
+ if (!self || !attacker || !tr)
+ {
+ return;
+ }
+
+ /* this needs to be here since this can be
+ called after the monster has "died" */
+ if (self->health < 1)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.duck) && (self->monsterinfo.unduck))
+ {
+ ducker = true;
+ }
+
+ if ((self->monsterinfo.sidestep) &&
+ !(self->monsterinfo.aiflags & AI_STAND_GROUND))
+ {
+ dodger = true;
+ }
+
+ if ((!ducker) && (!dodger))
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ if ((eta < 0.1) || (eta > 5))
+ {
+ return;
+ }
+
+ /* skill level determination.. */
+ if (r > (0.25 * ((skill->value) + 1)))
+ {
+ return;
+ }
+
+ if (ducker)
+ {
+ height = self->absmax[2] - 32 - 1; /* the -1 is because the absmax is s.origin + maxs + 1 */
+
+ if ((!dodger) && ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED)))
+ {
+ return;
+ }
+ }
+ else
+ {
+ height = self->absmax[2];
+ }
+
+ if (dodger)
+ {
+ /* if we're already dodging, just finish
+ the sequence, i.e. don't do anything else */
+ if (self->monsterinfo.aiflags & AI_DODGING)
+ {
+ return;
+ }
+
+ /* if we're ducking already, or the shot is at our knees */
+ if ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED))
+ {
+ vec3_t right, diff;
+
+ AngleVectors(self->s.angles, NULL, right, NULL);
+ VectorSubtract(tr->endpos, self->s.origin, diff);
+
+ if (DotProduct(right, diff) < 0)
+ {
+ self->monsterinfo.lefty = 0;
+ }
+ else
+ {
+ self->monsterinfo.lefty = 1;
+ }
+
+ /* if we are currently ducked, unduck */
+ if ((ducker) && (self->monsterinfo.aiflags & AI_DUCKED))
+ {
+ self->monsterinfo.unduck(self);
+ }
+
+ self->monsterinfo.aiflags |= AI_DODGING;
+ self->monsterinfo.attack_state = AS_SLIDING;
+
+ /* call the monster specific code here */
+ self->monsterinfo.sidestep(self);
+ return;
+ }
+ }
+
+ if (ducker)
+ {
+ if (self->monsterinfo.next_duck_time > level.time)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ /* set this prematurely; it doesn't hurt, and prevents extra iterations */
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->monsterinfo.duck(self, eta);
+ }
+}
+
+void
+monster_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] = self->monsterinfo.base_height - 32;
+ self->takedamage = DAMAGE_YES;
+
+ if (self->monsterinfo.duck_wait_time < level.time)
+ {
+ self->monsterinfo.duck_wait_time = level.time + 1;
+ }
+
+ gi.linkentity(self);
+}
+
+void
+monster_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.duck_wait_time)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+void
+monster_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] = self->monsterinfo.base_height;
+ self->takedamage = DAMAGE_AIM;
+ self->monsterinfo.next_duck_time = level.time + DUCK_INTERVAL;
+ gi.linkentity(self);
+}
+
+qboolean
+has_valid_enemy(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ if (!self->enemy->inuse)
+ {
+ return false;
+ }
+
+ if (self->enemy->health < 1)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+TargetTesla(edict_t *self, edict_t *tesla)
+{
+ if ((!self) || (!tesla))
+ {
+ return;
+ }
+
+ /* medic bails on healing things */
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ if (self->enemy)
+ {
+ cleanupHealTarget(self->enemy);
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MEDIC;
+ }
+
+ /* store the player enemy in case we lose track of him. */
+ if (self->enemy && self->enemy->client)
+ {
+ self->monsterinfo.last_player_enemy = self->enemy;
+ }
+
+ if (self->enemy != tesla)
+ {
+ self->oldenemy = self->enemy;
+ self->enemy = tesla;
+
+ if (self->monsterinfo.attack)
+ {
+ if (self->health <= 0)
+ {
+ return;
+ }
+
+ self->monsterinfo.attack(self);
+ }
+ else
+ {
+ FoundTarget(self);
+ }
+ }
+}
+
+edict_t *
+PickCoopTarget(edict_t *self)
+{
+ if (!self)
+ {
+ return NULL;
+ }
+
+ /* no more than 4 players in coop, so.. */
+ edict_t *targets[4];
+ int num_targets = 0, targetID;
+ edict_t *ent;
+ int player;
+
+ /* if we're not in coop, this is a noop */
+ if (!coop || !coop->value)
+ {
+ return NULL;
+ }
+
+ memset(targets, 0, 4 * sizeof(edict_t *));
+
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (visible(self, ent))
+ {
+ targets[num_targets++] = ent;
+ }
+ }
+
+ if (!num_targets)
+ {
+ return NULL;
+ }
+
+ /* get a number from 0 to (num_targets-1) */
+ targetID = (random() * (float)num_targets);
+
+ /* just in case we got a 1.0 from random */
+ if (targetID == num_targets)
+ {
+ targetID--;
+ }
+
+ return targets[targetID];
+}
+
+int
+CountPlayers(void)
+{
+ edict_t *ent;
+ int count = 0;
+ int player;
+
+ /* if we're not in coop, this is a noop */
+ if (!coop || !coop->value)
+ {
+ return 1;
+ }
+
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ count++;
+ }
+
+ return count;
+}
+
+void
+monster_jump_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->timestamp = level.time;
+}
+
+qboolean
+monster_jump_finished(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if ((level.time - self->timestamp) > 3)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+blind_rocket_ok (edict_t *self, vec3_t start, vec3_t right, vec3_t target, float ofs,
+ vec3_t dir)
+{
+ trace_t tr;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ tr = gi.trace(start, vec3_origin, vec3_origin, target, self, MASK_SHOT);
+
+ /* since all traces have the same start point this only needs one check */
+ if (tr.startsolid)
+ {
+ return false;
+ }
+
+ if (!tr.allsolid && (tr.fraction >= 0.5f))
+ {
+ return true;
+ }
+
+ VectorMA(target, -ofs, right, vec);
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ tr = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+
+ if (!tr.allsolid && (tr.fraction >= 0.5f))
+ {
+ return true;
+ }
+
+ VectorMA(target, ofs, right, vec);
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ tr = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+
+ return !tr.allsolid && (tr.fraction >= 0.5f);
+}
diff --git a/rogue/src/g_newdm.c b/rogue/src/g_newdm.c
new file mode 100644
index 0000000..92fc7a8
--- /dev/null
+++ b/rogue/src/g_newdm.c
@@ -0,0 +1,448 @@
+/*
+ * =======================================================================
+ *
+ * Rogue specific deathmatch stuff.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+#include "monster/misc/player.h"
+
+#define IT_TYPE_MASK (IT_WEAPON | IT_AMMO | IT_POWERUP | IT_ARMOR | IT_KEY)
+
+dm_game_rt DMGame;
+
+extern qboolean Pickup_Health(edict_t *ent, edict_t *other);
+extern qboolean Pickup_Adrenaline(edict_t *ent, edict_t *other);
+extern qboolean Pickup_Armor(edict_t *ent, edict_t *other);
+extern qboolean Pickup_PowerArmor(edict_t *ent, edict_t *other);
+extern edict_t *Sphere_Spawn(edict_t *owner, int spawnflags);
+extern void ED_CallSpawn(edict_t *ent);
+void fire_doppleganger(edict_t *ent, vec3_t start, vec3_t aimdir);
+
+void
+InitGameRules(void)
+{
+ int gameNum;
+
+ /* clear out the game rule structure before we start */
+ memset(&DMGame, 0, sizeof(dm_game_rt));
+
+ if (gamerules && gamerules->value)
+ {
+ gameNum = gamerules->value;
+
+ switch (gameNum)
+ {
+ case RDM_TAG:
+ DMGame.GameInit = Tag_GameInit;
+ DMGame.PostInitSetup = Tag_PostInitSetup;
+ DMGame.PlayerDeath = Tag_PlayerDeath;
+ DMGame.Score = Tag_Score;
+ DMGame.PlayerEffects = Tag_PlayerEffects;
+ DMGame.DogTag = Tag_DogTag;
+ DMGame.PlayerDisconnect = Tag_PlayerDisconnect;
+ DMGame.ChangeDamage = Tag_ChangeDamage;
+ break;
+
+ /* reset gamerules if it's not a valid number */
+ default:
+ gamerules->value = 0;
+ break;
+ }
+ }
+
+ /* if we're set up to play, initialize the game as needed. */
+ if (DMGame.GameInit)
+ {
+ DMGame.GameInit();
+ }
+}
+
+char *
+FindSubstituteItem(edict_t *ent)
+{
+ int i;
+ int itflags, myflags;
+ float rnd;
+ int count;
+ int pick;
+ gitem_t *it;
+
+ /* there are only two classes of power armor, and we don't want
+ to give out power screens. therefore, power shields should
+ remain power shields. (powerscreens shouldn't be there at all...) */
+ if (ent->item->pickup == Pickup_PowerArmor)
+ {
+ return NULL;
+ }
+
+ /* health is special case */
+ if ((ent->item->pickup == Pickup_Health) ||
+ (ent->item->pickup == Pickup_Adrenaline))
+ {
+ /* health pellets stay health pellets */
+ if (!strcmp(ent->classname, "item_health_small"))
+ {
+ return NULL;
+ }
+
+ rnd = random();
+
+ if (rnd < 0.6)
+ {
+ return "item_health";
+ }
+ else if (rnd < 0.9)
+ {
+ return "item_health_large";
+ }
+ else if (rnd < 0.99)
+ {
+ return "item_adrenaline";
+ }
+ else
+ {
+ return "item_health_mega";
+ }
+ }
+ /* armor is also special case */
+ else if (ent->item->pickup == Pickup_Armor)
+ {
+ /* armor shards stay armor shards */
+ if (ent->item->tag == ARMOR_SHARD)
+ {
+ return NULL;
+ }
+
+ rnd = random();
+
+ if (rnd < 0.6)
+ {
+ return "item_armor_jacket";
+ }
+ else if (rnd < 0.9)
+ {
+ return "item_armor_combat";
+ }
+ else
+ {
+ return "item_armor_body";
+ }
+ }
+
+ /* we want to stay within the item class */
+ myflags = ent->item->flags & IT_TYPE_MASK;
+
+ if ((myflags & IT_AMMO) && (myflags & IT_WEAPON))
+ {
+ myflags = IT_AMMO;
+ }
+
+ count = 0;
+
+ /* first pass, count the matching items */
+ it = itemlist;
+
+ for (i = 0; i < game.num_items; i++, it++)
+ {
+ itflags = it->flags;
+
+ if (!itflags || (itflags & IT_NOT_GIVEABLE))
+ {
+ continue;
+ }
+
+ /* prox,grenades,etc should count as ammo. */
+ if ((itflags & IT_AMMO) && (itflags & IT_WEAPON))
+ {
+ itflags = IT_AMMO;
+ }
+
+ /* don't respawn spheres if they're dmflag disabled. */
+ if ((int)dmflags->value & DF_NO_SPHERES)
+ {
+ if (!strcmp(ent->classname, "item_sphere_vengeance") ||
+ !strcmp(ent->classname, "item_sphere_hunter") ||
+ !strcmp(ent->classname, "item_spehre_defender"))
+ {
+ continue;
+ }
+ }
+
+ if (((int)dmflags->value & DF_NO_NUKES) &&
+ !strcmp(ent->classname, "ammo_nuke"))
+ {
+ continue;
+ }
+
+ if (((int)dmflags->value & DF_NO_MINES) &&
+ (!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla")))
+ {
+ continue;
+ }
+
+ if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK))
+ {
+ count++;
+ }
+ }
+
+ if (!count)
+ {
+ return NULL;
+ }
+
+ pick = ceil(random() * count);
+ count = 0;
+
+ /* second pass, pick one. */
+ it = itemlist;
+
+ for (i = 0; i < game.num_items; i++, it++)
+ {
+ itflags = it->flags;
+
+ if (!itflags || (itflags & IT_NOT_GIVEABLE))
+ {
+ continue;
+ }
+
+ /* prox,grenades,etc should count as ammo. */
+ if ((itflags & IT_AMMO) && (itflags & IT_WEAPON))
+ {
+ itflags = IT_AMMO;
+ }
+
+ if (((int)dmflags->value & DF_NO_NUKES) &&
+ !strcmp(ent->classname, "ammo_nuke"))
+ {
+ continue;
+ }
+
+ if (((int)dmflags->value & DF_NO_MINES) &&
+ (!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla")))
+ {
+ continue;
+ }
+
+ if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK))
+ {
+ count++;
+
+ if (pick == count)
+ {
+ return it->classname;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+edict_t *
+DoRandomRespawn(edict_t *ent)
+{
+ edict_t *newEnt;
+ char *classname;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ classname = FindSubstituteItem(ent);
+
+ if (classname == NULL)
+ {
+ return NULL;
+ }
+
+ gi.unlinkentity(ent);
+
+ newEnt = G_Spawn();
+ newEnt->classname = classname;
+ VectorCopy(ent->s.origin, newEnt->s.origin);
+ VectorCopy(ent->s.old_origin, newEnt->s.old_origin);
+ VectorCopy(ent->mins, newEnt->mins);
+ VectorCopy(ent->maxs, newEnt->maxs);
+
+ VectorSet(newEnt->gravityVector, 0, 0, -1);
+
+ ED_CallSpawn(newEnt);
+
+ newEnt->s.renderfx |= RF_IR_VISIBLE;
+
+ return newEnt;
+}
+
+void
+PrecacheForRandomRespawn(void)
+{
+ gitem_t *it;
+ int i;
+ int itflags;
+
+ it = itemlist;
+
+ for (i = 0; i < game.num_items; i++, it++)
+ {
+ itflags = it->flags;
+
+ if (!itflags || (itflags & IT_NOT_GIVEABLE))
+ {
+ continue;
+ }
+
+ PrecacheItem(it);
+ }
+}
+
+void
+doppleganger_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker,
+ int damage, vec3_t point)
+{
+ edict_t *sphere;
+ float dist;
+ vec3_t dir;
+
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if ((self->enemy) && (self->enemy != self->teammaster))
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ dist = VectorLength(dir);
+
+ if (dist > 768)
+ {
+ sphere = Sphere_Spawn(self, SPHERE_HUNTER | SPHERE_DOPPLEGANGER);
+ sphere->pain(sphere, attacker, 0, 0);
+ }
+ else
+ {
+ sphere = Sphere_Spawn(self, SPHERE_VENGEANCE | SPHERE_DOPPLEGANGER);
+ sphere->pain(sphere, attacker, 0, 0);
+ }
+ }
+
+ if (self->teamchain)
+ {
+ BecomeExplosion1(self->teamchain);
+ }
+
+ BecomeExplosion1(self);
+}
+
+void
+doppleganger_pain(edict_t *self, edict_t *other, float kick, int damage)
+{
+ self->enemy = other;
+}
+
+void
+doppleganger_timeout(edict_t *self)
+{
+ if (self->teamchain)
+ {
+ BecomeExplosion1(self->teamchain);
+ }
+
+ BecomeExplosion1(self);
+}
+
+void
+body_think(edict_t *self)
+{
+ float r;
+
+ if (fabsf(self->ideal_yaw - anglemod(self->s.angles[YAW])) < 2)
+ {
+ if (self->timestamp < level.time)
+ {
+ r = random();
+
+ if (r < 0.10)
+ {
+ self->ideal_yaw = random() * 350.0;
+ self->timestamp = level.time + 1;
+ }
+ }
+ }
+ else
+ {
+ M_ChangeYaw(self);
+ }
+
+ self->s.frame++;
+
+ if (self->s.frame > FRAME_stand40)
+ {
+ self->s.frame = FRAME_stand01;
+ }
+
+ self->nextthink = level.time + 0.1;
+}
+
+void
+fire_doppleganger(edict_t *ent, vec3_t start, vec3_t aimdir)
+{
+ edict_t *base;
+ edict_t *body;
+ vec3_t dir;
+ vec3_t forward, right, up;
+ int number;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ vectoangles2(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ base = G_Spawn();
+ VectorCopy(start, base->s.origin);
+ VectorCopy(dir, base->s.angles);
+ VectorClear(base->velocity);
+ VectorClear(base->avelocity);
+ base->movetype = MOVETYPE_TOSS;
+ base->solid = SOLID_BBOX;
+ base->s.renderfx |= RF_IR_VISIBLE;
+ base->s.angles[PITCH] = 0;
+ VectorSet(base->mins, -16, -16, -24);
+ VectorSet(base->maxs, 16, 16, 32);
+ base->s.modelindex = 0;
+ base->teammaster = ent;
+ base->svflags |= SVF_DAMAGEABLE;
+ base->takedamage = DAMAGE_AIM;
+ base->health = 30;
+ base->pain = doppleganger_pain;
+ base->die = doppleganger_die;
+
+ base->nextthink = level.time + 30;
+ base->think = doppleganger_timeout;
+ base->classname = "doppleganger";
+
+ gi.linkentity(base);
+
+ body = G_Spawn();
+ number = body->s.number;
+ body->s = ent->s;
+ body->s.sound = 0;
+ body->s.event = 0;
+ body->s.number = number;
+ body->yaw_speed = 30;
+ body->ideal_yaw = 0;
+ VectorCopy(start, body->s.origin);
+ body->s.origin[2] += 8;
+ body->think = body_think;
+ body->nextthink = level.time + FRAMETIME;
+ gi.linkentity(body);
+
+ base->teamchain = body;
+ body->teammaster = base;
+}
diff --git a/rogue/src/g_newfnc.c b/rogue/src/g_newfnc.c
new file mode 100644
index 0000000..2b49220
--- /dev/null
+++ b/rogue/src/g_newfnc.c
@@ -0,0 +1,464 @@
+/*
+ * =======================================================================
+ *
+ * Rogue specific level functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+void fd_secret_move1(edict_t *self);
+void fd_secret_move2(edict_t *self);
+void fd_secret_move3(edict_t *self);
+void fd_secret_move4(edict_t *self);
+void fd_secret_move5(edict_t *self);
+void fd_secret_move6(edict_t *self);
+void fd_secret_done(edict_t *self);
+void Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *));
+
+/*
+ * =============================================================================
+ *
+ * SECRET DOORS
+ *
+ * =============================================================================
+ */
+
+#define SEC_OPEN_ONCE 1 /* stays open */
+#define SEC_1ST_LEFT 2 /* 1st move is left of arrow */
+#define SEC_1ST_DOWN 4 /* 1st move is down from arrow */
+#define SEC_NO_SHOOT 8 /* only opened by trigger */
+#define SEC_YES_SHOOT 16 /* shootable even if targeted */
+#define SEC_MOVE_RIGHT 32
+#define SEC_MOVE_FORWARD 64
+
+void
+fd_secret_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ /* trigger all paired doors */
+ for (ent = self; ent; ent = ent->teamchain)
+ {
+ Move_Calc(ent, ent->moveinfo.start_origin, fd_secret_move1);
+ }
+}
+
+void
+fd_secret_killed(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point)
+{
+ if (!self || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ self->health = self->max_health;
+ self->takedamage = DAMAGE_NO;
+
+ if (self->flags & FL_TEAMSLAVE && self->teammaster &&
+ (self->teammaster->takedamage != DAMAGE_NO))
+ {
+ fd_secret_killed(self->teammaster, inflictor, attacker, damage, point);
+ }
+ else
+ {
+ fd_secret_use(self, inflictor, attacker);
+ }
+}
+
+void
+fd_secret_move1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + 1.0;
+ self->think = fd_secret_move2;
+}
+
+/*
+ * Start moving sideways w/sound...
+ */
+void
+fd_secret_move2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, self->moveinfo.end_origin, fd_secret_move3);
+}
+
+/*
+ * Wait here until time to go back...
+ */
+void
+fd_secret_move3(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->spawnflags & SEC_OPEN_ONCE))
+ {
+ self->nextthink = level.time + self->wait;
+ self->think = fd_secret_move4;
+ }
+}
+
+/*
+ * Move backward...
+ */
+void
+fd_secret_move4(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, self->moveinfo.start_origin, fd_secret_move5);
+}
+
+/*
+ * Wait 1 second...
+ */
+void
+fd_secret_move5(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + 1.0;
+ self->think = fd_secret_move6;
+}
+
+void
+fd_secret_move6(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, self->move_origin, fd_secret_done);
+}
+
+void
+fd_secret_done(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->targetname || self->spawnflags & SEC_YES_SHOOT)
+ {
+ self->health = 1;
+ self->takedamage = DAMAGE_YES;
+ self->die = fd_secret_killed;
+ }
+}
+
+void
+secret_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 0, 0, MOD_CRUSH);
+ }
+}
+
+/*
+ * Prints messages
+ */
+void
+secret_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ if (!(other->client))
+ {
+ return;
+ }
+
+ if (self->monsterinfo.attack_finished > level.time)
+ {
+ return;
+ }
+
+ self->monsterinfo.attack_finished = level.time + 2;
+
+ if (self->message)
+ {
+ gi.centerprintf(other, self->message);
+ }
+}
+
+/*
+ * QUAKED func_door_secret2 (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot slide_right slide_forward
+ *
+ * Basic secret door. Slides back, then to the left. Angle determines direction.
+ *
+ * FLAGS:
+ * open_once = not implemented yet
+ * 1st_left = 1st move is left/right of arrow
+ * 1st_down = 1st move is forwards/backwards
+ * no_shoot = not implemented yet
+ * always_shoot = even if targeted, keep shootable
+ * reverse_left = the sideways move will be to right of arrow
+ * reverse_back = the to/fro move will be forward
+ *
+ * VALUES:
+ * wait = # of seconds before coming back (5 default)
+ * dmg = damage to inflict when blocked (2 default)
+ */
+void
+SP_func_door_secret2(edict_t *ent)
+{
+ vec3_t forward, right, up;
+ float lrSize = 0, fbSize = 0;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ AngleVectors(ent->s.angles, forward, right, up);
+ VectorCopy(ent->s.origin, ent->move_origin);
+ VectorCopy(ent->s.angles, ent->move_angles);
+
+ G_SetMovedir(ent->s.angles, ent->movedir);
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ if ((ent->move_angles[1] == 0) || (ent->move_angles[1] == 180))
+ {
+ lrSize = ent->size[1];
+ fbSize = ent->size[0];
+ }
+ else if ((ent->move_angles[1] == 90) || (ent->move_angles[1] == 270))
+ {
+ lrSize = ent->size[0];
+ fbSize = ent->size[1];
+ }
+ else
+ {
+ gi.dprintf("Secret door not at 0,90,180,270!\n");
+ }
+
+ if (ent->spawnflags & SEC_MOVE_FORWARD)
+ {
+ VectorScale(forward, fbSize, forward);
+ }
+ else
+ {
+ VectorScale(forward, fbSize * -1, forward);
+ }
+
+ if (ent->spawnflags & SEC_MOVE_RIGHT)
+ {
+ VectorScale(right, lrSize, right);
+ }
+ else
+ {
+ VectorScale(right, lrSize * -1, right);
+ }
+
+ if (ent->spawnflags & SEC_1ST_DOWN)
+ {
+ VectorAdd(ent->s.origin, forward, ent->moveinfo.start_origin);
+ VectorAdd(ent->moveinfo.start_origin, right, ent->moveinfo.end_origin);
+ }
+ else
+ {
+ VectorAdd(ent->s.origin, right, ent->moveinfo.start_origin);
+ VectorAdd(ent->moveinfo.start_origin, forward, ent->moveinfo.end_origin);
+ }
+
+ ent->touch = secret_touch;
+ ent->blocked = secret_blocked;
+ ent->use = fd_secret_use;
+ ent->moveinfo.speed = 50;
+ ent->moveinfo.accel = 50;
+ ent->moveinfo.decel = 50;
+
+ if (!ent->targetname || ent->spawnflags & SEC_YES_SHOOT)
+ {
+ ent->health = 1;
+ ent->max_health = ent->health;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = fd_secret_killed;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 5; /* 5 seconds before closing */
+ }
+
+ gi.linkentity(ent);
+}
+
+/* ================================================== */
+
+#define FWALL_START_ON 1
+
+void
+force_wall_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->wait)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_FORCEWALL);
+ gi.WritePosition(self->pos1);
+ gi.WritePosition(self->pos2);
+ gi.WriteByte(self->style);
+ gi.multicast(self->offset, MULTICAST_PVS);
+ }
+
+ self->think = force_wall_think;
+ self->nextthink = level.time + 0.1;
+}
+
+void
+force_wall_use(edict_t *self, edict_t *other /* activator */,
+ edict_t *activator /* activator */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->wait)
+ {
+ self->wait = 1;
+ self->think = NULL;
+ self->nextthink = 0;
+ self->solid = SOLID_NOT;
+ gi.linkentity(self);
+ }
+ else
+ {
+ self->wait = 0;
+ self->think = force_wall_think;
+ self->nextthink = level.time + 0.1;
+ self->solid = SOLID_BSP;
+ KillBox(self); /* Is this appropriate? */
+ gi.linkentity(self);
+ }
+}
+
+/*
+ * QUAKED func_force_wall (1 0 1) ? start_on
+ *
+ * A vertical particle force wall. Turns on and solid when triggered.
+ * If someone is in the force wall when it turns on, they're telefragged.
+ *
+ * start_on - forcewall begins activated. triggering will turn it off.
+ * style - color of particles to use.
+ * 208: green, 240: red, 241: blue, 224: orange
+ */
+void
+SP_func_force_wall(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, ent->model);
+
+ ent->offset[0] = (ent->absmax[0] + ent->absmin[0]) / 2;
+ ent->offset[1] = (ent->absmax[1] + ent->absmin[1]) / 2;
+ ent->offset[2] = (ent->absmax[2] + ent->absmin[2]) / 2;
+
+ ent->pos1[2] = ent->absmax[2];
+ ent->pos2[2] = ent->absmax[2];
+
+ if (ent->size[0] > ent->size[1])
+ {
+ ent->pos1[0] = ent->absmin[0];
+ ent->pos2[0] = ent->absmax[0];
+ ent->pos1[1] = ent->offset[1];
+ ent->pos2[1] = ent->offset[1];
+ }
+ else
+ {
+ ent->pos1[0] = ent->offset[0];
+ ent->pos2[0] = ent->offset[0];
+ ent->pos1[1] = ent->absmin[1];
+ ent->pos2[1] = ent->absmax[1];
+ }
+
+ if (!ent->style)
+ {
+ ent->style = 208;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->wait = 1;
+
+ if (ent->spawnflags & FWALL_START_ON)
+ {
+ ent->solid = SOLID_BSP;
+ ent->think = force_wall_think;
+ ent->nextthink = level.time + 0.1;
+ }
+ else
+ {
+ ent->solid = SOLID_NOT;
+ }
+
+ ent->use = force_wall_use;
+
+ ent->svflags = SVF_NOCLIENT;
+
+ gi.linkentity(ent);
+}
diff --git a/rogue/src/g_newtarg.c b/rogue/src/g_newtarg.c
new file mode 100644
index 0000000..bf9316b
--- /dev/null
+++ b/rogue/src/g_newtarg.c
@@ -0,0 +1,431 @@
+/*
+ * =======================================================================
+ *
+ * Rogue specific targets.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+/*
+ * QUAKED target_steam (1 0 0) (-8 -8 -8) (8 8 8)
+ * Creates a steam effect (particles w/ velocity in a line).
+ *
+ * speed = velocity of particles (default 50)
+ * count = number of particles (default 32)
+ * sounds = color of particles (default 8 for steam)
+ * the color range is from this color to this color + 6
+ * wait = seconds to run before stopping (overrides default
+ * value derived from func_timer)
+ *
+ * best way to use this is to tie it to a func_timer that "pokes"
+ * it every second (or however long you set the wait time, above)
+ *
+ * note that the width of the base is proportional to the speed
+ * good colors to use:
+ * 6-9 - varying whites (darker to brighter)
+ * 224 - sparks
+ * 176 - blue water
+ * 80 - brown water
+ * 208 - slime
+ * 232 - blood
+ */
+void
+use_target_steam(edict_t *self, edict_t *other, edict_t *activator /* unused */)
+{
+ static int nextid;
+ vec3_t point;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (nextid > 20000)
+ {
+ nextid = nextid % 20000;
+ }
+
+ nextid++;
+
+ /* automagically set wait from func_timer unless they set it
+ already, or default to 1000 if not called by a func_timer */
+ if (!self->wait)
+ {
+ if (other)
+ {
+ self->wait = other->wait * 1000;
+ }
+ else
+ {
+ self->wait = 1000;
+ }
+ }
+
+ if (self->enemy)
+ {
+ VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point);
+ VectorSubtract(point, self->s.origin, self->movedir);
+ VectorNormalize(self->movedir);
+ }
+
+ VectorMA(self->s.origin, self->plat2flags * 0.5, self->movedir, point);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_STEAM);
+
+ if (self->wait > 100)
+ {
+ gi.WriteShort(nextid);
+ gi.WriteByte(self->count);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(self->movedir);
+ gi.WriteByte(self->sounds & 0xff);
+ gi.WriteShort((short int)(self->plat2flags));
+ gi.WriteLong((int)(self->wait));
+ }
+ else
+ {
+ gi.WriteShort((short int)-1);
+ gi.WriteByte(self->count);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(self->movedir);
+ gi.WriteByte(self->sounds & 0xff);
+ gi.WriteShort((short int)(self->plat2flags));
+ }
+
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+}
+
+void
+target_steam_start(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = use_target_steam;
+
+ if (self->target)
+ {
+ ent = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!ent)
+ {
+ gi.dprintf("%s at %s: %s is a bad target\n", self->classname,
+ vtos(self->s.origin), self->target);
+ }
+
+ self->enemy = ent;
+ }
+ else
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+
+ if (!self->count)
+ {
+ self->count = 32;
+ }
+
+ if (!self->plat2flags)
+ {
+ self->plat2flags = 75;
+ }
+
+ if (!self->sounds)
+ {
+ self->sounds = 8;
+ }
+
+ if (self->wait)
+ {
+ self->wait *= 1000; /* we want it in milliseconds, not seconds */
+ }
+
+ /* paranoia is good */
+ self->sounds &= 0xff;
+ self->count &= 0xff;
+
+ self->svflags = SVF_NOCLIENT;
+
+ gi.linkentity(self);
+}
+
+void
+SP_target_steam(edict_t *self)
+{
+ self->plat2flags = self->speed;
+
+ if (self->target)
+ {
+ self->think = target_steam_start;
+ self->nextthink = level.time + 1;
+ }
+ else
+ {
+ target_steam_start(self);
+ }
+}
+
+void
+target_anger_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ edict_t *target;
+ edict_t *t;
+
+ if (!self)
+ {
+ return;
+ }
+
+ t = NULL;
+ target = G_Find(t, FOFS(targetname), self->killtarget);
+
+ if (target && self->target)
+ {
+ /* Make whatever a "good guy" so the monster will try to kill it! */
+ target->monsterinfo.aiflags |= AI_GOOD_GUY;
+ target->svflags |= SVF_MONSTER;
+ target->health = 300;
+
+ t = NULL;
+
+ while ((t = G_Find(t, FOFS(targetname), self->target)))
+ {
+ if (t == self)
+ {
+ gi.dprintf("WARNING: entity used itself.\n");
+ }
+ else
+ {
+ if (t->use)
+ {
+ if (t->health < 0)
+ {
+ return;
+ }
+
+ t->enemy = target;
+ t->monsterinfo.aiflags |= AI_TARGET_ANGER;
+ FoundTarget(t);
+ }
+ }
+
+ if (!self->inuse)
+ {
+ gi.dprintf("entity was removed while using targets\n");
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * QUAKED target_anger (1 0 0) (-8 -8 -8) (8 8 8)
+ *
+ * This trigger will cause an entity to be angry at another entity when a player touches it.
+ * Target the entity you want to anger, and killtarget the entity you want it to be angry at.
+ *
+ * target - entity to piss off
+ * killtarget - entity to be pissed off at
+ */
+void
+SP_target_anger(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("target_anger without target!\n");
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (!self->killtarget)
+ {
+ gi.dprintf("target_anger without killtarget!\n");
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->use = target_anger_use;
+ self->svflags = SVF_NOCLIENT;
+}
+
+void
+target_killplayers_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ int i;
+ edict_t *ent, *player;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* kill the players */
+ for (i = 0; i < game.maxclients; i++)
+ {
+ player = &g_edicts[1 + i];
+
+ if (!player->inuse)
+ {
+ continue;
+ }
+
+ /* nail it */
+ T_Damage(player, self, self, vec3_origin, self->s.origin, vec3_origin,
+ 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+ }
+
+ /* kill any visible monsters */
+ for (ent = g_edicts; ent < &g_edicts[globals.num_edicts]; ent++)
+ {
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (ent->health < 1)
+ {
+ continue;
+ }
+
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ player = &g_edicts[1 + i];
+
+ if (!player->inuse)
+ {
+ continue;
+ }
+
+ if (visible(player, ent))
+ {
+ T_Damage(ent, self, self, vec3_origin, ent->s.origin, vec3_origin,
+ ent->health, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * QUAKED target_killplayers (1 0 0) (-8 -8 -8) (8 8 8)
+ *
+ * When triggered, this will kill all the players on the map.
+ */
+void
+SP_target_killplayers(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = target_killplayers_use;
+ self->svflags = SVF_NOCLIENT;
+}
+
+/*
+ * QUAKED target_blacklight (1 0 1) (-16 -16 -24) (16 16 24)
+ *
+ * Pulsing black light with sphere in the center
+ */
+void
+blacklight_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.angles[0] = rand() % 360;
+ self->s.angles[1] = rand() % 360;
+ self->s.angles[2] = rand() % 360;
+ self->nextthink = level.time + 0.1;
+}
+
+void
+SP_target_blacklight(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ VectorClear(ent->mins);
+ VectorClear(ent->maxs);
+
+ ent->s.effects |= (EF_TRACKERTRAIL | EF_TRACKER);
+ ent->think = blacklight_think;
+ ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2");
+ ent->s.frame = 1;
+ ent->nextthink = level.time + 0.1;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED target_orb (1 0 1) (-16 -16 -24) (16 16 24)
+ *
+ * Translucent pulsing orb with speckles
+ */
+void
+orb_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.angles[0] = rand() % 360;
+ self->s.angles[1] = rand() % 360;
+ self->s.angles[2] = rand() % 360;
+ self->nextthink = level.time + 0.1;
+}
+
+void
+SP_target_orb(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ VectorClear(ent->mins);
+ VectorClear(ent->maxs);
+
+ ent->think = orb_think;
+ ent->nextthink = level.time + 0.1;
+ ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2");
+ ent->s.frame = 2;
+ ent->s.effects |= EF_SPHERETRANS;
+ gi.linkentity(ent);
+}
diff --git a/rogue/src/g_newtrig.c b/rogue/src/g_newtrig.c
new file mode 100644
index 0000000..862a404
--- /dev/null
+++ b/rogue/src/g_newtrig.c
@@ -0,0 +1,249 @@
+/*
+ * =======================================================================
+ *
+ * Rogue specific triggers.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define TELEPORT_PLAYER_ONLY 1
+#define TELEPORT_SILENT 2
+#define TELEPORT_CTF_ONLY 4
+#define TELEPORT_START_ON 8
+
+extern void TeleportEffect(vec3_t origin);
+
+/*
+ * QUAKED info_teleport_destination (.5 .5 .5) (-16 -16 -24) (16 16 32)
+ *
+ * Destination marker for a teleporter.
+ */
+void
+SP_info_teleport_destination(edict_t *self)
+{
+}
+
+/*
+ * QUAKED trigger_teleport (.5 .5 .5) ? player_only silent ctf_only start_on
+ *
+ * Any object touching this will be transported to the corresponding
+ * info_teleport_destination entity. You must set the "target" field,
+ * and create an object with a "targetname" field that matches.
+ *
+ * If the trigger_teleport has a targetname, it will only teleport
+ * entities when it has been fired.
+ *
+ * player_only: only players are teleported
+ * silent: <not used right now>
+ * ctf_only: <not used right now>
+ * start_on: when trigger has targetname, start active, deactivate when used.
+ */
+void
+trigger_teleport_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ edict_t *dest;
+ int i;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->client))
+ {
+ return;
+ }
+
+ if (self->delay)
+ {
+ return;
+ }
+
+ dest = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!dest)
+ {
+ gi.dprintf("Teleport Destination not found!\n");
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_TELEPORT_EFFECT);
+ gi.WritePosition(other->s.origin);
+ gi.multicast(other->s.origin, MULTICAST_PVS);
+
+ /* unlink to make sure it can't possibly interfere with KillBox */
+ gi.unlinkentity(other);
+
+ VectorCopy(dest->s.origin, other->s.origin);
+ VectorCopy(dest->s.origin, other->s.old_origin);
+ other->s.origin[2] += 10;
+
+ /* clear the velocity and hold them in place briefly */
+ VectorClear(other->velocity);
+
+ if (other->client)
+ {
+ other->client->ps.pmove.pm_time = 160 >> 3; /* hold time */
+ other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+ /* draw the teleport splash at source and on the player */
+ other->s.event = EV_PLAYER_TELEPORT;
+
+ /* set angles */
+ for (i = 0; i < 3; i++)
+ {
+ other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
+ dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+ }
+
+ VectorClear(other->client->ps.viewangles);
+ VectorClear(other->client->v_angle);
+ }
+
+ VectorClear(other->s.angles);
+
+ /* kill anything at the destination */
+ KillBox(other);
+
+ gi.linkentity(other);
+}
+
+void
+trigger_teleport_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->delay)
+ {
+ self->delay = 0;
+ }
+ else
+ {
+ self->delay = 1;
+ }
+}
+
+void
+SP_trigger_teleport(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->wait)
+ {
+ self->wait = 0.2;
+ }
+
+ self->delay = 0;
+
+ if (self->targetname)
+ {
+ self->use = trigger_teleport_use;
+
+ if (!(self->spawnflags & TELEPORT_START_ON))
+ {
+ self->delay = 1;
+ }
+ }
+
+ self->touch = trigger_teleport_touch;
+
+ self->solid = SOLID_TRIGGER;
+ self->movetype = MOVETYPE_NONE;
+
+ if (!VectorCompare(self->s.angles, vec3_origin))
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+
+ gi.setmodel(self, self->model);
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED trigger_disguise (.5 .5 .5) ? TOGGLE START_ON REMOVE
+ *
+ * Anything passing through this trigger when it is active will
+ * be marked as disguised.
+ *
+ * TOGGLE - field is turned off and on when used.
+ * START_ON - field is active when spawned.
+ * REMOVE - field removes the disguise
+ */
+
+void
+trigger_disguise_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->client)
+ {
+ if (self->spawnflags & 4)
+ {
+ other->flags &= ~FL_DISGUISED;
+ }
+ else
+ {
+ other->flags |= FL_DISGUISED;
+ }
+ }
+}
+
+void
+trigger_disguise_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->solid == SOLID_NOT)
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ }
+
+ gi.linkentity(self);
+}
+
+void
+SP_trigger_disguise(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ }
+
+ self->touch = trigger_disguise_touch;
+ self->use = trigger_disguise_use;
+ self->movetype = MOVETYPE_NONE;
+ self->svflags = SVF_NOCLIENT;
+
+ gi.setmodel(self, self->model);
+ gi.linkentity(self);
+}
diff --git a/rogue/src/g_newweap.c b/rogue/src/g_newweap.c
new file mode 100644
index 0000000..b50d3f0
--- /dev/null
+++ b/rogue/src/g_newweap.c
@@ -0,0 +1,1927 @@
+/*
+ * =======================================================================
+ *
+ * Rogue specific weapons.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define NUKE_DELAY 4
+#define NUKE_TIME_TO_LIVE 6
+#define NUKE_RADIUS 512
+#define NUKE_DAMAGE 400
+#define NUKE_QUAKE_TIME 3
+#define NUKE_QUAKE_STRENGTH 100
+
+#define PROX_TIME_TO_LIVE 45
+#define PROX_TIME_DELAY 0.5
+#define PROX_BOUND_SIZE 96
+#define PROX_DAMAGE_RADIUS 192
+#define PROX_HEALTH 20
+#define PROX_DAMAGE 90
+
+#define TESLA_TIME_TO_LIVE 30
+#define TESLA_DAMAGE_RADIUS 128
+#define TESLA_DAMAGE 3
+#define TESLA_KNOCKBACK 8
+#define TESLA_ACTIVATE_TIME 3
+#define TESLA_EXPLOSION_DAMAGE_MULT 50
+#define TESLA_EXPLOSION_RADIUS 200
+
+#define TRACKER_DAMAGE_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK)
+#define TRACKER_IMPACT_FLAGS (DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY)
+#define TRACKER_DAMAGE_TIME 0.5
+
+extern byte P_DamageModifier(edict_t *ent);
+extern void check_dodge(edict_t *self, vec3_t start, vec3_t dir, int speed);
+extern void hurt_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+extern void droptofloor(edict_t *ent);
+extern void Grenade_Explode(edict_t *ent);
+extern void drawbbox(edict_t *ent);
+
+void
+flechette_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t dir;
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ get_normal_vector(plane, normal);
+
+ if (other->takedamage)
+ {
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, self->dmg, self->dmg_radius, DAMAGE_NO_REG_ARMOR,
+ MOD_ETF_RIFLE);
+ }
+ else
+ {
+ VectorScale(normal, 256, dir);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_FLECHETTE);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(dir);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+fire_flechette(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int kick)
+{
+ edict_t *flechette;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ flechette = G_Spawn();
+ VectorCopy(start, flechette->s.origin);
+ VectorCopy(start, flechette->s.old_origin);
+ vectoangles2(dir, flechette->s.angles);
+
+ VectorScale(dir, speed, flechette->velocity);
+ flechette->movetype = MOVETYPE_FLYMISSILE;
+ flechette->clipmask = MASK_SHOT;
+ flechette->solid = SOLID_BBOX;
+ flechette->s.renderfx = RF_FULLBRIGHT;
+ VectorClear(flechette->mins);
+ VectorClear(flechette->maxs);
+
+ flechette->s.modelindex = gi.modelindex("models/proj/flechette/tris.md2");
+
+ flechette->owner = self;
+ flechette->touch = flechette_touch;
+ flechette->nextthink = level.time + (8000.0f / (float)speed);
+ flechette->think = G_FreeEdict;
+ flechette->dmg = damage;
+ flechette->dmg_radius = kick;
+
+ gi.linkentity(flechette);
+
+ if (self->client)
+ {
+ check_dodge(self, flechette->s.origin, dir, speed);
+ }
+}
+
+void
+Prox_Explode(edict_t *ent)
+{
+ vec3_t origin;
+ edict_t *owner;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* free the trigger field */
+ if (ent->teamchain && (ent->teamchain->owner == ent))
+ {
+ G_FreeEdict(ent->teamchain);
+ }
+
+ owner = ent;
+
+ if (ent->teammaster)
+ {
+ owner = ent->teammaster;
+ PlayerNoise(owner, ent->s.origin, PNOISE_IMPACT);
+ }
+
+ /* play double/quad sound if appopriate */
+ if (ent->dmg >= (PROX_DAMAGE * 4))
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->dmg == (PROX_DAMAGE * 2))
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
+ }
+
+ ent->takedamage = DAMAGE_NO;
+ T_RadiusDamage(ent, owner, ent->dmg, ent, PROX_DAMAGE_RADIUS, MOD_PROX);
+
+ VectorMA(ent->s.origin, -0.02, ent->velocity, origin);
+ gi.WriteByte(svc_temp_entity);
+
+ if (ent->groundentity)
+ {
+ gi.WriteByte(TE_GRENADE_EXPLOSION);
+ }
+ else
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION);
+ }
+
+ gi.WritePosition(origin);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(ent);
+}
+
+void
+prox_die(edict_t *self, edict_t *inflictor, edict_t *attacker /* unused */,
+ int damage, vec3_t point)
+{
+ if (!self || !inflictor)
+ {
+ return;
+ }
+
+ if (strcmp(inflictor->classname, "prox"))
+ {
+ self->takedamage = DAMAGE_NO;
+ Prox_Explode(self);
+ }
+ else
+ {
+ self->takedamage = DAMAGE_NO;
+ self->think = Prox_Explode;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+Prox_Field_Touch(edict_t *ent, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ edict_t *prox;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && !other->client)
+ {
+ return;
+ }
+
+ /* trigger the prox mine if it's still there, and still mine. */
+ prox = ent->owner;
+
+ if (other == prox) /* don't set self off */
+ {
+ return;
+ }
+
+ if (prox->think == Prox_Explode) /* we're set to blow! */
+ {
+ return;
+ }
+
+ if (prox->teamchain == ent)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
+ prox->think = Prox_Explode;
+ prox->nextthink = level.time + PROX_TIME_DELAY;
+ return;
+ }
+
+ ent->solid = SOLID_NOT;
+ G_FreeEdict(ent);
+}
+
+void
+prox_seek(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (level.time > ent->wait)
+ {
+ Prox_Explode(ent);
+ }
+ else
+ {
+ ent->s.frame++;
+
+ if (ent->s.frame > 13)
+ {
+ ent->s.frame = 9;
+ }
+
+ ent->think = prox_seek;
+ ent->nextthink = level.time + 0.1;
+ }
+}
+
+void
+prox_open(edict_t *ent)
+{
+ edict_t *search;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ search = NULL;
+
+ if (ent->s.frame == 9) /* end of opening animation */
+ {
+ /* set the owner to NULL so the owner can shoot it, etc.
+ needs to be done here so the owner doesn't get stuck on
+ it while it's opening if fired at point blank wall */
+ ent->s.sound = 0;
+ ent->owner = NULL;
+
+ if (ent->teamchain)
+ {
+ ent->teamchain->touch = Prox_Field_Touch;
+ }
+
+ while ((search = findradius(search, ent->s.origin, PROX_DAMAGE_RADIUS + 10)) != NULL)
+ {
+ if (!search->classname) /* tag token and other weird shit */
+ {
+ continue;
+ }
+
+ /* if it's a monster or player with health > 0
+ or it's a player start point and we can see it
+ blow up */
+ if (((((search->svflags & SVF_MONSTER) ||
+ (search->client)) && (search->health > 0)) ||
+ ((deathmatch->value) &&((!strcmp(search->classname, "info_player_deathmatch")) ||
+ (!strcmp(search->classname, "info_player_start")) ||
+ (!strcmp(search->classname, "info_player_coop")) ||
+ (!strcmp(search->classname, "misc_teleporter_dest"))))) &&
+ (visible(search, ent)))
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
+ Prox_Explode(ent);
+ return;
+ }
+ }
+
+ if (strong_mines && (strong_mines->value))
+ {
+ ent->wait = level.time + PROX_TIME_TO_LIVE;
+ }
+ else
+ {
+ switch (ent->dmg / PROX_DAMAGE)
+ {
+ case 1:
+ ent->wait = level.time + PROX_TIME_TO_LIVE;
+ break;
+ case 2:
+ ent->wait = level.time + 30;
+ break;
+ case 4:
+ ent->wait = level.time + 15;
+ break;
+ case 8:
+ ent->wait = level.time + 10;
+ break;
+ default:
+ ent->wait = level.time + PROX_TIME_TO_LIVE;
+ break;
+ }
+ }
+
+ ent->think = prox_seek;
+ ent->nextthink = level.time + 0.2;
+ }
+ else
+ {
+ if (ent->s.frame == 0)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/proxopen.wav"), 1, ATTN_NORM, 0);
+ }
+
+ ent->s.frame++;
+ ent->think = prox_open;
+ ent->nextthink = level.time + 0.05;
+ }
+}
+
+void
+prox_land(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ edict_t *field;
+ vec3_t dir;
+ vec3_t normal;
+ vec3_t forward, right, up;
+ int movetype = MOVETYPE_NONE;
+ int stick_ok = 0;
+ vec3_t land_point;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ /* must turn off owner so owner can shoot it and set it off
+ moved to prox_open so owner can get away from it if fired
+ at pointblank range into wall */
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ get_normal_vector(plane, normal);
+
+ VectorMA(ent->s.origin, -10.0, normal, land_point);
+
+ if (gi.pointcontents(land_point) & (CONTENTS_SLIME | CONTENTS_LAVA))
+ {
+ Prox_Explode(ent);
+ return;
+ }
+
+ if ((other->svflags & (SVF_MONSTER|SVF_DAMAGEABLE)) || other->client)
+ {
+ if (other != ent->teammaster)
+ {
+ Prox_Explode(ent);
+ }
+
+ return;
+ }
+
+#define STOP_EPSILON 0.1
+
+ else if (other != world)
+ {
+ /* Here we need to check to see if we can stop on this entity. */
+ vec3_t out;
+ float backoff, change;
+ int i;
+
+ if ((other->movetype == MOVETYPE_PUSH) && (normal[2] > 0.7))
+ {
+ stick_ok = 1;
+ }
+ else
+ {
+ stick_ok = 0;
+ }
+
+ backoff = DotProduct(ent->velocity, normal) * 1.5;
+
+ for (i = 0; i < 3; i++)
+ {
+ change = normal[i] * backoff;
+ out[i] = ent->velocity[i] - change;
+
+ if ((out[i] > -STOP_EPSILON) && (out[i] < STOP_EPSILON))
+ {
+ out[i] = 0;
+ }
+ }
+
+ if (out[2] > 60)
+ {
+ return;
+ }
+
+ movetype = MOVETYPE_BOUNCE;
+
+ /* if we're here, we're going to stop on an entity */
+ if (stick_ok)
+ {
+ /* it's a happy entity */
+ VectorCopy(vec3_origin, ent->velocity);
+ VectorCopy(vec3_origin, ent->avelocity);
+ }
+ else /* no-stick. teflon time */
+ {
+ if (normal[2] > 0.7)
+ {
+ Prox_Explode(ent);
+ return;
+ }
+
+ return;
+ }
+ }
+ else if (other->s.modelindex != 1)
+ {
+ return;
+ }
+
+ vectoangles2(normal, dir);
+ AngleVectors(dir, forward, right, up);
+
+ if (gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA | CONTENTS_SLIME))
+ {
+ Prox_Explode(ent);
+ return;
+ }
+
+ field = G_Spawn();
+
+ VectorCopy(ent->s.origin, field->s.origin);
+ VectorClear(field->velocity);
+ VectorClear(field->avelocity);
+ VectorSet(field->mins, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE);
+ VectorSet(field->maxs, PROX_BOUND_SIZE, PROX_BOUND_SIZE, PROX_BOUND_SIZE);
+ field->movetype = MOVETYPE_NONE;
+ field->solid = SOLID_TRIGGER;
+ field->owner = ent;
+ field->classname = "prox_field";
+ field->teammaster = ent;
+ gi.linkentity(field);
+
+ VectorClear(ent->velocity);
+ VectorClear(ent->avelocity);
+
+ /* rotate to vertical */
+ dir[PITCH] = dir[PITCH] + 90;
+ VectorCopy(dir, ent->s.angles);
+ ent->takedamage = DAMAGE_AIM;
+ ent->movetype = movetype; /* either bounce or none, depending on whether we stuck to something */
+ ent->die = prox_die;
+ ent->teamchain = field;
+ ent->health = PROX_HEALTH;
+ ent->nextthink = level.time + 0.05;
+ ent->think = prox_open;
+ ent->touch = NULL;
+ ent->solid = SOLID_BBOX;
+
+ /* record who we're attached to */
+ gi.linkentity(ent);
+}
+
+void
+fire_prox(edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed)
+{
+ edict_t *prox;
+ vec3_t dir;
+ vec3_t forward, right, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles2(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ prox = G_Spawn();
+ VectorCopy(start, prox->s.origin);
+ VectorScale(aimdir, speed, prox->velocity);
+ VectorMA(prox->velocity, 200 + crandom() * 10.0, up, prox->velocity);
+ VectorMA(prox->velocity, crandom() * 10.0, right, prox->velocity);
+ VectorCopy(dir, prox->s.angles);
+ prox->s.angles[PITCH] -= 90;
+ prox->movetype = MOVETYPE_BOUNCE;
+ prox->solid = SOLID_BBOX;
+ prox->s.effects |= EF_GRENADE;
+ prox->clipmask = MASK_SHOT | CONTENTS_LAVA | CONTENTS_SLIME;
+ prox->s.renderfx |= RF_IR_VISIBLE;
+ VectorSet(prox->mins, -6, -6, -6);
+ VectorSet(prox->maxs, 6, 6, 6);
+ prox->s.modelindex = gi.modelindex("models/weapons/g_prox/tris.md2");
+ prox->owner = self;
+ prox->teammaster = self;
+ prox->touch = prox_land;
+ prox->think = Prox_Explode;
+ prox->dmg = PROX_DAMAGE * damage_multiplier;
+ prox->classname = "prox";
+ prox->svflags |= SVF_DAMAGEABLE;
+ prox->flags |= FL_MECHANICAL;
+
+ switch (damage_multiplier)
+ {
+ case 1:
+ prox->nextthink = level.time + PROX_TIME_TO_LIVE;
+ break;
+ case 2:
+ prox->nextthink = level.time + 30;
+ break;
+ case 4:
+ prox->nextthink = level.time + 15;
+ break;
+ case 8:
+ prox->nextthink = level.time + 10;
+ break;
+ default:
+ prox->nextthink = level.time + PROX_TIME_TO_LIVE;
+ break;
+ }
+
+ gi.linkentity(prox);
+}
+
+void
+fire_player_melee(edict_t *self, vec3_t start, vec3_t aim, int reach,
+ int damage, int kick, int quiet, int mod)
+{
+ vec3_t forward, right, up;
+ vec3_t v;
+ vec3_t point;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles2(aim, v);
+ AngleVectors(v, forward, right, up);
+ VectorNormalize(forward);
+ VectorMA(start, reach, forward, point);
+
+ /* see if the hit connects */
+ tr = gi.trace(start, NULL, NULL, point, self, MASK_SHOT);
+
+ if (tr.fraction == 1.0)
+ {
+ if (!quiet)
+ {
+ gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/swish.wav"), 1, ATTN_NORM, 0);
+ }
+
+ return;
+ }
+
+ if ((tr.ent->takedamage == DAMAGE_YES) ||
+ (tr.ent->takedamage == DAMAGE_AIM))
+ {
+ /* pull the player forward if you do damage */
+ VectorMA(self->velocity, 75, forward, self->velocity);
+ VectorMA(self->velocity, 75, up, self->velocity);
+
+ /* do the damage */
+ if (mod == MOD_CHAINFIST)
+ {
+ T_Damage(tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin,
+ damage, kick / 2, DAMAGE_DESTROY_ARMOR | DAMAGE_NO_KNOCKBACK, mod);
+ }
+ else
+ {
+ T_Damage(tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin,
+ damage, kick / 2, DAMAGE_NO_KNOCKBACK, mod);
+ }
+
+ if (!quiet)
+ {
+ gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/meatht.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+ else
+ {
+ if (!quiet)
+ {
+ gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/tink1.wav"), 1, ATTN_NORM, 0);
+ }
+
+ VectorScale(tr.plane.normal, 256, point);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_GUNSHOT);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(point);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+}
+
+void
+Nuke_Quake(edict_t *self)
+{
+ int i;
+ edict_t *e;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->last_move_time < level.time)
+ {
+ gi.positioned_sound(self->s.origin, self, CHAN_AUTO, self->noise_index,
+ 0.75, ATTN_NONE, 0);
+ self->last_move_time = level.time + 0.5;
+ }
+
+ for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
+ {
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->client)
+ {
+ continue;
+ }
+
+ if (!e->groundentity)
+ {
+ continue;
+ }
+
+ e->groundentity = NULL;
+ e->velocity[0] += crandom() * 150;
+ e->velocity[1] += crandom() * 150;
+ e->velocity[2] = self->speed * (100.0 / e->mass);
+ }
+
+ if (level.time < self->timestamp)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ G_FreeEdict(self);
+ }
+}
+
+void
+Nuke_Explode(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->teammaster->client)
+ {
+ PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT);
+ }
+
+ T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg,
+ ent, ent->dmg_radius, MOD_NUKE);
+
+ if (ent->dmg >= (NUKE_DAMAGE * 4))
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->dmg == (NUKE_DAMAGE * 2))
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
+ }
+
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, gi.soundindex("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1_BIG);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_NUKEBLAST);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_ALL);
+
+ /* become a quake */
+ ent->svflags |= SVF_NOCLIENT;
+ ent->noise_index = gi.soundindex("world/rumble.wav");
+ ent->think = Nuke_Quake;
+ ent->speed = NUKE_QUAKE_STRENGTH;
+ ent->timestamp = level.time + NUKE_QUAKE_TIME;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->last_move_time = 0;
+}
+
+void
+nuke_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker, int damage, vec3_t point)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+
+ if ((attacker) && !(strcmp(attacker->classname, "nuke")))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ Nuke_Explode(self);
+}
+
+void
+Nuke_Think(edict_t *ent)
+{
+ float attenuation, default_atten = 1.8;
+ int damage_multiplier, muzzleflash;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ damage_multiplier = ent->dmg / NUKE_DAMAGE;
+
+ switch (damage_multiplier)
+ {
+ case 1:
+ attenuation = default_atten / 1.4;
+ muzzleflash = MZ_NUKE1;
+ break;
+ case 2:
+ attenuation = default_atten / 2.0;
+ muzzleflash = MZ_NUKE2;
+ break;
+ case 4:
+ attenuation = default_atten / 3.0;
+ muzzleflash = MZ_NUKE4;
+ break;
+ case 8:
+ attenuation = default_atten / 5.0;
+ muzzleflash = MZ_NUKE8;
+ break;
+ default:
+ attenuation = default_atten;
+ muzzleflash = MZ_NUKE1;
+ break;
+ }
+
+ if (ent->wait < level.time)
+ {
+ Nuke_Explode(ent);
+ }
+ else if (level.time >= (ent->wait - NUKE_TIME_TO_LIVE))
+ {
+ ent->s.frame++;
+
+ if (ent->s.frame > 11)
+ {
+ ent->s.frame = 6;
+ }
+
+ if (gi.pointcontents(ent->s.origin) & (CONTENTS_SLIME | CONTENTS_LAVA))
+ {
+ Nuke_Explode(ent);
+ return;
+ }
+
+ ent->think = Nuke_Think;
+ ent->nextthink = level.time + 0.1;
+ ent->health = 1;
+ ent->owner = NULL;
+
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(muzzleflash);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ if (ent->timestamp <= level.time)
+ {
+ if ((ent->wait - level.time) <= (NUKE_TIME_TO_LIVE / 2.0))
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, gi.soundindex("weapons/nukewarn2.wav"), 1, attenuation, 0);
+ ent->timestamp = level.time + 0.3;
+ }
+ else
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, gi.soundindex("weapons/nukewarn2.wav"), 1, attenuation, 0);
+ ent->timestamp = level.time + 0.5;
+ }
+ }
+ }
+ else
+ {
+ if (ent->timestamp <= level.time)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, gi.soundindex("weapons/nukewarn2.wav"), 1, attenuation, 0);
+ ent->timestamp = level.time + 1.0;
+ }
+
+ ent->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+nuke_bounce(edict_t *ent, edict_t *other /* unused */, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (random() > 0.5)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+ }
+}
+
+void
+fire_nuke(edict_t *self, vec3_t start, vec3_t aimdir, int speed)
+{
+ edict_t *nuke;
+ vec3_t dir;
+ vec3_t forward, right, up;
+ int damage_modifier;
+
+ if (!self)
+ {
+ return;
+ }
+
+ damage_modifier = (int)P_DamageModifier(self);
+
+ vectoangles2(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ nuke = G_Spawn();
+ VectorCopy(start, nuke->s.origin);
+ VectorScale(aimdir, speed, nuke->velocity);
+
+ VectorMA(nuke->velocity, 200 + crandom() * 10.0, up, nuke->velocity);
+ VectorMA(nuke->velocity, crandom() * 10.0, right, nuke->velocity);
+ VectorClear(nuke->avelocity);
+ VectorClear(nuke->s.angles);
+ nuke->movetype = MOVETYPE_BOUNCE;
+ nuke->clipmask = MASK_SHOT;
+ nuke->solid = SOLID_BBOX;
+ nuke->s.effects |= EF_GRENADE;
+ nuke->s.renderfx |= RF_IR_VISIBLE;
+ VectorSet(nuke->mins, -8, -8, 0);
+ VectorSet(nuke->maxs, 8, 8, 16);
+ nuke->s.modelindex = gi.modelindex("models/weapons/g_nuke/tris.md2");
+ nuke->owner = self;
+ nuke->teammaster = self;
+ nuke->nextthink = level.time + FRAMETIME;
+ nuke->wait = level.time + NUKE_DELAY + NUKE_TIME_TO_LIVE;
+ nuke->think = Nuke_Think;
+ nuke->touch = nuke_bounce;
+
+ nuke->health = 10000;
+ nuke->takedamage = DAMAGE_YES;
+ nuke->svflags |= SVF_DAMAGEABLE;
+ nuke->dmg = NUKE_DAMAGE * damage_modifier;
+
+ if (damage_modifier == 1)
+ {
+ nuke->dmg_radius = NUKE_RADIUS;
+ }
+ else
+ {
+ nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS * (0.25 * (float)damage_modifier);
+ }
+
+ nuke->classname = "nuke";
+ nuke->die = nuke_die;
+
+ gi.linkentity(nuke);
+}
+void
+tesla_remove(edict_t *self)
+{
+ edict_t *cur, *next;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+
+ if (self->teamchain)
+ {
+ cur = self->teamchain;
+
+ while (cur)
+ {
+ next = cur->teamchain;
+ G_FreeEdict(cur);
+ cur = next;
+ }
+ }
+ else if (self->air_finished)
+ {
+ gi.dprintf("tesla without a field!\n");
+ }
+
+ self->owner = self->teammaster; /* Going away, set the owner correctly. */
+ self->enemy = NULL;
+
+ /* play double/quad sound if doubled/quadded and an underwater explosion */
+ if (self->dmg_radius)
+ {
+ if (self->dmg >= (TESLA_DAMAGE * TESLA_EXPLOSION_DAMAGE_MULT * 4))
+ {
+ gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (self->dmg == (TESLA_DAMAGE * TESLA_EXPLOSION_DAMAGE_MULT * 2))
+ {
+ gi.sound(self, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+
+ Grenade_Explode(self);
+}
+
+void
+tesla_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ tesla_remove(self);
+}
+
+void
+tesla_blow(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->dmg = self->dmg * TESLA_EXPLOSION_DAMAGE_MULT;
+ self->dmg_radius = TESLA_EXPLOSION_RADIUS;
+ tesla_remove(self);
+}
+
+void
+tesla_zap(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+}
+
+void
+tesla_think_active(edict_t *self)
+{
+ int i, num;
+ edict_t *touch[MAX_EDICTS], *hit;
+ vec3_t dir, start;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time > self->air_finished)
+ {
+ tesla_remove(self);
+ return;
+ }
+
+ VectorCopy(self->s.origin, start);
+ start[2] += 16;
+
+ num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax,
+ touch, MAX_EDICTS, AREA_SOLID);
+
+ for (i = 0; i < num; i++)
+ {
+ /* if the tesla died while zapping things, stop zapping. */
+ if (!(self->inuse))
+ {
+ break;
+ }
+
+ hit = touch[i];
+
+ if (!hit->inuse)
+ {
+ continue;
+ }
+
+ if (hit == self)
+ {
+ continue;
+ }
+
+ if (hit->health < 1)
+ {
+ continue;
+ }
+
+ /* don't hit clients in single-player or coop */
+ if (hit->client)
+ {
+ if (coop->value || !deathmatch->value)
+ {
+ continue;
+ }
+ }
+
+ if (!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client)
+ {
+ continue;
+ }
+
+ tr = gi.trace(start, vec3_origin, vec3_origin, hit->s.origin,
+ self, MASK_SHOT);
+
+ if ((tr.fraction == 1) || (tr.ent == hit))
+ {
+ VectorSubtract(hit->s.origin, start, dir);
+
+ /* play double/quad sound if it's above the "normal" damage */
+ if (self->dmg >= (TESLA_DAMAGE * 4))
+ {
+ gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (self->dmg == (TESLA_DAMAGE * 2))
+ {
+ gi.sound(self, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
+ }
+
+ /* don't do knockback to walking monsters */
+ if ((hit->svflags & SVF_MONSTER) &&
+ !(hit->flags & (FL_FLY | FL_SWIM)))
+ {
+ T_Damage(hit, self, self->teammaster, dir, tr.endpos,
+ tr.plane.normal, self->dmg, 0, 0, MOD_TESLA);
+ }
+ else
+ {
+ T_Damage(hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal,
+ self->dmg, TESLA_KNOCKBACK, 0, MOD_TESLA);
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_LIGHTNING);
+ gi.WriteShort(hit - g_edicts); /* destination entity */
+ gi.WriteShort(self - g_edicts); /* source entity */
+ gi.WritePosition(tr.endpos);
+ gi.WritePosition(start);
+ gi.multicast(start, MULTICAST_PVS);
+ }
+ }
+
+ if (self->inuse)
+ {
+ self->think = tesla_think_active;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+tesla_activate(edict_t *self)
+{
+ edict_t *trigger;
+ edict_t *search;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (gi.pointcontents(self->s.origin) & (CONTENTS_SLIME | CONTENTS_LAVA | CONTENTS_WATER))
+ {
+ tesla_blow(self);
+ return;
+ }
+
+ /* only check for spawn points in deathmatch */
+ if (deathmatch->value)
+ {
+ search = NULL;
+
+ while ((search = findradius(search, self->s.origin, 1.5 * TESLA_DAMAGE_RADIUS)) != NULL)
+ {
+ if (search->classname)
+ {
+ if (((!strcmp(search->classname, "info_player_deathmatch")) ||
+ (!strcmp(search->classname, "info_player_start")) ||
+ (!strcmp(search->classname, "info_player_coop")) ||
+ (!strcmp(search->classname, "misc_teleporter_dest"))) &&
+ (visible(search, self)))
+ {
+ tesla_remove(self);
+ return;
+ }
+ }
+ }
+ }
+
+ trigger = G_Spawn();
+ VectorCopy(self->s.origin, trigger->s.origin);
+ VectorSet(trigger->mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, self->mins[2]);
+ VectorSet(trigger->maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS);
+ trigger->movetype = MOVETYPE_NONE;
+ trigger->solid = SOLID_TRIGGER;
+ trigger->owner = self;
+ trigger->touch = tesla_zap;
+ trigger->classname = "tesla trigger";
+
+ /* doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains */
+ gi.linkentity(trigger);
+
+ VectorClear(self->s.angles);
+
+ /* clear the owner if in deathmatch */
+ if (deathmatch->value)
+ {
+ self->owner = NULL;
+ }
+
+ self->teamchain = trigger;
+ self->think = tesla_think_active;
+ self->nextthink = level.time + FRAMETIME;
+ self->air_finished = level.time + TESLA_TIME_TO_LIVE;
+}
+
+void
+tesla_think(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (gi.pointcontents(ent->s.origin) & (CONTENTS_SLIME | CONTENTS_LAVA))
+ {
+ tesla_remove(ent);
+ return;
+ }
+
+ VectorClear(ent->s.angles);
+
+ if (!(ent->s.frame))
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/teslaopen.wav"), 1, ATTN_NORM, 0);
+ }
+
+ ent->s.frame++;
+
+ if (ent->s.frame > 14)
+ {
+ ent->s.frame = 14;
+ ent->think = tesla_activate;
+ ent->nextthink = level.time + 0.1;
+ }
+ else
+ {
+ if (ent->s.frame > 9)
+ {
+ if (ent->s.frame == 10)
+ {
+ if (ent->owner && ent->owner->client)
+ {
+ PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON); /* PGM */
+ }
+
+ ent->s.skinnum = 1;
+ }
+ else if (ent->s.frame == 12)
+ {
+ ent->s.skinnum = 2;
+ }
+ else if (ent->s.frame == 14)
+ {
+ ent->s.skinnum = 3;
+ }
+ }
+
+ ent->think = tesla_think;
+ ent->nextthink = level.time + 0.1;
+ }
+}
+
+void
+tesla_lava(edict_t *ent, edict_t *other /* unused */, cplane_t *plane, csurface_t *surf /* unused */)
+{
+ vec3_t land_point;
+ vec3_t normal;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ get_normal_vector(plane, normal);
+
+ VectorMA(ent->s.origin, -20.0, normal, land_point);
+
+ if (gi.pointcontents(land_point) & (CONTENTS_SLIME | CONTENTS_LAVA))
+ {
+ tesla_blow(ent);
+ return;
+ }
+
+ if (random() > 0.5)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+ }
+}
+
+void
+fire_tesla(edict_t *self, vec3_t start, vec3_t aimdir,
+ int damage_multiplier, int speed)
+{
+ edict_t *tesla;
+ vec3_t dir;
+ vec3_t forward, right, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles2(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ tesla = G_Spawn();
+ VectorCopy(start, tesla->s.origin);
+ VectorScale(aimdir, speed, tesla->velocity);
+ VectorMA(tesla->velocity, 200 + crandom() * 10.0, up, tesla->velocity);
+ VectorMA(tesla->velocity, crandom() * 10.0, right, tesla->velocity);
+ VectorClear(tesla->s.angles);
+ tesla->movetype = MOVETYPE_BOUNCE;
+ tesla->solid = SOLID_BBOX;
+ tesla->s.effects |= EF_GRENADE;
+ tesla->s.renderfx |= RF_IR_VISIBLE;
+ VectorSet(tesla->mins, -12, -12, 0);
+ VectorSet(tesla->maxs, 12, 12, 20);
+ tesla->s.modelindex = gi.modelindex("models/weapons/g_tesla/tris.md2");
+
+ tesla->owner = self;
+ tesla->teammaster = self;
+
+ tesla->wait = level.time + TESLA_TIME_TO_LIVE;
+ tesla->think = tesla_think;
+ tesla->nextthink = level.time + TESLA_ACTIVATE_TIME;
+
+ /* blow up on contact with lava & slime code */
+ tesla->touch = tesla_lava;
+
+ if (deathmatch->value)
+ {
+ tesla->health = 20;
+ }
+ else
+ {
+ tesla->health = 30;
+ }
+
+ tesla->takedamage = DAMAGE_YES;
+ tesla->die = tesla_die;
+ tesla->dmg = TESLA_DAMAGE * damage_multiplier;
+ tesla->classname = "tesla";
+ tesla->svflags |= SVF_DAMAGEABLE;
+ tesla->clipmask = MASK_SHOT | CONTENTS_SLIME | CONTENTS_LAVA;
+ tesla->flags |= FL_MECHANICAL;
+
+ gi.linkentity(tesla);
+}
+
+void
+fire_beams(edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset,
+ int damage, int kick, int te_beam, int te_impact, int mod)
+{
+ trace_t tr;
+ vec3_t dir;
+ vec3_t forward, right, up;
+ vec3_t end;
+ vec3_t water_start, endpoint;
+ qboolean water = false, underwater = false;
+ int content_mask = MASK_SHOT | MASK_WATER;
+ vec3_t beam_endpt;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles2(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ VectorMA(start, 8192, forward, end);
+
+ if (gi.pointcontents(start) & MASK_WATER)
+ {
+ underwater = true;
+ VectorCopy(start, water_start);
+ content_mask &= ~MASK_WATER;
+ }
+
+ tr = gi.trace(start, NULL, NULL, end, self, content_mask);
+
+ /* see if we hit water */
+ if (tr.contents & MASK_WATER)
+ {
+ water = true;
+ VectorCopy(tr.endpos, water_start);
+
+ if (!VectorCompare(start, tr.endpos))
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_HEATBEAM_SPARKS);
+ gi.WritePosition(water_start);
+ gi.WriteDir(tr.plane.normal);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+
+ /* re-trace ignoring water this time */
+ tr = gi.trace(water_start, NULL, NULL, end, self, MASK_SHOT);
+ }
+
+ VectorCopy(tr.endpos, endpoint);
+
+ /* halve the damage if target underwater */
+ if (water)
+ {
+ damage = damage / 2;
+ }
+
+ /* send gun puff / flash */
+ if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+ {
+ if (tr.fraction < 1.0)
+ {
+ if (tr.ent->takedamage)
+ {
+ T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal,
+ damage, kick, DAMAGE_ENERGY, mod);
+ }
+ else
+ {
+ if ((!water) && (strncmp(tr.surface->name, "sky", 3)))
+ {
+ /* This is the truncated steam entry - uses 1+1+2 extra bytes of data */
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_HEATBEAM_STEAM);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+
+ if (self->client)
+ {
+ PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+ }
+ }
+ }
+ }
+ }
+
+ /* if went through water, determine where the end and make a bubble trail */
+ if ((water) || (underwater))
+ {
+ vec3_t pos;
+
+ VectorSubtract(tr.endpos, water_start, dir);
+ VectorNormalize(dir);
+ VectorMA(tr.endpos, -2, dir, pos);
+
+ if (gi.pointcontents(pos) & MASK_WATER)
+ {
+ VectorCopy(pos, tr.endpos);
+ }
+ else
+ {
+ tr = gi.trace(pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+ }
+
+ VectorAdd(water_start, tr.endpos, pos);
+ VectorScale(pos, 0.5, pos);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BUBBLETRAIL2);
+ gi.WritePosition(water_start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(pos, MULTICAST_PVS);
+ }
+
+ if ((!underwater) && (!water))
+ {
+ VectorCopy(tr.endpos, beam_endpt);
+ }
+ else
+ {
+ VectorCopy(endpoint, beam_endpt);
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(te_beam);
+ gi.WriteShort(self - g_edicts);
+ gi.WritePosition(start);
+ gi.WritePosition(beam_endpt);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+}
+
+void
+fire_heat(edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset,
+ int damage, int kick, qboolean monster)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (monster)
+ {
+ fire_beams(self, start, aimdir, offset, damage, kick,
+ TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
+ }
+ else
+ {
+ fire_beams(self, start, aimdir, offset, damage,
+ kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
+ }
+}
+
+/*
+ * Fires a single green blaster bolt. Used by monsters, generally.
+ */
+void
+blaster2_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ int mod;
+ int damagestat;
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner && self->owner->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ get_normal_vector(plane, normal);
+
+ if (other->takedamage)
+ {
+ mod = MOD_BLASTER2;
+
+ if (self->owner)
+ {
+ /* the only time players will be firing blaster2
+ bolts will be from the defender sphere. */
+ if (self->owner->client)
+ {
+ mod = MOD_DEFENDER_SPHERE;
+ }
+
+ damagestat = self->owner->takedamage;
+ self->owner->takedamage = DAMAGE_NO;
+
+ if (self->dmg >= 5)
+ {
+ T_RadiusDamage(self, self->owner, self->dmg * 3, other,
+ self->dmg_radius, 0);
+ }
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin, normal,
+ self->dmg, 1, DAMAGE_ENERGY, mod);
+
+ self->owner->takedamage = damagestat;
+ }
+ else
+ {
+ if (self->dmg >= 5)
+ {
+ T_RadiusDamage(self, self->owner, self->dmg * 3, other,
+ self->dmg_radius, 0);
+ }
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, self->dmg, 1, DAMAGE_ENERGY, mod);
+ }
+ }
+ else
+ {
+ /* yeowch this will get expensive */
+ if (self->dmg >= 5)
+ {
+ T_RadiusDamage(self, self->owner, self->dmg * 3, self->owner,
+ self->dmg_radius, 0);
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BLASTER2);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(normal);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int effect, qboolean hyper)
+{
+ edict_t *bolt;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ bolt = G_Spawn();
+ VectorCopy(start, bolt->s.origin);
+ VectorCopy(start, bolt->s.old_origin);
+ vectoangles2(dir, bolt->s.angles);
+ VectorScale(dir, speed, bolt->velocity);
+ bolt->movetype = MOVETYPE_FLYMISSILE;
+ bolt->clipmask = MASK_SHOT;
+ bolt->solid = SOLID_BBOX;
+ bolt->s.effects |= effect;
+ VectorClear(bolt->mins);
+ VectorClear(bolt->maxs);
+
+ if (effect)
+ {
+ bolt->s.effects |= EF_TRACKER;
+ }
+
+ bolt->dmg_radius = 128;
+ bolt->s.modelindex = gi.modelindex("models/proj/laser2/tris.md2");
+ bolt->touch = blaster2_touch;
+
+ bolt->owner = self;
+ bolt->nextthink = level.time + 2;
+ bolt->think = G_FreeEdict;
+ bolt->dmg = damage;
+ bolt->classname = "bolt";
+ gi.linkentity(bolt);
+
+ if (self->client)
+ {
+ check_dodge(self, bolt->s.origin, dir, speed);
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+
+ if (tr.fraction < 1.0)
+ {
+ VectorMA(bolt->s.origin, -10, dir, bolt->s.origin);
+ bolt->touch(bolt, tr.ent, NULL, NULL);
+ }
+}
+
+void
+tracker_pain_daemon_think(edict_t *self)
+{
+ static vec3_t pain_normal = {0, 0, 1};
+ int hurt;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->inuse)
+ {
+ return;
+ }
+
+ if ((level.time - self->timestamp) > TRACKER_DAMAGE_TIME)
+ {
+ if (!self->enemy->client)
+ {
+ self->enemy->s.effects &= ~EF_TRACKERTRAIL;
+ }
+
+ G_FreeEdict(self);
+ }
+ else
+ {
+ if (self->enemy->health > 0)
+ {
+ T_Damage(self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin,
+ pain_normal, self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
+
+ /* if we kill the player, we'll be removed. */
+ if (self->inuse)
+ {
+ /* if we killed a monster, gib them. */
+ if (self->enemy->health < 1)
+ {
+ if (self->enemy->gib_health)
+ {
+ hurt = -self->enemy->gib_health;
+ }
+ else
+ {
+ hurt = 500;
+ }
+
+ T_Damage(self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin,
+ pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
+ }
+
+ if (self->enemy->client)
+ {
+ self->enemy->client->tracker_pain_framenum = level.framenum + 1;
+ }
+ else
+ {
+ self->enemy->s.effects |= EF_TRACKERTRAIL;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+ }
+ }
+ else
+ {
+ if (!self->enemy->client)
+ {
+ self->enemy->s.effects &= ~EF_TRACKERTRAIL;
+ }
+
+ G_FreeEdict(self);
+ }
+ }
+}
+
+void
+tracker_pain_daemon_spawn(edict_t *owner, edict_t *enemy, int damage)
+{
+ edict_t *daemon;
+
+ if (!owner || !enemy)
+ {
+ return;
+ }
+
+ daemon = G_Spawn();
+ daemon->classname = "pain daemon";
+ daemon->think = tracker_pain_daemon_think;
+ daemon->nextthink = level.time + FRAMETIME;
+ daemon->timestamp = level.time;
+ daemon->owner = owner;
+ daemon->enemy = enemy;
+ daemon->dmg = damage;
+}
+
+void
+tracker_explode(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_TRACKER_EXPLOSION);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(self);
+}
+
+void
+tracker_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ float damagetime;
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ if ((other->svflags & SVF_MONSTER) || other->client)
+ {
+ if (other->health > 0) /* knockback only for living creatures */
+ {
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, 0, (self->dmg * 3), TRACKER_IMPACT_FLAGS,
+ MOD_TRACKER);
+
+ if (!(other->flags & (FL_FLY | FL_SWIM)))
+ {
+ other->velocity[2] += 140;
+ }
+
+ damagetime = ((float)self->dmg) * FRAMETIME;
+ damagetime = damagetime / TRACKER_DAMAGE_TIME;
+
+ tracker_pain_daemon_spawn(self->owner, other, (int)damagetime);
+ }
+ else /* lots of damage (almost autogib) for dead bodies */
+ {
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin, normal,
+ self->dmg * 4, (self->dmg * 3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
+ }
+ }
+ else /* full damage in one shot for inanimate objects */
+ {
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin, normal,
+ self->dmg, (self->dmg * 3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
+ }
+ }
+
+ tracker_explode(self);
+}
+
+void
+tracker_fly(edict_t *self)
+{
+ vec3_t dest;
+ vec3_t dir;
+ vec3_t center;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1))
+ {
+ tracker_explode(self);
+ return;
+ }
+
+ /* try to hunt for center of enemy, if possible and not client */
+ if (self->enemy->client)
+ {
+ VectorCopy(self->enemy->s.origin, dest);
+ dest[2] += self->enemy->viewheight;
+ }
+ else if (VectorCompare(self->enemy->absmin, vec3_origin) ||
+ VectorCompare(self->enemy->absmax, vec3_origin))
+ {
+ VectorCopy(self->enemy->s.origin, dest);
+ }
+ else
+ {
+ VectorMA(vec3_origin, 0.5, self->enemy->absmin, center);
+ VectorMA(center, 0.5, self->enemy->absmax, center);
+ VectorCopy(center, dest);
+ }
+
+ VectorSubtract(dest, self->s.origin, dir);
+ VectorNormalize(dir);
+ vectoangles2(dir, self->s.angles);
+ VectorScale(dir, self->speed, self->velocity);
+ VectorCopy(dest, self->monsterinfo.saved_goal);
+
+ self->nextthink = level.time + 0.1;
+}
+
+void
+fire_tracker(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, edict_t *enemy)
+{
+ edict_t *bolt;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ bolt = G_Spawn();
+ VectorCopy(start, bolt->s.origin);
+ VectorCopy(start, bolt->s.old_origin);
+ vectoangles2(dir, bolt->s.angles);
+ VectorScale(dir, speed, bolt->velocity);
+ bolt->movetype = MOVETYPE_FLYMISSILE;
+ bolt->clipmask = MASK_SHOT;
+ bolt->solid = SOLID_BBOX;
+ bolt->speed = speed;
+ bolt->s.effects = EF_TRACKER;
+ bolt->s.sound = gi.soundindex("weapons/disrupt.wav");
+ VectorClear(bolt->mins);
+ VectorClear(bolt->maxs);
+
+ bolt->s.modelindex = gi.modelindex("models/proj/disintegrator/tris.md2");
+ bolt->touch = tracker_touch;
+ bolt->enemy = enemy;
+ bolt->owner = self;
+ bolt->dmg = damage;
+ bolt->classname = "tracker";
+ gi.linkentity(bolt);
+
+ if (enemy)
+ {
+ bolt->nextthink = level.time + 0.1;
+ bolt->think = tracker_fly;
+ }
+ else
+ {
+ bolt->nextthink = level.time + 10;
+ bolt->think = G_FreeEdict;
+ }
+
+ if (self->client)
+ {
+ check_dodge(self, bolt->s.origin, dir, speed);
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+
+ if (tr.fraction < 1.0)
+ {
+ VectorMA(bolt->s.origin, -10, dir, bolt->s.origin);
+ bolt->touch(bolt, tr.ent, NULL, NULL);
+ }
+}
diff --git a/rogue/src/g_phys.c b/rogue/src/g_phys.c
new file mode 100644
index 0000000..b6a6795
--- /dev/null
+++ b/rogue/src/g_phys.c
@@ -0,0 +1,1425 @@
+/* =======================================================================
+ *
+ * Quake IIs legendary physic engine.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define MAX_CLIP_PLANES 5
+#define STOP_EPSILON 0.1
+#define sv_friction 6
+#define sv_waterfriction 1
+
+void SV_Physics_NewToss(edict_t *ent);
+
+typedef struct
+{
+ edict_t *ent;
+ vec3_t origin;
+ vec3_t angles;
+} pushed_t;
+pushed_t pushed[MAX_EDICTS], *pushed_p;
+
+edict_t *obstacle;
+
+/*
+ * pushmove objects do not obey gravity, and do not interact with each other or
+ * trigger fields, but block normal movement and push normal objects when they move.
+ *
+ * onground is set for toss objects when they come to a complete rest. it is set for
+ * steping or walking objects
+ *
+ * - doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
+ * - bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
+ * - corpses are SOLID_NOT and MOVETYPE_TOSS
+ * - crates are SOLID_BBOX and MOVETYPE_TOSS
+ * - walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
+ * - flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
+ * - solid_edge items only clip against bsp models.
+ *
+ */
+
+edict_t *
+SV_TestEntityPosition(edict_t *ent)
+{
+ trace_t trace;
+ int mask;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ /* dead bodies are supposed to not be solid so lets
+ ensure they only collide with BSP during pushmoves
+ */
+ if (ent->clipmask && !(ent->svflags & SVF_DEADMONSTER))
+ {
+ mask = ent->clipmask;
+ }
+ else
+ {
+ mask = MASK_SOLID;
+ }
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ ent->s.origin, ent, mask);
+
+ if (trace.startsolid)
+ {
+ return g_edicts;
+ }
+
+ return NULL;
+}
+
+void
+SV_CheckVelocity(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (VectorLength(ent->velocity) > sv_maxvelocity->value)
+ {
+ VectorNormalize(ent->velocity);
+ VectorScale(ent->velocity, sv_maxvelocity->value, ent->velocity);
+ }
+}
+
+/*
+ * Runs thinking code for
+ * this frame if necessary
+ */
+qboolean
+SV_RunThink(edict_t *ent)
+{
+ float thinktime;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ thinktime = ent->nextthink;
+
+ if (thinktime <= 0)
+ {
+ return true;
+ }
+
+ if (thinktime > level.time + 0.001)
+ {
+ return true;
+ }
+
+ ent->nextthink = 0;
+
+ if (!ent->think)
+ {
+ gi.error("NULL ent->think");
+ }
+
+ ent->think(ent);
+
+ return false;
+}
+
+/*
+ * Two entities have touched, so
+ * run their touch functions
+ */
+void
+SV_Impact(edict_t *e1, trace_t *trace)
+{
+ edict_t *e2;
+
+ if (!e1 || !trace)
+ {
+ return;
+ }
+
+ e2 = trace->ent;
+
+ if (e1->touch && (e1->solid != SOLID_NOT))
+ {
+ e1->touch(e1, e2, &trace->plane, trace->surface);
+ }
+
+ if (e2->touch && (e2->solid != SOLID_NOT))
+ {
+ e2->touch(e2, e1, NULL, NULL);
+ }
+}
+
+/*
+ * Slide off of the impacting object
+ * returns the blocked flags (1 = floor,
+ * 2 = step / wall)
+ */
+
+int
+ClipVelocity(vec3_t in, vec3_t normal, vec3_t out, float overbounce)
+{
+ float backoff;
+ float change;
+ int i, blocked;
+
+ blocked = 0;
+
+ if (normal[2] > 0)
+ {
+ blocked |= 1; /* floor */
+ }
+
+ if (!normal[2])
+ {
+ blocked |= 2; /* step */
+ }
+
+ backoff = DotProduct(in, normal) * overbounce;
+
+ for (i = 0; i < 3; i++)
+ {
+ change = normal[i] * backoff;
+ out[i] = in[i] - change;
+
+ if ((out[i] > -STOP_EPSILON) && (out[i] < STOP_EPSILON))
+ {
+ out[i] = 0;
+ }
+ }
+
+ return blocked;
+}
+
+/*
+ * The basic solid body movement clip that slides
+ * along multiple planes. Returns the clipflags if
+ * the velocity was modified (hit something solid)
+ * 1 = floor
+ * 2 = wall / step
+ * 4 = dead stop
+ */
+int
+SV_FlyMove(edict_t *ent, float time, int mask)
+{
+ edict_t *hit;
+ int bumpcount, numbumps;
+ vec3_t dir;
+ float d;
+ int numplanes;
+ vec3_t planes[MAX_CLIP_PLANES];
+ vec3_t primal_velocity, original_velocity, new_velocity;
+ int i, j;
+ trace_t trace;
+ vec3_t end;
+ float time_left;
+ int blocked;
+
+ if (!ent)
+ {
+ return 0;
+ }
+
+ numbumps = 4;
+
+ blocked = 0;
+ VectorCopy(ent->velocity, original_velocity);
+ VectorCopy(ent->velocity, primal_velocity);
+ numplanes = 0;
+
+ time_left = time;
+
+ ent->groundentity = NULL;
+
+ for (bumpcount = 0; bumpcount < numbumps; bumpcount++)
+ {
+ for (i = 0; i < 3; i++)
+ {
+ end[i] = ent->s.origin[i] + time_left * ent->velocity[i];
+ }
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
+
+ if (trace.allsolid)
+ {
+ /* entity is trapped in another solid */
+ VectorCopy(vec3_origin, ent->velocity);
+ return 3;
+ }
+
+ if (trace.fraction > 0)
+ {
+ /* actually covered some distance */
+ VectorCopy(trace.endpos, ent->s.origin);
+ VectorCopy(ent->velocity, original_velocity);
+ numplanes = 0;
+ }
+
+ if (trace.fraction == 1)
+ {
+ break; /* moved the entire distance */
+ }
+
+ hit = trace.ent;
+
+ if (trace.plane.normal[2] > 0.7)
+ {
+ blocked |= 1; /* floor */
+
+ if (hit->solid == SOLID_BSP)
+ {
+ ent->groundentity = hit;
+ ent->groundentity_linkcount = hit->linkcount;
+ }
+ }
+
+ if (!trace.plane.normal[2])
+ {
+ blocked |= 2; /* step */
+ }
+
+ /* run the impact function */
+ SV_Impact(ent, &trace);
+
+ if (!ent->inuse)
+ {
+ break; /* removed by the impact function */
+ }
+
+ time_left -= time_left * trace.fraction;
+
+ /* cliped to another plane */
+ if (numplanes >= MAX_CLIP_PLANES)
+ {
+ /* this shouldn't really happen */
+ VectorCopy(vec3_origin, ent->velocity);
+ return 3;
+ }
+
+ VectorCopy(trace.plane.normal, planes[numplanes]);
+ numplanes++;
+
+ /* modify original_velocity so it parallels all of the clip planes */
+ for (i = 0; i < numplanes; i++)
+ {
+ ClipVelocity(original_velocity, planes[i], new_velocity, 1);
+
+ for (j = 0; j < numplanes; j++)
+ {
+ if ((j != i) && !VectorCompare(planes[i], planes[j]))
+ {
+ if (DotProduct(new_velocity, planes[j]) < 0)
+ {
+ break; /* not ok */
+ }
+ }
+ }
+
+ if (j == numplanes)
+ {
+ break;
+ }
+ }
+
+ if (i != numplanes)
+ {
+ /* go along this plane */
+ VectorCopy(new_velocity, ent->velocity);
+ }
+ else
+ {
+ /* go along the crease */
+ if (numplanes != 2)
+ {
+ VectorCopy(vec3_origin, ent->velocity);
+ return 7;
+ }
+
+ CrossProduct(planes[0], planes[1], dir);
+ d = DotProduct(dir, ent->velocity);
+ VectorScale(dir, d, ent->velocity);
+ }
+
+ /* if original velocity is against the original velocity,
+ stop dead to avoid tiny occilations in sloping corners */
+ if (DotProduct(ent->velocity, primal_velocity) <= 0)
+ {
+ VectorCopy(vec3_origin, ent->velocity);
+ return blocked;
+ }
+ }
+
+ return blocked;
+}
+
+void
+SV_AddGravity(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->gravityVector[2] > 0)
+ {
+ VectorMA(ent->velocity, ent->gravity * sv_gravity->value * FRAMETIME,
+ ent->gravityVector, ent->velocity);
+ }
+ else
+ {
+ ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
+ }
+}
+
+/*
+ * Returns the actual bounding box of a bmodel.
+ * This is a big improvement over what q2 normally
+ * does with rotating bmodels - q2 sets absmin,
+ * absmax to a cube that will completely contain
+ * the bmodel at *any* rotation on *any* axis, whether
+ * the bmodel can actually rotate to that angle or not.
+ * This leads to a lot of false block tests in SV_Push
+ * if another bmodel is in the vicinity.
+ */
+void
+RealBoundingBox(edict_t *ent, vec3_t mins, vec3_t maxs)
+{
+ vec3_t forward, left, up, f1, l1, u1;
+ vec3_t p[8];
+ int i, j, k, j2, k4;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ for (k = 0; k < 2; k++)
+ {
+ k4 = k * 4;
+
+ if (k)
+ {
+ p[k4][2] = ent->maxs[2];
+ }
+ else
+ {
+ p[k4][2] = ent->mins[2];
+ }
+
+ p[k4 + 1][2] = p[k4][2];
+ p[k4 + 2][2] = p[k4][2];
+ p[k4 + 3][2] = p[k4][2];
+
+ for (j = 0; j < 2; j++)
+ {
+ j2 = j * 2;
+
+ if (j)
+ {
+ p[j2 + k4][1] = ent->maxs[1];
+ }
+ else
+ {
+ p[j2 + k4][1] = ent->mins[1];
+ }
+
+ p[j2 + k4 + 1][1] = p[j2 + k4][1];
+
+ for (i = 0; i < 2; i++)
+ {
+ if (i)
+ {
+ p[i + j2 + k4][0] = ent->maxs[0];
+ }
+ else
+ {
+ p[i + j2 + k4][0] = ent->mins[0];
+ }
+ }
+ }
+ }
+
+ AngleVectors(ent->s.angles, forward, left, up);
+
+ for (i = 0; i < 8; i++)
+ {
+ VectorScale(forward, p[i][0], f1);
+ VectorScale(left, -p[i][1], l1);
+ VectorScale(up, p[i][2], u1);
+ VectorAdd(ent->s.origin, f1, p[i]);
+ VectorAdd(p[i], l1, p[i]);
+ VectorAdd(p[i], u1, p[i]);
+ }
+
+ VectorCopy(p[0], mins);
+ VectorCopy(p[0], maxs);
+
+ for (i = 1; i < 8; i++)
+ {
+ if (mins[0] > p[i][0])
+ {
+ mins[0] = p[i][0];
+ }
+
+ if (mins[1] > p[i][1])
+ {
+ mins[1] = p[i][1];
+ }
+
+ if (mins[2] > p[i][2])
+ {
+ mins[2] = p[i][2];
+ }
+
+ if (maxs[0] < p[i][0])
+ {
+ maxs[0] = p[i][0];
+ }
+
+ if (maxs[1] < p[i][1])
+ {
+ maxs[1] = p[i][1];
+ }
+
+ if (maxs[2] < p[i][2])
+ {
+ maxs[2] = p[i][2];
+ }
+ }
+}
+
+/*
+ * Does not change the entities velocity at all
+ */
+trace_t
+SV_PushEntity(edict_t *ent, vec3_t push)
+{
+ trace_t trace;
+ vec3_t start;
+ vec3_t end;
+ int mask;
+
+ VectorCopy(ent->s.origin, start);
+ VectorAdd(start, push, end);
+
+retry:
+
+ if (ent->clipmask)
+ {
+ mask = ent->clipmask;
+ }
+ else
+ {
+ mask = MASK_SOLID;
+ }
+
+ trace = gi.trace(start, ent->mins, ent->maxs, end, ent, mask);
+
+ /* startsolid treats different-content volumes
+ as continuous, like the bbox of a monster/player
+ and the floor of an elevator. So do another trace
+ that only collides with BSP so that we make a best
+ effort to keep these entities inside non-solid space
+ */
+ if (trace.startsolid && (mask & ~MASK_SOLID))
+ {
+ trace = gi.trace (start, ent->mins, ent->maxs, end, ent, MASK_SOLID);
+ }
+
+ VectorCopy(trace.endpos, ent->s.origin);
+ gi.linkentity(ent);
+
+ /* Push slightly away from non-horizontal surfaces,
+ prevent origin stuck in the plane which causes
+ the entity to be rendered in full black. */
+ if (trace.plane.type != 2)
+ {
+ /* Limit the fix to gibs, debris and dead monsters.
+ Everything else may break existing maps. Items
+ may slide to unreachable locations, monsters may
+ get stuck, etc. */
+ if (((strncmp(ent->classname, "monster_", 8) == 0) && ent->health < 1) ||
+ (strcmp(ent->classname, "debris") == 0) || (ent->s.effects & EF_GIB))
+ {
+ VectorAdd(ent->s.origin, trace.plane.normal, ent->s.origin);
+ }
+ }
+
+ if (trace.fraction != 1.0)
+ {
+ SV_Impact(ent, &trace);
+
+ /* if the pushed entity went away and the pusher is still there */
+ if (!trace.ent->inuse && ent->inuse)
+ {
+ /* move the pusher back and try again */
+ VectorCopy(start, ent->s.origin);
+ gi.linkentity(ent);
+ goto retry;
+ }
+ }
+
+ ent->gravity = 1.0;
+
+ if (ent->inuse)
+ {
+ G_TouchTriggers(ent);
+ }
+
+ return trace;
+}
+
+/*
+ * Objects need to be moved back on a failed push,
+ * otherwise riders would continue to slide.
+ */
+qboolean
+SV_Push(edict_t *pusher, vec3_t move, vec3_t amove)
+{
+ int i, e;
+ edict_t *check, *block;
+ pushed_t *p;
+ vec3_t org, org2, move2, forward, right, up;
+ vec3_t realmins, realmaxs;
+
+ if (!pusher)
+ {
+ return false;
+ }
+
+ /* clamp the move to 1/8 units, so the position
+ will be accurate for client side prediction */
+ for (i = 0; i < 3; i++)
+ {
+ float temp;
+ temp = move[i] * 8.0;
+
+ if (temp > 0.0)
+ {
+ temp += 0.5;
+ }
+ else
+ {
+ temp -= 0.5;
+ }
+
+ move[i] = 0.125 * (int)temp;
+ }
+
+ /* we need this for pushing things later */
+ VectorSubtract(vec3_origin, amove, org);
+ AngleVectors(org, forward, right, up);
+
+ /* save the pusher's original position */
+ pushed_p->ent = pusher;
+ VectorCopy(pusher->s.origin, pushed_p->origin);
+ VectorCopy(pusher->s.angles, pushed_p->angles);
+ pushed_p++;
+
+ /* move the pusher to it's final position */
+ VectorAdd(pusher->s.origin, move, pusher->s.origin);
+ VectorAdd(pusher->s.angles, amove, pusher->s.angles);
+ gi.linkentity(pusher);
+
+ /* Create a real bounding box for
+ rotating brush models. */
+ RealBoundingBox(pusher, realmins, realmaxs);
+
+ /* see if any solid entities are inside the final position */
+ check = g_edicts + 1;
+
+ for (e = 1; e < globals.num_edicts; e++, check++)
+ {
+ if (!check->inuse)
+ {
+ continue;
+ }
+
+ if ((check->movetype == MOVETYPE_PUSH) ||
+ (check->movetype == MOVETYPE_STOP) ||
+ (check->movetype == MOVETYPE_NONE) ||
+ (check->movetype == MOVETYPE_NOCLIP))
+ {
+ continue;
+ }
+
+ if (!check->area.prev)
+ {
+ continue; /* not linked in anywhere */
+ }
+
+ /* if the entity is standing on the pusher, it will definitely be moved */
+ if (check->groundentity != pusher)
+ {
+ /* see if the ent needs to be tested */
+ if ((check->absmin[0] >= realmaxs[0]) ||
+ (check->absmin[1] >= realmaxs[1]) ||
+ (check->absmin[2] >= realmaxs[2]) ||
+ (check->absmax[0] <= realmins[0]) ||
+ (check->absmax[1] <= realmins[1]) ||
+ (check->absmax[2] <= realmins[2]))
+ {
+ continue;
+ }
+
+ /* see if the ent's bbox is inside the pusher's final position */
+ if (!SV_TestEntityPosition(check))
+ {
+ continue;
+ }
+ }
+
+ if ((pusher->movetype == MOVETYPE_PUSH) ||
+ (check->groundentity == pusher))
+ {
+ /* move this entity */
+ pushed_p->ent = check;
+ VectorCopy(check->s.origin, pushed_p->origin);
+ VectorCopy(check->s.angles, pushed_p->angles);
+ pushed_p++;
+
+ /* try moving the contacted entity */
+ VectorAdd(check->s.origin, move, check->s.origin);
+
+ /* figure movement due to the pusher's amove */
+ VectorSubtract(check->s.origin, pusher->s.origin, org);
+ org2[0] = DotProduct(org, forward);
+ org2[1] = -DotProduct(org, right);
+ org2[2] = DotProduct(org, up);
+ VectorSubtract(org2, org, move2);
+ VectorAdd(check->s.origin, move2, check->s.origin);
+
+ /* may have pushed them off an edge */
+ if (check->groundentity != pusher)
+ {
+ check->groundentity = NULL;
+ }
+
+ block = SV_TestEntityPosition(check);
+
+ if (!block)
+ {
+ /* pushed ok */
+ gi.linkentity(check);
+
+ /* impact? */
+ continue;
+ }
+
+ /* if it is ok to leave in the old position, do it
+ this is only relevent for riding entities, not pushed */
+ VectorSubtract(check->s.origin, move, check->s.origin);
+ block = SV_TestEntityPosition(check);
+
+ if (!block)
+ {
+ pushed_p--;
+ continue;
+ }
+ }
+
+ /* save off the obstacle so we can call the block function */
+ obstacle = check;
+
+ /* move back any entities we already moved
+ go backwards, so if the same entity was pushed
+ twice, it goes back to the original position */
+ for (p = pushed_p - 1; p >= pushed; p--)
+ {
+ VectorCopy(p->origin, p->ent->s.origin);
+ VectorCopy(p->angles, p->ent->s.angles);
+
+ gi.linkentity(p->ent);
+ }
+
+ return false;
+ }
+
+ /* see if anything we moved has touched a trigger */
+ for (p = pushed_p - 1; p >= pushed; p--)
+ {
+ G_TouchTriggers(p->ent);
+ }
+
+ return true;
+}
+
+/*
+ * Bmodel objects don't interact with each
+ * other, but push all box objects
+ */
+void
+SV_Physics_Pusher(edict_t *ent)
+{
+ vec3_t move, amove;
+ edict_t *part, *mv;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* if not a team captain, so movement
+ will be handled elsewhere */
+ if (ent->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ /* make sure all team slaves can move before commiting any moves
+ or calling any think functionsif the move is blocked, all moved
+ objects will be backed out */
+ pushed_p = pushed;
+
+ for (part = ent; part; part = part->teamchain)
+ {
+ if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
+ part->avelocity[0] || part->avelocity[1] || part->avelocity[2])
+ {
+ /* object is moving */
+ VectorScale(part->velocity, FRAMETIME, move);
+ VectorScale(part->avelocity, FRAMETIME, amove);
+
+ if (!SV_Push(part, move, amove))
+ {
+ break; /* move was blocked */
+ }
+ }
+ }
+
+ if (pushed_p > &pushed[MAX_EDICTS-1])
+ {
+ gi.error("pushed_p > &pushed[MAX_EDICTS-1], memory corrupted");
+ }
+
+ if (part)
+ {
+ /* the move failed, bump all nextthink times and back out moves */
+ for (mv = ent; mv; mv = mv->teamchain)
+ {
+ if (mv->nextthink > 0)
+ {
+ mv->nextthink += FRAMETIME;
+ }
+ }
+
+ /* if the pusher has a "blocked" function, call it
+ otherwise, just stay in place until the obstacle
+ is gone */
+ if (part->blocked)
+ {
+ part->blocked(part, obstacle);
+ }
+ }
+ else
+ {
+ /* the move succeeded, so call all think functions */
+ for (part = ent; part; part = part->teamchain)
+ {
+ /* prevent entities that are on trains that have gone away from thinking! */
+ if (part->inuse)
+ {
+ SV_RunThink(part);
+ }
+ }
+ }
+}
+
+/*
+ * Non moving objects can only think
+ */
+void
+SV_Physics_None(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* regular thinking */
+ SV_RunThink(ent);
+}
+
+/*
+ * A moving object that doesn't obey physics
+ */
+void
+SV_Physics_Noclip(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* regular thinking */
+ if (!SV_RunThink(ent))
+ {
+ return;
+ }
+
+ VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+ VectorMA(ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin);
+
+ gi.linkentity(ent);
+}
+
+/*
+ * Toss, bounce, and fly movement. When onground, do nothing.
+ */
+void
+SV_Physics_Toss(edict_t *ent)
+{
+ trace_t trace;
+ vec3_t move;
+ float backoff;
+ edict_t *slave;
+ qboolean wasinwater;
+ qboolean isinwater;
+ vec3_t old_origin;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* regular thinking */
+ SV_RunThink(ent);
+
+ /* entities are very often freed during thinking */
+ if (!ent->inuse)
+ {
+ return;
+ }
+
+ /* if not a team captain, so movement will be handled elsewhere */
+ if (ent->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ if (ent->velocity[2] > 0)
+ {
+ ent->groundentity = NULL;
+ }
+
+ /* check for the groundentity going away */
+ if (ent->groundentity)
+ {
+ if (!ent->groundentity->inuse)
+ {
+ ent->groundentity = NULL;
+ }
+ }
+
+ /* if onground, return without moving */
+ if (ent->groundentity && (ent->gravity > 0.0))
+ {
+ return;
+ }
+
+ VectorCopy(ent->s.origin, old_origin);
+
+ SV_CheckVelocity(ent);
+
+ /* add gravity */
+ if ((ent->movetype != MOVETYPE_FLY) &&
+ (ent->movetype != MOVETYPE_FLYMISSILE))
+ {
+ SV_AddGravity(ent);
+ }
+
+ /* move angles */
+ VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+
+ /* move origin */
+ VectorScale(ent->velocity, FRAMETIME, move);
+ trace = SV_PushEntity(ent, move);
+
+ if (!ent->inuse)
+ {
+ return;
+ }
+
+ if (trace.fraction < 1)
+ {
+ if (ent->movetype == MOVETYPE_BOUNCE)
+ {
+ backoff = 1.5;
+ }
+ else
+ {
+ backoff = 1;
+ }
+
+ ClipVelocity(ent->velocity, trace.plane.normal, ent->velocity, backoff);
+
+ /* stop if on ground */
+ if (trace.plane.normal[2] > 0.7)
+ {
+ if ((ent->velocity[2] < 60) || (ent->movetype != MOVETYPE_BOUNCE))
+ {
+ ent->groundentity = trace.ent;
+ ent->groundentity_linkcount = trace.ent->linkcount;
+ VectorCopy(vec3_origin, ent->velocity);
+ VectorCopy(vec3_origin, ent->avelocity);
+ }
+ }
+ }
+
+ /* check for water transition */
+ wasinwater = (ent->watertype & MASK_WATER);
+ ent->watertype = gi.pointcontents(ent->s.origin);
+ isinwater = ent->watertype & MASK_WATER;
+
+ if (isinwater)
+ {
+ ent->waterlevel = 1;
+ }
+ else
+ {
+ ent->waterlevel = 0;
+ }
+
+ if (!wasinwater && isinwater)
+ {
+ /* don't play splash sound for entities already in water on level start */
+ if (level.framenum > 3)
+ {
+ gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+ }
+ }
+ else if (wasinwater && !isinwater)
+ {
+ gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+ }
+
+ /* move teamslaves */
+ for (slave = ent->teamchain; slave; slave = slave->teamchain)
+ {
+ VectorCopy(ent->s.origin, slave->s.origin);
+ gi.linkentity(slave);
+ }
+}
+
+/*
+ * Monsters freefall when they don't have a ground entity, otherwise
+ * all movement is done with discrete steps.
+ *
+ * This is also used for objects that have become still on the ground, but
+ * will fall if the floor is pulled out from under them.
+ */
+void
+SV_AddRotationalFriction(edict_t *ent)
+{
+ int n;
+ float adjustment;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+ adjustment = FRAMETIME * sv_stopspeed->value * sv_friction;
+
+ for (n = 0; n < 3; n++)
+ {
+ if (ent->avelocity[n] > 0)
+ {
+ ent->avelocity[n] -= adjustment;
+
+ if (ent->avelocity[n] < 0)
+ {
+ ent->avelocity[n] = 0;
+ }
+ }
+ else
+ {
+ ent->avelocity[n] += adjustment;
+
+ if (ent->avelocity[n] > 0)
+ {
+ ent->avelocity[n] = 0;
+ }
+ }
+ }
+}
+
+void
+SV_Physics_Step(edict_t *ent)
+{
+ qboolean wasonground;
+ qboolean hitsound = false;
+ float *vel;
+ float speed, newspeed, control;
+ float friction;
+ edict_t *groundentity;
+ int mask;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* airborn monsters should always check for ground */
+ if (!ent->groundentity)
+ {
+ M_CheckGround(ent);
+ }
+
+ groundentity = ent->groundentity;
+
+ SV_CheckVelocity(ent);
+
+ if (groundentity)
+ {
+ wasonground = true;
+ }
+ else
+ {
+ wasonground = false;
+ }
+
+ if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
+ {
+ SV_AddRotationalFriction(ent);
+ }
+
+ /* add gravity except:
+ flying monsters
+ swimming monsters who are in the water */
+ if (!wasonground)
+ {
+ if (!(ent->flags & FL_FLY))
+ {
+ if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2)))
+ {
+ if (ent->velocity[2] < sv_gravity->value * -0.1)
+ {
+ hitsound = true;
+ }
+
+ if (ent->waterlevel == 0)
+ {
+ SV_AddGravity(ent);
+ }
+ }
+ }
+ }
+
+ /* friction for flying monsters that have been given vertical velocity */
+ if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
+ {
+ speed = fabs(ent->velocity[2]);
+ control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
+ friction = sv_friction / 3;
+ newspeed = speed - (FRAMETIME * control * friction);
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+ ent->velocity[2] *= newspeed;
+ }
+
+ /* friction for flying monsters that have been given vertical velocity */
+ if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
+ {
+ speed = fabs(ent->velocity[2]);
+ control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
+ newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+ ent->velocity[2] *= newspeed;
+ }
+
+ if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])
+ {
+ /* apply friction */
+ if ((wasonground) || (ent->flags & (FL_SWIM | FL_FLY)))
+ {
+ if (!((ent->health <= 0.0) && !M_CheckBottom(ent)))
+ {
+ vel = ent->velocity;
+ speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]);
+
+ if (speed)
+ {
+ friction = sv_friction;
+
+ control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
+ newspeed = speed - FRAMETIME * control * friction;
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+
+ vel[0] *= newspeed;
+ vel[1] *= newspeed;
+ }
+ }
+ }
+
+ if (ent->svflags & SVF_MONSTER)
+ {
+ mask = MASK_MONSTERSOLID;
+ }
+ else
+ {
+ mask = MASK_SOLID;
+ }
+
+ SV_FlyMove(ent, FRAMETIME, mask);
+ gi.linkentity(ent);
+ ent->gravity = 1.0;
+ G_TouchTriggers(ent);
+
+ if (!ent->inuse)
+ {
+ return;
+ }
+
+ if (ent->groundentity)
+ {
+ if (!wasonground)
+ {
+ if (hitsound)
+ {
+ gi.sound(ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
+ }
+ }
+ }
+ }
+
+ if (!ent->inuse) /* g_touchtrigger free problem */
+ {
+ return;
+ }
+
+ /* regular thinking */
+ SV_RunThink(ent);
+}
+
+void
+G_RunEntity(edict_t *ent)
+{
+ trace_t trace;
+ vec3_t previous_origin;
+ qboolean saved_origin;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->movetype == MOVETYPE_STEP)
+ {
+ VectorCopy(ent->s.origin, previous_origin);
+ saved_origin = true;
+ }
+ else
+ {
+ saved_origin = false;
+ }
+
+ if (ent->prethink)
+ {
+ ent->prethink(ent);
+ }
+
+ switch ((int)ent->movetype)
+ {
+ case MOVETYPE_PUSH:
+ case MOVETYPE_STOP:
+ SV_Physics_Pusher(ent);
+ break;
+ case MOVETYPE_NONE:
+ SV_Physics_None(ent);
+ break;
+ case MOVETYPE_NOCLIP:
+ SV_Physics_Noclip(ent);
+ break;
+ case MOVETYPE_STEP:
+ SV_Physics_Step(ent);
+ break;
+ case MOVETYPE_TOSS:
+ case MOVETYPE_BOUNCE:
+ case MOVETYPE_FLY:
+ case MOVETYPE_FLYMISSILE:
+ SV_Physics_Toss(ent);
+ break;
+ case MOVETYPE_NEWTOSS:
+ SV_Physics_NewToss(ent);
+ break;
+ default:
+ gi.error("SV_Physics: bad movetype %i", (int)ent->movetype);
+ }
+
+ /* if we moved, check and fix origin if needed */
+ /* also check inuse since entities are very often freed while thinking */
+ if (saved_origin && ent->inuse && !VectorCompare(ent->s.origin, previous_origin))
+ {
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ previous_origin, ent, MASK_MONSTERSOLID);
+
+ if (trace.allsolid || trace.startsolid)
+ {
+ VectorCopy(previous_origin, ent->s.origin);
+ }
+ }
+}
+
+/*
+ * Toss, bounce, and fly movement. When on ground and
+ * no velocity, do nothing. With velocity, slide.
+ */
+void
+SV_Physics_NewToss(edict_t *ent)
+{
+ trace_t trace;
+ vec3_t move;
+ edict_t *slave;
+ qboolean wasinwater;
+ qboolean isinwater;
+ float speed, newspeed;
+ vec3_t old_origin;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* regular thinking */
+ SV_RunThink(ent);
+
+ /* if not a team captain, so movement will be handled elsewhere */
+ if (ent->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ /* find out what we're sitting on. */
+ VectorCopy(ent->s.origin, move);
+ move[2] -= 0.25;
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ move, ent, ent->clipmask);
+
+ if (ent->groundentity && ent->groundentity->inuse)
+ {
+ ent->groundentity = trace.ent;
+ }
+ else
+ {
+ ent->groundentity = NULL;
+ }
+
+ /* if we're sitting on something flat and have no velocity of our own, return. */
+ if (ent->groundentity && (trace.plane.normal[2] == 1.0) &&
+ !ent->velocity[0] && !ent->velocity[1] && !ent->velocity[2])
+ {
+ return;
+ }
+
+ /* store the old origin */
+ VectorCopy(ent->s.origin, old_origin);
+
+ SV_CheckVelocity(ent);
+
+ /* add gravity */
+ SV_AddGravity(ent);
+
+ if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
+ {
+ SV_AddRotationalFriction(ent);
+ }
+
+ /* add friction */
+ speed = VectorLength(ent->velocity);
+
+ if (ent->waterlevel) /* friction for water movement */
+ {
+ newspeed = speed - (sv_waterfriction * 6 * ent->waterlevel);
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+ VectorScale(ent->velocity, newspeed, ent->velocity);
+ }
+ else if (!ent->groundentity) /* friction for air movement */
+ {
+ newspeed = speed - ((sv_friction));
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+ VectorScale(ent->velocity, newspeed, ent->velocity);
+ }
+ else /* use ground friction */
+ {
+ newspeed = speed - (sv_friction * 6);
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+ VectorScale(ent->velocity, newspeed, ent->velocity);
+ }
+
+ SV_FlyMove(ent, FRAMETIME, ent->clipmask);
+ gi.linkentity(ent);
+
+ G_TouchTriggers(ent);
+
+ /* check for water transition */
+ wasinwater = (ent->watertype & MASK_WATER);
+ ent->watertype = gi.pointcontents(ent->s.origin);
+ isinwater = ent->watertype & MASK_WATER;
+
+ if (isinwater)
+ {
+ ent->waterlevel = 1;
+ }
+ else
+ {
+ ent->waterlevel = 0;
+ }
+
+ if (!wasinwater && isinwater)
+ {
+ gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+ }
+ else if (wasinwater && !isinwater)
+ {
+ gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+ }
+
+ /* move teamslaves */
+ for (slave = ent->teamchain; slave; slave = slave->teamchain)
+ {
+ VectorCopy(ent->s.origin, slave->s.origin);
+ gi.linkentity(slave);
+ }
+}
diff --git a/rogue/src/g_spawn.c b/rogue/src/g_spawn.c
new file mode 100644
index 0000000..fcd94e9
--- /dev/null
+++ b/rogue/src/g_spawn.c
@@ -0,0 +1,1809 @@
+/*
+ * =======================================================================
+ *
+ * Item spawning.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define LEG_WAIT_TIME 1
+#define MAX_LEGSFRAME 23
+
+#define SPAWNGROW_LIFESPAN 0.3
+#define STEPSIZE 18
+
+typedef struct
+{
+ char *name;
+ void (*spawn)(edict_t *ent);
+} spawn_t;
+
+void SP_item_health(edict_t *self);
+void SP_item_health_small(edict_t *self);
+void SP_item_health_large(edict_t *self);
+void SP_item_health_mega(edict_t *self);
+
+void SP_info_player_start(edict_t *ent);
+void SP_info_player_deathmatch(edict_t *ent);
+void SP_info_player_coop(edict_t *ent);
+void SP_info_player_intermission(edict_t *ent);
+
+void SP_func_plat(edict_t *ent);
+void SP_func_rotating(edict_t *ent);
+void SP_func_button(edict_t *ent);
+void SP_func_door(edict_t *ent);
+void SP_func_door_secret(edict_t *ent);
+void SP_func_door_rotating(edict_t *ent);
+void SP_func_water(edict_t *ent);
+void SP_func_train(edict_t *ent);
+void SP_func_conveyor(edict_t *self);
+void SP_func_wall(edict_t *self);
+void SP_func_object(edict_t *self);
+void SP_func_explosive(edict_t *self);
+void SP_func_timer(edict_t *self);
+void SP_func_areaportal(edict_t *ent);
+void SP_func_clock(edict_t *ent);
+void SP_func_killbox(edict_t *ent);
+
+void SP_trigger_always(edict_t *ent);
+void SP_trigger_once(edict_t *ent);
+void SP_trigger_multiple(edict_t *ent);
+void SP_trigger_relay(edict_t *ent);
+void SP_trigger_push(edict_t *ent);
+void SP_trigger_hurt(edict_t *ent);
+void SP_trigger_key(edict_t *ent);
+void SP_trigger_counter(edict_t *ent);
+void SP_trigger_elevator(edict_t *ent);
+void SP_trigger_gravity(edict_t *ent);
+void SP_trigger_monsterjump(edict_t *ent);
+
+void SP_target_temp_entity(edict_t *ent);
+void SP_target_speaker(edict_t *ent);
+void SP_target_explosion(edict_t *ent);
+void SP_target_changelevel(edict_t *ent);
+void SP_target_secret(edict_t *ent);
+void SP_target_goal(edict_t *ent);
+void SP_target_splash(edict_t *ent);
+void SP_target_spawner(edict_t *ent);
+void SP_target_blaster(edict_t *ent);
+void SP_target_crosslevel_trigger(edict_t *ent);
+void SP_target_crosslevel_target(edict_t *ent);
+void SP_target_laser(edict_t *self);
+void SP_target_help(edict_t *ent);
+void SP_target_lightramp(edict_t *self);
+void SP_target_earthquake(edict_t *ent);
+void SP_target_character(edict_t *ent);
+void SP_target_string(edict_t *ent);
+
+void SP_worldspawn(edict_t *ent);
+void SP_viewthing(edict_t *ent);
+
+void SP_light(edict_t *self);
+void SP_light_mine1(edict_t *ent);
+void SP_light_mine2(edict_t *ent);
+void SP_info_null(edict_t *self);
+void SP_info_notnull(edict_t *self);
+void SP_path_corner(edict_t *self);
+void SP_point_combat(edict_t *self);
+
+void SP_misc_explobox(edict_t *self);
+void SP_misc_banner(edict_t *self);
+void SP_misc_satellite_dish(edict_t *self);
+void SP_misc_gib_arm(edict_t *self);
+void SP_misc_gib_leg(edict_t *self);
+void SP_misc_gib_head(edict_t *self);
+void SP_misc_insane(edict_t *self);
+void SP_misc_deadsoldier(edict_t *self);
+void SP_misc_viper(edict_t *self);
+void SP_misc_viper_bomb(edict_t *self);
+void SP_misc_bigviper(edict_t *self);
+void SP_misc_strogg_ship(edict_t *self);
+void SP_misc_teleporter(edict_t *self);
+void SP_misc_teleporter_dest(edict_t *self);
+void SP_misc_blackhole(edict_t *self);
+void SP_misc_eastertank(edict_t *self);
+void SP_misc_easterchick(edict_t *self);
+void SP_misc_easterchick2(edict_t *self);
+
+void SP_monster_berserk(edict_t *self);
+void SP_monster_gladiator(edict_t *self);
+void SP_monster_gunner(edict_t *self);
+void SP_monster_infantry(edict_t *self);
+void SP_monster_soldier_light(edict_t *self);
+void SP_monster_soldier(edict_t *self);
+void SP_monster_soldier_ss(edict_t *self);
+void SP_monster_tank(edict_t *self);
+void SP_monster_medic(edict_t *self);
+void SP_monster_flipper(edict_t *self);
+void SP_monster_chick(edict_t *self);
+void SP_monster_parasite(edict_t *self);
+void SP_monster_flyer(edict_t *self);
+void SP_monster_brain(edict_t *self);
+void SP_monster_floater(edict_t *self);
+void SP_monster_hover(edict_t *self);
+void SP_monster_mutant(edict_t *self);
+void SP_monster_supertank(edict_t *self);
+void SP_monster_boss2(edict_t *self);
+void SP_monster_jorg(edict_t *self);
+void SP_monster_makron(edict_t *self);
+void SP_monster_boss3_stand(edict_t *self);
+
+void SP_monster_commander_body(edict_t *self);
+
+void SP_turret_breach(edict_t *self);
+void SP_turret_base(edict_t *self);
+void SP_turret_driver(edict_t *self);
+
+void SP_func_plat2(edict_t *ent);
+void SP_func_door_secret2(edict_t *ent);
+void SP_func_force_wall(edict_t *ent);
+void SP_info_player_coop_lava(edict_t *self);
+void SP_info_teleport_destination(edict_t *self);
+void SP_trigger_teleport(edict_t *self);
+void SP_trigger_disguise(edict_t *self);
+void SP_monster_stalker(edict_t *self);
+void SP_monster_turret(edict_t *self);
+void SP_target_steam(edict_t *self);
+void SP_target_anger(edict_t *self);
+void SP_target_killplayers(edict_t *self);
+
+void SP_target_blacklight(edict_t *self);
+void SP_target_orb(edict_t *self);
+
+void SP_hint_path(edict_t *self);
+void SP_monster_carrier(edict_t *self);
+void SP_monster_widow(edict_t *self);
+void SP_monster_widow2(edict_t *self);
+void SP_dm_tag_token(edict_t *self);
+void SP_dm_dball_goal(edict_t *self);
+void SP_dm_dball_ball(edict_t *self);
+void SP_dm_dball_team1_start(edict_t *self);
+void SP_dm_dball_team2_start(edict_t *self);
+void SP_dm_dball_ball_start(edict_t *self);
+void SP_dm_dball_speed_change(edict_t *self);
+void SP_monster_kamikaze(edict_t *self);
+void SP_turret_invisible_brain(edict_t *self);
+void SP_xatrix_item(edict_t *self);
+void SP_misc_nuke_core(edict_t *self);
+
+void ThrowMoreStuff(edict_t *self, vec3_t point);
+void ThrowSmallStuff(edict_t *self, vec3_t point);
+void ThrowWidowGibLoc(edict_t *self, char *gibname, int damage,
+ int type, vec3_t startpos, qboolean fade);
+void ThrowWidowGibSized(edict_t *self, char *gibname, int damage, int type,
+ vec3_t startpos, int hitsound, qboolean fade);
+
+spawn_t spawns[] = {
+ {"item_health", SP_item_health},
+ {"item_health_small", SP_item_health_small},
+ {"item_health_large", SP_item_health_large},
+ {"item_health_mega", SP_item_health_mega},
+
+ {"info_player_start", SP_info_player_start},
+ {"info_player_deathmatch", SP_info_player_deathmatch},
+ {"info_player_coop", SP_info_player_coop},
+ {"info_player_intermission", SP_info_player_intermission},
+
+ {"func_plat", SP_func_plat},
+ {"func_button", SP_func_button},
+ {"func_door", SP_func_door},
+ {"func_door_secret", SP_func_door_secret},
+ {"func_door_rotating", SP_func_door_rotating},
+ {"func_rotating", SP_func_rotating},
+ {"func_train", SP_func_train},
+ {"func_water", SP_func_water},
+ {"func_conveyor", SP_func_conveyor},
+ {"func_areaportal", SP_func_areaportal},
+ {"func_clock", SP_func_clock},
+ {"func_wall", SP_func_wall},
+ {"func_object", SP_func_object},
+ {"func_timer", SP_func_timer},
+ {"func_explosive", SP_func_explosive},
+ {"func_killbox", SP_func_killbox},
+
+ {"trigger_always", SP_trigger_always},
+ {"trigger_once", SP_trigger_once},
+ {"trigger_multiple", SP_trigger_multiple},
+ {"trigger_relay", SP_trigger_relay},
+ {"trigger_push", SP_trigger_push},
+ {"trigger_hurt", SP_trigger_hurt},
+ {"trigger_key", SP_trigger_key},
+ {"trigger_counter", SP_trigger_counter},
+ {"trigger_elevator", SP_trigger_elevator},
+ {"trigger_gravity", SP_trigger_gravity},
+ {"trigger_monsterjump", SP_trigger_monsterjump},
+
+ {"target_temp_entity", SP_target_temp_entity},
+ {"target_speaker", SP_target_speaker},
+ {"target_explosion", SP_target_explosion},
+ {"target_changelevel", SP_target_changelevel},
+ {"target_secret", SP_target_secret},
+ {"target_goal", SP_target_goal},
+ {"target_splash", SP_target_splash},
+ {"target_spawner", SP_target_spawner},
+ {"target_blaster", SP_target_blaster},
+ {"target_crosslevel_trigger", SP_target_crosslevel_trigger},
+ {"target_crosslevel_target", SP_target_crosslevel_target},
+ {"target_laser", SP_target_laser},
+ {"target_help", SP_target_help},
+ {"target_lightramp", SP_target_lightramp},
+ {"target_earthquake", SP_target_earthquake},
+ {"target_character", SP_target_character},
+ {"target_string", SP_target_string},
+
+ {"worldspawn", SP_worldspawn},
+ {"viewthing", SP_viewthing},
+
+ {"light", SP_light},
+ {"light_mine1", SP_light_mine1},
+ {"light_mine2", SP_light_mine2},
+ {"info_null", SP_info_null},
+ {"func_group", SP_info_null},
+ {"info_notnull", SP_info_notnull},
+ {"path_corner", SP_path_corner},
+ {"point_combat", SP_point_combat},
+
+ {"misc_explobox", SP_misc_explobox},
+ {"misc_banner", SP_misc_banner},
+ {"misc_satellite_dish", SP_misc_satellite_dish},
+ {"misc_gib_arm", SP_misc_gib_arm},
+ {"misc_gib_leg", SP_misc_gib_leg},
+ {"misc_gib_head", SP_misc_gib_head},
+ {"misc_insane", SP_misc_insane},
+ {"misc_deadsoldier", SP_misc_deadsoldier},
+ {"misc_viper", SP_misc_viper},
+ {"misc_viper_bomb", SP_misc_viper_bomb},
+ {"misc_bigviper", SP_misc_bigviper},
+ {"misc_strogg_ship", SP_misc_strogg_ship},
+ {"misc_teleporter", SP_misc_teleporter},
+ {"misc_teleporter_dest", SP_misc_teleporter_dest},
+ {"misc_blackhole", SP_misc_blackhole},
+ {"misc_eastertank", SP_misc_eastertank},
+ {"misc_easterchick", SP_misc_easterchick},
+ {"misc_easterchick2", SP_misc_easterchick2},
+
+ {"monster_berserk", SP_monster_berserk},
+ {"monster_gladiator", SP_monster_gladiator},
+ {"monster_gunner", SP_monster_gunner},
+ {"monster_infantry", SP_monster_infantry},
+ {"monster_soldier_light", SP_monster_soldier_light},
+ {"monster_soldier", SP_monster_soldier},
+ {"monster_soldier_ss", SP_monster_soldier_ss},
+ {"monster_tank", SP_monster_tank},
+ {"monster_tank_commander", SP_monster_tank},
+ {"monster_medic", SP_monster_medic},
+ {"monster_flipper", SP_monster_flipper},
+ {"monster_chick", SP_monster_chick},
+ {"monster_parasite", SP_monster_parasite},
+ {"monster_flyer", SP_monster_flyer},
+ {"monster_brain", SP_monster_brain},
+ {"monster_floater", SP_monster_floater},
+ {"monster_hover", SP_monster_hover},
+ {"monster_mutant", SP_monster_mutant},
+ {"monster_supertank", SP_monster_supertank},
+ {"monster_boss2", SP_monster_boss2},
+ {"monster_boss3_stand", SP_monster_boss3_stand},
+ {"monster_makron", SP_monster_makron},
+ {"monster_jorg", SP_monster_jorg},
+
+ {"monster_commander_body", SP_monster_commander_body},
+
+ {"turret_breach", SP_turret_breach},
+ {"turret_base", SP_turret_base},
+ {"turret_driver", SP_turret_driver},
+
+ {"func_plat2", SP_func_plat2},
+ {"func_door_secret2", SP_func_door_secret2},
+ {"func_force_wall", SP_func_force_wall},
+ {"trigger_teleport", SP_trigger_teleport},
+ {"trigger_disguise", SP_trigger_disguise},
+ {"info_teleport_destination", SP_info_teleport_destination},
+ {"info_player_coop_lava", SP_info_player_coop_lava},
+ {"monster_stalker", SP_monster_stalker},
+ {"monster_turret", SP_monster_turret},
+ {"target_steam", SP_target_steam},
+ {"target_anger", SP_target_anger},
+ {"target_killplayers", SP_target_killplayers},
+ {"target_blacklight", SP_target_blacklight},
+ {"target_orb", SP_target_orb},
+ {"monster_daedalus", SP_monster_hover},
+ {"hint_path", SP_hint_path},
+ {"monster_carrier", SP_monster_carrier},
+ {"monster_widow", SP_monster_widow},
+ {"monster_widow2", SP_monster_widow2},
+ {"monster_medic_commander", SP_monster_medic},
+ {"dm_tag_token", SP_dm_tag_token},
+ {"dm_dball_goal", SP_dm_dball_goal},
+ {"dm_dball_ball", SP_dm_dball_ball},
+ {"dm_dball_team1_start", SP_dm_dball_team1_start},
+ {"dm_dball_team2_start", SP_dm_dball_team2_start},
+ {"dm_dball_ball_start", SP_dm_dball_ball_start},
+ {"dm_dball_speed_change", SP_dm_dball_speed_change},
+ {"monster_kamikaze", SP_monster_kamikaze},
+ {"turret_invisible_brain", SP_turret_invisible_brain},
+ {"misc_nuke_core", SP_misc_nuke_core},
+
+ {"ammo_magslug", SP_xatrix_item},
+ {"ammo_trap", SP_xatrix_item},
+ {"item_quadfire", SP_xatrix_item},
+ {"weapon_boomer", SP_xatrix_item},
+ {"weapon_phalanx", SP_xatrix_item},
+
+ {NULL, NULL}
+};
+
+/*
+ * Finds the spawn function for the entity and calls it
+ */
+void
+ED_CallSpawn(edict_t *ent)
+{
+ spawn_t *s;
+ gitem_t *item;
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->classname)
+ {
+ gi.dprintf("ED_CallSpawn: NULL classname\n");
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->gravityVector[0] = 0.0;
+ ent->gravityVector[1] = 0.0;
+ ent->gravityVector[2] = -1.0;
+
+ if (!strcmp(ent->classname, "weapon_nailgun"))
+ {
+ ent->classname = (FindItem("ETF Rifle"))->classname;
+ }
+
+ if (!strcmp(ent->classname, "ammo_nails"))
+ {
+ ent->classname = (FindItem("Flechettes"))->classname;
+ }
+
+ if (!strcmp(ent->classname, "weapon_heatbeam"))
+ {
+ ent->classname = (FindItem("Plasma Beam"))->classname;
+ }
+
+ /* check item spawn functions */
+ for (i = 0, item = itemlist; i < game.num_items; i++, item++)
+ {
+ if (!item->classname)
+ {
+ continue;
+ }
+
+ if (!strcmp(item->classname, ent->classname))
+ {
+ /* found it */
+ SpawnItem(ent, item);
+ return;
+ }
+ }
+
+ /* check normal spawn functions */
+ for (s = spawns; s->name; s++)
+ {
+ if (!strcmp(s->name, ent->classname))
+ {
+ /* found it */
+ s->spawn(ent);
+ return;
+ }
+ }
+
+ gi.dprintf("%s doesn't have a spawn function\n", ent->classname);
+}
+
+char *
+ED_NewString(const char *string)
+{
+ char *newb, *new_p;
+ int i, l;
+
+ if (!string)
+ {
+ return NULL;
+ }
+
+ l = strlen(string) + 1;
+
+ newb = gi.TagMalloc(l, TAG_LEVEL);
+
+ new_p = newb;
+
+ for (i = 0; i < l; i++)
+ {
+ if ((string[i] == '\\') && (i < l - 1))
+ {
+ i++;
+
+ if (string[i] == 'n')
+ {
+ *new_p++ = '\n';
+ }
+ else
+ {
+ *new_p++ = '\\';
+ }
+ }
+ else
+ {
+ *new_p++ = string[i];
+ }
+ }
+
+ return newb;
+}
+
+/*
+ * Takes a key/value pair and sets
+ * the binary values in an edict
+ */
+void
+ED_ParseField(const char *key, const char *value, edict_t *ent)
+{
+ field_t *f;
+ byte *b;
+ float v;
+ vec3_t vec;
+
+ if (!ent || !value || !key)
+ {
+ return;
+ }
+
+ for (f = fields; f->name; f++)
+ {
+ if (!(f->flags & FFL_NOSPAWN) && !Q_strcasecmp(f->name, (char *)key))
+ {
+ /* found it */
+ if (f->flags & FFL_SPAWNTEMP)
+ {
+ b = (byte *)&st;
+ }
+ else
+ {
+ b = (byte *)ent;
+ }
+
+ switch (f->type)
+ {
+ case F_LSTRING:
+ *(char **)(b + f->ofs) = ED_NewString(value);
+ break;
+ case F_VECTOR:
+ sscanf(value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
+ ((float *)(b + f->ofs))[0] = vec[0];
+ ((float *)(b + f->ofs))[1] = vec[1];
+ ((float *)(b + f->ofs))[2] = vec[2];
+ break;
+ case F_INT:
+ *(int *)(b + f->ofs) = (int)strtol(value, (char **)NULL, 10);
+ break;
+ case F_FLOAT:
+ *(float *)(b + f->ofs) = strtof(value, (char **)NULL);;
+ break;
+ case F_ANGLEHACK:
+ v = strtof(value, (char **)NULL);;
+ ((float *)(b + f->ofs))[0] = 0;
+ ((float *)(b + f->ofs))[1] = v;
+ ((float *)(b + f->ofs))[2] = 0;
+ break;
+ case F_IGNORE:
+ break;
+ default:
+ break;
+ }
+
+ return;
+ }
+ }
+
+ gi.dprintf("%s is not a field\n", key);
+}
+
+/*
+ * Parses an edict out of the given string, returning the new position
+ */
+char *
+ED_ParseEdict(char *data, edict_t *ent)
+{
+ qboolean init;
+ char keyname[256];
+ const char *com_token;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ init = false;
+ memset(&st, 0, sizeof(st));
+
+ /* go through all the dictionary pairs */
+ while (1)
+ {
+ /* parse key */
+ com_token = COM_Parse(&data);
+
+ if (com_token[0] == '}')
+ {
+ break;
+ }
+
+ if (!data)
+ {
+ gi.error("ED_ParseEntity: EOF without closing brace");
+ }
+
+ strncpy(keyname, com_token, sizeof(keyname) - 1);
+
+ /* parse value */
+ com_token = COM_Parse(&data);
+
+ if (!data)
+ {
+ gi.error("ED_ParseEntity: EOF without closing brace");
+ }
+
+ if (com_token[0] == '}')
+ {
+ gi.error("ED_ParseEntity: closing brace without data");
+ }
+
+ init = true;
+
+ /* keynames with a leading underscore are used for
+ utility comments, and are immediately discarded
+ by quake */
+ if (keyname[0] == '_')
+ {
+ continue;
+ }
+
+ ED_ParseField(keyname, com_token, ent);
+ }
+
+ if (!init)
+ {
+ memset(ent, 0, sizeof(*ent));
+ }
+
+ return data;
+}
+
+/*
+ * Chain together all entities with a matching team field.
+ *
+ * All but the first will have the FL_TEAMSLAVE flag set.
+ * All but the last will have the teamchain field set to the next one
+ */
+void
+G_FixTeams(void)
+{
+ edict_t *e, *e2, *chain;
+ int i, j;
+ int c, c2;
+
+ c = 0;
+ c2 = 0;
+
+ for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
+ {
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->team)
+ {
+ continue;
+ }
+
+ if (!strcmp(e->classname, "func_train"))
+ {
+ if (e->flags & FL_TEAMSLAVE)
+ {
+ chain = e;
+ e->teammaster = e;
+ e->teamchain = NULL;
+ e->flags &= ~FL_TEAMSLAVE;
+ c++;
+ c2++;
+
+ for (j = 1, e2 = g_edicts + j;
+ j < globals.num_edicts;
+ j++, e2++)
+ {
+ if (e2 == e)
+ {
+ continue;
+ }
+
+ if (!e2->inuse)
+ {
+ continue;
+ }
+
+ if (!e2->team)
+ {
+ continue;
+ }
+
+ if (!strcmp(e->team, e2->team))
+ {
+ c2++;
+ chain->teamchain = e2;
+ e2->teammaster = e;
+ e2->teamchain = NULL;
+ chain = e2;
+ e2->flags |= FL_TEAMSLAVE;
+ e2->movetype = MOVETYPE_PUSH;
+ e2->speed = e->speed;
+ }
+ }
+ }
+ }
+ }
+
+ gi.dprintf("%i teams repaired\n", c);
+}
+
+void
+G_FindTeams(void)
+{
+ edict_t *e, *e2, *chain;
+ int i, j;
+ int c, c2;
+
+ c = 0;
+ c2 = 0;
+
+ for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
+ {
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->team)
+ {
+ continue;
+ }
+
+ if (e->flags & FL_TEAMSLAVE)
+ {
+ continue;
+ }
+
+ chain = e;
+ e->teammaster = e;
+ c++;
+ c2++;
+
+ for (j = i + 1, e2 = e + 1; j < globals.num_edicts; j++, e2++)
+ {
+ if (!e2->inuse)
+ {
+ continue;
+ }
+
+ if (!e2->team)
+ {
+ continue;
+ }
+
+ if (e2->flags & FL_TEAMSLAVE)
+ {
+ continue;
+ }
+
+ if (!strcmp(e->team, e2->team))
+ {
+ c2++;
+ chain->teamchain = e2;
+ e2->teammaster = e;
+ chain = e2;
+ e2->flags |= FL_TEAMSLAVE;
+ }
+ }
+ }
+
+ G_FixTeams();
+
+ gi.dprintf("%i teams with %i entities.\n", c, c2);
+}
+
+/*
+ * Creates a server's entity / program execution context by
+ * parsing textual entity definitions out of an ent file.
+ */
+void
+SpawnEntities(const char *mapname, char *entities, const char *spawnpoint)
+{
+ edict_t *ent;
+ int inhibit;
+ const char *com_token;
+ int i;
+ float skill_level;
+
+ if (!mapname || !entities || !spawnpoint)
+ {
+ return;
+ }
+
+ skill_level = floor(skill->value);
+
+ if (skill_level < 0)
+ {
+ skill_level = 0;
+ }
+
+ if (skill_level > 3)
+ {
+ skill_level = 3;
+ }
+
+ if (skill->value != skill_level)
+ {
+ gi.cvar_forceset("skill", va("%f", skill_level));
+ }
+
+ SaveClientData();
+
+ gi.FreeTags(TAG_LEVEL);
+
+ memset(&level, 0, sizeof(level));
+ memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0]));
+
+ strncpy(level.mapname, mapname, sizeof(level.mapname) - 1);
+ strncpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint) - 1);
+
+ /* set client fields on player ents */
+ for (i = 0; i < game.maxclients; i++)
+ {
+ g_edicts[i + 1].client = game.clients + i;
+ }
+
+ ent = NULL;
+ inhibit = 0;
+
+ /* parse ents */
+ while (1)
+ {
+ /* parse the opening brace */
+ com_token = COM_Parse(&entities);
+
+ if (!entities)
+ {
+ break;
+ }
+
+ if (com_token[0] != '{')
+ {
+ gi.error("ED_LoadFromFile: found %s when expecting {", com_token);
+ }
+
+ if (!ent)
+ {
+ ent = g_edicts;
+ }
+ else
+ {
+ ent = G_Spawn();
+ }
+
+ entities = ED_ParseEdict(entities, ent);
+
+ /* yet another map hack */
+ if (!Q_stricmp(level.mapname, "command") &&
+ !Q_stricmp(ent->classname, "trigger_once") && !Q_stricmp(ent->model, "*27"))
+ {
+ ent->spawnflags &= ~SPAWNFLAG_NOT_HARD;
+ }
+
+ /* ahh, the joys of map hacks .. */
+ if (!Q_stricmp(level.mapname, "rhangar2") &&
+ !Q_stricmp(ent->classname, "func_door_rotating") &&
+ ent->targetname && !Q_stricmp(ent->targetname, "t265"))
+ {
+ ent->spawnflags &= ~SPAWNFLAG_NOT_COOP;
+ }
+
+ if (!Q_stricmp(level.mapname, "rhangar2") &&
+ !Q_stricmp(ent->classname, "trigger_always") &&
+ ent->target && !Q_stricmp(ent->target, "t265"))
+ {
+ ent->spawnflags |= SPAWNFLAG_NOT_COOP;
+ }
+
+ if (!Q_stricmp(level.mapname, "rhangar2") &&
+ !Q_stricmp(ent->classname, "func_wall") &&
+ !Q_stricmp(ent->model, "*15"))
+ {
+ ent->spawnflags |= SPAWNFLAG_NOT_COOP;
+ }
+
+ /* remove things (except the world) from
+ different skill levels or deathmatch */
+ if (ent != g_edicts)
+ {
+ if (deathmatch->value)
+ {
+ if (ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH)
+ {
+ G_FreeEdict(ent);
+ inhibit++;
+ continue;
+ }
+ }
+ else if (coop->value && !coop_baseq2->value)
+ {
+ if (ent->spawnflags & SPAWNFLAG_NOT_COOP)
+ {
+ G_FreeEdict(ent);
+ inhibit++;
+ continue;
+ }
+
+ /* stuff marked !easy & !med & !hard are coop only, all levels */
+ if (!((ent->spawnflags & SPAWNFLAG_NOT_EASY) &&
+ (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM) &&
+ (ent->spawnflags & SPAWNFLAG_NOT_HARD)))
+ {
+ if (((skill->value == SKILL_EASY) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
+ ((skill->value == SKILL_MEDIUM) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
+ (((skill->value == SKILL_HARD) || (skill->value == SKILL_HARDPLUS)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)))
+ {
+ G_FreeEdict(ent);
+ inhibit++;
+ continue;
+ }
+ }
+ }
+ else
+ {
+ if (((skill->value == SKILL_EASY) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
+ ((skill->value == SKILL_MEDIUM) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
+ (((skill->value == SKILL_HARD) || (skill->value == SKILL_HARDPLUS)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)))
+ {
+ G_FreeEdict(ent);
+ inhibit++;
+ continue;
+ }
+ }
+
+ ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY | SPAWNFLAG_NOT_MEDIUM |
+ SPAWNFLAG_NOT_HARD | SPAWNFLAG_NOT_COOP | SPAWNFLAG_NOT_DEATHMATCH);
+ }
+
+ ent->gravityVector[0] = 0.0;
+ ent->gravityVector[1] = 0.0;
+ ent->gravityVector[2] = -1.0;
+
+ ED_CallSpawn(ent);
+
+ ent->s.renderfx |= RF_IR_VISIBLE;
+ }
+
+ gi.dprintf("%i entities inhibited.\n", inhibit);
+
+ G_FindTeams();
+
+ PlayerTrail_Init();
+
+ if (deathmatch->value)
+ {
+ if (randomrespawn && randomrespawn->value)
+ {
+ PrecacheForRandomRespawn();
+ }
+ }
+ else
+ {
+ InitHintPaths();
+ }
+
+ if (deathmatch->value && gamerules && gamerules->value)
+ {
+ if (DMGame.PostInitSetup)
+ {
+ DMGame.PostInitSetup();
+ }
+ }
+}
+
+/* =================================================================== */
+
+char *single_statusbar =
+"yb -24 "
+
+/* health */
+"xv 0 "
+"hnum "
+"xv 50 "
+"pic 0 "
+
+/* ammo */
+"if 2 "
+" xv 100 "
+" anum "
+" xv 150 "
+" pic 2 "
+"endif "
+
+/* armor */
+"if 4 "
+" xv 200 "
+" rnum "
+" xv 250 "
+" pic 4 "
+"endif "
+
+/* selected item */
+"if 6 "
+" xv 296 "
+" pic 6 "
+"endif "
+
+"yb -50 "
+
+/* picked up item */
+"if 7 "
+" xv 0 "
+" pic 7 "
+" xv 26 "
+" yb -42 "
+" stat_string 8 "
+" yb -50 "
+"endif "
+
+/* timer */
+"if 9 "
+" xv 262 "
+" num 2 10 "
+" xv 296 "
+" pic 9 "
+"endif "
+
+/* help / weapon icon */
+"if 11 "
+" xv 148 "
+" pic 11 "
+"endif "
+;
+
+char *dm_statusbar =
+"yb -24 "
+
+/* health */
+"xv 0 "
+"hnum "
+"xv 50 "
+"pic 0 "
+
+/* ammo */
+"if 2 "
+" xv 100 "
+" anum "
+" xv 150 "
+" pic 2 "
+"endif "
+
+/* armor */
+"if 4 "
+" xv 200 "
+" rnum "
+" xv 250 "
+" pic 4 "
+"endif "
+
+/* selected item */
+"if 6 "
+" xv 296 "
+" pic 6 "
+"endif "
+
+"yb -50 "
+
+/* picked up item */
+"if 7 "
+" xv 0 "
+" pic 7 "
+" xv 26 "
+" yb -42 "
+" stat_string 8 "
+" yb -50 "
+"endif "
+
+/* timer */
+"if 9 "
+" xv 246 "
+" num 2 10 "
+" xv 296 "
+" pic 9 "
+"endif "
+
+/* help / weapon icon */
+"if 11 "
+" xv 148 "
+" pic 11 "
+"endif "
+
+/* frags */
+"xr -50 "
+"yt 2 "
+"num 3 14 "
+
+/* spectator */
+"if 17 "
+"xv 0 "
+"yb -58 "
+"string2 \"SPECTATOR MODE\" "
+"endif "
+
+/* chase camera */
+"if 16 "
+"xv 0 "
+"yb -68 "
+"string \"Chasing\" "
+"xv 64 "
+"stat_string 16 "
+"endif "
+;
+
+/*
+ * QUAKED worldspawn (0 0 0) ?
+ *
+ * Only used for the world.
+ * "sky" environment map name
+ * "skyaxis" vector axis for rotating sky
+ * "skyrotate" speed of rotation in degrees/second
+ * "sounds" music cd track number
+ * "gravity" 800 is default gravity
+ * "message" text to print at user logon
+ */
+void
+SP_worldspawn(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ ent->inuse = true; /* since the world doesn't use G_Spawn() */
+ ent->s.modelindex = 1; /* world model is always index 1 */
+
+ /* reserve some spots for dead player
+ bodies for coop / deathmatch */
+ InitBodyQue();
+
+ /* set configstrings for items */
+ SetItemNames();
+
+ if (st.nextmap)
+ {
+ strcpy(level.nextmap, st.nextmap);
+ }
+
+ /* make some data visible to the server */
+ if (ent->message && ent->message[0])
+ {
+ gi.configstring(CS_NAME, ent->message);
+ Q_strlcpy(level.level_name, ent->message, sizeof(level.level_name));
+ }
+ else
+ {
+ Q_strlcpy(level.level_name, level.mapname, sizeof(level.level_name));
+ }
+
+ if (st.sky && st.sky[0])
+ {
+ gi.configstring(CS_SKY, st.sky);
+ }
+ else
+ {
+ gi.configstring(CS_SKY, "unit1_");
+ }
+
+ gi.configstring(CS_SKYROTATE, va("%f", st.skyrotate));
+ gi.configstring(CS_SKYAXIS, va("%f %f %f", st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]));
+ gi.configstring(CS_CDTRACK, va("%i", ent->sounds));
+ gi.configstring(CS_MAXCLIENTS, va("%i", (int)(maxclients->value)));
+
+ /* status bar program */
+ if (deathmatch->value)
+ {
+ gi.configstring(CS_STATUSBAR, dm_statusbar);
+ }
+ else
+ {
+ gi.configstring(CS_STATUSBAR, single_statusbar);
+ }
+
+ /* help icon for statusbar */
+ gi.imageindex("i_help");
+ level.pic_health = gi.imageindex("i_health");
+ gi.imageindex("help");
+ gi.imageindex("field_3");
+
+ if (!st.gravity)
+ {
+ gi.cvar_set("sv_gravity", "800");
+ }
+ else
+ {
+ gi.cvar_set("sv_gravity", st.gravity);
+ }
+
+ snd_fry = gi.soundindex("player/fry.wav"); /* standing in lava / slime */
+
+ PrecacheItem(FindItem("Blaster"));
+
+ gi.soundindex("player/lava1.wav");
+ gi.soundindex("player/lava2.wav");
+
+ gi.soundindex("misc/pc_up.wav");
+ gi.soundindex("misc/talk1.wav");
+
+ gi.soundindex("misc/udeath.wav");
+
+ /* gibs */
+ gi.soundindex("items/respawn1.wav");
+
+ /* sexed sounds */
+ gi.soundindex("*death1.wav");
+ gi.soundindex("*death2.wav");
+ gi.soundindex("*death3.wav");
+ gi.soundindex("*death4.wav");
+ gi.soundindex("*fall1.wav");
+ gi.soundindex("*fall2.wav");
+ gi.soundindex("*gurp1.wav"); /* drowning damage */
+ gi.soundindex("*gurp2.wav");
+ gi.soundindex("*jump1.wav"); /* player jump */
+ gi.soundindex("*pain25_1.wav");
+ gi.soundindex("*pain25_2.wav");
+ gi.soundindex("*pain50_1.wav");
+ gi.soundindex("*pain50_2.wav");
+ gi.soundindex("*pain75_1.wav");
+ gi.soundindex("*pain75_2.wav");
+ gi.soundindex("*pain100_1.wav");
+ gi.soundindex("*pain100_2.wav");
+
+ /* sexed models: THIS ORDER MUST MATCH THE DEFINES IN g_local.h
+ you can add more, max 19 (pete change)these models are only
+ loaded in coop or deathmatch. not singleplayer. */
+ if (coop->value || deathmatch->value)
+ {
+ gi.modelindex("#w_blaster.md2");
+ gi.modelindex("#w_shotgun.md2");
+ gi.modelindex("#w_sshotgun.md2");
+ gi.modelindex("#w_machinegun.md2");
+ gi.modelindex("#w_chaingun.md2");
+ gi.modelindex("#a_grenades.md2");
+ gi.modelindex("#w_glauncher.md2");
+ gi.modelindex("#w_rlauncher.md2");
+ gi.modelindex("#w_hyperblaster.md2");
+ gi.modelindex("#w_railgun.md2");
+ gi.modelindex("#w_bfg.md2");
+ gi.modelindex("#w_disrupt.md2");
+ gi.modelindex("#w_etfrifle.md2");
+ gi.modelindex("#w_plasma.md2");
+ gi.modelindex("#w_plauncher.md2");
+ gi.modelindex("#w_chainfist.md2");
+ }
+
+ /* ------------------- */
+
+ gi.soundindex("player/gasp1.wav"); /* gasping for air */
+ gi.soundindex("player/gasp2.wav"); /* head breaking surface, not gasping */
+
+ gi.soundindex("player/watr_in.wav"); /* feet hitting water */
+ gi.soundindex("player/watr_out.wav"); /* feet leaving water */
+
+ gi.soundindex("player/watr_un.wav"); /* head going underwater */
+
+ gi.soundindex("player/u_breath1.wav");
+ gi.soundindex("player/u_breath2.wav");
+
+ gi.soundindex("items/pkup.wav"); /* bonus item pickup */
+ gi.soundindex("world/land.wav"); /* landing thud */
+ gi.soundindex("misc/h2ohit1.wav"); /* landing splash */
+
+ gi.soundindex("items/damage.wav");
+ gi.soundindex("misc/ddamage1.wav");
+ gi.soundindex("items/protect.wav");
+ gi.soundindex("items/protect4.wav");
+ gi.soundindex("weapons/noammo.wav");
+
+ gi.soundindex("infantry/inflies1.wav");
+
+ sm_meat_index = gi.modelindex("models/objects/gibs/sm_meat/tris.md2");
+ gi.modelindex("models/objects/gibs/arm/tris.md2");
+ gi.modelindex("models/objects/gibs/bone/tris.md2");
+ gi.modelindex("models/objects/gibs/bone2/tris.md2");
+ gi.modelindex("models/objects/gibs/chest/tris.md2");
+ gi.modelindex("models/objects/gibs/skull/tris.md2");
+ gi.modelindex("models/objects/gibs/head2/tris.md2");
+
+ /* Setup light animation tables. 'a' is total darkness, 'z' is doublebright. */
+
+ /* 0 normal */
+ gi.configstring(CS_LIGHTS + 0, "m");
+
+ /* 1 FLICKER (first variety) */
+ gi.configstring(CS_LIGHTS + 1, "mmnmmommommnonmmonqnmmo");
+
+ /* 2 SLOW STRONG PULSE */
+ gi.configstring(CS_LIGHTS + 2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
+
+ /* 3 CANDLE (first variety) */
+ gi.configstring(CS_LIGHTS + 3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
+
+ /* 4 FAST STROBE */
+ gi.configstring(CS_LIGHTS + 4, "mamamamamama");
+
+ /* 5 GENTLE PULSE 1 */
+ gi.configstring(CS_LIGHTS + 5, "jklmnopqrstuvwxyzyxwvutsrqponmlkj");
+
+ /* 6 FLICKER (second variety) */
+ gi.configstring(CS_LIGHTS + 6, "nmonqnmomnmomomno");
+
+ /* 7 CANDLE (second variety) */
+ gi.configstring(CS_LIGHTS + 7, "mmmaaaabcdefgmmmmaaaammmaamm");
+
+ /* 8 CANDLE (third variety) */
+ gi.configstring(CS_LIGHTS + 8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
+
+ /* 9 SLOW STROBE (fourth variety) */
+ gi.configstring(CS_LIGHTS + 9, "aaaaaaaazzzzzzzz");
+
+ /* 10 FLUORESCENT FLICKER */
+ gi.configstring(CS_LIGHTS + 10, "mmamammmmammamamaaamammma");
+
+ /* 11 SLOW PULSE NOT FADE TO BLACK */
+ gi.configstring(CS_LIGHTS + 11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
+
+ /* styles 32-62 are assigned by the light program for switchable lights */
+
+ /* 63 testing */
+ gi.configstring(CS_LIGHTS + 63, "a");
+}
+
+/*
+ * Monster spawning code:
+ * Used by the carrier, the medic_commander, and the black widow
+ *
+ * The sequence to create a flying monster is:
+ * FindSpawnPoint - tries to find suitable spot to spawn the monster in
+ * CreateFlyMonster - this verifies the point as good and creates the monster
+ *
+ * To create a ground walking monster:
+ * FindSpawnPoint - same thing
+ * CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable
+ */
+
+edict_t *
+CreateMonster(vec3_t origin, vec3_t angles, char *classname)
+{
+ edict_t *newEnt;
+
+ if (!classname)
+ {
+ return NULL;
+ }
+
+ newEnt = G_Spawn();
+
+ VectorCopy(origin, newEnt->s.origin);
+ VectorCopy(angles, newEnt->s.angles);
+ newEnt->classname = ED_NewString(classname);
+ newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+
+ VectorSet(newEnt->gravityVector, 0, 0, -1);
+ ED_CallSpawn(newEnt);
+ newEnt->s.renderfx |= RF_IR_VISIBLE;
+
+ return newEnt;
+}
+
+edict_t *
+CreateFlyMonster(vec3_t origin, vec3_t angles, vec3_t mins,
+ vec3_t maxs, char *classname)
+{
+ if (!classname)
+ {
+ return NULL;
+ }
+
+ if (!mins || !maxs ||
+ VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin))
+ {
+ DetermineBBox(classname, mins, maxs);
+ }
+
+ if (!CheckSpawnPoint(origin, mins, maxs))
+ {
+ return NULL;
+ }
+
+ return CreateMonster(origin, angles, classname);
+}
+
+edict_t *
+CreateGroundMonster(vec3_t origin, vec3_t angles, vec3_t entMins,
+ vec3_t entMaxs, char *classname, int height)
+{
+ edict_t *newEnt;
+ vec3_t mins, maxs;
+
+ if (!classname)
+ {
+ return NULL;
+ }
+
+ /* if they don't provide us a bounding box, figure it out */
+ if (!entMins || !entMaxs || VectorCompare(entMins,
+ vec3_origin) || VectorCompare(entMaxs, vec3_origin))
+ {
+ DetermineBBox(classname, mins, maxs);
+ }
+ else
+ {
+ VectorCopy(entMins, mins);
+ VectorCopy(entMaxs, maxs);
+ }
+
+ /* check the ground to make sure it's there, it's relatively flat, and it's not toxic */
+ if (!CheckGroundSpawnPoint(origin, mins, maxs, height, -1))
+ {
+ return NULL;
+ }
+
+ newEnt = CreateMonster(origin, angles, classname);
+
+ if (!newEnt)
+ {
+ return NULL;
+ }
+
+ return newEnt;
+}
+
+qboolean
+FindSpawnPoint(vec3_t startpoint, vec3_t mins, vec3_t maxs,
+ vec3_t spawnpoint, float maxMoveUp)
+{
+ trace_t tr;
+ vec3_t top;
+
+ tr = gi.trace(startpoint, mins, maxs, startpoint,
+ NULL, MASK_MONSTERSOLID | CONTENTS_PLAYERCLIP);
+
+ if ((tr.startsolid || tr.allsolid) || (tr.ent != world))
+ {
+ VectorCopy(startpoint, top);
+ top[2] += maxMoveUp;
+
+ tr = gi.trace(top, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID);
+
+ if (tr.startsolid || tr.allsolid)
+ {
+ return false;
+ }
+ else
+ {
+ VectorCopy(tr.endpos, spawnpoint);
+ return true;
+ }
+ }
+ else
+ {
+ VectorCopy(startpoint, spawnpoint);
+ return true;
+ }
+}
+
+qboolean
+CheckSpawnPoint(vec3_t origin, vec3_t mins, vec3_t maxs)
+{
+ trace_t tr;
+
+ if (!mins || !maxs ||
+ VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin))
+ {
+ return false;
+ }
+
+ tr = gi.trace(origin, mins, maxs, origin, NULL, MASK_MONSTERSOLID);
+
+ if (tr.startsolid || tr.allsolid)
+ {
+ return false;
+ }
+
+ if (tr.ent != world)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+qboolean
+CheckGroundSpawnPoint(vec3_t origin, vec3_t entMins, vec3_t entMaxs,
+ float height, float gravity)
+{
+ trace_t tr;
+ vec3_t start, stop;
+ vec3_t mins, maxs;
+ int x, y;
+ float mid, bottom;
+
+ if (!CheckSpawnPoint(origin, entMins, entMaxs))
+ {
+ return false;
+ }
+
+
+ VectorCopy(origin, stop);
+ stop[2] = origin[2] + entMins[2] - height;
+
+ tr = gi.trace(origin, entMins, entMaxs, stop,
+ NULL, MASK_MONSTERSOLID | MASK_WATER);
+
+ if ((tr.fraction < 1) && (tr.contents & MASK_MONSTERSOLID))
+ {
+ /* first, do the midpoint trace */
+ VectorAdd(tr.endpos, entMins, mins);
+ VectorAdd(tr.endpos, entMaxs, maxs);
+
+ /* first, do the easy flat check */
+ if (gravity > 0)
+ {
+ start[2] = maxs[2] + 1;
+ }
+ else
+ {
+ start[2] = mins[2] - 1;
+ }
+
+ for (x = 0; x <= 1; x++)
+ {
+ for (y = 0; y <= 1; y++)
+ {
+ start[0] = x ? maxs[0] : mins[0];
+ start[1] = y ? maxs[1] : mins[1];
+
+ if (gi.pointcontents(start) != CONTENTS_SOLID)
+ {
+ goto realcheck;
+ }
+ }
+ }
+
+ /* if it passed all four above checks, we're done */
+ return true;
+
+ realcheck:
+
+ /* check it for real */
+ start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5;
+ start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5;
+ start[2] = mins[2];
+
+ tr = gi.trace(start, vec3_origin, vec3_origin,
+ stop, NULL, MASK_MONSTERSOLID);
+
+ if (tr.fraction == 1.0)
+ {
+ return false;
+ }
+
+ if (gravity < 0)
+ {
+ start[2] = mins[2];
+ stop[2] = start[2] - STEPSIZE - STEPSIZE;
+ mid = bottom = tr.endpos[2] + entMins[2];
+ }
+ else
+ {
+ start[2] = maxs[2];
+ stop[2] = start[2] + STEPSIZE + STEPSIZE;
+ mid = bottom = tr.endpos[2] - entMaxs[2];
+ }
+
+ for (x = 0; x <= 1; x++)
+ {
+ for (y = 0; y <= 1; y++)
+ {
+ start[0] = stop[0] = x ? maxs[0] : mins[0];
+ start[1] = stop[1] = y ? maxs[1] : mins[1];
+
+ tr = gi.trace(start, vec3_origin, vec3_origin,
+ stop, NULL, MASK_MONSTERSOLID);
+
+ if (gravity > 0)
+ {
+ if ((tr.fraction != 1.0) && (tr.endpos[2] < bottom))
+ {
+ bottom = tr.endpos[2];
+ }
+
+ if ((tr.fraction == 1.0) || (tr.endpos[2] - mid > STEPSIZE))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if ((tr.fraction != 1.0) && (tr.endpos[2] > bottom))
+ {
+ bottom = tr.endpos[2];
+ }
+
+ if ((tr.fraction == 1.0) || (mid - tr.endpos[2] > STEPSIZE))
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true; /* we can land on it, it's ok */
+ }
+
+ /* otherwise, it's either water (bad) or not
+ * there (too far) if we're here, it's bad below */
+ return false;
+}
+
+void
+DetermineBBox(char *classname, vec3_t mins, vec3_t maxs)
+{
+ edict_t *newEnt;
+
+ if (!classname)
+ {
+ return;
+ }
+
+ newEnt = G_Spawn();
+
+ VectorCopy(vec3_origin, newEnt->s.origin);
+ VectorCopy(vec3_origin, newEnt->s.angles);
+ newEnt->classname = ED_NewString(classname);
+ newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+
+ ED_CallSpawn(newEnt);
+
+ VectorCopy(newEnt->mins, mins);
+ VectorCopy(newEnt->maxs, maxs);
+
+ G_FreeEdict(newEnt);
+}
+
+
+void
+spawngrow_think(edict_t *self)
+{
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ self->s.angles[0] = rand() % 360;
+ self->s.angles[1] = rand() % 360;
+ self->s.angles[2] = rand() % 360;
+ }
+
+ if ((level.time < self->wait) && (self->s.frame < 2))
+ {
+ self->s.frame++;
+ }
+
+ if (level.time >= self->wait)
+ {
+ if (self->s.effects & EF_SPHERETRANS)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+ else if (self->s.frame > 0)
+ {
+ self->s.frame--;
+ }
+ else
+ {
+ G_FreeEdict(self);
+ return;
+ }
+ }
+
+ self->nextthink += FRAMETIME;
+}
+
+void
+SpawnGrow_Spawn(vec3_t startpos, int size)
+{
+ edict_t *ent;
+ int i;
+ float lifespan;
+
+ ent = G_Spawn();
+ VectorCopy(startpos, ent->s.origin);
+
+ for (i = 0; i < 2; i++)
+ {
+ ent->s.angles[0] = rand() % 360;
+ ent->s.angles[1] = rand() % 360;
+ ent->s.angles[2] = rand() % 360;
+ }
+
+ ent->solid = SOLID_NOT;
+ ent->s.renderfx = RF_IR_VISIBLE;
+ ent->movetype = MOVETYPE_NONE;
+ ent->classname = "spawngro";
+
+ if (size <= 1)
+ {
+ lifespan = SPAWNGROW_LIFESPAN;
+ ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2");
+ }
+ else if (size == 2)
+ {
+ ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2");
+ lifespan = 2;
+ }
+ else
+ {
+ ent->s.modelindex = gi.modelindex("models/items/spawngro/tris.md2");
+ lifespan = SPAWNGROW_LIFESPAN;
+ }
+
+ ent->think = spawngrow_think;
+
+ ent->wait = level.time + lifespan;
+ ent->nextthink = level.time + FRAMETIME;
+
+ if (size != 2)
+ {
+ ent->s.effects |= EF_SPHERETRANS;
+ }
+
+ gi.linkentity(ent);
+}
+
+void
+widowlegs_think(edict_t *self)
+{
+ vec3_t offset;
+ vec3_t point;
+ vec3_t f, r, u;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == 17)
+ {
+ VectorSet(offset, 11.77, -7.24, 23.31);
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, point);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(point);
+ gi.multicast(point, MULTICAST_ALL);
+ ThrowSmallStuff(self, point);
+ }
+
+ if (self->s.frame < MAX_LEGSFRAME)
+ {
+ self->s.frame++;
+ self->nextthink = level.time + FRAMETIME;
+ return;
+ }
+ else if (self->wait == 0)
+ {
+ self->wait = level.time + LEG_WAIT_TIME;
+ }
+
+ if (level.time > self->wait)
+ {
+ AngleVectors(self->s.angles, f, r, u);
+
+ VectorSet(offset, -65.6, -8.44, 28.59);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, point);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(point);
+ gi.multicast(point, MULTICAST_ALL);
+ ThrowSmallStuff(self, point);
+
+ ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2",
+ 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
+ ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2",
+ 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
+
+ VectorSet(offset, -1.04, -51.18, 7.04);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, point);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(point);
+ gi.multicast(point, MULTICAST_ALL);
+ ThrowSmallStuff(self, point);
+
+ ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2",
+ 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
+ ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2",
+ 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
+ ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2",
+ 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true);
+
+ G_FreeEdict(self);
+ return;
+ }
+
+ if ((level.time > (self->wait - 0.5)) && (self->count == 0))
+ {
+ self->count = 1;
+ AngleVectors(self->s.angles, f, r, u);
+
+ VectorSet(offset, 31, -88.7, 10.96);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, point);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(point);
+ gi.multicast(point, MULTICAST_ALL);
+
+ VectorSet(offset, -12.67, -4.39, 15.68);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, point);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(point);
+ gi.multicast(point, MULTICAST_ALL);
+
+ self->nextthink = level.time + FRAMETIME;
+ return;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+Widowlegs_Spawn(vec3_t startpos, vec3_t angles)
+{
+ edict_t *ent;
+
+ ent = G_Spawn();
+ VectorCopy(startpos, ent->s.origin);
+ VectorCopy(angles, ent->s.angles);
+ ent->solid = SOLID_NOT;
+ ent->s.renderfx = RF_IR_VISIBLE;
+ ent->movetype = MOVETYPE_NONE;
+ ent->classname = "widowlegs";
+
+ ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2");
+ ent->think = widowlegs_think;
+
+ ent->nextthink = level.time + FRAMETIME;
+ gi.linkentity(ent);
+}
diff --git a/rogue/src/g_sphere.c b/rogue/src/g_sphere.c
new file mode 100644
index 0000000..d759bee
--- /dev/null
+++ b/rogue/src/g_sphere.c
@@ -0,0 +1,862 @@
+/*
+ * =======================================================================
+ *
+ * Defender sphere.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define DEFENDER_LIFESPAN 30
+#define HUNTER_LIFESPAN 30
+#define VENGEANCE_LIFESPAN 30
+#define MINIMUM_FLY_TIME 15
+
+extern char *ED_NewString(char *string);
+void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker);
+
+void defender_think(edict_t *self);
+void hunter_think(edict_t *self);
+void vengeance_think(edict_t *self);
+void vengeance_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+void hunter_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+void
+sphere_think_explode(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->owner && self->owner->client &&
+ !(self->spawnflags & SPHERE_DOPPLEGANGER))
+ {
+ self->owner->client->owned_sphere = NULL;
+ }
+
+ BecomeExplosion1(self);
+}
+
+void
+sphere_explode(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ sphere_think_explode(self);
+}
+
+void
+sphere_if_idle_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ sphere_think_explode(self);
+ }
+}
+
+void
+sphere_fly(edict_t *self)
+{
+ vec3_t dest;
+ vec3_t dir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->wait)
+ {
+ sphere_think_explode(self);
+ return;
+ }
+
+ VectorCopy(self->owner->s.origin, dest);
+ dest[2] = self->owner->absmax[2] + 4;
+
+ if (level.time == (float)(int)level.time)
+ {
+ if (!visible(self, self->owner))
+ {
+ VectorCopy(dest, self->s.origin);
+ gi.linkentity(self);
+ return;
+ }
+ }
+
+ VectorSubtract(dest, self->s.origin, dir);
+ VectorScale(dir, 5, self->velocity);
+}
+
+void
+sphere_chase(edict_t *self, int stupidChase)
+{
+ vec3_t dest;
+ vec3_t dir;
+ float dist;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((level.time >= self->wait) ||
+ (self->enemy && (self->enemy->health < 1)))
+ {
+ sphere_think_explode(self);
+ return;
+ }
+
+ VectorCopy(self->enemy->s.origin, dest);
+
+ if (self->enemy->client)
+ {
+ dest[2] += self->enemy->viewheight;
+ }
+
+ if (visible(self, self->enemy) || stupidChase)
+ {
+ /* if moving, hunter sphere uses active sound */
+ if (!stupidChase)
+ {
+ self->s.sound = gi.soundindex("spheres/h_active.wav");
+ }
+
+ VectorSubtract(dest, self->s.origin, dir);
+ VectorNormalize(dir);
+ vectoangles2(dir, self->s.angles);
+ VectorScale(dir, 500, self->velocity);
+ VectorCopy(dest, self->monsterinfo.saved_goal);
+ }
+ else if (VectorCompare(self->monsterinfo.saved_goal, vec3_origin))
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ vectoangles2(dir, self->s.angles);
+
+ /* if lurking, hunter sphere uses lurking sound */
+ self->s.sound = gi.soundindex("spheres/h_lurk.wav");
+ VectorClear(self->velocity);
+ }
+ else
+ {
+ VectorSubtract(self->monsterinfo.saved_goal, self->s.origin, dir);
+ dist = VectorNormalize(dir);
+
+ if (dist > 1)
+ {
+ vectoangles2(dir, self->s.angles);
+
+ if (dist > 500)
+ {
+ VectorScale(dir, 500, self->velocity);
+ }
+ else if (dist < 20)
+ {
+ VectorScale(dir, (dist / FRAMETIME), self->velocity);
+ }
+ else
+ {
+ VectorScale(dir, dist, self->velocity);
+ }
+
+ /* if moving, hunter sphere uses active sound */
+ self->s.sound = gi.soundindex("spheres/h_active.wav");
+ }
+ else
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ vectoangles2(dir, self->s.angles);
+
+ /* if not moving, hunter sphere uses lurk sound */
+ self->s.sound = gi.soundindex("spheres/h_lurk.wav");
+
+ VectorClear(self->velocity);
+ }
+ }
+}
+
+void
+sphere_fire(edict_t *self, edict_t *enemy)
+{
+ vec3_t dest;
+ vec3_t dir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((level.time >= self->wait) || !enemy)
+ {
+ sphere_think_explode(self);
+ return;
+ }
+
+ VectorCopy(enemy->s.origin, dest);
+ self->s.effects |= EF_ROCKET;
+
+ VectorSubtract(dest, self->s.origin, dir);
+ VectorNormalize(dir);
+ vectoangles2(dir, self->s.angles);
+ VectorScale(dir, 1000, self->velocity);
+
+ self->touch = vengeance_touch;
+ self->think = sphere_think_explode;
+ self->nextthink = self->wait;
+}
+
+void
+sphere_touch(edict_t *self, edict_t *other, cplane_t *plane,
+ csurface_t *surf, int mod)
+{
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPHERE_DOPPLEGANGER)
+ {
+ if (other == self->teammaster)
+ {
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+ self->owner = self->teammaster;
+ self->teammaster = NULL;
+ }
+ else
+ {
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ /* Don't blow up on bodies */
+ if (!strcmp(other->classname, "bodyque"))
+ {
+ return;
+ }
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, 10000, 1, DAMAGE_DESTROY_ARMOR, mod);
+ }
+ else
+ {
+ T_RadiusDamage(self, self->owner, 512, self->owner, 256, mod);
+ }
+
+ sphere_think_explode(self);
+}
+
+void
+vengeance_touch(edict_t *self, edict_t *other, cplane_t *plane,
+ csurface_t *surf)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPHERE_DOPPLEGANGER)
+ {
+ sphere_touch(self, other, plane, surf, MOD_DOPPLE_VENGEANCE);
+ }
+ else
+ {
+ sphere_touch(self, other, plane, surf, MOD_VENGEANCE_SPHERE);
+ }
+}
+
+void
+hunter_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ edict_t *owner;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ /* don't blow up if you hit the world.... sheesh. */
+ if (other == world)
+ {
+ return;
+ }
+
+ if (self->owner)
+ {
+ /* if owner is flying with us, make sure they stop too. */
+ owner = self->owner;
+
+ if (owner->flags & FL_SAM_RAIMI)
+ {
+ VectorClear(owner->velocity);
+ owner->movetype = MOVETYPE_NONE;
+ gi.linkentity(owner);
+ }
+ }
+
+ if (self->spawnflags & SPHERE_DOPPLEGANGER)
+ {
+ sphere_touch(self, other, plane, surf, MOD_DOPPLE_HUNTER);
+ }
+ else
+ {
+ sphere_touch(self, other, plane, surf, MOD_HUNTER_SPHERE);
+ }
+}
+
+void
+defender_shoot(edict_t *self, edict_t *enemy)
+{
+ vec3_t dir;
+ vec3_t start;
+
+ if (!self || !enemy)
+ {
+ return;
+ }
+
+ if (!(enemy->inuse) || (enemy->health <= 0))
+ {
+ return;
+ }
+
+ if (enemy == self->owner)
+ {
+ return;
+ }
+
+ VectorSubtract(enemy->s.origin, self->s.origin, dir);
+ VectorNormalize(dir);
+
+ if (self->monsterinfo.attack_finished > level.time)
+ {
+ return;
+ }
+
+ if (!visible(self, self->enemy))
+ {
+ return;
+ }
+
+ VectorCopy(self->s.origin, start);
+ start[2] += 2;
+ fire_blaster2(self->owner, start, dir, 10, 1000, EF_BLASTER, 0);
+
+ self->monsterinfo.attack_finished = level.time + 0.4;
+}
+
+void
+body_gib(edict_t *self)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 50, GIB_ORGANIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/skull/tris.md2", 50, GIB_ORGANIC);
+}
+
+void
+hunter_pain(edict_t *self, edict_t *other, float kick, int damage)
+{
+ edict_t *owner;
+ float dist;
+ vec3_t dir;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ owner = self->owner;
+
+ if (!(self->spawnflags & SPHERE_DOPPLEGANGER))
+ {
+ if (owner && (owner->health > 0))
+ {
+ return;
+ }
+
+ if (other == owner)
+ {
+ return;
+ }
+ }
+ else
+ {
+ /* if fired by a doppleganger, set it to 10 second timeout */
+ self->wait = level.time + MINIMUM_FLY_TIME;
+ }
+
+ if ((self->wait - level.time) < MINIMUM_FLY_TIME)
+ {
+ self->wait = level.time + MINIMUM_FLY_TIME;
+ }
+
+ self->s.effects |= EF_BLASTER | EF_TRACKER;
+ self->touch = hunter_touch;
+ self->enemy = other;
+
+ if ((self->spawnflags & SPHERE_DOPPLEGANGER) || !(owner && owner->client))
+ {
+ return;
+ }
+
+ if (!((int)dmflags->value & DF_FORCE_RESPAWN) &&
+ (huntercam && (huntercam->value)))
+ {
+ VectorSubtract(other->s.origin, self->s.origin, dir);
+ dist = VectorLength(dir);
+
+ if (dist >= 192)
+ {
+ /* detach owner from body and send him flying */
+ owner->movetype = MOVETYPE_FLYMISSILE;
+
+ /* gib like we just died, even though we didn't, really. */
+ body_gib(owner);
+
+ /* move the sphere to the owner's current viewpoint./
+ we know it's a valid spot (or will be momentarily) */
+ VectorCopy(owner->s.origin, self->s.origin);
+ self->s.origin[2] += owner->viewheight;
+
+ /* move the player's origin to the sphere's new origin */
+ VectorCopy(self->s.origin, owner->s.origin);
+ VectorCopy(self->s.angles, owner->s.angles);
+ VectorCopy(self->s.angles, owner->client->v_angle);
+ VectorClear(owner->mins);
+ VectorClear(owner->maxs);
+ VectorSet(owner->mins, -5, -5, -5);
+ VectorSet(owner->maxs, 5, 5, 5);
+ owner->client->ps.fov = 140;
+ owner->s.modelindex = 0;
+ owner->s.modelindex2 = 0;
+ owner->viewheight = 8;
+ owner->solid = SOLID_NOT;
+ owner->flags |= FL_SAM_RAIMI;
+ gi.linkentity(owner);
+
+ self->solid = SOLID_BBOX;
+ gi.linkentity(self);
+ }
+ }
+}
+
+void
+defender_pain(edict_t *self, edict_t *other, float kick, int damage)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ self->enemy = other;
+}
+
+void
+vengeance_pain(edict_t *self, edict_t *other, float kick, int damage)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ if (!(self->spawnflags & SPHERE_DOPPLEGANGER))
+ {
+ if (self->owner->health >= 25)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+ }
+ else
+ {
+ self->wait = level.time + MINIMUM_FLY_TIME;
+ }
+
+ if ((self->wait - level.time) < MINIMUM_FLY_TIME)
+ {
+ self->wait = level.time + MINIMUM_FLY_TIME;
+ }
+
+ self->s.effects |= EF_ROCKET;
+ self->touch = vengeance_touch;
+ self->enemy = other;
+}
+
+void
+defender_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->owner)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ /* if we've exited the level, just remove ourselves. */
+ if (level.intermissiontime)
+ {
+ sphere_think_explode(self);
+ return;
+ }
+
+ if (self->owner->health <= 0)
+ {
+ sphere_think_explode(self);
+ return;
+ }
+
+ self->s.frame++;
+
+ if (self->s.frame > 19)
+ {
+ self->s.frame = 0;
+ }
+
+ if (self->enemy)
+ {
+ if (self->enemy->health > 0)
+ {
+ defender_shoot(self, self->enemy);
+ }
+ else
+ {
+ self->enemy = NULL;
+ }
+ }
+
+ sphere_fly(self);
+
+ if (self->inuse)
+ {
+ self->nextthink = level.time + 0.1;
+ }
+}
+
+void
+hunter_think(edict_t *self)
+{
+ edict_t *owner;
+ vec3_t dir, ang;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we've exited the level, just remove ourselves. */
+ if (level.intermissiontime)
+ {
+ sphere_think_explode(self);
+ return;
+ }
+
+ owner = self->owner;
+
+ if (!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (owner)
+ {
+ self->ideal_yaw = owner->s.angles[YAW];
+ }
+ else if (self->enemy) /* fired by doppleganger */
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ vectoangles2(dir, ang);
+ self->ideal_yaw = ang[YAW];
+ }
+
+ M_ChangeYaw(self);
+
+ if (self->enemy)
+ {
+ sphere_chase(self, 0);
+
+ /* deal with sam raimi cam */
+ if (owner && (owner->flags & FL_SAM_RAIMI))
+ {
+ if (self->inuse)
+ {
+ owner->movetype = MOVETYPE_FLYMISSILE;
+ LookAtKiller(owner, self, self->enemy);
+
+ /* owner is flying with us, move him too */
+ owner->movetype = MOVETYPE_FLYMISSILE;
+ owner->viewheight = self->s.origin[2] - owner->s.origin[2];
+ VectorCopy(self->s.origin, owner->s.origin);
+ VectorCopy(self->velocity, owner->velocity);
+ VectorClear(owner->mins);
+ VectorClear(owner->maxs);
+ gi.linkentity(owner);
+ }
+ else /* sphere timed out */
+ {
+ VectorClear(owner->velocity);
+ owner->movetype = MOVETYPE_NONE;
+ gi.linkentity(owner);
+ }
+ }
+ }
+ else
+ {
+ sphere_fly(self);
+ }
+
+ if (self->inuse)
+ {
+ self->nextthink = level.time + 0.1;
+ }
+}
+
+void
+vengeance_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we've exited the level, just remove ourselves. */
+ if (level.intermissiontime)
+ {
+ sphere_think_explode(self);
+ return;
+ }
+
+ if (!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->enemy)
+ {
+ sphere_chase(self, 1);
+ }
+ else
+ {
+ sphere_fly(self);
+ }
+
+ if (self->inuse)
+ {
+ self->nextthink = level.time + 0.1;
+ }
+}
+
+edict_t *
+Sphere_Spawn(edict_t *owner, int spawnflags)
+{
+ edict_t *sphere;
+
+ if (!owner)
+ {
+ return NULL;
+ }
+
+ sphere = G_Spawn();
+ VectorCopy(owner->s.origin, sphere->s.origin);
+ sphere->s.origin[2] = owner->absmax[2];
+ sphere->s.angles[YAW] = owner->s.angles[YAW];
+ sphere->solid = SOLID_BBOX;
+ sphere->clipmask = MASK_SHOT;
+ sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE;
+ sphere->movetype = MOVETYPE_FLYMISSILE;
+
+ if (spawnflags & SPHERE_DOPPLEGANGER)
+ {
+ sphere->teammaster = owner->teammaster;
+ }
+ else
+ {
+ sphere->owner = owner;
+ }
+
+ sphere->classname = "sphere";
+ sphere->yaw_speed = 40;
+ sphere->monsterinfo.attack_finished = 0;
+ sphere->spawnflags = spawnflags; /* need this for the HUD to recognize sphere */
+ /* PMM */
+ sphere->takedamage = DAMAGE_NO;
+
+ switch (spawnflags & SPHERE_TYPE)
+ {
+ case SPHERE_DEFENDER:
+ sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2");
+ sphere->s.modelindex2 =
+ gi.modelindex("models/items/shell/tris.md2");
+ sphere->s.sound = gi.soundindex("spheres/d_idle.wav");
+ sphere->pain = defender_pain;
+ sphere->wait = level.time + DEFENDER_LIFESPAN;
+ sphere->die = sphere_explode;
+ sphere->think = defender_think;
+ break;
+ case SPHERE_HUNTER:
+ sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2");
+ sphere->s.sound = gi.soundindex("spheres/h_idle.wav");
+ sphere->wait = level.time + HUNTER_LIFESPAN;
+ sphere->pain = hunter_pain;
+ sphere->die = sphere_if_idle_die;
+ sphere->think = hunter_think;
+ break;
+ case SPHERE_VENGEANCE:
+ sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2");
+ sphere->s.sound = gi.soundindex("spheres/v_idle.wav");
+ sphere->wait = level.time + VENGEANCE_LIFESPAN;
+ sphere->pain = vengeance_pain;
+ sphere->die = sphere_if_idle_die;
+ sphere->think = vengeance_think;
+ VectorSet(sphere->avelocity, 30, 30, 0);
+ break;
+ default:
+ gi.dprintf("Tried to create an invalid sphere\n");
+ G_FreeEdict(sphere);
+ return NULL;
+ }
+
+ sphere->nextthink = level.time + 0.1;
+
+ gi.linkentity(sphere);
+
+ return sphere;
+}
+
+void
+Own_Sphere(edict_t *self, edict_t *sphere)
+{
+ if (!sphere || !self)
+ {
+ return;
+ }
+
+ /* ownership only for players */
+ if (self->client)
+ {
+ /* if they don't have one */
+ if (!(self->client->owned_sphere))
+ {
+ self->client->owned_sphere = sphere;
+ }
+ /* they already have one, take care of the old one */
+ else
+ {
+ if (self->client->owned_sphere->inuse)
+ {
+ G_FreeEdict(self->client->owned_sphere);
+ self->client->owned_sphere = sphere;
+ }
+ else
+ {
+ self->client->owned_sphere = sphere;
+ }
+ }
+ }
+}
+
+void
+Defender_Launch(edict_t *self)
+{
+ edict_t *sphere;
+
+ if (!self)
+ {
+ return;
+ }
+
+ sphere = Sphere_Spawn(self, SPHERE_DEFENDER);
+ Own_Sphere(self, sphere);
+}
+
+void
+Hunter_Launch(edict_t *self)
+{
+ edict_t *sphere;
+
+ if (!self)
+ {
+ return;
+ }
+
+ sphere = Sphere_Spawn(self, SPHERE_HUNTER);
+ Own_Sphere(self, sphere);
+}
+
+void
+Vengeance_Launch(edict_t *self)
+{
+ edict_t *sphere;
+
+ if (!self)
+ {
+ return;
+ }
+
+ sphere = Sphere_Spawn(self, SPHERE_VENGEANCE);
+ Own_Sphere(self, sphere);
+}
diff --git a/rogue/src/g_svcmds.c b/rogue/src/g_svcmds.c
new file mode 100644
index 0000000..ac5b643
--- /dev/null
+++ b/rogue/src/g_svcmds.c
@@ -0,0 +1,334 @@
+/* =======================================================================
+ *
+ * Game side of server CMDs. At this time only the ipfilter.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define MAX_IPFILTERS 1024
+
+/*
+ * ==============================================================================
+ *
+ * PACKET FILTERING
+ *
+ *
+ * You can add or remove addresses from the filter list with:
+ *
+ * addip <ip>
+ * removeip <ip>
+ *
+ * The ip address is specified in dot format, and any unspecified digits will match
+ * any value, so you can specify an entire class C network with "addip 192.246.40".
+ *
+ * Removeip will only remove an address specified exactly the same way. You cannot
+ * addip a subnet, then removeip a single host.
+ *
+ * listip
+ * Prints the current list of filters.
+ *
+ * writeip
+ * Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date.
+ * The filter lists are not saved and restored by default, because I beleive it
+ * would cause too much confusion.
+ *
+ * filterban <0 or 1>
+ *
+ * If 1 (the default), then ip addresses matching the current list will be prohibited
+ * from entering the game. This is the default setting.
+ *
+ * If 0, then only addresses matching the list will be allowed. This lets you easily
+ * set up a private game, or a game that only allows players from your local network.
+ *
+ * ==============================================================================
+ */
+
+typedef struct
+{
+ unsigned mask;
+ unsigned compare;
+} ipfilter_t;
+
+ipfilter_t ipfilters[MAX_IPFILTERS];
+int numipfilters;
+
+void
+Svcmd_Test_f(void)
+{
+ gi.cprintf(NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
+}
+
+qboolean
+StringToFilter(char *s, ipfilter_t *f)
+{
+ char num[128];
+ int i, j;
+ byte b[4];
+ byte m[4];
+
+ if (!s || !f)
+ {
+ return false;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ b[i] = 0;
+ m[i] = 0;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ if ((*s < '0') || (*s > '9'))
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s);
+ return false;
+ }
+
+ j = 0;
+
+ while (*s >= '0' && *s <= '9')
+ {
+ num[j++] = *s++;
+ }
+
+ num[j] = 0;
+ b[i] = atoi(num);
+
+ if (b[i] != 0)
+ {
+ m[i] = 255;
+ }
+
+ if (!*s)
+ {
+ break;
+ }
+
+ s++;
+ }
+
+ /* PVS NOTE: maybe use memcpy here? */
+ f->mask = *(unsigned *)m;
+ f->compare = *(unsigned *)b;
+
+ return true;
+}
+
+qboolean
+SV_FilterPacket(char *from)
+{
+ int i;
+ unsigned in;
+ byte m[4];
+ char *p;
+
+ if (!from)
+ {
+ return false;
+ }
+
+ i = 0;
+ p = from;
+
+ while (*p && i < 4)
+ {
+ m[i] = 0;
+
+ while (*p >= '0' && *p <= '9')
+ {
+ m[i] = m[i] * 10 + (*p - '0');
+ p++;
+ }
+
+ if (!*p || (*p == ':'))
+ {
+ break;
+ }
+
+ i++, p++;
+ }
+
+ /* PVS NOTE: maybe use memcpy here? */
+ in = *(unsigned *)m;
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ if ((in & ipfilters[i].mask) == ipfilters[i].compare)
+ {
+ return (int)filterban->value;
+ }
+ }
+
+ return (int)!filterban->value;
+}
+
+void
+SVCmd_AddIP_f(void)
+{
+ int i;
+
+ if (gi.argc() < 3)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Usage: addip <ip-mask>\n");
+ return;
+ }
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ if (ipfilters[i].compare == 0xffffffff)
+ {
+ break; /* free spot */
+ }
+ }
+
+ if (i == numipfilters)
+ {
+ if (numipfilters == MAX_IPFILTERS)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "IP filter list is full\n");
+ return;
+ }
+
+ numipfilters++;
+ }
+
+ if (!StringToFilter(gi.argv(2), &ipfilters[i]))
+ {
+ ipfilters[i].compare = 0xffffffff;
+ }
+}
+
+void
+SVCmd_RemoveIP_f(void)
+{
+ ipfilter_t f;
+ int i, j;
+
+ if (gi.argc() < 3)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Usage: sv removeip <ip-mask>\n");
+ return;
+ }
+
+ if (!StringToFilter(gi.argv(2), &f))
+ {
+ return;
+ }
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ if ((ipfilters[i].mask == f.mask) &&
+ (ipfilters[i].compare == f.compare))
+ {
+ for (j = i + 1; j < numipfilters; j++)
+ {
+ ipfilters[j - 1] = ipfilters[j];
+ }
+
+ numipfilters--;
+ gi.cprintf(NULL, PRINT_HIGH, "Removed.\n");
+ return;
+ }
+ }
+
+ gi.cprintf(NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv(2));
+}
+
+void
+SVCmd_ListIP_f(void)
+{
+ int i;
+ byte b[4];
+
+ gi.cprintf(NULL, PRINT_HIGH, "Filter list:\n");
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ /* PVS NOTE: maybe use memcpy here? */
+ *(unsigned *)b = ipfilters[i].compare;
+ gi.cprintf(NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
+ }
+}
+
+void
+SVCmd_WriteIP_f(void)
+{
+ FILE *f;
+ char name[MAX_OSPATH];
+ byte b[4];
+ int i;
+ cvar_t *game;
+
+ game = gi.cvar("game", "", 0);
+
+ if (!*game->string)
+ {
+ sprintf(name, "%s/listip.cfg", GAMEVERSION);
+ }
+ else
+ {
+ sprintf(name, "%s/listip.cfg", game->string);
+ }
+
+ gi.cprintf(NULL, PRINT_HIGH, "Writing %s.\n", name);
+
+ f = fopen(name, "wb");
+
+ if (!f)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Couldn't open %s\n", name);
+ return;
+ }
+
+ fprintf(f, "set filterban %d\n", (int)filterban->value);
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ /* PVS NOTE: maybe use memcpy here? */
+ *(unsigned *)b = ipfilters[i].compare;
+ fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
+ }
+
+ fclose(f);
+}
+
+/*
+ * ServerCommand will be called when an "sv" command is issued.
+ * The game can issue gi.argc() / gi.argv() commands to get the
+ * rest of the parameters
+ */
+void
+ServerCommand(void)
+{
+ char *cmd;
+
+ cmd = gi.argv(1);
+
+ if (Q_stricmp(cmd, "test") == 0)
+ {
+ Svcmd_Test_f();
+ }
+ else if (Q_stricmp(cmd, "addip") == 0)
+ {
+ SVCmd_AddIP_f();
+ }
+ else if (Q_stricmp(cmd, "removeip") == 0)
+ {
+ SVCmd_RemoveIP_f();
+ }
+ else if (Q_stricmp(cmd, "listip") == 0)
+ {
+ SVCmd_ListIP_f();
+ }
+ else if (Q_stricmp(cmd, "writeip") == 0)
+ {
+ SVCmd_WriteIP_f();
+ }
+ else
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
+ }
+}
diff --git a/rogue/src/g_target.c b/rogue/src/g_target.c
new file mode 100644
index 0000000..42a423f
--- /dev/null
+++ b/rogue/src/g_target.c
@@ -0,0 +1,1200 @@
+/* =======================================================================
+ *
+ * Targets.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define LASER_ON 0x0001
+#define LASER_RED 0x0002
+#define LASER_GREEN 0x0004
+#define LASER_BLUE 0x0008
+#define LASER_YELLOW 0x0010
+#define LASER_ORANGE 0x0020
+#define LASER_FAT 0x0040
+#define LASER_STOPWINDOW 0x0080
+
+void ED_CallSpawn(edict_t *ent);
+
+/*
+ * QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
+ *
+ * Fire an origin based temp entity event to the clients.
+ * "style" type byte
+ */
+void
+Use_Target_Tent(edict_t *ent, edict_t *other, edict_t *activator)
+{
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(ent->style);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+}
+
+void
+SP_target_temp_entity(edict_t *ent)
+{
+ ent->use = Use_Target_Tent;
+}
+
+/*
+ * QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
+ *
+ * "noise" wav file to play
+ * "attenuation"
+ * -1 = none, send to whole level
+ * 1 = normal fighting sounds
+ * 2 = idle sound level
+ * 3 = ambient sound level
+ * "volume" 0.0 to 1.0
+ *
+ * Normal sounds play each time the target is used. The reliable flag can be set for
+ * crucial voiceovers.
+ *
+ * Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
+ * Multiple identical looping sounds will just increase volume without any speed cost.
+ */
+void
+Use_Target_Speaker(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ int chan;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->spawnflags & 3)
+ {
+ /* looping sound toggles */
+ if (ent->s.sound)
+ {
+ ent->s.sound = 0; /* turn it off */
+ }
+ else
+ {
+ ent->s.sound = ent->noise_index; /* start it */
+ }
+ }
+ else
+ {
+ /* normal sound */
+ if (ent->spawnflags & 4)
+ {
+ chan = CHAN_VOICE | CHAN_RELIABLE;
+ }
+ else
+ {
+ chan = CHAN_VOICE;
+ }
+
+ /* use a positioned_sound, because this entity won't
+ normally be sent to any clients because it is invisible */
+ gi.positioned_sound(ent->s.origin, ent, chan, ent->noise_index,
+ ent->volume, ent->attenuation, 0);
+ }
+}
+
+void
+SP_target_speaker(edict_t *ent)
+{
+ char buffer[MAX_QPATH];
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!st.noise)
+ {
+ gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
+ return;
+ }
+
+ if (!strstr(st.noise, ".wav"))
+ {
+ Com_sprintf(buffer, sizeof(buffer), "%s.wav", st.noise);
+ }
+ else
+ {
+ Q_strlcpy(buffer, st.noise, sizeof(buffer));
+ }
+
+ ent->noise_index = gi.soundindex(buffer);
+
+ if (!ent->volume)
+ {
+ ent->volume = 1.0;
+ }
+
+ if (!ent->attenuation)
+ {
+ ent->attenuation = 1.0;
+ }
+ else if (ent->attenuation == -1) /* use -1 so 0 defaults to 1 */
+ {
+ ent->attenuation = 0;
+ }
+
+ /* check for prestarted looping sound */
+ if (ent->spawnflags & 1)
+ {
+ ent->s.sound = ent->noise_index;
+ }
+
+ ent->use = Use_Target_Speaker;
+
+ /* must link the entity so we get areas and clusters so
+ the server can determine who to send updates to */
+ gi.linkentity(ent);
+}
+
+/* ========================================================== */
+
+void
+Use_Target_Help(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->spawnflags & 1)
+ {
+ strncpy(game.helpmessage1, ent->message, sizeof(game.helpmessage2) - 1);
+ }
+ else
+ {
+ strncpy(game.helpmessage2, ent->message, sizeof(game.helpmessage1) - 1);
+ }
+
+ game.helpchanged++;
+}
+
+/*
+ * QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
+ *
+ * When fired, the "message" key becomes the current personal computer string,
+ * and the message light will be set on all clients status bars.
+ */
+void
+SP_target_help(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->message)
+ {
+ gi.dprintf("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->use = Use_Target_Help;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
+ *
+ * Counts a secret found.
+ * These are single use targets.
+ */
+void
+use_target_secret(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+ level.found_secrets++;
+
+ G_UseTargets(ent, activator);
+ G_FreeEdict(ent);
+}
+
+void
+SP_target_secret(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->use = use_target_secret;
+
+ if (!st.noise)
+ {
+ st.noise = "misc/secret.wav";
+ }
+
+ ent->noise_index = gi.soundindex(st.noise);
+ ent->svflags = SVF_NOCLIENT;
+ level.total_secrets++;
+
+ /* map bug hack */
+ if (!Q_stricmp(level.mapname, "mine3") && (ent->s.origin[0] == 280) &&
+ (ent->s.origin[1] == -2048) && (ent->s.origin[2] == -624))
+ {
+ ent->message = "You have found a secret area.";
+ }
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
+ * Counts a goal completed.
+ * These are single use targets.
+ */
+void
+use_target_goal(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+ level.found_goals++;
+
+ if (level.found_goals == level.total_goals)
+ {
+ gi.configstring(CS_CDTRACK, "0");
+ }
+
+ G_UseTargets(ent, activator);
+ G_FreeEdict(ent);
+}
+
+void
+SP_target_goal(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->use = use_target_goal;
+
+ if (!st.noise)
+ {
+ st.noise = "misc/secret.wav";
+ }
+
+ ent->noise_index = gi.soundindex(st.noise);
+ ent->svflags = SVF_NOCLIENT;
+ level.total_goals++;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
+ * Spawns an explosion temporary entity when used.
+ *
+ * "delay" wait this long before going off
+ * "dmg" how much radius damage should be done, defaults to 0
+ */
+void
+target_explosion_explode(edict_t *self)
+{
+ float save;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+
+ T_RadiusDamage(self, self->activator, self->dmg,
+ NULL, self->dmg + 40, MOD_EXPLOSIVE);
+
+ save = self->delay;
+ self->delay = 0;
+ G_UseTargets(self, self->activator);
+ self->delay = save;
+}
+
+void
+use_target_explosion(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ if (!self->delay)
+ {
+ target_explosion_explode(self);
+ return;
+ }
+
+ self->think = target_explosion_explode;
+ self->nextthink = level.time + self->delay;
+}
+
+void
+SP_target_explosion(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->use = use_target_explosion;
+ ent->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
+ *
+ * Changes level to "map" when fired
+ */
+void
+use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return; /* already activated */
+ }
+
+ if (!deathmatch->value && !coop->value)
+ {
+ if (g_edicts[1].health <= 0)
+ {
+ return;
+ }
+ }
+
+ /* if noexit, do a ton of damage to other */
+ if (deathmatch->value && !((int)dmflags->value & DF_ALLOW_EXIT) &&
+ (other != world))
+ {
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
+ return;
+ }
+
+ /* if multiplayer, let everyone know who hit the exit */
+ if (deathmatch->value)
+ {
+ if (activator && activator->client)
+ {
+ gi.bprintf(PRINT_HIGH, "%s exited the level.\n",
+ activator->client->pers.netname);
+ }
+ }
+
+ /* if going to a new unit, clear cross triggers */
+ if (strstr(self->map, "*"))
+ {
+ game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
+ }
+
+ BeginIntermission(self);
+}
+
+void
+SP_target_changelevel(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->map)
+ {
+ gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ /* ugly hack because *SOMEBODY* screwed up their map */
+ if ((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0))
+ {
+ ent->map = "fact3$secret1";
+ }
+
+ ent->use = use_target_changelevel;
+ ent->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
+ * Creates a particle splash effect when used.
+ *
+ * Set "sounds" to one of the following:
+ * 1) sparks
+ * 2) blue water
+ * 3) brown water
+ * 4) slime
+ * 5) lava
+ * 6) blood
+ *
+ * "count" how many pixels in the splash
+ * "dmg" if set, does a radius damage at this location when it splashes
+ * useful for lava/sparks
+ */
+
+void
+use_target_splash(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_SPLASH);
+ gi.WriteByte(self->count);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(self->movedir);
+ gi.WriteByte(self->sounds);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ if (self->dmg)
+ {
+ T_RadiusDamage(self, activator, self->dmg, NULL,
+ self->dmg + 40, MOD_SPLASH);
+ }
+}
+
+void
+SP_target_splash(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = use_target_splash;
+ G_SetMovedir(self->s.angles, self->movedir);
+
+ if (!self->count)
+ {
+ self->count = 32;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
+ * Set target to the type of entity you want spawned.
+ * Useful for spawning monsters and gibs in the factory levels.
+ *
+ * For monsters:
+ * Set direction to the facing you want it to have.
+ *
+ * For gibs:
+ * Set direction if you want it moving and
+ * speed how fast it should be moving otherwise it
+ * will just be dropped
+ */
+
+void
+use_target_spawner(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = G_Spawn();
+ ent->classname = self->target;
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(self->s.angles, ent->s.angles);
+ ED_CallSpawn(ent);
+ gi.unlinkentity(ent);
+ KillBox(ent);
+ gi.linkentity(ent);
+
+ if (self->speed)
+ {
+ VectorCopy(self->movedir, ent->velocity);
+ }
+
+ ent->s.renderfx |= RF_IR_VISIBLE; /* PGM */
+}
+
+void
+SP_target_spawner(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = use_target_spawner;
+ self->svflags = SVF_NOCLIENT;
+
+ if (self->speed)
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ VectorScale(self->movedir, self->speed, self->movedir);
+ }
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
+ * Fires a blaster bolt in the set direction when triggered.
+ *
+ * dmg default is 15
+ * speed default is 1000
+ */
+
+void
+use_target_blaster(edict_t *self, edict_t *other, edict_t *activator)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_blaster(self, self->s.origin, self->movedir,
+ self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
+ gi.sound(self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
+}
+
+void
+SP_target_blaster(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = use_target_blaster;
+ G_SetMovedir(self->s.angles, self->movedir);
+ self->noise_index = gi.soundindex("weapons/laser2.wav");
+
+ if (!self->dmg)
+ {
+ self->dmg = 15;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 1000;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+ *
+ * Once this trigger is touched/used, any trigger_crosslevel_target
+ * with the same trigger number is automatically used when a level
+ * is started within the same unit. It is OK to check multiple triggers.
+ * Message, delay, target, and killtarget also work.
+ */
+void
+trigger_crosslevel_trigger_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ game.serverflags |= self->spawnflags;
+ G_FreeEdict(self);
+}
+
+void
+SP_target_crosslevel_trigger(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+ self->use = trigger_crosslevel_trigger_use;
+}
+
+/*
+ * QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+ *
+ * Triggered by a trigger_crosslevel elsewhere within a unit.
+ * If multiple triggers are checked, all must be true.
+ * Delay, target and killtarget also work.
+ *
+ * "delay" delay before using targets if the trigger has been activated (default 1)
+ */
+void
+target_crosslevel_target_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
+ {
+ G_UseTargets(self, self);
+ G_FreeEdict(self);
+ }
+}
+
+void
+SP_target_crosslevel_target(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->delay)
+ {
+ self->delay = 1;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+
+ self->think = target_crosslevel_target_think;
+ self->nextthink = level.time + self->delay;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP
+ * When triggered, fires a laser. You can either set a target
+ * or a direction.
+ *
+ * WINDOWSTOP - stops at CONTENTS_WINDOW
+ */
+void
+target_laser_think(edict_t *self)
+{
+ edict_t *ignore;
+ vec3_t start;
+ vec3_t end;
+ trace_t tr;
+ vec3_t point;
+ vec3_t last_movedir;
+ int count;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 0x80000000)
+ {
+ count = 8;
+ }
+ else
+ {
+ count = 4;
+ }
+
+ if (self->enemy)
+ {
+ VectorCopy(self->movedir, last_movedir);
+ VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point);
+ VectorSubtract(point, self->s.origin, self->movedir);
+ VectorNormalize(self->movedir);
+
+ if (!VectorCompare(self->movedir, last_movedir))
+ {
+ self->spawnflags |= 0x80000000;
+ }
+ }
+
+ ignore = self;
+ VectorCopy(self->s.origin, start);
+ VectorMA(start, 2048, self->movedir, end);
+
+ while (1)
+ {
+ if (self->spawnflags & LASER_STOPWINDOW)
+ {
+ tr = gi.trace(start, NULL, NULL, end, ignore, MASK_SHOT);
+ }
+ else
+ {
+ tr = gi.trace(start, NULL, NULL, end, ignore,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
+ }
+
+ if (!tr.ent)
+ {
+ break;
+ }
+
+ /* hurt it if we can */
+ if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
+ {
+ T_Damage(tr.ent, self, self->activator, self->movedir, tr.endpos,
+ vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
+ }
+
+ /* if we hit something that's not a monster or player or is immune to lasers, we're done */
+ if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) &&
+ !(tr.ent->svflags & SVF_DAMAGEABLE))
+ {
+ if (self->spawnflags & 0x80000000)
+ {
+ self->spawnflags &= ~0x80000000;
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_LASER_SPARKS);
+ gi.WriteByte(count);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(self->s.skinnum);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+
+ break;
+ }
+
+ ignore = tr.ent;
+ VectorCopy(tr.endpos, start);
+ }
+
+ VectorCopy(tr.endpos, self->s.old_origin);
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+target_laser_on(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->activator)
+ {
+ self->activator = self;
+ }
+
+ self->spawnflags |= 0x80000001;
+ self->svflags &= ~SVF_NOCLIENT;
+ target_laser_think(self);
+}
+
+void
+target_laser_off(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->spawnflags &= ~1;
+ self->svflags |= SVF_NOCLIENT;
+ self->nextthink = 0;
+}
+
+void
+target_laser_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ if (self->spawnflags & 1)
+ {
+ target_laser_off(self);
+ }
+ else
+ {
+ target_laser_on(self);
+ }
+}
+
+void
+target_laser_start(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_NOT;
+ self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT;
+ self->s.modelindex = 1; /* must be non-zero */
+
+ /* set the beam diameter */
+ if (self->spawnflags & 64)
+ {
+ self->s.frame = 16;
+ }
+ else
+ {
+ self->s.frame = 4;
+ }
+
+ /* set the color */
+ if (self->spawnflags & 2)
+ {
+ self->s.skinnum = 0xf2f2f0f0;
+ }
+ else if (self->spawnflags & 4)
+ {
+ self->s.skinnum = 0xd0d1d2d3;
+ }
+ else if (self->spawnflags & 8)
+ {
+ self->s.skinnum = 0xf3f3f1f1;
+ }
+ else if (self->spawnflags & 16)
+ {
+ self->s.skinnum = 0xdcdddedf;
+ }
+ else if (self->spawnflags & 32)
+ {
+ self->s.skinnum = 0xe0e1e2e3;
+ }
+
+ if (!self->enemy)
+ {
+ if (self->target)
+ {
+ ent = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!ent)
+ {
+ gi.dprintf("%s at %s: %s is a bad target\n", self->classname,
+ vtos(self->s.origin), self->target);
+ }
+
+ self->enemy = ent;
+ }
+ else
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+ }
+
+ self->use = target_laser_use;
+ self->think = target_laser_think;
+
+ if (!self->dmg)
+ {
+ self->dmg = 1;
+ }
+
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+ gi.linkentity(self);
+
+ if (self->spawnflags & 1)
+ {
+ target_laser_on(self);
+ }
+ else
+ {
+ target_laser_off(self);
+ }
+}
+
+void
+SP_target_laser(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = target_laser_start;
+ self->nextthink = level.time + 1;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
+ *
+ * speed How many seconds the ramping will take
+ * message two letters; starting lightlevel and ending lightlevel
+ */
+
+void
+target_lightramp_think(edict_t *self)
+{
+ char style[2];
+
+ if (!self)
+ {
+ return;
+ }
+
+ style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
+ style[1] = 0;
+ gi.configstring(CS_LIGHTS + self->enemy->style, style);
+
+ if ((level.time - self->timestamp) < self->speed)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else if (self->spawnflags & 1)
+ {
+ char temp;
+
+ temp = self->movedir[0];
+ self->movedir[0] = self->movedir[1];
+ self->movedir[1] = temp;
+ self->movedir[2] *= -1;
+ }
+}
+
+void
+target_lightramp_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ edict_t *e;
+
+ /* check all the targets */
+ e = NULL;
+
+ while (1)
+ {
+ e = G_Find(e, FOFS(targetname), self->target);
+
+ if (!e)
+ {
+ break;
+ }
+
+ if (strcmp(e->classname, "light") != 0)
+ {
+ gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
+ gi.dprintf("target %s (%s at %s) is not a light\n", self->target,
+ e->classname, vtos(e->s.origin));
+ }
+ else
+ {
+ self->enemy = e;
+ }
+ }
+
+ if (!self->enemy)
+ {
+ gi.dprintf("%s target %s not found at %s\n", self->classname,
+ self->target, vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+ }
+
+ self->timestamp = level.time;
+ target_lightramp_think(self);
+}
+
+void
+SP_target_lightramp(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->message || (strlen(self->message) != 2) ||
+ (self->message[0] < 'a') || (self->message[0] > 'z') ||
+ (self->message[1] < 'a') || (self->message[1] > 'z') ||
+ (self->message[0] == self->message[1]))
+ {
+ gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->svflags |= SVF_NOCLIENT;
+ self->use = target_lightramp_use;
+ self->think = target_lightramp_think;
+
+ self->movedir[0] = self->message[0] - 'a';
+ self->movedir[1] = self->message[1] - 'a';
+ self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT
+ *
+ * When triggered, this initiates a level-wide earthquake.
+ * All players and monsters are affected.
+ * "speed" severity of the quake (default:200)
+ * "count" duration of the quake (default:5)
+ */
+void
+target_earthquake_think(edict_t *self)
+{
+ int i;
+ edict_t *e;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->spawnflags & 1))
+ {
+ if (self->last_move_time < level.time)
+ {
+ gi.positioned_sound(self->s.origin, self, CHAN_AUTO, self->noise_index,
+ 1.0, ATTN_NONE, 0);
+ self->last_move_time = level.time + 0.5;
+ }
+ }
+
+ for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
+ {
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->client)
+ {
+ continue;
+ }
+
+ if (!e->groundentity)
+ {
+ continue;
+ }
+
+ e->groundentity = NULL;
+ e->velocity[0] += crandom() * 150;
+ e->velocity[1] += crandom() * 150;
+ e->velocity[2] = self->speed * (100.0 / e->mass);
+ }
+
+ if (level.time < self->timestamp)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+target_earthquake_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->timestamp = level.time + self->count;
+ self->nextthink = level.time + FRAMETIME;
+ self->activator = activator;
+ self->last_move_time = 0;
+}
+
+void
+SP_target_earthquake(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->targetname)
+ {
+ gi.dprintf("untargeted %s at %s\n", self->classname, vtos( self->s.origin));
+ }
+
+ if (!self->count)
+ {
+ self->count = 5;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 200;
+ }
+
+ self->svflags |= SVF_NOCLIENT;
+ self->think = target_earthquake_think;
+ self->use = target_earthquake_use;
+
+ if (!(self->spawnflags & 1))
+ {
+ self->noise_index = gi.soundindex("world/quake.wav");
+ }
+}
diff --git a/rogue/src/g_trigger.c b/rogue/src/g_trigger.c
new file mode 100644
index 0000000..da13d63
--- /dev/null
+++ b/rogue/src/g_trigger.c
@@ -0,0 +1,911 @@
+/* =======================================================================
+ *
+ * Trigger.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define TRIGGER_MONSTER 0x01
+#define TRIGGER_NOT_PLAYER 0x02
+#define TRIGGER_TRIGGERED 0x04
+#define TRIGGER_TOGGLE 0x08
+
+#define PUSH_ONCE 0x01
+#define PUSH_START_OFF 0x02
+#define PUSH_SILENT 0x04
+
+static int windsound;
+
+void
+InitTrigger(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!VectorCompare(self->s.angles, vec3_origin))
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->movetype = MOVETYPE_NONE;
+ gi.setmodel(self, self->model);
+ self->svflags = SVF_NOCLIENT;
+}
+
+/*
+ * the wait time has passed, so set
+ * back up for another activation
+ */
+void
+multi_wait(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->nextthink = 0;
+}
+
+void
+multi_trigger(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->nextthink)
+ {
+ return; /* already been triggered */
+ }
+
+ G_UseTargets(ent, ent->activator);
+
+ if (ent->wait > 0)
+ {
+ ent->think = multi_wait;
+ ent->nextthink = level.time + ent->wait;
+ }
+ else
+ {
+ /* we can't just remove (self) here, because
+ this is a touch function called while looping
+ through area links... */
+ ent->touch = NULL;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = G_FreeEdict;
+ }
+}
+
+void
+Use_Multi(edict_t *ent, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!ent || !activator)
+ {
+ return;
+ }
+
+ if (ent->spawnflags & TRIGGER_TOGGLE)
+ {
+ if (ent->solid == SOLID_TRIGGER)
+ {
+ ent->solid = SOLID_NOT;
+ }
+ else
+ {
+ ent->solid = SOLID_TRIGGER;
+ }
+
+ gi.linkentity(ent);
+ }
+ else
+ {
+ ent->activator = activator;
+ multi_trigger(ent);
+ }
+}
+
+void
+Touch_Multi(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->client)
+ {
+ if (self->spawnflags & 2)
+ {
+ return;
+ }
+ }
+ else if (other->svflags & SVF_MONSTER)
+ {
+ if (!(self->spawnflags & 1))
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ if (!VectorCompare(self->movedir, vec3_origin))
+ {
+ vec3_t forward;
+
+ AngleVectors(other->s.angles, forward, NULL, NULL);
+
+ if (_DotProduct(forward, self->movedir) < 0)
+ {
+ return;
+ }
+ }
+
+ self->activator = other;
+ multi_trigger(self);
+}
+
+/*
+ * QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE
+ * Variable sized repeatable trigger. Must be targeted at one or more entities.
+ * If "delay" is set, the trigger waits some time after activating before firing.
+ * "wait" : Seconds between triggerings. (.2 default)
+ *
+ * TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive.
+ *
+ * sounds
+ * 1) secret
+ * 2) beep beep
+ * 3) large switch
+ * 4)
+ * set "message" to text string
+ */
+void
+trigger_enable(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->use = Use_Multi;
+ gi.linkentity(self);
+}
+
+void
+SP_trigger_multiple(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->sounds == 1)
+ {
+ ent->noise_index = gi.soundindex("misc/secret.wav");
+ }
+ else if (ent->sounds == 2)
+ {
+ ent->noise_index = gi.soundindex("misc/talk.wav");
+ }
+ else if (ent->sounds == 3)
+ {
+ ent->noise_index = gi.soundindex("misc/trigger1.wav");
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 0.2;
+ }
+
+ ent->touch = Touch_Multi;
+ ent->movetype = MOVETYPE_NONE;
+ ent->svflags |= SVF_NOCLIENT;
+
+ if (ent->spawnflags & (TRIGGER_TRIGGERED | TRIGGER_TOGGLE))
+ {
+ ent->solid = SOLID_NOT;
+ ent->use = trigger_enable;
+ }
+ else
+ {
+ ent->solid = SOLID_TRIGGER;
+ ent->use = Use_Multi;
+ }
+
+ if (!VectorCompare(ent->s.angles, vec3_origin))
+ {
+ G_SetMovedir(ent->s.angles, ent->movedir);
+ }
+
+ gi.setmodel(ent, ent->model);
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
+ *
+ * Triggers once, then removes itself.
+ * You must set the key "target" to the name of another object in the level that has a matching "targetname".
+ *
+ * If TRIGGERED, this trigger must be triggered before it is live.
+ *
+ * sounds
+ * 1) secret
+ * 2) beep beep
+ * 3) large switch
+ * 4)
+ *
+ * "message" string to be displayed when triggered
+ */
+
+void
+SP_trigger_once(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* make old maps work because I messed up on flag assignments here */
+ if (ent->spawnflags & 1)
+ {
+ vec3_t v;
+
+ VectorMA(ent->mins, 0.5, ent->size, v);
+ ent->spawnflags &= ~1;
+ ent->spawnflags |= 4;
+ gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
+ }
+
+ ent->wait = -1;
+ SP_trigger_multiple(ent);
+}
+
+/*
+ * QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
+ *
+ * This fixed size trigger cannot be touched, it can only be fired by other events.
+ */
+void
+trigger_relay_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ G_UseTargets(self, activator);
+}
+
+void
+SP_trigger_relay(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = trigger_relay_use;
+}
+
+/*
+ * QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
+ *
+ * A relay trigger that only fires it's targets if player has the proper key.
+ * Use "item" to specify the required key, for example "key_data_cd"
+ */
+void
+trigger_key_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ int index;
+
+ if (!self|| !activator)
+ {
+ return;
+ }
+
+ if (!self->item)
+ {
+ return;
+ }
+
+ if (!activator->client)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(self->item);
+
+ if (!activator->client->pers.inventory[index])
+ {
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 5.0;
+ gi.centerprintf(activator, "You need the %s", self->item->pickup_name);
+ gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0);
+ return;
+ }
+
+ gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keyuse.wav"), 1, ATTN_NORM, 0);
+
+ if (coop->value)
+ {
+ int player;
+ edict_t *ent;
+
+ if (strcmp(self->item->classname, "key_power_cube") == 0)
+ {
+ int cube;
+
+ for (cube = 0; cube < 8; cube++)
+ {
+ if (activator->client->pers.power_cubes & (1 << cube))
+ {
+ break;
+ }
+ }
+
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (ent->client->pers.power_cubes & (1 << cube))
+ {
+ ent->client->pers.inventory[index]--;
+ ent->client->pers.power_cubes &= ~(1 << cube);
+ }
+ }
+ }
+ else
+ {
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ ent->client->pers.inventory[index] = 0;
+ }
+ }
+ }
+ else
+ {
+ activator->client->pers.inventory[index]--;
+ }
+
+ G_UseTargets(self, activator);
+
+ self->use = NULL;
+}
+
+void
+SP_trigger_key(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!st.item)
+ {
+ gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
+ return;
+ }
+
+ self->item = FindItemByClassname(st.item);
+
+ if (!self->item)
+ {
+ gi.dprintf("item %s not found for trigger_key at %s\n", st.item,
+ vtos(self->s.origin));
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("%s at %s has no target\n", self->classname,
+ vtos(self->s.origin));
+ return;
+ }
+
+ gi.soundindex("misc/keytry.wav");
+ gi.soundindex("misc/keyuse.wav");
+
+ self->use = trigger_key_use;
+}
+
+/*
+ * QUAKED trigger_counter (.5 .5 .5) ? nomessage
+ *
+ * Acts as an intermediary for an action that takes multiple inputs.
+ *
+ * If nomessage is not set, t will print "1 more.. " etc when triggered
+ * and "sequence complete" when finished.
+ *
+ * After the counter has been triggered "count" times (default 2),
+ * it will fire all of it's targets and remove itself.
+ */
+
+void
+trigger_counter_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (self->count == 0)
+ {
+ return;
+ }
+
+ self->count--;
+
+ if (self->count)
+ {
+ if (!(self->spawnflags & 1))
+ {
+ gi.centerprintf(activator, "%i more to go...", self->count);
+ gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
+ }
+
+ return;
+ }
+
+ if (!(self->spawnflags & 1))
+ {
+ gi.centerprintf(activator, "Sequence completed!");
+ gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
+ }
+
+ self->activator = activator;
+ multi_trigger(self);
+}
+
+void
+SP_trigger_counter(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->wait = -1;
+
+ if (!self->count)
+ {
+ self->count = 2;
+ }
+
+ self->use = trigger_counter_use;
+}
+
+/*
+ * QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
+ *
+ * This trigger will always fire. It is activated by the world.
+ */
+void
+SP_trigger_always(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* we must have some delay to make sure our use targets are present */
+ if (ent->delay < 0.2)
+ {
+ ent->delay = 0.2;
+ }
+
+ G_UseTargets(ent, ent);
+}
+
+void
+trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (strcmp(other->classname, "grenade") == 0)
+ {
+ VectorScale(self->movedir, self->speed * 10, other->velocity);
+ }
+ else if (other->health > 0)
+ {
+ VectorScale(self->movedir, self->speed * 10, other->velocity);
+
+ if (other->client)
+ {
+ /* don't take falling damage immediately from this */
+ VectorCopy(other->velocity, other->client->oldvelocity);
+
+ if (!(self->spawnflags & PUSH_SILENT) &&
+ (other->fly_sound_debounce_time < level.time))
+ {
+ other->fly_sound_debounce_time = level.time + 1.5;
+ gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
+ }
+ }
+ }
+
+ if (self->spawnflags & PUSH_ONCE)
+ {
+ G_FreeEdict(self);
+ }
+}
+
+void
+trigger_push_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->solid == SOLID_NOT)
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ }
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE START_OFF SILENT
+ * Pushes the player
+ * "speed" defaults to 1000
+ *
+ * If targeted, it will toggle on and off when used.
+ *
+ * START_OFF - toggled trigger_push begins in off setting
+ * SILENT - doesn't make wind noise
+ */
+void
+SP_trigger_push(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ InitTrigger(self);
+ windsound = gi.soundindex("misc/windfly.wav");
+ self->touch = trigger_push_touch;
+
+ if (!self->speed)
+ {
+ self->speed = 1000;
+ }
+
+ if (self->targetname) /* toggleable */
+ {
+ self->use = trigger_push_use;
+
+ if (self->spawnflags & PUSH_START_OFF)
+ {
+ self->solid = SOLID_NOT;
+ }
+ }
+ else if (self->spawnflags & PUSH_START_OFF)
+ {
+ gi.dprintf("trigger_push is START_OFF but not targeted.\n");
+ self->svflags = 0;
+ self->touch = NULL;
+ self->solid = SOLID_BSP;
+ self->movetype = MOVETYPE_PUSH;
+ }
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
+ *
+ * Any entity that touches this will be hurt.
+ *
+ * It does dmg points of damage each server frame
+ *
+ * SILENT supresses playing the sound
+ * SLOW changes the damage rate to once per second
+ * NO_PROTECTION *nothing* stops the damage
+ *
+ * "dmg" default 5 (whole numbers only)
+ *
+ */
+void
+hurt_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->solid == SOLID_NOT)
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ }
+
+ gi.linkentity(self);
+
+ if (!(self->spawnflags & 2))
+ {
+ self->use = NULL;
+ }
+}
+
+void
+hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ int dflags;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->takedamage)
+ {
+ return;
+ }
+
+ if (self->timestamp > level.time)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 16)
+ {
+ self->timestamp = level.time + 1;
+ }
+ else
+ {
+ self->timestamp = level.time + FRAMETIME;
+ }
+
+ if (!(self->spawnflags & 4))
+ {
+ if ((level.framenum % 10) == 0)
+ {
+ gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
+ }
+ }
+
+ if (self->spawnflags & 8)
+ {
+ dflags = DAMAGE_NO_PROTECTION;
+ }
+ else
+ {
+ dflags = 0;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin,
+ self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
+}
+
+void
+SP_trigger_hurt(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ InitTrigger(self);
+
+ self->noise_index = gi.soundindex("world/electro.wav");
+ self->touch = hurt_touch;
+
+ if (!self->dmg)
+ {
+ self->dmg = 5;
+ }
+
+ if (self->spawnflags & 1)
+ {
+ self->solid = SOLID_NOT;
+ }
+ else
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->use = hurt_use;
+ }
+
+ gi.linkentity(self);
+}
+
+void
+trigger_gravity_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->solid == SOLID_NOT)
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ }
+
+ gi.linkentity(self);
+}
+
+/* PGM */
+
+void
+trigger_gravity_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ other->gravity = self->gravity;
+}
+
+/*
+ * QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF
+ * Changes the touching entites gravity to
+ * the value of "gravity". 1.0 is standard
+ * gravity for the level.
+ *
+ * TOGGLE - trigger_gravity can be turned on and off
+ * START_OFF - trigger_gravity starts turned off (implies TOGGLE)
+ */
+void
+SP_trigger_gravity(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (st.gravity == 0)
+ {
+ gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ InitTrigger(self);
+
+ self->gravity = strtof(st.gravity, (char **)NULL);
+
+ if (self->spawnflags & 1) /* TOGGLE */
+ {
+ self->use = trigger_gravity_use;
+ }
+
+ if (self->spawnflags & 2) /* START_OFF */
+ {
+ self->use = trigger_gravity_use;
+ self->solid = SOLID_NOT;
+ }
+
+ self->touch = trigger_gravity_touch;
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED trigger_monsterjump (.5 .5 .5) ?
+ *
+ * Walking monsters that touch this will jump in the direction of the trigger's angle
+ * "speed" default to 200, the speed thrown forward
+ * "height" default to 200, the speed thrown upwards
+ */
+
+void
+trigger_monsterjump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->flags & (FL_FLY | FL_SWIM))
+ {
+ return;
+ }
+
+ if (other->svflags & SVF_DEADMONSTER)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER))
+ {
+ return;
+ }
+
+ /* set XY even if not on ground, so the jump will clear lips */
+ other->velocity[0] = self->movedir[0] * self->speed;
+ other->velocity[1] = self->movedir[1] * self->speed;
+
+ if (!other->groundentity)
+ {
+ return;
+ }
+
+ other->groundentity = NULL;
+ other->velocity[2] = self->movedir[2];
+}
+
+void
+SP_trigger_monsterjump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 200;
+ }
+
+ if (!st.height)
+ {
+ st.height = 200;
+ }
+
+ if (self->s.angles[YAW] == 0)
+ {
+ self->s.angles[YAW] = 360;
+ }
+
+ InitTrigger(self);
+ self->touch = trigger_monsterjump_touch;
+ self->movedir[2] = st.height;
+}
diff --git a/rogue/src/g_turret.c b/rogue/src/g_turret.c
new file mode 100644
index 0000000..920e419
--- /dev/null
+++ b/rogue/src/g_turret.c
@@ -0,0 +1,814 @@
+/* =======================================================================
+ *
+ * Turrets aka big cannons with a driver.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+qboolean FindTarget(edict_t *self);
+void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void infantry_stand(edict_t *self);
+void monster_use(edict_t *self, edict_t *other, edict_t *activator);
+void SpawnTargetingSystem(edict_t *turret);
+
+void
+AnglesNormalize(vec3_t vec)
+{
+ while (vec[0] > 360)
+ {
+ vec[0] -= 360;
+ }
+
+ while (vec[0] < 0)
+ {
+ vec[0] += 360;
+ }
+
+ while (vec[1] > 360)
+ {
+ vec[1] -= 360;
+ }
+
+ while (vec[1] < 0)
+ {
+ vec[1] += 360;
+ }
+}
+
+float
+SnapToEights(float x)
+{
+ x *= 8.0;
+
+ if (x > 0.0)
+ {
+ x += 0.5;
+ }
+ else
+ {
+ x -= 0.5;
+ }
+
+ return 0.125 * (int)x;
+}
+
+void
+turret_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ edict_t *attacker;
+
+ if (other->takedamage)
+ {
+ if (self->teammaster->owner)
+ {
+ attacker = self->teammaster->owner;
+ }
+ else
+ {
+ attacker = self->teammaster;
+ }
+
+ T_Damage(other, self, attacker, vec3_origin, other->s.origin,
+ vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
+ }
+}
+
+/*
+ * QUAKED turret_breach (0 0 0) ?
+ * This portion of the turret can change both pitch and yaw.
+ * The model should be made with a flat pitch.
+ * It (and the associated base) need to be oriented towards 0.
+ * Use "angle" to set the starting angle.
+ *
+ * "speed" default 50
+ * "dmg" default 10
+ * "angle" point this forward
+ * "target" point this at an info_notnull at the muzzle tip
+ * "minpitch" min acceptable pitch angle : default -30
+ * "maxpitch" max acceptable pitch angle : default 30
+ * "minyaw" min acceptable yaw angle : default 0
+ * "maxyaw" max acceptable yaw angle : default 360
+ */
+
+void
+turret_breach_fire(edict_t *self)
+{
+ vec3_t f, r, u;
+ vec3_t start;
+ int damage;
+ int speed;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ VectorMA(self->s.origin, self->move_origin[0], f, start);
+ VectorMA(start, self->move_origin[1], r, start);
+ VectorMA(start, self->move_origin[2], u, start);
+
+ damage = 100 + random() * 50;
+ speed = 550 + 50 * skill->value;
+ fire_rocket(self->teammaster->owner, start, f, damage, speed, 150, damage);
+ gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+turret_breach_think(edict_t *self)
+{
+ edict_t *ent;
+ vec3_t current_angles;
+ vec3_t delta;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->s.angles, current_angles);
+ AnglesNormalize(current_angles);
+
+ AnglesNormalize(self->move_angles);
+
+ if (self->move_angles[PITCH] > 180)
+ {
+ self->move_angles[PITCH] -= 360;
+ }
+
+ /* clamp angles to mins & maxs */
+ if (self->move_angles[PITCH] > self->pos1[PITCH])
+ {
+ self->move_angles[PITCH] = self->pos1[PITCH];
+ }
+ else if (self->move_angles[PITCH] < self->pos2[PITCH])
+ {
+ self->move_angles[PITCH] = self->pos2[PITCH];
+ }
+
+ if ((self->move_angles[YAW] < self->pos1[YAW]) ||
+ (self->move_angles[YAW] > self->pos2[YAW]))
+ {
+ float dmin, dmax;
+
+ dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
+
+ if (dmin < -180)
+ {
+ dmin += 360;
+ }
+ else if (dmin > 180)
+ {
+ dmin -= 360;
+ }
+
+ dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
+
+ if (dmax < -180)
+ {
+ dmax += 360;
+ }
+ else if (dmax > 180)
+ {
+ dmax -= 360;
+ }
+
+ if (fabs(dmin) < fabs(dmax))
+ {
+ self->move_angles[YAW] = self->pos1[YAW];
+ }
+ else
+ {
+ self->move_angles[YAW] = self->pos2[YAW];
+ }
+ }
+
+ VectorSubtract(self->move_angles, current_angles, delta);
+
+ if (delta[0] < -180)
+ {
+ delta[0] += 360;
+ }
+ else if (delta[0] > 180)
+ {
+ delta[0] -= 360;
+ }
+
+ if (delta[1] < -180)
+ {
+ delta[1] += 360;
+ }
+ else if (delta[1] > 180)
+ {
+ delta[1] -= 360;
+ }
+
+ delta[2] = 0;
+
+ if (delta[0] > self->speed * FRAMETIME)
+ {
+ delta[0] = self->speed * FRAMETIME;
+ }
+
+ if (delta[0] < -1 * self->speed * FRAMETIME)
+ {
+ delta[0] = -1 * self->speed * FRAMETIME;
+ }
+
+ if (delta[1] > self->speed * FRAMETIME)
+ {
+ delta[1] = self->speed * FRAMETIME;
+ }
+
+ if (delta[1] < -1 * self->speed * FRAMETIME)
+ {
+ delta[1] = -1 * self->speed * FRAMETIME;
+ }
+
+ VectorScale(delta, 1.0 / FRAMETIME, self->avelocity);
+
+ self->nextthink = level.time + FRAMETIME;
+
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ ent->avelocity[1] = self->avelocity[1];
+ }
+
+ /* if we have adriver, adjust his velocities */
+ if (self->owner)
+ {
+ float angle;
+ float target_z;
+ float diff;
+ vec3_t target;
+ vec3_t dir;
+
+ /* angular is easy, just copy ours */
+ self->owner->avelocity[0] = self->avelocity[0];
+ self->owner->avelocity[1] = self->avelocity[1];
+
+ /* x & y */
+ angle = self->s.angles[1] + self->owner->move_origin[1];
+ angle *= (M_PI * 2 / 360);
+ target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
+ target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
+ target[2] = self->owner->s.origin[2];
+
+ VectorSubtract(target, self->owner->s.origin, dir);
+ self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
+ self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
+
+ /* z */
+ angle = self->s.angles[PITCH] * (M_PI * 2 / 360);
+ target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
+
+ diff = target_z - self->owner->s.origin[2];
+ self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
+
+ if (self->spawnflags & 65536)
+ {
+ turret_breach_fire(self);
+ self->spawnflags &= ~65536;
+ }
+ }
+}
+
+void
+turret_breach_finish_init(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* get and save info for muzzle location */
+ if (!self->target)
+ {
+ gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
+ }
+ else
+ {
+ self->target_ent = G_PickTarget(self->target);
+
+ if (self->target_ent)
+ {
+ VectorSubtract(self->target_ent->s.origin, self->s.origin, self->move_origin);
+ G_FreeEdict(self->target_ent);
+ }
+ else
+ {
+ gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin));
+ }
+ }
+
+ self->teammaster->dmg = self->dmg;
+ self->think = turret_breach_think;
+ self->think(self);
+}
+
+void
+SP_turret_breach(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BSP;
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+
+ if (!self->speed)
+ {
+ self->speed = 50;
+ }
+
+ if (!self->dmg)
+ {
+ self->dmg = 10;
+ }
+
+ if (!st.minpitch)
+ {
+ st.minpitch = -30;
+ }
+
+ if (!st.maxpitch)
+ {
+ st.maxpitch = 30;
+ }
+
+ if (!st.maxyaw)
+ {
+ st.maxyaw = 360;
+ }
+
+ self->pos1[PITCH] = -1 * st.minpitch;
+ self->pos1[YAW] = st.minyaw;
+ self->pos2[PITCH] = -1 * st.maxpitch;
+ self->pos2[YAW] = st.maxyaw;
+
+ self->ideal_yaw = self->s.angles[YAW];
+ self->move_angles[YAW] = self->ideal_yaw;
+
+ self->blocked = turret_blocked;
+
+ self->think = turret_breach_finish_init;
+ self->nextthink = level.time + FRAMETIME;
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED turret_base (0 0 0) ?
+ * This portion of the turret changes yaw only.
+ * MUST be teamed with a turret_breach.
+ */
+
+void
+SP_turret_base(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BSP;
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+ self->blocked = turret_blocked;
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
+ * Must NOT be on the team with the rest of the turret parts.
+ * Instead it must target the turret_breach.
+ */
+void
+turret_driver_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point)
+{
+ edict_t *ent;
+
+ if (!self || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ /* level the gun */
+ self->target_ent->move_angles[0] = 0;
+
+ /* remove the driver from the end of them team chain */
+ for (ent = self->target_ent->teammaster;
+ ent->teamchain != self;
+ ent = ent->teamchain)
+ {
+ }
+
+ ent->teamchain = NULL;
+ self->teammaster = NULL;
+ self->flags &= ~FL_TEAMSLAVE;
+
+ self->target_ent->owner = NULL;
+ self->target_ent->teammaster->owner = NULL;
+
+ infantry_die(self, inflictor, attacker, damage, point);
+}
+
+void
+turret_driver_think(edict_t *self)
+{
+ vec3_t target;
+ vec3_t dir;
+ float reaction_time;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+
+ if (self->enemy && (!self->enemy->inuse || (self->enemy->health <= 0)))
+ {
+ self->enemy = NULL;
+ }
+
+ if (!self->enemy)
+ {
+ if (!FindTarget(self))
+ {
+ return;
+ }
+
+ self->monsterinfo.trail_time = level.time;
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ }
+ else
+ {
+ if (visible(self, self->enemy))
+ {
+ if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
+ {
+ self->monsterinfo.trail_time = level.time;
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ }
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_LOST_SIGHT;
+ return;
+ }
+ }
+
+ /* let the turret know where we want it to aim */
+ VectorCopy(self->enemy->s.origin, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, self->target_ent->s.origin, dir);
+ vectoangles(dir, self->target_ent->move_angles);
+
+ /* decide if we should shoot */
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return;
+ }
+
+ reaction_time = (3 - skill->value) * 1.0;
+
+ if ((level.time - self->monsterinfo.trail_time) < reaction_time)
+ {
+ return;
+ }
+
+ self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
+ self->target_ent->spawnflags |= 65536;
+}
+
+void
+turret_driver_link(edict_t *self)
+{
+ vec3_t vec;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = turret_driver_think;
+ self->nextthink = level.time + FRAMETIME;
+
+ self->target_ent = G_PickTarget(self->target);
+ self->target_ent->owner = self;
+ self->target_ent->teammaster->owner = self;
+ VectorCopy(self->target_ent->s.angles, self->s.angles);
+
+ vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
+ vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
+ vec[2] = 0;
+ self->move_origin[0] = VectorLength(vec);
+
+ VectorSubtract(self->s.origin, self->target_ent->s.origin, vec);
+ vectoangles(vec, vec);
+ AnglesNormalize(vec);
+ self->move_origin[1] = vec[1];
+
+ self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
+
+ /* add the driver to the end of them team chain */
+ for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
+ {
+ }
+
+ ent->teamchain = self;
+ self->teammaster = self->target_ent->teammaster;
+ self->flags |= FL_TEAMSLAVE;
+}
+
+void
+SP_turret_driver(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 100;
+ self->gib_health = 0;
+ self->mass = 200;
+ self->viewheight = 24;
+
+ self->die = turret_driver_die;
+ self->monsterinfo.stand = infantry_stand;
+
+ self->flags |= FL_NO_KNOCKBACK;
+
+ level.total_monsters++;
+
+ self->svflags |= SVF_MONSTER;
+ self->s.renderfx |= RF_FRAMELERP;
+ self->takedamage = DAMAGE_AIM;
+ self->use = monster_use;
+ self->clipmask = MASK_MONSTERSOLID;
+ VectorCopy(self->s.origin, self->s.old_origin);
+ self->monsterinfo.aiflags |= AI_STAND_GROUND | AI_DUCKED;
+
+ if (st.item)
+ {
+ self->item = FindItemByClassname(st.item);
+
+ if (!self->item)
+ {
+ gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
+ }
+ }
+
+ self->think = turret_driver_link;
+ self->nextthink = level.time + FRAMETIME;
+
+ gi.linkentity(self);
+}
+
+/*
+ * invisible turret drivers so we can have unmanned turrets.
+ * originally designed to shoot at func_trains and such, so they
+ * fire at the center of the bounding box, rather than the entity's
+ * origin. */
+
+void
+turret_brain_think(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t endpos;
+ float reaction_time;
+ trace_t trace;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+
+ if (self->enemy)
+ {
+ if (!self->enemy->inuse)
+ {
+ self->enemy = NULL;
+ }
+ else if (self->enemy->takedamage && (self->enemy->health <= 0))
+ {
+ self->enemy = NULL;
+ }
+ }
+
+ if (!self->enemy)
+ {
+ if (!FindTarget(self))
+ {
+ return;
+ }
+
+ self->monsterinfo.trail_time = level.time;
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+
+ VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos);
+ VectorScale(endpos, 0.5, endpos);
+ }
+ else
+ {
+ VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos);
+ VectorScale(endpos, 0.5, endpos);
+
+ trace = gi.trace(self->target_ent->s.origin, vec3_origin, vec3_origin,
+ endpos, self->target_ent, MASK_SHOT);
+
+ if ((trace.fraction == 1) || (trace.ent == self->enemy))
+ {
+ if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
+ {
+ self->monsterinfo.trail_time = level.time;
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ }
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_LOST_SIGHT;
+ return;
+ }
+ }
+
+ /* let the turret know where we want it to aim */
+ VectorSubtract(endpos, self->target_ent->s.origin, dir);
+ vectoangles(dir, self->target_ent->move_angles);
+
+ /* decide if we should shoot */
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return;
+ }
+
+ if (self->delay)
+ {
+ reaction_time = self->delay;
+ }
+ else
+ {
+ reaction_time = (3 - skill->value) * 1.0;
+ }
+
+ if ((level.time - self->monsterinfo.trail_time) < reaction_time)
+ {
+ return;
+ }
+
+ self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
+ self->target_ent->spawnflags |= 65536;
+}
+
+void
+turret_brain_link(edict_t *self)
+{
+ vec3_t vec;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->killtarget)
+ {
+ self->enemy = G_PickTarget(self->killtarget);
+ }
+
+ self->think = turret_brain_think;
+ self->nextthink = level.time + FRAMETIME;
+
+ self->target_ent = G_PickTarget(self->target);
+ self->target_ent->owner = self;
+ self->target_ent->teammaster->owner = self;
+ VectorCopy(self->target_ent->s.angles, self->s.angles);
+
+ vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
+ vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
+ vec[2] = 0;
+ self->move_origin[0] = VectorLength(vec);
+
+ VectorSubtract(self->s.origin, self->target_ent->s.origin, vec);
+ vectoangles(vec, vec);
+ AnglesNormalize(vec);
+ self->move_origin[1] = vec[1];
+
+ self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
+
+ /* add the driver to the end of them team chain */
+ for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
+ {
+ }
+
+ ent->teamchain = self;
+ self->teammaster = self->target_ent->teammaster;
+ self->flags |= FL_TEAMSLAVE;
+}
+
+void
+turret_brain_deactivate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = NULL;
+ self->nextthink = 0;
+}
+
+void
+turret_brain_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = activator;
+ }
+
+ /* wait at least 3 seconds to fire. */
+ self->monsterinfo.attack_finished = level.time + 3;
+ self->use = turret_brain_deactivate;
+
+ self->think = turret_brain_link;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+/*
+ * QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16)
+ * Invisible brain to drive the turret.
+ *
+ * Does not search for targets. If targeted, can only be turned on once
+ * and then off once. After that they are completely disabled.
+ *
+ * "delay" the delay between firing (default ramps for skill level)
+ * "Target" the turret breach
+ * "Killtarget" the item you want it to attack.
+ * Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds
+ * before firing to acquire the target.
+ */
+void
+SP_turret_invisible_brain(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->killtarget)
+ {
+ gi.dprintf("turret_invisible_brain with no killtarget!\n");
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("turret_invisible_brain with no target!\n");
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->targetname)
+ {
+ self->use = turret_brain_activate;
+ }
+ else
+ {
+ self->think = turret_brain_link;
+ self->nextthink = level.time + FRAMETIME;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+ gi.linkentity(self);
+}
diff --git a/rogue/src/g_utils.c b/rogue/src/g_utils.c
new file mode 100644
index 0000000..5df691f
--- /dev/null
+++ b/rogue/src/g_utils.c
@@ -0,0 +1,875 @@
+/* =======================================================================
+ *
+ * Misc. utility functions for the game logic.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define MAXCHOICES 8
+
+vec3_t VEC_UP = {0, -1, 0};
+vec3_t MOVEDIR_UP = {0, 0, 1};
+vec3_t VEC_DOWN = {0, -2, 0};
+vec3_t MOVEDIR_DOWN = {0, 0, -1};
+
+void
+G_ProjectSource(vec3_t point, vec3_t distance, vec3_t forward,
+ vec3_t right, vec3_t result)
+{
+ result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
+ result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
+ result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
+}
+
+void
+G_ProjectSource2(vec3_t point, vec3_t distance, vec3_t forward,
+ vec3_t right, vec3_t up, vec3_t result)
+{
+ result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] +
+ up[0] * distance[2];
+ result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] +
+ up[1] * distance[2];
+ result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] +
+ up[2] * distance[2];
+}
+
+/*
+ * Searches all active entities for the next one that holds
+ * the matching string at fieldofs (use the FOFS() macro) in
+ * the structure.
+ *
+ * Searches beginning at the edict after from, or the beginning
+ * if NULL.
+ *
+ * NULL will be returned if the end of the list is reached.
+ */
+edict_t *
+G_Find(edict_t *from, int fieldofs, char *match)
+{
+ char *s;
+
+ if (!match)
+ {
+ return NULL;
+ }
+
+ if (!from)
+ {
+ from = g_edicts;
+ }
+ else
+ {
+ from++;
+ }
+
+ for ( ; from < &g_edicts[globals.num_edicts]; from++)
+ {
+ if (!from->inuse)
+ {
+ continue;
+ }
+
+ s = *(char **)((byte *)from + fieldofs);
+
+ if (!s)
+ {
+ continue;
+ }
+
+ if (!Q_stricmp(s, match))
+ {
+ return from;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Returns entities that have origins within a spherical area
+ */
+edict_t *
+findradius(edict_t *from, vec3_t org, float rad)
+{
+ vec3_t eorg;
+ int j;
+
+ if (!from)
+ {
+ from = g_edicts;
+ }
+ else
+ {
+ from++;
+ }
+
+ for ( ; from < &g_edicts[globals.num_edicts]; from++)
+ {
+ if (!from->inuse)
+ {
+ continue;
+ }
+
+ if (from->solid == SOLID_NOT)
+ {
+ continue;
+ }
+
+ for (j = 0; j < 3; j++)
+ {
+ eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5);
+ }
+
+ if (VectorLength(eorg) > rad)
+ {
+ continue;
+ }
+
+ return from;
+ }
+
+ return NULL;
+}
+
+/*
+ * Returns entities that have origins within a spherical area
+ */
+edict_t *
+findradius2(edict_t *from, vec3_t org, float rad)
+{
+ /* rad must be positive */
+ vec3_t eorg;
+ int j;
+
+ if (!from)
+ {
+ from = g_edicts;
+ }
+ else
+ {
+ from++;
+ }
+
+ for ( ; from < &g_edicts[globals.num_edicts]; from++)
+ {
+ if (!from->inuse)
+ {
+ continue;
+ }
+
+ if (from->solid == SOLID_NOT)
+ {
+ continue;
+ }
+
+ if (!from->takedamage)
+ {
+ continue;
+ }
+
+ if (!(from->svflags & SVF_DAMAGEABLE))
+ {
+ continue;
+ }
+
+ for (j = 0; j < 3; j++)
+ {
+ eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5);
+ }
+
+ if (VectorLength(eorg) > rad)
+ {
+ continue;
+ }
+
+ return from;
+ }
+
+ return NULL;
+}
+
+/*
+ * Searches all active entities for the next one that holds
+ * the matching string at fieldofs (use the FOFS() macro) in
+ * the structure.
+ *
+ * Searches beginning at the edict after from, or the beginning
+ * if NULL.
+ *
+ * NULL will be returned if the end of the list is reached.
+ */
+edict_t *
+G_PickTarget(char *targetname)
+{
+ edict_t *ent = NULL;
+ int num_choices = 0;
+ edict_t *choice[MAXCHOICES];
+
+ if (!targetname)
+ {
+ gi.dprintf("G_PickTarget called with NULL targetname\n");
+ return NULL;
+ }
+
+ while (1)
+ {
+ ent = G_Find(ent, FOFS(targetname), targetname);
+
+ if (!ent)
+ {
+ break;
+ }
+
+ choice[num_choices++] = ent;
+
+ if (num_choices == MAXCHOICES)
+ {
+ break;
+ }
+ }
+
+ if (!num_choices)
+ {
+ gi.dprintf("G_PickTarget: target %s not found\n", targetname);
+ return NULL;
+ }
+
+ return choice[rand() % num_choices];
+}
+
+void
+Think_Delay(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ G_UseTargets(ent, ent->activator);
+ G_FreeEdict(ent);
+}
+
+/*
+ * the global "activator" should be set to the entity that initiated the firing.
+ *
+ * If self.delay is set, a DelayedUse entity will be created that will actually
+ * do the SUB_UseTargets after that many seconds have passed.
+ *
+ * Centerprints any self.message to the activator.
+ *
+ * Search for (string)targetname in all entities that
+ * match (string)self.target and call their .use function
+ *
+ * ==============================
+ */
+void
+G_UseTargets(edict_t *ent, edict_t *activator)
+{
+ edict_t *t;
+ edict_t *master;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* check for a delay */
+ if (ent->delay)
+ {
+ /* create a temp object to fire at a later time */
+ t = G_Spawn();
+ t->classname = "DelayedUse";
+ t->nextthink = level.time + ent->delay;
+ t->think = Think_Delay;
+ t->activator = activator;
+
+ t->message = ent->message;
+ t->target = ent->target;
+ t->killtarget = ent->killtarget;
+ return;
+ }
+
+ /* print the message */
+ if (activator && (ent->message) && !(activator->svflags & SVF_MONSTER))
+ {
+ gi.centerprintf(activator, "%s", ent->message);
+
+ if (ent->noise_index)
+ {
+ gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+
+ /* kill killtargets */
+ if (ent->killtarget)
+ {
+ t = NULL;
+
+ while ((t = G_Find(t, FOFS(targetname), ent->killtarget)))
+ {
+ /* if this entity is part of a train, cleanly remove it */
+ if (t->flags & FL_TEAMSLAVE)
+ {
+ master = t->teammaster;
+
+ while (master)
+ {
+ if (master->teamchain == t)
+ {
+ master->teamchain = t->teamchain;
+ break;
+ }
+
+ master = master->teamchain;
+ }
+ }
+
+ /* correct killcounter if a living monster gets killtargeted */
+ if ((t->monsterinfo.checkattack || strcmp (t->classname, "turret_driver") == 0) &&
+ !(t->monsterinfo.aiflags & (AI_GOOD_GUY|AI_DO_NOT_COUNT)) && t->deadflag != DEAD_DEAD)
+ {
+ level.killed_monsters++;
+ }
+
+ G_FreeEdict(t);
+
+ if (!ent->inuse)
+ {
+ gi.dprintf("entity was removed while using killtargets\n");
+ return;
+ }
+ }
+ }
+
+ /* fire targets */
+ if (ent->target)
+ {
+ t = NULL;
+
+ while ((t = G_Find(t, FOFS(targetname), ent->target)))
+ {
+ /* doors fire area portals in a specific way */
+ if (!Q_stricmp(t->classname, "func_areaportal") &&
+ (!Q_stricmp(ent->classname, "func_door") ||
+ !Q_stricmp(ent->classname, "func_door_rotating")))
+ {
+ continue;
+ }
+
+ if (t == ent)
+ {
+ gi.dprintf("WARNING: Entity used itself.\n");
+ }
+ else
+ {
+ if (t->use)
+ {
+ t->use(t, ent, activator);
+ }
+ }
+
+ if (!ent->inuse)
+ {
+ gi.dprintf("entity was removed while using targets\n");
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * This is just a convenience function
+ * for making temporary vectors for function calls
+ */
+float *
+tv(float x, float y, float z)
+{
+ static int index;
+ static vec3_t vecs[8];
+ float *v;
+
+ /* use an array so that multiple tempvectors
+ won't collide for a while */
+ v = vecs[index];
+ index = (index + 1) & 7;
+
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+
+ return v;
+}
+
+/*
+ * This is just a convenience function
+ * for printing vectors
+ */
+char *
+vtos(vec3_t v)
+{
+ static int index;
+ static char str[8][32];
+ char *s;
+
+ /* use an array so that multiple vtos won't collide */
+ s = str[index];
+ index = (index + 1) & 7;
+
+ Com_sprintf(s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
+
+ return s;
+}
+
+void
+get_normal_vector(const cplane_t *p, vec3_t normal)
+{
+ if (p)
+ {
+ VectorCopy(p->normal, normal);
+ }
+ else
+ {
+ VectorCopy(vec3_origin, normal);
+ }
+}
+
+void
+G_SetMovedir(vec3_t angles, vec3_t movedir)
+{
+ if (VectorCompare(angles, VEC_UP))
+ {
+ VectorCopy(MOVEDIR_UP, movedir);
+ }
+ else if (VectorCompare(angles, VEC_DOWN))
+ {
+ VectorCopy(MOVEDIR_DOWN, movedir);
+ }
+ else
+ {
+ AngleVectors(angles, movedir, NULL, NULL);
+ }
+
+ VectorClear(angles);
+}
+
+float
+vectoyaw(vec3_t vec)
+{
+ float yaw;
+
+ if (vec[PITCH] == 0)
+ {
+ if (vec[YAW] == 0)
+ {
+ yaw = 0;
+ }
+ else if (vec[YAW] > 0)
+ {
+ yaw = 90;
+ }
+ else
+ {
+ yaw = 270;
+ }
+ }
+ else
+ {
+ yaw = (int)(atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+ }
+
+ return yaw;
+}
+
+float
+vectoyaw2(vec3_t vec)
+{
+ float yaw;
+
+ if (vec[PITCH] == 0)
+ {
+ if (vec[YAW] == 0)
+ {
+ yaw = 0;
+ }
+ else if (vec[YAW] > 0)
+ {
+ yaw = 90;
+ }
+ else
+ {
+ yaw = 270;
+ }
+ }
+ else
+ {
+ yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+ }
+
+ return yaw;
+}
+
+void
+vectoangles(vec3_t value1, vec3_t angles)
+{
+ float forward;
+ float yaw, pitch;
+
+ if ((value1[1] == 0) && (value1[0] == 0))
+ {
+ yaw = 0;
+
+ if (value1[2] > 0)
+ {
+ pitch = 90;
+ }
+ else
+ {
+ pitch = 270;
+ }
+ }
+ else
+ {
+ if (value1[0])
+ {
+ yaw = (int)(atan2(value1[1], value1[0]) * 180 / M_PI);
+ }
+ else if (value1[1] > 0)
+ {
+ yaw = 90;
+ }
+ else
+ {
+ yaw = 270;
+ }
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+
+ forward = sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
+ pitch = (int)(atan2(value1[2], forward) * 180 / M_PI);
+
+ if (pitch < 0)
+ {
+ pitch += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = 0;
+}
+
+void
+vectoangles2(vec3_t value1, vec3_t angles)
+{
+ float forward;
+ float yaw, pitch;
+
+ if ((value1[1] == 0) && (value1[0] == 0))
+ {
+ yaw = 0;
+
+ if (value1[2] > 0)
+ {
+ pitch = 90;
+ }
+ else
+ {
+ pitch = 270;
+ }
+ }
+ else
+ {
+ if (value1[0])
+ {
+ yaw = (atan2(value1[1], value1[0]) * 180 / M_PI);
+ }
+ else if (value1[1] > 0)
+ {
+ yaw = 90;
+ }
+ else
+ {
+ yaw = 270;
+ }
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+
+ forward = sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
+ pitch = (atan2(value1[2], forward) * 180 / M_PI);
+
+ if (pitch < 0)
+ {
+ pitch += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = 0;
+}
+
+char *
+G_CopyString(char *in)
+{
+ char *out;
+
+ if (!in)
+ {
+ return NULL;
+ }
+
+ out = gi.TagMalloc(strlen(in) + 1, TAG_LEVEL);
+ strcpy(out, in);
+ return out;
+}
+
+void
+G_InitEdict(edict_t *e)
+{
+ if (!e)
+ {
+ return;
+ }
+
+ if (e->nextthink)
+ {
+ e->nextthink = 0;
+ }
+
+ e->inuse = true;
+ e->classname = "noclass";
+ e->gravity = 1.0;
+ e->s.number = e - g_edicts;
+
+ e->gravityVector[0] = 0.0;
+ e->gravityVector[1] = 0.0;
+ e->gravityVector[2] = -1.0;
+}
+
+/*
+ * Either finds a free edict, or allocates a
+ * new one. Try to avoid reusing an entity
+ * that was recently freed, because it can
+ * cause the client to think the entity
+ * morphed into something else instead of
+ * being removed and recreated, which can
+ * cause interpolated angles and bad trails.
+ */
+#define POLICY_DEFAULT 0
+#define POLICY_DESPERATE 1
+
+static edict_t *
+G_FindFreeEdict(int policy)
+{
+ edict_t *e;
+
+ for (e = g_edicts + game.maxclients + 1 ; e < &g_edicts[globals.num_edicts] ; e++)
+ {
+ /* the first couple seconds of server time can involve a lot of
+ freeing and allocating, so relax the replacement policy
+ */
+ if (!e->inuse && (policy == POLICY_DESPERATE || e->freetime < 2.0f || (level.time - e->freetime) > 0.5f))
+ {
+ G_InitEdict (e);
+ return e;
+ }
+ }
+
+ return NULL;
+}
+
+edict_t *
+G_SpawnOptional(void)
+{
+ edict_t *e = G_FindFreeEdict (POLICY_DEFAULT);
+
+ if (e)
+ {
+ return e;
+ }
+
+ if (globals.num_edicts >= game.maxentities)
+ {
+ return G_FindFreeEdict (POLICY_DESPERATE);
+ }
+
+ e = &g_edicts[globals.num_edicts++];
+ G_InitEdict (e);
+
+ return e;
+}
+
+edict_t *
+G_Spawn(void)
+{
+ edict_t *e = G_SpawnOptional();
+
+ if (!e)
+ gi.error ("ED_Alloc: no free edicts");
+
+ return e;
+}
+
+/*
+ * Marks the edict as free
+ */
+void
+G_FreeEdict(edict_t *ed)
+{
+ if (!ed)
+ {
+ return;
+ }
+
+ gi.unlinkentity(ed); /* unlink from world */
+
+ if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
+ {
+ return;
+ }
+
+ memset(ed, 0, sizeof(*ed));
+ ed->classname = "freed";
+ ed->freetime = level.time;
+ ed->inuse = false;
+}
+
+void
+G_TouchTriggers(edict_t *ent)
+{
+ int i, num;
+ edict_t *touch[MAX_EDICTS], *hit;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* dead things don't activate triggers! */
+ if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
+ {
+ return;
+ }
+
+ num = gi.BoxEdicts(ent->absmin, ent->absmax, touch,
+ MAX_EDICTS, AREA_TRIGGERS);
+
+ /* be careful, it is possible to have an entity in this
+ list removed before we get to it (killtriggered) */
+ for (i = 0; i < num; i++)
+ {
+ hit = touch[i];
+
+ if (!hit->inuse)
+ {
+ continue;
+ }
+
+ if (!hit->touch)
+ {
+ continue;
+ }
+
+ hit->touch(hit, ent, NULL, NULL);
+ }
+}
+
+/*
+ * Call after linking a new trigger in during gameplay
+ * to force all entities it covers to immediately touch it
+ */
+void
+G_TouchSolids(edict_t *ent)
+{
+ int i, num;
+ edict_t *touch[MAX_EDICTS], *hit;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ num = gi.BoxEdicts(ent->absmin, ent->absmax, touch, MAX_EDICTS, AREA_SOLID);
+
+ /* be careful, it is possible to have an entity in this
+ list removed before we get to it (killtriggered) */
+ for (i = 0; i < num; i++)
+ {
+ hit = touch[i];
+
+ if (!hit->inuse)
+ {
+ continue;
+ }
+
+ if (ent->touch)
+ {
+ ent->touch(hit, ent, NULL, NULL);
+ }
+
+ if (!ent->inuse)
+ {
+ break;
+ }
+ }
+}
+
+/*
+ * Kills all entities that would touch the proposed new positioning
+ * of ent. Ent should be unlinked before calling this!
+ */
+qboolean
+KillBox(edict_t *ent)
+{
+ trace_t tr;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ while (1)
+ {
+ tr = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ ent->s.origin, NULL, MASK_PLAYERSOLID);
+
+ if (!tr.ent)
+ {
+ break;
+ }
+
+ /* nail it */
+ T_Damage(tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
+ 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+
+ /* if we didn't kill it, fail */
+ if (tr.ent->solid)
+ {
+ return false;
+ }
+ }
+
+ return true; /* all clear */
+}
diff --git a/rogue/src/g_weapon.c b/rogue/src/g_weapon.c
new file mode 100644
index 0000000..c4a02d2
--- /dev/null
+++ b/rogue/src/g_weapon.c
@@ -0,0 +1,1158 @@
+/* =======================================================================
+ *
+ * Weapon support functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+/*
+ * This is a support routine used when a client is firing
+ * a non-instant attack weapon. It checks to see if a
+ * monster's dodge function should be called.
+ */
+void
+check_dodge(edict_t *self, vec3_t start, vec3_t dir, int speed)
+{
+ vec3_t end;
+ vec3_t v;
+ trace_t tr;
+ float eta;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* easy mode only ducks one quarter the time */
+ if (skill->value == SKILL_EASY)
+ {
+ if (random() > 0.25)
+ {
+ return;
+ }
+ }
+
+ VectorMA(start, 8192, dir, end);
+ tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
+
+ if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) &&
+ (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
+ {
+ VectorSubtract(tr.endpos, start, v);
+ eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
+ tr.ent->monsterinfo.dodge(tr.ent, self, eta, &tr);
+ }
+}
+
+/*
+ * Used for all impact (hit/punch/slash) attacks
+ */
+qboolean
+fire_hit(edict_t *self, vec3_t aim, int damage, int kick)
+{
+ trace_t tr;
+ vec3_t forward, right, up;
+ vec3_t v;
+ vec3_t point;
+ float range;
+ vec3_t dir;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ /* see if enemy is in range */
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ range = VectorLength(dir);
+
+ if (range > aim[0])
+ {
+ return false;
+ }
+
+ if ((aim[1] > self->mins[0]) && (aim[1] < self->maxs[0]))
+ {
+ /* the hit is straight on so back the range up to the edge of their bbox */
+ range -= self->enemy->maxs[0];
+ }
+ else
+ {
+ /* this is a side hit so adjust the "right" value out to the edge of their bbox */
+ if (aim[1] < 0)
+ {
+ aim[1] = self->enemy->mins[0];
+ }
+ else
+ {
+ aim[1] = self->enemy->maxs[0];
+ }
+ }
+
+ VectorMA(self->s.origin, range, dir, point);
+
+ tr = gi.trace(self->s.origin, NULL, NULL, point, self, MASK_SHOT);
+
+ if (tr.fraction < 1)
+ {
+ if (!tr.ent->takedamage)
+ {
+ return false;
+ }
+
+ /* if it will hit any client/monster then hit the one we wanted to hit */
+ if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
+ {
+ tr.ent = self->enemy;
+ }
+ }
+
+ AngleVectors(self->s.angles, forward, right, up);
+ VectorMA(self->s.origin, range, forward, point);
+ VectorMA(point, aim[1], right, point);
+ VectorMA(point, aim[2], up, point);
+ VectorSubtract(point, self->enemy->s.origin, dir);
+
+ /* do the damage */
+ T_Damage(tr.ent, self, self, dir, point, vec3_origin,
+ damage, kick / 2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
+
+ if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+ {
+ return false;
+ }
+
+ /* do our special form of knockback here */
+ VectorMA(self->enemy->absmin, 0.5, self->enemy->size, v);
+ VectorSubtract(v, point, v);
+ VectorNormalize(v);
+ VectorMA(self->enemy->velocity, kick, v, self->enemy->velocity);
+
+ if (self->enemy->velocity[2] > 0)
+ {
+ self->enemy->groundentity = NULL;
+ }
+
+ return true;
+}
+
+/*
+ * This is an internal support routine used for bullet/pellet based weapons.
+ */
+void
+fire_lead(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick,
+ int te_impact, int hspread, int vspread, int mod)
+{
+ trace_t tr;
+ vec3_t dir;
+ vec3_t forward, right, up;
+ vec3_t end;
+ float r;
+ float u;
+ vec3_t water_start;
+ qboolean water = false;
+ int content_mask = MASK_SHOT | MASK_WATER;
+
+ if (!self)
+ {
+ return;
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+
+ if (!(tr.fraction < 1.0))
+ {
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ r = crandom() * hspread;
+ u = crandom() * vspread;
+ VectorMA(start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+
+ if (gi.pointcontents(start) & MASK_WATER)
+ {
+ water = true;
+ VectorCopy(start, water_start);
+ content_mask &= ~MASK_WATER;
+ }
+
+ tr = gi.trace(start, NULL, NULL, end, self, content_mask);
+
+ /* see if we hit water */
+ if (tr.contents & MASK_WATER)
+ {
+ int color;
+
+ water = true;
+ VectorCopy(tr.endpos, water_start);
+
+ if (!VectorCompare(start, tr.endpos))
+ {
+ if (tr.contents & CONTENTS_WATER)
+ {
+ if (strcmp(tr.surface->name, "*brwater") == 0)
+ {
+ color = SPLASH_BROWN_WATER;
+ }
+ else
+ {
+ color = SPLASH_BLUE_WATER;
+ }
+ }
+ else if (tr.contents & CONTENTS_SLIME)
+ {
+ color = SPLASH_SLIME;
+ }
+ else if (tr.contents & CONTENTS_LAVA)
+ {
+ color = SPLASH_LAVA;
+ }
+ else
+ {
+ color = SPLASH_UNKNOWN;
+ }
+
+ if (color != SPLASH_UNKNOWN)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_SPLASH);
+ gi.WriteByte(8);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(color);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+
+ /* change bullet's course when it enters water */
+ VectorSubtract(end, start, dir);
+ vectoangles(dir, dir);
+ AngleVectors(dir, forward, right, up);
+ r = crandom() * hspread * 2;
+ u = crandom() * vspread * 2;
+ VectorMA(water_start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+ }
+
+ /* re-trace ignoring water this time */
+ tr = gi.trace(water_start, NULL, NULL, end, self, MASK_SHOT);
+ }
+ }
+
+ /* send gun puff / flash */
+ if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+ {
+ if (tr.fraction < 1.0)
+ {
+ if (tr.ent->takedamage)
+ {
+ T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal,
+ damage, kick, DAMAGE_BULLET, mod);
+ }
+ else
+ {
+ if (strncmp(tr.surface->name, "sky", 3) != 0)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(te_impact);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+
+ if (self->client)
+ {
+ PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+ }
+ }
+ }
+ }
+ }
+
+ /* if went through water, determine where the end and make a bubble trail */
+ if (water)
+ {
+ vec3_t pos;
+
+ VectorSubtract(tr.endpos, water_start, dir);
+ VectorNormalize(dir);
+ VectorMA(tr.endpos, -2, dir, pos);
+
+ if (gi.pointcontents(pos) & MASK_WATER)
+ {
+ VectorCopy(pos, tr.endpos);
+ }
+ else
+ {
+ tr = gi.trace(pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+ }
+
+ VectorAdd(water_start, tr.endpos, pos);
+ VectorScale(pos, 0.5, pos);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BUBBLETRAIL);
+ gi.WritePosition(water_start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(pos, MULTICAST_PVS);
+ }
+}
+
+/*
+ * Fires a single round. Used for machinegun and chaingun.
+ * Would be fine for pistols, rifles, etc....
+ */
+void
+fire_bullet(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int mod)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_lead(self, start, aimdir, damage, kick, TE_GUNSHOT,
+ hspread, vspread, mod);
+}
+
+/*
+ * Shoots shotgun pellets. Used by shotgun and super shotgun.
+ */
+void
+fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick,
+ int hspread, int vspread, int count, int mod)
+{
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ fire_lead(self, start, aimdir, damage, kick, TE_SHOTGUN,
+ hspread, vspread, mod);
+ }
+}
+
+/*
+ * Fires a single blaster bolt. Used by the blaster and hyper blaster.
+ */
+void
+blaster_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ int mod;
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner && self->owner->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ get_normal_vector(plane, normal);
+
+ if (other->takedamage)
+ {
+ if (self->spawnflags & 1)
+ {
+ mod = MOD_HYPERBLASTER;
+ }
+ else
+ {
+ mod = MOD_BLASTER;
+ }
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin, normal,
+ self->dmg, 1, DAMAGE_ENERGY, mod);
+ }
+ else
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BLASTER);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(normal);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int effect, qboolean hyper)
+{
+ edict_t *bolt;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ bolt = G_Spawn();
+ bolt->svflags = SVF_DEADMONSTER;
+ VectorCopy(start, bolt->s.origin);
+ VectorCopy(start, bolt->s.old_origin);
+ vectoangles(dir, bolt->s.angles);
+ VectorScale(dir, speed, bolt->velocity);
+ bolt->movetype = MOVETYPE_FLYMISSILE;
+ bolt->clipmask = MASK_SHOT;
+ bolt->solid = SOLID_BBOX;
+ bolt->s.effects |= effect;
+ VectorClear(bolt->mins);
+ VectorClear(bolt->maxs);
+ bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2");
+ bolt->s.sound = gi.soundindex("misc/lasfly.wav");
+ bolt->owner = self;
+ bolt->touch = blaster_touch;
+ bolt->nextthink = level.time + 2;
+ bolt->think = G_FreeEdict;
+ bolt->dmg = damage;
+ bolt->classname = "bolt";
+
+ if (hyper)
+ {
+ bolt->spawnflags = 1;
+ }
+
+ gi.linkentity(bolt);
+
+ if (self->client)
+ {
+ check_dodge(self, bolt->s.origin, dir, speed);
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+
+ if (tr.fraction < 1.0)
+ {
+ VectorMA(bolt->s.origin, -10, dir, bolt->s.origin);
+ bolt->touch(bolt, tr.ent, NULL, NULL);
+ }
+}
+
+void
+Grenade_Explode(edict_t *ent)
+{
+ vec3_t origin;
+ int mod;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->owner->client)
+ {
+ PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+ }
+
+ if (ent->enemy)
+ {
+ float points;
+ vec3_t v;
+ vec3_t dir;
+
+ VectorAdd(ent->enemy->mins, ent->enemy->maxs, v);
+ VectorMA(ent->enemy->s.origin, 0.5, v, v);
+ VectorSubtract(ent->s.origin, v, v);
+ points = ent->dmg - 0.5 * VectorLength(v);
+ VectorSubtract(ent->enemy->s.origin, ent->s.origin, dir);
+
+ if (ent->spawnflags & 1)
+ {
+ mod = MOD_HANDGRENADE;
+ }
+ else
+ {
+ mod = MOD_GRENADE;
+ }
+
+ T_Damage(ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin,
+ (int)points, (int)points, DAMAGE_RADIUS, mod);
+ }
+
+ if (ent->spawnflags & 2)
+ {
+ mod = MOD_HELD_GRENADE;
+ }
+ else if (ent->spawnflags & 1)
+ {
+ mod = MOD_HG_SPLASH;
+ }
+ else
+ {
+ mod = MOD_G_SPLASH;
+ }
+
+ T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
+
+ VectorMA(ent->s.origin, -0.02, ent->velocity, origin);
+ gi.WriteByte(svc_temp_entity);
+
+ if (ent->waterlevel)
+ {
+ if (ent->groundentity)
+ {
+ gi.WriteByte(TE_GRENADE_EXPLOSION_WATER);
+ }
+ else
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION_WATER);
+ }
+ }
+ else
+ {
+ if (ent->groundentity)
+ {
+ gi.WriteByte(TE_GRENADE_EXPLOSION);
+ }
+ else
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION);
+ }
+ }
+
+ gi.WritePosition(origin);
+ gi.multicast(ent->s.origin, MULTICAST_PHS);
+
+ G_FreeEdict(ent);
+}
+
+void
+Grenade_Touch(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf)
+{
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (other == ent->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!other->takedamage)
+ {
+ if (ent->spawnflags & 1)
+ {
+ if (random() > 0.5)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+ else
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
+ }
+
+ return;
+ }
+
+ ent->enemy = other;
+ Grenade_Explode(ent);
+}
+
+void
+fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius)
+{
+ edict_t *grenade;
+ vec3_t dir;
+ vec3_t forward, right, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ grenade = G_Spawn();
+ VectorCopy(start, grenade->s.origin);
+ VectorScale(aimdir, speed, grenade->velocity);
+ VectorMA(grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+ VectorMA(grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+ VectorSet(grenade->avelocity, 300, 300, 300);
+ grenade->movetype = MOVETYPE_BOUNCE;
+ grenade->clipmask = MASK_SHOT;
+ grenade->solid = SOLID_BBOX;
+ grenade->s.effects |= EF_GRENADE;
+ VectorClear(grenade->mins);
+ VectorClear(grenade->maxs);
+ grenade->s.modelindex = gi.modelindex("models/objects/grenade/tris.md2");
+ grenade->owner = self;
+ grenade->touch = Grenade_Touch;
+ grenade->nextthink = level.time + timer;
+ grenade->think = Grenade_Explode;
+ grenade->dmg = damage;
+ grenade->dmg_radius = damage_radius;
+ grenade->classname = "grenade";
+
+ gi.linkentity(grenade);
+}
+
+void
+fire_grenade2(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius, qboolean held)
+{
+ edict_t *grenade;
+ vec3_t dir;
+ vec3_t forward, right, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ grenade = G_Spawn();
+ VectorCopy(start, grenade->s.origin);
+ VectorScale(aimdir, speed, grenade->velocity);
+ VectorMA(grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+ VectorMA(grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+ VectorSet(grenade->avelocity, 300, 300, 300);
+ grenade->movetype = MOVETYPE_BOUNCE;
+ grenade->clipmask = MASK_SHOT;
+ grenade->solid = SOLID_BBOX;
+ grenade->s.effects |= EF_GRENADE;
+ VectorClear(grenade->mins);
+ VectorClear(grenade->maxs);
+ grenade->s.modelindex = gi.modelindex("models/objects/grenade2/tris.md2");
+ grenade->owner = self;
+ grenade->touch = Grenade_Touch;
+ grenade->nextthink = level.time + timer;
+ grenade->think = Grenade_Explode;
+ grenade->dmg = damage;
+ grenade->dmg_radius = damage_radius;
+ grenade->classname = "hgrenade";
+
+ if (held)
+ {
+ grenade->spawnflags = 3;
+ }
+ else
+ {
+ grenade->spawnflags = 1;
+ }
+
+ grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
+
+ if (timer <= 0.0)
+ {
+ Grenade_Explode(grenade);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
+ gi.linkentity(grenade);
+ }
+}
+
+void
+rocket_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t origin;
+ vec3_t normal;
+ int n;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (other == ent->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (ent->owner->client)
+ {
+ PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+ }
+
+ /* calculate position for the explosion entity */
+ VectorMA(ent->s.origin, -0.02, ent->velocity, origin);
+
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, ent, ent->owner, ent->velocity, ent->s.origin, normal,
+ ent->dmg, 0, 0, MOD_ROCKET);
+ }
+ else
+ {
+ /* don't throw any debris in net games */
+ if (!deathmatch->value && !coop->value)
+ {
+ if ((surf) &&
+ !(surf->flags &
+ (SURF_WARP | SURF_TRANS33 | SURF_TRANS66 | SURF_FLOWING)))
+ {
+ n = rand() % 5;
+
+ while (n--)
+ {
+ ThrowDebris(ent, "models/objects/debris2/tris.md2", 2, ent->s.origin);
+ }
+ }
+ }
+ }
+
+ T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other,
+ ent->dmg_radius, MOD_R_SPLASH);
+
+ gi.WriteByte(svc_temp_entity);
+
+ if (ent->waterlevel)
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION_WATER);
+ }
+ else
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION);
+ }
+
+ gi.WritePosition(origin);
+ gi.multicast(ent->s.origin, MULTICAST_PHS);
+
+ G_FreeEdict(ent);
+}
+
+void
+fire_rocket(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed,
+ float damage_radius, int radius_damage)
+{
+ edict_t *rocket;
+
+ if (!self)
+ {
+ return;
+ }
+
+ rocket = G_Spawn();
+ VectorCopy(start, rocket->s.origin);
+ VectorCopy(dir, rocket->movedir);
+ vectoangles(dir, rocket->s.angles);
+ VectorScale(dir, speed, rocket->velocity);
+ rocket->movetype = MOVETYPE_FLYMISSILE;
+ rocket->clipmask = MASK_SHOT;
+ rocket->solid = SOLID_BBOX;
+ rocket->s.effects |= EF_ROCKET;
+ VectorClear(rocket->mins);
+ VectorClear(rocket->maxs);
+ rocket->s.modelindex = gi.modelindex("models/objects/rocket/tris.md2");
+ rocket->owner = self;
+ rocket->touch = rocket_touch;
+ rocket->nextthink = level.time + (8000.0f / (float)speed);
+ rocket->think = G_FreeEdict;
+ rocket->dmg = damage;
+ rocket->radius_dmg = radius_damage;
+ rocket->dmg_radius = damage_radius;
+ rocket->s.sound = gi.soundindex("weapons/rockfly.wav");
+ rocket->classname = "rocket";
+
+ if (self->client)
+ {
+ check_dodge(self, rocket->s.origin, dir, speed);
+ }
+
+ gi.linkentity(rocket);
+}
+
+void
+fire_rail(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
+{
+ vec3_t from;
+ vec3_t end;
+ trace_t tr;
+ edict_t *ignore;
+ int mask;
+ qboolean water;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorMA(start, 8192, aimdir, end);
+ VectorCopy(start, from);
+ ignore = self;
+ water = false;
+ mask = MASK_SHOT | CONTENTS_SLIME | CONTENTS_LAVA;
+
+ while (ignore)
+ {
+ tr = gi.trace(from, NULL, NULL, end, ignore, mask);
+
+ if (tr.contents & (CONTENTS_SLIME | CONTENTS_LAVA))
+ {
+ mask &= ~(CONTENTS_SLIME | CONTENTS_LAVA);
+ water = true;
+ }
+ else
+ {
+ if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) ||
+ (tr.ent->svflags & SVF_DAMAGEABLE) ||
+ (tr.ent->solid == SOLID_BBOX))
+ {
+ ignore = tr.ent;
+ }
+ else
+ {
+ ignore = NULL;
+ }
+
+ if ((tr.ent != self) && (tr.ent->takedamage))
+ {
+ T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal,
+ damage, kick, 0, MOD_RAILGUN);
+ }
+ }
+
+ VectorCopy(tr.endpos, from);
+ }
+
+ /* send gun puff / flash */
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_RAILTRAIL);
+ gi.WritePosition(start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+
+ if (water)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_RAILTRAIL);
+ gi.WritePosition(start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(tr.endpos, MULTICAST_PHS);
+ }
+
+ if (self->client)
+ {
+ PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+ }
+}
+
+void
+bfg_explode(edict_t *self)
+{
+ edict_t *ent;
+ float points;
+ vec3_t v;
+ float dist;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == 0)
+ {
+ /* the BFG effect */
+ ent = NULL;
+
+ while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
+ {
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ if (ent == self->owner)
+ {
+ continue;
+ }
+
+ if (!CanDamage(ent, self))
+ {
+ continue;
+ }
+
+ if (!CanDamage(ent, self->owner))
+ {
+ continue;
+ }
+
+ VectorAdd(ent->mins, ent->maxs, v);
+ VectorMA(ent->s.origin, 0.5, v, v);
+ VectorSubtract(self->s.origin, v, v);
+ dist = VectorLength(v);
+ points = self->radius_dmg * (1.0 - sqrt(dist / self->dmg_radius));
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BFG_EXPLOSION);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_PHS);
+ T_Damage(ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin,
+ (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
+ }
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+ self->s.frame++;
+
+ if (self->s.frame == 5)
+ {
+ self->think = G_FreeEdict;
+ }
+}
+
+void
+bfg_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ /* core explosion - prevents firing it into the wall/floor */
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, 200, 0, 0, MOD_BFG_BLAST);
+ }
+
+ T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST);
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
+ self->solid = SOLID_NOT;
+ self->touch = NULL;
+
+ /* move it back a bit from walls so the effects aren't cut off */
+ if (!other->takedamage)
+ {
+ VectorNormalize(self->velocity);
+ VectorMA(self->s.origin, -40.0f, self->velocity, self->s.origin);
+ }
+
+ VectorClear(self->velocity);
+ self->s.modelindex = gi.modelindex("sprites/s_bfg3.sp2");
+ self->s.frame = 0;
+ self->s.sound = 0;
+ self->s.effects &= ~EF_ANIM_ALLFAST;
+ self->think = bfg_explode;
+ self->nextthink = level.time + FRAMETIME;
+ self->enemy = other;
+
+ gi.linkentity(self);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BFG_BIGEXPLOSION);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+}
+
+void
+bfg_think(edict_t *self)
+{
+ edict_t *ent;
+ edict_t *ignore;
+ vec3_t point;
+ vec3_t dir;
+ vec3_t start;
+ vec3_t end;
+ int dmg;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ dmg = 5;
+ }
+ else
+ {
+ dmg = 10;
+ }
+
+ ent = NULL;
+
+ while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
+ {
+ if (ent == self)
+ {
+ continue;
+ }
+
+ if (ent == self->owner)
+ {
+ continue;
+ }
+
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ if (!(ent->svflags & SVF_MONSTER) && !(ent->svflags & SVF_DAMAGEABLE) &&
+ (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
+ {
+ continue;
+ }
+
+ VectorMA(ent->absmin, 0.5, ent->size, point);
+
+ VectorSubtract(point, self->s.origin, dir);
+ VectorNormalize(dir);
+
+ ignore = self;
+ VectorCopy(self->s.origin, start);
+ VectorMA(start, 2048, dir, end);
+
+ while (1)
+ {
+ tr = gi.trace(start, NULL, NULL, end, ignore,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
+
+ if (!tr.ent)
+ {
+ break;
+ }
+
+ /* hurt it if we can */
+ if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) &&
+ (tr.ent != self->owner))
+ {
+ T_Damage(tr.ent, self, self->owner, dir, tr.endpos, vec3_origin,
+ dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
+ }
+
+ /* if we hit something that's not a monster or player we're done */
+ if (!(tr.ent->svflags & SVF_MONSTER) &&
+ !(tr.ent->svflags & SVF_DAMAGEABLE) && (!tr.ent->client))
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_LASER_SPARKS);
+ gi.WriteByte(4);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(self->s.skinnum);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ break;
+ }
+
+ ignore = tr.ent;
+ VectorCopy(tr.endpos, start);
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BFG_LASER);
+ gi.WritePosition(self->s.origin);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+fire_bfg(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, float damage_radius)
+{
+ edict_t *bfg;
+
+ if (!self)
+ {
+ return;
+ }
+
+ bfg = G_Spawn();
+ VectorCopy(start, bfg->s.origin);
+ VectorCopy(dir, bfg->movedir);
+ vectoangles(dir, bfg->s.angles);
+ VectorScale(dir, speed, bfg->velocity);
+ bfg->movetype = MOVETYPE_FLYMISSILE;
+ bfg->clipmask = MASK_SHOT;
+ bfg->solid = SOLID_BBOX;
+ bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
+ VectorClear(bfg->mins);
+ VectorClear(bfg->maxs);
+ bfg->s.modelindex = gi.modelindex("sprites/s_bfg1.sp2");
+ bfg->owner = self;
+ bfg->touch = bfg_touch;
+ bfg->nextthink = level.time + (8000.0f / (float)speed);
+ bfg->think = G_FreeEdict;
+ bfg->radius_dmg = damage;
+ bfg->dmg_radius = damage_radius;
+ bfg->classname = "bfg blast";
+ bfg->s.sound = gi.soundindex("weapons/bfg__l1a.wav");
+
+ bfg->think = bfg_think;
+ bfg->nextthink = level.time + FRAMETIME;
+ bfg->teammaster = bfg;
+ bfg->teamchain = NULL;
+
+ if (self->client)
+ {
+ check_dodge(self, bfg->s.origin, dir, speed);
+ }
+
+ gi.linkentity(bfg);
+}
diff --git a/rogue/src/header/game.h b/rogue/src/header/game.h
new file mode 100644
index 0000000..3a9d3b3
--- /dev/null
+++ b/rogue/src/header/game.h
@@ -0,0 +1,215 @@
+/* =======================================================================
+ *
+ * Here are the client, server and game are tied together.
+ *
+ * =======================================================================
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * THIS FILE IS _VERY_ FRAGILE AND THERE'S NOTHING IN IT THAT CAN OR
+ * MUST BE CHANGED. IT'S MOST LIKELY A VERY GOOD IDEA TO CLOSE THE
+ * EDITOR NOW AND NEVER LOOK BACK. OTHERWISE YOU MAY SCREW UP EVERYTHING!
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ */
+
+#ifndef ROGUE_GAME_H
+#define ROGUE_GAME_H
+
+#define GAME_API_VERSION 3
+
+#define SVF_NOCLIENT 0x00000001 /* don't send entity to clients, even if it has effects */
+#define SVF_DEADMONSTER 0x00000002 /* treat as CONTENTS_DEADMONSTER for collision */
+#define SVF_MONSTER 0x00000004 /* treat as CONTENTS_MONSTER for collision */
+#define SVF_DAMAGEABLE 0x00000008
+
+typedef enum
+{
+ SOLID_NOT, /* no interaction with other objects */
+ SOLID_TRIGGER, /* only touch when inside, after moving */
+ SOLID_BBOX, /* touch on edge */
+ SOLID_BSP /* bsp clip, touch on edge */
+} solid_t;
+
+/* =============================================================== */
+
+/* link_t is only used for entity area links now */
+typedef struct link_s
+{
+ struct link_s *prev, *next;
+} link_t;
+
+#define MAX_ENT_CLUSTERS 16
+
+typedef struct edict_s edict_t;
+typedef struct gclient_s gclient_t;
+
+#ifndef GAME_INCLUDE
+
+struct gclient_s
+{
+ player_state_t ps; /* communicated by server to clients */
+ int ping;
+ /* the game dll can add anything it wants
+ after this point in the structure */
+};
+
+struct edict_s
+{
+ entity_state_t s;
+ struct gclient_s *client;
+ qboolean inuse;
+ int linkcount;
+
+ link_t area; /* linked to a division node or leaf */
+
+ int num_clusters; /* if -1, use headnode instead */
+ int clusternums[MAX_ENT_CLUSTERS];
+ int headnode; /* unused if num_clusters != -1 */
+ int areanum, areanum2;
+
+ /* ================================ */
+
+ int svflags; /* SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc */
+ vec3_t mins, maxs;
+ vec3_t absmin, absmax, size;
+ solid_t solid;
+ int clipmask;
+ edict_t *owner;
+
+ /* the game dll can add anything it wants
+ after this point in the structure */
+};
+
+#endif /* GAME_INCLUDE */
+
+/* =============================================================== */
+
+/* functions provided by the main engine */
+typedef struct
+{
+ /* special messages */
+ void (*bprintf)(int printlevel, char *fmt, ...);
+ void (*dprintf)(char *fmt, ...);
+ void (*cprintf)(edict_t *ent, int printlevel, char *fmt, ...);
+ void (*centerprintf)(edict_t *ent, char *fmt, ...);
+ void (*sound)(edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
+ void (*positioned_sound)(vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
+
+ /* config strings hold all the index strings, the lightstyles,
+ and misc data like the sky definition and cdtrack.
+ All of the current configstrings are sent to clients when
+ they connect, and changes are sent to all connected clients. */
+ void (*configstring)(int num, char *string);
+ void (*error)(char *fmt, ...);
+
+ /* the *index functions create configstrings and some internal server state */
+ int (*modelindex)(char *name);
+ int (*soundindex)(char *name);
+ int (*imageindex)(char *name);
+
+ void (*setmodel)(edict_t *ent, char *name);
+
+ /* collision detection */
+ trace_t (*trace)(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
+ int (*pointcontents)(vec3_t point);
+ qboolean (*inPVS)(vec3_t p1, vec3_t p2);
+ qboolean (*inPHS)(vec3_t p1, vec3_t p2);
+ void (*SetAreaPortalState)(int portalnum, qboolean open);
+ qboolean (*AreasConnected)(int area1, int area2);
+
+ /* an entity will never be sent to a client or used for collision
+ if it is not passed to linkentity. If the size, position, or
+ solidity changes, it must be relinked. */
+ void (*linkentity)(edict_t *ent);
+ void (*unlinkentity)(edict_t *ent); /* call before removing an interactive edict */
+ int (*BoxEdicts)(vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype);
+ void (*Pmove)(pmove_t *pmove); /* player movement code common with client prediction */
+
+ /* network messaging */
+ void (*multicast)(vec3_t origin, multicast_t to);
+ void (*unicast)(edict_t *ent, qboolean reliable);
+ void (*WriteChar)(int c);
+ void (*WriteByte)(int c);
+ void (*WriteShort)(int c);
+ void (*WriteLong)(int c);
+ void (*WriteFloat)(float f);
+ void (*WriteString)(char *s);
+ void (*WritePosition)(vec3_t pos); /* some fractional bits */
+ void (*WriteDir)(vec3_t pos); /* single byte encoded, very coarse */
+ void (*WriteAngle)(float f);
+
+ /* managed memory allocation */
+ void *(*TagMalloc)(int size, int tag);
+ void (*TagFree)(void *block);
+ void (*FreeTags)(int tag);
+
+ /* console variable interaction */
+ cvar_t *(*cvar)(char *var_name, char *value, int flags);
+ cvar_t *(*cvar_set)(char *var_name, char *value);
+ cvar_t *(*cvar_forceset)(char *var_name, char *value);
+
+ /* ClientCommand and ServerCommand parameter access */
+ int (*argc)(void);
+ char *(*argv)(int n);
+ char *(*args)(void); /* concatenation of all argv >= 1 */
+
+ /* add commands to the server console as if they were typed in for map changing, etc */
+ void (*AddCommandString)(char *text);
+
+ void (*DebugGraph)(float value, int color);
+} game_import_t;
+
+/* functions exported by the game subsystem */
+typedef struct
+{
+ int apiversion;
+
+ /* the init function will only be called when a game starts,
+ not each time a level is loaded. Persistant data for clients
+ and the server can be allocated in init */
+ void (*Init)(void);
+ void (*Shutdown)(void);
+
+ /* each new level entered will cause a call to SpawnEntities */
+ void (*SpawnEntities)(char *mapname, char *entstring, char *spawnpoint);
+
+ /* Read/Write Game is for storing persistant cross level information
+ about the world state and the clients. WriteGame is called every time
+ a level is exited. ReadGame is called on a loadgame. */
+ void (*WriteGame)(char *filename, qboolean autosave);
+ void (*ReadGame)(char *filename);
+
+ /* ReadLevel is called after the default map
+ information has been loaded with SpawnEntities */
+ void (*WriteLevel)(char *filename);
+ void (*ReadLevel)(char *filename);
+
+ qboolean (*ClientConnect)(edict_t *ent, char *userinfo);
+ void (*ClientBegin)(edict_t *ent);
+ void (*ClientUserinfoChanged)(edict_t *ent, char *userinfo);
+ void (*ClientDisconnect)(edict_t *ent);
+ void (*ClientCommand)(edict_t *ent);
+ void (*ClientThink)(edict_t *ent, usercmd_t *cmd);
+
+ void (*RunFrame)(void);
+
+ /* ServerCommand will be called when an "sv <command>" command
+ is issued on the server console. The game can issue gi.argc()
+ gi.argv() commands to get the rest of the parameters */
+ void (*ServerCommand)(void);
+
+ /* global variables shared between game and server */
+
+ /* The edict array is allocated in the game dll so it can vary in
+ size from one game to another. The size will be fixed when
+ ge->Init() is called */
+ struct edict_s *edicts;
+ int edict_size;
+ int num_edicts; /* current number, <= max_edicts */
+ int max_edicts;
+} game_export_t;
+
+#endif /* ROGUE_GAME_H */
diff --git a/rogue/src/header/local.h b/rogue/src/header/local.h
new file mode 100644
index 0000000..003c3a7
--- /dev/null
+++ b/rogue/src/header/local.h
@@ -0,0 +1,1365 @@
+/* =======================================================================
+ *
+ * Main header file for the game module.
+ *
+ * =======================================================================
+ */
+
+#ifndef ROGUE_LOCAL_H
+#define ROGUE_LOCAL_H
+
+#include "shared.h"
+
+/* define GAME_INCLUDE so that game.h does not define the
+ short, server-visible gclient_t and edict_t structures,
+ because we define the full size ones in this file */
+#define GAME_INCLUDE
+#include "game.h"
+
+/* the "gameversion" client command will print this plus compile date */
+#define GAMEVERSION "rogue"
+
+/* protocol bytes that can be directly added to messages */
+#define svc_muzzleflash 1
+#define svc_muzzleflash2 2
+#define svc_temp_entity 3
+#define svc_layout 4
+#define svc_inventory 5
+#define svc_stufftext 11
+
+/* ================================================================== */
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#define _isnan(a) (isnan(a))
+
+/* ================================================================== */
+
+/* view pitching times */
+#define DAMAGE_TIME 0.5
+#define FALL_TIME 0.3
+
+/* these are set with checkboxes on each entity in the map editor */
+#define SPAWNFLAG_NOT_EASY 0x00000100
+#define SPAWNFLAG_NOT_MEDIUM 0x00000200
+#define SPAWNFLAG_NOT_HARD 0x00000400
+#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800
+#define SPAWNFLAG_NOT_COOP 0x00001000
+
+/* edict->flags */
+#define FL_FLY 0x00000001
+#define FL_SWIM 0x00000002 /* implied immunity to drowining */
+#define FL_IMMUNE_LASER 0x00000004
+#define FL_INWATER 0x00000008
+#define FL_GODMODE 0x00000010
+#define FL_NOTARGET 0x00000020
+#define FL_IMMUNE_SLIME 0x00000040
+#define FL_IMMUNE_LAVA 0x00000080
+#define FL_PARTIALGROUND 0x00000100 /* not all corners are valid */
+#define FL_WATERJUMP 0x00000200 /* player jumping out of water */
+#define FL_TEAMSLAVE 0x00000400 /* not the first on the team */
+#define FL_NO_KNOCKBACK 0x00000800
+#define FL_POWER_ARMOR 0x00001000 /* power armor (if any) is active */
+#define FL_COOP_TAKEN 0x00002000 /* Another client has already taken it */
+#define FL_RESPAWN 0x80000000 /* used for item respawning */
+
+#define FL_MECHANICAL 0x00002000 /* entity is mechanical, use sparks not blood */
+#define FL_SAM_RAIMI 0x00004000 /* entity is in sam raimi cam mode */
+#define FL_DISGUISED 0x00008000 /* entity is in disguise, monsters will not recognize. */
+#define FL_NOGIB 0x00010000 /* player has been vaporized by a nuke, drop no gibs */
+
+#define FRAMETIME 0.1
+
+/* memory tags to allow dynamic memory to be cleaned up */
+#define TAG_GAME 765 /* clear when unloading the dll */
+#define TAG_LEVEL 766 /* clear when loading a new level */
+
+#define MELEE_DISTANCE 80
+#define BODY_QUEUE_SIZE 8
+
+typedef enum
+{
+ DAMAGE_NO,
+ DAMAGE_YES, /* will take damage if hit */
+ DAMAGE_AIM /* auto targeting recognizes this */
+} damage_t;
+
+typedef enum
+{
+ WEAPON_READY,
+ WEAPON_ACTIVATING,
+ WEAPON_DROPPING,
+ WEAPON_FIRING
+} weaponstate_t;
+
+typedef enum
+{
+ AMMO_BULLETS,
+ AMMO_SHELLS,
+ AMMO_ROCKETS,
+ AMMO_GRENADES,
+ AMMO_CELLS,
+ AMMO_SLUGS,
+
+ AMMO_FLECHETTES,
+ AMMO_TESLA,
+ AMMO_PROX,
+ AMMO_DISRUPTOR
+} ammo_t;
+
+/* Maximum debris / gibs per frame */
+#define MAX_GIBS 20
+#define MAX_DEBRIS 20
+
+/* deadflag */
+#define DEAD_NO 0
+#define DEAD_DYING 1
+#define DEAD_DEAD 2
+#define DEAD_RESPAWNABLE 3
+
+/* range */
+#define RANGE_MELEE 0
+#define RANGE_NEAR 1
+#define RANGE_MID 2
+#define RANGE_FAR 3
+
+/* gib types */
+#define GIB_ORGANIC 0
+#define GIB_METALLIC 1
+
+/* monster ai flags */
+#define AI_STAND_GROUND 0x00000001
+#define AI_TEMP_STAND_GROUND 0x00000002
+#define AI_SOUND_TARGET 0x00000004
+#define AI_LOST_SIGHT 0x00000008
+#define AI_PURSUIT_LAST_SEEN 0x00000010
+#define AI_PURSUE_NEXT 0x00000020
+#define AI_PURSUE_TEMP 0x00000040
+#define AI_HOLD_FRAME 0x00000080
+#define AI_GOOD_GUY 0x00000100
+#define AI_BRUTAL 0x00000200
+#define AI_NOSTEP 0x00000400
+#define AI_DUCKED 0x00000800
+#define AI_COMBAT_POINT 0x00001000
+#define AI_MEDIC 0x00002000
+#define AI_RESURRECTING 0x00004000
+
+/* ROGUE */
+#define AI_WALK_WALLS 0x00008000
+#define AI_MANUAL_STEERING 0x00010000
+#define AI_TARGET_ANGER 0x00020000
+#define AI_DODGING 0x00040000
+#define AI_CHARGING 0x00080000
+#define AI_HINT_PATH 0x00100000
+#define AI_IGNORE_SHOTS 0x00200000
+#define AI_DO_NOT_COUNT 0x00400000 /* set for healed monsters */
+#define AI_SPAWNED_CARRIER 0x00800000 /* both do_not_count and spawned are set for spawned monsters */
+#define AI_SPAWNED_MEDIC_C 0x01000000 /* both do_not_count and spawned are set for spawned monsters */
+#define AI_SPAWNED_WIDOW 0x02000000 /* both do_not_count and spawned are set for spawned monsters */
+#define AI_SPAWNED_MASK 0x03800000 /* mask to catch all three flavors of spawned */
+#define AI_BLOCKED 0x04000000 /* used by blocked_checkattack: set to say I'm attacking while blocked */
+ /* (prevents run-attacks) */
+/* monster attack state */
+#define AS_STRAIGHT 1
+#define AS_SLIDING 2
+#define AS_MELEE 3
+#define AS_MISSILE 4
+#define AS_BLIND 5
+
+/* armor types */
+#define ARMOR_NONE 0
+#define ARMOR_JACKET 1
+#define ARMOR_COMBAT 2
+#define ARMOR_BODY 3
+#define ARMOR_SHARD 4
+
+/* power armor types */
+#define POWER_ARMOR_NONE 0
+#define POWER_ARMOR_SCREEN 1
+#define POWER_ARMOR_SHIELD 2
+
+/* handedness values */
+#define RIGHT_HANDED 0
+#define LEFT_HANDED 1
+#define CENTER_HANDED 2
+
+/* game.serverflags values */
+#define SFL_CROSS_TRIGGER_1 0x00000001
+#define SFL_CROSS_TRIGGER_2 0x00000002
+#define SFL_CROSS_TRIGGER_3 0x00000004
+#define SFL_CROSS_TRIGGER_4 0x00000008
+#define SFL_CROSS_TRIGGER_5 0x00000010
+#define SFL_CROSS_TRIGGER_6 0x00000020
+#define SFL_CROSS_TRIGGER_7 0x00000040
+#define SFL_CROSS_TRIGGER_8 0x00000080
+#define SFL_CROSS_TRIGGER_MASK 0x000000ff
+
+/* noise types for PlayerNoise */
+#define PNOISE_SELF 0
+#define PNOISE_WEAPON 1
+#define PNOISE_IMPACT 2
+
+/* edict->movetype values */
+typedef enum
+{
+ MOVETYPE_NONE, /* never moves */
+ MOVETYPE_NOCLIP, /* origin and angles change with no interaction */
+ MOVETYPE_PUSH, /* no clip to world, push on box contact */
+ MOVETYPE_STOP, /* no clip to world, stops on box contact */
+
+ MOVETYPE_WALK, /* gravity */
+ MOVETYPE_STEP, /* gravity, special edge handling */
+ MOVETYPE_FLY,
+ MOVETYPE_TOSS, /* gravity */
+ MOVETYPE_FLYMISSILE, /* extra size to monsters */
+ MOVETYPE_BOUNCE,
+ MOVETYPE_NEWTOSS /* for deathball */
+} movetype_t;
+
+typedef struct
+{
+ int base_count;
+ int max_count;
+ float normal_protection;
+ float energy_protection;
+ int armor;
+} gitem_armor_t;
+
+#define IT_WEAPON 0x00000001 /* use makes active weapon */
+#define IT_AMMO 0x00000002
+#define IT_ARMOR 0x00000004
+#define IT_STAY_COOP 0x00000008
+#define IT_KEY 0x00000010
+#define IT_POWERUP 0x00000020
+#define IT_MELEE 0x00000040
+#define IT_NOT_GIVEABLE 0x00000080 /* item can not be given */
+#define IT_INSTANT_USE 0x000000100 /* item is insta-used on pickup if dmflag is set */
+
+/* gitem_t->weapmodel for weapons indicates model index */
+#define WEAP_BLASTER 1
+#define WEAP_SHOTGUN 2
+#define WEAP_SUPERSHOTGUN 3
+#define WEAP_MACHINEGUN 4
+#define WEAP_CHAINGUN 5
+#define WEAP_GRENADES 6
+#define WEAP_GRENADELAUNCHER 7
+#define WEAP_ROCKETLAUNCHER 8
+#define WEAP_HYPERBLASTER 9
+#define WEAP_RAILGUN 10
+#define WEAP_BFG 11
+
+#define WEAP_DISRUPTOR 12
+#define WEAP_ETFRIFLE 13
+#define WEAP_PLASMA 14
+#define WEAP_PROXLAUNCH 15
+#define WEAP_CHAINFIST 16
+
+typedef struct gitem_s
+{
+ char *classname; /* spawning name */
+ qboolean (*pickup)(struct edict_s *ent, struct edict_s *other);
+ void (*use)(struct edict_s *ent, struct gitem_s *item);
+ void (*drop)(struct edict_s *ent, struct gitem_s *item);
+ void (*weaponthink)(struct edict_s *ent);
+ char *pickup_sound;
+ char *world_model;
+ int world_model_flags;
+ char *view_model;
+
+ /* client side info */
+ char *icon;
+ char *pickup_name; /* for printing on pickup */
+ int count_width; /* number of digits to display by icon */
+
+ int quantity; /* for ammo how much, for weapons how much is used per shot */
+ char *ammo; /* for weapons */
+ int flags; /* IT_* flags */
+
+ int weapmodel; /* weapon model index (for weapons) */
+
+ void *info;
+ int tag;
+
+ char *precaches; /* string of all models, sounds, and images this item will use */
+} gitem_t;
+
+/* this structure is left intact through an entire game
+ it should be initialized at dll load time, and read/written to
+ the server.ssv file for savegames */
+typedef struct
+{
+ char helpmessage1[512];
+ char helpmessage2[512];
+ int helpchanged; /* flash F1 icon if non 0, play sound */
+ /* and increment only if 1, 2, or 3 */
+
+ gclient_t *clients; /* [maxclients] */
+
+ /* can't store spawnpoint in level, because it
+ would get overwritten by the savegame restore */
+ char spawnpoint[512]; /* needed for coop respawns */
+
+ /* store latched cvars here that we want to get at often */
+ int maxclients;
+ int maxentities;
+
+ /* cross level triggers */
+ int serverflags;
+
+ /* items */
+ int num_items;
+
+ qboolean autosaved;
+} game_locals_t;
+
+/* this structure is cleared as each map is entered
+ it is read/written to the level.sav file for savegames */
+typedef struct
+{
+ int framenum;
+ float time;
+
+ char level_name[MAX_QPATH]; /* the descriptive name (Outer Base, etc) */
+ char mapname[MAX_QPATH]; /* the server name (base1, etc) */
+ char nextmap[MAX_QPATH]; /* go here when fraglimit is hit */
+
+ /* intermission state */
+ float intermissiontime; /* time the intermission was started */
+ char *changemap;
+ int exitintermission;
+ vec3_t intermission_origin;
+ vec3_t intermission_angle;
+
+ edict_t *sight_client; /* changed once each frame for coop games */
+
+ edict_t *sight_entity;
+ int sight_entity_framenum;
+ edict_t *sound_entity;
+ int sound_entity_framenum;
+ edict_t *sound2_entity;
+ int sound2_entity_framenum;
+
+ int pic_health;
+
+ int total_secrets;
+ int found_secrets;
+
+ int total_goals;
+ int found_goals;
+
+ int total_monsters;
+ int killed_monsters;
+
+ edict_t *current_entity; /* entity running from G_RunFrame */
+ int body_que; /* dead bodies */
+
+ int power_cubes; /* ugly necessity for coop */
+
+ edict_t *disguise_violator;
+ int disguise_violation_framenum;
+} level_locals_t;
+
+/* spawn_temp_t is only used to hold entity field values that
+ can be set from the editor, but aren't actualy present/
+ in edict_t during gameplay */
+typedef struct
+{
+ /* world vars */
+ char *sky;
+ float skyrotate;
+ vec3_t skyaxis;
+ char *nextmap;
+
+ int lip;
+ int distance;
+ int height;
+ char *noise;
+ float pausetime;
+ char *item;
+ char *gravity;
+
+ float minyaw;
+ float maxyaw;
+ float minpitch;
+ float maxpitch;
+} spawn_temp_t;
+
+typedef struct
+{
+ /* fixed data */
+ vec3_t start_origin;
+ vec3_t start_angles;
+ vec3_t end_origin;
+ vec3_t end_angles;
+
+ int sound_start;
+ int sound_middle;
+ int sound_end;
+
+ float accel;
+ float speed;
+ float decel;
+ float distance;
+
+ float wait;
+
+ /* state data */
+ int state;
+ vec3_t dir;
+ float current_speed;
+ float move_speed;
+ float next_speed;
+ float remaining_distance;
+ float decel_distance;
+ void (*endfunc)(edict_t *);
+} moveinfo_t;
+
+typedef struct
+{
+ void (*aifunc)(edict_t *self, float dist);
+ float dist;
+ void (*thinkfunc)(edict_t *self);
+} mframe_t;
+
+typedef struct
+{
+ int firstframe;
+ int lastframe;
+ mframe_t *frame;
+ void (*endfunc)(edict_t *self);
+} mmove_t;
+
+typedef struct
+{
+ mmove_t *currentmove;
+ unsigned int aiflags; /* unsigned, since we're close to the max */
+ int nextframe;
+ float scale;
+
+ void (*stand)(edict_t *self);
+ void (*idle)(edict_t *self);
+ void (*search)(edict_t *self);
+ void (*walk)(edict_t *self);
+ void (*run)(edict_t *self);
+ void (*dodge)(edict_t *self, edict_t *other, float eta, trace_t *tr);
+ void (*attack)(edict_t *self);
+ void (*melee)(edict_t *self);
+ void (*sight)(edict_t *self, edict_t *other);
+ qboolean (*checkattack)(edict_t *self);
+
+ float pausetime;
+ float attack_finished;
+
+ vec3_t saved_goal;
+ float search_time;
+ float trail_time;
+ vec3_t last_sighting;
+ int attack_state;
+ int lefty;
+ float idle_time;
+ int linkcount;
+
+ int power_armor_type;
+ int power_armor_power;
+
+ qboolean (*blocked)(edict_t *self, float dist);
+ float last_hint_time; /* last time the monster checked for hintpaths. */
+ edict_t *goal_hint; /* which hint_path we're trying to get to */
+ int medicTries;
+ edict_t *badMedic1, *badMedic2; /* these medics have declared this monster "unhealable" */
+ edict_t *healer; /* this is who is healing this monster */
+ void (*duck)(edict_t *self, float eta);
+ void (*unduck)(edict_t *self);
+ void (*sidestep)(edict_t *self);
+ float base_height;
+ float next_duck_time;
+ float duck_wait_time;
+ edict_t *last_player_enemy;
+ qboolean blindfire; /* will the monster blindfire? */
+ float blind_fire_delay;
+ vec3_t blind_fire_target;
+
+ /* used by the spawners to not spawn too much and keep track of #s of monsters spawned */
+ int monster_slots;
+ int monster_used;
+ edict_t *commander;
+
+ /* powerup timers, used by widow, our friend */
+ float quad_framenum;
+ float invincible_framenum;
+ float double_framenum;
+} monsterinfo_t;
+
+/* this determines how long to wait after a duck to duck again.
+ this needs to be longer than the time after the monster_duck_up
+ in all of the animation sequences */
+#define DUCK_INTERVAL 0.5
+
+extern game_locals_t game;
+extern level_locals_t level;
+extern game_import_t gi;
+extern game_export_t globals;
+extern spawn_temp_t st;
+
+extern int sm_meat_index;
+extern int snd_fry;
+
+extern int jacket_armor_index;
+extern int combat_armor_index;
+extern int body_armor_index;
+
+extern int debristhisframe;
+extern int gibsthisframe;
+
+/* means of death */
+#define MOD_UNKNOWN 0
+#define MOD_BLASTER 1
+#define MOD_SHOTGUN 2
+#define MOD_SSHOTGUN 3
+#define MOD_MACHINEGUN 4
+#define MOD_CHAINGUN 5
+#define MOD_GRENADE 6
+#define MOD_G_SPLASH 7
+#define MOD_ROCKET 8
+#define MOD_R_SPLASH 9
+#define MOD_HYPERBLASTER 10
+#define MOD_RAILGUN 11
+#define MOD_BFG_LASER 12
+#define MOD_BFG_BLAST 13
+#define MOD_BFG_EFFECT 14
+#define MOD_HANDGRENADE 15
+#define MOD_HG_SPLASH 16
+#define MOD_WATER 17
+#define MOD_SLIME 18
+#define MOD_LAVA 19
+#define MOD_CRUSH 20
+#define MOD_TELEFRAG 21
+#define MOD_FALLING 22
+#define MOD_SUICIDE 23
+#define MOD_HELD_GRENADE 24
+#define MOD_EXPLOSIVE 25
+#define MOD_BARREL 26
+#define MOD_BOMB 27
+#define MOD_EXIT 28
+#define MOD_SPLASH 29
+#define MOD_TARGET_LASER 30
+#define MOD_TRIGGER_HURT 31
+#define MOD_HIT 32
+#define MOD_TARGET_BLASTER 33
+#define MOD_FRIENDLY_FIRE 0x8000000
+#define MOD_CHAINFIST 40
+#define MOD_DISINTEGRATOR 41
+#define MOD_ETF_RIFLE 42
+#define MOD_BLASTER2 43
+#define MOD_HEATBEAM 44
+#define MOD_TESLA 45
+#define MOD_PROX 46
+#define MOD_NUKE 47
+#define MOD_VENGEANCE_SPHERE 48
+#define MOD_HUNTER_SPHERE 49
+#define MOD_DEFENDER_SPHERE 50
+#define MOD_TRACKER 51
+#define MOD_DBALL_CRUSH 52
+#define MOD_DOPPLE_EXPLODE 53
+#define MOD_DOPPLE_VENGEANCE 54
+#define MOD_DOPPLE_HUNTER 55
+
+/* Easier handling of AI skill levels */
+#define SKILL_EASY 0
+#define SKILL_MEDIUM 1
+#define SKILL_HARD 2
+#define SKILL_HARDPLUS 3
+
+extern int meansOfDeath;
+extern edict_t *g_edicts;
+
+#define FOFS(x) (size_t)&(((edict_t *)NULL)->x)
+#define STOFS(x) (size_t)&(((spawn_temp_t *)NULL)->x)
+#define LLOFS(x) (size_t)&(((level_locals_t *)NULL)->x)
+#define CLOFS(x) (size_t)&(((gclient_t *)NULL)->x)
+
+#define random() ((randk() & 0x7fff) / ((float)0x7fff))
+#define crandom() (2.0 * (random() - 0.5))
+
+extern cvar_t *maxentities;
+extern cvar_t *deathmatch;
+extern cvar_t *coop;
+extern cvar_t *coop_baseq2; /* treat spawnflags according to baseq2 rules */
+extern cvar_t *coop_elevator_delay;
+extern cvar_t *coop_pickup_weapons;
+extern cvar_t *dmflags;
+extern cvar_t *skill;
+extern cvar_t *fraglimit;
+extern cvar_t *timelimit;
+extern cvar_t *password;
+extern cvar_t *spectator_password;
+extern cvar_t *g_select_empty;
+extern cvar_t *dedicated;
+extern cvar_t *g_footsteps;
+extern cvar_t *g_fix_triggered;
+
+extern cvar_t *filterban;
+
+extern cvar_t *sv_gravity;
+extern cvar_t *sv_maxvelocity;
+
+extern cvar_t *gun_x, *gun_y, *gun_z;
+extern cvar_t *sv_rollspeed;
+extern cvar_t *sv_rollangle;
+
+extern cvar_t *run_pitch;
+extern cvar_t *run_roll;
+extern cvar_t *bob_up;
+extern cvar_t *bob_pitch;
+extern cvar_t *bob_roll;
+
+extern cvar_t *sv_cheats;
+extern cvar_t *maxclients;
+extern cvar_t *maxspectators;
+
+extern cvar_t *flood_msgs;
+extern cvar_t *flood_persecond;
+extern cvar_t *flood_waitdelay;
+
+extern cvar_t *sv_maplist;
+
+extern cvar_t *sv_stopspeed;
+
+extern cvar_t *g_showlogic;
+extern cvar_t *gamerules;
+extern cvar_t *huntercam;
+extern cvar_t *strong_mines;
+extern cvar_t *randomrespawn;
+
+extern cvar_t *g_disruptor;
+
+extern cvar_t *aimfix;
+extern cvar_t *g_machinegun_norecoil;
+extern cvar_t *g_swap_speed;
+
+/* this is for the count of monsters */
+#define ENT_SLOTS_LEFT \
+ (ent->monsterinfo.monster_slots - \
+ ent->monsterinfo.monster_used)
+#define SELF_SLOTS_LEFT \
+ (self->monsterinfo.monster_slots - \
+ self->monsterinfo.monster_used)
+
+#define world (&g_edicts[0])
+
+/* item spawnflags */
+#define ITEM_TRIGGER_SPAWN 0x00000001
+#define ITEM_NO_TOUCH 0x00000002
+/* 6 bits reserved for editor flags */
+/* 8 bits used as power cube id bits for coop games */
+#define DROPPED_ITEM 0x00010000
+#define DROPPED_PLAYER_ITEM 0x00020000
+#define ITEM_TARGETS_USED 0x00040000
+
+/* fields are needed for spawning from the entity string
+ and saving / loading games */
+#define FFL_SPAWNTEMP 1
+#define FFL_NOSPAWN 2
+
+typedef enum
+{
+ F_INT,
+ F_FLOAT,
+ F_LSTRING, /* string on disk, pointer in memory, TAG_LEVEL */
+ F_GSTRING, /* string on disk, pointer in memory, TAG_GAME */
+ F_VECTOR,
+ F_ANGLEHACK,
+ F_EDICT, /* index on disk, pointer in memory */
+ F_ITEM, /* index on disk, pointer in memory */
+ F_CLIENT, /* index on disk, pointer in memory */
+ F_FUNCTION,
+ F_MMOVE,
+ F_IGNORE
+} fieldtype_t;
+
+typedef struct
+{
+ char *name;
+ int ofs;
+ fieldtype_t type;
+ int flags;
+ short save_ver;
+} field_t;
+
+extern field_t fields[];
+extern gitem_t itemlist[];
+
+/* g_cmds.c */
+void Cmd_Help_f(edict_t *ent);
+
+/* g_items.c */
+void PrecacheItem(gitem_t *it);
+void InitItems(void);
+void SetItemNames(void);
+gitem_t *FindItem(char *pickup_name);
+gitem_t *FindItemByClassname(char *classname);
+
+#define ITEM_INDEX(x) ((x) - itemlist)
+edict_t *Drop_Item(edict_t *ent, gitem_t *item);
+void SetRespawn(edict_t *ent, float delay);
+void ChangeWeapon(edict_t *ent);
+void SpawnItem(edict_t *ent, gitem_t *item);
+void Think_Weapon(edict_t *ent);
+int ArmorIndex(edict_t *ent);
+int PowerArmorType(edict_t *ent);
+gitem_t *GetItemByIndex(int index);
+qboolean Add_Ammo(edict_t *ent, gitem_t *item, int count);
+void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane,
+ csurface_t *surf);
+
+/* g_utils.c */
+qboolean KillBox(edict_t *ent);
+void G_ProjectSource(vec3_t point, vec3_t distance, vec3_t forward,
+ vec3_t right, vec3_t result);
+edict_t *G_Find(edict_t *from, int fieldofs, char *match);
+edict_t *findradius(edict_t *from, vec3_t org, float rad);
+edict_t *G_PickTarget(char *targetname);
+void G_UseTargets(edict_t *ent, edict_t *activator);
+void G_SetMovedir(vec3_t angles, vec3_t movedir);
+
+void G_InitEdict(edict_t *e);
+edict_t *G_SpawnOptional(void);
+edict_t *G_Spawn(void);
+void G_FreeEdict(edict_t *e);
+
+void G_TouchTriggers(edict_t *ent);
+void G_TouchSolids(edict_t *ent);
+
+char *G_CopyString(char *in);
+
+float *tv(float x, float y, float z);
+char *vtos(vec3_t v);
+void get_normal_vector(const cplane_t *p, vec3_t normal);
+
+float vectoyaw(vec3_t vec);
+void vectoangles(vec3_t vec, vec3_t angles);
+
+void G_ProjectSource2(vec3_t point, vec3_t distance, vec3_t forward, vec3_t right,
+ vec3_t up, vec3_t result);
+float vectoyaw2(vec3_t vec);
+void vectoangles2(vec3_t vec, vec3_t angles);
+edict_t *findradius2(edict_t *from, vec3_t org, float rad);
+
+/* g_combat.c */
+qboolean OnSameTeam(edict_t *ent1, edict_t *ent2);
+qboolean CanDamage(edict_t *targ, edict_t *inflictor);
+void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir,
+ vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod);
+void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore,
+ float radius, int mod);
+
+void T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage,
+ edict_t *ignore, float radius, int mod);
+void T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage,
+ char *ignoreClass, float radius, int mod);
+void cleanupHealTarget(edict_t *ent);
+
+/* damage flags */
+#define DAMAGE_RADIUS 0x00000001 /* damage was indirect */
+#define DAMAGE_NO_ARMOR 0x00000002 /* armour does not protect from this damage */
+#define DAMAGE_ENERGY 0x00000004 /* damage is from an energy based weapon */
+#define DAMAGE_NO_KNOCKBACK 0x00000008 /* do not affect velocity, just view angles */
+#define DAMAGE_BULLET 0x00000010 /* damage is from a bullet (used for ricochets) */
+#define DAMAGE_NO_PROTECTION 0x00000020 /* armor, shields, invulnerability, and godmode have no effect */
+#define DAMAGE_DESTROY_ARMOR 0x00000040 /* damage is done to armor and health. */
+#define DAMAGE_NO_REG_ARMOR 0x00000080 /* damage skips regular armor */
+#define DAMAGE_NO_POWER_ARMOR 0x00000100 /* damage skips power armor */
+
+#define DEFAULT_BULLET_HSPREAD 300
+#define DEFAULT_BULLET_VSPREAD 500
+#define DEFAULT_SHOTGUN_HSPREAD 1000
+#define DEFAULT_SHOTGUN_VSPREAD 500
+#define DEFAULT_SHOTGUN_COUNT 12
+#define DEFAULT_SSHOTGUN_COUNT 20
+
+/* g_monster.c */
+void monster_fire_bullet(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int kick, int hspread, int vspread, int flashtype);
+void monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int count, int flashtype);
+void monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect);
+void monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int flashtype);
+void monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype);
+void monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int flashtype);
+void monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int kick, float damage_radius, int flashtype);
+void M_droptofloor(edict_t *ent);
+void monster_think(edict_t *self);
+void walkmonster_start(edict_t *self);
+void swimmonster_start(edict_t *self);
+void flymonster_start(edict_t *self);
+void AttackFinished(edict_t *self, float time);
+void monster_death_use(edict_t *self);
+void M_CatagorizePosition(edict_t *ent);
+qboolean M_CheckAttack(edict_t *self);
+void M_FlyCheck(edict_t *self);
+void M_CheckGround(edict_t *ent);
+
+void monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect);
+void monster_fire_tracker(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, edict_t *enemy, int flashtype);
+void monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, vec3_t offset,
+ int damage, int kick, int flashtype);
+void stationarymonster_start(edict_t *self);
+void monster_done_dodge(edict_t *self);
+
+/* g_misc.c */
+void ThrowHead(edict_t *self, char *gibname, int damage, int type);
+void ThrowClientHead(edict_t *self, int damage);
+void ThrowGib(edict_t *self, char *gibname, int damage, int type);
+void BecomeExplosion1(edict_t *self);
+
+/* g_ai.c */
+void AI_SetSightClient(void);
+
+void ai_stand(edict_t *self, float dist);
+void ai_move(edict_t *self, float dist);
+void ai_walk(edict_t *self, float dist);
+void ai_turn(edict_t *self, float dist);
+void ai_run(edict_t *self, float dist);
+void ai_charge(edict_t *self, float dist);
+int range(edict_t *self, edict_t *other);
+
+void FoundTarget(edict_t *self);
+qboolean infront(edict_t *self, edict_t *other);
+qboolean visible(edict_t *self, edict_t *other);
+qboolean FacingIdeal(edict_t *self);
+
+/* g_weapon.c */
+void ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin);
+qboolean fire_hit(edict_t *self, vec3_t aim, int damage, int kick);
+void fire_bullet(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int mod);
+void fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int count, int mod);
+void fire_blaster(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int effect, qboolean hyper);
+void fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius);
+void fire_grenade2(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius, qboolean held);
+void fire_rocket(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, float damage_radius, int radius_damage);
+void fire_rail(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick);
+void fire_bfg(edict_t *self, vec3_t start, vec3_t dir,
+ int damage, int speed, float damage_radius);
+
+/* g_ptrail.c */
+void PlayerTrail_Init(void);
+void PlayerTrail_Add(vec3_t spot);
+void PlayerTrail_New(vec3_t spot);
+edict_t *PlayerTrail_PickFirst(edict_t *self);
+edict_t *PlayerTrail_PickNext(edict_t *self);
+edict_t *PlayerTrail_LastSpot(void);
+
+/* g_client.c */
+void respawn(edict_t *ent);
+void BeginIntermission(edict_t *targ);
+void PutClientInServer(edict_t *ent);
+void InitClientPersistant(gclient_t *client);
+void InitClientResp(gclient_t *client);
+void InitBodyQue(void);
+void ClientBeginServerFrame(edict_t *ent);
+
+/* g_player.c */
+void player_pain(edict_t *self, edict_t *other, float kick, int damage);
+void player_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+/* g_svcmds.c */
+void ServerCommand(void);
+qboolean SV_FilterPacket(char *from);
+
+/* p_view.c */
+void ClientEndServerFrame(edict_t *ent);
+
+/* p_hud.c */
+void MoveClientToIntermission(edict_t *client);
+void G_SetStats(edict_t *ent);
+void G_SetSpectatorStats(edict_t *ent);
+void G_CheckChaseStats(edict_t *ent);
+void ValidateSelectedItem(edict_t *ent);
+void DeathmatchScoreboardMessage(edict_t *client, edict_t *killer);
+void HelpComputerMessage(edict_t *client);
+void InventoryMessage(edict_t *client);
+
+/* g_pweapon.c */
+void PlayerNoise(edict_t *who, vec3_t where, int type);
+
+/* m_move.c */
+qboolean M_CheckBottom(edict_t *ent);
+qboolean M_walkmove(edict_t *ent, float yaw, float dist);
+void M_MoveToGoal(edict_t *ent, float dist);
+void M_ChangeYaw(edict_t *ent);
+
+/* g_phys.c */
+void G_RunEntity(edict_t *ent);
+
+/* g_main.c */
+void SaveClientData(void);
+void FetchClientEntData(edict_t *ent);
+
+/* g_chase.c */
+void UpdateChaseCam(edict_t *ent);
+void ChaseNext(edict_t *ent);
+void ChasePrev(edict_t *ent);
+void GetChaseTarget(edict_t *ent);
+
+void fire_flechette(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int kick);
+void fire_prox(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed);
+void fire_nuke(edict_t *self, vec3_t start, vec3_t aimdir, int speed);
+void fire_flame(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed);
+void fire_burst(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed);
+void fire_maintain(edict_t *, edict_t *, vec3_t start, vec3_t aimdir,
+ int damage, int speed);
+void fire_incendiary_grenade(edict_t *self, vec3_t start, vec3_t aimdir,
+ int damage, int speed, float timer, float damage_radius);
+void fire_player_melee(edict_t *self, vec3_t start, vec3_t aim, int reach,
+ int damage, int kick, int quiet, int mod);
+void fire_tesla(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed);
+void fire_blaster2(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int effect, qboolean hyper);
+void fire_heat(edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset,
+ int damage, int kick,
+ qboolean monster);
+void fire_tracker(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, edict_t *enemy);
+
+/* g_newai.c */
+qboolean blocked_checkplat(edict_t *self, float dist);
+qboolean blocked_checkjump(edict_t *self, float dist, float maxDown, float maxUp);
+qboolean blocked_checknewenemy(edict_t *self);
+qboolean monsterlost_checkhint(edict_t *self);
+qboolean inback(edict_t *self, edict_t *other);
+float realrange(edict_t *self, edict_t *other);
+edict_t *SpawnBadArea(vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner);
+edict_t *CheckForBadArea(edict_t *ent);
+qboolean MarkTeslaArea(edict_t *self, edict_t *tesla);
+void InitHintPaths(void);
+void PredictAim(edict_t *target, vec3_t start, float bolt_speed, qboolean eye_height,
+ float offset, vec3_t aimdir, vec3_t aimpoint);
+qboolean below(edict_t *self, edict_t *other);
+void drawbbox(edict_t *self);
+void M_MonsterDodge(edict_t *self, edict_t *attacker, float eta, trace_t *tr);
+void monster_duck_down(edict_t *self);
+void monster_duck_hold(edict_t *self);
+void monster_duck_up(edict_t *self);
+qboolean has_valid_enemy(edict_t *self);
+void TargetTesla(edict_t *self, edict_t *tesla);
+void hintpath_stop(edict_t *self);
+edict_t *PickCoopTarget(edict_t *self);
+int CountPlayers(void);
+void monster_jump_start(edict_t *self);
+qboolean monster_jump_finished(edict_t *self);
+qboolean blind_rocket_ok (edict_t *self, vec3_t start, vec3_t right,
+ vec3_t target, float ofs, vec3_t dir);
+
+/* g_sphere.c */
+void Defender_Launch(edict_t *self);
+void Vengeance_Launch(edict_t *self);
+void Hunter_Launch(edict_t *self);
+
+/* g_newdm.c */
+void InitGameRules(void);
+edict_t *DoRandomRespawn(edict_t *ent);
+void PrecacheForRandomRespawn(void);
+qboolean Tag_PickupToken(edict_t *ent, edict_t *other);
+void Tag_DropToken(edict_t *ent, gitem_t *item);
+void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker);
+void fire_doppleganger(edict_t *ent, vec3_t start, vec3_t aimdir);
+
+/* g_spawn.c */
+edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname);
+edict_t *CreateFlyMonster(vec3_t origin, vec3_t angles, vec3_t mins,
+ vec3_t maxs, char *classname);
+edict_t *CreateGroundMonster(vec3_t origin, vec3_t angles, vec3_t mins,
+ vec3_t maxs, char *classname, int height);
+qboolean FindSpawnPoint(vec3_t startpoint, vec3_t mins, vec3_t maxs,
+ vec3_t spawnpoint, float maxMoveUp);
+qboolean CheckSpawnPoint(vec3_t origin, vec3_t mins, vec3_t maxs);
+qboolean CheckGroundSpawnPoint(vec3_t origin, vec3_t entMins, vec3_t entMaxs,
+ float height, float gravity);
+void DetermineBBox(char *classname, vec3_t mins, vec3_t maxs);
+void SpawnGrow_Spawn(vec3_t startpos, int size);
+void Widowlegs_Spawn(vec3_t startpos, vec3_t angles);
+
+/* p_client.c */
+void RemoveAttackingPainDaemons(edict_t *self);
+
+/* ============================================================================ */
+
+/* client_t->anim_priority */
+#define ANIM_BASIC 0 /* stand / run */
+#define ANIM_WAVE 1
+#define ANIM_JUMP 2
+#define ANIM_PAIN 3
+#define ANIM_ATTACK 4
+#define ANIM_DEATH 5
+#define ANIM_REVERSE 6
+
+/* client data that stays across multiple level loads */
+typedef struct
+{
+ char userinfo[MAX_INFO_STRING];
+ char netname[16];
+ int hand;
+
+ qboolean connected; /* a loadgame will leave valid entities that
+ just don't have a connection yet */
+
+ /* values saved and restored from edicts when changing levels */
+ int health;
+ int max_health;
+ int savedFlags;
+
+ int selected_item;
+ int inventory[MAX_ITEMS];
+
+ /* ammo capacities */
+ int max_bullets;
+ int max_shells;
+ int max_rockets;
+ int max_grenades;
+ int max_cells;
+ int max_slugs;
+
+ gitem_t *weapon;
+ gitem_t *lastweapon;
+
+ int power_cubes; /* used for tracking the cubes in coop games */
+ int score; /* for calculating total unit score in coop games */
+
+ int game_helpchanged;
+ int helpchanged;
+
+ qboolean spectator; /* client is a spectator */
+
+ int max_tesla;
+ int max_prox;
+ int max_mines;
+ int max_flechettes;
+ int max_rounds;
+} client_persistant_t;
+
+/* client data that stays across deathmatch respawns */
+typedef struct
+{
+ client_persistant_t coop_respawn; /* what to set client->pers to on a respawn */
+ int enterframe; /* level.framenum the client entered the game */
+ int score; /* frags, etc */
+ vec3_t cmd_angles; /* angles sent over in the last command */
+
+ qboolean spectator; /* client is a spectator */
+} client_respawn_t;
+
+/* this structure is cleared on each
+ PutClientInServer(), except for 'client->pers' */
+struct gclient_s
+{
+ /* known to server */
+ player_state_t ps; /* communicated by server to clients */
+ int ping;
+
+ /* private to game */
+ client_persistant_t pers;
+ client_respawn_t resp;
+ pmove_state_t old_pmove; /* for detecting out-of-pmove changes */
+
+ qboolean showscores; /* set layout stat */
+ qboolean showinventory; /* set layout stat */
+ qboolean showhelp;
+ qboolean showhelpicon;
+
+ int ammo_index;
+
+ int buttons;
+ int oldbuttons;
+ int latched_buttons;
+
+ qboolean weapon_thunk;
+
+ gitem_t *newweapon;
+
+ /* sum up damage over an entire frame, so
+ shotgun blasts give a single big kick */
+ int damage_armor; /* damage absorbed by armor */
+ int damage_parmor; /* damage absorbed by power armor */
+ int damage_blood; /* damage taken out of health */
+ int damage_knockback; /* impact damage */
+ vec3_t damage_from; /* origin for vector calculation */
+
+ float killer_yaw; /* when dead, look at killer */
+
+ weaponstate_t weaponstate;
+ vec3_t kick_angles; /* weapon kicks */
+ vec3_t kick_origin;
+ float v_dmg_roll, v_dmg_pitch, v_dmg_time; /* damage kicks */
+ float fall_time, fall_value; /* for view drop on fall */
+ float damage_alpha;
+ float bonus_alpha;
+ vec3_t damage_blend;
+ vec3_t v_angle; /* aiming direction */
+ float bobtime; /* so off-ground doesn't change it */
+ vec3_t oldviewangles;
+ vec3_t oldvelocity;
+
+ float next_drown_time;
+ int old_waterlevel;
+ int breather_sound;
+
+ int machinegun_shots; /* for weapon raising */
+
+ /* animation vars */
+ int anim_end;
+ int anim_priority;
+ qboolean anim_duck;
+ qboolean anim_run;
+
+ /* powerup timers */
+ float quad_framenum;
+ float invincible_framenum;
+ float breather_framenum;
+ float enviro_framenum;
+
+ qboolean grenade_blew_up;
+ float grenade_time;
+ int silencer_shots;
+ int weapon_sound;
+
+ float pickup_msg_time;
+
+ float flood_locktill; /* locked from talking */
+ float flood_when[10]; /* when messages were said */
+ int flood_whenhead; /* head pointer for when said */
+
+ float respawn_time; /* can respawn when time > this */
+
+ edict_t *chase_target; /* player we are chasing */
+ qboolean update_chase; /* need to update chase info? */
+
+ float double_framenum;
+ float ir_framenum;
+ float nuke_framenum;
+ float tracker_pain_framenum;
+
+ edict_t *owned_sphere; /* this points to the player's sphere */
+};
+
+struct edict_s
+{
+ entity_state_t s;
+ struct gclient_s *client; /* NULL if not a player the server expects the first part
+ of gclient_s to be a player_state_t but the rest of it is
+ opaque */
+
+ qboolean inuse;
+ int linkcount;
+
+ link_t area; /* linked to a division node or leaf */
+
+ int num_clusters; /* if -1, use headnode instead */
+ int clusternums[MAX_ENT_CLUSTERS];
+ int headnode; /* unused if num_clusters != -1 */
+ int areanum, areanum2;
+
+ /* ================================ */
+
+ int svflags;
+ vec3_t mins, maxs;
+ vec3_t absmin, absmax, size;
+ solid_t solid;
+ int clipmask;
+ edict_t *owner;
+
+ /* DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER */
+ /* EXPECTS THE FIELDS IN THAT ORDER! */
+
+ /* ================================ */
+
+ int movetype;
+ int flags;
+
+ char *model;
+ float freetime; /* sv.time when the object was freed */
+
+ /* only used locally in game, not by server */
+ char *message;
+ char *classname;
+ int spawnflags;
+
+ float timestamp;
+
+ float angle; /* set in qe3, -1 = up, -2 = down */
+ char *target;
+ char *targetname;
+ char *killtarget;
+ char *team;
+ char *pathtarget;
+ char *deathtarget;
+ char *combattarget;
+ edict_t *target_ent;
+
+ float speed, accel, decel;
+ vec3_t movedir;
+ vec3_t pos1, pos2;
+
+ vec3_t velocity;
+ vec3_t avelocity;
+ int mass;
+ float air_finished;
+ float gravity; /* per entity gravity multiplier (1.0 is normal) */
+ /* use for lowgrav artifact, flares */
+
+ edict_t *goalentity;
+ edict_t *movetarget;
+ float yaw_speed;
+ float ideal_yaw;
+
+ float nextthink;
+ void (*prethink) (edict_t *ent);
+ void (*think)(edict_t *self);
+ void (*blocked)(edict_t *self, edict_t *other); /* move to moveinfo? */
+ void (*touch)(edict_t *self, edict_t *other, cplane_t *plane,
+ csurface_t *surf);
+ void (*use)(edict_t *self, edict_t *other, edict_t *activator);
+ void (*pain)(edict_t *self, edict_t *other, float kick, int damage);
+ void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+ float touch_debounce_time;
+ float pain_debounce_time;
+ float damage_debounce_time;
+ float fly_sound_debounce_time; /* now also used by insane marines to store pain sound timeout */
+ float last_move_time;
+
+ int health;
+ int max_health;
+ int gib_health;
+ int deadflag;
+
+ float show_hostile;
+ float powerarmor_time;
+
+ char *map; /* target_changelevel */
+
+ int viewheight; /* height above origin where eyesight is determined */
+ int takedamage;
+ int dmg;
+ int radius_dmg;
+ float dmg_radius;
+ int sounds; /* now also used for player death sound aggregation */
+ int count;
+
+ edict_t *chain;
+ edict_t *enemy;
+ edict_t *oldenemy;
+ edict_t *activator;
+ edict_t *groundentity;
+ int groundentity_linkcount;
+ edict_t *teamchain;
+ edict_t *teammaster;
+
+ edict_t *mynoise; /* can go in client only */
+ edict_t *mynoise2;
+
+ int noise_index;
+ int noise_index2;
+ float volume;
+ float attenuation;
+
+ /* timing variables */
+ float wait;
+ float delay; /* before firing targets */
+ float random;
+
+ float last_sound_time;
+
+ int watertype;
+ int waterlevel;
+
+ vec3_t move_origin;
+ vec3_t move_angles;
+
+ int light_level;
+ int style; /* also used as areaportal number */
+
+ gitem_t *item; /* for bonus items */
+
+ /* common data blocks */
+ moveinfo_t moveinfo;
+ monsterinfo_t monsterinfo;
+
+ int plat2flags;
+ vec3_t offset;
+ vec3_t gravityVector;
+ edict_t *bad_area;
+ edict_t *hint_chain;
+ edict_t *monster_hint_chain;
+ edict_t *target_hint_chain;
+ int hint_chain_id;
+ float lastMoveTime;
+};
+
+#define SPHERE_DEFENDER 0x0001
+#define SPHERE_HUNTER 0x0002
+#define SPHERE_VENGEANCE 0x0004
+#define SPHERE_DOPPLEGANGER 0x0100
+
+#define SPHERE_TYPE 0x00FF
+#define SPHERE_FLAGS 0xFF00
+
+/* deathmatch games */
+#define RDM_TAG 2
+#define RDM_DEATHBALL 3
+
+typedef struct dm_game_rs
+{
+ void (*GameInit)(void);
+ void (*PostInitSetup)(void);
+ void (*ClientBegin)(edict_t *ent);
+ void (*SelectSpawnPoint)(edict_t *ent, vec3_t origin, vec3_t angles);
+ void (*PlayerDeath)(edict_t *targ, edict_t *inflictor, edict_t *attacker);
+ void (*Score)(edict_t *attacker, edict_t *victim, int scoreChange);
+ void (*PlayerEffects)(edict_t *ent);
+ void (*DogTag)(edict_t *ent, edict_t *killer, char **pic);
+ void (*PlayerDisconnect)(edict_t *ent);
+ int (*ChangeDamage)(edict_t *targ, edict_t *attacker, int damage, int mod);
+ int (*ChangeKnockback)(edict_t *targ, edict_t *attacker, int knockback, int mod);
+ int (*CheckDMRules)(void);
+} dm_game_rt;
+
+extern dm_game_rt DMGame;
+
+void Tag_GameInit(void);
+void Tag_PostInitSetup(void);
+void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker);
+void Tag_Score(edict_t *attacker, edict_t *victim, int scoreChange);
+void Tag_PlayerEffects(edict_t *ent);
+void Tag_DogTag(edict_t *ent, edict_t *killer, char **pic);
+void Tag_PlayerDisconnect(edict_t *ent);
+int Tag_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, int mod);
+
+void DBall_GameInit(void);
+void DBall_ClientBegin(edict_t *ent);
+void DBall_SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles);
+int DBall_ChangeKnockback(edict_t *targ, edict_t *attacker, int knockback, int mod);
+int DBall_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, int mod);
+void DBall_PostInitSetup(void);
+int DBall_CheckDMRules(void);
+
+#endif /* ROGUE_LOCAL_H */
diff --git a/rogue/src/header/shared.h b/rogue/src/header/shared.h
new file mode 100644
index 0000000..eeeb329
--- /dev/null
+++ b/rogue/src/header/shared.h
@@ -0,0 +1,1081 @@
+/*
+ * =======================================================================
+ *
+ * This is the main header file shared between client, renderer, server
+ * and the game.
+ *
+ * =======================================================================
+ */
+
+#ifndef ROGUE_SHARED_H
+#define ROGUE_SHARED_H
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+typedef unsigned char byte;
+typedef enum {false, true} qboolean;
+
+#ifndef NULL
+ #define NULL ((void *)0)
+#endif
+
+/* angle indexes */
+#define PITCH 0 /* up / down */
+#define YAW 1 /* left / right */
+#define ROLL 2 /* fall over */
+
+#define MAX_STRING_CHARS 1024 /* max length of a string passed to Cmd_TokenizeString */
+#define MAX_STRING_TOKENS 80 /* max tokens resulting from Cmd_TokenizeString */
+#define MAX_TOKEN_CHARS 128 /* max length of an individual token */
+
+#define MAX_QPATH 64 /* max length of a quake game pathname */
+
+#ifdef _WIN32
+#define MAX_OSPATH 256 /* max length of a filesystem pathname (same as MAX_PATH) */
+#else
+#define MAX_OSPATH 128 /* max length of a filesystem pathname */
+#endif
+
+/* */
+/* per-level limits */
+/* */
+#define MAX_CLIENTS 256 /* absolute limit */
+#define MAX_EDICTS 1024 /* must change protocol to increase more */
+#define MAX_LIGHTSTYLES 256
+#define MAX_MODELS 256 /* these are sent over the net as bytes */
+#define MAX_SOUNDS 256 /* so they cannot be blindly increased */
+#define MAX_IMAGES 256
+#define MAX_ITEMS 256
+#define MAX_GENERAL (MAX_CLIENTS * 2) /* general config strings */
+
+/* game print flags */
+#define PRINT_LOW 0 /* pickup messages */
+#define PRINT_MEDIUM 1 /* death messages */
+#define PRINT_HIGH 2 /* critical messages */
+#define PRINT_CHAT 3 /* chat messages */
+
+#define ERR_FATAL 0 /* exit the entire game with a popup window */
+#define ERR_DROP 1 /* print to console and disconnect from game */
+#define ERR_DISCONNECT 2 /* don't kill server */
+
+#define PRINT_ALL 0
+#define PRINT_DEVELOPER 1 /* only print when "developer 1" */
+#define PRINT_ALERT 2
+
+/* destination class for gi.multicast() */
+typedef enum
+{
+ MULTICAST_ALL,
+ MULTICAST_PHS,
+ MULTICAST_PVS,
+ MULTICAST_ALL_R,
+ MULTICAST_PHS_R,
+ MULTICAST_PVS_R
+} multicast_t;
+
+/*
+ * ==============================================================
+ *
+ * MATHLIB
+ *
+ * ==============================================================
+ */
+
+typedef float vec_t;
+typedef vec_t vec3_t[3];
+typedef vec_t vec5_t[5];
+
+typedef int fixed4_t;
+typedef int fixed8_t;
+typedef int fixed16_t;
+
+#ifndef M_PI
+ #define M_PI 3.14159265358979323846 /* matches value in gcc v2 math.h */
+#endif
+
+struct cplane_s;
+
+extern vec3_t vec3_origin;
+
+#define nanmask (255 << 23)
+
+#define IS_NAN(x) (((*(int *)&x) & nanmask) == nanmask)
+
+#define Q_ftol(f) (long)(f)
+
+#define DotProduct(x, y) (x[0] * y[0] + x[1] * y[1] + x[2] * y[2])
+#define VectorSubtract(a, b, c) (c[0] = a[0] - b[0], c[1] = a[1] - b[1], c[2] = \
+ a[2] - b[2])
+#define VectorAdd(a, b, c) (c[0] = a[0] + b[0], c[1] = a[1] + b[1], c[2] = \
+ a[2] + b[2])
+#define VectorCopy(a, b) (b[0] = a[0], b[1] = a[1], b[2] = a[2])
+#define VectorClear(a) (a[0] = a[1] = a[2] = 0)
+#define VectorNegate(a, b) (b[0] = -a[0], b[1] = -a[1], b[2] = -a[2])
+#define VectorSet(v, x, y, z) (v[0] = (x), v[1] = (y), v[2] = (z))
+
+void VectorMA(vec3_t veca, float scale, vec3_t vecb, vec3_t vecc);
+
+/* just in case you do't want to use the macros */
+vec_t _DotProduct(vec3_t v1, vec3_t v2);
+void _VectorSubtract(vec3_t veca, vec3_t vecb, vec3_t out);
+void _VectorAdd(vec3_t veca, vec3_t vecb, vec3_t out);
+void _VectorCopy(vec3_t in, vec3_t out);
+
+void ClearBounds(vec3_t mins, vec3_t maxs);
+void AddPointToBounds(vec3_t v, vec3_t mins, vec3_t maxs);
+int VectorCompare(vec3_t v1, vec3_t v2);
+vec_t VectorLength(vec3_t v);
+void CrossProduct(vec3_t v1, vec3_t v2, vec3_t cross);
+vec_t VectorNormalize(vec3_t v); /* returns vector length */
+vec_t VectorNormalize2(vec3_t v, vec3_t out);
+void VectorInverse(vec3_t v);
+void VectorScale(vec3_t in, vec_t scale, vec3_t out);
+int Q_log2(int val);
+
+void R_ConcatRotations(float in1[3][3], float in2[3][3], float out[3][3]);
+void R_ConcatTransforms(float in1[3][4], float in2[3][4], float out[3][4]);
+
+void AngleVectors(vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
+void AngleVectors2(vec3_t value1, vec3_t angles);
+int BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *plane);
+float anglemod(float a);
+float LerpAngle(float a1, float a2, float frac);
+
+#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \
+ (((p)->type < 3) ? \
+ ( \
+ ((p)->dist <= (emins)[(p)->type]) ? \
+ 1 \
+ : \
+ ( \
+ ((p)->dist >= (emaxs)[(p)->type]) ? \
+ 2 \
+ : \
+ 3 \
+ ) \
+ ) \
+ : \
+ BoxOnPlaneSide((emins), (emaxs), (p)))
+
+void ProjectPointOnPlane(vec3_t dst, const vec3_t p, const vec3_t normal);
+void PerpendicularVector(vec3_t dst, const vec3_t src);
+void RotatePointAroundVector(vec3_t dst, const vec3_t dir,
+ const vec3_t point, float degrees);
+
+/* ============================================= */
+
+char *COM_SkipPath(char *pathname);
+void COM_StripExtension(char *in, char *out);
+void COM_FileBase(char *in, char *out);
+void COM_FilePath(const char *in, char *out);
+void COM_DefaultExtension(char *path, const char *extension);
+
+char *COM_Parse(char **data_p);
+
+/* data is an in/out parm, returns a parsed out token */
+void Com_sprintf(char *dest, int size, char *fmt, ...);
+
+void Com_PageInMemory(byte *buffer, int size);
+
+char *strlwr ( char *s );
+int Q_strlcpy(char *dst, const char *src, int size);
+int Q_strlcat(char *dst, const char *src, int size);
+
+/* ============================================= */
+
+/* portable case insensitive compare */
+int Q_stricmp(const char *s1, const char *s2);
+int Q_strcasecmp(char *s1, char *s2);
+int Q_strncasecmp(char *s1, char *s2, int n);
+
+/* ============================================= */
+
+short BigShort(short l);
+short LittleShort(short l);
+int BigLong(int l);
+int LittleLong(int l);
+float BigFloat(float l);
+float LittleFloat(float l);
+
+void Swap_Init(void);
+char *va(const char *format, ...);
+
+/* ============================================= */
+
+/* key / value info strings */
+#define MAX_INFO_KEY 64
+#define MAX_INFO_VALUE 64
+#define MAX_INFO_STRING 512
+
+char *Info_ValueForKey(char *s, char *key);
+void Info_RemoveKey(char *s, char *key);
+void Info_SetValueForKey(char *s, char *key, char *value);
+qboolean Info_Validate(char *s);
+
+/* ============================================= */
+
+/* Random number generator */
+int randk(void);
+float frandk(void);
+float crandk(void);
+void randk_seed(void);
+
+/*
+ * ==============================================================
+ *
+ * SYSTEM SPECIFIC
+ *
+ * ==============================================================
+ */
+
+extern int curtime; /* time returned by last Sys_Milliseconds */
+
+int Sys_Milliseconds(void);
+void Sys_Mkdir(char *path);
+char *strlwr(char *s);
+
+/* large block stack allocation routines */
+void *Hunk_Begin(int maxsize);
+void *Hunk_Alloc(int size);
+void Hunk_Free(void *buf);
+int Hunk_End(void);
+
+/* directory searching */
+#define SFF_ARCH 0x01
+#define SFF_HIDDEN 0x02
+#define SFF_RDONLY 0x04
+#define SFF_SUBDIR 0x08
+#define SFF_SYSTEM 0x10
+
+/* pass in an attribute mask of things you wish to REJECT */
+char *Sys_FindFirst(char *path, unsigned musthave, unsigned canthave);
+char *Sys_FindNext(unsigned musthave, unsigned canthave);
+void Sys_FindClose(void);
+
+/* this is only here so the functions in q_shared.c and q_shwin.c can link */
+void Sys_Error(char *error, ...);
+void Com_Printf(char *msg, ...);
+
+/*
+ * ==========================================================
+ *
+ * CVARS (console variables)
+ *
+ * ==========================================================
+ */
+
+#ifndef CVAR
+ #define CVAR
+
+ #define CVAR_ARCHIVE 1 /* set to cause it to be saved to vars.rc */
+ #define CVAR_USERINFO 2 /* added to userinfo when changed */
+ #define CVAR_SERVERINFO 4 /* added to serverinfo when changed */
+ #define CVAR_NOSET 8 /* don't allow change from console at all, */
+ /* but can be set from the command line */
+ #define CVAR_LATCH 16 /* save changes until server restart */
+
+/* nothing outside the Cvar_*() functions should modify these fields! */
+typedef struct cvar_s
+{
+ char *name;
+ char *string;
+ char *latched_string; /* for CVAR_LATCH vars */
+ int flags;
+ qboolean modified; /* set each time the cvar is changed */
+ float value;
+ struct cvar_s *next;
+} cvar_t;
+
+#endif /* CVAR */
+
+/*
+ * ==============================================================
+ *
+ * COLLISION DETECTION
+ *
+ * ==============================================================
+ */
+
+/* lower bits are stronger, and will eat weaker brushes completely */
+#define CONTENTS_SOLID 1 /* an eye is never valid in a solid */
+#define CONTENTS_WINDOW 2 /* translucent, but not watery */
+#define CONTENTS_AUX 4
+#define CONTENTS_LAVA 8
+#define CONTENTS_SLIME 16
+#define CONTENTS_WATER 32
+#define CONTENTS_MIST 64
+#define LAST_VISIBLE_CONTENTS 64
+
+/* remaining contents are non-visible, and don't eat brushes */
+#define CONTENTS_AREAPORTAL 0x8000
+
+#define CONTENTS_PLAYERCLIP 0x10000
+#define CONTENTS_MONSTERCLIP 0x20000
+
+/* currents can be added to any other contents, and may be mixed */
+#define CONTENTS_CURRENT_0 0x40000
+#define CONTENTS_CURRENT_90 0x80000
+#define CONTENTS_CURRENT_180 0x100000
+#define CONTENTS_CURRENT_270 0x200000
+#define CONTENTS_CURRENT_UP 0x400000
+#define CONTENTS_CURRENT_DOWN 0x800000
+
+#define CONTENTS_ORIGIN 0x1000000 /* removed before bsping an entity */
+
+#define CONTENTS_MONSTER 0x2000000 /* should never be on a brush, only in game */
+#define CONTENTS_DEADMONSTER 0x4000000
+#define CONTENTS_DETAIL 0x8000000 /* brushes to be added after vis leafs */
+#define CONTENTS_TRANSLUCENT 0x10000000 /* auto set if any surface has trans */
+#define CONTENTS_LADDER 0x20000000
+
+#define SURF_LIGHT 0x1 /* value will hold the light strength */
+
+#define SURF_SLICK 0x2 /* effects game physics */
+
+#define SURF_SKY 0x4 /* don't draw, but add to skybox */
+#define SURF_WARP 0x8 /* turbulent water warp */
+#define SURF_TRANS33 0x10
+#define SURF_TRANS66 0x20
+#define SURF_FLOWING 0x40 /* scroll towards angle */
+#define SURF_NODRAW 0x80 /* don't bother referencing the texture */
+
+/* content masks */
+#define MASK_ALL (-1)
+#define MASK_SOLID (CONTENTS_SOLID | CONTENTS_WINDOW)
+#define MASK_PLAYERSOLID (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | \
+ CONTENTS_WINDOW | CONTENTS_MONSTER)
+#define MASK_DEADSOLID (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW)
+#define MASK_MONSTERSOLID (CONTENTS_SOLID | CONTENTS_MONSTERCLIP | \
+ CONTENTS_WINDOW | CONTENTS_MONSTER)
+#define MASK_WATER (CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME)
+#define MASK_OPAQUE (CONTENTS_SOLID | CONTENTS_SLIME | CONTENTS_LAVA)
+#define MASK_SHOT (CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_WINDOW | \
+ CONTENTS_DEADMONSTER)
+#define MASK_CURRENT (CONTENTS_CURRENT_0 | CONTENTS_CURRENT_90 | \
+ CONTENTS_CURRENT_180 | CONTENTS_CURRENT_270 | \
+ CONTENTS_CURRENT_UP | \
+ CONTENTS_CURRENT_DOWN)
+
+/* gi.BoxEdicts() can return a list of either solid or trigger entities */
+#define AREA_SOLID 1
+#define AREA_TRIGGERS 2
+
+/* plane_t structure */
+typedef struct cplane_s
+{
+ vec3_t normal;
+ float dist;
+ byte type; /* for fast side tests */
+ byte signbits; /* signx + (signy<<1) + (signz<<1) */
+ byte pad[2];
+} cplane_t;
+
+/* structure offset for asm code */
+#define CPLANE_NORMAL_X 0
+#define CPLANE_NORMAL_Y 4
+#define CPLANE_NORMAL_Z 8
+#define CPLANE_DIST 12
+#define CPLANE_TYPE 16
+#define CPLANE_SIGNBITS 17
+#define CPLANE_PAD0 18
+#define CPLANE_PAD1 19
+
+typedef struct cmodel_s
+{
+ vec3_t mins, maxs;
+ vec3_t origin; /* for sounds or lights */
+ int headnode;
+} cmodel_t;
+
+typedef struct csurface_s
+{
+ char name[16];
+ int flags;
+ int value;
+} csurface_t;
+
+typedef struct mapsurface_s /* used internally due to name len probs */
+{
+ csurface_t c;
+ char rname[32];
+} mapsurface_t;
+
+/* a trace is returned when a box is swept through the world */
+typedef struct
+{
+ qboolean allsolid; /* if true, plane is not valid */
+ qboolean startsolid; /* if true, the initial point was in a solid area */
+ float fraction; /* time completed, 1.0 = didn't hit anything */
+ vec3_t endpos; /* final position */
+ cplane_t plane; /* surface normal at impact */
+ csurface_t *surface; /* surface hit */
+ int contents; /* contents on other side of surface hit */
+ struct edict_s *ent; /* not set by CM_*() functions */
+} trace_t;
+
+/* pmove_state_t is the information necessary for client side movement */
+/* prediction */
+typedef enum
+{
+ /* can accelerate and turn */
+ PM_NORMAL,
+ PM_SPECTATOR,
+ /* no acceleration or turning */
+ PM_DEAD,
+ PM_GIB, /* different bounding box */
+ PM_FREEZE
+} pmtype_t;
+
+/* pmove->pm_flags */
+#define PMF_DUCKED 1
+#define PMF_JUMP_HELD 2
+#define PMF_ON_GROUND 4
+#define PMF_TIME_WATERJUMP 8 /* pm_time is waterjump */
+#define PMF_TIME_LAND 16 /* pm_time is time before rejump */
+#define PMF_TIME_TELEPORT 32 /* pm_time is non-moving time */
+#define PMF_NO_PREDICTION 64 /* temporarily disables prediction (used for grappling hook) */
+
+/* this structure needs to be communicated bit-accurate/
+ from the server to the client to guarantee that
+ prediction stays in sync, so no floats are used.
+ if any part of the game code modifies this struct, it
+ will result in a prediction error of some degree. */
+typedef struct
+{
+ pmtype_t pm_type;
+
+ short origin[3]; /* 12.3 */
+ short velocity[3]; /* 12.3 */
+ byte pm_flags; /* ducked, jump_held, etc */
+ byte pm_time; /* each unit = 8 ms */
+ short gravity;
+ short delta_angles[3]; /* add to command angles to get view direction
+ changed by spawns, rotating objects, and teleporters */
+} pmove_state_t;
+
+/* button bits */
+#define BUTTON_ATTACK 1
+#define BUTTON_USE 2
+#define BUTTON_ANY 128 /* any key whatsoever */
+
+/* usercmd_t is sent to the server each client frame */
+typedef struct usercmd_s
+{
+ byte msec;
+ byte buttons;
+ short angles[3];
+ short forwardmove, sidemove, upmove;
+ byte impulse; /* remove? */
+ byte lightlevel; /* light level the player is standing on */
+} usercmd_t;
+
+#define MAXTOUCH 32
+typedef struct
+{
+ /* state (in / out) */
+ pmove_state_t s;
+
+ /* command (in) */
+ usercmd_t cmd;
+ qboolean snapinitial; /* if s has been changed outside pmove */
+
+ /* results (out) */
+ int numtouch;
+ struct edict_s *touchents[MAXTOUCH];
+
+ vec3_t viewangles; /* clamped */
+ float viewheight;
+
+ vec3_t mins, maxs; /* bounding box size */
+
+ struct edict_s *groundentity;
+ int watertype;
+ int waterlevel;
+
+ /* callbacks to test the world */
+ trace_t (*trace)(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end);
+ int (*pointcontents)(vec3_t point);
+} pmove_t;
+
+/* entity_state_t->effects
+ Effects are things handled on the client side (lights, particles,
+ frame animations) that happen constantly on the given entity.
+ An entity that has effects will be sent to the client even if
+ it has a zero index model. */
+#define EF_ROTATE 0x00000001 /* rotate (bonus items) */
+#define EF_GIB 0x00000002 /* leave a trail */
+#define EF_BLASTER 0x00000008 /* redlight + trail */
+#define EF_ROCKET 0x00000010 /* redlight + trail */
+#define EF_GRENADE 0x00000020
+#define EF_HYPERBLASTER 0x00000040
+#define EF_BFG 0x00000080
+#define EF_COLOR_SHELL 0x00000100
+#define EF_POWERSCREEN 0x00000200
+#define EF_ANIM01 0x00000400 /* automatically cycle between frames 0 and 1 at 2 hz */
+#define EF_ANIM23 0x00000800 /* automatically cycle between frames 2 and 3 at 2 hz */
+#define EF_ANIM_ALL 0x00001000 /* automatically cycle through all frames at 2hz */
+#define EF_ANIM_ALLFAST 0x00002000 /* automatically cycle through all frames at 10hz */
+#define EF_FLIES 0x00004000
+#define EF_QUAD 0x00008000
+#define EF_PENT 0x00010000
+#define EF_TELEPORTER 0x00020000 /* particle fountain */
+#define EF_FLAG1 0x00040000
+#define EF_FLAG2 0x00080000
+#define EF_IONRIPPER 0x00100000
+#define EF_GREENGIB 0x00200000
+#define EF_BLUEHYPERBLASTER 0x00400000
+#define EF_SPINNINGLIGHTS 0x00800000
+#define EF_PLASMA 0x01000000
+#define EF_TRAP 0x02000000
+#define EF_TRACKER 0x04000000
+#define EF_DOUBLE 0x08000000
+#define EF_SPHERETRANS 0x10000000
+#define EF_TAGTRAIL 0x20000000
+#define EF_HALF_DAMAGE 0x40000000
+#define EF_TRACKERTRAIL 0x80000000
+
+/* entity_state_t->renderfx flags */
+#define RF_MINLIGHT 1 /* allways have some light (viewmodel) */
+#define RF_VIEWERMODEL 2 /* don't draw through eyes, only mirrors */
+#define RF_WEAPONMODEL 4 /* only draw through eyes */
+#define RF_FULLBRIGHT 8 /* allways draw full intensity */
+#define RF_DEPTHHACK 16 /* for view weapon Z crunching */
+#define RF_TRANSLUCENT 32
+#define RF_FRAMELERP 64
+#define RF_BEAM 128
+#define RF_CUSTOMSKIN 256 /* skin is an index in image_precache */
+#define RF_GLOW 512 /* pulse lighting for bonus items */
+#define RF_SHELL_RED 1024
+#define RF_SHELL_GREEN 2048
+#define RF_SHELL_BLUE 4096
+#define RF_NOSHADOW 8192 /* don't draw a shadow */
+#define RF_IR_VISIBLE 0x00008000 /* 32768 */
+#define RF_SHELL_DOUBLE 0x00010000 /* 65536 */
+#define RF_SHELL_HALF_DAM 0x00020000
+#define RF_USE_DISGUISE 0x00040000
+
+/* player_state_t->refdef flags */
+#define RDF_UNDERWATER 1 /* warp the screen as apropriate */
+#define RDF_NOWORLDMODEL 2 /* used for player configuration screen */
+#define RDF_IRGOGGLES 4
+#define RDF_UVGOGGLES 8
+
+/* muzzle flashes / player effects */
+#define MZ_BLASTER 0
+#define MZ_MACHINEGUN 1
+#define MZ_SHOTGUN 2
+#define MZ_CHAINGUN1 3
+#define MZ_CHAINGUN2 4
+#define MZ_CHAINGUN3 5
+#define MZ_RAILGUN 6
+#define MZ_ROCKET 7
+#define MZ_GRENADE 8
+#define MZ_LOGIN 9
+#define MZ_LOGOUT 10
+#define MZ_RESPAWN 11
+#define MZ_BFG 12
+#define MZ_SSHOTGUN 13
+#define MZ_HYPERBLASTER 14
+#define MZ_ITEMRESPAWN 15
+#define MZ_IONRIPPER 16
+#define MZ_BLUEHYPERBLASTER 17
+#define MZ_PHALANX 18
+#define MZ_SILENCED 128 /* bit flag ORed with one of the above numbers */
+#define MZ_ETF_RIFLE 30
+#define MZ_UNUSED 31
+#define MZ_SHOTGUN2 32
+#define MZ_HEATBEAM 33
+#define MZ_BLASTER2 34
+#define MZ_TRACKER 35
+#define MZ_NUKE1 36
+#define MZ_NUKE2 37
+#define MZ_NUKE4 38
+#define MZ_NUKE8 39
+
+/* monster muzzle flashes */
+#define MZ2_TANK_BLASTER_1 1
+#define MZ2_TANK_BLASTER_2 2
+#define MZ2_TANK_BLASTER_3 3
+#define MZ2_TANK_MACHINEGUN_1 4
+#define MZ2_TANK_MACHINEGUN_2 5
+#define MZ2_TANK_MACHINEGUN_3 6
+#define MZ2_TANK_MACHINEGUN_4 7
+#define MZ2_TANK_MACHINEGUN_5 8
+#define MZ2_TANK_MACHINEGUN_6 9
+#define MZ2_TANK_MACHINEGUN_7 10
+#define MZ2_TANK_MACHINEGUN_8 11
+#define MZ2_TANK_MACHINEGUN_9 12
+#define MZ2_TANK_MACHINEGUN_10 13
+#define MZ2_TANK_MACHINEGUN_11 14
+#define MZ2_TANK_MACHINEGUN_12 15
+#define MZ2_TANK_MACHINEGUN_13 16
+#define MZ2_TANK_MACHINEGUN_14 17
+#define MZ2_TANK_MACHINEGUN_15 18
+#define MZ2_TANK_MACHINEGUN_16 19
+#define MZ2_TANK_MACHINEGUN_17 20
+#define MZ2_TANK_MACHINEGUN_18 21
+#define MZ2_TANK_MACHINEGUN_19 22
+#define MZ2_TANK_ROCKET_1 23
+#define MZ2_TANK_ROCKET_2 24
+#define MZ2_TANK_ROCKET_3 25
+
+#define MZ2_INFANTRY_MACHINEGUN_1 26
+#define MZ2_INFANTRY_MACHINEGUN_2 27
+#define MZ2_INFANTRY_MACHINEGUN_3 28
+#define MZ2_INFANTRY_MACHINEGUN_4 29
+#define MZ2_INFANTRY_MACHINEGUN_5 30
+#define MZ2_INFANTRY_MACHINEGUN_6 31
+#define MZ2_INFANTRY_MACHINEGUN_7 32
+#define MZ2_INFANTRY_MACHINEGUN_8 33
+#define MZ2_INFANTRY_MACHINEGUN_9 34
+#define MZ2_INFANTRY_MACHINEGUN_10 35
+#define MZ2_INFANTRY_MACHINEGUN_11 36
+#define MZ2_INFANTRY_MACHINEGUN_12 37
+#define MZ2_INFANTRY_MACHINEGUN_13 38
+
+#define MZ2_SOLDIER_BLASTER_1 39
+#define MZ2_SOLDIER_BLASTER_2 40
+#define MZ2_SOLDIER_SHOTGUN_1 41
+#define MZ2_SOLDIER_SHOTGUN_2 42
+#define MZ2_SOLDIER_MACHINEGUN_1 43
+#define MZ2_SOLDIER_MACHINEGUN_2 44
+
+#define MZ2_GUNNER_MACHINEGUN_1 45
+#define MZ2_GUNNER_MACHINEGUN_2 46
+#define MZ2_GUNNER_MACHINEGUN_3 47
+#define MZ2_GUNNER_MACHINEGUN_4 48
+#define MZ2_GUNNER_MACHINEGUN_5 49
+#define MZ2_GUNNER_MACHINEGUN_6 50
+#define MZ2_GUNNER_MACHINEGUN_7 51
+#define MZ2_GUNNER_MACHINEGUN_8 52
+#define MZ2_GUNNER_GRENADE_1 53
+#define MZ2_GUNNER_GRENADE_2 54
+#define MZ2_GUNNER_GRENADE_3 55
+#define MZ2_GUNNER_GRENADE_4 56
+
+#define MZ2_CHICK_ROCKET_1 57
+
+#define MZ2_FLYER_BLASTER_1 58
+#define MZ2_FLYER_BLASTER_2 59
+
+#define MZ2_MEDIC_BLASTER_1 60
+
+#define MZ2_GLADIATOR_RAILGUN_1 61
+
+#define MZ2_HOVER_BLASTER_1 62
+
+#define MZ2_ACTOR_MACHINEGUN_1 63
+
+#define MZ2_SUPERTANK_MACHINEGUN_1 64
+#define MZ2_SUPERTANK_MACHINEGUN_2 65
+#define MZ2_SUPERTANK_MACHINEGUN_3 66
+#define MZ2_SUPERTANK_MACHINEGUN_4 67
+#define MZ2_SUPERTANK_MACHINEGUN_5 68
+#define MZ2_SUPERTANK_MACHINEGUN_6 69
+#define MZ2_SUPERTANK_ROCKET_1 70
+#define MZ2_SUPERTANK_ROCKET_2 71
+#define MZ2_SUPERTANK_ROCKET_3 72
+
+#define MZ2_BOSS2_MACHINEGUN_L1 73
+#define MZ2_BOSS2_MACHINEGUN_L2 74
+#define MZ2_BOSS2_MACHINEGUN_L3 75
+#define MZ2_BOSS2_MACHINEGUN_L4 76
+#define MZ2_BOSS2_MACHINEGUN_L5 77
+#define MZ2_BOSS2_ROCKET_1 78
+#define MZ2_BOSS2_ROCKET_2 79
+#define MZ2_BOSS2_ROCKET_3 80
+#define MZ2_BOSS2_ROCKET_4 81
+
+#define MZ2_FLOAT_BLASTER_1 82
+
+#define MZ2_SOLDIER_BLASTER_3 83
+#define MZ2_SOLDIER_SHOTGUN_3 84
+#define MZ2_SOLDIER_MACHINEGUN_3 85
+#define MZ2_SOLDIER_BLASTER_4 86
+#define MZ2_SOLDIER_SHOTGUN_4 87
+#define MZ2_SOLDIER_MACHINEGUN_4 88
+#define MZ2_SOLDIER_BLASTER_5 89
+#define MZ2_SOLDIER_SHOTGUN_5 90
+#define MZ2_SOLDIER_MACHINEGUN_5 91
+#define MZ2_SOLDIER_BLASTER_6 92
+#define MZ2_SOLDIER_SHOTGUN_6 93
+#define MZ2_SOLDIER_MACHINEGUN_6 94
+#define MZ2_SOLDIER_BLASTER_7 95
+#define MZ2_SOLDIER_SHOTGUN_7 96
+#define MZ2_SOLDIER_MACHINEGUN_7 97
+#define MZ2_SOLDIER_BLASTER_8 98
+#define MZ2_SOLDIER_SHOTGUN_8 99
+#define MZ2_SOLDIER_MACHINEGUN_8 100
+
+#define MZ2_MAKRON_BFG 101
+#define MZ2_MAKRON_BLASTER_1 102
+#define MZ2_MAKRON_BLASTER_2 103
+#define MZ2_MAKRON_BLASTER_3 104
+#define MZ2_MAKRON_BLASTER_4 105
+#define MZ2_MAKRON_BLASTER_5 106
+#define MZ2_MAKRON_BLASTER_6 107
+#define MZ2_MAKRON_BLASTER_7 108
+#define MZ2_MAKRON_BLASTER_8 109
+#define MZ2_MAKRON_BLASTER_9 110
+#define MZ2_MAKRON_BLASTER_10 111
+#define MZ2_MAKRON_BLASTER_11 112
+#define MZ2_MAKRON_BLASTER_12 113
+#define MZ2_MAKRON_BLASTER_13 114
+#define MZ2_MAKRON_BLASTER_14 115
+#define MZ2_MAKRON_BLASTER_15 116
+#define MZ2_MAKRON_BLASTER_16 117
+#define MZ2_MAKRON_BLASTER_17 118
+#define MZ2_MAKRON_RAILGUN_1 119
+#define MZ2_JORG_MACHINEGUN_L1 120
+#define MZ2_JORG_MACHINEGUN_L2 121
+#define MZ2_JORG_MACHINEGUN_L3 122
+#define MZ2_JORG_MACHINEGUN_L4 123
+#define MZ2_JORG_MACHINEGUN_L5 124
+#define MZ2_JORG_MACHINEGUN_L6 125
+#define MZ2_JORG_MACHINEGUN_R1 126
+#define MZ2_JORG_MACHINEGUN_R2 127
+#define MZ2_JORG_MACHINEGUN_R3 128
+#define MZ2_JORG_MACHINEGUN_R4 129
+#define MZ2_JORG_MACHINEGUN_R5 130
+#define MZ2_JORG_MACHINEGUN_R6 131
+#define MZ2_JORG_BFG_1 132
+#define MZ2_BOSS2_MACHINEGUN_R1 133
+#define MZ2_BOSS2_MACHINEGUN_R2 134
+#define MZ2_BOSS2_MACHINEGUN_R3 135
+#define MZ2_BOSS2_MACHINEGUN_R4 136
+#define MZ2_BOSS2_MACHINEGUN_R5 137
+
+#define MZ2_CARRIER_MACHINEGUN_L1 138
+#define MZ2_CARRIER_MACHINEGUN_R1 139
+#define MZ2_CARRIER_GRENADE 140
+#define MZ2_TURRET_MACHINEGUN 141
+#define MZ2_TURRET_ROCKET 142
+#define MZ2_TURRET_BLASTER 143
+#define MZ2_STALKER_BLASTER 144
+#define MZ2_DAEDALUS_BLASTER 145
+#define MZ2_MEDIC_BLASTER_2 146
+#define MZ2_CARRIER_RAILGUN 147
+#define MZ2_WIDOW_DISRUPTOR 148
+#define MZ2_WIDOW_BLASTER 149
+#define MZ2_WIDOW_RAIL 150
+#define MZ2_WIDOW_PLASMABEAM 151
+#define MZ2_CARRIER_MACHINEGUN_L2 152
+#define MZ2_CARRIER_MACHINEGUN_R2 153
+#define MZ2_WIDOW_RAIL_LEFT 154
+#define MZ2_WIDOW_RAIL_RIGHT 155
+#define MZ2_WIDOW_BLASTER_SWEEP1 156
+#define MZ2_WIDOW_BLASTER_SWEEP2 157
+#define MZ2_WIDOW_BLASTER_SWEEP3 158
+#define MZ2_WIDOW_BLASTER_SWEEP4 159
+#define MZ2_WIDOW_BLASTER_SWEEP5 160
+#define MZ2_WIDOW_BLASTER_SWEEP6 161
+#define MZ2_WIDOW_BLASTER_SWEEP7 162
+#define MZ2_WIDOW_BLASTER_SWEEP8 163
+#define MZ2_WIDOW_BLASTER_SWEEP9 164
+#define MZ2_WIDOW_BLASTER_100 165
+#define MZ2_WIDOW_BLASTER_90 166
+#define MZ2_WIDOW_BLASTER_80 167
+#define MZ2_WIDOW_BLASTER_70 168
+#define MZ2_WIDOW_BLASTER_60 169
+#define MZ2_WIDOW_BLASTER_50 170
+#define MZ2_WIDOW_BLASTER_40 171
+#define MZ2_WIDOW_BLASTER_30 172
+#define MZ2_WIDOW_BLASTER_20 173
+#define MZ2_WIDOW_BLASTER_10 174
+#define MZ2_WIDOW_BLASTER_0 175
+#define MZ2_WIDOW_BLASTER_10L 176
+#define MZ2_WIDOW_BLASTER_20L 177
+#define MZ2_WIDOW_BLASTER_30L 178
+#define MZ2_WIDOW_BLASTER_40L 179
+#define MZ2_WIDOW_BLASTER_50L 180
+#define MZ2_WIDOW_BLASTER_60L 181
+#define MZ2_WIDOW_BLASTER_70L 182
+#define MZ2_WIDOW_RUN_1 183
+#define MZ2_WIDOW_RUN_2 184
+#define MZ2_WIDOW_RUN_3 185
+#define MZ2_WIDOW_RUN_4 186
+#define MZ2_WIDOW_RUN_5 187
+#define MZ2_WIDOW_RUN_6 188
+#define MZ2_WIDOW_RUN_7 189
+#define MZ2_WIDOW_RUN_8 190
+#define MZ2_CARRIER_ROCKET_1 191
+#define MZ2_CARRIER_ROCKET_2 192
+#define MZ2_CARRIER_ROCKET_3 193
+#define MZ2_CARRIER_ROCKET_4 194
+#define MZ2_WIDOW2_BEAMER_1 195
+#define MZ2_WIDOW2_BEAMER_2 196
+#define MZ2_WIDOW2_BEAMER_3 197
+#define MZ2_WIDOW2_BEAMER_4 198
+#define MZ2_WIDOW2_BEAMER_5 199
+#define MZ2_WIDOW2_BEAM_SWEEP_1 200
+#define MZ2_WIDOW2_BEAM_SWEEP_2 201
+#define MZ2_WIDOW2_BEAM_SWEEP_3 202
+#define MZ2_WIDOW2_BEAM_SWEEP_4 203
+#define MZ2_WIDOW2_BEAM_SWEEP_5 204
+#define MZ2_WIDOW2_BEAM_SWEEP_6 205
+#define MZ2_WIDOW2_BEAM_SWEEP_7 206
+#define MZ2_WIDOW2_BEAM_SWEEP_8 207
+#define MZ2_WIDOW2_BEAM_SWEEP_9 208
+#define MZ2_WIDOW2_BEAM_SWEEP_10 209
+#define MZ2_WIDOW2_BEAM_SWEEP_11 210
+
+extern vec3_t monster_flash_offset[];
+
+/* Temp entity events are for things that happen
+ at a location seperate from any existing entity.
+ Temporary entity messages are explicitly constructed
+ and broadcast. */
+typedef enum
+{
+ TE_GUNSHOT,
+ TE_BLOOD,
+ TE_BLASTER,
+ TE_RAILTRAIL,
+ TE_SHOTGUN,
+ TE_EXPLOSION1,
+ TE_EXPLOSION2,
+ TE_ROCKET_EXPLOSION,
+ TE_GRENADE_EXPLOSION,
+ TE_SPARKS,
+ TE_SPLASH,
+ TE_BUBBLETRAIL,
+ TE_SCREEN_SPARKS,
+ TE_SHIELD_SPARKS,
+ TE_BULLET_SPARKS,
+ TE_LASER_SPARKS,
+ TE_PARASITE_ATTACK,
+ TE_ROCKET_EXPLOSION_WATER,
+ TE_GRENADE_EXPLOSION_WATER,
+ TE_MEDIC_CABLE_ATTACK,
+ TE_BFG_EXPLOSION,
+ TE_BFG_BIGEXPLOSION,
+ TE_BOSSTPORT, /* used as '22' in a map, so DON'T RENUMBER!!! */
+ TE_BFG_LASER,
+ TE_GRAPPLE_CABLE,
+ TE_WELDING_SPARKS,
+ TE_GREENBLOOD,
+ TE_BLUEHYPERBLASTER,
+ TE_PLASMA_EXPLOSION,
+ TE_TUNNEL_SPARKS,
+ TE_BLASTER2,
+ TE_RAILTRAIL2,
+ TE_FLAME,
+ TE_LIGHTNING,
+ TE_DEBUGTRAIL,
+ TE_PLAIN_EXPLOSION,
+ TE_FLASHLIGHT,
+ TE_FORCEWALL,
+ TE_HEATBEAM,
+ TE_MONSTER_HEATBEAM,
+ TE_STEAM,
+ TE_BUBBLETRAIL2,
+ TE_MOREBLOOD,
+ TE_HEATBEAM_SPARKS,
+ TE_HEATBEAM_STEAM,
+ TE_CHAINFIST_SMOKE,
+ TE_ELECTRIC_SPARKS,
+ TE_TRACKER_EXPLOSION,
+ TE_TELEPORT_EFFECT,
+ TE_DBALL_GOAL,
+ TE_WIDOWBEAMOUT,
+ TE_NUKEBLAST,
+ TE_WIDOWSPLASH,
+ TE_EXPLOSION1_BIG,
+ TE_EXPLOSION1_NP,
+ TE_FLECHETTE
+} temp_event_t;
+
+#define SPLASH_UNKNOWN 0
+#define SPLASH_SPARKS 1
+#define SPLASH_BLUE_WATER 2
+#define SPLASH_BROWN_WATER 3
+#define SPLASH_SLIME 4
+#define SPLASH_LAVA 5
+#define SPLASH_BLOOD 6
+
+/* sound channels:
+ channel 0 never willingly overrides
+ other channels (1-7) allways override
+ a playing sound on that channel */
+#define CHAN_AUTO 0
+#define CHAN_WEAPON 1
+#define CHAN_VOICE 2
+#define CHAN_ITEM 3
+#define CHAN_BODY 4
+/* modifier flags */
+#define CHAN_NO_PHS_ADD 8 /* send to all clients, not just ones in PHS (ATTN 0 will also do this) */
+#define CHAN_RELIABLE 16 /* send by reliable message, not datagram */
+
+/* sound attenuation values */
+#define ATTN_NONE 0 /* full volume the entire level */
+#define ATTN_NORM 1
+#define ATTN_IDLE 2
+#define ATTN_STATIC 3 /* diminish very rapidly with distance */
+
+/* player_state->stats[] indexes */
+#define STAT_HEALTH_ICON 0
+#define STAT_HEALTH 1
+#define STAT_AMMO_ICON 2
+#define STAT_AMMO 3
+#define STAT_ARMOR_ICON 4
+#define STAT_ARMOR 5
+#define STAT_SELECTED_ICON 6
+#define STAT_PICKUP_ICON 7
+#define STAT_PICKUP_STRING 8
+#define STAT_TIMER_ICON 9
+#define STAT_TIMER 10
+#define STAT_HELPICON 11
+#define STAT_SELECTED_ITEM 12
+#define STAT_LAYOUTS 13
+#define STAT_FRAGS 14
+#define STAT_FLASHES 15 /* cleared each frame, 1 = health, 2 = armor */
+#define STAT_CHASE 16
+#define STAT_SPECTATOR 17
+
+#define MAX_STATS 32
+
+/* dmflags->value flags */
+#define DF_NO_HEALTH 0x00000001 /* 1 */
+#define DF_NO_ITEMS 0x00000002 /* 2 */
+#define DF_WEAPONS_STAY 0x00000004 /* 4 */
+#define DF_NO_FALLING 0x00000008 /* 8 */
+#define DF_INSTANT_ITEMS 0x00000010 /* 16 */
+#define DF_SAME_LEVEL 0x00000020 /* 32 */
+#define DF_SKINTEAMS 0x00000040 /* 64 */
+#define DF_MODELTEAMS 0x00000080 /* 128 */
+#define DF_NO_FRIENDLY_FIRE 0x00000100 /* 256 */
+#define DF_SPAWN_FARTHEST 0x00000200 /* 512 */
+#define DF_FORCE_RESPAWN 0x00000400 /* 1024 */
+#define DF_NO_ARMOR 0x00000800 /* 2048 */
+#define DF_ALLOW_EXIT 0x00001000 /* 4096 */
+#define DF_INFINITE_AMMO 0x00002000 /* 8192 */
+#define DF_QUAD_DROP 0x00004000 /* 16384 */
+#define DF_FIXED_FOV 0x00008000 /* 32768 */
+#define DF_QUADFIRE_DROP 0x00010000 /* 65536 */
+#define DF_NO_MINES 0x00020000
+#define DF_NO_STACK_DOUBLE 0x00040000
+#define DF_NO_NUKES 0x00080000
+#define DF_NO_SPHERES 0x00100000
+
+#define ROGUE_VERSION_STRING "08/21/1998 Beta 2 for Ensemble"
+
+/*
+ * ==========================================================
+ *
+ * ELEMENTS COMMUNICATED ACROSS THE NET
+ *
+ * ==========================================================
+ */
+
+#define ANGLE2SHORT(x) ((int)((x) * 65536 / 360) & 65535)
+#define SHORT2ANGLE(x) ((x) * (360.0 / 65536))
+
+/* config strings are a general means of communication from
+ the server to all connected clients. Each config string
+ can be at most MAX_QPATH characters. */
+#define CS_NAME 0
+#define CS_CDTRACK 1
+#define CS_SKY 2
+#define CS_SKYAXIS 3 /* %f %f %f format */
+#define CS_SKYROTATE 4
+#define CS_STATUSBAR 5 /* display program string */
+
+#define CS_AIRACCEL 29 /* air acceleration control */
+#define CS_MAXCLIENTS 30
+#define CS_MAPCHECKSUM 31 /* for catching cheater maps */
+
+#define CS_MODELS 32
+#define CS_SOUNDS (CS_MODELS + MAX_MODELS)
+#define CS_IMAGES (CS_SOUNDS + MAX_SOUNDS)
+#define CS_LIGHTS (CS_IMAGES + MAX_IMAGES)
+#define CS_ITEMS (CS_LIGHTS + MAX_LIGHTSTYLES)
+#define CS_PLAYERSKINS (CS_ITEMS + MAX_ITEMS)
+#define CS_GENERAL (CS_PLAYERSKINS + MAX_CLIENTS)
+#define MAX_CONFIGSTRINGS (CS_GENERAL + MAX_GENERAL)
+
+/* ============================================== */
+
+/* entity_state_t->event values
+ entity events are for effects that take place reletive
+ to an existing entities origin. Very network efficient.
+ All muzzle flashes really should be converted to events... */
+typedef enum
+{
+ EV_NONE,
+ EV_ITEM_RESPAWN,
+ EV_FOOTSTEP,
+ EV_FALLSHORT,
+ EV_FALL,
+ EV_FALLFAR,
+ EV_PLAYER_TELEPORT,
+ EV_OTHER_TELEPORT
+} entity_event_t;
+
+/* entity_state_t is the information conveyed from the server
+ in an update message about entities that the client will
+ need to render in some way */
+typedef struct entity_state_s
+{
+ int number; /* edict index */
+
+ vec3_t origin;
+ vec3_t angles;
+ vec3_t old_origin; /* for lerping */
+ int modelindex;
+ int modelindex2, modelindex3, modelindex4; /* weapons, CTF flags, etc */
+ int frame;
+ int skinnum;
+ unsigned int effects;
+ int renderfx;
+ int solid; /* for client side prediction, 8*(bits 0-4) is x/y radius */
+ /* 8*(bits 5-9) is z down distance, 8(bits10-15) is z up */
+ /* gi.linkentity sets this properly */
+ int sound; /* for looping sounds, to guarantee shutoff */
+ int event; /* impulse events -- muzzle flashes, footsteps, etc */
+ /* events only go out for a single frame, they */
+ /* are automatically cleared each frame */
+} entity_state_t;
+
+/* ============================================== */
+
+/* player_state_t is the information needed in addition to pmove_state_t
+ to rendered a view. There will only be 10 player_state_t sent each second,
+ but the number of pmove_state_t changes will be reletive to client
+ frame rates */
+typedef struct
+{
+ pmove_state_t pmove; /* for prediction */
+
+ vec3_t viewangles; /* for fixed views */
+ vec3_t viewoffset; /* add to pmovestate->origin */
+ vec3_t kick_angles; /* add to view direction to get render angles */
+ /* set by weapon kicks, pain effects, etc */
+
+ vec3_t gunangles;
+ vec3_t gunoffset;
+ int gunindex;
+ int gunframe;
+
+ float blend[4]; /* rgba full screen effect */
+ float fov; /* horizontal field of view */
+ int rdflags; /* refdef flags */
+
+ short stats[MAX_STATS]; /* fast status bar updates */
+} player_state_t;
+
+#define VIDREF_GL 1
+#define VIDREF_SOFT 2
+#define VIDREF_OTHER 3
+
+extern int vidref_val;
+
+size_t verify_fread(void *, size_t, size_t, FILE *);
+size_t verify_fwrite(void *, size_t, size_t, FILE *);
+
+#endif /* ROGUE_SHARED_H */
diff --git a/rogue/src/monster/berserker/berserker.c b/rogue/src/monster/berserker/berserker.c
new file mode 100644
index 0000000..b58de7c
--- /dev/null
+++ b/rogue/src/monster/berserker/berserker.c
@@ -0,0 +1,715 @@
+/* =======================================================================
+ *
+ * The berserker.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "berserker.h"
+
+static int sound_pain;
+static int sound_die;
+static int sound_idle;
+static int sound_punch;
+static int sound_sight;
+static int sound_search;
+
+void berserk_fidget(edict_t *self);
+
+void
+berserk_sight(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+berserk_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+mframe_t berserk_frames_stand[] = {
+ {ai_stand, 0, berserk_fidget},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t berserk_move_stand = {
+ FRAME_stand1,
+ FRAME_stand5,
+ berserk_frames_stand,
+ NULL
+};
+
+void
+berserk_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &berserk_move_stand;
+}
+
+mframe_t berserk_frames_stand_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t berserk_move_stand_fidget = {
+ FRAME_standb1,
+ FRAME_standb20,
+ berserk_frames_stand_fidget,
+ berserk_stand
+};
+
+void
+berserk_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ return;
+ }
+
+ if (random() > 0.15)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &berserk_move_stand_fidget;
+ gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t berserk_frames_walk[] = {
+ {ai_walk, 9.1, NULL},
+ {ai_walk, 6.3, NULL},
+ {ai_walk, 4.9, NULL},
+ {ai_walk, 6.7, NULL},
+ {ai_walk, 6.0, NULL},
+ {ai_walk, 8.2, NULL},
+ {ai_walk, 7.2, NULL},
+ {ai_walk, 6.1, NULL},
+ {ai_walk, 4.9, NULL},
+ {ai_walk, 4.7, NULL},
+ {ai_walk, 4.7, NULL},
+ {ai_walk, 4.8, NULL}
+};
+
+mmove_t berserk_move_walk = {
+ FRAME_walkc1,
+ FRAME_walkc11,
+ berserk_frames_walk,
+ NULL
+};
+
+void
+berserk_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &berserk_move_walk;
+}
+
+mframe_t berserk_frames_run1[] = {
+ {ai_run, 21, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 21, NULL},
+ {ai_run, 25, monster_done_dodge},
+ {ai_run, 18, NULL},
+ {ai_run, 19, NULL}
+};
+
+mmove_t berserk_move_run1 = {
+ FRAME_run1,
+ FRAME_run6,
+ berserk_frames_run1,
+ NULL
+};
+
+void
+berserk_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &berserk_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_run1;
+ }
+}
+
+void
+berserk_attack_spike(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ static vec3_t aim = {MELEE_DISTANCE, 0, -24};
+ fire_hit(self, aim, (15 + (rand() % 6)), 400);
+}
+
+void
+berserk_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0);
+}
+
+mframe_t berserk_frames_attack_spike[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, berserk_swing},
+ {ai_charge, 0, berserk_attack_spike},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t berserk_move_attack_spike = {
+ FRAME_att_c1,
+ FRAME_att_c8,
+ berserk_frames_attack_spike,
+ berserk_run
+};
+
+void
+berserk_attack_club(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4);
+ fire_hit(self, aim, (5 + (rand() % 6)), 400);
+}
+
+mframe_t berserk_frames_attack_club[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, berserk_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, berserk_attack_club},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t berserk_move_attack_club = {
+ FRAME_att_c9,
+ FRAME_att_c20,
+ berserk_frames_attack_club,
+ berserk_run
+};
+
+void
+berserk_strike(edict_t *self)
+{
+}
+
+mframe_t berserk_frames_attack_strike[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, berserk_swing},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, berserk_strike},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 9.7, NULL},
+ {ai_move, 13.6, NULL}
+};
+
+mmove_t berserk_move_attack_strike = {
+ FRAME_att_c21,
+ FRAME_att_c34,
+ berserk_frames_attack_strike,
+ berserk_run
+};
+
+void
+berserk_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if ((rand() % 2) == 0)
+ {
+ self->monsterinfo.currentmove = &berserk_move_attack_spike;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_attack_club;
+ }
+}
+
+mframe_t berserk_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_pain1 = {
+ FRAME_painc1,
+ FRAME_painc4,
+ berserk_frames_pain1,
+ berserk_run
+};
+
+mframe_t berserk_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_pain2 = {
+ FRAME_painb1,
+ FRAME_painb20,
+ berserk_frames_pain2,
+ berserk_run
+};
+
+void
+berserk_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ monster_done_dodge(self);
+
+ if ((damage < 20) || (random() < 0.5))
+ {
+ self->monsterinfo.currentmove = &berserk_move_pain1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_pain2;
+ }
+}
+
+void
+berserk_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t berserk_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_death1 = {
+ FRAME_death1,
+ FRAME_death13,
+ berserk_frames_death1,
+ berserk_dead
+};
+
+mframe_t berserk_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_death2 = {
+ FRAME_deathc1,
+ FRAME_deathc8,
+ berserk_frames_death2,
+ berserk_dead
+};
+
+void
+berserk_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ if (damage >= 50)
+ {
+ self->monsterinfo.currentmove = &berserk_move_death1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_death2;
+ }
+}
+
+void
+berserk_jump_now(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 100, forward, self->velocity);
+ VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void
+berserk_jump2_now(edict_t *self)
+{
+ vec3_t forward,up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 150, forward, self->velocity);
+ VectorMA(self->velocity, 400, up, self->velocity);
+}
+
+void
+berserk_jump_wait_land(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity == NULL)
+ {
+ self->monsterinfo.nextframe = self->s.frame;
+
+ if (monster_jump_finished(self))
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+ }
+ else
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+}
+
+mframe_t berserk_frames_jump[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, berserk_jump_now},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, berserk_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_jump = {
+ FRAME_jump1,
+ FRAME_jump9,
+ berserk_frames_jump,
+ berserk_run
+};
+
+mframe_t berserk_frames_jump2[] = {
+ {ai_move, -8, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 0, berserk_jump2_now},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, berserk_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_jump2 = {
+ FRAME_jump1,
+ FRAME_jump9,
+ berserk_frames_jump2,
+ berserk_run
+};
+
+void
+berserk_jump(edict_t *self)
+{
+ if (!self || !self->enemy)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->enemy->absmin[2] > self->absmin[2])
+ {
+ self->monsterinfo.currentmove = &berserk_move_jump2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_jump;
+ }
+}
+
+qboolean
+berserk_blocked(edict_t *self, float dist)
+{
+ if (blocked_checkjump(self, dist, 256, 40))
+ {
+ berserk_jump(self);
+ return true;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+berserk_sidestep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &berserk_move_jump) ||
+ (self->monsterinfo.currentmove == &berserk_move_jump2))
+ {
+ return;
+ }
+
+ if (self->monsterinfo.currentmove != &berserk_move_run1)
+ {
+ self->monsterinfo.currentmove = &berserk_move_run1;
+ }
+}
+
+/*
+ * QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_berserk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ /* pre-caches */
+ sound_pain = gi.soundindex("berserk/berpain2.wav");
+ sound_die = gi.soundindex("berserk/berdeth2.wav");
+ sound_idle = gi.soundindex("berserk/beridle1.wav");
+ sound_punch = gi.soundindex("berserk/attack.wav");
+ sound_search = gi.soundindex("berserk/bersrch1.wav");
+ sound_sight = gi.soundindex("berserk/sight.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->health = 240;
+ self->gib_health = -60;
+ self->mass = 250;
+
+ self->pain = berserk_pain;
+ self->die = berserk_die;
+
+ self->monsterinfo.stand = berserk_stand;
+ self->monsterinfo.walk = berserk_walk;
+ self->monsterinfo.run = berserk_run;
+ self->monsterinfo.dodge = M_MonsterDodge;
+ self->monsterinfo.sidestep = berserk_sidestep;
+ self->monsterinfo.attack = NULL;
+ self->monsterinfo.melee = berserk_melee;
+ self->monsterinfo.sight = berserk_sight;
+ self->monsterinfo.search = berserk_search;
+ self->monsterinfo.blocked = berserk_blocked;
+
+ self->monsterinfo.currentmove = &berserk_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ gi.linkentity(self);
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/berserker/berserker.h b/rogue/src/monster/berserker/berserker.h
new file mode 100644
index 0000000..67eedbc
--- /dev/null
+++ b/rogue/src/monster/berserker/berserker.h
@@ -0,0 +1,261 @@
+/* =======================================================================
+ *
+ * Berserker animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand1 0
+#define FRAME_stand2 1
+#define FRAME_stand3 2
+#define FRAME_stand4 3
+#define FRAME_stand5 4
+#define FRAME_standb1 5
+#define FRAME_standb2 6
+#define FRAME_standb3 7
+#define FRAME_standb4 8
+#define FRAME_standb5 9
+#define FRAME_standb6 10
+#define FRAME_standb7 11
+#define FRAME_standb8 12
+#define FRAME_standb9 13
+#define FRAME_standb10 14
+#define FRAME_standb11 15
+#define FRAME_standb12 16
+#define FRAME_standb13 17
+#define FRAME_standb14 18
+#define FRAME_standb15 19
+#define FRAME_standb16 20
+#define FRAME_standb17 21
+#define FRAME_standb18 22
+#define FRAME_standb19 23
+#define FRAME_standb20 24
+#define FRAME_walkc1 25
+#define FRAME_walkc2 26
+#define FRAME_walkc3 27
+#define FRAME_walkc4 28
+#define FRAME_walkc5 29
+#define FRAME_walkc6 30
+#define FRAME_walkc7 31
+#define FRAME_walkc8 32
+#define FRAME_walkc9 33
+#define FRAME_walkc10 34
+#define FRAME_walkc11 35
+#define FRAME_run1 36
+#define FRAME_run2 37
+#define FRAME_run3 38
+#define FRAME_run4 39
+#define FRAME_run5 40
+#define FRAME_run6 41
+#define FRAME_att_a1 42
+#define FRAME_att_a2 43
+#define FRAME_att_a3 44
+#define FRAME_att_a4 45
+#define FRAME_att_a5 46
+#define FRAME_att_a6 47
+#define FRAME_att_a7 48
+#define FRAME_att_a8 49
+#define FRAME_att_a9 50
+#define FRAME_att_a10 51
+#define FRAME_att_a11 52
+#define FRAME_att_a12 53
+#define FRAME_att_a13 54
+#define FRAME_att_b1 55
+#define FRAME_att_b2 56
+#define FRAME_att_b3 57
+#define FRAME_att_b4 58
+#define FRAME_att_b5 59
+#define FRAME_att_b6 60
+#define FRAME_att_b7 61
+#define FRAME_att_b8 62
+#define FRAME_att_b9 63
+#define FRAME_att_b10 64
+#define FRAME_att_b11 65
+#define FRAME_att_b12 66
+#define FRAME_att_b13 67
+#define FRAME_att_b14 68
+#define FRAME_att_b15 69
+#define FRAME_att_b16 70
+#define FRAME_att_b17 71
+#define FRAME_att_b18 72
+#define FRAME_att_b19 73
+#define FRAME_att_b20 74
+#define FRAME_att_b21 75
+#define FRAME_att_c1 76
+#define FRAME_att_c2 77
+#define FRAME_att_c3 78
+#define FRAME_att_c4 79
+#define FRAME_att_c5 80
+#define FRAME_att_c6 81
+#define FRAME_att_c7 82
+#define FRAME_att_c8 83
+#define FRAME_att_c9 84
+#define FRAME_att_c10 85
+#define FRAME_att_c11 86
+#define FRAME_att_c12 87
+#define FRAME_att_c13 88
+#define FRAME_att_c14 89
+#define FRAME_att_c15 90
+#define FRAME_att_c16 91
+#define FRAME_att_c17 92
+#define FRAME_att_c18 93
+#define FRAME_att_c19 94
+#define FRAME_att_c20 95
+#define FRAME_att_c21 96
+#define FRAME_att_c22 97
+#define FRAME_att_c23 98
+#define FRAME_att_c24 99
+#define FRAME_att_c25 100
+#define FRAME_att_c26 101
+#define FRAME_att_c27 102
+#define FRAME_att_c28 103
+#define FRAME_att_c29 104
+#define FRAME_att_c30 105
+#define FRAME_att_c31 106
+#define FRAME_att_c32 107
+#define FRAME_att_c33 108
+#define FRAME_att_c34 109
+#define FRAME_r_att1 110
+#define FRAME_r_att2 111
+#define FRAME_r_att3 112
+#define FRAME_r_att4 113
+#define FRAME_r_att5 114
+#define FRAME_r_att6 115
+#define FRAME_r_att7 116
+#define FRAME_r_att8 117
+#define FRAME_r_att9 118
+#define FRAME_r_att10 119
+#define FRAME_r_att11 120
+#define FRAME_r_att12 121
+#define FRAME_r_att13 122
+#define FRAME_r_att14 123
+#define FRAME_r_att15 124
+#define FRAME_r_att16 125
+#define FRAME_r_att17 126
+#define FRAME_r_att18 127
+#define FRAME_r_attb1 128
+#define FRAME_r_attb2 129
+#define FRAME_r_attb3 130
+#define FRAME_r_attb4 131
+#define FRAME_r_attb5 132
+#define FRAME_r_attb6 133
+#define FRAME_r_attb7 134
+#define FRAME_r_attb8 135
+#define FRAME_r_attb9 136
+#define FRAME_r_attb10 137
+#define FRAME_r_attb11 138
+#define FRAME_r_attb12 139
+#define FRAME_r_attb13 140
+#define FRAME_r_attb14 141
+#define FRAME_r_attb15 142
+#define FRAME_r_attb16 143
+#define FRAME_r_attb17 144
+#define FRAME_r_attb18 145
+#define FRAME_slam1 146
+#define FRAME_slam2 147
+#define FRAME_slam3 148
+#define FRAME_slam4 149
+#define FRAME_slam5 150
+#define FRAME_slam6 151
+#define FRAME_slam7 152
+#define FRAME_slam8 153
+#define FRAME_slam9 154
+#define FRAME_slam10 155
+#define FRAME_slam11 156
+#define FRAME_slam12 157
+#define FRAME_slam13 158
+#define FRAME_slam14 159
+#define FRAME_slam15 160
+#define FRAME_slam16 161
+#define FRAME_slam17 162
+#define FRAME_slam18 163
+#define FRAME_slam19 164
+#define FRAME_slam20 165
+#define FRAME_slam21 166
+#define FRAME_slam22 167
+#define FRAME_slam23 168
+#define FRAME_duck1 169
+#define FRAME_duck2 170
+#define FRAME_duck3 171
+#define FRAME_duck4 172
+#define FRAME_duck5 173
+#define FRAME_duck6 174
+#define FRAME_duck7 175
+#define FRAME_duck8 176
+#define FRAME_duck9 177
+#define FRAME_duck10 178
+#define FRAME_fall1 179
+#define FRAME_fall2 180
+#define FRAME_fall3 181
+#define FRAME_fall4 182
+#define FRAME_fall5 183
+#define FRAME_fall6 184
+#define FRAME_fall7 185
+#define FRAME_fall8 186
+#define FRAME_fall9 187
+#define FRAME_fall10 188
+#define FRAME_fall11 189
+#define FRAME_fall12 190
+#define FRAME_fall13 191
+#define FRAME_fall14 192
+#define FRAME_fall15 193
+#define FRAME_fall16 194
+#define FRAME_fall17 195
+#define FRAME_fall18 196
+#define FRAME_fall19 197
+#define FRAME_fall20 198
+#define FRAME_painc1 199
+#define FRAME_painc2 200
+#define FRAME_painc3 201
+#define FRAME_painc4 202
+#define FRAME_painb1 203
+#define FRAME_painb2 204
+#define FRAME_painb3 205
+#define FRAME_painb4 206
+#define FRAME_painb5 207
+#define FRAME_painb6 208
+#define FRAME_painb7 209
+#define FRAME_painb8 210
+#define FRAME_painb9 211
+#define FRAME_painb10 212
+#define FRAME_painb11 213
+#define FRAME_painb12 214
+#define FRAME_painb13 215
+#define FRAME_painb14 216
+#define FRAME_painb15 217
+#define FRAME_painb16 218
+#define FRAME_painb17 219
+#define FRAME_painb18 220
+#define FRAME_painb19 221
+#define FRAME_painb20 222
+#define FRAME_death1 223
+#define FRAME_death2 224
+#define FRAME_death3 225
+#define FRAME_death4 226
+#define FRAME_death5 227
+#define FRAME_death6 228
+#define FRAME_death7 229
+#define FRAME_death8 230
+#define FRAME_death9 231
+#define FRAME_death10 232
+#define FRAME_death11 233
+#define FRAME_death12 234
+#define FRAME_death13 235
+#define FRAME_deathc1 236
+#define FRAME_deathc2 237
+#define FRAME_deathc3 238
+#define FRAME_deathc4 239
+#define FRAME_deathc5 240
+#define FRAME_deathc6 241
+#define FRAME_deathc7 242
+#define FRAME_deathc8 243
+#define FRAME_jump1 244
+#define FRAME_jump2 245
+#define FRAME_jump3 246
+#define FRAME_jump4 247
+#define FRAME_jump5 248
+#define FRAME_jump6 249
+#define FRAME_jump7 250
+#define FRAME_jump8 251
+#define FRAME_jump9 252
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/boss2/boss2.c b/rogue/src/monster/boss2/boss2.c
new file mode 100644
index 0000000..23ffd1a
--- /dev/null
+++ b/rogue/src/monster/boss2/boss2.c
@@ -0,0 +1,880 @@
+/* =======================================================================
+ *
+ * Boss 2 aka Hornet.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "boss2.h"
+
+#define BOSS2_ROCKET_SPEED 750
+
+qboolean infront(edict_t *self, edict_t *other);
+void BossExplode(edict_t *self);
+void boss2_run(edict_t *self);
+void boss2_stand(edict_t *self);
+void boss2_dead(edict_t *self);
+void boss2_attack(edict_t *self);
+void boss2_attack_mg(edict_t *self);
+void boss2_reattack_mg(edict_t *self);
+void boss2_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_death;
+static int sound_search1;
+
+void
+boss2_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+ }
+}
+
+void
+Boss2PredictiveRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+ float time, dist;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+
+//1
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start);
+ VectorSubtract(self->enemy->s.origin, start, dir);
+ dist = VectorLength(dir);
+ time = dist / BOSS2_ROCKET_SPEED;
+ VectorMA(self->enemy->s.origin, time-0.3, self->enemy->velocity, vec);
+
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1);
+
+//2
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start);
+ VectorSubtract(self->enemy->s.origin, start, dir);
+ dist = VectorLength(dir);
+ time = dist / BOSS2_ROCKET_SPEED;
+ VectorMA(self->enemy->s.origin, time-0.15, self->enemy->velocity, vec);
+
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_2);
+
+//3
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start);
+ VectorSubtract(self->enemy->s.origin, start, dir);
+ dist = VectorLength(dir);
+ time = dist / BOSS2_ROCKET_SPEED;
+ VectorMA(self->enemy->s.origin, time, self->enemy->velocity, vec);
+
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_3);
+
+//4
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
+ VectorSubtract(self->enemy->s.origin, start, dir);
+ dist = VectorLength(dir);
+ time = dist / BOSS2_ROCKET_SPEED;
+ VectorMA(self->enemy->s.origin, time+0.15, self->enemy->velocity, vec);
+
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_4);
+}
+
+void
+Boss2Rocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if (self->enemy->client && random() < 0.9)
+ {
+ Boss2PredictiveRocket(self);
+ return;
+ }
+
+ AngleVectors (self->s.angles, forward, right, NULL);
+
+//1
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] -= 15;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, 0.4, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1);
+
+//2
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, 0.025, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
+
+//3
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, -0.025, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3);
+
+//4
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] -= 15;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, -0.4, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4);
+}
+
+void
+boss2_firebullet_right(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, 0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3,
+ DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1);
+}
+
+void
+boss2_firebullet_left(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, 0.2, self->enemy->velocity, target);
+
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3,
+ DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1);
+}
+
+void
+Boss2MachineGun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ boss2_firebullet_left(self);
+ boss2_firebullet_right(self);
+}
+
+mframe_t boss2_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t boss2_move_stand = {
+ FRAME_stand30,
+ FRAME_stand50,
+ boss2_frames_stand,
+ NULL
+};
+
+mframe_t boss2_frames_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t boss2_move_fidget = {
+ FRAME_stand1,
+ FRAME_stand30,
+ boss2_frames_fidget,
+ NULL
+};
+
+mframe_t boss2_frames_walk[] = {
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 10, NULL}
+};
+
+mmove_t boss2_move_walk = {
+ FRAME_walk1,
+ FRAME_walk20,
+ boss2_frames_walk,
+ NULL
+};
+
+mframe_t boss2_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL}
+};
+
+mmove_t boss2_move_run = {
+ FRAME_walk1,
+ FRAME_walk20,
+ boss2_frames_run,
+ NULL
+};
+
+mframe_t boss2_frames_attack_pre_mg[] = {
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, boss2_attack_mg}
+};
+
+mmove_t boss2_move_attack_pre_mg = {
+ FRAME_attack1,
+ FRAME_attack9,
+ boss2_frames_attack_pre_mg,
+ NULL
+};
+
+/* Loop this */
+mframe_t boss2_frames_attack_mg[] = {
+ {ai_charge, 2, Boss2MachineGun},
+ {ai_charge, 2, Boss2MachineGun},
+ {ai_charge, 2, Boss2MachineGun},
+ {ai_charge, 2, Boss2MachineGun},
+ {ai_charge, 2, Boss2MachineGun},
+ {ai_charge, 2, boss2_reattack_mg}
+};
+
+mmove_t boss2_move_attack_mg = {
+ FRAME_attack10,
+ FRAME_attack15,
+ boss2_frames_attack_mg,
+ NULL
+};
+
+mframe_t boss2_frames_attack_post_mg[] = {
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL}
+};
+
+
+mmove_t boss2_move_attack_post_mg = {
+ FRAME_attack16,
+ FRAME_attack19,
+ boss2_frames_attack_post_mg,
+ boss2_run
+};
+
+mframe_t boss2_frames_attack_rocket[] = {
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_move, -5, Boss2Rocket},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL}
+};
+
+mmove_t boss2_move_attack_rocket = {FRAME_attack20,
+ FRAME_attack40,
+ boss2_frames_attack_rocket,
+ boss2_run
+};
+
+mframe_t boss2_frames_pain_heavy[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss2_move_pain_heavy = {
+ FRAME_pain2,
+ FRAME_pain19,
+ boss2_frames_pain_heavy,
+ boss2_run
+};
+
+mframe_t boss2_frames_pain_light[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss2_move_pain_light = {
+ FRAME_pain20,
+ FRAME_pain23,
+ boss2_frames_pain_light,
+ boss2_run
+};
+
+mframe_t boss2_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, BossExplode}
+};
+
+mmove_t boss2_move_death = {
+ FRAME_death2,
+ FRAME_death50,
+ boss2_frames_death,
+ boss2_dead
+};
+
+void
+boss2_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss2_move_stand;
+}
+
+void
+boss2_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &boss2_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_run;
+ }
+}
+
+void
+boss2_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss2_move_walk;
+}
+
+void
+boss2_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ if (range <= 125)
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+ }
+ else
+ {
+ if (random() <= 0.6)
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_rocket;
+ }
+ }
+}
+
+void
+boss2_attack_mg(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss2_move_attack_mg;
+}
+
+void
+boss2_reattack_mg(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (infront(self, self->enemy))
+ {
+ if (random() <= 0.7)
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_mg;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+ }
+}
+
+void
+boss2_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (damage < 10)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &boss2_move_pain_light;
+ }
+ else if (damage < 30)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &boss2_move_pain_light;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &boss2_move_pain_heavy;
+ }
+}
+
+void
+boss2_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -56, -56, 0);
+ VectorSet(self->maxs, 56, 56, 80);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+boss2_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ self->monsterinfo.currentmove = &boss2_move_death;
+}
+
+qboolean
+Boss2_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ /* we want them to go ahead and shoot at info_notnulls if they can */
+ if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)
+ {
+ return false;
+ }
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.8;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.8;
+ }
+ else
+ {
+ return false;
+ }
+
+ if ((random() < chance) || (self->enemy->solid == SOLID_NOT))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.3)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_boss2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("bosshovr/bhvpain1.wav");
+ sound_pain2 = gi.soundindex("bosshovr/bhvpain2.wav");
+ sound_pain3 = gi.soundindex("bosshovr/bhvpain3.wav");
+ sound_death = gi.soundindex("bosshovr/bhvdeth1.wav");
+ sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav");
+
+ self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss2/tris.md2");
+ VectorSet(self->mins, -56, -56, 0);
+ VectorSet(self->maxs, 56, 56, 80);
+
+ self->health = 2000;
+ self->gib_health = -200;
+ self->mass = 1000;
+
+ self->flags |= FL_IMMUNE_LASER;
+
+ self->pain = boss2_pain;
+ self->die = boss2_die;
+
+ self->monsterinfo.stand = boss2_stand;
+ self->monsterinfo.walk = boss2_walk;
+ self->monsterinfo.run = boss2_run;
+ self->monsterinfo.attack = boss2_attack;
+ self->monsterinfo.search = boss2_search;
+ self->monsterinfo.checkattack = Boss2_CheckAttack;
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &boss2_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/rogue/src/monster/boss2/boss2.h b/rogue/src/monster/boss2/boss2.h
new file mode 100644
index 0000000..c29bd0c
--- /dev/null
+++ b/rogue/src/monster/boss2/boss2.h
@@ -0,0 +1,189 @@
+/* =======================================================================
+ *
+ * Animations for boss2.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand30 0
+#define FRAME_stand31 1
+#define FRAME_stand32 2
+#define FRAME_stand33 3
+#define FRAME_stand34 4
+#define FRAME_stand35 5
+#define FRAME_stand36 6
+#define FRAME_stand37 7
+#define FRAME_stand38 8
+#define FRAME_stand39 9
+#define FRAME_stand40 10
+#define FRAME_stand41 11
+#define FRAME_stand42 12
+#define FRAME_stand43 13
+#define FRAME_stand44 14
+#define FRAME_stand45 15
+#define FRAME_stand46 16
+#define FRAME_stand47 17
+#define FRAME_stand48 18
+#define FRAME_stand49 19
+#define FRAME_stand50 20
+#define FRAME_stand1 21
+#define FRAME_stand2 22
+#define FRAME_stand3 23
+#define FRAME_stand4 24
+#define FRAME_stand5 25
+#define FRAME_stand6 26
+#define FRAME_stand7 27
+#define FRAME_stand8 28
+#define FRAME_stand9 29
+#define FRAME_stand10 30
+#define FRAME_stand11 31
+#define FRAME_stand12 32
+#define FRAME_stand13 33
+#define FRAME_stand14 34
+#define FRAME_stand15 35
+#define FRAME_stand16 36
+#define FRAME_stand17 37
+#define FRAME_stand18 38
+#define FRAME_stand19 39
+#define FRAME_stand20 40
+#define FRAME_stand21 41
+#define FRAME_stand22 42
+#define FRAME_stand23 43
+#define FRAME_stand24 44
+#define FRAME_stand25 45
+#define FRAME_stand26 46
+#define FRAME_stand27 47
+#define FRAME_stand28 48
+#define FRAME_stand29 49
+#define FRAME_walk1 50
+#define FRAME_walk2 51
+#define FRAME_walk3 52
+#define FRAME_walk4 53
+#define FRAME_walk5 54
+#define FRAME_walk6 55
+#define FRAME_walk7 56
+#define FRAME_walk8 57
+#define FRAME_walk9 58
+#define FRAME_walk10 59
+#define FRAME_walk11 60
+#define FRAME_walk12 61
+#define FRAME_walk13 62
+#define FRAME_walk14 63
+#define FRAME_walk15 64
+#define FRAME_walk16 65
+#define FRAME_walk17 66
+#define FRAME_walk18 67
+#define FRAME_walk19 68
+#define FRAME_walk20 69
+#define FRAME_attack1 70
+#define FRAME_attack2 71
+#define FRAME_attack3 72
+#define FRAME_attack4 73
+#define FRAME_attack5 74
+#define FRAME_attack6 75
+#define FRAME_attack7 76
+#define FRAME_attack8 77
+#define FRAME_attack9 78
+#define FRAME_attack10 79
+#define FRAME_attack11 80
+#define FRAME_attack12 81
+#define FRAME_attack13 82
+#define FRAME_attack14 83
+#define FRAME_attack15 84
+#define FRAME_attack16 85
+#define FRAME_attack17 86
+#define FRAME_attack18 87
+#define FRAME_attack19 88
+#define FRAME_attack20 89
+#define FRAME_attack21 90
+#define FRAME_attack22 91
+#define FRAME_attack23 92
+#define FRAME_attack24 93
+#define FRAME_attack25 94
+#define FRAME_attack26 95
+#define FRAME_attack27 96
+#define FRAME_attack28 97
+#define FRAME_attack29 98
+#define FRAME_attack30 99
+#define FRAME_attack31 100
+#define FRAME_attack32 101
+#define FRAME_attack33 102
+#define FRAME_attack34 103
+#define FRAME_attack35 104
+#define FRAME_attack36 105
+#define FRAME_attack37 106
+#define FRAME_attack38 107
+#define FRAME_attack39 108
+#define FRAME_attack40 109
+#define FRAME_pain2 110
+#define FRAME_pain3 111
+#define FRAME_pain4 112
+#define FRAME_pain5 113
+#define FRAME_pain6 114
+#define FRAME_pain7 115
+#define FRAME_pain8 116
+#define FRAME_pain9 117
+#define FRAME_pain10 118
+#define FRAME_pain11 119
+#define FRAME_pain12 120
+#define FRAME_pain13 121
+#define FRAME_pain14 122
+#define FRAME_pain15 123
+#define FRAME_pain16 124
+#define FRAME_pain17 125
+#define FRAME_pain18 126
+#define FRAME_pain19 127
+#define FRAME_pain20 128
+#define FRAME_pain21 129
+#define FRAME_pain22 130
+#define FRAME_pain23 131
+#define FRAME_death2 132
+#define FRAME_death3 133
+#define FRAME_death4 134
+#define FRAME_death5 135
+#define FRAME_death6 136
+#define FRAME_death7 137
+#define FRAME_death8 138
+#define FRAME_death9 139
+#define FRAME_death10 140
+#define FRAME_death11 141
+#define FRAME_death12 142
+#define FRAME_death13 143
+#define FRAME_death14 144
+#define FRAME_death15 145
+#define FRAME_death16 146
+#define FRAME_death17 147
+#define FRAME_death18 148
+#define FRAME_death19 149
+#define FRAME_death20 150
+#define FRAME_death21 151
+#define FRAME_death22 152
+#define FRAME_death23 153
+#define FRAME_death24 154
+#define FRAME_death25 155
+#define FRAME_death26 156
+#define FRAME_death27 157
+#define FRAME_death28 158
+#define FRAME_death29 159
+#define FRAME_death30 160
+#define FRAME_death31 161
+#define FRAME_death32 162
+#define FRAME_death33 163
+#define FRAME_death34 164
+#define FRAME_death35 165
+#define FRAME_death36 166
+#define FRAME_death37 167
+#define FRAME_death38 168
+#define FRAME_death39 169
+#define FRAME_death40 170
+#define FRAME_death41 171
+#define FRAME_death42 172
+#define FRAME_death43 173
+#define FRAME_death44 174
+#define FRAME_death45 175
+#define FRAME_death46 176
+#define FRAME_death47 177
+#define FRAME_death48 178
+#define FRAME_death49 179
+#define FRAME_death50 180
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/boss3/boss3.c b/rogue/src/monster/boss3/boss3.c
new file mode 100644
index 0000000..52d1e5e
--- /dev/null
+++ b/rogue/src/monster/boss3/boss3.c
@@ -0,0 +1,73 @@
+#include "../../header/local.h"
+#include "boss32.h"
+
+void
+Use_Boss3(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BOSSTPORT);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+ G_FreeEdict(ent);
+}
+
+void
+Think_Boss3Stand(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.frame == FRAME_stand260)
+ {
+ ent->s.frame = FRAME_stand201;
+ }
+ else
+ {
+ ent->s.frame++;
+ }
+
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+/*
+ * QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90)
+ *
+ * Just stands and cycles in one place until targeted, then teleports away.
+ */
+void
+SP_monster_boss3_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->model = "models/monsters/boss3/rider/tris.md2";
+ self->s.modelindex = gi.modelindex(self->model);
+ self->s.frame = FRAME_stand201;
+
+ gi.soundindex("misc/bigtele.wav");
+
+ VectorSet(self->mins, -32, -32, 0);
+ VectorSet(self->maxs, 32, 32, 90);
+
+ self->use = Use_Boss3;
+ self->think = Think_Boss3Stand;
+ self->nextthink = level.time + FRAMETIME;
+ gi.linkentity(self);
+}
diff --git a/rogue/src/monster/boss3/boss31.c b/rogue/src/monster/boss3/boss31.c
new file mode 100644
index 0000000..5cb920a
--- /dev/null
+++ b/rogue/src/monster/boss3/boss31.c
@@ -0,0 +1,904 @@
+/* =======================================================================
+ *
+ * Final boss, stage 1 (jorg).
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "boss31.h"
+
+extern void SP_monster_makron(edict_t *self);
+qboolean visible(edict_t *self, edict_t *other);
+void BossExplode(edict_t *self);
+void MakronToss(edict_t *self);
+void jorg_dead(edict_t *self);
+void jorgBFG(edict_t *self);
+void jorgMachineGun(edict_t *self);
+void jorg_firebullet(edict_t *self);
+void jorg_reattack1(edict_t *self);
+void jorg_attack1(edict_t *self);
+void jorg_idle(edict_t *self);
+void jorg_step_left(edict_t *self);
+void jorg_step_right(edict_t *self);
+void jorg_death_hit(edict_t *self);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_idle;
+static int sound_death;
+static int sound_search1;
+static int sound_search2;
+static int sound_search3;
+static int sound_attack1;
+static int sound_attack2;
+static int sound_firegun;
+static int sound_step_left;
+static int sound_step_right;
+static int sound_death_hit;
+
+void
+jorg_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ float r;
+
+ r = random();
+
+ if (r <= 0.3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+ }
+ else if (r <= 0.6)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0);
+ }
+}
+
+/* stand */
+mframe_t jorg_frames_stand[] = {
+ {ai_stand, 0, jorg_idle},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 10 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 20 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 30 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 19, NULL},
+ {ai_stand, 11, jorg_step_left},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 6, NULL},
+ {ai_stand, 9, jorg_step_right},
+ {ai_stand, 0, NULL}, /* 40 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -2, NULL},
+ {ai_stand, -17, jorg_step_left},
+ {ai_stand, 0, NULL},
+ {ai_stand, -12, NULL}, /* 50 */
+ {ai_stand, -14, jorg_step_right}
+};
+
+mmove_t jorg_move_stand = {
+ FRAME_stand01,
+ FRAME_stand51,
+ jorg_frames_stand,
+ NULL
+};
+
+void
+jorg_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_death_hit(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_step_left(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_step_right(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &jorg_move_stand;
+}
+
+mframe_t jorg_frames_run[] = {
+ {ai_run, 17, jorg_step_left},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 33, jorg_step_right},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, NULL}
+};
+
+mmove_t jorg_move_run = {
+ FRAME_walk06,
+ FRAME_walk19,
+ jorg_frames_run,
+ NULL
+};
+
+/* walk */
+mframe_t jorg_frames_start_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 15, NULL}
+};
+
+mmove_t jorg_move_start_walk = {
+ FRAME_walk01,
+ FRAME_walk05,
+ jorg_frames_start_walk,
+ NULL
+};
+
+mframe_t jorg_frames_walk[] = {
+ {ai_walk, 17, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 33, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 9, NULL}
+};
+
+mmove_t jorg_move_walk = {
+ FRAME_walk06,
+ FRAME_walk19,
+ jorg_frames_walk,
+ NULL
+};
+
+mframe_t jorg_frames_end_walk[] = {
+ {ai_walk, 11, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, -8, NULL}
+};
+
+mmove_t jorg_move_end_walk = {
+ FRAME_walk20,
+ FRAME_walk25,
+ jorg_frames_end_walk,
+ NULL
+};
+
+void
+jorg_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &jorg_move_walk;
+}
+
+void
+jorg_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &jorg_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &jorg_move_run;
+ }
+}
+
+mframe_t jorg_frames_pain3[] = {
+ {ai_move, -28, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -3, jorg_step_left},
+ {ai_move, -9, NULL},
+ {ai_move, 0, jorg_step_right},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -11, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 7, jorg_step_left},
+ {ai_move, 17, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, jorg_step_right}
+};
+
+mmove_t jorg_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain325,
+ jorg_frames_pain3,
+ jorg_run
+};
+
+mframe_t jorg_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain203,
+ jorg_frames_pain2,
+ jorg_run
+};
+
+mframe_t jorg_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain103,
+ jorg_frames_pain1,
+ jorg_run
+};
+
+mframe_t jorg_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 10 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 20 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 30 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 40 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, MakronToss},
+ {ai_move, 0, BossExplode} /* 50 */
+};
+
+mmove_t jorg_move_death = {
+ FRAME_death01,
+ FRAME_death50,
+ jorg_frames_death1,
+ jorg_dead
+};
+
+mframe_t jorg_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, jorgBFG},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak213,
+ jorg_frames_attack2,
+ jorg_run
+};
+
+mframe_t jorg_frames_start_attack1[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t jorg_move_start_attack1 = {
+ FRAME_attak101,
+ FRAME_attak108,
+ jorg_frames_start_attack1,
+ jorg_attack1
+};
+
+mframe_t jorg_frames_attack1[] = {
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet}
+};
+
+mmove_t jorg_move_attack1 = {
+ FRAME_attak109,
+ FRAME_attak114,
+ jorg_frames_attack1,
+ jorg_reattack1
+};
+
+mframe_t jorg_frames_end_attack1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_end_attack1 = {
+ FRAME_attak115,
+ FRAME_attak118,
+ jorg_frames_end_attack1,
+ jorg_run
+};
+
+void
+jorg_reattack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ if (random() < 0.9)
+ {
+ self->monsterinfo.currentmove = &jorg_move_attack1;
+ }
+ else
+ {
+ self->s.sound = 0;
+ self->monsterinfo.currentmove = &jorg_move_end_attack1;
+ }
+ }
+ else
+ {
+ self->s.sound = 0;
+ self->monsterinfo.currentmove = &jorg_move_end_attack1;
+ }
+}
+
+void
+jorg_attack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &jorg_move_attack1;
+}
+
+void
+jorg_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ self->s.sound = 0;
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ /* Lessen the chance of him going into his
+ pain frames if he takes little damage */
+ if (damage <= 40)
+ {
+ if (random() <= 0.6)
+ {
+ return;
+ }
+ }
+
+ /* If he's entering his attack1 or using attack1,
+ lessen the chance of him going into pain */
+
+ if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108))
+ {
+ if (random() <= 0.005)
+ {
+ return;
+ }
+ }
+
+ if ((self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114))
+ {
+ if (random() <= 0.00005)
+ {
+ return;
+ }
+ }
+
+ if ((self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208))
+ {
+ if (random() <= 0.005)
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 50)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_pain1;
+ }
+ else if (damage <= 100)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_pain2;
+ }
+ else
+ {
+ if (random() <= 0.3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_pain3;
+ }
+ }
+}
+
+void
+jorgBFG(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0);
+ monster_fire_bfg(self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1);
+}
+
+void
+jorg_firebullet_right(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1);
+}
+
+void
+jorg_firebullet_left(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1);
+}
+
+void
+jorg_firebullet(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ jorg_firebullet_left(self);
+ jorg_firebullet_right(self);
+}
+
+void
+jorg_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.75)
+ {
+ gi.sound(self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM, 0);
+ self->s.sound = gi.soundindex("boss3/w_loop.wav");
+ self->monsterinfo.currentmove = &jorg_move_start_attack1;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_attack2;
+ }
+}
+
+void
+jorg_dead(edict_t *self)
+{
+}
+
+void
+jorg_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->s.sound = 0;
+ self->count = 0;
+ self->monsterinfo.currentmove = &jorg_move_death;
+}
+
+qboolean
+Jorg_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ return false;
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.2;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (random() < chance)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.3)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+void MakronPrecache(void);
+
+/*
+ * QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_jorg(edict_t *self)
+{
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("boss3/bs3pain1.wav");
+ sound_pain2 = gi.soundindex("boss3/bs3pain2.wav");
+ sound_pain3 = gi.soundindex("boss3/bs3pain3.wav");
+ sound_death = gi.soundindex("boss3/bs3deth1.wav");
+ sound_attack1 = gi.soundindex("boss3/bs3atck1.wav");
+ sound_attack2 = gi.soundindex("boss3/bs3atck2.wav");
+ sound_search1 = gi.soundindex("boss3/bs3srch1.wav");
+ sound_search2 = gi.soundindex("boss3/bs3srch2.wav");
+ sound_search3 = gi.soundindex("boss3/bs3srch3.wav");
+ sound_idle = gi.soundindex("boss3/bs3idle1.wav");
+ sound_step_left = gi.soundindex("boss3/step1.wav");
+ sound_step_right = gi.soundindex("boss3/step2.wav");
+ sound_firegun = gi.soundindex("boss3/xfire.wav");
+ sound_death_hit = gi.soundindex("boss3/d_hit.wav");
+
+ MakronPrecache();
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss3/jorg/tris.md2");
+ self->s.modelindex2 = gi.modelindex("models/monsters/boss3/rider/tris.md2");
+ VectorSet(self->mins, -80, -80, 0);
+ VectorSet(self->maxs, 80, 80, 140);
+
+ self->health = 3000;
+ self->gib_health = -2000;
+ self->mass = 1000;
+
+ self->pain = jorg_pain;
+ self->die = jorg_die;
+ self->monsterinfo.stand = jorg_stand;
+ self->monsterinfo.walk = jorg_walk;
+ self->monsterinfo.run = jorg_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = jorg_attack;
+ self->monsterinfo.search = jorg_search;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = NULL;
+ self->monsterinfo.checkattack = Jorg_CheckAttack;
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &jorg_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/boss3/boss31.h b/rogue/src/monster/boss3/boss31.h
new file mode 100644
index 0000000..b700b08
--- /dev/null
+++ b/rogue/src/monster/boss3/boss31.h
@@ -0,0 +1,196 @@
+/* =======================================================================
+ *
+ * Animations for final boss stage 1.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak113 12
+#define FRAME_attak114 13
+#define FRAME_attak115 14
+#define FRAME_attak116 15
+#define FRAME_attak117 16
+#define FRAME_attak118 17
+#define FRAME_attak201 18
+#define FRAME_attak202 19
+#define FRAME_attak203 20
+#define FRAME_attak204 21
+#define FRAME_attak205 22
+#define FRAME_attak206 23
+#define FRAME_attak207 24
+#define FRAME_attak208 25
+#define FRAME_attak209 26
+#define FRAME_attak210 27
+#define FRAME_attak211 28
+#define FRAME_attak212 29
+#define FRAME_attak213 30
+#define FRAME_death01 31
+#define FRAME_death02 32
+#define FRAME_death03 33
+#define FRAME_death04 34
+#define FRAME_death05 35
+#define FRAME_death06 36
+#define FRAME_death07 37
+#define FRAME_death08 38
+#define FRAME_death09 39
+#define FRAME_death10 40
+#define FRAME_death11 41
+#define FRAME_death12 42
+#define FRAME_death13 43
+#define FRAME_death14 44
+#define FRAME_death15 45
+#define FRAME_death16 46
+#define FRAME_death17 47
+#define FRAME_death18 48
+#define FRAME_death19 49
+#define FRAME_death20 50
+#define FRAME_death21 51
+#define FRAME_death22 52
+#define FRAME_death23 53
+#define FRAME_death24 54
+#define FRAME_death25 55
+#define FRAME_death26 56
+#define FRAME_death27 57
+#define FRAME_death28 58
+#define FRAME_death29 59
+#define FRAME_death30 60
+#define FRAME_death31 61
+#define FRAME_death32 62
+#define FRAME_death33 63
+#define FRAME_death34 64
+#define FRAME_death35 65
+#define FRAME_death36 66
+#define FRAME_death37 67
+#define FRAME_death38 68
+#define FRAME_death39 69
+#define FRAME_death40 70
+#define FRAME_death41 71
+#define FRAME_death42 72
+#define FRAME_death43 73
+#define FRAME_death44 74
+#define FRAME_death45 75
+#define FRAME_death46 76
+#define FRAME_death47 77
+#define FRAME_death48 78
+#define FRAME_death49 79
+#define FRAME_death50 80
+#define FRAME_pain101 81
+#define FRAME_pain102 82
+#define FRAME_pain103 83
+#define FRAME_pain201 84
+#define FRAME_pain202 85
+#define FRAME_pain203 86
+#define FRAME_pain301 87
+#define FRAME_pain302 88
+#define FRAME_pain303 89
+#define FRAME_pain304 90
+#define FRAME_pain305 91
+#define FRAME_pain306 92
+#define FRAME_pain307 93
+#define FRAME_pain308 94
+#define FRAME_pain309 95
+#define FRAME_pain310 96
+#define FRAME_pain311 97
+#define FRAME_pain312 98
+#define FRAME_pain313 99
+#define FRAME_pain314 100
+#define FRAME_pain315 101
+#define FRAME_pain316 102
+#define FRAME_pain317 103
+#define FRAME_pain318 104
+#define FRAME_pain319 105
+#define FRAME_pain320 106
+#define FRAME_pain321 107
+#define FRAME_pain322 108
+#define FRAME_pain323 109
+#define FRAME_pain324 110
+#define FRAME_pain325 111
+#define FRAME_stand01 112
+#define FRAME_stand02 113
+#define FRAME_stand03 114
+#define FRAME_stand04 115
+#define FRAME_stand05 116
+#define FRAME_stand06 117
+#define FRAME_stand07 118
+#define FRAME_stand08 119
+#define FRAME_stand09 120
+#define FRAME_stand10 121
+#define FRAME_stand11 122
+#define FRAME_stand12 123
+#define FRAME_stand13 124
+#define FRAME_stand14 125
+#define FRAME_stand15 126
+#define FRAME_stand16 127
+#define FRAME_stand17 128
+#define FRAME_stand18 129
+#define FRAME_stand19 130
+#define FRAME_stand20 131
+#define FRAME_stand21 132
+#define FRAME_stand22 133
+#define FRAME_stand23 134
+#define FRAME_stand24 135
+#define FRAME_stand25 136
+#define FRAME_stand26 137
+#define FRAME_stand27 138
+#define FRAME_stand28 139
+#define FRAME_stand29 140
+#define FRAME_stand30 141
+#define FRAME_stand31 142
+#define FRAME_stand32 143
+#define FRAME_stand33 144
+#define FRAME_stand34 145
+#define FRAME_stand35 146
+#define FRAME_stand36 147
+#define FRAME_stand37 148
+#define FRAME_stand38 149
+#define FRAME_stand39 150
+#define FRAME_stand40 151
+#define FRAME_stand41 152
+#define FRAME_stand42 153
+#define FRAME_stand43 154
+#define FRAME_stand44 155
+#define FRAME_stand45 156
+#define FRAME_stand46 157
+#define FRAME_stand47 158
+#define FRAME_stand48 159
+#define FRAME_stand49 160
+#define FRAME_stand50 161
+#define FRAME_stand51 162
+#define FRAME_walk01 163
+#define FRAME_walk02 164
+#define FRAME_walk03 165
+#define FRAME_walk04 166
+#define FRAME_walk05 167
+#define FRAME_walk06 168
+#define FRAME_walk07 169
+#define FRAME_walk08 170
+#define FRAME_walk09 171
+#define FRAME_walk10 172
+#define FRAME_walk11 173
+#define FRAME_walk12 174
+#define FRAME_walk13 175
+#define FRAME_walk14 176
+#define FRAME_walk15 177
+#define FRAME_walk16 178
+#define FRAME_walk17 179
+#define FRAME_walk18 180
+#define FRAME_walk19 181
+#define FRAME_walk20 182
+#define FRAME_walk21 183
+#define FRAME_walk22 184
+#define FRAME_walk23 185
+#define FRAME_walk24 186
+#define FRAME_walk25 187
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/boss3/boss32.c b/rogue/src/monster/boss3/boss32.c
new file mode 100644
index 0000000..db23f7d
--- /dev/null
+++ b/rogue/src/monster/boss3/boss32.c
@@ -0,0 +1,1252 @@
+/* =======================================================================
+ *
+ * Final boss, stage 2 (makron).
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "boss32.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+
+void MakronRailgun(edict_t *self);
+void MakronSaveloc(edict_t *self);
+void MakronHyperblaster(edict_t *self);
+void makron_step_left(edict_t *self);
+void makron_step_right(edict_t *self);
+void makronBFG(edict_t *self);
+void makron_dead(edict_t *self);
+
+static int sound_pain4;
+static int sound_pain5;
+static int sound_pain6;
+static int sound_death;
+static int sound_step_left;
+static int sound_step_right;
+static int sound_attack_bfg;
+static int sound_brainsplorch;
+static int sound_prerailgun;
+static int sound_popup;
+static int sound_taunt1;
+static int sound_taunt2;
+static int sound_taunt3;
+static int sound_hit;
+
+void
+makron_taunt(edict_t *self)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r <= 0.3)
+ {
+ gi.sound(self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0);
+ }
+ else if (r <= 0.6)
+ {
+ gi.sound(self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0);
+ }
+}
+
+/* stand */
+mframe_t makron_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 10 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 20 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 30 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 40 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 50 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL} /* 60 */
+};
+
+mmove_t makron_move_stand = {
+ FRAME_stand201,
+ FRAME_stand260,
+ makron_frames_stand,
+ NULL
+};
+
+void
+makron_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &makron_move_stand;
+}
+
+mframe_t makron_frames_run[] = {
+ {ai_run, 3, makron_step_left},
+ {ai_run, 12, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, makron_step_right},
+ {ai_run, 6, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 12, NULL}
+};
+
+mmove_t makron_move_run = {
+ FRAME_walk204,
+ FRAME_walk213,
+ makron_frames_run,
+ NULL
+};
+
+void
+makron_hit(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_AUTO, sound_hit, 1, ATTN_NONE, 0);
+}
+
+void
+makron_popup(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_popup, 1, ATTN_NONE, 0);
+}
+
+void
+makron_step_left(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0);
+}
+
+void
+makron_step_right(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0);
+}
+
+void
+makron_brainsplorch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM, 0);
+}
+
+void
+makron_prerailgun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM, 0);
+}
+
+mframe_t makron_frames_walk[] = {
+ {ai_walk, 3, makron_step_left},
+ {ai_walk, 12, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, makron_step_right},
+ {ai_walk, 6, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 12, NULL}
+};
+
+mmove_t makron_move_walk = {
+ FRAME_walk204,
+ FRAME_walk213,
+ makron_frames_run,
+ NULL
+};
+
+void
+makron_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &makron_move_walk;
+}
+
+void
+makron_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &makron_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &makron_move_run;
+ }
+}
+
+mframe_t makron_frames_pain6[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 10 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, makron_popup},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 20 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, makron_taunt},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_pain6 = {
+ FRAME_pain601,
+ FRAME_pain627,
+ makron_frames_pain6,
+ makron_run
+};
+
+mframe_t makron_frames_pain5[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_pain5 = {
+ FRAME_pain501,
+ FRAME_pain504,
+ makron_frames_pain5,
+ makron_run
+};
+
+mframe_t makron_frames_pain4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_pain4 = {
+ FRAME_pain401,
+ FRAME_pain404,
+ makron_frames_pain4,
+ makron_run
+};
+
+mframe_t makron_frames_death2[] = {
+ {ai_move, -15, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -12, NULL},
+ {ai_move, 0, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 10 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 12, NULL},
+ {ai_move, 11, makron_step_right},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 20 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 30 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 6, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 2, NULL}, /* 40 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 50 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -6, makron_step_right},
+ {ai_move, -4, NULL},
+ {ai_move, -4, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 60 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -3, makron_step_right},
+ {ai_move, -8, NULL},
+ {ai_move, -3, makron_step_left},
+ {ai_move, -7, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -4, makron_step_right}, /* 70 */
+ {ai_move, -6, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 0, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 80 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL}, /* 90 */
+ {ai_move, 27, makron_hit},
+ {ai_move, 26, NULL},
+ {ai_move, 0, makron_brainsplorch},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL} /* 95 */
+};
+
+mmove_t makron_move_death2 = {
+ FRAME_death201,
+ FRAME_death295,
+ makron_frames_death2,
+ makron_dead
+};
+
+mframe_t makron_frames_death3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_death3 = {
+ FRAME_death301,
+ FRAME_death320,
+ makron_frames_death3,
+ NULL
+};
+
+mframe_t makron_frames_sight[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_sight = {
+ FRAME_active01,
+ FRAME_active13,
+ makron_frames_sight,
+ makron_run
+};
+
+void
+makronBFG(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ gi.sound(self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0);
+ monster_fire_bfg(self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG);
+}
+
+mframe_t makron_frames_attack3[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, makronBFG},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_attack3 = {
+ FRAME_attak301,
+ FRAME_attak308,
+ makron_frames_attack3,
+ makron_run
+};
+
+mframe_t makron_frames_attack4[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_attack4 = {
+ FRAME_attak401,
+ FRAME_attak426,
+ makron_frames_attack4,
+ makron_run
+};
+
+mframe_t makron_frames_attack5[] = {
+ {ai_charge, 0, makron_prerailgun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, MakronSaveloc},
+ {ai_move, 0, MakronRailgun}, /* Fire railgun */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_attack5 = {
+ FRAME_attak501,
+ FRAME_attak516,
+ makron_frames_attack5,
+ makron_run
+};
+
+void
+MakronSaveloc(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+}
+
+void
+MakronRailgun(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1],
+ forward, right, start);
+
+ /* calc direction to where we targted */
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_railgun(self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1);
+}
+
+void
+MakronHyperblaster(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t vec;
+ vec3_t start;
+ vec3_t forward, right;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, vec);
+ vectoangles(vec, vec);
+ dir[0] = vec[0];
+ }
+ else
+ {
+ dir[0] = 0;
+ }
+
+ if (self->s.frame <= FRAME_attak413)
+ {
+ dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413);
+ }
+ else
+ {
+ dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421);
+ }
+
+ dir[2] = 0;
+
+ AngleVectors(dir, forward, NULL, NULL);
+
+ monster_fire_blaster(self, start, forward, 15,
+ 1000, MZ2_MAKRON_BLASTER_1, EF_BLASTER);
+}
+
+void
+makron_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ /* Lessen the chance of him going into his pain frames */
+ if (damage <= 25)
+ {
+ if (random() < 0.2)
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 40)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain4;
+ }
+ else if (damage <= 110)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain5;
+ }
+ else
+ {
+ if (damage <= 150)
+ {
+ if (random() <= 0.45)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain6;
+ }
+ }
+ else
+ {
+ if (random() <= 0.35)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain6;
+ }
+ }
+ }
+}
+
+void
+makron_sight(edict_t *self, edict_t *other /* unused */)
+{
+}
+
+void
+makron_attack(edict_t *self)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r <= 0.3)
+ {
+ self->monsterinfo.currentmove = &makron_move_attack3;
+ }
+ else if (r <= 0.6)
+ {
+ self->monsterinfo.currentmove = &makron_move_attack4;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &makron_move_attack5;
+ }
+}
+
+void
+makron_torso_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* detach from the makron if the legs are gone completely */
+ if (self->owner && (!self->owner->inuse || (self->owner->health <= self->owner->gib_health)))
+ {
+ self->owner = NULL;
+ }
+
+ /* if the makron is revived the torso was put back on him */
+ if (self->owner && self->owner->deadflag != DEAD_DEAD)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner && (self->owner->monsterinfo.aiflags & AI_RESURRECTING))
+ {
+ self->s.effects |= EF_COLOR_SHELL;
+ self->s.renderfx |= RF_SHELL_RED;
+ }
+ else
+ {
+ self->s.effects &= ~EF_COLOR_SHELL;
+ self->s.renderfx &= ~RF_SHELL_RED;
+ }
+
+ if (++self->s.frame >= FRAME_death320)
+ {
+ self->s.frame = FRAME_death301;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+static void
+makron_torso_origin(edict_t *self, edict_t *torso)
+{
+ vec3_t v;
+ trace_t tr;
+
+ AngleVectors(self->s.angles, v, NULL, NULL);
+ VectorMA(self->s.origin, -84.0f, v, v);
+
+ tr = gi.trace(self->s.origin, torso->mins, torso->maxs, v, self, MASK_SOLID);
+
+ VectorCopy (tr.endpos, torso->s.origin);
+}
+
+void
+makron_torso_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ int n;
+
+ if (self->health > self->gib_health)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex( "misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2",
+ damage, GIB_METALLIC);
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+makron_torso(edict_t *self)
+{
+ edict_t *torso;
+
+ if (!self)
+ {
+ return;
+ }
+
+ torso = G_SpawnOptional();
+
+ if (!torso)
+ {
+ return;
+ }
+
+ VectorCopy(self->s.angles, torso->s.angles);
+ VectorSet(torso->mins, -24, -24, 0);
+ VectorSet(torso->maxs, 24, 24, 16);
+ makron_torso_origin(self, torso);
+
+ torso->gib_health = -800;
+ torso->takedamage = DAMAGE_YES;
+ torso->die = makron_torso_die;
+ torso->deadflag = DEAD_DEAD;
+
+ torso->owner = self;
+ torso->movetype = MOVETYPE_TOSS;
+ torso->solid = SOLID_BBOX;
+ torso->svflags = SVF_MONSTER|SVF_DEADMONSTER;
+ torso->clipmask = MASK_MONSTERSOLID;
+ torso->s.frame = FRAME_death301;
+ torso->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2");
+ torso->think = makron_torso_think;
+ torso->nextthink = level.time + 2 * FRAMETIME;
+ torso->s.sound = gi.soundindex("makron/spine.wav");
+
+ gi.linkentity(torso);
+}
+
+/* death */
+void
+makron_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -48, -48, 0);
+ VectorSet(self->maxs, 48, 48, 24);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+makron_die(edict_t *self, edict_t *inflictor /* update */, edict_t *attacker /* update */,
+ int damage, vec3_t point /* update */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.sound = 0;
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 1 /*4*/; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ makron_torso(self);
+
+ /* lower bbox since the torso is gone */
+ self->maxs[2] = 64;
+ gi.linkentity (self);
+
+ self->monsterinfo.currentmove = &makron_move_death2;
+}
+
+qboolean
+Makron_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ return false;
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.2;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (random() < chance)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.3)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+void
+MakronPrecache(void)
+{
+ sound_pain4 = gi.soundindex("makron/pain3.wav");
+ sound_pain5 = gi.soundindex("makron/pain2.wav");
+ sound_pain6 = gi.soundindex("makron/pain1.wav");
+ sound_death = gi.soundindex("makron/death.wav");
+ sound_step_left = gi.soundindex("makron/step1.wav");
+ sound_step_right = gi.soundindex("makron/step2.wav");
+ sound_attack_bfg = gi.soundindex("makron/bfg_fire.wav");
+ sound_brainsplorch = gi.soundindex("makron/brain1.wav");
+ sound_prerailgun = gi.soundindex("makron/rail_up.wav");
+ sound_popup = gi.soundindex("makron/popup.wav");
+ sound_taunt1 = gi.soundindex("makron/voice4.wav");
+ sound_taunt2 = gi.soundindex("makron/voice3.wav");
+ sound_taunt3 = gi.soundindex("makron/voice.wav");
+ sound_hit = gi.soundindex("makron/bhit.wav");
+
+ gi.modelindex("models/monsters/boss3/rider/tris.md2");
+}
+
+/*
+ * QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_makron(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ MakronPrecache();
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2");
+ VectorSet(self->mins, -30, -30, 0);
+ VectorSet(self->maxs, 30, 30, 90);
+
+ self->health = 3000;
+ self->gib_health = -2000;
+ self->mass = 500;
+
+ self->pain = makron_pain;
+ self->die = makron_die;
+ self->monsterinfo.stand = makron_stand;
+ self->monsterinfo.walk = makron_walk;
+ self->monsterinfo.run = makron_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = makron_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = makron_sight;
+ self->monsterinfo.checkattack = Makron_CheckAttack;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &makron_move_sight;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
+
+void
+MakronSpawn(edict_t *self)
+{
+ vec3_t vec;
+ edict_t *enemy;
+ edict_t *oldenemy;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* spawning can mess with enemy state so clear it temporarily */
+ enemy = self->enemy;
+ self->enemy = NULL;
+
+ oldenemy = self->oldenemy;
+ self->oldenemy = NULL;
+
+ SP_monster_makron(self);
+ if (self->think)
+ {
+ self->think(self);
+ }
+
+ /* and re-link enemy state now that he's spawned */
+ if (enemy && enemy->inuse && enemy->deadflag != DEAD_DEAD)
+ {
+ self->enemy = enemy;
+ }
+
+ if (oldenemy && oldenemy->inuse && oldenemy->deadflag != DEAD_DEAD)
+ {
+ self->oldenemy = oldenemy;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = self->oldenemy;
+ self->oldenemy = NULL;
+ }
+
+ enemy = self->enemy;
+
+ if (enemy)
+ {
+ FoundTarget(self);
+ VectorCopy(self->pos1, self->monsterinfo.last_sighting);
+ }
+
+ if (enemy && visible(self, enemy))
+ {
+ VectorSubtract(enemy->s.origin, self->s.origin, vec);
+ self->s.angles[YAW] = vectoyaw(vec);
+ VectorNormalize(vec);
+ }
+ else
+ AngleVectors(self->s.angles, vec, NULL, NULL);
+
+ VectorScale(vec, 400, self->velocity);
+ /* the jump frames are fixed length so best to normalize the up speed */
+ self->velocity[2] = 200.0f * (sv_gravity->value / 800.0f);
+
+ self->groundentity = NULL;
+ self->s.origin[2] += 1;
+ gi.linkentity(self);
+
+ self->pain_debounce_time = level.time + 1;
+
+ self->monsterinfo.currentmove = &makron_move_sight;
+}
+
+/*
+ * Jorg is just about dead, so set up to launch Makron out
+ */
+void
+MakronToss(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = G_Spawn();
+ ent->classname = "monster_makron";
+ ent->nextthink = level.time + 0.8;
+ ent->think = MakronSpawn;
+ ent->target = self->target;
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(self->s.angles, ent->s.angles);
+ VectorCopy(self->monsterinfo.last_sighting, ent->pos1);
+
+ ent->enemy = self->enemy;
+ ent->oldenemy = self->oldenemy;
+}
diff --git a/rogue/src/monster/boss3/boss32.h b/rogue/src/monster/boss3/boss32.h
new file mode 100644
index 0000000..c583b0f
--- /dev/null
+++ b/rogue/src/monster/boss3/boss32.h
@@ -0,0 +1,499 @@
+/* =======================================================================
+ *
+ * Final boss, stage 2 (makron).
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak113 12
+#define FRAME_attak114 13
+#define FRAME_attak115 14
+#define FRAME_attak116 15
+#define FRAME_attak117 16
+#define FRAME_attak118 17
+#define FRAME_attak201 18
+#define FRAME_attak202 19
+#define FRAME_attak203 20
+#define FRAME_attak204 21
+#define FRAME_attak205 22
+#define FRAME_attak206 23
+#define FRAME_attak207 24
+#define FRAME_attak208 25
+#define FRAME_attak209 26
+#define FRAME_attak210 27
+#define FRAME_attak211 28
+#define FRAME_attak212 29
+#define FRAME_attak213 30
+#define FRAME_death01 31
+#define FRAME_death02 32
+#define FRAME_death03 33
+#define FRAME_death04 34
+#define FRAME_death05 35
+#define FRAME_death06 36
+#define FRAME_death07 37
+#define FRAME_death08 38
+#define FRAME_death09 39
+#define FRAME_death10 40
+#define FRAME_death11 41
+#define FRAME_death12 42
+#define FRAME_death13 43
+#define FRAME_death14 44
+#define FRAME_death15 45
+#define FRAME_death16 46
+#define FRAME_death17 47
+#define FRAME_death18 48
+#define FRAME_death19 49
+#define FRAME_death20 50
+#define FRAME_death21 51
+#define FRAME_death22 52
+#define FRAME_death23 53
+#define FRAME_death24 54
+#define FRAME_death25 55
+#define FRAME_death26 56
+#define FRAME_death27 57
+#define FRAME_death28 58
+#define FRAME_death29 59
+#define FRAME_death30 60
+#define FRAME_death31 61
+#define FRAME_death32 62
+#define FRAME_death33 63
+#define FRAME_death34 64
+#define FRAME_death35 65
+#define FRAME_death36 66
+#define FRAME_death37 67
+#define FRAME_death38 68
+#define FRAME_death39 69
+#define FRAME_death40 70
+#define FRAME_death41 71
+#define FRAME_death42 72
+#define FRAME_death43 73
+#define FRAME_death44 74
+#define FRAME_death45 75
+#define FRAME_death46 76
+#define FRAME_death47 77
+#define FRAME_death48 78
+#define FRAME_death49 79
+#define FRAME_death50 80
+#define FRAME_pain101 81
+#define FRAME_pain102 82
+#define FRAME_pain103 83
+#define FRAME_pain201 84
+#define FRAME_pain202 85
+#define FRAME_pain203 86
+#define FRAME_pain301 87
+#define FRAME_pain302 88
+#define FRAME_pain303 89
+#define FRAME_pain304 90
+#define FRAME_pain305 91
+#define FRAME_pain306 92
+#define FRAME_pain307 93
+#define FRAME_pain308 94
+#define FRAME_pain309 95
+#define FRAME_pain310 96
+#define FRAME_pain311 97
+#define FRAME_pain312 98
+#define FRAME_pain313 99
+#define FRAME_pain314 100
+#define FRAME_pain315 101
+#define FRAME_pain316 102
+#define FRAME_pain317 103
+#define FRAME_pain318 104
+#define FRAME_pain319 105
+#define FRAME_pain320 106
+#define FRAME_pain321 107
+#define FRAME_pain322 108
+#define FRAME_pain323 109
+#define FRAME_pain324 110
+#define FRAME_pain325 111
+#define FRAME_stand01 112
+#define FRAME_stand02 113
+#define FRAME_stand03 114
+#define FRAME_stand04 115
+#define FRAME_stand05 116
+#define FRAME_stand06 117
+#define FRAME_stand07 118
+#define FRAME_stand08 119
+#define FRAME_stand09 120
+#define FRAME_stand10 121
+#define FRAME_stand11 122
+#define FRAME_stand12 123
+#define FRAME_stand13 124
+#define FRAME_stand14 125
+#define FRAME_stand15 126
+#define FRAME_stand16 127
+#define FRAME_stand17 128
+#define FRAME_stand18 129
+#define FRAME_stand19 130
+#define FRAME_stand20 131
+#define FRAME_stand21 132
+#define FRAME_stand22 133
+#define FRAME_stand23 134
+#define FRAME_stand24 135
+#define FRAME_stand25 136
+#define FRAME_stand26 137
+#define FRAME_stand27 138
+#define FRAME_stand28 139
+#define FRAME_stand29 140
+#define FRAME_stand30 141
+#define FRAME_stand31 142
+#define FRAME_stand32 143
+#define FRAME_stand33 144
+#define FRAME_stand34 145
+#define FRAME_stand35 146
+#define FRAME_stand36 147
+#define FRAME_stand37 148
+#define FRAME_stand38 149
+#define FRAME_stand39 150
+#define FRAME_stand40 151
+#define FRAME_stand41 152
+#define FRAME_stand42 153
+#define FRAME_stand43 154
+#define FRAME_stand44 155
+#define FRAME_stand45 156
+#define FRAME_stand46 157
+#define FRAME_stand47 158
+#define FRAME_stand48 159
+#define FRAME_stand49 160
+#define FRAME_stand50 161
+#define FRAME_stand51 162
+#define FRAME_walk01 163
+#define FRAME_walk02 164
+#define FRAME_walk03 165
+#define FRAME_walk04 166
+#define FRAME_walk05 167
+#define FRAME_walk06 168
+#define FRAME_walk07 169
+#define FRAME_walk08 170
+#define FRAME_walk09 171
+#define FRAME_walk10 172
+#define FRAME_walk11 173
+#define FRAME_walk12 174
+#define FRAME_walk13 175
+#define FRAME_walk14 176
+#define FRAME_walk15 177
+#define FRAME_walk16 178
+#define FRAME_walk17 179
+#define FRAME_walk18 180
+#define FRAME_walk19 181
+#define FRAME_walk20 182
+#define FRAME_walk21 183
+#define FRAME_walk22 184
+#define FRAME_walk23 185
+#define FRAME_walk24 186
+#define FRAME_walk25 187
+#define FRAME_active01 188
+#define FRAME_active02 189
+#define FRAME_active03 190
+#define FRAME_active04 191
+#define FRAME_active05 192
+#define FRAME_active06 193
+#define FRAME_active07 194
+#define FRAME_active08 195
+#define FRAME_active09 196
+#define FRAME_active10 197
+#define FRAME_active11 198
+#define FRAME_active12 199
+#define FRAME_active13 200
+#define FRAME_attak301 201
+#define FRAME_attak302 202
+#define FRAME_attak303 203
+#define FRAME_attak304 204
+#define FRAME_attak305 205
+#define FRAME_attak306 206
+#define FRAME_attak307 207
+#define FRAME_attak308 208
+#define FRAME_attak401 209
+#define FRAME_attak402 210
+#define FRAME_attak403 211
+#define FRAME_attak404 212
+#define FRAME_attak405 213
+#define FRAME_attak406 214
+#define FRAME_attak407 215
+#define FRAME_attak408 216
+#define FRAME_attak409 217
+#define FRAME_attak410 218
+#define FRAME_attak411 219
+#define FRAME_attak412 220
+#define FRAME_attak413 221
+#define FRAME_attak414 222
+#define FRAME_attak415 223
+#define FRAME_attak416 224
+#define FRAME_attak417 225
+#define FRAME_attak418 226
+#define FRAME_attak419 227
+#define FRAME_attak420 228
+#define FRAME_attak421 229
+#define FRAME_attak422 230
+#define FRAME_attak423 231
+#define FRAME_attak424 232
+#define FRAME_attak425 233
+#define FRAME_attak426 234
+#define FRAME_attak501 235
+#define FRAME_attak502 236
+#define FRAME_attak503 237
+#define FRAME_attak504 238
+#define FRAME_attak505 239
+#define FRAME_attak506 240
+#define FRAME_attak507 241
+#define FRAME_attak508 242
+#define FRAME_attak509 243
+#define FRAME_attak510 244
+#define FRAME_attak511 245
+#define FRAME_attak512 246
+#define FRAME_attak513 247
+#define FRAME_attak514 248
+#define FRAME_attak515 249
+#define FRAME_attak516 250
+#define FRAME_death201 251
+#define FRAME_death202 252
+#define FRAME_death203 253
+#define FRAME_death204 254
+#define FRAME_death205 255
+#define FRAME_death206 256
+#define FRAME_death207 257
+#define FRAME_death208 258
+#define FRAME_death209 259
+#define FRAME_death210 260
+#define FRAME_death211 261
+#define FRAME_death212 262
+#define FRAME_death213 263
+#define FRAME_death214 264
+#define FRAME_death215 265
+#define FRAME_death216 266
+#define FRAME_death217 267
+#define FRAME_death218 268
+#define FRAME_death219 269
+#define FRAME_death220 270
+#define FRAME_death221 271
+#define FRAME_death222 272
+#define FRAME_death223 273
+#define FRAME_death224 274
+#define FRAME_death225 275
+#define FRAME_death226 276
+#define FRAME_death227 277
+#define FRAME_death228 278
+#define FRAME_death229 279
+#define FRAME_death230 280
+#define FRAME_death231 281
+#define FRAME_death232 282
+#define FRAME_death233 283
+#define FRAME_death234 284
+#define FRAME_death235 285
+#define FRAME_death236 286
+#define FRAME_death237 287
+#define FRAME_death238 288
+#define FRAME_death239 289
+#define FRAME_death240 290
+#define FRAME_death241 291
+#define FRAME_death242 292
+#define FRAME_death243 293
+#define FRAME_death244 294
+#define FRAME_death245 295
+#define FRAME_death246 296
+#define FRAME_death247 297
+#define FRAME_death248 298
+#define FRAME_death249 299
+#define FRAME_death250 300
+#define FRAME_death251 301
+#define FRAME_death252 302
+#define FRAME_death253 303
+#define FRAME_death254 304
+#define FRAME_death255 305
+#define FRAME_death256 306
+#define FRAME_death257 307
+#define FRAME_death258 308
+#define FRAME_death259 309
+#define FRAME_death260 310
+#define FRAME_death261 311
+#define FRAME_death262 312
+#define FRAME_death263 313
+#define FRAME_death264 314
+#define FRAME_death265 315
+#define FRAME_death266 316
+#define FRAME_death267 317
+#define FRAME_death268 318
+#define FRAME_death269 319
+#define FRAME_death270 320
+#define FRAME_death271 321
+#define FRAME_death272 322
+#define FRAME_death273 323
+#define FRAME_death274 324
+#define FRAME_death275 325
+#define FRAME_death276 326
+#define FRAME_death277 327
+#define FRAME_death278 328
+#define FRAME_death279 329
+#define FRAME_death280 330
+#define FRAME_death281 331
+#define FRAME_death282 332
+#define FRAME_death283 333
+#define FRAME_death284 334
+#define FRAME_death285 335
+#define FRAME_death286 336
+#define FRAME_death287 337
+#define FRAME_death288 338
+#define FRAME_death289 339
+#define FRAME_death290 340
+#define FRAME_death291 341
+#define FRAME_death292 342
+#define FRAME_death293 343
+#define FRAME_death294 344
+#define FRAME_death295 345
+#define FRAME_death301 346
+#define FRAME_death302 347
+#define FRAME_death303 348
+#define FRAME_death304 349
+#define FRAME_death305 350
+#define FRAME_death306 351
+#define FRAME_death307 352
+#define FRAME_death308 353
+#define FRAME_death309 354
+#define FRAME_death310 355
+#define FRAME_death311 356
+#define FRAME_death312 357
+#define FRAME_death313 358
+#define FRAME_death314 359
+#define FRAME_death315 360
+#define FRAME_death316 361
+#define FRAME_death317 362
+#define FRAME_death318 363
+#define FRAME_death319 364
+#define FRAME_death320 365
+#define FRAME_jump01 366
+#define FRAME_jump02 367
+#define FRAME_jump03 368
+#define FRAME_jump04 369
+#define FRAME_jump05 370
+#define FRAME_jump06 371
+#define FRAME_jump07 372
+#define FRAME_jump08 373
+#define FRAME_jump09 374
+#define FRAME_jump10 375
+#define FRAME_jump11 376
+#define FRAME_jump12 377
+#define FRAME_jump13 378
+#define FRAME_pain401 379
+#define FRAME_pain402 380
+#define FRAME_pain403 381
+#define FRAME_pain404 382
+#define FRAME_pain501 383
+#define FRAME_pain502 384
+#define FRAME_pain503 385
+#define FRAME_pain504 386
+#define FRAME_pain601 387
+#define FRAME_pain602 388
+#define FRAME_pain603 389
+#define FRAME_pain604 390
+#define FRAME_pain605 391
+#define FRAME_pain606 392
+#define FRAME_pain607 393
+#define FRAME_pain608 394
+#define FRAME_pain609 395
+#define FRAME_pain610 396
+#define FRAME_pain611 397
+#define FRAME_pain612 398
+#define FRAME_pain613 399
+#define FRAME_pain614 400
+#define FRAME_pain615 401
+#define FRAME_pain616 402
+#define FRAME_pain617 403
+#define FRAME_pain618 404
+#define FRAME_pain619 405
+#define FRAME_pain620 406
+#define FRAME_pain621 407
+#define FRAME_pain622 408
+#define FRAME_pain623 409
+#define FRAME_pain624 410
+#define FRAME_pain625 411
+#define FRAME_pain626 412
+#define FRAME_pain627 413
+#define FRAME_stand201 414
+#define FRAME_stand202 415
+#define FRAME_stand203 416
+#define FRAME_stand204 417
+#define FRAME_stand205 418
+#define FRAME_stand206 419
+#define FRAME_stand207 420
+#define FRAME_stand208 421
+#define FRAME_stand209 422
+#define FRAME_stand210 423
+#define FRAME_stand211 424
+#define FRAME_stand212 425
+#define FRAME_stand213 426
+#define FRAME_stand214 427
+#define FRAME_stand215 428
+#define FRAME_stand216 429
+#define FRAME_stand217 430
+#define FRAME_stand218 431
+#define FRAME_stand219 432
+#define FRAME_stand220 433
+#define FRAME_stand221 434
+#define FRAME_stand222 435
+#define FRAME_stand223 436
+#define FRAME_stand224 437
+#define FRAME_stand225 438
+#define FRAME_stand226 439
+#define FRAME_stand227 440
+#define FRAME_stand228 441
+#define FRAME_stand229 442
+#define FRAME_stand230 443
+#define FRAME_stand231 444
+#define FRAME_stand232 445
+#define FRAME_stand233 446
+#define FRAME_stand234 447
+#define FRAME_stand235 448
+#define FRAME_stand236 449
+#define FRAME_stand237 450
+#define FRAME_stand238 451
+#define FRAME_stand239 452
+#define FRAME_stand240 453
+#define FRAME_stand241 454
+#define FRAME_stand242 455
+#define FRAME_stand243 456
+#define FRAME_stand244 457
+#define FRAME_stand245 458
+#define FRAME_stand246 459
+#define FRAME_stand247 460
+#define FRAME_stand248 461
+#define FRAME_stand249 462
+#define FRAME_stand250 463
+#define FRAME_stand251 464
+#define FRAME_stand252 465
+#define FRAME_stand253 466
+#define FRAME_stand254 467
+#define FRAME_stand255 468
+#define FRAME_stand256 469
+#define FRAME_stand257 470
+#define FRAME_stand258 471
+#define FRAME_stand259 472
+#define FRAME_stand260 473
+#define FRAME_walk201 474
+#define FRAME_walk202 475
+#define FRAME_walk203 476
+#define FRAME_walk204 477
+#define FRAME_walk205 478
+#define FRAME_walk206 479
+#define FRAME_walk207 480
+#define FRAME_walk208 481
+#define FRAME_walk209 482
+#define FRAME_walk210 483
+#define FRAME_walk211 484
+#define FRAME_walk212 485
+#define FRAME_walk213 486
+#define FRAME_walk214 487
+#define FRAME_walk215 488
+#define FRAME_walk216 489
+#define FRAME_walk217 490
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/brain/brain.c b/rogue/src/monster/brain/brain.c
new file mode 100644
index 0000000..f5545c1
--- /dev/null
+++ b/rogue/src/monster/brain/brain.c
@@ -0,0 +1,776 @@
+/* =======================================================================
+ *
+ * Brain.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "brain.h"
+
+static int sound_chest_open;
+static int sound_tentacles_extend;
+static int sound_tentacles_retract;
+static int sound_death;
+static int sound_idle1;
+static int sound_idle2;
+static int sound_idle3;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_sight;
+static int sound_search;
+static int sound_melee1;
+static int sound_melee2;
+static int sound_melee3;
+
+void brain_run(edict_t *self);
+void brain_dead(edict_t *self);
+
+void
+brain_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+brain_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+/* STAND */
+mframe_t brain_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t brain_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ brain_frames_stand,
+ NULL
+};
+
+void
+brain_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &brain_move_stand;
+}
+
+/* IDLE */
+mframe_t brain_frames_idle[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t brain_move_idle = {
+ FRAME_stand31,
+ FRAME_stand60,
+ brain_frames_idle,
+ brain_stand
+};
+
+void
+brain_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0);
+ self->monsterinfo.currentmove = &brain_move_idle;
+}
+
+/* WALK */
+mframe_t brain_frames_walk1[] = {
+ {ai_walk, 7, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, -4, NULL},
+ {ai_walk, -1, NULL},
+ {ai_walk, 2, NULL}
+};
+
+mmove_t brain_move_walk1 = {
+ FRAME_walk101,
+ FRAME_walk111,
+ brain_frames_walk1,
+ NULL
+};
+
+void
+brain_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &brain_move_walk1;
+}
+
+mframe_t brain_frames_defense[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t brain_move_defense = {
+ FRAME_defens01,
+ FRAME_defens08,
+ brain_frames_defense,
+ NULL
+};
+
+mframe_t brain_frames_pain3[] = {
+ {ai_move, -2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -4, NULL}
+};
+
+mmove_t brain_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain306,
+ brain_frames_pain3,
+ brain_run
+};
+
+mframe_t brain_frames_pain2[] = {
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -2, NULL}
+};
+
+mmove_t brain_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain208,
+ brain_frames_pain2,
+ brain_run
+};
+
+mframe_t brain_frames_pain1[] = {
+ {ai_move, -6, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -1, NULL}
+};
+
+mmove_t brain_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain121,
+ brain_frames_pain1,
+ brain_run
+};
+
+mframe_t brain_frames_duck[] = {
+ {ai_move, 0, NULL},
+ {ai_move, -2, monster_duck_down},
+ {ai_move, 17, monster_duck_hold},
+ {ai_move, -3, NULL},
+ {ai_move, -1, monster_duck_up},
+ {ai_move, -5, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -6, NULL}
+};
+
+mmove_t brain_move_duck = {
+ FRAME_duck01,
+ FRAME_duck08,
+ brain_frames_duck,
+ brain_run
+};
+
+mframe_t brain_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t brain_move_death2 = {
+ FRAME_death201,
+ FRAME_death205,
+ brain_frames_death2,
+ brain_dead
+};
+
+mframe_t brain_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t brain_move_death1 = {
+ FRAME_death101,
+ FRAME_death118,
+ brain_frames_death1,
+ brain_dead
+};
+
+/* MELEE */
+void
+brain_swing_right(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0);
+}
+
+void
+brain_hit_right(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8);
+
+ if (fire_hit(self, aim, (15 + (rand() % 5)), 40))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+brain_swing_left(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0);
+}
+
+void
+brain_hit_left(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ vec3_t aim;
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8);
+
+ if (fire_hit(self, aim, (15 + (rand() % 5)), 40))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t brain_frames_attack1[] = {
+ {ai_charge, 8, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -3, brain_swing_right},
+ {ai_charge, 0, NULL},
+ {ai_charge, -5, NULL},
+ {ai_charge, -7, brain_hit_right},
+ {ai_charge, 0, NULL},
+ {ai_charge, 6, brain_swing_left},
+ {ai_charge, 1, NULL},
+ {ai_charge, 2, brain_hit_left},
+ {ai_charge, -3, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, -11, NULL}
+};
+
+mmove_t brain_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak118,
+ brain_frames_attack1,
+ brain_run
+};
+
+void
+brain_chest_open(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->spawnflags &= ~65536;
+ self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+ gi.sound(self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0);
+}
+
+void
+brain_tentacle_attack(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 8);
+
+ if (fire_hit(self, aim, (10 + (rand() % 5)), -600) && (skill->value > 0))
+ {
+ self->spawnflags |= 65536;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+}
+
+void
+brain_chest_closed(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+
+ if (self->spawnflags & 65536)
+ {
+ self->spawnflags &= ~65536;
+ self->monsterinfo.currentmove = &brain_move_attack1;
+ }
+}
+
+mframe_t brain_frames_attack2[] = {
+ {ai_charge, 5, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 0, brain_chest_open},
+ {ai_charge, 0, NULL},
+ {ai_charge, 13, brain_tentacle_attack},
+ {ai_charge, 0, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -9, brain_chest_closed},
+ {ai_charge, 0, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, -6, NULL}
+};
+
+mmove_t brain_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak217,
+ brain_frames_attack2,
+ brain_run
+};
+
+void
+brain_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &brain_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &brain_move_attack2;
+ }
+}
+
+/* RUN */
+mframe_t brain_frames_run[] = {
+ {ai_run, 9, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 1, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, -4, NULL},
+ {ai_run, -1, NULL},
+ {ai_run, 2, NULL}
+};
+
+mmove_t brain_move_run = {
+ FRAME_walk101,
+ FRAME_walk111,
+ brain_frames_run,
+ NULL
+};
+
+void
+brain_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &brain_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &brain_move_run;
+ }
+}
+
+void
+brain_pain(edict_t *self, edict_t *other /* unused */, float kick /* unused */,
+ int damage /* unused */)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &brain_move_pain1;
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &brain_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &brain_move_pain3;
+ }
+
+ /* clear duck flag */
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+}
+
+void
+brain_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+brain_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.effects = 0;
+ self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &brain_move_death1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &brain_move_death2;
+ }
+}
+
+void
+brain_duck(edict_t *self, float eta)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* has to be done immediately otherwise he can get stuck */
+ monster_duck_down(self);
+
+ if (skill->value == SKILL_EASY)
+ {
+ /* PMM - stupid dodge */
+ self->monsterinfo.duck_wait_time = level.time + eta + 1;
+ }
+ else
+ {
+ self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+ }
+
+ self->monsterinfo.currentmove = &brain_move_duck;
+ self->monsterinfo.nextframe = FRAME_duck01;
+ return;
+}
+
+/*
+ * QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_brain(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_chest_open = gi.soundindex("brain/brnatck1.wav");
+ sound_tentacles_extend = gi.soundindex("brain/brnatck2.wav");
+ sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav");
+ sound_death = gi.soundindex("brain/brndeth1.wav");
+ sound_idle1 = gi.soundindex("brain/brnidle1.wav");
+ sound_idle2 = gi.soundindex("brain/brnidle2.wav");
+ sound_idle3 = gi.soundindex("brain/brnlens1.wav");
+ sound_pain1 = gi.soundindex("brain/brnpain1.wav");
+ sound_pain2 = gi.soundindex("brain/brnpain2.wav");
+ sound_sight = gi.soundindex("brain/brnsght1.wav");
+ sound_search = gi.soundindex("brain/brnsrch1.wav");
+ sound_melee1 = gi.soundindex("brain/melee1.wav");
+ sound_melee2 = gi.soundindex("brain/melee2.wav");
+ sound_melee3 = gi.soundindex("brain/melee3.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/brain/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 300;
+ self->gib_health = -150;
+ self->mass = 400;
+
+ self->pain = brain_pain;
+ self->die = brain_die;
+
+ self->monsterinfo.stand = brain_stand;
+ self->monsterinfo.walk = brain_walk;
+ self->monsterinfo.run = brain_run;
+ self->monsterinfo.dodge = M_MonsterDodge;
+ self->monsterinfo.duck = brain_duck;
+ self->monsterinfo.unduck = monster_duck_up;
+ self->monsterinfo.melee = brain_melee;
+ self->monsterinfo.sight = brain_sight;
+ self->monsterinfo.search = brain_search;
+ self->monsterinfo.idle = brain_idle;
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+ self->monsterinfo.power_armor_power = 100;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &brain_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/brain/brain.h b/rogue/src/monster/brain/brain.h
new file mode 100644
index 0000000..fde2e98
--- /dev/null
+++ b/rogue/src/monster/brain/brain.h
@@ -0,0 +1,231 @@
+/* =======================================================================
+ *
+ * Brain animations.
+ *
+ * =======================================================================
+ *
+ */
+
+#define FRAME_walk101 0
+#define FRAME_walk102 1
+#define FRAME_walk103 2
+#define FRAME_walk104 3
+#define FRAME_walk105 4
+#define FRAME_walk106 5
+#define FRAME_walk107 6
+#define FRAME_walk108 7
+#define FRAME_walk109 8
+#define FRAME_walk110 9
+#define FRAME_walk111 10
+#define FRAME_walk112 11
+#define FRAME_walk113 12
+#define FRAME_walk201 13
+#define FRAME_walk202 14
+#define FRAME_walk203 15
+#define FRAME_walk204 16
+#define FRAME_walk205 17
+#define FRAME_walk206 18
+#define FRAME_walk207 19
+#define FRAME_walk208 20
+#define FRAME_walk209 21
+#define FRAME_walk210 22
+#define FRAME_walk211 23
+#define FRAME_walk212 24
+#define FRAME_walk213 25
+#define FRAME_walk214 26
+#define FRAME_walk215 27
+#define FRAME_walk216 28
+#define FRAME_walk217 29
+#define FRAME_walk218 30
+#define FRAME_walk219 31
+#define FRAME_walk220 32
+#define FRAME_walk221 33
+#define FRAME_walk222 34
+#define FRAME_walk223 35
+#define FRAME_walk224 36
+#define FRAME_walk225 37
+#define FRAME_walk226 38
+#define FRAME_walk227 39
+#define FRAME_walk228 40
+#define FRAME_walk229 41
+#define FRAME_walk230 42
+#define FRAME_walk231 43
+#define FRAME_walk232 44
+#define FRAME_walk233 45
+#define FRAME_walk234 46
+#define FRAME_walk235 47
+#define FRAME_walk236 48
+#define FRAME_walk237 49
+#define FRAME_walk238 50
+#define FRAME_walk239 51
+#define FRAME_walk240 52
+#define FRAME_attak101 53
+#define FRAME_attak102 54
+#define FRAME_attak103 55
+#define FRAME_attak104 56
+#define FRAME_attak105 57
+#define FRAME_attak106 58
+#define FRAME_attak107 59
+#define FRAME_attak108 60
+#define FRAME_attak109 61
+#define FRAME_attak110 62
+#define FRAME_attak111 63
+#define FRAME_attak112 64
+#define FRAME_attak113 65
+#define FRAME_attak114 66
+#define FRAME_attak115 67
+#define FRAME_attak116 68
+#define FRAME_attak117 69
+#define FRAME_attak118 70
+#define FRAME_attak201 71
+#define FRAME_attak202 72
+#define FRAME_attak203 73
+#define FRAME_attak204 74
+#define FRAME_attak205 75
+#define FRAME_attak206 76
+#define FRAME_attak207 77
+#define FRAME_attak208 78
+#define FRAME_attak209 79
+#define FRAME_attak210 80
+#define FRAME_attak211 81
+#define FRAME_attak212 82
+#define FRAME_attak213 83
+#define FRAME_attak214 84
+#define FRAME_attak215 85
+#define FRAME_attak216 86
+#define FRAME_attak217 87
+#define FRAME_pain101 88
+#define FRAME_pain102 89
+#define FRAME_pain103 90
+#define FRAME_pain104 91
+#define FRAME_pain105 92
+#define FRAME_pain106 93
+#define FRAME_pain107 94
+#define FRAME_pain108 95
+#define FRAME_pain109 96
+#define FRAME_pain110 97
+#define FRAME_pain111 98
+#define FRAME_pain112 99
+#define FRAME_pain113 100
+#define FRAME_pain114 101
+#define FRAME_pain115 102
+#define FRAME_pain116 103
+#define FRAME_pain117 104
+#define FRAME_pain118 105
+#define FRAME_pain119 106
+#define FRAME_pain120 107
+#define FRAME_pain121 108
+#define FRAME_pain201 109
+#define FRAME_pain202 110
+#define FRAME_pain203 111
+#define FRAME_pain204 112
+#define FRAME_pain205 113
+#define FRAME_pain206 114
+#define FRAME_pain207 115
+#define FRAME_pain208 116
+#define FRAME_pain301 117
+#define FRAME_pain302 118
+#define FRAME_pain303 119
+#define FRAME_pain304 120
+#define FRAME_pain305 121
+#define FRAME_pain306 122
+#define FRAME_death101 123
+#define FRAME_death102 124
+#define FRAME_death103 125
+#define FRAME_death104 126
+#define FRAME_death105 127
+#define FRAME_death106 128
+#define FRAME_death107 129
+#define FRAME_death108 130
+#define FRAME_death109 131
+#define FRAME_death110 132
+#define FRAME_death111 133
+#define FRAME_death112 134
+#define FRAME_death113 135
+#define FRAME_death114 136
+#define FRAME_death115 137
+#define FRAME_death116 138
+#define FRAME_death117 139
+#define FRAME_death118 140
+#define FRAME_death201 141
+#define FRAME_death202 142
+#define FRAME_death203 143
+#define FRAME_death204 144
+#define FRAME_death205 145
+#define FRAME_duck01 146
+#define FRAME_duck02 147
+#define FRAME_duck03 148
+#define FRAME_duck04 149
+#define FRAME_duck05 150
+#define FRAME_duck06 151
+#define FRAME_duck07 152
+#define FRAME_duck08 153
+#define FRAME_defens01 154
+#define FRAME_defens02 155
+#define FRAME_defens03 156
+#define FRAME_defens04 157
+#define FRAME_defens05 158
+#define FRAME_defens06 159
+#define FRAME_defens07 160
+#define FRAME_defens08 161
+#define FRAME_stand01 162
+#define FRAME_stand02 163
+#define FRAME_stand03 164
+#define FRAME_stand04 165
+#define FRAME_stand05 166
+#define FRAME_stand06 167
+#define FRAME_stand07 168
+#define FRAME_stand08 169
+#define FRAME_stand09 170
+#define FRAME_stand10 171
+#define FRAME_stand11 172
+#define FRAME_stand12 173
+#define FRAME_stand13 174
+#define FRAME_stand14 175
+#define FRAME_stand15 176
+#define FRAME_stand16 177
+#define FRAME_stand17 178
+#define FRAME_stand18 179
+#define FRAME_stand19 180
+#define FRAME_stand20 181
+#define FRAME_stand21 182
+#define FRAME_stand22 183
+#define FRAME_stand23 184
+#define FRAME_stand24 185
+#define FRAME_stand25 186
+#define FRAME_stand26 187
+#define FRAME_stand27 188
+#define FRAME_stand28 189
+#define FRAME_stand29 190
+#define FRAME_stand30 191
+#define FRAME_stand31 192
+#define FRAME_stand32 193
+#define FRAME_stand33 194
+#define FRAME_stand34 195
+#define FRAME_stand35 196
+#define FRAME_stand36 197
+#define FRAME_stand37 198
+#define FRAME_stand38 199
+#define FRAME_stand39 200
+#define FRAME_stand40 201
+#define FRAME_stand41 202
+#define FRAME_stand42 203
+#define FRAME_stand43 204
+#define FRAME_stand44 205
+#define FRAME_stand45 206
+#define FRAME_stand46 207
+#define FRAME_stand47 208
+#define FRAME_stand48 209
+#define FRAME_stand49 210
+#define FRAME_stand50 211
+#define FRAME_stand51 212
+#define FRAME_stand52 213
+#define FRAME_stand53 214
+#define FRAME_stand54 215
+#define FRAME_stand55 216
+#define FRAME_stand56 217
+#define FRAME_stand57 218
+#define FRAME_stand58 219
+#define FRAME_stand59 220
+#define FRAME_stand60 221
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/carrier/carrier.c b/rogue/src/monster/carrier/carrier.c
new file mode 100644
index 0000000..05d4fca
--- /dev/null
+++ b/rogue/src/monster/carrier/carrier.c
@@ -0,0 +1,1590 @@
+/*
+ * ==============================================================================
+ *
+ * Carrier.
+ *
+ * ==============================================================================
+ */
+
+#include "../../header/local.h"
+#include "carrier.h"
+
+#define CARRIER_ROCKET_TIME 2 /* number of seconds between rocket shots */
+#define CARRIER_ROCKET_SPEED 750
+#define NUM_FLYERS_SPAWNED 6 /* max # of flyers he can spawn */
+#define RAIL_FIRE_TIME 3
+
+void BossExplode(edict_t *self);
+void Grenade_Explode(edict_t *ent);
+
+void carrier_run(edict_t *self);
+void carrier_stand(edict_t *self);
+void carrier_dead(edict_t *self);
+void carrier_attack(edict_t *self);
+void carrier_attack_mg(edict_t *self);
+void carrier_reattack_mg(edict_t *self);
+void carrier_die(edict_t *self,
+ edict_t *inflictor,
+ edict_t *attacker,
+ int damage,
+ vec3_t point);
+void carrier_attack_gren(edict_t *self);
+void carrier_reattack_gren(edict_t *self);
+void carrier_start_spawn(edict_t *self);
+void carrier_spawn_check(edict_t *self);
+void carrier_prep_spawn(edict_t *self);
+void CarrierMachineGunHold(edict_t *self);
+void CarrierRocket(edict_t *self);
+
+qboolean infront(edict_t *self, edict_t *other);
+qboolean inback(edict_t *self, edict_t *other);
+qboolean below(edict_t *self, edict_t *other);
+void drawbbox(edict_t *self);
+void ED_CallSpawn(edict_t *ent);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_death;
+static int sound_sight;
+static int sound_rail;
+static int sound_spawn;
+
+float orig_yaw_speed;
+
+vec3_t flyer_mins = {-16, -16, -24};
+vec3_t flyer_maxs = {16, 16, 16};
+
+extern mmove_t flyer_move_attack2, flyer_move_attack3, flyer_move_kamikaze;
+
+void
+carrier_sight(edict_t *self, edict_t *other /* other */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+CarrierCoopCheck(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* no more than 4 players in coop, so.. */
+ edict_t *targets[4];
+ int num_targets = 0, target, player;
+ edict_t *ent;
+ trace_t tr;
+
+ /* if we're not in coop, this is a noop */
+ if (!coop || !coop->value)
+ {
+ return;
+ }
+
+ /* if we are, and we have recently fired, bail */
+ if (self->wait > level.time)
+ {
+ return;
+ }
+
+ memset(targets, 0, 4 * sizeof(edict_t *));
+
+ /* cycle through players */
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (inback(self, ent) || below(self, ent))
+ {
+ tr = gi.trace(self->s.origin, NULL, NULL, ent->s.origin,
+ self, MASK_SOLID);
+
+ if (tr.fraction == 1.0)
+ {
+ targets[num_targets++] = ent;
+ }
+ }
+ }
+
+ if (!num_targets)
+ {
+ return;
+ }
+
+ /* get a number from 0 to (num_targets-1) */
+ target = random() * num_targets;
+
+ /* just in case we got a 1.0 from random */
+ if (target == num_targets)
+ {
+ target--;
+ }
+
+ /* make sure to prevent rapid fire rockets */
+ self->wait = level.time + CARRIER_ROCKET_TIME;
+
+ /* save off the real enemy */
+ ent = self->enemy;
+ /* set the new guy as temporary enemy */
+ self->enemy = targets[target];
+ CarrierRocket(self);
+ /* put the real enemy back */
+ self->enemy = ent;
+
+ /* we're done */
+ return;
+}
+
+void
+CarrierGrenade(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t aim;
+ int flash_number;
+ float direction; /* from lower left to upper right, or lower right to upper left */
+ float spreadR, spreadU;
+ int mytime;
+
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ direction = -1.0;
+ }
+ else
+ {
+ direction = 1.0;
+ }
+
+ mytime = (int)((level.time - self->timestamp) / 0.4);
+
+ if (mytime == 0)
+ {
+ spreadR = 0.15 * direction;
+ spreadU = 0.1 - 0.1 * direction;
+ }
+ else if (mytime == 1)
+ {
+ spreadR = 0;
+ spreadU = 0.1;
+ }
+ else if (mytime == 2)
+ {
+ spreadR = -0.15 * direction;
+ spreadU = 0.1 - -0.1 * direction;
+ }
+ else if (mytime == 3)
+ {
+ spreadR = 0;
+ spreadU = 0.1;
+ }
+ else
+ {
+ /* error, shoot straight */
+ spreadR = 0;
+ spreadU = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, up);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_GRENADE],
+ forward, right, start);
+
+ VectorSubtract(self->enemy->s.origin, start, aim);
+ VectorNormalize(aim);
+
+ VectorMA(aim, spreadR, right, aim);
+ VectorMA(aim, spreadU, up, aim);
+
+ if (aim[2] > 0.15)
+ {
+ aim[2] = 0.15;
+ }
+ else if (aim[2] < -0.5)
+ {
+ aim[2] = -0.5;
+ }
+
+ flash_number = MZ2_GUNNER_GRENADE_1;
+ monster_fire_grenade(self, start, aim, 50, 600, flash_number);
+}
+
+void
+CarrierPredictiveRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+
+ /* 1 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1],
+ forward, right, start);
+ PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.3, dir, NULL);
+ monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_1);
+
+ /* 2 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2],
+ forward, right, start);
+ PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.15, dir, NULL);
+ monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_2);
+
+ /* 3 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward,
+ right, start);
+ PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, 0, dir, NULL);
+ monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_3);
+
+ /* 4 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward,
+ right, start);
+ PredictAim(self->enemy, start, CARRIER_ROCKET_SPEED, false, 0.15, dir, NULL);
+ monster_fire_rocket(self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_4);
+}
+
+void
+CarrierRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ if (self->enemy->client && (random() < 0.5))
+ {
+ CarrierPredictiveRocket(self);
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+
+ /* 1 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] -= 15;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, 0.4, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_1);
+
+ /* 2 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, 0.025, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_2);
+
+ /* 3 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, -0.025, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_3);
+
+ /* 4 */
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] -= 15;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ VectorMA(dir, -0.4, right, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_4);
+}
+
+void
+carrier_firebullet_right(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+ int flashnum;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we're in manual steering mode, it means we're leaning down .. use the lower shot */
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ flashnum = MZ2_CARRIER_MACHINEGUN_R2;
+ }
+ else
+ {
+ flashnum = MZ2_CARRIER_MACHINEGUN_R1;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+
+ VectorMA(self->enemy->s.origin, 0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3,
+ DEFAULT_BULLET_VSPREAD, flashnum);
+}
+
+void
+carrier_firebullet_left(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+ int flashnum;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we're in manual steering mode, it means we're leaning down .. use the lower shot */
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ flashnum = MZ2_CARRIER_MACHINEGUN_L2;
+ }
+ else
+ {
+ flashnum = MZ2_CARRIER_MACHINEGUN_L1;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flashnum],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3,
+ DEFAULT_BULLET_VSPREAD, flashnum);
+}
+
+void
+CarrierMachineGun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+
+ if (self->enemy)
+ {
+ carrier_firebullet_left(self);
+ }
+
+ if (self->enemy)
+ {
+ carrier_firebullet_right(self);
+ }
+}
+
+void
+CarrierSpawn(edict_t *self)
+{
+ vec3_t f, r, offset, startpoint, spawnpoint;
+ edict_t *ent;
+ int mytime;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(offset, 105, 0, -58);
+ AngleVectors(self->s.angles, f, r, NULL);
+
+ G_ProjectSource(self->s.origin, offset, f, r, startpoint);
+
+ /* the +0.1 is because level.time is sometimes a little low */
+ mytime = (int)((level.time + 0.1 - self->timestamp) / 0.5);
+
+ if (FindSpawnPoint(startpoint, flyer_mins, flyer_maxs, spawnpoint, 32))
+ {
+ /* the second flier should be a kamikaze flyer */
+ if (mytime != 2)
+ {
+ ent = CreateMonster(spawnpoint, self->s.angles, "monster_flyer");
+ }
+ else
+ {
+ ent = CreateMonster(spawnpoint, self->s.angles, "monster_kamikaze");
+ }
+
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_spawn, 1, ATTN_NONE, 0);
+
+ self->monsterinfo.monster_slots--;
+
+ ent->nextthink = level.time;
+ ent->think(ent);
+
+ ent->monsterinfo.aiflags |= AI_SPAWNED_CARRIER | AI_DO_NOT_COUNT |
+ AI_IGNORE_SHOTS;
+ ent->monsterinfo.commander = self;
+
+ if ((self->enemy->inuse) && (self->enemy->health > 0))
+ {
+ ent->enemy = self->enemy;
+ FoundTarget(ent);
+
+ if (mytime == 1)
+ {
+ ent->monsterinfo.lefty = 0;
+ ent->monsterinfo.attack_state = AS_SLIDING;
+ ent->monsterinfo.currentmove = &flyer_move_attack3;
+ }
+ else if (mytime == 2)
+ {
+ ent->monsterinfo.lefty = 0;
+ ent->monsterinfo.attack_state = AS_STRAIGHT;
+ ent->monsterinfo.currentmove = &flyer_move_kamikaze;
+ ent->mass = 100;
+ ent->monsterinfo.aiflags |= AI_CHARGING;
+ }
+ else if (mytime == 3)
+ {
+ ent->monsterinfo.lefty = 1;
+ ent->monsterinfo.attack_state = AS_SLIDING;
+ ent->monsterinfo.currentmove = &flyer_move_attack3;
+ }
+ }
+ }
+}
+
+void
+carrier_prep_spawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+ self->timestamp = level.time;
+ self->yaw_speed = 10;
+ CarrierMachineGun(self);
+}
+
+void
+carrier_spawn_check(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+ CarrierMachineGun(self);
+ CarrierSpawn(self);
+
+ if (level.time > (self->timestamp + 1.1)) /* 0.5 seconds per flyer. this gets three */
+ {
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->yaw_speed = orig_yaw_speed;
+ return;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_spawn08;
+ }
+}
+
+void
+carrier_ready_spawn(edict_t *self)
+{
+ float current_yaw;
+ vec3_t offset, f, r, startpoint, spawnpoint;
+
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+ CarrierMachineGun(self);
+
+ current_yaw = anglemod(self->s.angles[YAW]);
+
+ if (fabs(current_yaw - self->ideal_yaw) > 0.1)
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ self->timestamp += FRAMETIME;
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+ VectorSet(offset, 105, 0, -58);
+ AngleVectors(self->s.angles, f, r, NULL);
+ G_ProjectSource(self->s.origin, offset, f, r, startpoint);
+
+ if (FindSpawnPoint(startpoint, flyer_mins, flyer_maxs, spawnpoint, 32))
+ {
+ SpawnGrow_Spawn(spawnpoint, 0);
+ }
+}
+
+void
+carrier_start_spawn(edict_t *self)
+{
+ int mytime;
+ float enemy_yaw;
+ vec3_t temp;
+
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+
+ if (!orig_yaw_speed)
+ {
+ orig_yaw_speed = self->yaw_speed;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ mytime = (int)((level.time - self->timestamp) / 0.5);
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw2(temp);
+
+ /* note that the offsets are based on a forward of 105 from the end angle */
+ if (mytime == 0)
+ {
+ self->ideal_yaw = anglemod(enemy_yaw - 30);
+ }
+ else if (mytime == 1)
+ {
+ self->ideal_yaw = anglemod(enemy_yaw);
+ }
+ else if (mytime == 2)
+ {
+ self->ideal_yaw = anglemod(enemy_yaw + 30);
+ }
+
+ CarrierMachineGun(self);
+}
+
+mframe_t carrier_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t carrier_move_stand = {
+ FRAME_search01,
+ FRAME_search13,
+ carrier_frames_stand,
+ NULL
+};
+
+mframe_t carrier_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t carrier_move_walk = {
+ FRAME_search01,
+ FRAME_search13,
+ carrier_frames_walk,
+ NULL
+};
+
+mframe_t carrier_frames_run[] = {
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck},
+ {ai_run, 6, CarrierCoopCheck}
+};
+
+mmove_t carrier_move_run = {
+ FRAME_search01,
+ FRAME_search13,
+ carrier_frames_run,
+ NULL
+};
+
+mframe_t carrier_frames_attack_pre_mg[] = {
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, carrier_attack_mg}
+};
+
+mmove_t carrier_move_attack_pre_mg = {
+ FRAME_firea01,
+ FRAME_firea08,
+ carrier_frames_attack_pre_mg,
+ NULL
+};
+
+/* Loop this */
+mframe_t carrier_frames_attack_mg[] = {
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, carrier_reattack_mg}
+};
+
+mmove_t carrier_move_attack_mg = {
+ FRAME_firea09,
+ FRAME_firea11,
+ carrier_frames_attack_mg,
+ NULL
+};
+
+mframe_t carrier_frames_attack_post_mg[] = {
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck}
+};
+
+mmove_t carrier_move_attack_post_mg = {
+ FRAME_firea12,
+ FRAME_firea15,
+ carrier_frames_attack_post_mg,
+ carrier_run
+};
+
+mframe_t carrier_frames_attack_pre_gren[] = {
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, carrier_attack_gren}
+};
+
+mmove_t carrier_move_attack_pre_gren = {
+ FRAME_fireb01,
+ FRAME_fireb06,
+ carrier_frames_attack_pre_gren,
+ NULL
+};
+
+mframe_t carrier_frames_attack_gren[] = {
+ {ai_charge, -15, CarrierGrenade},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, carrier_reattack_gren}
+};
+
+mmove_t carrier_move_attack_gren = {
+ FRAME_fireb07,
+ FRAME_fireb10,
+ carrier_frames_attack_gren,
+ NULL
+};
+
+mframe_t carrier_frames_attack_post_gren[] = {
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck},
+ {ai_charge, 4, CarrierCoopCheck}
+};
+
+mmove_t carrier_move_attack_post_gren = {
+ FRAME_fireb11,
+ FRAME_fireb16,
+ carrier_frames_attack_post_gren,
+ carrier_run
+};
+
+mframe_t carrier_frames_attack_rocket[] = {
+ {ai_charge, 15, CarrierRocket}
+};
+
+mmove_t carrier_move_attack_rocket = {
+ FRAME_fireb01,
+ FRAME_fireb01,
+ carrier_frames_attack_rocket,
+ carrier_run
+};
+
+void
+CarrierRail(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CARRIER_RAILGUN],
+ forward, right, start);
+
+ /* calc direction to where we targeted */
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_railgun(self, start, dir, 50, 100, MZ2_CARRIER_RAILGUN);
+ self->monsterinfo.attack_finished = level.time + RAIL_FIRE_TIME;
+}
+
+void
+CarrierSaveLoc(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+}
+
+mframe_t carrier_frames_attack_rail[] = {
+ {ai_charge, 2, CarrierCoopCheck},
+ {ai_charge, 2, CarrierSaveLoc},
+ {ai_charge, 2, CarrierCoopCheck},
+ {ai_charge, -20, CarrierRail},
+ {ai_charge, 2, CarrierCoopCheck},
+ {ai_charge, 2, CarrierCoopCheck},
+ {ai_charge, 2, CarrierCoopCheck},
+ {ai_charge, 2, CarrierCoopCheck},
+ {ai_charge, 2, CarrierCoopCheck}
+};
+
+mmove_t carrier_move_attack_rail = {
+ FRAME_search01,
+ FRAME_search09,
+ carrier_frames_attack_rail,
+ carrier_run
+};
+
+mframe_t carrier_frames_spawn[] = {
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, carrier_prep_spawn}, /* 7 - end of wind down */
+ {ai_charge, -2, carrier_start_spawn}, /* 8 - start of spawn */
+ {ai_charge, -2, carrier_ready_spawn},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -10, carrier_spawn_check}, /* 12 - actual spawn */
+ {ai_charge, -2, CarrierMachineGun}, /* 13 - begin of wind down */
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, CarrierMachineGun},
+ {ai_charge, -2, carrier_reattack_mg} /* 18 - end of wind down */
+};
+
+mmove_t carrier_move_spawn = {
+ FRAME_spawn01,
+ FRAME_spawn18,
+ carrier_frames_spawn,
+ NULL
+};
+
+mframe_t carrier_frames_pain_heavy[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t carrier_move_pain_heavy = {
+ FRAME_death01,
+ FRAME_death10,
+ carrier_frames_pain_heavy,
+ carrier_run
+};
+
+mframe_t carrier_frames_pain_light[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t carrier_move_pain_light = {
+ FRAME_spawn01,
+ FRAME_spawn04,
+ carrier_frames_pain_light,
+ carrier_run
+};
+
+mframe_t carrier_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, BossExplode}
+};
+
+mmove_t carrier_move_death = {
+ FRAME_death01,
+ FRAME_death16,
+ carrier_frames_death,
+ carrier_dead
+};
+
+void
+carrier_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &carrier_move_stand;
+}
+
+void
+carrier_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &carrier_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &carrier_move_run;
+ }
+}
+
+void
+carrier_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &carrier_move_walk;
+}
+
+void
+CarrierMachineGunHold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierMachineGun(self);
+}
+
+void
+carrier_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range, luck;
+ qboolean enemy_inback, enemy_infront, enemy_below;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ return;
+ }
+
+ enemy_inback = inback(self, self->enemy);
+ enemy_infront = infront(self, self->enemy);
+ enemy_below = below(self, self->enemy);
+
+ if (self->bad_area)
+ {
+ if ((enemy_inback) || (enemy_below))
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_rocket;
+ }
+ else if ((random() < 0.1) ||
+ (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &carrier_move_attack_rail;
+ }
+
+ return;
+ }
+
+ if (self->monsterinfo.attack_state == AS_BLIND)
+ {
+ self->monsterinfo.currentmove = &carrier_move_spawn;
+ return;
+ }
+
+ if (!enemy_inback && !enemy_infront && !enemy_below) /* to side and not under */
+ {
+ if ((random() < 0.1) ||
+ (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &carrier_move_attack_rail;
+ }
+
+ return;
+ }
+
+ if (enemy_infront)
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ if (range <= 125)
+ {
+ if ((random() < 0.8) ||
+ (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &carrier_move_attack_rail;
+ }
+ }
+ else if (range < 600)
+ {
+ luck = random();
+
+ if (self->monsterinfo.monster_slots > 2)
+ {
+ if (luck <= 0.20)
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ else if (luck <= 0.40)
+ {
+ self->monsterinfo.currentmove =
+ &carrier_move_attack_pre_gren;
+ }
+ else if ((luck <= 0.7) &&
+ !(level.time < self->monsterinfo.attack_finished))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &carrier_move_attack_rail;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &carrier_move_spawn;
+ }
+ }
+ else
+ {
+ if (luck <= 0.30)
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ else if (luck <= 0.65)
+ {
+ self->monsterinfo.currentmove =
+ &carrier_move_attack_pre_gren;
+ }
+ else if (level.time >= self->monsterinfo.attack_finished)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &carrier_move_attack_rail;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ }
+ }
+ else /* won't use grenades at this range */
+ {
+ luck = random();
+
+ if (self->monsterinfo.monster_slots > 2)
+ {
+ if (luck < 0.3)
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ else if ((luck < 0.65) && !(level.time < self->monsterinfo.attack_finished))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+ self->monsterinfo.currentmove = &carrier_move_attack_rail;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &carrier_move_spawn;
+ }
+ }
+ else
+ {
+ if ((luck < 0.45) ||
+ (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &carrier_move_attack_rail;
+ }
+ }
+ }
+ }
+ else if ((enemy_below) || (enemy_inback))
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_rocket;
+ }
+}
+
+void
+carrier_attack_mg(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+ self->monsterinfo.currentmove = &carrier_move_attack_mg;
+}
+
+void
+carrier_reattack_mg(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+
+ if (infront(self, self->enemy))
+ {
+ if (random() <= 0.5)
+ {
+ if ((random() < 0.7) || (self->monsterinfo.monster_slots <= 2))
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_mg;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &carrier_move_spawn;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_post_mg;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_post_mg;
+ }
+}
+
+void
+carrier_attack_gren(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+ self->timestamp = level.time;
+ self->monsterinfo.currentmove = &carrier_move_attack_gren;
+}
+
+void
+carrier_reattack_gren(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ CarrierCoopCheck(self);
+
+ if (infront(self, self->enemy))
+ {
+ if (self->timestamp + 1.3 > level.time) /* four grenades */
+ {
+ self->monsterinfo.currentmove = &carrier_move_attack_gren;
+ return;
+ }
+ }
+
+ self->monsterinfo.currentmove = &carrier_move_attack_post_gren;
+}
+
+void
+carrier_pain(edict_t *self, edict_t *other /* unused */, float kick /* unused */, int damage)
+{
+ qboolean changed = false;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 5;
+
+ if (damage < 10)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+ }
+ else if (damage < 30)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+
+ if (random() < 0.5)
+ {
+ changed = true;
+ self->monsterinfo.currentmove = &carrier_move_pain_light;
+ }
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &carrier_move_pain_heavy;
+ changed = true;
+ }
+
+ if (changed)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->yaw_speed = orig_yaw_speed;
+ }
+}
+
+void
+carrier_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -56, -56, 0);
+ VectorSet(self->maxs, 56, 56, 80);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+carrier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ self->monsterinfo.currentmove = &carrier_move_death;
+}
+
+qboolean
+Carrier_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance = 0;
+ trace_t tr;
+ qboolean enemy_infront, enemy_inback, enemy_below;
+ int enemy_range;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ /* go ahead and spawn stuff if we're mad a a client */
+ if (self->enemy->client && (self->monsterinfo.monster_slots > 2))
+ {
+ self->monsterinfo.attack_state = AS_BLIND;
+ return true;
+ }
+
+ /* we want them to go ahead and shoot at info_notnulls if they can. */
+ if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0))
+ {
+ return false;
+ }
+ }
+ }
+
+ enemy_infront = infront(self, self->enemy);
+ enemy_inback = inback(self, self->enemy);
+ enemy_below = below(self, self->enemy);
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw2(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* shoot out the back if appropriate */
+ if ((enemy_inback) || (!enemy_infront && enemy_below))
+ {
+ /* this is using wait because the attack is supposed to be independent */
+ if (level.time >= self->wait)
+ {
+ self->wait = level.time + CARRIER_ROCKET_TIME;
+ self->monsterinfo.attack(self);
+
+ if (random() < 0.6)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+
+ return true;
+ }
+ }
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.8;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.8;
+ }
+ else if (enemy_range == RANGE_FAR)
+ {
+ chance = 0.5;
+ }
+
+ /* go ahead and shoot every time if it's a info_notnull */
+ if ((random() < chance) || (self->enemy->solid == SOLID_NOT))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.6)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+void
+CarrierPrecache()
+{
+ gi.soundindex("flyer/flysght1.wav");
+ gi.soundindex("flyer/flysrch1.wav");
+ gi.soundindex("flyer/flypain1.wav");
+ gi.soundindex("flyer/flypain2.wav");
+ gi.soundindex("flyer/flyatck2.wav");
+ gi.soundindex("flyer/flyatck1.wav");
+ gi.soundindex("flyer/flydeth1.wav");
+ gi.soundindex("flyer/flyatck3.wav");
+ gi.soundindex("flyer/flyidle1.wav");
+ gi.soundindex("weapons/rockfly.wav");
+ gi.soundindex("infantry/infatck1.wav");
+ gi.soundindex("gunner/gunatck3.wav");
+ gi.soundindex("weapons/grenlb1b.wav");
+ gi.soundindex("tank/rocket.wav");
+
+ gi.modelindex("models/monsters/flyer/tris.md2");
+ gi.modelindex("models/objects/rocket/tris.md2");
+ gi.modelindex("models/objects/debris2/tris.md2");
+ gi.modelindex("models/objects/grenade/tris.md2");
+ gi.modelindex("models/items/spawngro/tris.md2");
+ gi.modelindex("models/items/spawngro2/tris.md2");
+ gi.modelindex("models/objects/gibs/sm_metal/tris.md2");
+ gi.modelindex("models/objects/gibs/gear/tris.md2");
+}
+
+/*
+ * QUAKED monster_carrier (1 .5 0) (-56 -56 -44) (56 56 44) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_carrier(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("carrier/pain_md.wav");
+ sound_pain2 = gi.soundindex("carrier/pain_lg.wav");
+ sound_pain3 = gi.soundindex("carrier/pain_sm.wav");
+ sound_death = gi.soundindex("carrier/death.wav");
+ sound_rail = gi.soundindex("gladiator/railgun.wav");
+ sound_sight = gi.soundindex("carrier/sight.wav");
+ sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav");
+
+ self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/carrier/tris.md2");
+ VectorSet(self->mins, -56, -56, -44);
+ VectorSet(self->maxs, 56, 56, 44);
+
+ /* 2000 - 4000 health */
+ self->health = max(2000, 2000 + 1000 * ((skill->value) - 1));
+
+ /* add health in coop (500 * skill) */
+ if (coop->value)
+ {
+ self->health += 500 * (skill->value);
+ }
+
+ self->gib_health = -200;
+ self->mass = 1000;
+
+ self->yaw_speed = 15;
+ orig_yaw_speed = self->yaw_speed;
+
+ self->flags |= FL_IMMUNE_LASER;
+ self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+ self->pain = carrier_pain;
+ self->die = carrier_die;
+
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.stand = carrier_stand;
+ self->monsterinfo.walk = carrier_walk;
+ self->monsterinfo.run = carrier_run;
+ self->monsterinfo.attack = carrier_attack;
+ self->monsterinfo.sight = carrier_sight;
+ self->monsterinfo.checkattack = Carrier_CheckAttack;
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &carrier_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ CarrierPrecache();
+
+ flymonster_start(self);
+
+ self->monsterinfo.attack_finished = 0;
+
+ switch ((int)skill->value)
+ {
+ case 0:
+ self->monsterinfo.monster_slots = 3;
+ break;
+ case 1:
+ case 2:
+ self->monsterinfo.monster_slots = 6;
+ break;
+ case 3:
+ self->monsterinfo.monster_slots = 9;
+ break;
+ default:
+ self->monsterinfo.monster_slots = 6;
+ break;
+ }
+}
diff --git a/rogue/src/monster/carrier/carrier.h b/rogue/src/monster/carrier/carrier.h
new file mode 100644
index 0000000..c05f6a0
--- /dev/null
+++ b/rogue/src/monster/carrier/carrier.h
@@ -0,0 +1,87 @@
+/* =======================================================================
+ *
+ * Carrier animations.
+ *
+ * =======================================================================
+ *
+ */
+
+#define FRAME_search01 0
+#define FRAME_search02 1
+#define FRAME_search03 2
+#define FRAME_search04 3
+#define FRAME_search05 4
+#define FRAME_search06 5
+#define FRAME_search07 6
+#define FRAME_search08 7
+#define FRAME_search09 8
+#define FRAME_search10 9
+#define FRAME_search11 10
+#define FRAME_search12 11
+#define FRAME_search13 12
+#define FRAME_firea01 13
+#define FRAME_firea02 14
+#define FRAME_firea03 15
+#define FRAME_firea04 16
+#define FRAME_firea05 17
+#define FRAME_firea06 18
+#define FRAME_firea07 19
+#define FRAME_firea08 20
+#define FRAME_firea09 21
+#define FRAME_firea10 22
+#define FRAME_firea11 23
+#define FRAME_firea12 24
+#define FRAME_firea13 25
+#define FRAME_firea14 26
+#define FRAME_firea15 27
+#define FRAME_fireb01 28
+#define FRAME_fireb02 29
+#define FRAME_fireb03 30
+#define FRAME_fireb04 31
+#define FRAME_fireb05 32
+#define FRAME_fireb06 33
+#define FRAME_fireb07 34
+#define FRAME_fireb08 35
+#define FRAME_fireb09 36
+#define FRAME_fireb10 37
+#define FRAME_fireb11 38
+#define FRAME_fireb12 39
+#define FRAME_fireb13 40
+#define FRAME_fireb14 41
+#define FRAME_fireb15 42
+#define FRAME_fireb16 43
+#define FRAME_spawn01 44
+#define FRAME_spawn02 45
+#define FRAME_spawn03 46
+#define FRAME_spawn04 47
+#define FRAME_spawn05 48
+#define FRAME_spawn06 49
+#define FRAME_spawn07 50
+#define FRAME_spawn08 51
+#define FRAME_spawn09 52
+#define FRAME_spawn10 53
+#define FRAME_spawn11 54
+#define FRAME_spawn12 55
+#define FRAME_spawn13 56
+#define FRAME_spawn14 57
+#define FRAME_spawn15 58
+#define FRAME_spawn16 59
+#define FRAME_spawn17 60
+#define FRAME_spawn18 61
+#define FRAME_death01 62
+#define FRAME_death02 63
+#define FRAME_death03 64
+#define FRAME_death04 65
+#define FRAME_death05 66
+#define FRAME_death06 67
+#define FRAME_death07 68
+#define FRAME_death08 69
+#define FRAME_death09 70
+#define FRAME_death10 71
+#define FRAME_death11 72
+#define FRAME_death12 73
+#define FRAME_death13 74
+#define FRAME_death14 75
+#define FRAME_death15 76
+#define FRAME_death16 77
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/chick/chick.c b/rogue/src/monster/chick/chick.c
new file mode 100644
index 0000000..957e24c
--- /dev/null
+++ b/rogue/src/monster/chick/chick.c
@@ -0,0 +1,1083 @@
+/* =======================================================================
+ *
+ * Iron Maiden.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "chick.h"
+
+#define LEAD_TARGET 1
+
+qboolean visible(edict_t *self, edict_t *other);
+
+void chick_stand(edict_t *self);
+void chick_run(edict_t *self);
+void chick_reslash(edict_t *self);
+void chick_rerocket(edict_t *self);
+void chick_attack1(edict_t *self);
+
+static int sound_missile_prelaunch;
+static int sound_missile_launch;
+static int sound_melee_swing;
+static int sound_melee_hit;
+static int sound_missile_reload;
+static int sound_death1;
+static int sound_death2;
+static int sound_fall_down;
+static int sound_idle1;
+static int sound_idle2;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_sight;
+static int sound_search;
+
+void
+ChickMoan(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0);
+ }
+}
+
+mframe_t chick_frames_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, ChickMoan},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t chick_move_fidget = {
+ FRAME_stand201,
+ FRAME_stand230,
+ chick_frames_fidget,
+ chick_stand
+};
+
+void
+chick_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ return;
+ }
+
+ if (random() <= 0.3)
+ {
+ self->monsterinfo.currentmove = &chick_move_fidget;
+ }
+}
+
+mframe_t chick_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, chick_fidget},
+};
+
+mmove_t chick_move_stand = {
+ FRAME_stand101,
+ FRAME_stand130,
+ chick_frames_stand,
+ NULL
+};
+
+void
+chick_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_stand;
+}
+
+mframe_t chick_frames_start_run[] = {
+ {ai_run, 1, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, -1, NULL},
+ {ai_run, -1, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 1, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 3, NULL}
+};
+
+mmove_t chick_move_start_run = {
+ FRAME_walk01,
+ FRAME_walk10,
+ chick_frames_start_run,
+ chick_run
+};
+
+mframe_t chick_frames_run[] = {
+ {ai_run, 6, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 7, NULL},
+ {ai_run, 4, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 7, NULL}
+};
+
+mmove_t chick_move_run = {
+ FRAME_walk11,
+ FRAME_walk20,
+ chick_frames_run,
+ NULL
+};
+
+mframe_t chick_frames_walk[] = {
+ {ai_walk, 6, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 13, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 11, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 7, NULL}
+};
+
+mmove_t chick_move_walk = {
+ FRAME_walk11,
+ FRAME_walk20,
+ chick_frames_walk,
+ NULL
+};
+
+void
+chick_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_walk;
+}
+
+void
+chick_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &chick_move_stand;
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &chick_move_walk) ||
+ (self->monsterinfo.currentmove == &chick_move_start_run))
+ {
+ self->monsterinfo.currentmove = &chick_move_run;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_start_run;
+ }
+}
+
+mframe_t chick_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t chick_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain105,
+ chick_frames_pain1, chick_run
+};
+
+mframe_t chick_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t chick_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain205,
+ chick_frames_pain2,
+ chick_run
+};
+
+mframe_t chick_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t chick_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain321,
+ chick_frames_pain3,
+ chick_run
+};
+
+void
+chick_pain(edict_t *self, edict_t *other /* other */, float kick /* other */, int damage)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ /* clear this from blindfire */
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+ if (damage <= 10)
+ {
+ self->monsterinfo.currentmove = &chick_move_pain1;
+ }
+ else if (damage <= 25)
+ {
+ self->monsterinfo.currentmove = &chick_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_pain3;
+ }
+
+ /* clear duck flag */
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+}
+
+void
+chick_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 16);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t chick_frames_death2[] = {
+ {ai_move, -6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 15, NULL},
+ {ai_move, 14, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t chick_move_death2 = {
+ FRAME_death201,
+ FRAME_death223,
+ chick_frames_death2,
+ chick_dead
+};
+
+mframe_t chick_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t chick_move_death1 = {
+ FRAME_death101,
+ FRAME_death112,
+ chick_frames_death1,
+ chick_dead
+};
+
+void
+chick_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage,
+ GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ n = rand() % 2;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &chick_move_death1;
+ gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_death2;
+ gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t chick_frames_duck[] = {
+ {ai_move, 0, monster_duck_down},
+ {ai_move, 1, NULL},
+ {ai_move, 4, monster_duck_hold},
+ {ai_move, -4, NULL},
+ {ai_move, -5, monster_duck_up},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t chick_move_duck = {
+ FRAME_duck01,
+ FRAME_duck07,
+ chick_frames_duck,
+ chick_run
+};
+
+void
+ChickSlash(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 10);
+ gi.sound(self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0);
+ fire_hit(self, aim, (10 + (rand() % 6)), 100);
+}
+
+void
+ChickRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+ trace_t trace; /* check target */
+ int rocketSpeed;
+ float dist;
+ vec3_t target;
+ qboolean blindfire = false;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ blindfire = true;
+ }
+ else
+ {
+ blindfire = false;
+ }
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1],
+ forward, right, start);
+
+ rocketSpeed = 500 + (100 * skill->value); /* rock & roll.... :) */
+
+ if (blindfire)
+ {
+ VectorCopy(self->monsterinfo.blind_fire_target, target);
+ }
+ else
+ {
+ VectorCopy(self->enemy->s.origin, target);
+ }
+
+ /* blindfire shooting */
+ if (blindfire)
+ {
+ VectorCopy(target, vec);
+ VectorSubtract(vec, start, dir);
+ }
+ /* don't shoot at feet if they're above where i'm shooting from. */
+ else if ((random() < 0.33) || (start[2] < self->enemy->absmin[2]))
+ {
+ VectorCopy(target, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ }
+ else
+ {
+ VectorCopy(target, vec);
+ vec[2] = self->enemy->absmin[2];
+ VectorSubtract(vec, start, dir);
+ }
+
+ /* lead target (not when blindfiring) */
+ if ((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15)))))
+ {
+ float time;
+
+ dist = VectorLength(dir);
+ time = dist / rocketSpeed;
+ VectorMA(vec, time, self->enemy->velocity, vec);
+ VectorSubtract(vec, start, dir);
+ }
+
+ VectorNormalize(dir);
+
+ if (blindfire)
+ {
+ /* blindfire has different fail criteria for the trace */
+ if (!blind_rocket_ok(self, start, right, target, 10.0f, dir))
+ {
+ return;
+ }
+ }
+ else
+ {
+ trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+
+ if (((trace.ent != self->enemy) && (trace.ent != world)) ||
+ ((trace.fraction <= 0.5f) && !trace.ent->client))
+ {
+ return;
+ }
+ }
+
+ monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
+}
+
+void
+Chick_PreAttack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0);
+}
+
+void
+ChickReload(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0);
+}
+
+mframe_t chick_frames_start_attack1[] = {
+ {ai_charge, 0, Chick_PreAttack1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 7, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, chick_attack1}
+};
+
+mmove_t chick_move_start_attack1 = {
+ FRAME_attak101,
+ FRAME_attak113,
+ chick_frames_start_attack1,
+ NULL
+};
+
+mframe_t chick_frames_attack1[] = {
+ {ai_charge, 19, ChickRocket},
+ {ai_charge, -6, NULL},
+ {ai_charge, -5, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -7, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 10, ChickReload},
+ {ai_charge, 4, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 3, chick_rerocket}
+};
+
+mmove_t chick_move_attack1 = {
+ FRAME_attak114,
+ FRAME_attak127,
+ chick_frames_attack1,
+ NULL
+};
+
+mframe_t chick_frames_end_attack1[] = {
+ {ai_charge, -3, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -6, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -2, NULL}
+};
+
+mmove_t chick_move_end_attack1 = {
+ FRAME_attak128,
+ FRAME_attak132,
+ chick_frames_end_attack1,
+ chick_run
+};
+
+void
+chick_rerocket(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &chick_move_end_attack1;
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (range(self, self->enemy) > RANGE_MELEE)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= (0.6 + (0.05 * ((float)skill->value))))
+ {
+ self->monsterinfo.currentmove = &chick_move_attack1;
+ return;
+ }
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &chick_move_end_attack1;
+}
+
+void
+chick_attack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_attack1;
+}
+
+mframe_t chick_frames_slash[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 7, ChickSlash},
+ {ai_charge, -7, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, -2, chick_reslash}
+};
+
+mmove_t chick_move_slash = {
+ FRAME_attak204,
+ FRAME_attak212,
+ chick_frames_slash,
+ NULL
+};
+
+mframe_t chick_frames_end_slash[] = {
+ {ai_charge, -6, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -6, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t chick_move_end_slash = {
+ FRAME_attak213,
+ FRAME_attak216,
+ chick_frames_end_slash,
+ chick_run
+};
+
+void
+chick_reslash(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ if (random() <= 0.9)
+ {
+ self->monsterinfo.currentmove = &chick_move_slash;
+ return;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_end_slash;
+ return;
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &chick_move_end_slash;
+}
+
+void
+chick_slash(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_slash;
+}
+
+mframe_t chick_frames_start_slash[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 8, NULL},
+ {ai_charge, 3, NULL}
+};
+
+mmove_t chick_move_start_slash = {
+ FRAME_attak201,
+ FRAME_attak203,
+ chick_frames_start_slash,
+ chick_slash
+};
+
+void
+chick_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_start_slash;
+}
+
+void
+chick_attack(edict_t *self)
+{
+ float r, chance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->monsterinfo.attack_state == AS_BLIND)
+ {
+ /* setup shot probabilities */
+ if (self->monsterinfo.blind_fire_delay < 1.0)
+ {
+ chance = 1.0;
+ }
+ else if (self->monsterinfo.blind_fire_delay < 7.5)
+ {
+ chance = 0.4;
+ }
+ else
+ {
+ chance = 0.1;
+ }
+
+ r = random();
+
+ /* minimum of 2 seconds, plus 0-3, after the shots are done */
+ self->monsterinfo.blind_fire_delay += 4.0 + 1.5 + random();
+
+ /* don't shoot at the origin */
+ if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin))
+ {
+ return;
+ }
+
+ /* don't shoot if the dice say not to */
+ if (r > chance)
+ {
+ return;
+ }
+
+ /* turn on manual steering to signal both manual steering and blindfire */
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &chick_move_start_attack1;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_start_attack1;
+}
+
+void
+chick_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+qboolean
+chick_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+chick_duck(edict_t *self, float eta)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &chick_move_start_attack1) ||
+ (self->monsterinfo.currentmove == &chick_move_attack1))
+ {
+ /* if we're shooting, and not on easy, don't dodge */
+ if (skill->value)
+ {
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ return;
+ }
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ /* stupid dodge */
+ self->monsterinfo.duck_wait_time = level.time + eta + 1;
+ }
+ else
+ {
+ self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+ }
+
+ /* has to be done immediately otherwise she can get stuck */
+ monster_duck_down(self);
+
+ self->monsterinfo.nextframe = FRAME_duck01;
+ self->monsterinfo.currentmove = &chick_move_duck;
+ return;
+}
+
+void
+chick_sidestep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &chick_move_start_attack1) ||
+ (self->monsterinfo.currentmove == &chick_move_attack1))
+ {
+ /* if we're shooting, and not on easy, don't dodge */
+ if (skill->value > SKILL_EASY)
+ {
+ self->monsterinfo.aiflags &= ~AI_DODGING;
+ return;
+ }
+ }
+
+ if (self->monsterinfo.currentmove != &chick_move_run)
+ {
+ self->monsterinfo.currentmove = &chick_move_run;
+ }
+}
+
+/*
+ * QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_chick(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_missile_prelaunch = gi.soundindex("chick/chkatck1.wav");
+ sound_missile_launch = gi.soundindex("chick/chkatck2.wav");
+ sound_melee_swing = gi.soundindex("chick/chkatck3.wav");
+ sound_melee_hit = gi.soundindex("chick/chkatck4.wav");
+ sound_missile_reload = gi.soundindex("chick/chkatck5.wav");
+ sound_death1 = gi.soundindex("chick/chkdeth1.wav");
+ sound_death2 = gi.soundindex("chick/chkdeth2.wav");
+ sound_fall_down = gi.soundindex("chick/chkfall1.wav");
+ sound_idle1 = gi.soundindex("chick/chkidle1.wav");
+ sound_idle2 = gi.soundindex("chick/chkidle2.wav");
+ sound_pain1 = gi.soundindex("chick/chkpain1.wav");
+ sound_pain2 = gi.soundindex("chick/chkpain2.wav");
+ sound_pain3 = gi.soundindex("chick/chkpain3.wav");
+ sound_sight = gi.soundindex("chick/chksght1.wav");
+ sound_search = gi.soundindex("chick/chksrch1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/bitch2/tris.md2");
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 56);
+
+ self->health = 175;
+ self->gib_health = -70;
+ self->mass = 200;
+
+ self->pain = chick_pain;
+ self->die = chick_die;
+
+ self->monsterinfo.stand = chick_stand;
+ self->monsterinfo.walk = chick_walk;
+ self->monsterinfo.run = chick_run;
+ self->monsterinfo.dodge = M_MonsterDodge;
+ self->monsterinfo.duck = chick_duck;
+ self->monsterinfo.unduck = monster_duck_up;
+ self->monsterinfo.sidestep = chick_sidestep;
+ self->monsterinfo.attack = chick_attack;
+ self->monsterinfo.melee = chick_melee;
+ self->monsterinfo.sight = chick_sight;
+ self->monsterinfo.blocked = chick_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &chick_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ self->monsterinfo.blindfire = true;
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/chick/chick.h b/rogue/src/monster/chick/chick.h
new file mode 100644
index 0000000..64d8c20
--- /dev/null
+++ b/rogue/src/monster/chick/chick.h
@@ -0,0 +1,296 @@
+/* =======================================================================
+ *
+ * Iron Maiden animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak113 12
+#define FRAME_attak114 13
+#define FRAME_attak115 14
+#define FRAME_attak116 15
+#define FRAME_attak117 16
+#define FRAME_attak118 17
+#define FRAME_attak119 18
+#define FRAME_attak120 19
+#define FRAME_attak121 20
+#define FRAME_attak122 21
+#define FRAME_attak123 22
+#define FRAME_attak124 23
+#define FRAME_attak125 24
+#define FRAME_attak126 25
+#define FRAME_attak127 26
+#define FRAME_attak128 27
+#define FRAME_attak129 28
+#define FRAME_attak130 29
+#define FRAME_attak131 30
+#define FRAME_attak132 31
+#define FRAME_attak201 32
+#define FRAME_attak202 33
+#define FRAME_attak203 34
+#define FRAME_attak204 35
+#define FRAME_attak205 36
+#define FRAME_attak206 37
+#define FRAME_attak207 38
+#define FRAME_attak208 39
+#define FRAME_attak209 40
+#define FRAME_attak210 41
+#define FRAME_attak211 42
+#define FRAME_attak212 43
+#define FRAME_attak213 44
+#define FRAME_attak214 45
+#define FRAME_attak215 46
+#define FRAME_attak216 47
+#define FRAME_death101 48
+#define FRAME_death102 49
+#define FRAME_death103 50
+#define FRAME_death104 51
+#define FRAME_death105 52
+#define FRAME_death106 53
+#define FRAME_death107 54
+#define FRAME_death108 55
+#define FRAME_death109 56
+#define FRAME_death110 57
+#define FRAME_death111 58
+#define FRAME_death112 59
+#define FRAME_death201 60
+#define FRAME_death202 61
+#define FRAME_death203 62
+#define FRAME_death204 63
+#define FRAME_death205 64
+#define FRAME_death206 65
+#define FRAME_death207 66
+#define FRAME_death208 67
+#define FRAME_death209 68
+#define FRAME_death210 69
+#define FRAME_death211 70
+#define FRAME_death212 71
+#define FRAME_death213 72
+#define FRAME_death214 73
+#define FRAME_death215 74
+#define FRAME_death216 75
+#define FRAME_death217 76
+#define FRAME_death218 77
+#define FRAME_death219 78
+#define FRAME_death220 79
+#define FRAME_death221 80
+#define FRAME_death222 81
+#define FRAME_death223 82
+#define FRAME_duck01 83
+#define FRAME_duck02 84
+#define FRAME_duck03 85
+#define FRAME_duck04 86
+#define FRAME_duck05 87
+#define FRAME_duck06 88
+#define FRAME_duck07 89
+#define FRAME_pain101 90
+#define FRAME_pain102 91
+#define FRAME_pain103 92
+#define FRAME_pain104 93
+#define FRAME_pain105 94
+#define FRAME_pain201 95
+#define FRAME_pain202 96
+#define FRAME_pain203 97
+#define FRAME_pain204 98
+#define FRAME_pain205 99
+#define FRAME_pain301 100
+#define FRAME_pain302 101
+#define FRAME_pain303 102
+#define FRAME_pain304 103
+#define FRAME_pain305 104
+#define FRAME_pain306 105
+#define FRAME_pain307 106
+#define FRAME_pain308 107
+#define FRAME_pain309 108
+#define FRAME_pain310 109
+#define FRAME_pain311 110
+#define FRAME_pain312 111
+#define FRAME_pain313 112
+#define FRAME_pain314 113
+#define FRAME_pain315 114
+#define FRAME_pain316 115
+#define FRAME_pain317 116
+#define FRAME_pain318 117
+#define FRAME_pain319 118
+#define FRAME_pain320 119
+#define FRAME_pain321 120
+#define FRAME_stand101 121
+#define FRAME_stand102 122
+#define FRAME_stand103 123
+#define FRAME_stand104 124
+#define FRAME_stand105 125
+#define FRAME_stand106 126
+#define FRAME_stand107 127
+#define FRAME_stand108 128
+#define FRAME_stand109 129
+#define FRAME_stand110 130
+#define FRAME_stand111 131
+#define FRAME_stand112 132
+#define FRAME_stand113 133
+#define FRAME_stand114 134
+#define FRAME_stand115 135
+#define FRAME_stand116 136
+#define FRAME_stand117 137
+#define FRAME_stand118 138
+#define FRAME_stand119 139
+#define FRAME_stand120 140
+#define FRAME_stand121 141
+#define FRAME_stand122 142
+#define FRAME_stand123 143
+#define FRAME_stand124 144
+#define FRAME_stand125 145
+#define FRAME_stand126 146
+#define FRAME_stand127 147
+#define FRAME_stand128 148
+#define FRAME_stand129 149
+#define FRAME_stand130 150
+#define FRAME_stand201 151
+#define FRAME_stand202 152
+#define FRAME_stand203 153
+#define FRAME_stand204 154
+#define FRAME_stand205 155
+#define FRAME_stand206 156
+#define FRAME_stand207 157
+#define FRAME_stand208 158
+#define FRAME_stand209 159
+#define FRAME_stand210 160
+#define FRAME_stand211 161
+#define FRAME_stand212 162
+#define FRAME_stand213 163
+#define FRAME_stand214 164
+#define FRAME_stand215 165
+#define FRAME_stand216 166
+#define FRAME_stand217 167
+#define FRAME_stand218 168
+#define FRAME_stand219 169
+#define FRAME_stand220 170
+#define FRAME_stand221 171
+#define FRAME_stand222 172
+#define FRAME_stand223 173
+#define FRAME_stand224 174
+#define FRAME_stand225 175
+#define FRAME_stand226 176
+#define FRAME_stand227 177
+#define FRAME_stand228 178
+#define FRAME_stand229 179
+#define FRAME_stand230 180
+#define FRAME_walk01 181
+#define FRAME_walk02 182
+#define FRAME_walk03 183
+#define FRAME_walk04 184
+#define FRAME_walk05 185
+#define FRAME_walk06 186
+#define FRAME_walk07 187
+#define FRAME_walk08 188
+#define FRAME_walk09 189
+#define FRAME_walk10 190
+#define FRAME_walk11 191
+#define FRAME_walk12 192
+#define FRAME_walk13 193
+#define FRAME_walk14 194
+#define FRAME_walk15 195
+#define FRAME_walk16 196
+#define FRAME_walk17 197
+#define FRAME_walk18 198
+#define FRAME_walk19 199
+#define FRAME_walk20 200
+#define FRAME_walk21 201
+#define FRAME_walk22 202
+#define FRAME_walk23 203
+#define FRAME_walk24 204
+#define FRAME_walk25 205
+#define FRAME_walk26 206
+#define FRAME_walk27 207
+#define FRAME_recln201 208
+#define FRAME_recln202 209
+#define FRAME_recln203 210
+#define FRAME_recln204 211
+#define FRAME_recln205 212
+#define FRAME_recln206 213
+#define FRAME_recln207 214
+#define FRAME_recln208 215
+#define FRAME_recln209 216
+#define FRAME_recln210 217
+#define FRAME_recln211 218
+#define FRAME_recln212 219
+#define FRAME_recln213 220
+#define FRAME_recln214 221
+#define FRAME_recln215 222
+#define FRAME_recln216 223
+#define FRAME_recln217 224
+#define FRAME_recln218 225
+#define FRAME_recln219 226
+#define FRAME_recln220 227
+#define FRAME_recln221 228
+#define FRAME_recln222 229
+#define FRAME_recln223 230
+#define FRAME_recln224 231
+#define FRAME_recln225 232
+#define FRAME_recln226 233
+#define FRAME_recln227 234
+#define FRAME_recln228 235
+#define FRAME_recln229 236
+#define FRAME_recln230 237
+#define FRAME_recln231 238
+#define FRAME_recln232 239
+#define FRAME_recln233 240
+#define FRAME_recln234 241
+#define FRAME_recln235 242
+#define FRAME_recln236 243
+#define FRAME_recln237 244
+#define FRAME_recln238 245
+#define FRAME_recln239 246
+#define FRAME_recln240 247
+#define FRAME_recln101 248
+#define FRAME_recln102 249
+#define FRAME_recln103 250
+#define FRAME_recln104 251
+#define FRAME_recln105 252
+#define FRAME_recln106 253
+#define FRAME_recln107 254
+#define FRAME_recln108 255
+#define FRAME_recln109 256
+#define FRAME_recln110 257
+#define FRAME_recln111 258
+#define FRAME_recln112 259
+#define FRAME_recln113 260
+#define FRAME_recln114 261
+#define FRAME_recln115 262
+#define FRAME_recln116 263
+#define FRAME_recln117 264
+#define FRAME_recln118 265
+#define FRAME_recln119 266
+#define FRAME_recln120 267
+#define FRAME_recln121 268
+#define FRAME_recln122 269
+#define FRAME_recln123 270
+#define FRAME_recln124 271
+#define FRAME_recln125 272
+#define FRAME_recln126 273
+#define FRAME_recln127 274
+#define FRAME_recln128 275
+#define FRAME_recln129 276
+#define FRAME_recln130 277
+#define FRAME_recln131 278
+#define FRAME_recln132 279
+#define FRAME_recln133 280
+#define FRAME_recln134 281
+#define FRAME_recln135 282
+#define FRAME_recln136 283
+#define FRAME_recln137 284
+#define FRAME_recln138 285
+#define FRAME_recln139 286
+#define FRAME_recln140 287
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/flipper/flipper.c b/rogue/src/monster/flipper/flipper.c
new file mode 100644
index 0000000..4c70273
--- /dev/null
+++ b/rogue/src/monster/flipper/flipper.c
@@ -0,0 +1,532 @@
+/* =======================================================================
+ *
+ * Baracuda Shark.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "flipper.h"
+
+#define FLIPPER_RUN_SPEED 24
+
+static int sound_chomp;
+static int sound_attack;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_death;
+static int sound_idle;
+static int sound_search;
+static int sound_sight;
+
+void flipper_stand(edict_t *self);
+
+mframe_t flipper_frames_stand[] = {
+ {ai_stand, 0, NULL}
+};
+
+mmove_t flipper_move_stand = {
+ FRAME_flphor01,
+ FRAME_flphor01,
+ flipper_frames_stand,
+ NULL
+};
+
+void
+flipper_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_stand;
+}
+
+mframe_t flipper_frames_run[] = {
+ {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 6 */
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 10 */
+
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 20 */
+
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL} /* 29 */
+};
+
+mmove_t flipper_move_run_loop = {
+ FRAME_flpver06,
+ FRAME_flpver29,
+ flipper_frames_run,
+ NULL
+};
+
+void
+flipper_run_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_run_loop;
+}
+
+mframe_t flipper_frames_run_start[] = {
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL}
+};
+
+mmove_t flipper_move_run_start = {
+ FRAME_flpver01,
+ FRAME_flpver06,
+ flipper_frames_run_start,
+ flipper_run_loop
+};
+
+void
+flipper_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_run_start;
+}
+
+/* Standard Swimming */
+mframe_t flipper_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t flipper_move_walk = {
+ FRAME_flphor01,
+ FRAME_flphor24,
+ flipper_frames_walk,
+ NULL
+};
+
+void
+flipper_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_walk;
+}
+
+mframe_t flipper_frames_start_run[] = {
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, flipper_run}
+};
+
+mmove_t flipper_move_start_run = {
+ FRAME_flphor01,
+ FRAME_flphor05,
+ flipper_frames_start_run,
+ NULL
+};
+
+void
+flipper_start_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_start_run;
+}
+
+mframe_t flipper_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flipper_move_pain2 = {
+ FRAME_flppn101,
+ FRAME_flppn105,
+ flipper_frames_pain2,
+ flipper_run
+};
+
+mframe_t flipper_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flipper_move_pain1 = {
+ FRAME_flppn201,
+ FRAME_flppn205,
+ flipper_frames_pain1,
+ flipper_run
+};
+
+void
+flipper_bite(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 0);
+ fire_hit(self, aim, 5, 0);
+}
+
+void
+flipper_preattack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0);
+}
+
+mframe_t flipper_frames_attack[] = {
+ {ai_charge, 0, flipper_preattack},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flipper_bite},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flipper_bite},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flipper_move_attack = {
+ FRAME_flpbit01,
+ FRAME_flpbit20,
+ flipper_frames_attack,
+ flipper_run
+};
+
+void
+flipper_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_attack;
+}
+
+void
+flipper_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = (rand() + 1) % 2;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flipper_move_pain1;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flipper_move_pain2;
+ }
+}
+
+void
+flipper_dead(edict_t *self)
+{
+ vec3_t p;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* original dead bbox was wrong - and make sure the bbox adjustment stays in solidity */
+
+ p[0] = self->s.origin[0];
+ p[1] = self->s.origin[1];
+ p[2] = self->s.origin[2] - 8;
+
+ tr = gi.trace(self->s.origin, self->mins, self->maxs, p, self, self->clipmask);
+
+ self->mins[2] = tr.endpos[2] - self->s.origin[2];
+
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t flipper_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flipper_move_death = {
+ FRAME_flpdth01,
+ FRAME_flpdth56,
+ flipper_frames_death,
+ flipper_dead
+};
+
+void
+flipper_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+flipper_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &flipper_move_death;
+}
+
+/*
+ * QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_flipper(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("flipper/flppain1.wav");
+ sound_pain2 = gi.soundindex("flipper/flppain2.wav");
+ sound_death = gi.soundindex("flipper/flpdeth1.wav");
+ sound_chomp = gi.soundindex("flipper/flpatck1.wav");
+ sound_attack = gi.soundindex("flipper/flpatck2.wav");
+ sound_idle = gi.soundindex("flipper/flpidle1.wav");
+ sound_search = gi.soundindex("flipper/flpsrch1.wav");
+ sound_sight = gi.soundindex("flipper/flpsght1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/flipper/tris.md2");
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 50;
+ self->gib_health = -30;
+ self->mass = 100;
+
+ self->pain = flipper_pain;
+ self->die = flipper_die;
+
+ self->monsterinfo.stand = flipper_stand;
+ self->monsterinfo.walk = flipper_walk;
+ self->monsterinfo.run = flipper_start_run;
+ self->monsterinfo.melee = flipper_melee;
+ self->monsterinfo.sight = flipper_sight;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &flipper_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ swimmonster_start(self);
+}
diff --git a/rogue/src/monster/flipper/flipper.h b/rogue/src/monster/flipper/flipper.h
new file mode 100644
index 0000000..009f22f
--- /dev/null
+++ b/rogue/src/monster/flipper/flipper.h
@@ -0,0 +1,168 @@
+/* =======================================================================
+ *
+ * Baracuda Shark animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_flpbit01 0
+#define FRAME_flpbit02 1
+#define FRAME_flpbit03 2
+#define FRAME_flpbit04 3
+#define FRAME_flpbit05 4
+#define FRAME_flpbit06 5
+#define FRAME_flpbit07 6
+#define FRAME_flpbit08 7
+#define FRAME_flpbit09 8
+#define FRAME_flpbit10 9
+#define FRAME_flpbit11 10
+#define FRAME_flpbit12 11
+#define FRAME_flpbit13 12
+#define FRAME_flpbit14 13
+#define FRAME_flpbit15 14
+#define FRAME_flpbit16 15
+#define FRAME_flpbit17 16
+#define FRAME_flpbit18 17
+#define FRAME_flpbit19 18
+#define FRAME_flpbit20 19
+#define FRAME_flptal01 20
+#define FRAME_flptal02 21
+#define FRAME_flptal03 22
+#define FRAME_flptal04 23
+#define FRAME_flptal05 24
+#define FRAME_flptal06 25
+#define FRAME_flptal07 26
+#define FRAME_flptal08 27
+#define FRAME_flptal09 28
+#define FRAME_flptal10 29
+#define FRAME_flptal11 30
+#define FRAME_flptal12 31
+#define FRAME_flptal13 32
+#define FRAME_flptal14 33
+#define FRAME_flptal15 34
+#define FRAME_flptal16 35
+#define FRAME_flptal17 36
+#define FRAME_flptal18 37
+#define FRAME_flptal19 38
+#define FRAME_flptal20 39
+#define FRAME_flptal21 40
+#define FRAME_flphor01 41
+#define FRAME_flphor02 42
+#define FRAME_flphor03 43
+#define FRAME_flphor04 44
+#define FRAME_flphor05 45
+#define FRAME_flphor06 46
+#define FRAME_flphor07 47
+#define FRAME_flphor08 48
+#define FRAME_flphor09 49
+#define FRAME_flphor10 50
+#define FRAME_flphor11 51
+#define FRAME_flphor12 52
+#define FRAME_flphor13 53
+#define FRAME_flphor14 54
+#define FRAME_flphor15 55
+#define FRAME_flphor16 56
+#define FRAME_flphor17 57
+#define FRAME_flphor18 58
+#define FRAME_flphor19 59
+#define FRAME_flphor20 60
+#define FRAME_flphor21 61
+#define FRAME_flphor22 62
+#define FRAME_flphor23 63
+#define FRAME_flphor24 64
+#define FRAME_flpver01 65
+#define FRAME_flpver02 66
+#define FRAME_flpver03 67
+#define FRAME_flpver04 68
+#define FRAME_flpver05 69
+#define FRAME_flpver06 70
+#define FRAME_flpver07 71
+#define FRAME_flpver08 72
+#define FRAME_flpver09 73
+#define FRAME_flpver10 74
+#define FRAME_flpver11 75
+#define FRAME_flpver12 76
+#define FRAME_flpver13 77
+#define FRAME_flpver14 78
+#define FRAME_flpver15 79
+#define FRAME_flpver16 80
+#define FRAME_flpver17 81
+#define FRAME_flpver18 82
+#define FRAME_flpver19 83
+#define FRAME_flpver20 84
+#define FRAME_flpver21 85
+#define FRAME_flpver22 86
+#define FRAME_flpver23 87
+#define FRAME_flpver24 88
+#define FRAME_flpver25 89
+#define FRAME_flpver26 90
+#define FRAME_flpver27 91
+#define FRAME_flpver28 92
+#define FRAME_flpver29 93
+#define FRAME_flppn101 94
+#define FRAME_flppn102 95
+#define FRAME_flppn103 96
+#define FRAME_flppn104 97
+#define FRAME_flppn105 98
+#define FRAME_flppn201 99
+#define FRAME_flppn202 100
+#define FRAME_flppn203 101
+#define FRAME_flppn204 102
+#define FRAME_flppn205 103
+#define FRAME_flpdth01 104
+#define FRAME_flpdth02 105
+#define FRAME_flpdth03 106
+#define FRAME_flpdth04 107
+#define FRAME_flpdth05 108
+#define FRAME_flpdth06 109
+#define FRAME_flpdth07 110
+#define FRAME_flpdth08 111
+#define FRAME_flpdth09 112
+#define FRAME_flpdth10 113
+#define FRAME_flpdth11 114
+#define FRAME_flpdth12 115
+#define FRAME_flpdth13 116
+#define FRAME_flpdth14 117
+#define FRAME_flpdth15 118
+#define FRAME_flpdth16 119
+#define FRAME_flpdth17 120
+#define FRAME_flpdth18 121
+#define FRAME_flpdth19 122
+#define FRAME_flpdth20 123
+#define FRAME_flpdth21 124
+#define FRAME_flpdth22 125
+#define FRAME_flpdth23 126
+#define FRAME_flpdth24 127
+#define FRAME_flpdth25 128
+#define FRAME_flpdth26 129
+#define FRAME_flpdth27 130
+#define FRAME_flpdth28 131
+#define FRAME_flpdth29 132
+#define FRAME_flpdth30 133
+#define FRAME_flpdth31 134
+#define FRAME_flpdth32 135
+#define FRAME_flpdth33 136
+#define FRAME_flpdth34 137
+#define FRAME_flpdth35 138
+#define FRAME_flpdth36 139
+#define FRAME_flpdth37 140
+#define FRAME_flpdth38 141
+#define FRAME_flpdth39 142
+#define FRAME_flpdth40 143
+#define FRAME_flpdth41 144
+#define FRAME_flpdth42 145
+#define FRAME_flpdth43 146
+#define FRAME_flpdth44 147
+#define FRAME_flpdth45 148
+#define FRAME_flpdth46 149
+#define FRAME_flpdth47 150
+#define FRAME_flpdth48 151
+#define FRAME_flpdth49 152
+#define FRAME_flpdth50 153
+#define FRAME_flpdth51 154
+#define FRAME_flpdth52 155
+#define FRAME_flpdth53 156
+#define FRAME_flpdth54 157
+#define FRAME_flpdth55 158
+#define FRAME_flpdth56 159
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/float/float.c b/rogue/src/monster/float/float.c
new file mode 100644
index 0000000..6af849a
--- /dev/null
+++ b/rogue/src/monster/float/float.c
@@ -0,0 +1,875 @@
+/* =======================================================================
+ *
+ * Mechanic.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "float.h"
+
+static int sound_attack2;
+static int sound_attack3;
+static int sound_death1;
+static int sound_idle;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_sight;
+
+void floater_dead(edict_t *self);
+void floater_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+void floater_run(edict_t *self);
+void floater_wham(edict_t *self);
+void floater_zap(edict_t *self);
+
+void
+floater_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+floater_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+floater_fire_blaster(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward,
+ right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect);
+}
+
+mframe_t floater_frames_stand1[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t floater_move_stand1 = {
+ FRAME_stand101,
+ FRAME_stand152,
+ floater_frames_stand1,
+ NULL
+};
+
+mframe_t floater_frames_stand2[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t floater_move_stand2 = {
+ FRAME_stand201,
+ FRAME_stand252,
+ floater_frames_stand2,
+ NULL
+};
+
+void
+floater_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &floater_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_stand2;
+ }
+}
+
+mframe_t floater_frames_activate[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_activate = {
+ FRAME_actvat01,
+ FRAME_actvat31,
+ floater_frames_activate,
+ NULL
+};
+
+mframe_t floater_frames_attack1[] = {
+ {ai_charge, 0, NULL}, /* Blaster attack */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, floater_fire_blaster}, /* BOOM (0, -25.8, 32.5) -- LOOP Starts */
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL} /* -- LOOP Ends */
+};
+
+mmove_t floater_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak114,
+ floater_frames_attack1,
+ floater_run
+};
+
+/* circle strafe frames */
+mframe_t floater_frames_attack1a[] = {
+ {ai_charge, 10, NULL}, // Blaster attack
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, floater_fire_blaster}, // BOOM (0, -25.8, 32.5) -- LOOP Starts
+ {ai_charge, 10, floater_fire_blaster},
+ {ai_charge, 10, floater_fire_blaster},
+ {ai_charge, 10, floater_fire_blaster},
+ {ai_charge, 10, floater_fire_blaster},
+ {ai_charge, 10, floater_fire_blaster},
+ {ai_charge, 10, floater_fire_blaster},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL} // -- LOOP Ends
+};
+
+mmove_t floater_move_attack1a = {
+ FRAME_attak101,
+ FRAME_attak114,
+ floater_frames_attack1a,
+ floater_run
+};
+
+mframe_t floater_frames_attack2[] = {
+ {ai_charge, 0, NULL}, /* Claws */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, floater_wham}, /* WHAM (0, -45, 29}.6) -- LOOP Starts */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* -- LOOP Ends */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t floater_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak225,
+ floater_frames_attack2,
+ floater_run
+};
+
+mframe_t floater_frames_attack3[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, floater_zap}, /* -- LOOP Starts */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* -- LOOP Ends */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t floater_move_attack3 = {
+ FRAME_attak301,
+ FRAME_attak334,
+ floater_frames_attack3,
+ floater_run
+};
+
+mframe_t floater_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_death = {
+ FRAME_death01,
+ FRAME_death13,
+ floater_frames_death,
+ floater_dead
+};
+
+mframe_t floater_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain107,
+ floater_frames_pain1,
+ floater_run
+};
+
+mframe_t floater_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain208,
+ floater_frames_pain2,
+ floater_run
+};
+
+mframe_t floater_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain312,
+ floater_frames_pain3,
+ floater_run
+};
+
+mframe_t floater_frames_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL}
+};
+
+mmove_t floater_move_walk = {
+ FRAME_stand101,
+ FRAME_stand152,
+ floater_frames_walk,
+ NULL
+};
+
+mframe_t floater_frames_run[] = {
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL}
+};
+
+mmove_t floater_move_run = {
+ FRAME_stand101,
+ FRAME_stand152,
+ floater_frames_run,
+ NULL
+};
+
+void
+floater_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &floater_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_run;
+ }
+}
+
+void
+floater_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &floater_move_walk;
+}
+
+void
+floater_wham(edict_t *self)
+{
+ static vec3_t aim = {MELEE_DISTANCE, 0, 0};
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0);
+ fire_hit(self, aim, 5 + rand() % 6, -50);
+}
+
+void
+floater_zap(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t origin;
+ vec3_t dir;
+ vec3_t offset;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ VectorSet(offset, 18.5, -0.9, 10);
+ G_ProjectSource(self->s.origin, offset, forward, right, origin);
+
+ gi.sound(self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_SPLASH);
+ gi.WriteByte(32);
+ gi.WritePosition(origin);
+ gi.WriteDir(dir);
+ gi.WriteByte(1); /* sparks */
+ gi.multicast(origin, MULTICAST_PVS);
+
+ if (range(self, self->enemy) == RANGE_MELEE && infront(self, self->enemy) &&
+ visible(self, self->enemy))
+ {
+ T_Damage(self->enemy, self, self, dir, self->enemy->s.origin,
+ vec3_origin, 5 + rand() % 6, -10, DAMAGE_ENERGY, MOD_UNKNOWN);
+ }
+}
+
+void
+floater_attack(edict_t *self)
+{
+ float chance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ // 0% chance of circle in easy
+ // 50% chance in normal
+ // 75% chance in hard
+ // 86.67% chance in nightmare
+ if (skill->value == SKILL_EASY)
+ {
+ chance = 0;
+ }
+ else
+ {
+ chance = 1.0 - (0.5/(float)(skill->value));
+ }
+
+ if (random() > chance)
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ self->monsterinfo.currentmove = &floater_move_attack1;
+ }
+ else // circle strafe
+ {
+ if (random () <= 0.5) // switch directions
+ {
+ self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+ }
+
+ self->monsterinfo.attack_state = AS_SLIDING;
+ self->monsterinfo.currentmove = &floater_move_attack1a;
+ }
+}
+
+void
+floater_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &floater_move_attack3;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_attack2;
+ }
+}
+
+void
+floater_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = (rand() + 1) % 3;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &floater_move_pain1;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &floater_move_pain2;
+ }
+}
+
+void
+floater_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+floater_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+ BecomeExplosion1(self);
+}
+
+qboolean
+floater_blocked(edict_t *self, float dist)
+{
+ return false;
+}
+
+/*
+ * QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_floater(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_attack2 = gi.soundindex("floater/fltatck2.wav");
+ sound_attack3 = gi.soundindex("floater/fltatck3.wav");
+ sound_death1 = gi.soundindex("floater/fltdeth1.wav");
+ sound_idle = gi.soundindex("floater/fltidle1.wav");
+ sound_pain1 = gi.soundindex("floater/fltpain1.wav");
+ sound_pain2 = gi.soundindex("floater/fltpain2.wav");
+ sound_sight = gi.soundindex("floater/fltsght1.wav");
+
+ gi.soundindex("floater/fltatck1.wav");
+
+ self->s.sound = gi.soundindex("floater/fltsrch1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/float/tris.md2");
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 32);
+
+ self->health = 200;
+ self->gib_health = -80;
+ self->mass = 300;
+
+ self->pain = floater_pain;
+ self->die = floater_die;
+
+ self->monsterinfo.stand = floater_stand;
+ self->monsterinfo.walk = floater_walk;
+ self->monsterinfo.run = floater_run;
+ self->monsterinfo.attack = floater_attack;
+ self->monsterinfo.melee = floater_melee;
+ self->monsterinfo.sight = floater_sight;
+ self->monsterinfo.idle = floater_idle;
+ self->monsterinfo.blocked = floater_blocked;
+
+ gi.linkentity(self);
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &floater_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_stand2;
+ }
+
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/rogue/src/monster/float/float.h b/rogue/src/monster/float/float.h
new file mode 100644
index 0000000..a31d611
--- /dev/null
+++ b/rogue/src/monster/float/float.h
@@ -0,0 +1,256 @@
+/* =======================================================================
+ *
+ * Mechanic animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_actvat01 0
+#define FRAME_actvat02 1
+#define FRAME_actvat03 2
+#define FRAME_actvat04 3
+#define FRAME_actvat05 4
+#define FRAME_actvat06 5
+#define FRAME_actvat07 6
+#define FRAME_actvat08 7
+#define FRAME_actvat09 8
+#define FRAME_actvat10 9
+#define FRAME_actvat11 10
+#define FRAME_actvat12 11
+#define FRAME_actvat13 12
+#define FRAME_actvat14 13
+#define FRAME_actvat15 14
+#define FRAME_actvat16 15
+#define FRAME_actvat17 16
+#define FRAME_actvat18 17
+#define FRAME_actvat19 18
+#define FRAME_actvat20 19
+#define FRAME_actvat21 20
+#define FRAME_actvat22 21
+#define FRAME_actvat23 22
+#define FRAME_actvat24 23
+#define FRAME_actvat25 24
+#define FRAME_actvat26 25
+#define FRAME_actvat27 26
+#define FRAME_actvat28 27
+#define FRAME_actvat29 28
+#define FRAME_actvat30 29
+#define FRAME_actvat31 30
+#define FRAME_attak101 31
+#define FRAME_attak102 32
+#define FRAME_attak103 33
+#define FRAME_attak104 34
+#define FRAME_attak105 35
+#define FRAME_attak106 36
+#define FRAME_attak107 37
+#define FRAME_attak108 38
+#define FRAME_attak109 39
+#define FRAME_attak110 40
+#define FRAME_attak111 41
+#define FRAME_attak112 42
+#define FRAME_attak113 43
+#define FRAME_attak114 44
+#define FRAME_attak201 45
+#define FRAME_attak202 46
+#define FRAME_attak203 47
+#define FRAME_attak204 48
+#define FRAME_attak205 49
+#define FRAME_attak206 50
+#define FRAME_attak207 51
+#define FRAME_attak208 52
+#define FRAME_attak209 53
+#define FRAME_attak210 54
+#define FRAME_attak211 55
+#define FRAME_attak212 56
+#define FRAME_attak213 57
+#define FRAME_attak214 58
+#define FRAME_attak215 59
+#define FRAME_attak216 60
+#define FRAME_attak217 61
+#define FRAME_attak218 62
+#define FRAME_attak219 63
+#define FRAME_attak220 64
+#define FRAME_attak221 65
+#define FRAME_attak222 66
+#define FRAME_attak223 67
+#define FRAME_attak224 68
+#define FRAME_attak225 69
+#define FRAME_attak301 70
+#define FRAME_attak302 71
+#define FRAME_attak303 72
+#define FRAME_attak304 73
+#define FRAME_attak305 74
+#define FRAME_attak306 75
+#define FRAME_attak307 76
+#define FRAME_attak308 77
+#define FRAME_attak309 78
+#define FRAME_attak310 79
+#define FRAME_attak311 80
+#define FRAME_attak312 81
+#define FRAME_attak313 82
+#define FRAME_attak314 83
+#define FRAME_attak315 84
+#define FRAME_attak316 85
+#define FRAME_attak317 86
+#define FRAME_attak318 87
+#define FRAME_attak319 88
+#define FRAME_attak320 89
+#define FRAME_attak321 90
+#define FRAME_attak322 91
+#define FRAME_attak323 92
+#define FRAME_attak324 93
+#define FRAME_attak325 94
+#define FRAME_attak326 95
+#define FRAME_attak327 96
+#define FRAME_attak328 97
+#define FRAME_attak329 98
+#define FRAME_attak330 99
+#define FRAME_attak331 100
+#define FRAME_attak332 101
+#define FRAME_attak333 102
+#define FRAME_attak334 103
+#define FRAME_death01 104
+#define FRAME_death02 105
+#define FRAME_death03 106
+#define FRAME_death04 107
+#define FRAME_death05 108
+#define FRAME_death06 109
+#define FRAME_death07 110
+#define FRAME_death08 111
+#define FRAME_death09 112
+#define FRAME_death10 113
+#define FRAME_death11 114
+#define FRAME_death12 115
+#define FRAME_death13 116
+#define FRAME_pain101 117
+#define FRAME_pain102 118
+#define FRAME_pain103 119
+#define FRAME_pain104 120
+#define FRAME_pain105 121
+#define FRAME_pain106 122
+#define FRAME_pain107 123
+#define FRAME_pain201 124
+#define FRAME_pain202 125
+#define FRAME_pain203 126
+#define FRAME_pain204 127
+#define FRAME_pain205 128
+#define FRAME_pain206 129
+#define FRAME_pain207 130
+#define FRAME_pain208 131
+#define FRAME_pain301 132
+#define FRAME_pain302 133
+#define FRAME_pain303 134
+#define FRAME_pain304 135
+#define FRAME_pain305 136
+#define FRAME_pain306 137
+#define FRAME_pain307 138
+#define FRAME_pain308 139
+#define FRAME_pain309 140
+#define FRAME_pain310 141
+#define FRAME_pain311 142
+#define FRAME_pain312 143
+#define FRAME_stand101 144
+#define FRAME_stand102 145
+#define FRAME_stand103 146
+#define FRAME_stand104 147
+#define FRAME_stand105 148
+#define FRAME_stand106 149
+#define FRAME_stand107 150
+#define FRAME_stand108 151
+#define FRAME_stand109 152
+#define FRAME_stand110 153
+#define FRAME_stand111 154
+#define FRAME_stand112 155
+#define FRAME_stand113 156
+#define FRAME_stand114 157
+#define FRAME_stand115 158
+#define FRAME_stand116 159
+#define FRAME_stand117 160
+#define FRAME_stand118 161
+#define FRAME_stand119 162
+#define FRAME_stand120 163
+#define FRAME_stand121 164
+#define FRAME_stand122 165
+#define FRAME_stand123 166
+#define FRAME_stand124 167
+#define FRAME_stand125 168
+#define FRAME_stand126 169
+#define FRAME_stand127 170
+#define FRAME_stand128 171
+#define FRAME_stand129 172
+#define FRAME_stand130 173
+#define FRAME_stand131 174
+#define FRAME_stand132 175
+#define FRAME_stand133 176
+#define FRAME_stand134 177
+#define FRAME_stand135 178
+#define FRAME_stand136 179
+#define FRAME_stand137 180
+#define FRAME_stand138 181
+#define FRAME_stand139 182
+#define FRAME_stand140 183
+#define FRAME_stand141 184
+#define FRAME_stand142 185
+#define FRAME_stand143 186
+#define FRAME_stand144 187
+#define FRAME_stand145 188
+#define FRAME_stand146 189
+#define FRAME_stand147 190
+#define FRAME_stand148 191
+#define FRAME_stand149 192
+#define FRAME_stand150 193
+#define FRAME_stand151 194
+#define FRAME_stand152 195
+#define FRAME_stand201 196
+#define FRAME_stand202 197
+#define FRAME_stand203 198
+#define FRAME_stand204 199
+#define FRAME_stand205 200
+#define FRAME_stand206 201
+#define FRAME_stand207 202
+#define FRAME_stand208 203
+#define FRAME_stand209 204
+#define FRAME_stand210 205
+#define FRAME_stand211 206
+#define FRAME_stand212 207
+#define FRAME_stand213 208
+#define FRAME_stand214 209
+#define FRAME_stand215 210
+#define FRAME_stand216 211
+#define FRAME_stand217 212
+#define FRAME_stand218 213
+#define FRAME_stand219 214
+#define FRAME_stand220 215
+#define FRAME_stand221 216
+#define FRAME_stand222 217
+#define FRAME_stand223 218
+#define FRAME_stand224 219
+#define FRAME_stand225 220
+#define FRAME_stand226 221
+#define FRAME_stand227 222
+#define FRAME_stand228 223
+#define FRAME_stand229 224
+#define FRAME_stand230 225
+#define FRAME_stand231 226
+#define FRAME_stand232 227
+#define FRAME_stand233 228
+#define FRAME_stand234 229
+#define FRAME_stand235 230
+#define FRAME_stand236 231
+#define FRAME_stand237 232
+#define FRAME_stand238 233
+#define FRAME_stand239 234
+#define FRAME_stand240 235
+#define FRAME_stand241 236
+#define FRAME_stand242 237
+#define FRAME_stand243 238
+#define FRAME_stand244 239
+#define FRAME_stand245 240
+#define FRAME_stand246 241
+#define FRAME_stand247 242
+#define FRAME_stand248 243
+#define FRAME_stand249 244
+#define FRAME_stand250 245
+#define FRAME_stand251 246
+#define FRAME_stand252 247
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/flyer/flyer.c b/rogue/src/monster/flyer/flyer.c
new file mode 100644
index 0000000..2e920d9
--- /dev/null
+++ b/rogue/src/monster/flyer/flyer.c
@@ -0,0 +1,1120 @@
+/* =======================================================================
+ *
+ * Flyer.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "flyer.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+
+static int nextmove; /* Used for start/stop frames */
+
+static int sound_sight;
+static int sound_idle;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_slash;
+static int sound_sproing;
+static int sound_die;
+
+void flyer_check_melee(edict_t *self);
+void flyer_loop_melee(edict_t *self);
+void flyer_melee(edict_t *self);
+void flyer_setstart(edict_t *self);
+void flyer_stand(edict_t *self);
+void flyer_nextmove(edict_t *self);
+
+void flyer_kamikaze(edict_t *self);
+void flyer_kamikaze_check(edict_t *self);
+void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+void
+flyer_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+flyer_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+flyer_pop_blades(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0);
+}
+
+mframe_t flyer_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t flyer_move_stand = {
+ FRAME_stand01,
+ FRAME_stand45,
+ flyer_frames_stand,
+ NULL
+};
+
+mframe_t flyer_frames_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL}
+};
+
+mmove_t flyer_move_walk = {
+ FRAME_stand01,
+ FRAME_stand45,
+ flyer_frames_walk,
+ NULL
+};
+
+mframe_t flyer_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL}
+};
+
+mmove_t flyer_move_run = {
+ FRAME_stand01,
+ FRAME_stand45,
+ flyer_frames_run,
+ NULL
+};
+
+mframe_t flyer_frames_kamizake[] = {
+ {ai_charge, 40, flyer_kamikaze_check},
+ {ai_charge, 40, flyer_kamikaze_check},
+ {ai_charge, 40, flyer_kamikaze_check},
+ {ai_charge, 40, flyer_kamikaze_check},
+ {ai_charge, 40, flyer_kamikaze_check}
+};
+
+mmove_t flyer_move_kamikaze = {
+ FRAME_rollr02,
+ FRAME_rollr06,
+ flyer_frames_kamizake,
+ flyer_kamikaze
+};
+
+void
+flyer_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass > 50)
+ {
+ self->monsterinfo.currentmove = &flyer_move_kamikaze;
+ }
+ else
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &flyer_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_run;
+ }
+}
+
+void
+flyer_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass > 50)
+ {
+ flyer_run(self);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_walk;
+ }
+}
+
+void
+flyer_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass > 50)
+ {
+ flyer_run(self);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_stand;
+ }
+}
+
+void
+flyer_kamikaze_explode(edict_t *self)
+{
+ vec3_t dir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.commander && self->monsterinfo.commander->inuse &&
+ !strcmp(self->monsterinfo.commander->classname, "monster_carrier"))
+ {
+ self->monsterinfo.commander->monsterinfo.monster_slots++;
+ }
+
+ if (self->enemy)
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ T_Damage(self->enemy, self, self, dir, self->s.origin,
+ vec3_origin, (int)50, (int)50, DAMAGE_RADIUS, MOD_UNKNOWN);
+ }
+
+ flyer_die(self, NULL, NULL, 0, dir);
+}
+
+void
+flyer_kamikaze(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_kamikaze;
+}
+
+void
+flyer_kamikaze_check(edict_t *self)
+{
+ float dist;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* this needed because we could have gone away before we get here (blocked code) */
+ if (!self->inuse)
+ {
+ return;
+ }
+
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ flyer_kamikaze_explode(self);
+ return;
+ }
+
+ self->goalentity = self->enemy;
+
+ dist = realrange(self, self->enemy);
+
+ if (dist < 90)
+ {
+ flyer_kamikaze_explode(self);
+ }
+}
+
+mframe_t flyer_frames_start[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, flyer_nextmove}
+};
+
+mmove_t flyer_move_start = {
+ FRAME_start01,
+ FRAME_start06,
+ flyer_frames_start,
+ NULL
+};
+
+mframe_t flyer_frames_stop[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, flyer_nextmove}
+};
+
+mmove_t flyer_move_stop = {
+ FRAME_stop01,
+ FRAME_stop07,
+ flyer_frames_stop,
+ NULL
+};
+
+void
+flyer_stop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_stop;
+}
+
+void
+flyer_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+mframe_t flyer_frames_rollright[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_rollright = {
+ FRAME_rollr01,
+ FRAME_rollr09,
+ flyer_frames_rollright,
+ NULL
+};
+
+mframe_t flyer_frames_rollleft[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_rollleft = {
+ FRAME_rollf01,
+ FRAME_rollf09,
+ flyer_frames_rollleft,
+ NULL
+};
+
+mframe_t flyer_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain304,
+ flyer_frames_pain3,
+ flyer_run
+};
+
+mframe_t flyer_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain204,
+ flyer_frames_pain2,
+ flyer_run
+};
+
+mframe_t flyer_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain109,
+ flyer_frames_pain1,
+ flyer_run
+};
+
+mframe_t flyer_frames_defense[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* Hold this frame */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_defense = {
+ FRAME_defens01,
+ FRAME_defens06,
+ flyer_frames_defense,
+ NULL
+};
+
+mframe_t flyer_frames_bankright[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_bankright = {
+ FRAME_bankr01,
+ FRAME_bankr07,
+ flyer_frames_bankright,
+ NULL
+};
+
+mframe_t flyer_frames_bankleft[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_bankleft = {
+ FRAME_bankl01,
+ FRAME_bankl07,
+ flyer_frames_bankleft,
+ NULL
+};
+
+void
+flyer_fire(edict_t *self, int flash_number)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if ((self->s.frame == FRAME_attak204) ||
+ (self->s.frame == FRAME_attak207) ||
+ (self->s.frame == FRAME_attak210))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 1, 1000, flash_number, effect);
+}
+
+void
+flyer_fireleft(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ flyer_fire(self, MZ2_FLYER_BLASTER_1);
+}
+
+void
+flyer_fireright(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ flyer_fire(self, MZ2_FLYER_BLASTER_2);
+}
+
+mframe_t flyer_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flyer_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak217,
+ flyer_frames_attack2,
+ flyer_run
+};
+
+/* circle strafe frames */
+mframe_t flyer_frames_attack3[] = {
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, flyer_fireleft}, /* left gun */
+ {ai_charge, 10, flyer_fireright}, /* right gun */
+ {ai_charge, 10, flyer_fireleft}, /* left gun */
+ {ai_charge, 10, flyer_fireright}, /* right gun */
+ {ai_charge, 10, flyer_fireleft}, /* left gun */
+ {ai_charge, 10, flyer_fireright}, /* right gun */
+ {ai_charge, 10, flyer_fireleft}, /* left gun */
+ {ai_charge, 10, flyer_fireright}, /* right gun */
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, NULL}
+};
+
+mmove_t flyer_move_attack3 = {
+ FRAME_attak201,
+ FRAME_attak217,
+ flyer_frames_attack3,
+ flyer_run
+};
+
+void
+flyer_slash_left(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 0);
+ fire_hit(self, aim, 5, 0);
+ gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+void
+flyer_slash_right(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 0);
+ fire_hit(self, aim, 5, 0);
+ gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+mframe_t flyer_frames_start_melee[] = {
+ {ai_charge, 0, flyer_pop_blades},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flyer_move_start_melee = {
+ FRAME_attak101,
+ FRAME_attak106,
+ flyer_frames_start_melee,
+ flyer_loop_melee
+};
+
+mframe_t flyer_frames_end_melee[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flyer_move_end_melee = {
+ FRAME_attak119,
+ FRAME_attak121,
+ flyer_frames_end_melee,
+ flyer_run
+};
+
+mframe_t flyer_frames_loop_melee[] = {
+ {ai_charge, 0, NULL}, /* Loop Start */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flyer_slash_left}, /* Left Wing Strike */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flyer_slash_right}, /* Right Wing Strike */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL} /* Loop Ends */
+};
+
+mmove_t flyer_move_loop_melee = {
+ FRAME_attak107,
+ FRAME_attak118,
+ flyer_frames_loop_melee,
+ flyer_check_melee
+};
+
+void
+flyer_loop_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_loop_melee;
+}
+
+void
+flyer_attack(edict_t *self)
+{
+ float chance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass > 50)
+ {
+ flyer_run(self);
+ return;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ chance = 0;
+ }
+ else
+ {
+ chance = 1.0 - (0.5 / (float)(skill->value));
+ }
+
+ if (random() > chance)
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ self->monsterinfo.currentmove = &flyer_move_attack2;
+ }
+ else /* circle strafe */
+ {
+ if (random() <= 0.5) /* switch directions */
+ {
+ self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+ }
+
+ self->monsterinfo.attack_state = AS_SLIDING;
+ self->monsterinfo.currentmove = &flyer_move_attack3;
+ }
+}
+
+void
+flyer_setstart(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ nextmove = ACTION_run;
+ self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+void
+flyer_nextmove(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (nextmove == ACTION_attack1)
+ {
+ self->monsterinfo.currentmove = &flyer_move_start_melee;
+ }
+ else if (nextmove == ACTION_attack2)
+ {
+ self->monsterinfo.currentmove = &flyer_move_attack2;
+ }
+ else if (nextmove == ACTION_run)
+ {
+ self->monsterinfo.currentmove = &flyer_move_run;
+ }
+}
+
+void
+flyer_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass > 50)
+ {
+ flyer_run(self);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_start_melee;
+ }
+}
+
+void
+flyer_check_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ if (random() <= 0.8)
+ {
+ self->monsterinfo.currentmove = &flyer_move_loop_melee;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_end_melee;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_end_melee;
+ }
+}
+
+void
+flyer_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ int n;
+
+ /* kamikaze's don't feel pain */
+ if (self->mass != 50)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = rand() % 3;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flyer_move_pain1;
+ }
+ else if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flyer_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flyer_move_pain3;
+ }
+}
+
+void
+flyer_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ BecomeExplosion1(self);
+}
+
+/* kamikaze code .. blow up if blocked */
+int
+flyer_blocked(edict_t *self, float dist)
+{
+ vec3_t origin;
+
+ if (!self)
+ {
+ return 0;
+ }
+
+ /* kamikaze = 100, normal = 50 */
+ if (self->mass == 100)
+ {
+ flyer_kamikaze_check(self);
+
+ /* if the above didn't blow us up (i.e. I got blocked by the player) */
+ if (self->inuse)
+ {
+ if (self->monsterinfo.commander &&
+ self->monsterinfo.commander->inuse &&
+ !strcmp(self->monsterinfo.commander->classname, "monster_carrier"))
+ {
+ self->monsterinfo.commander->monsterinfo.monster_slots++;
+ }
+
+ VectorMA(self->s.origin, -0.02, self->velocity, origin);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_ROCKET_EXPLOSION);
+ gi.WritePosition(origin);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+
+ G_FreeEdict(self);
+ }
+
+ return true;
+ }
+
+ /* we're a normal flyer */
+
+ return false;
+}
+
+/*
+ * QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_flyer(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ /* fix a map bug in jail5.bsp */
+ if (!Q_stricmp(level.mapname, "jail5") && (self->s.origin[2] == -104))
+ {
+ self->targetname = self->target;
+ self->target = NULL;
+ }
+
+ sound_sight = gi.soundindex("flyer/flysght1.wav");
+ sound_idle = gi.soundindex("flyer/flysrch1.wav");
+ sound_pain1 = gi.soundindex("flyer/flypain1.wav");
+ sound_pain2 = gi.soundindex("flyer/flypain2.wav");
+ sound_slash = gi.soundindex("flyer/flyatck2.wav");
+ sound_sproing = gi.soundindex("flyer/flyatck1.wav");
+ sound_die = gi.soundindex("flyer/flydeth1.wav");
+
+ gi.soundindex("flyer/flyatck3.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 16);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->s.sound = gi.soundindex("flyer/flyidle1.wav");
+
+ self->health = 50;
+ self->mass = 50;
+
+ self->pain = flyer_pain;
+ self->die = flyer_die;
+
+ self->monsterinfo.stand = flyer_stand;
+ self->monsterinfo.walk = flyer_walk;
+ self->monsterinfo.run = flyer_run;
+ self->monsterinfo.attack = flyer_attack;
+ self->monsterinfo.melee = flyer_melee;
+ self->monsterinfo.sight = flyer_sight;
+ self->monsterinfo.idle = flyer_idle;
+ self->monsterinfo.blocked = (void *)flyer_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &flyer_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
+
+/* suicide fliers */
+void
+SP_monster_kamikaze(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_sight = gi.soundindex("flyer/flysght1.wav");
+ sound_idle = gi.soundindex("flyer/flysrch1.wav");
+ sound_pain1 = gi.soundindex("flyer/flypain1.wav");
+ sound_pain2 = gi.soundindex("flyer/flypain2.wav");
+ sound_slash = gi.soundindex("flyer/flyatck2.wav");
+ sound_sproing = gi.soundindex("flyer/flyatck1.wav");
+ sound_die = gi.soundindex("flyer/flydeth1.wav");
+
+ gi.soundindex("flyer/flyatck3.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 16);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->s.sound = gi.soundindex("flyer/flyidle1.wav");
+
+ self->s.effects |= EF_ROCKET;
+
+ self->health = 50;
+ self->mass = 100;
+
+ self->pain = flyer_pain;
+ self->die = flyer_die;
+
+ self->monsterinfo.stand = flyer_stand;
+ self->monsterinfo.walk = flyer_walk;
+ self->monsterinfo.run = flyer_run;
+ self->monsterinfo.attack = flyer_attack;
+ self->monsterinfo.melee = flyer_melee;
+ self->monsterinfo.sight = flyer_sight;
+ self->monsterinfo.idle = flyer_idle;
+
+ self->monsterinfo.blocked = (void *)flyer_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &flyer_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/rogue/src/monster/flyer/flyer.h b/rogue/src/monster/flyer/flyer.h
new file mode 100644
index 0000000..0d1b6c8
--- /dev/null
+++ b/rogue/src/monster/flyer/flyer.h
@@ -0,0 +1,164 @@
+/* =======================================================================
+ *
+ * Flyer animations.
+ *
+ * =======================================================================
+ */
+
+#define ACTION_nothing 0
+#define ACTION_attack1 1
+#define ACTION_attack2 2
+#define ACTION_run 3
+#define ACTION_walk 4
+#define FRAME_start01 0
+#define FRAME_start02 1
+#define FRAME_start03 2
+#define FRAME_start04 3
+#define FRAME_start05 4
+#define FRAME_start06 5
+#define FRAME_stop01 6
+#define FRAME_stop02 7
+#define FRAME_stop03 8
+#define FRAME_stop04 9
+#define FRAME_stop05 10
+#define FRAME_stop06 11
+#define FRAME_stop07 12
+#define FRAME_stand01 13
+#define FRAME_stand02 14
+#define FRAME_stand03 15
+#define FRAME_stand04 16
+#define FRAME_stand05 17
+#define FRAME_stand06 18
+#define FRAME_stand07 19
+#define FRAME_stand08 20
+#define FRAME_stand09 21
+#define FRAME_stand10 22
+#define FRAME_stand11 23
+#define FRAME_stand12 24
+#define FRAME_stand13 25
+#define FRAME_stand14 26
+#define FRAME_stand15 27
+#define FRAME_stand16 28
+#define FRAME_stand17 29
+#define FRAME_stand18 30
+#define FRAME_stand19 31
+#define FRAME_stand20 32
+#define FRAME_stand21 33
+#define FRAME_stand22 34
+#define FRAME_stand23 35
+#define FRAME_stand24 36
+#define FRAME_stand25 37
+#define FRAME_stand26 38
+#define FRAME_stand27 39
+#define FRAME_stand28 40
+#define FRAME_stand29 41
+#define FRAME_stand30 42
+#define FRAME_stand31 43
+#define FRAME_stand32 44
+#define FRAME_stand33 45
+#define FRAME_stand34 46
+#define FRAME_stand35 47
+#define FRAME_stand36 48
+#define FRAME_stand37 49
+#define FRAME_stand38 50
+#define FRAME_stand39 51
+#define FRAME_stand40 52
+#define FRAME_stand41 53
+#define FRAME_stand42 54
+#define FRAME_stand43 55
+#define FRAME_stand44 56
+#define FRAME_stand45 57
+#define FRAME_attak101 58
+#define FRAME_attak102 59
+#define FRAME_attak103 60
+#define FRAME_attak104 61
+#define FRAME_attak105 62
+#define FRAME_attak106 63
+#define FRAME_attak107 64
+#define FRAME_attak108 65
+#define FRAME_attak109 66
+#define FRAME_attak110 67
+#define FRAME_attak111 68
+#define FRAME_attak112 69
+#define FRAME_attak113 70
+#define FRAME_attak114 71
+#define FRAME_attak115 72
+#define FRAME_attak116 73
+#define FRAME_attak117 74
+#define FRAME_attak118 75
+#define FRAME_attak119 76
+#define FRAME_attak120 77
+#define FRAME_attak121 78
+#define FRAME_attak201 79
+#define FRAME_attak202 80
+#define FRAME_attak203 81
+#define FRAME_attak204 82
+#define FRAME_attak205 83
+#define FRAME_attak206 84
+#define FRAME_attak207 85
+#define FRAME_attak208 86
+#define FRAME_attak209 87
+#define FRAME_attak210 88
+#define FRAME_attak211 89
+#define FRAME_attak212 90
+#define FRAME_attak213 91
+#define FRAME_attak214 92
+#define FRAME_attak215 93
+#define FRAME_attak216 94
+#define FRAME_attak217 95
+#define FRAME_bankl01 96
+#define FRAME_bankl02 97
+#define FRAME_bankl03 98
+#define FRAME_bankl04 99
+#define FRAME_bankl05 100
+#define FRAME_bankl06 101
+#define FRAME_bankl07 102
+#define FRAME_bankr01 103
+#define FRAME_bankr02 104
+#define FRAME_bankr03 105
+#define FRAME_bankr04 106
+#define FRAME_bankr05 107
+#define FRAME_bankr06 108
+#define FRAME_bankr07 109
+#define FRAME_rollf01 110
+#define FRAME_rollf02 111
+#define FRAME_rollf03 112
+#define FRAME_rollf04 113
+#define FRAME_rollf05 114
+#define FRAME_rollf06 115
+#define FRAME_rollf07 116
+#define FRAME_rollf08 117
+#define FRAME_rollf09 118
+#define FRAME_rollr01 119
+#define FRAME_rollr02 120
+#define FRAME_rollr03 121
+#define FRAME_rollr04 122
+#define FRAME_rollr05 123
+#define FRAME_rollr06 124
+#define FRAME_rollr07 125
+#define FRAME_rollr08 126
+#define FRAME_rollr09 127
+#define FRAME_defens01 128
+#define FRAME_defens02 129
+#define FRAME_defens03 130
+#define FRAME_defens04 131
+#define FRAME_defens05 132
+#define FRAME_defens06 133
+#define FRAME_pain101 134
+#define FRAME_pain102 135
+#define FRAME_pain103 136
+#define FRAME_pain104 137
+#define FRAME_pain105 138
+#define FRAME_pain106 139
+#define FRAME_pain107 140
+#define FRAME_pain108 141
+#define FRAME_pain109 142
+#define FRAME_pain201 143
+#define FRAME_pain202 144
+#define FRAME_pain203 145
+#define FRAME_pain204 146
+#define FRAME_pain301 147
+#define FRAME_pain302 148
+#define FRAME_pain303 149
+#define FRAME_pain304 150
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/gladiator/gladiator.c b/rogue/src/monster/gladiator/gladiator.c
new file mode 100644
index 0000000..05471cf
--- /dev/null
+++ b/rogue/src/monster/gladiator/gladiator.c
@@ -0,0 +1,548 @@
+/* =======================================================================
+ *
+ * Gladiator.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "gladiator.h"
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die;
+static int sound_gun;
+static int sound_cleaver_swing;
+static int sound_cleaver_hit;
+static int sound_cleaver_miss;
+static int sound_idle;
+static int sound_search;
+static int sound_sight;
+
+void
+gladiator_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+gladiator_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+gladiator_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void
+gladiator_cleaver_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladiator_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t gladiator_move_stand = {
+ FRAME_stand1,
+ FRAME_stand7,
+ gladiator_frames_stand,
+ NULL
+};
+
+void
+gladiator_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladiator_move_stand;
+}
+
+mframe_t gladiator_frames_walk[] = {
+ {ai_walk, 15, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 8, NULL}
+};
+
+mmove_t gladiator_move_walk = {
+ FRAME_walk1,
+ FRAME_walk16,
+ gladiator_frames_walk,
+ NULL
+};
+
+void
+gladiator_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladiator_move_walk;
+}
+
+mframe_t gladiator_frames_run[] = {
+ {ai_run, 23, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 21, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 13, NULL}
+};
+
+mmove_t gladiator_move_run = {
+ FRAME_run1,
+ FRAME_run6,
+ gladiator_frames_run,
+ NULL
+};
+
+void
+gladiator_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &gladiator_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gladiator_move_run;
+ }
+}
+
+void
+GaldiatorMelee(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4);
+
+ if (fire_hit(self, aim, (20 + (rand() % 5)), 300))
+ {
+ gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t gladiator_frames_attack_melee[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladiator_cleaver_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GaldiatorMelee},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladiator_cleaver_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GaldiatorMelee},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gladiator_move_attack_melee = {
+ FRAME_melee1,
+ FRAME_melee17,
+ gladiator_frames_attack_melee,
+ gladiator_run
+};
+
+void
+gladiator_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladiator_move_attack_melee;
+}
+
+void
+GladiatorGun(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1],
+ forward, right, start);
+
+ /* calc direction to where we targted */
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_railgun(self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1);
+}
+
+mframe_t gladiator_frames_attack_gun[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GladiatorGun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gladiator_move_attack_gun = {
+ FRAME_attack1,
+ FRAME_attack9,
+ gladiator_frames_attack_gun,
+ gladiator_run
+};
+
+void
+gladiator_attack(edict_t *self)
+{
+ float range;
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* a small safe zone
+ but not for stand-ground ones since players can
+ abuse it by standing still inside this range
+ */
+ if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+ {
+ VectorSubtract(self->s.origin, self->enemy->s.origin, v);
+ range = VectorLength(v);
+
+ if (range <= (MELEE_DISTANCE + 32))
+ {
+ return;
+ }
+ }
+
+ /* charge up the railgun */
+ gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0);
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+ self->monsterinfo.currentmove = &gladiator_move_attack_gun;
+}
+
+mframe_t gladiator_frames_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladiator_move_pain = {
+ FRAME_pain1,
+ FRAME_pain6,
+ gladiator_frames_pain,
+ gladiator_run
+};
+
+mframe_t gladiator_frames_pain_air[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladiator_move_pain_air = {
+ FRAME_painup1,
+ FRAME_painup7,
+ gladiator_frames_pain_air,
+ gladiator_run
+};
+
+void
+gladiator_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ if ((self->velocity[2] > 100) &&
+ (self->monsterinfo.currentmove == &gladiator_move_pain))
+ {
+ self->monsterinfo.currentmove = &gladiator_move_pain_air;
+ }
+
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (self->velocity[2] > 100)
+ {
+ self->monsterinfo.currentmove = &gladiator_move_pain_air;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gladiator_move_pain;
+ }
+}
+
+void
+gladiator_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t gladiator_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladiator_move_death = {
+ FRAME_death1,
+ FRAME_death22,
+ gladiator_frames_death,
+ gladiator_dead
+};
+
+void
+gladiator_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ self->monsterinfo.currentmove = &gladiator_move_death;
+}
+
+qboolean
+gladiator_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_gladiator(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("gladiator/pain.wav");
+ sound_pain2 = gi.soundindex("gladiator/gldpain2.wav");
+ sound_die = gi.soundindex("gladiator/glddeth2.wav");
+ sound_gun = gi.soundindex("gladiator/railgun.wav");
+ sound_cleaver_swing = gi.soundindex("gladiator/melee1.wav");
+ sound_cleaver_hit = gi.soundindex("gladiator/melee2.wav");
+ sound_cleaver_miss = gi.soundindex("gladiator/melee3.wav");
+ sound_idle = gi.soundindex("gladiator/gldidle1.wav");
+ sound_search = gi.soundindex("gladiator/gldsrch1.wav");
+ sound_sight = gi.soundindex("gladiator/sight.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/gladiatr/tris.md2");
+ VectorSet(self->mins, -32, -32, -24);
+ VectorSet(self->maxs, 32, 32, 64);
+
+ self->health = 400;
+ self->gib_health = -175;
+ self->mass = 400;
+
+ self->pain = gladiator_pain;
+ self->die = gladiator_die;
+
+ self->monsterinfo.stand = gladiator_stand;
+ self->monsterinfo.walk = gladiator_walk;
+ self->monsterinfo.run = gladiator_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = gladiator_attack;
+ self->monsterinfo.melee = gladiator_melee;
+ self->monsterinfo.sight = gladiator_sight;
+ self->monsterinfo.idle = gladiator_idle;
+ self->monsterinfo.search = gladiator_search;
+ self->monsterinfo.blocked = gladiator_blocked;
+
+ gi.linkentity(self);
+ self->monsterinfo.currentmove = &gladiator_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/gladiator/gladiator.h b/rogue/src/monster/gladiator/gladiator.h
new file mode 100644
index 0000000..1d9e3f3
--- /dev/null
+++ b/rogue/src/monster/gladiator/gladiator.h
@@ -0,0 +1,98 @@
+/* =======================================================================
+ *
+ * Gladiator animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand1 0
+#define FRAME_stand2 1
+#define FRAME_stand3 2
+#define FRAME_stand4 3
+#define FRAME_stand5 4
+#define FRAME_stand6 5
+#define FRAME_stand7 6
+#define FRAME_walk1 7
+#define FRAME_walk2 8
+#define FRAME_walk3 9
+#define FRAME_walk4 10
+#define FRAME_walk5 11
+#define FRAME_walk6 12
+#define FRAME_walk7 13
+#define FRAME_walk8 14
+#define FRAME_walk9 15
+#define FRAME_walk10 16
+#define FRAME_walk11 17
+#define FRAME_walk12 18
+#define FRAME_walk13 19
+#define FRAME_walk14 20
+#define FRAME_walk15 21
+#define FRAME_walk16 22
+#define FRAME_run1 23
+#define FRAME_run2 24
+#define FRAME_run3 25
+#define FRAME_run4 26
+#define FRAME_run5 27
+#define FRAME_run6 28
+#define FRAME_melee1 29
+#define FRAME_melee2 30
+#define FRAME_melee3 31
+#define FRAME_melee4 32
+#define FRAME_melee5 33
+#define FRAME_melee6 34
+#define FRAME_melee7 35
+#define FRAME_melee8 36
+#define FRAME_melee9 37
+#define FRAME_melee10 38
+#define FRAME_melee11 39
+#define FRAME_melee12 40
+#define FRAME_melee13 41
+#define FRAME_melee14 42
+#define FRAME_melee15 43
+#define FRAME_melee16 44
+#define FRAME_melee17 45
+#define FRAME_attack1 46
+#define FRAME_attack2 47
+#define FRAME_attack3 48
+#define FRAME_attack4 49
+#define FRAME_attack5 50
+#define FRAME_attack6 51
+#define FRAME_attack7 52
+#define FRAME_attack8 53
+#define FRAME_attack9 54
+#define FRAME_pain1 55
+#define FRAME_pain2 56
+#define FRAME_pain3 57
+#define FRAME_pain4 58
+#define FRAME_pain5 59
+#define FRAME_pain6 60
+#define FRAME_death1 61
+#define FRAME_death2 62
+#define FRAME_death3 63
+#define FRAME_death4 64
+#define FRAME_death5 65
+#define FRAME_death6 66
+#define FRAME_death7 67
+#define FRAME_death8 68
+#define FRAME_death9 69
+#define FRAME_death10 70
+#define FRAME_death11 71
+#define FRAME_death12 72
+#define FRAME_death13 73
+#define FRAME_death14 74
+#define FRAME_death15 75
+#define FRAME_death16 76
+#define FRAME_death17 77
+#define FRAME_death18 78
+#define FRAME_death19 79
+#define FRAME_death20 80
+#define FRAME_death21 81
+#define FRAME_death22 82
+#define FRAME_painup1 83
+#define FRAME_painup2 84
+#define FRAME_painup3 85
+#define FRAME_painup4 86
+#define FRAME_painup5 87
+#define FRAME_painup6 88
+#define FRAME_painup7 89
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/gunner/gunner.c b/rogue/src/monster/gunner/gunner.c
new file mode 100644
index 0000000..394e687
--- /dev/null
+++ b/rogue/src/monster/gunner/gunner.c
@@ -0,0 +1,1270 @@
+/* =======================================================================
+ *
+ * Gunner.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "gunner.h"
+
+static int sound_pain;
+static int sound_pain2;
+static int sound_death;
+static int sound_idle;
+static int sound_open;
+static int sound_search;
+static int sound_sight;
+
+qboolean visible(edict_t *self, edict_t *other);
+void GunnerGrenade(edict_t *self);
+void GunnerFire(edict_t *self);
+void gunner_fire_chain(edict_t *self);
+void gunner_refire_chain(edict_t *self);
+
+void gunner_stand(edict_t *self);
+
+void
+gunner_idlesound(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+gunner_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+gunner_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+mframe_t gunner_frames_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_idlesound},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t gunner_move_fidget = {
+ FRAME_stand31,
+ FRAME_stand70,
+ gunner_frames_fidget,
+ gunner_stand
+};
+
+void
+gunner_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ return;
+ }
+
+ if (random() <= 0.05)
+ {
+ self->monsterinfo.currentmove = &gunner_move_fidget;
+ }
+}
+
+mframe_t gunner_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_fidget},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_fidget},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_fidget}
+};
+
+mmove_t gunner_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ gunner_frames_stand,
+ NULL
+};
+
+void
+gunner_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_stand;
+}
+
+mframe_t gunner_frames_walk[] = {
+ {ai_walk, 0, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t gunner_move_walk = {
+ FRAME_walk07,
+ FRAME_walk19,
+ gunner_frames_walk,
+ NULL
+};
+
+void
+gunner_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_walk;
+}
+
+mframe_t gunner_frames_run[] = {
+ {ai_run, 26, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, monster_done_dodge},
+ {ai_run, 15, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 6, NULL}
+};
+
+mmove_t gunner_move_run = {
+ FRAME_run01,
+ FRAME_run08,
+ gunner_frames_run,
+ NULL
+};
+
+void
+gunner_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &gunner_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gunner_move_run;
+ }
+}
+
+mframe_t gunner_frames_runandshoot[] = {
+ {ai_run, 32, NULL},
+ {ai_run, 15, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 18, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 20, NULL}
+};
+
+mmove_t gunner_move_runandshoot = {
+ FRAME_runs01,
+ FRAME_runs06,
+ gunner_frames_runandshoot,
+ NULL
+};
+
+void
+gunner_runandshoot(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_runandshoot;
+}
+
+mframe_t gunner_frames_pain3[] = {
+ {ai_move, -3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t gunner_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain305,
+ gunner_frames_pain3,
+ gunner_run
+};
+
+mframe_t gunner_frames_pain2[] = {
+ {ai_move, -2, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -7, NULL}
+};
+
+mmove_t gunner_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain208,
+ gunner_frames_pain2,
+ gunner_run
+};
+
+mframe_t gunner_frames_pain1[] = {
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gunner_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain118,
+ gunner_frames_pain1,
+ gunner_run
+};
+
+void
+gunner_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ monster_done_dodge(self);
+
+ if (!self->groundentity)
+ {
+ return;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (rand() & 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 10)
+ {
+ self->monsterinfo.currentmove = &gunner_move_pain3;
+ }
+ else if (damage <= 25)
+ {
+ self->monsterinfo.currentmove = &gunner_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gunner_move_pain1;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+}
+
+void
+gunner_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t gunner_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 8, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gunner_move_death = {
+ FRAME_death01,
+ FRAME_death11,
+ gunner_frames_death,
+ gunner_dead
+};
+
+void
+gunner_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &gunner_move_death;
+}
+
+void
+gunner_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+
+ self->maxs[2] = self->monsterinfo.base_height - 32;
+ self->takedamage = DAMAGE_YES;
+
+ if (self->monsterinfo.duck_wait_time < level.time)
+ {
+ self->monsterinfo.duck_wait_time = level.time + 1;
+ }
+
+ gi.linkentity(self);
+}
+
+static void
+gunner_duck_down_think(edict_t *self)
+{
+ gunner_duck_down(self);
+
+ /* rogue code calls duck_down twice, so move this here */
+ if (skill->value >= SKILL_HARD)
+ {
+ if (random() > 0.5)
+ {
+ GunnerGrenade(self);
+ }
+ }
+}
+
+mframe_t gunner_frames_duck[] = {
+ {ai_move, 1, gunner_duck_down_think},
+ {ai_move, 1, NULL},
+ {ai_move, 1, monster_duck_hold},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 0, monster_duck_up},
+ {ai_move, -1, NULL}
+};
+
+mmove_t gunner_move_duck = {
+ FRAME_duck01,
+ FRAME_duck08,
+ gunner_frames_duck,
+ gunner_run
+};
+
+/* gunner dodge moved below so I know about attack sequences */
+void
+gunner_opengun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0);
+}
+
+void
+GunnerFire(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t target;
+ vec3_t aim;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ /* project enemy back a bit and target there */
+ VectorCopy(self->enemy->s.origin, target);
+ VectorMA(target, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+
+ VectorSubtract(target, start, aim);
+ VectorNormalize(aim);
+ monster_fire_bullet(self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+qboolean
+gunner_grenade_check(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ trace_t tr;
+ vec3_t target, dir;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ /* if the player is above my head, use machinegun. */
+
+ /* check for flag telling us that we're blindfiring */
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ if (self->s.origin[2] + self->viewheight <
+ self->monsterinfo.blind_fire_target[2])
+ {
+ return false;
+ }
+ }
+ else if (self->absmax[2] <= self->enemy->absmin[2])
+ {
+ return false;
+ }
+
+ /* check to see that we can trace to the player
+ before we start tossing grenades around. */
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GUNNER_GRENADE_1],
+ forward, right, start);
+
+ /* check for blindfire flag */
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ VectorCopy(self->monsterinfo.blind_fire_target, target);
+ }
+ else
+ {
+ VectorCopy(self->enemy->s.origin, target);
+ }
+
+ /* see if we're too close */
+ VectorSubtract(self->s.origin, target, dir);
+
+ if (VectorLength(dir) < 100)
+ {
+ return false;
+ }
+
+ tr = gi.trace(start, vec3_origin, vec3_origin, target, self, MASK_SHOT);
+
+ if ((tr.ent == self->enemy) || (tr.fraction == 1))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+GunnerGrenade(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t aim;
+ int flash_number;
+ float spread;
+ float pitch = 0;
+ vec3_t target;
+ qboolean blindfire = false;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ blindfire = true;
+ }
+
+ if (self->s.frame == FRAME_attak105)
+ {
+ spread = .02;
+ flash_number = MZ2_GUNNER_GRENADE_1;
+ }
+ else if (self->s.frame == FRAME_attak108)
+ {
+ spread = .05;
+ flash_number = MZ2_GUNNER_GRENADE_2;
+ }
+ else if (self->s.frame == FRAME_attak111)
+ {
+ spread = .08;
+ flash_number = MZ2_GUNNER_GRENADE_3;
+ }
+ else
+ {
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ spread = .11;
+ flash_number = MZ2_GUNNER_GRENADE_4;
+ }
+
+ /* if we're shooting blind and we still can't see our enemy */
+ if ((blindfire) && (!visible(self, self->enemy)))
+ {
+ /* and we have a valid blind_fire_target */
+ if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin))
+ {
+ return;
+ }
+
+ VectorCopy(self->monsterinfo.blind_fire_target, target);
+ }
+ else
+ {
+ VectorCopy(self->s.origin, target);
+ }
+
+ AngleVectors(self->s.angles, forward, right, up);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ float dist;
+
+ VectorSubtract(target, self->s.origin, aim);
+ dist = VectorLength(aim);
+
+ /* aim up if they're on the same level as me and far away. */
+ if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64))
+ {
+ aim[2] += (dist - 512);
+ }
+
+ VectorNormalize(aim);
+ pitch = aim[2];
+
+ if (pitch > 0.4)
+ {
+ pitch = 0.4;
+ }
+ else if (pitch < -0.5)
+ {
+ pitch = -0.5;
+ }
+ }
+
+ VectorMA(forward, spread, right, aim);
+ VectorMA(aim, pitch, up, aim);
+
+ monster_fire_grenade(self, start, aim, 50, 600, flash_number);
+}
+
+mframe_t gunner_frames_attack_chain[] = {
+ {ai_charge, 0, gunner_opengun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gunner_move_attack_chain = {
+ FRAME_attak209,
+ FRAME_attak215,
+ gunner_frames_attack_chain,
+ gunner_fire_chain
+};
+
+mframe_t gunner_frames_fire_chain[] = {
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire}
+};
+
+mmove_t gunner_move_fire_chain = {
+ FRAME_attak216,
+ FRAME_attak223,
+ gunner_frames_fire_chain,
+ gunner_refire_chain
+};
+
+mframe_t gunner_frames_endfire_chain[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gunner_move_endfire_chain = {
+ FRAME_attak224,
+ FRAME_attak230,
+ gunner_frames_endfire_chain,
+ gunner_run
+};
+
+void
+gunner_blind_check(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin,
+ aim);
+ self->ideal_yaw = vectoyaw(aim);
+ }
+}
+
+mframe_t gunner_frames_attack_grenade[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gunner_move_attack_grenade = {
+ FRAME_attak101,
+ FRAME_attak121,
+ gunner_frames_attack_grenade,
+ gunner_run
+};
+
+void
+gunner_attack(edict_t *self)
+{
+ float chance, r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->monsterinfo.attack_state == AS_BLIND)
+ {
+ /* setup shot probabilities */
+ if (self->monsterinfo.blind_fire_delay < 1.0)
+ {
+ chance = 1.0;
+ }
+ else if (self->monsterinfo.blind_fire_delay < 7.5)
+ {
+ chance = 0.4;
+ }
+ else
+ {
+ chance = 0.1;
+ }
+
+ r = random();
+
+ /* minimum of 2 seconds, plus 0-3, after the shots are done */
+ self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random() * 3.0;
+
+ /* don't shoot at the origin */
+ if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin))
+ {
+ return;
+ }
+
+ /* don't shoot if the dice say not to */
+ if (r > chance)
+ {
+ return;
+ }
+
+ /* turn on manual steering to signal both manual steering and blindfire */
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+
+ if (gunner_grenade_check(self))
+ {
+ /* if the check passes, go for the attack */
+ self->monsterinfo.currentmove = &gunner_move_attack_grenade;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ }
+
+ /* turn off blindfire flag */
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ return;
+ }
+
+ /* gunner needs to use his chaingun if he's being attacked by a tesla. */
+ if ((range(self, self->enemy) == RANGE_MELEE) || self->bad_area)
+ {
+ self->monsterinfo.currentmove = &gunner_move_attack_chain;
+ }
+ else
+ {
+ if ((random() <= 0.5) && gunner_grenade_check(self))
+ {
+ self->monsterinfo.currentmove = &gunner_move_attack_grenade;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gunner_move_attack_chain;
+ }
+ }
+}
+
+void
+gunner_fire_chain(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_fire_chain;
+}
+
+void
+gunner_refire_chain(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &gunner_move_fire_chain;
+ return;
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_endfire_chain;
+}
+
+void
+gunner_jump_now(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 100, forward, self->velocity);
+ VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void
+gunner_jump2_now(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 150, forward, self->velocity);
+ VectorMA(self->velocity, 400, up, self->velocity);
+}
+
+void
+gunner_jump_wait_land(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity == NULL)
+ {
+ self->monsterinfo.nextframe = self->s.frame;
+
+ if (monster_jump_finished(self))
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+ }
+ else
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+}
+
+mframe_t gunner_frames_jump[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, gunner_jump_now},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, gunner_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gunner_move_jump = {
+ FRAME_jump01,
+ FRAME_jump10,
+ gunner_frames_jump,
+ gunner_run
+};
+
+mframe_t gunner_frames_jump2[] = {
+ {ai_move, -8, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 0, gunner_jump2_now},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, gunner_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gunner_move_jump2 = {
+ FRAME_jump01,
+ FRAME_jump10,
+ gunner_frames_jump2,
+ gunner_run
+};
+
+void
+gunner_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->enemy->absmin[2] > self->absmin[2])
+ {
+ self->monsterinfo.currentmove = &gunner_move_jump2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gunner_move_jump;
+ }
+}
+
+qboolean
+gunner_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ if (blocked_checkjump(self, dist, 192, 40))
+ {
+ gunner_jump(self);
+ return true;
+ }
+
+ return false;
+}
+
+/* new duck code */
+void
+gunner_duck(edict_t *self, float eta)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &gunner_move_jump2) ||
+ (self->monsterinfo.currentmove == &gunner_move_jump))
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
+ (self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
+ (self->monsterinfo.currentmove == &gunner_move_attack_grenade)
+ )
+ {
+ /* if we're shooting, and not on easy, don't dodge */
+ if (skill->value > SKILL_EASY)
+ {
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ return;
+ }
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ /* stupid dodge */
+ self->monsterinfo.duck_wait_time = level.time + eta + 1;
+ }
+ else
+ {
+ self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+ }
+
+ /* has to be done immediately otherwise he can get stuck */
+ gunner_duck_down(self);
+
+ self->monsterinfo.nextframe = FRAME_duck01;
+ self->monsterinfo.currentmove = &gunner_move_duck;
+ return;
+}
+
+void
+gunner_sidestep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &gunner_move_jump2) ||
+ (self->monsterinfo.currentmove == &gunner_move_jump))
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
+ (self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
+ (self->monsterinfo.currentmove == &gunner_move_attack_grenade)
+ )
+ {
+ /* if we're shooting, and not on easy, don't dodge */
+ if (skill->value > SKILL_EASY)
+ {
+ self->monsterinfo.aiflags &= ~AI_DODGING;
+ return;
+ }
+ }
+
+ if (self->monsterinfo.currentmove != &gunner_move_run)
+ {
+ self->monsterinfo.currentmove = &gunner_move_run;
+ }
+}
+
+/*
+ * QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_gunner(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_death = gi.soundindex("gunner/death1.wav");
+ sound_pain = gi.soundindex("gunner/gunpain2.wav");
+ sound_pain2 = gi.soundindex("gunner/gunpain1.wav");
+ sound_idle = gi.soundindex("gunner/gunidle1.wav");
+ sound_open = gi.soundindex("gunner/gunatck1.wav");
+ sound_search = gi.soundindex("gunner/gunsrch1.wav");
+ sound_sight = gi.soundindex("gunner/sight1.wav");
+
+ gi.soundindex("gunner/gunatck2.wav");
+ gi.soundindex("gunner/gunatck3.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 175;
+ self->gib_health = -70;
+ self->mass = 200;
+
+ self->pain = gunner_pain;
+ self->die = gunner_die;
+
+ self->monsterinfo.stand = gunner_stand;
+ self->monsterinfo.walk = gunner_walk;
+ self->monsterinfo.run = gunner_run;
+ self->monsterinfo.dodge = M_MonsterDodge;
+ self->monsterinfo.duck = gunner_duck;
+ self->monsterinfo.unduck = monster_duck_up;
+ self->monsterinfo.sidestep = gunner_sidestep;
+ self->monsterinfo.attack = gunner_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = gunner_sight;
+ self->monsterinfo.search = gunner_search;
+ self->monsterinfo.blocked = gunner_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &gunner_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ self->monsterinfo.blindfire = true;
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/gunner/gunner.h b/rogue/src/monster/gunner/gunner.h
new file mode 100644
index 0000000..b5bce56
--- /dev/null
+++ b/rogue/src/monster/gunner/gunner.h
@@ -0,0 +1,227 @@
+/* =======================================================================
+ *
+ * Gunner animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_stand31 30
+#define FRAME_stand32 31
+#define FRAME_stand33 32
+#define FRAME_stand34 33
+#define FRAME_stand35 34
+#define FRAME_stand36 35
+#define FRAME_stand37 36
+#define FRAME_stand38 37
+#define FRAME_stand39 38
+#define FRAME_stand40 39
+#define FRAME_stand41 40
+#define FRAME_stand42 41
+#define FRAME_stand43 42
+#define FRAME_stand44 43
+#define FRAME_stand45 44
+#define FRAME_stand46 45
+#define FRAME_stand47 46
+#define FRAME_stand48 47
+#define FRAME_stand49 48
+#define FRAME_stand50 49
+#define FRAME_stand51 50
+#define FRAME_stand52 51
+#define FRAME_stand53 52
+#define FRAME_stand54 53
+#define FRAME_stand55 54
+#define FRAME_stand56 55
+#define FRAME_stand57 56
+#define FRAME_stand58 57
+#define FRAME_stand59 58
+#define FRAME_stand60 59
+#define FRAME_stand61 60
+#define FRAME_stand62 61
+#define FRAME_stand63 62
+#define FRAME_stand64 63
+#define FRAME_stand65 64
+#define FRAME_stand66 65
+#define FRAME_stand67 66
+#define FRAME_stand68 67
+#define FRAME_stand69 68
+#define FRAME_stand70 69
+#define FRAME_walk01 70
+#define FRAME_walk02 71
+#define FRAME_walk03 72
+#define FRAME_walk04 73
+#define FRAME_walk05 74
+#define FRAME_walk06 75
+#define FRAME_walk07 76
+#define FRAME_walk08 77
+#define FRAME_walk09 78
+#define FRAME_walk10 79
+#define FRAME_walk11 80
+#define FRAME_walk12 81
+#define FRAME_walk13 82
+#define FRAME_walk14 83
+#define FRAME_walk15 84
+#define FRAME_walk16 85
+#define FRAME_walk17 86
+#define FRAME_walk18 87
+#define FRAME_walk19 88
+#define FRAME_walk20 89
+#define FRAME_walk21 90
+#define FRAME_walk22 91
+#define FRAME_walk23 92
+#define FRAME_walk24 93
+#define FRAME_run01 94
+#define FRAME_run02 95
+#define FRAME_run03 96
+#define FRAME_run04 97
+#define FRAME_run05 98
+#define FRAME_run06 99
+#define FRAME_run07 100
+#define FRAME_run08 101
+#define FRAME_runs01 102
+#define FRAME_runs02 103
+#define FRAME_runs03 104
+#define FRAME_runs04 105
+#define FRAME_runs05 106
+#define FRAME_runs06 107
+#define FRAME_attak101 108
+#define FRAME_attak102 109
+#define FRAME_attak103 110
+#define FRAME_attak104 111
+#define FRAME_attak105 112
+#define FRAME_attak106 113
+#define FRAME_attak107 114
+#define FRAME_attak108 115
+#define FRAME_attak109 116
+#define FRAME_attak110 117
+#define FRAME_attak111 118
+#define FRAME_attak112 119
+#define FRAME_attak113 120
+#define FRAME_attak114 121
+#define FRAME_attak115 122
+#define FRAME_attak116 123
+#define FRAME_attak117 124
+#define FRAME_attak118 125
+#define FRAME_attak119 126
+#define FRAME_attak120 127
+#define FRAME_attak121 128
+#define FRAME_attak201 129
+#define FRAME_attak202 130
+#define FRAME_attak203 131
+#define FRAME_attak204 132
+#define FRAME_attak205 133
+#define FRAME_attak206 134
+#define FRAME_attak207 135
+#define FRAME_attak208 136
+#define FRAME_attak209 137
+#define FRAME_attak210 138
+#define FRAME_attak211 139
+#define FRAME_attak212 140
+#define FRAME_attak213 141
+#define FRAME_attak214 142
+#define FRAME_attak215 143
+#define FRAME_attak216 144
+#define FRAME_attak217 145
+#define FRAME_attak218 146
+#define FRAME_attak219 147
+#define FRAME_attak220 148
+#define FRAME_attak221 149
+#define FRAME_attak222 150
+#define FRAME_attak223 151
+#define FRAME_attak224 152
+#define FRAME_attak225 153
+#define FRAME_attak226 154
+#define FRAME_attak227 155
+#define FRAME_attak228 156
+#define FRAME_attak229 157
+#define FRAME_attak230 158
+#define FRAME_pain101 159
+#define FRAME_pain102 160
+#define FRAME_pain103 161
+#define FRAME_pain104 162
+#define FRAME_pain105 163
+#define FRAME_pain106 164
+#define FRAME_pain107 165
+#define FRAME_pain108 166
+#define FRAME_pain109 167
+#define FRAME_pain110 168
+#define FRAME_pain111 169
+#define FRAME_pain112 170
+#define FRAME_pain113 171
+#define FRAME_pain114 172
+#define FRAME_pain115 173
+#define FRAME_pain116 174
+#define FRAME_pain117 175
+#define FRAME_pain118 176
+#define FRAME_pain201 177
+#define FRAME_pain202 178
+#define FRAME_pain203 179
+#define FRAME_pain204 180
+#define FRAME_pain205 181
+#define FRAME_pain206 182
+#define FRAME_pain207 183
+#define FRAME_pain208 184
+#define FRAME_pain301 185
+#define FRAME_pain302 186
+#define FRAME_pain303 187
+#define FRAME_pain304 188
+#define FRAME_pain305 189
+#define FRAME_death01 190
+#define FRAME_death02 191
+#define FRAME_death03 192
+#define FRAME_death04 193
+#define FRAME_death05 194
+#define FRAME_death06 195
+#define FRAME_death07 196
+#define FRAME_death08 197
+#define FRAME_death09 198
+#define FRAME_death10 199
+#define FRAME_death11 200
+#define FRAME_duck01 201
+#define FRAME_duck02 202
+#define FRAME_duck03 203
+#define FRAME_duck04 204
+#define FRAME_duck05 205
+#define FRAME_duck06 206
+#define FRAME_duck07 207
+#define FRAME_duck08 208
+#define FRAME_jump01 209
+#define FRAME_jump02 210
+#define FRAME_jump03 211
+#define FRAME_jump04 212
+#define FRAME_jump05 213
+#define FRAME_jump06 214
+#define FRAME_jump07 215
+#define FRAME_jump08 216
+#define FRAME_jump09 217
+#define FRAME_jump10 218
+#define MODEL_SCALE 1.150000
diff --git a/rogue/src/monster/hover/hover.c b/rogue/src/monster/hover/hover.c
new file mode 100644
index 0000000..0e6eb9d
--- /dev/null
+++ b/rogue/src/monster/hover/hover.c
@@ -0,0 +1,851 @@
+/* =======================================================================
+ *
+ * Icarus and Daedalus.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "hover.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+void hover_run(edict_t *self);
+void hover_stand(edict_t *self);
+void hover_dead(edict_t *self);
+void hover_attack(edict_t *self);
+void hover_reattack(edict_t *self);
+void hover_fire_blaster(edict_t *self);
+void hover_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_death1;
+static int sound_death2;
+static int sound_sight;
+static int sound_search1;
+static int sound_search2;
+
+/* daedalus sounds */
+static int daed_sound_pain1;
+static int daed_sound_pain2;
+static int daed_sound_death1;
+static int daed_sound_death2;
+static int daed_sound_sight;
+static int daed_sound_search1;
+static int daed_sound_search2;
+
+void
+hover_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass < 225)
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+hover_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass < 225)
+ {
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+ }
+ }
+ else
+ {
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0);
+ }
+ }
+}
+
+mframe_t hover_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t hover_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ hover_frames_stand,
+ NULL
+};
+
+mframe_t hover_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain309,
+ hover_frames_pain3,
+ hover_run
+};
+
+mframe_t hover_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain212,
+ hover_frames_pain2,
+ hover_run
+};
+
+mframe_t hover_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 4, NULL}
+};
+
+mmove_t hover_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain128,
+ hover_frames_pain1,
+ hover_run
+};
+
+mframe_t hover_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t hover_move_walk = {
+ FRAME_forwrd01,
+ FRAME_forwrd35,
+ hover_frames_walk,
+ NULL
+};
+
+mframe_t hover_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL}
+};
+
+mmove_t hover_move_run = {
+ FRAME_forwrd01,
+ FRAME_forwrd35,
+ hover_frames_run,
+ NULL
+};
+
+mframe_t hover_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 7, NULL}
+};
+
+mmove_t hover_move_death1 = {
+ FRAME_death101,
+ FRAME_death111,
+ hover_frames_death1,
+ hover_dead
+};
+
+mframe_t hover_frames_start_attack[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t hover_move_start_attack = {
+ FRAME_attak101,
+ FRAME_attak103,
+ hover_frames_start_attack,
+ hover_attack
+};
+
+mframe_t hover_frames_attack1[] = {
+ {ai_charge, -10, hover_fire_blaster},
+ {ai_charge, -10, hover_fire_blaster},
+ {ai_charge, 0, hover_reattack},
+};
+
+mmove_t hover_move_attack1 = {
+ FRAME_attak104,
+ FRAME_attak106,
+ hover_frames_attack1,
+ NULL
+};
+
+mframe_t hover_frames_end_attack[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t hover_move_end_attack = {
+ FRAME_attak107,
+ FRAME_attak108,
+ hover_frames_end_attack,
+ hover_run
+};
+
+mframe_t hover_frames_start_attack2[] = {
+ {ai_charge, 15, NULL},
+ {ai_charge, 15, NULL},
+ {ai_charge, 15, NULL}
+};
+
+mmove_t hover_move_start_attack2 = {
+ FRAME_attak101,
+ FRAME_attak103,
+ hover_frames_start_attack2,
+ hover_attack
+};
+
+mframe_t hover_frames_attack2[] = {
+ {ai_charge, 10, hover_fire_blaster},
+ {ai_charge, 10, hover_fire_blaster},
+ {ai_charge, 10, hover_reattack},
+};
+
+mmove_t hover_move_attack2 = {
+ FRAME_attak104,
+ FRAME_attak106,
+ hover_frames_attack2,
+ NULL
+};
+
+mframe_t hover_frames_end_attack2[] = {
+ {ai_charge, 15, NULL},
+ {ai_charge, 15, NULL}
+};
+
+mmove_t hover_move_end_attack2 = {
+ FRAME_attak107,
+ FRAME_attak108,
+ hover_frames_end_attack2,
+ hover_run
+};
+
+void
+hover_reattack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.6)
+ {
+ if (self->monsterinfo.attack_state == AS_STRAIGHT)
+ {
+ self->monsterinfo.currentmove = &hover_move_attack1;
+ return;
+ }
+ else if (self->monsterinfo.attack_state == AS_SLIDING)
+ {
+ self->monsterinfo.currentmove = &hover_move_attack2;
+ return;
+ }
+ else
+ {
+ gi.dprintf("hover_reattack: unexpected state %d\n", self->monsterinfo.attack_state);
+ }
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &hover_move_end_attack;
+}
+
+void
+hover_fire_blaster(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak104)
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ if (self->mass < 200)
+ {
+ monster_fire_blaster(self, start, dir, 1,
+ 1000, MZ2_HOVER_BLASTER_1, effect);
+ }
+ else
+ {
+ monster_fire_blaster2(self, start, dir, 1, 1000,
+ MZ2_DAEDALUS_BLASTER, EF_BLASTER);
+ }
+}
+
+void
+hover_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_stand;
+}
+
+void
+hover_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &hover_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &hover_move_run;
+ }
+}
+
+void
+hover_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_walk;
+}
+
+void
+hover_start_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_start_attack;
+}
+
+void
+hover_attack(edict_t *self)
+{
+ float chance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ chance = 0;
+ }
+ else
+ {
+ chance = 1.0 - (0.5 / (float)(skill->value));
+ }
+
+ if (self->mass > 150) /* the daedalus strafes more */
+ {
+ chance += 0.1;
+ }
+
+ if (random() > chance)
+ {
+ self->monsterinfo.currentmove = &hover_move_attack1;
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ else /* circle strafe */
+ {
+ if (random() <= 0.5) /* switch directions */
+ {
+ self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_attack2;
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+}
+
+void
+hover_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum |= 1; /* support for skins 2 & 3. */
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 25)
+ {
+ if (random() < 0.5)
+ {
+ /* daedalus sounds */
+ if (self->mass < 225)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0);
+ }
+
+ self->monsterinfo.currentmove = &hover_move_pain3;
+ }
+ else
+ {
+ /* daedalus sounds */
+ if (self->mass < 225)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ self->monsterinfo.currentmove = &hover_move_pain2;
+ }
+ }
+ else
+ {
+ if (random() < (0.45 - (0.1 * skill->value)))
+ {
+ /* daedalus sounds */
+ if (self->mass < 225)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0);
+ }
+
+ self->monsterinfo.currentmove = &hover_move_pain1;
+ }
+ else
+ {
+ /* daedalus sounds */
+ if (self->mass < 225)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ self->monsterinfo.currentmove = &hover_move_pain2;
+ }
+ }
+}
+
+void
+hover_deadthink(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->groundentity && (level.time < self->timestamp))
+ {
+ self->nextthink = level.time + FRAMETIME;
+ return;
+ }
+
+ BecomeExplosion1(self);
+}
+
+void
+hover_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->think = hover_deadthink;
+ self->nextthink = level.time + FRAMETIME;
+ self->timestamp = level.time + 15;
+ gi.linkentity(self);
+}
+
+void
+hover_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.effects = 0;
+ self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex( "misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ if (self->mass < 225)
+ {
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+ }
+ }
+ else
+ {
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0);
+ }
+ }
+
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &hover_move_death1;
+}
+
+qboolean
+hover_blocked(edict_t *self, float dist)
+{
+ return false;
+}
+
+
+/*
+ * QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ *
+ * QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ * This is the improved icarus monster.
+ */
+void
+SP_monster_hover(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2");
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 32);
+
+ self->health = 240;
+ self->gib_health = -100;
+ self->mass = 150;
+
+ self->pain = hover_pain;
+ self->die = hover_die;
+
+ self->monsterinfo.stand = hover_stand;
+ self->monsterinfo.walk = hover_walk;
+ self->monsterinfo.run = hover_run;
+ self->monsterinfo.attack = hover_start_attack;
+ self->monsterinfo.sight = hover_sight;
+ self->monsterinfo.search = hover_search;
+ self->monsterinfo.blocked = hover_blocked;
+
+ if (strcmp(self->classname, "monster_daedalus") == 0)
+ {
+ self->health = 450;
+ self->mass = 225;
+ self->yaw_speed = 25;
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+ self->monsterinfo.power_armor_power = 100;
+ self->s.sound = gi.soundindex("daedalus/daedidle1.wav");
+ daed_sound_pain1 = gi.soundindex("daedalus/daedpain1.wav");
+ daed_sound_pain2 = gi.soundindex("daedalus/daedpain2.wav");
+ daed_sound_death1 = gi.soundindex("daedalus/daeddeth1.wav");
+ daed_sound_death2 = gi.soundindex("daedalus/daeddeth2.wav");
+ daed_sound_sight = gi.soundindex("daedalus/daedsght1.wav");
+ daed_sound_search1 = gi.soundindex("daedalus/daedsrch1.wav");
+ daed_sound_search2 = gi.soundindex("daedalus/daedsrch2.wav");
+ gi.soundindex("tank/tnkatck3.wav");
+ }
+ else
+ {
+ sound_pain1 = gi.soundindex("hover/hovpain1.wav");
+ sound_pain2 = gi.soundindex("hover/hovpain2.wav");
+ sound_death1 = gi.soundindex("hover/hovdeth1.wav");
+ sound_death2 = gi.soundindex("hover/hovdeth2.wav");
+ sound_sight = gi.soundindex("hover/hovsght1.wav");
+ sound_search1 = gi.soundindex("hover/hovsrch1.wav");
+ sound_search2 = gi.soundindex("hover/hovsrch2.wav");
+ gi.soundindex("hover/hovatck1.wav");
+
+ self->s.sound = gi.soundindex("hover/hovidle1.wav");
+ }
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &hover_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+
+ if (strcmp(self->classname, "monster_daedalus") == 0)
+ {
+ self->s.skinnum = 2;
+ }
+}
+
diff --git a/rogue/src/monster/hover/hover.h b/rogue/src/monster/hover/hover.h
new file mode 100644
index 0000000..541bf3f
--- /dev/null
+++ b/rogue/src/monster/hover/hover.h
@@ -0,0 +1,213 @@
+/* =======================================================================
+ *
+ * Icarus and Daedalus animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_forwrd01 30
+#define FRAME_forwrd02 31
+#define FRAME_forwrd03 32
+#define FRAME_forwrd04 33
+#define FRAME_forwrd05 34
+#define FRAME_forwrd06 35
+#define FRAME_forwrd07 36
+#define FRAME_forwrd08 37
+#define FRAME_forwrd09 38
+#define FRAME_forwrd10 39
+#define FRAME_forwrd11 40
+#define FRAME_forwrd12 41
+#define FRAME_forwrd13 42
+#define FRAME_forwrd14 43
+#define FRAME_forwrd15 44
+#define FRAME_forwrd16 45
+#define FRAME_forwrd17 46
+#define FRAME_forwrd18 47
+#define FRAME_forwrd19 48
+#define FRAME_forwrd20 49
+#define FRAME_forwrd21 50
+#define FRAME_forwrd22 51
+#define FRAME_forwrd23 52
+#define FRAME_forwrd24 53
+#define FRAME_forwrd25 54
+#define FRAME_forwrd26 55
+#define FRAME_forwrd27 56
+#define FRAME_forwrd28 57
+#define FRAME_forwrd29 58
+#define FRAME_forwrd30 59
+#define FRAME_forwrd31 60
+#define FRAME_forwrd32 61
+#define FRAME_forwrd33 62
+#define FRAME_forwrd34 63
+#define FRAME_forwrd35 64
+#define FRAME_stop101 65
+#define FRAME_stop102 66
+#define FRAME_stop103 67
+#define FRAME_stop104 68
+#define FRAME_stop105 69
+#define FRAME_stop106 70
+#define FRAME_stop107 71
+#define FRAME_stop108 72
+#define FRAME_stop109 73
+#define FRAME_stop201 74
+#define FRAME_stop202 75
+#define FRAME_stop203 76
+#define FRAME_stop204 77
+#define FRAME_stop205 78
+#define FRAME_stop206 79
+#define FRAME_stop207 80
+#define FRAME_stop208 81
+#define FRAME_takeof01 82
+#define FRAME_takeof02 83
+#define FRAME_takeof03 84
+#define FRAME_takeof04 85
+#define FRAME_takeof05 86
+#define FRAME_takeof06 87
+#define FRAME_takeof07 88
+#define FRAME_takeof08 89
+#define FRAME_takeof09 90
+#define FRAME_takeof10 91
+#define FRAME_takeof11 92
+#define FRAME_takeof12 93
+#define FRAME_takeof13 94
+#define FRAME_takeof14 95
+#define FRAME_takeof15 96
+#define FRAME_takeof16 97
+#define FRAME_takeof17 98
+#define FRAME_takeof18 99
+#define FRAME_takeof19 100
+#define FRAME_takeof20 101
+#define FRAME_takeof21 102
+#define FRAME_takeof22 103
+#define FRAME_takeof23 104
+#define FRAME_takeof24 105
+#define FRAME_takeof25 106
+#define FRAME_takeof26 107
+#define FRAME_takeof27 108
+#define FRAME_takeof28 109
+#define FRAME_takeof29 110
+#define FRAME_takeof30 111
+#define FRAME_land01 112
+#define FRAME_pain101 113
+#define FRAME_pain102 114
+#define FRAME_pain103 115
+#define FRAME_pain104 116
+#define FRAME_pain105 117
+#define FRAME_pain106 118
+#define FRAME_pain107 119
+#define FRAME_pain108 120
+#define FRAME_pain109 121
+#define FRAME_pain110 122
+#define FRAME_pain111 123
+#define FRAME_pain112 124
+#define FRAME_pain113 125
+#define FRAME_pain114 126
+#define FRAME_pain115 127
+#define FRAME_pain116 128
+#define FRAME_pain117 129
+#define FRAME_pain118 130
+#define FRAME_pain119 131
+#define FRAME_pain120 132
+#define FRAME_pain121 133
+#define FRAME_pain122 134
+#define FRAME_pain123 135
+#define FRAME_pain124 136
+#define FRAME_pain125 137
+#define FRAME_pain126 138
+#define FRAME_pain127 139
+#define FRAME_pain128 140
+#define FRAME_pain201 141
+#define FRAME_pain202 142
+#define FRAME_pain203 143
+#define FRAME_pain204 144
+#define FRAME_pain205 145
+#define FRAME_pain206 146
+#define FRAME_pain207 147
+#define FRAME_pain208 148
+#define FRAME_pain209 149
+#define FRAME_pain210 150
+#define FRAME_pain211 151
+#define FRAME_pain212 152
+#define FRAME_pain301 153
+#define FRAME_pain302 154
+#define FRAME_pain303 155
+#define FRAME_pain304 156
+#define FRAME_pain305 157
+#define FRAME_pain306 158
+#define FRAME_pain307 159
+#define FRAME_pain308 160
+#define FRAME_pain309 161
+#define FRAME_death101 162
+#define FRAME_death102 163
+#define FRAME_death103 164
+#define FRAME_death104 165
+#define FRAME_death105 166
+#define FRAME_death106 167
+#define FRAME_death107 168
+#define FRAME_death108 169
+#define FRAME_death109 170
+#define FRAME_death110 171
+#define FRAME_death111 172
+#define FRAME_backwd01 173
+#define FRAME_backwd02 174
+#define FRAME_backwd03 175
+#define FRAME_backwd04 176
+#define FRAME_backwd05 177
+#define FRAME_backwd06 178
+#define FRAME_backwd07 179
+#define FRAME_backwd08 180
+#define FRAME_backwd09 181
+#define FRAME_backwd10 182
+#define FRAME_backwd11 183
+#define FRAME_backwd12 184
+#define FRAME_backwd13 185
+#define FRAME_backwd14 186
+#define FRAME_backwd15 187
+#define FRAME_backwd16 188
+#define FRAME_backwd17 189
+#define FRAME_backwd18 190
+#define FRAME_backwd19 191
+#define FRAME_backwd20 192
+#define FRAME_backwd21 193
+#define FRAME_backwd22 194
+#define FRAME_backwd23 195
+#define FRAME_backwd24 196
+#define FRAME_attak101 197
+#define FRAME_attak102 198
+#define FRAME_attak103 199
+#define FRAME_attak104 200
+#define FRAME_attak105 201
+#define FRAME_attak106 202
+#define FRAME_attak107 203
+#define FRAME_attak108 204
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/infantry/infantry.c b/rogue/src/monster/infantry/infantry.c
new file mode 100644
index 0000000..35cbdfa
--- /dev/null
+++ b/rogue/src/monster/infantry/infantry.c
@@ -0,0 +1,977 @@
+/* =======================================================================
+ *
+ * Infantry.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "infantry.h"
+
+void InfantryMachineGun(edict_t *self);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die1;
+static int sound_die2;
+
+static int sound_gunshot;
+static int sound_weapon_cock;
+static int sound_punch_swing;
+static int sound_punch_hit;
+static int sound_sight;
+static int sound_search;
+static int sound_idle;
+
+mframe_t infantry_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t infantry_move_stand = {
+ FRAME_stand50,
+ FRAME_stand71,
+ infantry_frames_stand,
+ NULL
+};
+
+void
+infantry_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &infantry_move_stand;
+}
+
+mframe_t infantry_frames_fidget[] = {
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 3, NULL},
+ {ai_stand, 6, NULL},
+ {ai_stand, 3, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -2, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -3, NULL},
+ {ai_stand, -2, NULL},
+ {ai_stand, -3, NULL},
+ {ai_stand, -3, NULL},
+ {ai_stand, -2, NULL}
+};
+
+mmove_t infantry_move_fidget = {
+ FRAME_stand01,
+ FRAME_stand49,
+ infantry_frames_fidget,
+ infantry_stand
+};
+
+void
+infantry_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &infantry_move_fidget;
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t infantry_frames_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL}
+};
+
+mmove_t infantry_move_walk = {
+ FRAME_walk03,
+ FRAME_walk14,
+ infantry_frames_walk,
+ NULL
+};
+
+void
+infantry_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &infantry_move_walk;
+}
+
+mframe_t infantry_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 20, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 7, monster_done_dodge},
+ {ai_run, 30, NULL},
+ {ai_run, 35, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 6, NULL}
+};
+
+mmove_t infantry_move_run = {
+ FRAME_run01,
+ FRAME_run08,
+ infantry_frames_run,
+ NULL
+};
+
+void
+infantry_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &infantry_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_run;
+ }
+}
+
+mframe_t infantry_frames_pain1[] = {
+ {ai_move, -3, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t infantry_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain110,
+ infantry_frames_pain1,
+ infantry_run
+};
+
+mframe_t infantry_frames_pain2[] = {
+ {ai_move, -3, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t infantry_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain210,
+ infantry_frames_pain2,
+ infantry_run
+};
+
+void
+infantry_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (!self->groundentity)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = rand() % 2;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &infantry_move_pain1;
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_pain2;
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ /* clear duck flag */
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+}
+
+vec3_t aimangles[] = {
+ {0.0, 5.0, 0.0},
+ {10.0, 15.0, 0.0},
+ {20.0, 25.0, 0.0},
+ {25.0, 35.0, 0.0},
+ {30.0, 40.0, 0.0},
+ {30.0, 45.0, 0.0},
+ {25.0, 50.0, 0.0},
+ {20.0, 40.0, 0.0},
+ {15.0, 35.0, 0.0},
+ {40.0, 35.0, 0.0},
+ {70.0, 35.0, 0.0},
+ {90.0, 35.0, 0.0}
+};
+
+void
+InfantryMachineGun(edict_t *self)
+{
+ vec3_t start, target;
+ vec3_t forward, right;
+ vec3_t vec;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* new attack start frame */
+ if (self->s.frame == FRAME_attak104)
+ {
+ flash_number = MZ2_INFANTRY_MACHINEGUN_1;
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy && self->enemy->inuse)
+ {
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+ }
+ else
+ {
+ AngleVectors(self->s.angles, forward, right, NULL);
+ }
+ }
+ else
+ {
+ flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorSubtract(self->s.angles, aimangles[flash_number - MZ2_INFANTRY_MACHINEGUN_2], vec);
+ AngleVectors(vec, forward, NULL, NULL);
+ }
+
+ monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void
+infantry_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+infantry_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ gi.linkentity(self);
+
+ M_FlyCheck(self);
+}
+
+mframe_t infantry_frames_death1[] = {
+ {ai_move, -4, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -3, NULL}
+};
+
+mmove_t infantry_move_death1 = {
+ FRAME_death101,
+ FRAME_death120,
+ infantry_frames_death1,
+ infantry_dead
+};
+
+/* Off with his head */
+mframe_t infantry_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, InfantryMachineGun},
+ {ai_move, -2, InfantryMachineGun},
+ {ai_move, -3, InfantryMachineGun},
+ {ai_move, -1, InfantryMachineGun},
+ {ai_move, -2, InfantryMachineGun},
+ {ai_move, 0, InfantryMachineGun},
+ {ai_move, 2, InfantryMachineGun},
+ {ai_move, 2, InfantryMachineGun},
+ {ai_move, 3, InfantryMachineGun},
+ {ai_move, -10, InfantryMachineGun},
+ {ai_move, -7, InfantryMachineGun},
+ {ai_move, -8, InfantryMachineGun},
+ {ai_move, -6, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_death2 = {
+ FRAME_death201,
+ FRAME_death225,
+ infantry_frames_death2,
+ infantry_dead
+};
+
+mframe_t infantry_frames_death3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -11, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -11, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_death3 = {
+ FRAME_death301,
+ FRAME_death309,
+ infantry_frames_death3,
+ infantry_dead
+};
+
+void
+infantry_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum = 1; /* switch to bloody skin */
+
+ n = rand() % 3;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &infantry_move_death1;
+ gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+ }
+ else if (n == 1)
+ {
+ self->monsterinfo.currentmove = &infantry_move_death2;
+ gi.sound(self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_death3;
+ gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t infantry_frames_duck[] = {
+ {ai_move, -2, monster_duck_down},
+ {ai_move, -5, monster_duck_hold},
+ {ai_move, 3, NULL},
+ {ai_move, 4, monster_duck_up},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_duck = {
+ FRAME_duck01,
+ FRAME_duck05,
+ infantry_frames_duck,
+ infantry_run
+};
+
+void
+infantry_cock_gun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0);
+}
+
+void
+infantry_fire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ InfantryMachineGun(self);
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+void
+infantry_fire_prep(edict_t *self)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ n = (rand() & 15) + 3 + 1;
+ self->monsterinfo.pausetime = level.time + n * FRAMETIME;
+}
+
+mframe_t infantry_frames_attack1[] = {
+ {ai_charge, -3, NULL}, /* 101 */
+ {ai_charge, -2, NULL}, /* 102 */
+ {ai_charge, -1, infantry_fire_prep}, /* 103 */
+ {ai_charge, 5, infantry_fire}, /* 104 */
+ {ai_charge, 1, NULL}, /* 105 */
+ {ai_charge, -3, NULL}, /* 106 */
+ {ai_charge, -2, NULL}, /* 107 */
+ {ai_charge, 2, infantry_cock_gun}, /* 108 */
+ {ai_charge, 1, NULL}, /* 109 */
+ {ai_charge, 1, NULL}, /* 110 */
+ {ai_charge, -1, NULL}, /* 111 */
+ {ai_charge, 0, NULL}, /* 112 */
+ {ai_charge, -1, NULL}, /* 113 */
+ {ai_charge, -1, NULL}, /* 114 */
+ {ai_charge, 4, NULL} /* 115 */
+};
+
+mmove_t infantry_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak115,
+ infantry_frames_attack1,
+ infantry_run
+};
+
+void
+infantry_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0);
+}
+
+void
+infantry_smack(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 0);
+
+ if (fire_hit(self, aim, (5 + (rand() % 5)), 50))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t infantry_frames_attack2[] = {
+ {ai_charge, 3, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 0, infantry_swing},
+ {ai_charge, 8, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 8, infantry_smack},
+ {ai_charge, 6, NULL},
+ {ai_charge, 3, NULL},
+};
+
+mmove_t infantry_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak208,
+ infantry_frames_attack2,
+ infantry_run
+};
+
+void
+infantry_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ self->monsterinfo.currentmove = &infantry_move_attack2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_attack1;
+ }
+}
+
+void
+infantry_jump_now(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 100, forward, self->velocity);
+ VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void
+infantry_jump2_now(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 150, forward, self->velocity);
+ VectorMA(self->velocity, 400, up, self->velocity);
+}
+
+void
+infantry_jump_wait_land(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity == NULL)
+ {
+ self->monsterinfo.nextframe = self->s.frame;
+
+ if (monster_jump_finished(self))
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+ }
+ else
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+}
+
+mframe_t infantry_frames_jump[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, infantry_jump_now},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, infantry_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_jump = {
+ FRAME_jump01,
+ FRAME_jump10,
+ infantry_frames_jump,
+ infantry_run
+};
+
+mframe_t infantry_frames_jump2[] = {
+ {ai_move, -8, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 0, infantry_jump2_now},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, infantry_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_jump2 = {
+ FRAME_jump01,
+ FRAME_jump10,
+ infantry_frames_jump2,
+ infantry_run
+};
+
+void
+infantry_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->enemy->absmin[2] > self->absmin[2])
+ {
+ self->monsterinfo.currentmove = &infantry_move_jump2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_jump;
+ }
+}
+
+qboolean
+infantry_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkjump(self, dist, 192, 40))
+ {
+ infantry_jump(self);
+ return true;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+infantry_duck(edict_t *self, float eta)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we're jumping, don't dodge */
+ if ((self->monsterinfo.currentmove == &infantry_move_jump) ||
+ (self->monsterinfo.currentmove == &infantry_move_jump2))
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &infantry_move_attack1) ||
+ (self->monsterinfo.currentmove == &infantry_move_attack2))
+ {
+ /* if we're shooting, and not on easy, don't dodge */
+ if (skill->value > SKILL_EASY)
+ {
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ return;
+ }
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ /* stupid dodge */
+ self->monsterinfo.duck_wait_time = level.time + eta + 1;
+ }
+ else
+ {
+ self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+ }
+
+ /* has to be done immediately otherwise he can get stuck */
+ monster_duck_down(self);
+
+ self->monsterinfo.nextframe = FRAME_duck01;
+ self->monsterinfo.currentmove = &infantry_move_duck;
+ return;
+}
+
+void
+infantry_sidestep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we're jumping, don't dodge */
+ if ((self->monsterinfo.currentmove == &infantry_move_jump) ||
+ (self->monsterinfo.currentmove == &infantry_move_jump2))
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &infantry_move_attack1) ||
+ (self->monsterinfo.currentmove == &infantry_move_attack2))
+ {
+ /* if we're shooting, and not on easy, don't dodge */
+ if (skill->value > SKILL_EASY)
+ {
+ self->monsterinfo.aiflags &= ~AI_DODGING;
+ return;
+ }
+ }
+
+ if (self->monsterinfo.currentmove != &infantry_move_run)
+ {
+ self->monsterinfo.currentmove = &infantry_move_run;
+ }
+}
+
+/*
+ * QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_infantry(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("infantry/infpain1.wav");
+ sound_pain2 = gi.soundindex("infantry/infpain2.wav");
+ sound_die1 = gi.soundindex("infantry/infdeth1.wav");
+ sound_die2 = gi.soundindex("infantry/infdeth2.wav");
+
+ sound_gunshot = gi.soundindex("infantry/infatck1.wav");
+ sound_weapon_cock = gi.soundindex("infantry/infatck3.wav");
+ sound_punch_swing = gi.soundindex("infantry/infatck2.wav");
+ sound_punch_hit = gi.soundindex("infantry/melee2.wav");
+
+ sound_sight = gi.soundindex("infantry/infsght1.wav");
+ sound_search = gi.soundindex("infantry/infsrch1.wav");
+ sound_idle = gi.soundindex("infantry/infidle1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 100;
+ self->gib_health = -40;
+ self->mass = 200;
+
+ self->pain = infantry_pain;
+ self->die = infantry_die;
+
+ self->monsterinfo.stand = infantry_stand;
+ self->monsterinfo.walk = infantry_walk;
+ self->monsterinfo.run = infantry_run;
+ self->monsterinfo.dodge = M_MonsterDodge;
+ self->monsterinfo.duck = infantry_duck;
+ self->monsterinfo.unduck = monster_duck_up;
+ self->monsterinfo.sidestep = infantry_sidestep;
+ self->monsterinfo.attack = infantry_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = infantry_sight;
+ self->monsterinfo.idle = infantry_fidget;
+ self->monsterinfo.blocked = infantry_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &infantry_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/infantry/infantry.h b/rogue/src/monster/infantry/infantry.h
new file mode 100644
index 0000000..42e9b7d
--- /dev/null
+++ b/rogue/src/monster/infantry/infantry.h
@@ -0,0 +1,225 @@
+/* =======================================================================
+ *
+ * Infantry animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_gun02 0
+#define FRAME_stand01 1
+#define FRAME_stand02 2
+#define FRAME_stand03 3
+#define FRAME_stand04 4
+#define FRAME_stand05 5
+#define FRAME_stand06 6
+#define FRAME_stand07 7
+#define FRAME_stand08 8
+#define FRAME_stand09 9
+#define FRAME_stand10 10
+#define FRAME_stand11 11
+#define FRAME_stand12 12
+#define FRAME_stand13 13
+#define FRAME_stand14 14
+#define FRAME_stand15 15
+#define FRAME_stand16 16
+#define FRAME_stand17 17
+#define FRAME_stand18 18
+#define FRAME_stand19 19
+#define FRAME_stand20 20
+#define FRAME_stand21 21
+#define FRAME_stand22 22
+#define FRAME_stand23 23
+#define FRAME_stand24 24
+#define FRAME_stand25 25
+#define FRAME_stand26 26
+#define FRAME_stand27 27
+#define FRAME_stand28 28
+#define FRAME_stand29 29
+#define FRAME_stand30 30
+#define FRAME_stand31 31
+#define FRAME_stand32 32
+#define FRAME_stand33 33
+#define FRAME_stand34 34
+#define FRAME_stand35 35
+#define FRAME_stand36 36
+#define FRAME_stand37 37
+#define FRAME_stand38 38
+#define FRAME_stand39 39
+#define FRAME_stand40 40
+#define FRAME_stand41 41
+#define FRAME_stand42 42
+#define FRAME_stand43 43
+#define FRAME_stand44 44
+#define FRAME_stand45 45
+#define FRAME_stand46 46
+#define FRAME_stand47 47
+#define FRAME_stand48 48
+#define FRAME_stand49 49
+#define FRAME_stand50 50
+#define FRAME_stand51 51
+#define FRAME_stand52 52
+#define FRAME_stand53 53
+#define FRAME_stand54 54
+#define FRAME_stand55 55
+#define FRAME_stand56 56
+#define FRAME_stand57 57
+#define FRAME_stand58 58
+#define FRAME_stand59 59
+#define FRAME_stand60 60
+#define FRAME_stand61 61
+#define FRAME_stand62 62
+#define FRAME_stand63 63
+#define FRAME_stand64 64
+#define FRAME_stand65 65
+#define FRAME_stand66 66
+#define FRAME_stand67 67
+#define FRAME_stand68 68
+#define FRAME_stand69 69
+#define FRAME_stand70 70
+#define FRAME_stand71 71
+#define FRAME_walk01 72
+#define FRAME_walk02 73
+#define FRAME_walk03 74
+#define FRAME_walk04 75
+#define FRAME_walk05 76
+#define FRAME_walk06 77
+#define FRAME_walk07 78
+#define FRAME_walk08 79
+#define FRAME_walk09 80
+#define FRAME_walk10 81
+#define FRAME_walk11 82
+#define FRAME_walk12 83
+#define FRAME_walk13 84
+#define FRAME_walk14 85
+#define FRAME_walk15 86
+#define FRAME_walk16 87
+#define FRAME_walk17 88
+#define FRAME_walk18 89
+#define FRAME_walk19 90
+#define FRAME_walk20 91
+#define FRAME_run01 92
+#define FRAME_run02 93
+#define FRAME_run03 94
+#define FRAME_run04 95
+#define FRAME_run05 96
+#define FRAME_run06 97
+#define FRAME_run07 98
+#define FRAME_run08 99
+#define FRAME_pain101 100
+#define FRAME_pain102 101
+#define FRAME_pain103 102
+#define FRAME_pain104 103
+#define FRAME_pain105 104
+#define FRAME_pain106 105
+#define FRAME_pain107 106
+#define FRAME_pain108 107
+#define FRAME_pain109 108
+#define FRAME_pain110 109
+#define FRAME_pain201 110
+#define FRAME_pain202 111
+#define FRAME_pain203 112
+#define FRAME_pain204 113
+#define FRAME_pain205 114
+#define FRAME_pain206 115
+#define FRAME_pain207 116
+#define FRAME_pain208 117
+#define FRAME_pain209 118
+#define FRAME_pain210 119
+#define FRAME_duck01 120
+#define FRAME_duck02 121
+#define FRAME_duck03 122
+#define FRAME_duck04 123
+#define FRAME_duck05 124
+#define FRAME_death101 125
+#define FRAME_death102 126
+#define FRAME_death103 127
+#define FRAME_death104 128
+#define FRAME_death105 129
+#define FRAME_death106 130
+#define FRAME_death107 131
+#define FRAME_death108 132
+#define FRAME_death109 133
+#define FRAME_death110 134
+#define FRAME_death111 135
+#define FRAME_death112 136
+#define FRAME_death113 137
+#define FRAME_death114 138
+#define FRAME_death115 139
+#define FRAME_death116 140
+#define FRAME_death117 141
+#define FRAME_death118 142
+#define FRAME_death119 143
+#define FRAME_death120 144
+#define FRAME_death201 145
+#define FRAME_death202 146
+#define FRAME_death203 147
+#define FRAME_death204 148
+#define FRAME_death205 149
+#define FRAME_death206 150
+#define FRAME_death207 151
+#define FRAME_death208 152
+#define FRAME_death209 153
+#define FRAME_death210 154
+#define FRAME_death211 155
+#define FRAME_death212 156
+#define FRAME_death213 157
+#define FRAME_death214 158
+#define FRAME_death215 159
+#define FRAME_death216 160
+#define FRAME_death217 161
+#define FRAME_death218 162
+#define FRAME_death219 163
+#define FRAME_death220 164
+#define FRAME_death221 165
+#define FRAME_death222 166
+#define FRAME_death223 167
+#define FRAME_death224 168
+#define FRAME_death225 169
+#define FRAME_death301 170
+#define FRAME_death302 171
+#define FRAME_death303 172
+#define FRAME_death304 173
+#define FRAME_death305 174
+#define FRAME_death306 175
+#define FRAME_death307 176
+#define FRAME_death308 177
+#define FRAME_death309 178
+#define FRAME_block01 179
+#define FRAME_block02 180
+#define FRAME_block03 181
+#define FRAME_block04 182
+#define FRAME_block05 183
+#define FRAME_attak101 184
+#define FRAME_attak102 185
+#define FRAME_attak103 186
+#define FRAME_attak104 187
+#define FRAME_attak105 188
+#define FRAME_attak106 189
+#define FRAME_attak107 190
+#define FRAME_attak108 191
+#define FRAME_attak109 192
+#define FRAME_attak110 193
+#define FRAME_attak111 194
+#define FRAME_attak112 195
+#define FRAME_attak113 196
+#define FRAME_attak114 197
+#define FRAME_attak115 198
+#define FRAME_attak201 199
+#define FRAME_attak202 200
+#define FRAME_attak203 201
+#define FRAME_attak204 202
+#define FRAME_attak205 203
+#define FRAME_attak206 204
+#define FRAME_attak207 205
+#define FRAME_attak208 206
+#define FRAME_jump01 207
+#define FRAME_jump02 208
+#define FRAME_jump03 209
+#define FRAME_jump04 210
+#define FRAME_jump05 211
+#define FRAME_jump06 212
+#define FRAME_jump07 213
+#define FRAME_jump08 214
+#define FRAME_jump09 215
+#define FRAME_jump10 216
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/insane/insane.c b/rogue/src/monster/insane/insane.c
new file mode 100644
index 0000000..257e339
--- /dev/null
+++ b/rogue/src/monster/insane/insane.c
@@ -0,0 +1,934 @@
+/* =======================================================================
+ *
+ * The insane earth soldiers.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "insane.h"
+
+#define SPAWNFLAG_CRUSIFIED 8
+
+static int sound_fist;
+static int sound_shake;
+static int sound_moan;
+static int sound_scream[8];
+
+void insane_stand(edict_t *self);
+void insane_dead(edict_t *self);
+void insane_cross(edict_t *self);
+void insane_walk(edict_t *self);
+void insane_run(edict_t *self);
+void insane_checkdown(edict_t *self);
+void insane_checkup(edict_t *self);
+void insane_onground(edict_t *self);
+
+void
+insane_fist(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0);
+}
+
+void
+insane_shake(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0);
+}
+
+void
+insane_moan(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* suppress screaming so pain sounds can play */
+ if (self->fly_sound_debounce_time > level.time)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0);
+}
+
+void
+insane_scream(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* suppress screaming so pain sounds can play */
+ if (self->fly_sound_debounce_time > level.time)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_scream[rand() % 8], 1, ATTN_IDLE, 0);
+}
+
+mframe_t insane_frames_stand_normal[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, insane_checkdown}
+};
+
+mmove_t insane_move_stand_normal = {
+ FRAME_stand60,
+ FRAME_stand65,
+ insane_frames_stand_normal,
+ insane_stand
+};
+
+mframe_t insane_frames_stand_insane[] = {
+ {ai_stand, 0, insane_shake},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, insane_checkdown}
+};
+
+mmove_t insane_move_stand_insane = {
+ FRAME_stand65,
+ FRAME_stand94,
+ insane_frames_stand_insane,
+ insane_stand
+};
+
+mframe_t insane_frames_uptodown[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_moan},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 2.7, NULL},
+ {ai_move, 4.1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 7.6, NULL},
+ {ai_move, 3.6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_fist},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_fist},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_uptodown = {
+ FRAME_stand1,
+ FRAME_stand40,
+ insane_frames_uptodown,
+ insane_onground
+};
+
+mframe_t insane_frames_downtoup[] = {
+ {ai_move, -0.7, NULL}, /* 41 */
+ {ai_move, -1.2, NULL}, /* 42 */
+ {ai_move, -1.5, NULL}, /* 43 */
+ {ai_move, -4.5, NULL}, /* 44 */
+ {ai_move, -3.5, NULL}, /* 45 */
+ {ai_move, -0.2, NULL}, /* 46 */
+ {ai_move, 0, NULL}, /* 47 */
+ {ai_move, -1.3, NULL}, /* 48 */
+ {ai_move, -3, NULL}, /* 49 */
+ {ai_move, -2, NULL}, /* 50 */
+ {ai_move, 0, NULL}, /* 51 */
+ {ai_move, 0, NULL}, /* 52 */
+ {ai_move, 0, NULL}, /* 53 */
+ {ai_move, -3.3, NULL}, /* 54 */
+ {ai_move, -1.6, NULL}, /* 55 */
+ {ai_move, -0.3, NULL}, /* 56 */
+ {ai_move, 0, NULL}, /* 57 */
+ {ai_move, 0, NULL}, /* 58 */
+ {ai_move, 0, NULL} /* 59 */
+};
+
+mmove_t insane_move_downtoup = {
+ FRAME_stand41,
+ FRAME_stand59,
+ insane_frames_downtoup,
+ insane_stand
+};
+
+mframe_t insane_frames_jumpdown[] = {
+ {ai_move, 0.2, NULL},
+ {ai_move, 11.5, NULL},
+ {ai_move, 5.1, NULL},
+ {ai_move, 7.1, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_jumpdown = {
+ FRAME_stand96,
+ FRAME_stand100,
+ insane_frames_jumpdown,
+ insane_onground
+};
+
+mframe_t insane_frames_down[] = {
+ {ai_move, 0, NULL}, /* 100 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 110 */
+ {ai_move, -1.7, NULL},
+ {ai_move, -1.6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_fist},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 120 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 130 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_moan},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 140 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 150 */
+ {ai_move, 0.5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -0.2, insane_scream},
+ {ai_move, 0, NULL},
+ {ai_move, 0.2, NULL},
+ {ai_move, 0.4, NULL},
+ {ai_move, 0.6, NULL},
+ {ai_move, 0.8, NULL},
+ {ai_move, 0.7, NULL},
+ {ai_move, 0, insane_checkup} /* 160 */
+};
+
+mmove_t insane_move_down = {
+ FRAME_stand100,
+ FRAME_stand160,
+ insane_frames_down,
+ insane_onground
+};
+
+mframe_t insane_frames_walk_normal[] = {
+ {ai_walk, 0, insane_scream},
+ {ai_walk, 2.5, NULL},
+ {ai_walk, 3.5, NULL},
+ {ai_walk, 1.7, NULL},
+ {ai_walk, 2.3, NULL},
+ {ai_walk, 2.4, NULL},
+ {ai_walk, 2.2, NULL},
+ {ai_walk, 4.2, NULL},
+ {ai_walk, 5.6, NULL},
+ {ai_walk, 3.3, NULL},
+ {ai_walk, 2.4, NULL},
+ {ai_walk, 0.9, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t insane_move_walk_normal = {
+ FRAME_walk27,
+ FRAME_walk39,
+ insane_frames_walk_normal,
+ insane_walk
+};
+
+mmove_t insane_move_run_normal = {
+ FRAME_walk27,
+ FRAME_walk39,
+ insane_frames_walk_normal,
+ insane_run
+};
+
+mframe_t insane_frames_walk_insane[] = {
+ {ai_walk, 0, insane_scream}, /* walk 1 */
+ {ai_walk, 3.4, NULL}, /* walk 2 */
+ {ai_walk, 3.6, NULL}, /* 3 */
+ {ai_walk, 2.9, NULL}, /* 4 */
+ {ai_walk, 2.2, NULL}, /* 5 */
+ {ai_walk, 2.6, NULL}, /* 6 */
+ {ai_walk, 0, NULL}, /* 7 */
+ {ai_walk, 0.7, NULL}, /* 8 */
+ {ai_walk, 4.8, NULL}, /* 9 */
+ {ai_walk, 5.3, NULL}, /* 10 */
+ {ai_walk, 1.1, NULL}, /* 11 */
+ {ai_walk, 2, NULL}, /* 12 */
+ {ai_walk, 0.5, NULL}, /* 13 */
+ {ai_walk, 0, NULL}, /* 14 */
+ {ai_walk, 0, NULL}, /* 15 */
+ {ai_walk, 4.9, NULL}, /* 16 */
+ {ai_walk, 6.7, NULL}, /* 17 */
+ {ai_walk, 3.8, NULL}, /* 18 */
+ {ai_walk, 2, NULL}, /* 19 */
+ {ai_walk, 0.2, NULL}, /* 20 */
+ {ai_walk, 0, NULL}, /* 21 */
+ {ai_walk, 3.4, NULL}, /* 22 */
+ {ai_walk, 6.4, NULL}, /* 23 */
+ {ai_walk, 5, NULL}, /* 24 */
+ {ai_walk, 1.8, NULL}, /* 25 */
+ {ai_walk, 0, NULL} /* 26 */
+};
+
+mmove_t insane_move_walk_insane = {
+ FRAME_walk1,
+ FRAME_walk26,
+ insane_frames_walk_insane,
+ insane_walk
+};
+
+mmove_t insane_move_run_insane = {
+ FRAME_walk1,
+ FRAME_walk26,
+ insane_frames_walk_insane,
+ insane_run
+};
+
+mframe_t insane_frames_stand_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_stand_pain = {
+ FRAME_st_pain2,
+ FRAME_st_pain12,
+ insane_frames_stand_pain,
+ insane_run
+};
+
+mframe_t insane_frames_stand_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_stand_death = {
+ FRAME_st_death2,
+ FRAME_st_death18,
+ insane_frames_stand_death,
+ insane_dead
+};
+
+mframe_t insane_frames_crawl[] = {
+ {ai_walk, 0, insane_scream},
+ {ai_walk, 1.5, NULL},
+ {ai_walk, 2.1, NULL},
+ {ai_walk, 3.6, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 0.9, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 3.4, NULL},
+ {ai_walk, 2.4, NULL}
+};
+
+mmove_t insane_move_crawl = {
+ FRAME_crawl1,
+ FRAME_crawl9,
+ insane_frames_crawl,
+ NULL
+};
+
+mmove_t insane_move_runcrawl = {
+ FRAME_crawl1,
+ FRAME_crawl9,
+ insane_frames_crawl,
+ NULL
+};
+
+mframe_t insane_frames_crawl_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_crawl_pain = {
+ FRAME_cr_pain2,
+ FRAME_cr_pain10,
+ insane_frames_crawl_pain,
+ insane_run
+};
+
+mframe_t insane_frames_crawl_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_crawl_death = {
+ FRAME_cr_death10,
+ FRAME_cr_death16,
+ insane_frames_crawl_death,
+ insane_dead
+};
+
+mframe_t insane_frames_cross[] = {
+ {ai_move, 0, insane_moan},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_cross = {
+ FRAME_cross1,
+ FRAME_cross15,
+ insane_frames_cross,
+ insane_cross
+};
+
+mframe_t insane_frames_struggle_cross[] = {
+ {ai_move, 0, insane_scream},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_struggle_cross = {
+ FRAME_cross16,
+ FRAME_cross30,
+ insane_frames_struggle_cross,
+ insane_cross
+};
+
+void
+insane_cross(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.8)
+ {
+ self->monsterinfo.currentmove = &insane_move_cross;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_struggle_cross;
+ }
+}
+
+void
+insane_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 16) /* Hold Ground? */
+ {
+ if (self->s.frame == FRAME_cr_pain10)
+ {
+ self->monsterinfo.currentmove = &insane_move_down;
+ return;
+ }
+ }
+
+ if (self->spawnflags & 4)
+ {
+ self->monsterinfo.currentmove = &insane_move_crawl;
+ }
+ else
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_walk_normal;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_walk_insane;
+ }
+}
+
+void
+insane_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 16) /* Hold Ground? */
+ {
+ if (self->s.frame == FRAME_cr_pain10)
+ {
+ self->monsterinfo.currentmove = &insane_move_down;
+ return;
+ }
+ }
+
+ if (self->spawnflags & 4) /* Crawling? */
+ {
+ self->monsterinfo.currentmove = &insane_move_runcrawl;
+ }
+ else
+ if (random() <= 0.5) /* Else, mix it up */
+ {
+ self->monsterinfo.currentmove = &insane_move_run_normal;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_run_insane;
+ }
+}
+
+void
+insane_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ int l, r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ r = 1 + (rand() & 1);
+
+ if (self->health < 25)
+ {
+ l = 25;
+ }
+ else if (self->health < 50)
+ {
+ l = 50;
+ }
+ else if (self->health < 75)
+ {
+ l = 75;
+ }
+ else
+ {
+ l = 100;
+ }
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex(va("player/male/pain%i_%i.wav", l, r)), 1, ATTN_IDLE, 0);
+
+ /* suppress screaming and moaning for 1 second so pain sound plays */
+ self->fly_sound_debounce_time = level.time + 1;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ /* Don't go into pain frames if crucified. */
+ if (self->spawnflags & 8)
+ {
+ self->monsterinfo.currentmove = &insane_move_struggle_cross;
+ return;
+ }
+
+ if (((self->s.frame >= FRAME_crawl1) &&
+ (self->s.frame <= FRAME_crawl9)) ||
+ ((self->s.frame >= FRAME_stand99) &&
+ (self->s.frame <= FRAME_stand160)))
+ {
+ self->monsterinfo.currentmove = &insane_move_crawl_pain;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_pain;
+ }
+}
+
+void
+insane_onground(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &insane_move_down;
+}
+
+void
+insane_checkdown(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 32) /* Always stand */
+ {
+ return;
+ }
+
+ if (random() < 0.3)
+ {
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_uptodown;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_jumpdown;
+ }
+ }
+}
+
+void
+insane_checkup(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* If Hold_Ground and Crawl are set */
+ if ((self->spawnflags & 4) && (self->spawnflags & 16))
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_downtoup;
+ }
+}
+
+void
+insane_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED) /* If crucified */
+ {
+ self->monsterinfo.currentmove = &insane_move_cross;
+ self->monsterinfo.aiflags |= AI_STAND_GROUND;
+ }
+ /* If Hold_Ground and Crawl are set */
+ else if ((self->spawnflags & 4) && (self->spawnflags & 16))
+ {
+ self->monsterinfo.currentmove = &insane_move_down;
+ }
+ else
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_normal;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_insane;
+ }
+}
+
+void
+insane_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED)
+ {
+ self->flags |= FL_FLY;
+ }
+ else
+ {
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ }
+
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+insane_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex(
+ "misc/udeath.wav"), 1, ATTN_IDLE, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav", (rand() % 4) + 1)), 1, ATTN_IDLE, 0);
+
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED)
+ {
+ insane_dead(self);
+ }
+ else
+ {
+ if (((self->s.frame >= FRAME_crawl1) &&
+ (self->s.frame <= FRAME_crawl9)) ||
+ ((self->s.frame >= FRAME_stand99) &&
+ (self->s.frame <= FRAME_stand160)))
+ {
+ self->monsterinfo.currentmove = &insane_move_crawl_death;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_death;
+ }
+ }
+}
+
+/*
+ * QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND
+ */
+void
+SP_misc_insane(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_fist = gi.soundindex("insane/insane11.wav");
+ sound_shake = gi.soundindex("insane/insane5.wav");
+ sound_moan = gi.soundindex("insane/insane7.wav");
+ sound_scream[0] = gi.soundindex("insane/insane1.wav");
+ sound_scream[1] = gi.soundindex("insane/insane2.wav");
+ sound_scream[2] = gi.soundindex("insane/insane3.wav");
+ sound_scream[3] = gi.soundindex("insane/insane4.wav");
+ sound_scream[4] = gi.soundindex("insane/insane6.wav");
+ sound_scream[5] = gi.soundindex("insane/insane8.wav");
+ sound_scream[6] = gi.soundindex("insane/insane9.wav");
+ sound_scream[7] = gi.soundindex("insane/insane10.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2");
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 100;
+ self->gib_health = -50;
+ self->mass = 300;
+
+ self->pain = insane_pain;
+ self->die = insane_die;
+
+ self->monsterinfo.stand = insane_stand;
+ self->monsterinfo.walk = insane_walk;
+ self->monsterinfo.run = insane_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = NULL;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = NULL;
+ self->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+ gi.linkentity(self);
+
+ if (self->spawnflags & 16) /* Stand Ground */
+ {
+ self->monsterinfo.aiflags |= AI_STAND_GROUND;
+ }
+
+ self->monsterinfo.currentmove = &insane_move_stand_normal;
+
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED) /* Crucified ? */
+ {
+ VectorSet(self->mins, -16, 0, 0);
+ VectorSet(self->maxs, 16, 8, 32);
+ self->flags |= FL_NO_KNOCKBACK;
+ flymonster_start(self);
+ }
+ else
+ {
+ walkmonster_start(self);
+ self->s.skinnum = rand() % 3;
+ }
+}
diff --git a/rogue/src/monster/insane/insane.h b/rogue/src/monster/insane/insane.h
new file mode 100644
index 0000000..a14c851
--- /dev/null
+++ b/rogue/src/monster/insane/insane.h
@@ -0,0 +1,290 @@
+/* =======================================================================
+ *
+ * Insane animations
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand1 0
+#define FRAME_stand2 1
+#define FRAME_stand3 2
+#define FRAME_stand4 3
+#define FRAME_stand5 4
+#define FRAME_stand6 5
+#define FRAME_stand7 6
+#define FRAME_stand8 7
+#define FRAME_stand9 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_stand31 30
+#define FRAME_stand32 31
+#define FRAME_stand33 32
+#define FRAME_stand34 33
+#define FRAME_stand35 34
+#define FRAME_stand36 35
+#define FRAME_stand37 36
+#define FRAME_stand38 37
+#define FRAME_stand39 38
+#define FRAME_stand40 39
+#define FRAME_stand41 40
+#define FRAME_stand42 41
+#define FRAME_stand43 42
+#define FRAME_stand44 43
+#define FRAME_stand45 44
+#define FRAME_stand46 45
+#define FRAME_stand47 46
+#define FRAME_stand48 47
+#define FRAME_stand49 48
+#define FRAME_stand50 49
+#define FRAME_stand51 50
+#define FRAME_stand52 51
+#define FRAME_stand53 52
+#define FRAME_stand54 53
+#define FRAME_stand55 54
+#define FRAME_stand56 55
+#define FRAME_stand57 56
+#define FRAME_stand58 57
+#define FRAME_stand59 58
+#define FRAME_stand60 59
+#define FRAME_stand61 60
+#define FRAME_stand62 61
+#define FRAME_stand63 62
+#define FRAME_stand64 63
+#define FRAME_stand65 64
+#define FRAME_stand66 65
+#define FRAME_stand67 66
+#define FRAME_stand68 67
+#define FRAME_stand69 68
+#define FRAME_stand70 69
+#define FRAME_stand71 70
+#define FRAME_stand72 71
+#define FRAME_stand73 72
+#define FRAME_stand74 73
+#define FRAME_stand75 74
+#define FRAME_stand76 75
+#define FRAME_stand77 76
+#define FRAME_stand78 77
+#define FRAME_stand79 78
+#define FRAME_stand80 79
+#define FRAME_stand81 80
+#define FRAME_stand82 81
+#define FRAME_stand83 82
+#define FRAME_stand84 83
+#define FRAME_stand85 84
+#define FRAME_stand86 85
+#define FRAME_stand87 86
+#define FRAME_stand88 87
+#define FRAME_stand89 88
+#define FRAME_stand90 89
+#define FRAME_stand91 90
+#define FRAME_stand92 91
+#define FRAME_stand93 92
+#define FRAME_stand94 93
+#define FRAME_stand95 94
+#define FRAME_stand96 95
+#define FRAME_stand97 96
+#define FRAME_stand98 97
+#define FRAME_stand99 98
+#define FRAME_stand100 99
+#define FRAME_stand101 100
+#define FRAME_stand102 101
+#define FRAME_stand103 102
+#define FRAME_stand104 103
+#define FRAME_stand105 104
+#define FRAME_stand106 105
+#define FRAME_stand107 106
+#define FRAME_stand108 107
+#define FRAME_stand109 108
+#define FRAME_stand110 109
+#define FRAME_stand111 110
+#define FRAME_stand112 111
+#define FRAME_stand113 112
+#define FRAME_stand114 113
+#define FRAME_stand115 114
+#define FRAME_stand116 115
+#define FRAME_stand117 116
+#define FRAME_stand118 117
+#define FRAME_stand119 118
+#define FRAME_stand120 119
+#define FRAME_stand121 120
+#define FRAME_stand122 121
+#define FRAME_stand123 122
+#define FRAME_stand124 123
+#define FRAME_stand125 124
+#define FRAME_stand126 125
+#define FRAME_stand127 126
+#define FRAME_stand128 127
+#define FRAME_stand129 128
+#define FRAME_stand130 129
+#define FRAME_stand131 130
+#define FRAME_stand132 131
+#define FRAME_stand133 132
+#define FRAME_stand134 133
+#define FRAME_stand135 134
+#define FRAME_stand136 135
+#define FRAME_stand137 136
+#define FRAME_stand138 137
+#define FRAME_stand139 138
+#define FRAME_stand140 139
+#define FRAME_stand141 140
+#define FRAME_stand142 141
+#define FRAME_stand143 142
+#define FRAME_stand144 143
+#define FRAME_stand145 144
+#define FRAME_stand146 145
+#define FRAME_stand147 146
+#define FRAME_stand148 147
+#define FRAME_stand149 148
+#define FRAME_stand150 149
+#define FRAME_stand151 150
+#define FRAME_stand152 151
+#define FRAME_stand153 152
+#define FRAME_stand154 153
+#define FRAME_stand155 154
+#define FRAME_stand156 155
+#define FRAME_stand157 156
+#define FRAME_stand158 157
+#define FRAME_stand159 158
+#define FRAME_stand160 159
+#define FRAME_walk27 160
+#define FRAME_walk28 161
+#define FRAME_walk29 162
+#define FRAME_walk30 163
+#define FRAME_walk31 164
+#define FRAME_walk32 165
+#define FRAME_walk33 166
+#define FRAME_walk34 167
+#define FRAME_walk35 168
+#define FRAME_walk36 169
+#define FRAME_walk37 170
+#define FRAME_walk38 171
+#define FRAME_walk39 172
+#define FRAME_walk1 173
+#define FRAME_walk2 174
+#define FRAME_walk3 175
+#define FRAME_walk4 176
+#define FRAME_walk5 177
+#define FRAME_walk6 178
+#define FRAME_walk7 179
+#define FRAME_walk8 180
+#define FRAME_walk9 181
+#define FRAME_walk10 182
+#define FRAME_walk11 183
+#define FRAME_walk12 184
+#define FRAME_walk13 185
+#define FRAME_walk14 186
+#define FRAME_walk15 187
+#define FRAME_walk16 188
+#define FRAME_walk17 189
+#define FRAME_walk18 190
+#define FRAME_walk19 191
+#define FRAME_walk20 192
+#define FRAME_walk21 193
+#define FRAME_walk22 194
+#define FRAME_walk23 195
+#define FRAME_walk24 196
+#define FRAME_walk25 197
+#define FRAME_walk26 198
+#define FRAME_st_pain2 199
+#define FRAME_st_pain3 200
+#define FRAME_st_pain4 201
+#define FRAME_st_pain5 202
+#define FRAME_st_pain6 203
+#define FRAME_st_pain7 204
+#define FRAME_st_pain8 205
+#define FRAME_st_pain9 206
+#define FRAME_st_pain10 207
+#define FRAME_st_pain11 208
+#define FRAME_st_pain12 209
+#define FRAME_st_death2 210
+#define FRAME_st_death3 211
+#define FRAME_st_death4 212
+#define FRAME_st_death5 213
+#define FRAME_st_death6 214
+#define FRAME_st_death7 215
+#define FRAME_st_death8 216
+#define FRAME_st_death9 217
+#define FRAME_st_death10 218
+#define FRAME_st_death11 219
+#define FRAME_st_death12 220
+#define FRAME_st_death13 221
+#define FRAME_st_death14 222
+#define FRAME_st_death15 223
+#define FRAME_st_death16 224
+#define FRAME_st_death17 225
+#define FRAME_st_death18 226
+#define FRAME_crawl1 227
+#define FRAME_crawl2 228
+#define FRAME_crawl3 229
+#define FRAME_crawl4 230
+#define FRAME_crawl5 231
+#define FRAME_crawl6 232
+#define FRAME_crawl7 233
+#define FRAME_crawl8 234
+#define FRAME_crawl9 235
+#define FRAME_cr_pain2 236
+#define FRAME_cr_pain3 237
+#define FRAME_cr_pain4 238
+#define FRAME_cr_pain5 239
+#define FRAME_cr_pain6 240
+#define FRAME_cr_pain7 241
+#define FRAME_cr_pain8 242
+#define FRAME_cr_pain9 243
+#define FRAME_cr_pain10 244
+#define FRAME_cr_death10 245
+#define FRAME_cr_death11 246
+#define FRAME_cr_death12 247
+#define FRAME_cr_death13 248
+#define FRAME_cr_death14 249
+#define FRAME_cr_death15 250
+#define FRAME_cr_death16 251
+#define FRAME_cross1 252
+#define FRAME_cross2 253
+#define FRAME_cross3 254
+#define FRAME_cross4 255
+#define FRAME_cross5 256
+#define FRAME_cross6 257
+#define FRAME_cross7 258
+#define FRAME_cross8 259
+#define FRAME_cross9 260
+#define FRAME_cross10 261
+#define FRAME_cross11 262
+#define FRAME_cross12 263
+#define FRAME_cross13 264
+#define FRAME_cross14 265
+#define FRAME_cross15 266
+#define FRAME_cross16 267
+#define FRAME_cross17 268
+#define FRAME_cross18 269
+#define FRAME_cross19 270
+#define FRAME_cross20 271
+#define FRAME_cross21 272
+#define FRAME_cross22 273
+#define FRAME_cross23 274
+#define FRAME_cross24 275
+#define FRAME_cross25 276
+#define FRAME_cross26 277
+#define FRAME_cross27 278
+#define FRAME_cross28 279
+#define FRAME_cross29 280
+#define FRAME_cross30 281
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/medic/medic.c b/rogue/src/monster/medic/medic.c
new file mode 100644
index 0000000..8d04e9c
--- /dev/null
+++ b/rogue/src/monster/medic/medic.c
@@ -0,0 +1,1995 @@
+/* =======================================================================
+ *
+ * Medic and Medic commander.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "medic.h"
+
+#define MEDIC_MIN_DISTANCE 32
+#define MEDIC_MAX_HEAL_DISTANCE 400
+#define MEDIC_TRY_TIME 10.0
+
+qboolean visible(edict_t *self, edict_t *other);
+void M_SetEffects(edict_t *ent);
+qboolean FindTarget(edict_t *self);
+void HuntTarget(edict_t *self);
+void FoundTarget(edict_t *self);
+char *ED_NewString(char *string);
+void spawngrow_think(edict_t *self);
+void SpawnGrow_Spawn(vec3_t startpos, int size);
+void ED_CallSpawn(edict_t *ent);
+void M_FliesOff(edict_t *self);
+void M_FliesOn(edict_t *self);
+
+static int sound_idle1;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die;
+static int sound_sight;
+static int sound_search;
+static int sound_hook_launch;
+static int sound_hook_hit;
+static int sound_hook_heal;
+static int sound_hook_retract;
+
+/* commander sounds */
+static int commander_sound_idle1;
+static int commander_sound_pain1;
+static int commander_sound_pain2;
+static int commander_sound_die;
+static int commander_sound_sight;
+static int commander_sound_search;
+static int commander_sound_hook_launch;
+static int commander_sound_hook_hit;
+static int commander_sound_hook_heal;
+static int commander_sound_hook_retract;
+static int commander_sound_spawn;
+
+char *reinforcements[] = {
+ "monster_soldier_light", /* 0 */
+ "monster_soldier", /* 1 */
+ "monster_soldier_ss", /* 2 */
+ "monster_infantry", /* 3 */
+ "monster_gunner", /* 4 */
+ "monster_medic", /* 5 */
+ "monster_gladiator" /* 6 */
+};
+
+vec3_t reinforcement_mins[] = {
+ {-16, -16, -24},
+ {-16, -16, -24},
+ {-16, -16, -24},
+ {-16, -16, -24},
+ {-16, -16, -24},
+ {-16, -16, -24},
+ {-32, -32, -24}
+};
+
+vec3_t reinforcement_maxs[] = {
+ {16, 16, 32},
+ {16, 16, 32},
+ {16, 16, 32},
+ {16, 16, 32},
+ {16, 16, 32},
+ {16, 16, 32},
+ {32, 32, 64}
+};
+
+vec3_t reinforcement_position[] = {
+ {80, 0, 0},
+ {40, 60, 0},
+ {40, -60, 0},
+ {0, 80, 0},
+ {0, -80, 0}
+};
+
+void
+cleanupHeal(edict_t *self, qboolean change_frame)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* clean up target, if we have one and it's legit */
+ if (self->enemy && self->enemy->inuse)
+ {
+ self->enemy->monsterinfo.healer = NULL;
+ self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+ self->enemy->takedamage = DAMAGE_YES;
+ M_SetEffects(self->enemy);
+ }
+
+ if (change_frame)
+ {
+ self->monsterinfo.nextframe = FRAME_attack52;
+ }
+}
+
+void
+abortHeal(edict_t *self, qboolean change_frame, qboolean gib, qboolean mark)
+{
+ int hurt;
+ static vec3_t pain_normal = {0, 0, 1};
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* clean up target */
+ cleanupHeal(self, change_frame);
+
+ /* gib em! */
+ if ((mark) && (self->enemy) && (self->enemy->inuse))
+ {
+ if ((self->enemy->monsterinfo.badMedic1) &&
+ (self->enemy->monsterinfo.badMedic1->inuse) &&
+ (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13)))
+ {
+ self->enemy->monsterinfo.badMedic2 = self;
+ }
+ else
+ {
+ self->enemy->monsterinfo.badMedic1 = self;
+ }
+ }
+
+ if ((gib) && (self->enemy) && (self->enemy->inuse))
+ {
+ if (self->enemy->gib_health)
+ {
+ hurt = -self->enemy->gib_health;
+ }
+ else
+ {
+ hurt = 500;
+ }
+
+ T_Damage(self->enemy, self, self, vec3_origin, self->enemy->s.origin,
+ pain_normal, hurt, 0, 0, MOD_UNKNOWN);
+ }
+
+ /* clean up self */
+ self->monsterinfo.aiflags &= ~AI_MEDIC;
+
+ if ((self->oldenemy) && (self->oldenemy->inuse))
+ {
+ self->enemy = self->oldenemy;
+ }
+ else
+ {
+ self->enemy = NULL;
+ }
+
+ self->monsterinfo.medicTries = 0;
+}
+
+qboolean
+canReach(edict_t *self, edict_t *other)
+{
+ vec3_t spot1;
+ vec3_t spot2;
+ trace_t trace;
+
+ if (!self || !other)
+ {
+ return false;
+ }
+
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(other->s.origin, spot2);
+ spot2[2] += other->viewheight;
+ trace = gi.trace(spot1, vec3_origin, vec3_origin, spot2,
+ self, MASK_SHOT | MASK_WATER);
+
+ if ((trace.fraction == 1.0) || (trace.ent == other))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+edict_t *
+medic_FindDeadMonster(edict_t *self)
+{
+ float radius;
+ edict_t *ent = NULL;
+ edict_t *best = NULL;
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ radius = MEDIC_MAX_HEAL_DISTANCE;
+ }
+ else
+ {
+ radius = 1024;
+ }
+
+ while ((ent = findradius(ent, self->s.origin, radius)) != NULL)
+ {
+ if (ent == self)
+ {
+ continue;
+ }
+
+ if (!(ent->svflags & SVF_MONSTER))
+ {
+ continue;
+ }
+
+ if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
+ {
+ continue;
+ }
+
+ /* check to make sure we haven't bailed on this guy already */
+ if ((ent->monsterinfo.badMedic1 == self) ||
+ (ent->monsterinfo.badMedic2 == self))
+ {
+ continue;
+ }
+
+ if (ent->monsterinfo.healer)
+ {
+ if ((ent->monsterinfo.healer->inuse) &&
+ (ent->monsterinfo.healer->health > 0) &&
+ (ent->monsterinfo.healer->svflags & SVF_MONSTER) &&
+ (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC))
+ {
+ continue;
+ }
+ }
+
+ if (ent->health > 0)
+ {
+ continue;
+ }
+
+ if ((ent->nextthink) &&
+ !((ent->think == M_FliesOn) || (ent->think == M_FliesOff)))
+ {
+ continue;
+ }
+
+ if (!visible(self, ent))
+ {
+ continue;
+ }
+
+ if (!strncmp(ent->classname, "player", 6)) /* stop it from trying to heal player_noise entities */
+ {
+ continue;
+ }
+
+ /* make sure we don't spawn people right on top of us */
+ if (realrange(self, ent) <= MEDIC_MIN_DISTANCE)
+ {
+ continue;
+ }
+
+ if (!best)
+ {
+ best = ent;
+ continue;
+ }
+
+ if (ent->max_health <= best->max_health)
+ {
+ continue;
+ }
+
+ best = ent;
+ }
+
+ if (best)
+ {
+ self->timestamp = level.time + MEDIC_TRY_TIME;
+ }
+
+ return best;
+}
+
+void
+medic_idle(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* commander sounds */
+ if (self->mass == 400)
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, commander_sound_idle1, 1, ATTN_IDLE, 0);
+ }
+
+ if (!self->oldenemy)
+ {
+ ent = medic_FindDeadMonster(self);
+
+ if (ent)
+ {
+ self->oldenemy = self->enemy;
+ self->enemy = ent;
+ self->enemy->monsterinfo.healer = self;
+ self->monsterinfo.aiflags |= AI_MEDIC;
+ FoundTarget(self);
+ }
+ }
+}
+
+void
+medic_search(edict_t *self)
+{
+ edict_t *ent;
+
+ /* commander sounds */
+ if (self->mass == 400)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, commander_sound_search, 1, ATTN_IDLE, 0);
+ }
+
+ if (!self->oldenemy)
+ {
+ ent = medic_FindDeadMonster(self);
+
+ if (ent)
+ {
+ self->oldenemy = self->enemy;
+ self->enemy = ent;
+ self->enemy->monsterinfo.healer = self;
+ self->monsterinfo.aiflags |= AI_MEDIC;
+ FoundTarget(self);
+ }
+ }
+}
+
+void
+medic_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* commander sounds */
+ if (self->mass == 400)
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, commander_sound_sight, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t medic_frames_stand[] = {
+ {ai_stand, 0, medic_idle},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+};
+
+mmove_t medic_move_stand = {
+ FRAME_wait1,
+ FRAME_wait90,
+ medic_frames_stand,
+ NULL
+};
+
+void
+medic_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &medic_move_stand;
+}
+
+mframe_t medic_frames_walk[] = {
+ {ai_walk, 6.2, NULL},
+ {ai_walk, 18.1, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 11, NULL},
+ {ai_walk, 11.6, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 9.9, NULL},
+ {ai_walk, 14, NULL},
+ {ai_walk, 9.3, NULL}
+};
+
+mmove_t medic_move_walk = {
+ FRAME_walk1,
+ FRAME_walk12,
+ medic_frames_walk,
+ NULL
+};
+
+void
+medic_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &medic_move_walk;
+}
+
+mframe_t medic_frames_run[] = {
+ {ai_run, 18, NULL},
+ {ai_run, 22.5, NULL},
+ {ai_run, 25.4, monster_done_dodge},
+ {ai_run, 23.4, NULL},
+ {ai_run, 24, NULL},
+ {ai_run, 35.6, NULL}
+};
+
+mmove_t medic_move_run = {
+ FRAME_run1,
+ FRAME_run6,
+ medic_frames_run,
+ NULL
+};
+
+void
+medic_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (!(self->monsterinfo.aiflags & AI_MEDIC))
+ {
+ edict_t *ent;
+
+ ent = medic_FindDeadMonster(self);
+
+ if (ent)
+ {
+ self->oldenemy = self->enemy;
+ self->enemy = ent;
+ self->enemy->monsterinfo.healer = self;
+ self->monsterinfo.aiflags |= AI_MEDIC;
+ FoundTarget(self);
+ return;
+ }
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &medic_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_run;
+ }
+}
+
+mframe_t medic_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t medic_move_pain1 = {
+ FRAME_paina1,
+ FRAME_paina8,
+ medic_frames_pain1,
+ medic_run
+};
+
+mframe_t medic_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t medic_move_pain2 = {
+ FRAME_painb1,
+ FRAME_painb15,
+ medic_frames_pain2,
+ medic_run
+};
+
+void
+medic_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if ((self->health < (self->max_health / 2)))
+ {
+ if (self->mass > 400)
+ {
+ self->s.skinnum = 3;
+ }
+ else
+ {
+ self->s.skinnum = 1;
+ }
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ /* if we're healing someone, we ignore pain */
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ return;
+ }
+
+ if (self->mass > 400)
+ {
+ if (damage < 35)
+ {
+ gi.sound(self, CHAN_VOICE, commander_sound_pain1, 1, ATTN_NORM, 0);
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+ gi.sound(self, CHAN_VOICE, commander_sound_pain2, 1, ATTN_NORM, 0);
+
+ if (random() < (min(((float)damage * 0.005), 0.5))) /* no more than 50% chance of big pain */
+ {
+ self->monsterinfo.currentmove = &medic_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_pain1;
+ }
+ }
+ else if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &medic_move_pain1;
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_pain2;
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ /* clear duck flag */
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+}
+
+void
+medic_fire_blaster(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+ int damage = 2;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* paranoia checking */
+ if (!(self->enemy && self->enemy->inuse))
+ {
+ return;
+ }
+
+ if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12))
+ {
+ effect = EF_BLASTER;
+ }
+ else if ((self->s.frame == FRAME_attack19) ||
+ (self->s.frame == FRAME_attack22) ||
+ (self->s.frame == FRAME_attack25) ||
+ (self->s.frame == FRAME_attack28))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ if (!strcmp(self->enemy->classname, "tesla"))
+ {
+ damage = 3;
+ }
+
+ /* medic commander shoots blaster2 */
+ if (self->mass > 400)
+ {
+ monster_fire_blaster2(self, start, dir, damage,
+ 1000, MZ2_MEDIC_BLASTER_2, effect);
+ }
+ else
+ {
+ monster_fire_blaster(self, start, dir, damage,
+ 1000, MZ2_MEDIC_BLASTER_1, effect);
+ }
+}
+
+void
+medic_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t medic_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t medic_move_death = {
+ FRAME_death1,
+ FRAME_death30,
+ medic_frames_death,
+ medic_dead
+};
+
+void
+medic_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ if (self->mass == 400)
+ {
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, commander_sound_die, 1, ATTN_NORM, 0);
+ }
+
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ self->monsterinfo.currentmove = &medic_move_death;
+}
+
+mframe_t medic_frames_duck[] = {
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, monster_duck_down},
+ {ai_move, -1, monster_duck_hold},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, monster_duck_up},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL}
+};
+
+mmove_t medic_move_duck = {
+ FRAME_duck1,
+ FRAME_duck16,
+ medic_frames_duck,
+ medic_run
+};
+
+mframe_t medic_frames_attackHyperBlaster[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster}
+};
+
+mmove_t medic_move_attackHyperBlaster = {
+ FRAME_attack15,
+ FRAME_attack30,
+ medic_frames_attackHyperBlaster,
+ medic_run
+};
+
+void
+medic_continue(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.95)
+ {
+ self->monsterinfo.currentmove = &medic_move_attackHyperBlaster;
+ }
+ }
+}
+
+mframe_t medic_frames_attackBlaster[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_continue}
+};
+
+mmove_t medic_move_attackBlaster = {
+ FRAME_attack1,
+ FRAME_attack14,
+ medic_frames_attackBlaster,
+ medic_run
+};
+
+void
+medic_hook_launch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* commander sounds */
+ if (self->mass == 400)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, commander_sound_hook_launch, 1, ATTN_NORM, 0);
+ }
+}
+
+static vec3_t medic_cable_offsets[] = {
+ {45.0, -9.2, 15.5},
+ {48.4, -9.7, 15.2},
+ {47.8, -9.8, 15.8},
+ {47.3, -9.3, 14.3},
+ {45.4, -10.1, 13.1},
+ {41.9, -12.7, 12.0},
+ {37.8, -15.8, 11.2},
+ {34.3, -18.4, 10.7},
+ {32.7, -19.7, 10.4},
+ {32.7, -19.7, 10.4}
+};
+
+void
+medic_cable_attack(edict_t *self)
+{
+ vec3_t offset, start, end, f, r;
+ trace_t tr;
+ vec3_t dir;
+ float distance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((!self->enemy) || (!self->enemy->inuse) ||
+ (self->enemy->s.effects & EF_GIB))
+ {
+ abortHeal(self, true, false, false);
+ return;
+ }
+
+ /* see if our enemy has changed to a client, or our
+ target has more than 0 health, abort it .. we got
+ switched to someone else due to damage */
+ if ((self->enemy->client) || (self->enemy->health > 0))
+ {
+ abortHeal(self, true, false, false);
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorCopy(medic_cable_offsets[self->s.frame - FRAME_attack42], offset);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ VectorSubtract(start, self->enemy->s.origin, dir);
+ distance = VectorLength(dir);
+
+ if (distance < MEDIC_MIN_DISTANCE)
+ {
+ abortHeal(self, true, true, false);
+ return;
+ }
+
+ tr = gi.trace(start, NULL, NULL, self->enemy->s.origin, self, MASK_SOLID);
+
+ if ((tr.fraction != 1.0) && (tr.ent != self->enemy))
+ {
+ if (tr.ent == world)
+ {
+ /* give up on second try */
+ if (self->monsterinfo.medicTries > 1)
+ {
+ abortHeal(self, true, false, true);
+ return;
+ }
+
+ self->monsterinfo.medicTries++;
+ cleanupHeal(self, 1);
+ return;
+ }
+
+ abortHeal(self, true, false, false);
+ return;
+ }
+
+ if (self->s.frame == FRAME_attack43)
+ {
+ /* commander sounds */
+ if (self->mass == 400)
+ {
+ gi.sound(self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self->enemy, CHAN_AUTO, commander_sound_hook_hit,
+ 1, ATTN_NORM, 0);
+ }
+
+ self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+ self->enemy->takedamage = DAMAGE_NO;
+ M_SetEffects(self->enemy);
+ }
+ else if (self->s.frame == FRAME_attack50)
+ {
+ vec3_t maxs;
+ self->enemy->spawnflags = 0;
+ self->enemy->monsterinfo.aiflags = 0;
+ self->enemy->target = NULL;
+ self->enemy->targetname = NULL;
+ self->enemy->combattarget = NULL;
+ self->enemy->deathtarget = NULL;
+ self->enemy->monsterinfo.healer = self;
+
+ VectorCopy(self->enemy->maxs, maxs);
+ maxs[2] += 48;
+
+ tr = gi.trace(self->enemy->s.origin, self->enemy->mins,
+ maxs, self->enemy->s.origin, self->enemy,
+ MASK_MONSTERSOLID);
+
+ if (tr.startsolid || tr.allsolid)
+ {
+ abortHeal(self, true, true, false);
+ return;
+ }
+ else if (tr.ent != world)
+ {
+ abortHeal(self, true, true, false);
+ return;
+ }
+ else
+ {
+ self->enemy->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+ ED_CallSpawn(self->enemy);
+
+ if (self->enemy->think)
+ {
+ self->enemy->nextthink = level.time;
+ self->enemy->think(self->enemy);
+ }
+
+ self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+ self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS |
+ AI_DO_NOT_COUNT;
+ /* turn off flies */
+ self->enemy->s.effects &= ~EF_FLIES;
+ self->enemy->monsterinfo.healer = NULL;
+
+ if ((self->oldenemy) && (self->oldenemy->inuse) &&
+ (self->oldenemy->health > 0))
+ {
+ self->enemy->enemy = self->oldenemy;
+ FoundTarget(self->enemy);
+ }
+ else
+ {
+ self->enemy->enemy = NULL;
+
+ if (!FindTarget(self->enemy))
+ {
+ /* no valid enemy, so stop acting */
+ self->enemy->monsterinfo.pausetime = level.time + 100000000;
+ self->enemy->monsterinfo.stand(self->enemy);
+ }
+
+ self->enemy = NULL;
+ self->oldenemy = NULL;
+
+ if (!FindTarget(self))
+ {
+ /* no valid enemy, so stop acting */
+ self->monsterinfo.pausetime = level.time + 100000000;
+ self->monsterinfo.stand(self);
+ return;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (self->s.frame == FRAME_attack44)
+ {
+ /* medic commander sounds */
+ if (self->mass == 400)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, commander_sound_hook_heal,
+ 1, ATTN_NORM, 0);
+ }
+ }
+ }
+
+ /* adjust start for beam origin being in middle of a segment */
+ VectorMA(start, 8, f, start);
+
+ /* adjust end z for end spot since the monster is currently dead */
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2;
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_MEDIC_CABLE_ATTACK);
+ gi.WriteShort(self - g_edicts);
+ gi.WritePosition(start);
+ gi.WritePosition(end);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+}
+
+void
+medic_hook_retract(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->mass == 400)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, commander_sound_hook_retract, 1, ATTN_NORM, 0);
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MEDIC;
+
+ if ((self->oldenemy) && (self->oldenemy->inuse))
+ {
+ self->enemy = self->oldenemy;
+ }
+ else
+ {
+ self->enemy = NULL;
+ self->oldenemy = NULL;
+
+ if (!FindTarget(self))
+ {
+ /* no valid enemy, so stop acting */
+ self->monsterinfo.pausetime = level.time + 100000000;
+ self->monsterinfo.stand(self);
+ return;
+ }
+ }
+}
+
+mframe_t medic_frames_attackCable[] = {
+ {ai_charge, 2, NULL}, /* 33 */
+ {ai_charge, 3, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, -4.4, NULL}, /* 36 */
+ {ai_charge, -4.7, NULL}, /* 37 */
+ {ai_charge, -5, NULL},
+ {ai_charge, -6, NULL},
+ {ai_charge, -4, NULL}, /* 40 */
+ {ai_charge, 0, NULL},
+ {ai_move, 0, medic_hook_launch}, /* 42 */
+ {ai_move, 0, medic_cable_attack}, /* 43 */
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack}, /* 51 */
+ {ai_move, 0, medic_hook_retract}, /* 52 */
+ {ai_move, -1.5, NULL},
+ {ai_move, -1.2, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0.3, NULL},
+ {ai_move, 0.7, NULL},
+ {ai_move, 1.2, NULL},
+ {ai_move, 1.3, NULL} /* 60 */
+
+};
+mmove_t medic_move_attackCable = {
+ FRAME_attack33,
+ FRAME_attack60,
+ medic_frames_attackCable,
+ medic_run
+};
+
+void
+medic_start_spawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0);
+ self->monsterinfo.nextframe = FRAME_attack48;
+}
+
+void
+medic_determine_spawn(edict_t *self)
+{
+ vec3_t f, r, offset, startpoint, spawnpoint;
+ float lucky;
+ int summonStr;
+ int count;
+ int inc;
+ int num_summoned; /* should be 1, 3, or 5 */
+ int num_success = 0;
+
+ lucky = random();
+ summonStr = skill->value;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (lucky < 0.05)
+ {
+ summonStr -= 3;
+ }
+ else if (lucky < 0.15)
+ {
+ summonStr -= 2;
+ }
+ else if (lucky < 0.3)
+ {
+ summonStr -= 1;
+ }
+ else if (lucky > 0.95)
+ {
+ summonStr += 3;
+ }
+ else if (lucky > 0.85)
+ {
+ summonStr += 2;
+ }
+ else if (lucky > 0.7)
+ {
+ summonStr += 1;
+ }
+
+ if (summonStr < 0)
+ {
+ summonStr = 0;
+ }
+
+ self->plat2flags = summonStr;
+ AngleVectors(self->s.angles, f, r, NULL);
+
+ /* this yields either 1, 3, or 5 */
+ if (summonStr)
+ {
+ num_summoned = (summonStr - 1) + (summonStr % 2);
+ }
+ else
+ {
+ num_summoned = 1;
+ }
+
+ for (count = 0; count < num_summoned; count++)
+ {
+ inc = count + (count % 2); /* 0, 2, 2, 4, 4 */
+ VectorCopy(reinforcement_position[count], offset);
+
+ G_ProjectSource(self->s.origin, offset, f, r, startpoint);
+ startpoint[2] += 10;
+
+ if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc], spawnpoint, 32))
+ {
+ if (CheckGroundSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc], 256, -1))
+ {
+ num_success++;
+ /* we found a spot, we're done here */
+ count = num_summoned;
+ }
+ }
+ }
+
+ if (num_success == 0)
+ {
+ for (count = 0; count < num_summoned; count++)
+ {
+ inc = count + (count % 2); /* 0, 2, 2, 4, 4 */
+ VectorCopy(reinforcement_position[count], offset);
+
+ /* check behind */
+ offset[0] *= -1.0;
+ offset[1] *= -1.0;
+ G_ProjectSource(self->s.origin, offset, f, r, startpoint);
+ /* a little off the ground */
+ startpoint[2] += 10;
+
+ if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc], spawnpoint, 32))
+ {
+ if (CheckGroundSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc], 256, -1))
+ {
+ num_success++;
+ /* we found a spot, we're done here */
+ count = num_summoned;
+ }
+ }
+ }
+
+ if (num_success)
+ {
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+ self->ideal_yaw = anglemod(self->s.angles[YAW]) + 180;
+
+ if (self->ideal_yaw > 360.0)
+ {
+ self->ideal_yaw -= 360.0;
+ }
+ }
+ }
+
+ if (num_success == 0)
+ {
+ self->monsterinfo.nextframe = FRAME_attack53;
+ }
+}
+
+void
+medic_spawngrows(edict_t *self)
+{
+ vec3_t f, r, offset, startpoint, spawnpoint;
+ int summonStr;
+ int count;
+ int inc;
+ int num_summoned; /* should be 1, 3, or 5 */
+ int num_success = 0;
+ float current_yaw;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we've been directed to turn around */
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ current_yaw = anglemod(self->s.angles[YAW]);
+
+ if (fabs(current_yaw - self->ideal_yaw) > 0.1)
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ return;
+ }
+
+ /* done turning around */
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ }
+
+ summonStr = self->plat2flags;
+ AngleVectors(self->s.angles, f, r, NULL);
+
+ if (summonStr)
+ {
+ num_summoned = (summonStr - 1) + (summonStr % 2);
+ }
+ else
+ {
+ num_summoned = 1;
+ }
+
+ for (count = 0; count < num_summoned; count++)
+ {
+ inc = count + (count % 2); /* 0, 2, 2, 4, 4 */
+ VectorCopy(reinforcement_position[count], offset);
+
+ G_ProjectSource(self->s.origin, offset, f, r, startpoint);
+ /* a little off the ground */
+ startpoint[2] += 10;
+
+ if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc], spawnpoint, 32))
+ {
+ if (CheckGroundSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc], 256, -1))
+ {
+ num_success++;
+
+ if ((summonStr - inc) > 3)
+ {
+ SpawnGrow_Spawn(spawnpoint, 1); /* big monster */
+ }
+ else
+ {
+ SpawnGrow_Spawn(spawnpoint, 0); /* normal size */
+ }
+ }
+ }
+ }
+
+ if (num_success == 0)
+ {
+ self->monsterinfo.nextframe = FRAME_attack53;
+ }
+}
+
+void
+medic_finish_spawn(edict_t *self)
+{
+ edict_t *ent;
+ vec3_t f, r, offset, startpoint, spawnpoint;
+ int summonStr;
+ int count;
+ int inc;
+ int num_summoned; /* should be 1, 3, or 5 */
+ edict_t *designated_enemy;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->plat2flags < 0)
+ {
+ self->plat2flags *= -1;
+ }
+
+ summonStr = self->plat2flags;
+
+ AngleVectors(self->s.angles, f, r, NULL);
+
+ if (summonStr)
+ {
+ num_summoned = (summonStr - 1) + (summonStr % 2);
+ }
+ else
+ {
+ num_summoned = 1;
+ }
+
+ for (count = 0; count < num_summoned; count++)
+ {
+ inc = count + (count % 2); /* 0, 2, 2, 4, 4 */
+ VectorCopy(reinforcement_position[count], offset);
+
+ G_ProjectSource(self->s.origin, offset, f, r, startpoint);
+
+ /* a little off the ground */
+ startpoint[2] += 10;
+
+ ent = NULL;
+
+ if (FindSpawnPoint(startpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc], spawnpoint, 32))
+ {
+ if (CheckSpawnPoint(spawnpoint, reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc]))
+ {
+ ent = CreateGroundMonster(spawnpoint, self->s.angles,
+ reinforcement_mins[summonStr - inc],
+ reinforcement_maxs[summonStr - inc],
+ reinforcements[summonStr - inc], 256);
+ }
+ }
+
+ if (!ent)
+ {
+ continue;
+ }
+
+
+ if (ent->think)
+ {
+ ent->nextthink = level.time;
+ ent->think(ent);
+ }
+
+ ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS | AI_DO_NOT_COUNT |
+ AI_SPAWNED_MEDIC_C;
+ ent->monsterinfo.commander = self;
+ self->monsterinfo.monster_slots--;
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ designated_enemy = self->oldenemy;
+ }
+ else
+ {
+ designated_enemy = self->enemy;
+ }
+
+ if (coop && coop->value)
+ {
+ designated_enemy = PickCoopTarget(ent);
+
+ if (designated_enemy)
+ {
+ /* try to avoid using my enemy */
+ if (designated_enemy == self->enemy)
+ {
+ designated_enemy = PickCoopTarget(ent);
+
+ if (!designated_enemy)
+ {
+ designated_enemy = self->enemy;
+ }
+ }
+ }
+ else
+ {
+ designated_enemy = self->enemy;
+ }
+ }
+
+ if ((designated_enemy) && (designated_enemy->inuse) &&
+ (designated_enemy->health > 0))
+ {
+ ent->enemy = designated_enemy;
+ FoundTarget(ent);
+ }
+ else
+ {
+ ent->enemy = NULL;
+ ent->monsterinfo.stand(ent);
+ }
+ }
+}
+
+mframe_t medic_frames_callReinforcements[] = {
+ {ai_charge, 2, NULL}, /* 33 */
+ {ai_charge, 3, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 4.4, NULL}, /* 36 */
+ {ai_charge, 4.7, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 4, NULL}, /* 40 */
+ {ai_charge, 0, NULL},
+ {ai_move, 0, medic_start_spawn}, /* 42 */
+ {ai_move, 0, NULL}, /* 43 -- 43 through 47 are skipped */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, medic_determine_spawn}, /* 48 */
+ {ai_charge, 0, medic_spawngrows}, /* 49 */
+ {ai_move, 0, NULL}, /* 50 */
+ {ai_move, 0, NULL}, /* 51 */
+ {ai_move, -15, medic_finish_spawn}, /* 52 */
+ {ai_move, -1.5, NULL},
+ {ai_move, -1.2, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0.3, NULL},
+ {ai_move, 0.7, NULL},
+ {ai_move, 1.2, NULL},
+ {ai_move, 1.3, NULL} /* 60 */
+};
+
+mmove_t medic_move_callReinforcements = {
+ FRAME_attack33,
+ FRAME_attack60,
+ medic_frames_callReinforcements,
+ medic_run
+};
+
+void
+medic_attack(edict_t *self)
+{
+ int enemy_range;
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ enemy_range = range(self, self->enemy);
+
+ /* signal from checkattack to spawn */
+ if (self->monsterinfo.aiflags & AI_BLOCKED)
+ {
+ self->monsterinfo.currentmove = &medic_move_callReinforcements;
+ self->monsterinfo.aiflags &= ~AI_BLOCKED;
+ }
+
+ r = random();
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ if ((self->mass > 400) && (r > 0.8) &&
+ (self->monsterinfo.monster_slots > 2))
+ {
+ self->monsterinfo.currentmove = &medic_move_callReinforcements;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_attackCable;
+ }
+ }
+ else
+ {
+ if (self->monsterinfo.attack_state == AS_BLIND)
+ {
+ self->monsterinfo.currentmove = &medic_move_callReinforcements;
+ return;
+ }
+
+ if ((self->mass > 400) && (r > 0.2) && (enemy_range != RANGE_MELEE) &&
+ (self->monsterinfo.monster_slots > 2))
+ {
+ self->monsterinfo.currentmove = &medic_move_callReinforcements;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_attackBlaster;
+ }
+ }
+}
+
+qboolean
+medic_checkattack(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ /* if our target went away */
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ abortHeal(self, true, false, false);
+ return false;
+ }
+
+ /* if we ran out of time, give up */
+ if (self->timestamp < level.time)
+ {
+ abortHeal(self, true, false, true);
+ self->timestamp = 0;
+ return false;
+ }
+
+ if (realrange(self, self->enemy) < MEDIC_MAX_HEAL_DISTANCE + 10)
+ {
+ medic_attack(self);
+ return true;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ return false;
+ }
+ }
+
+ if (self->enemy && self->enemy->client &&
+ !visible(self, self->enemy) && (self->monsterinfo.monster_slots > 2))
+ {
+ self->monsterinfo.attack_state = AS_BLIND;
+ return true;
+ }
+
+ if ((random() < 0.8) && (self->monsterinfo.monster_slots > 5) &&
+ (realrange(self, self->enemy) > 150))
+ {
+ self->monsterinfo.aiflags |= AI_BLOCKED;
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ if (skill->value > SKILL_EASY)
+ {
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+ }
+
+ return M_CheckAttack(self);
+}
+
+void
+MedicCommanderCache(void)
+{
+ edict_t *newEnt;
+ int i;
+
+ /* better way to do this? this is quick and dirty */
+ for (i = 0; i < 7; i++)
+ {
+ newEnt = G_Spawn();
+
+ VectorCopy(vec3_origin, newEnt->s.origin);
+ VectorCopy(vec3_origin, newEnt->s.angles);
+ newEnt->classname = ED_NewString(reinforcements[i]);
+
+ newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+
+ ED_CallSpawn(newEnt);
+ G_FreeEdict(newEnt);
+ }
+
+ gi.modelindex("models/items/spawngro/tris.md2");
+ gi.modelindex("models/items/spawngro2/tris.md2");
+}
+
+void
+medic_duck(edict_t *self, float eta)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* don't dodge if you're healing */
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
+ (self->monsterinfo.currentmove == &medic_move_attackCable) ||
+ (self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
+ (self->monsterinfo.currentmove == &medic_move_callReinforcements))
+ {
+ /* he ignores skill */
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ return;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ /* stupid dodge */
+ self->monsterinfo.duck_wait_time = level.time + eta + 1;
+ }
+ else
+ {
+ self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+ }
+
+ /* has to be done immediately otherwise he can get stuck */
+ monster_duck_down(self);
+
+ self->monsterinfo.nextframe = FRAME_duck1;
+ self->monsterinfo.currentmove = &medic_move_duck;
+ return;
+}
+
+void
+medic_sidestep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
+ (self->monsterinfo.currentmove == &medic_move_attackCable) ||
+ (self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
+ (self->monsterinfo.currentmove == &medic_move_callReinforcements))
+ {
+ /* if we're shooting, and not on easy, don't dodge */
+ if (skill->value > SKILL_EASY)
+ {
+ self->monsterinfo.aiflags &= ~AI_DODGING;
+ return;
+ }
+ }
+
+ if (self->monsterinfo.currentmove != &medic_move_run)
+ {
+ self->monsterinfo.currentmove = &medic_move_run;
+ }
+}
+
+qboolean
+medic_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * QUAKED monster_medic_commander (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ *
+ * QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_medic(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/medic/tris.md2");
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 32);
+
+ if (strcmp(self->classname, "monster_medic_commander") == 0)
+ {
+ self->health = 600;
+ self->gib_health = -130;
+ self->mass = 600;
+ self->yaw_speed = 40;
+ MedicCommanderCache();
+ }
+ else
+ {
+ self->health = 300;
+ self->gib_health = -130;
+ self->mass = 400;
+ }
+
+ self->pain = medic_pain;
+ self->die = medic_die;
+
+ self->monsterinfo.stand = medic_stand;
+ self->monsterinfo.walk = medic_walk;
+ self->monsterinfo.run = medic_run;
+ self->monsterinfo.dodge = M_MonsterDodge;
+ self->monsterinfo.duck = medic_duck;
+ self->monsterinfo.unduck = monster_duck_up;
+ self->monsterinfo.sidestep = medic_sidestep;
+ self->monsterinfo.attack = medic_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = medic_sight;
+ self->monsterinfo.idle = medic_idle;
+ self->monsterinfo.search = medic_search;
+ self->monsterinfo.checkattack = medic_checkattack;
+ self->monsterinfo.blocked = medic_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &medic_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+
+ self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+ if (self->mass > 400)
+ {
+ self->s.skinnum = 2;
+
+ if (skill->value == SKILL_EASY)
+ {
+ self->monsterinfo.monster_slots = 3;
+ }
+ else if (skill->value == SKILL_MEDIUM)
+ {
+ self->monsterinfo.monster_slots = 4;
+ }
+ else if (skill->value == SKILL_HARD)
+ {
+ self->monsterinfo.monster_slots = 6;
+ }
+ else if (skill->value == SKILL_HARDPLUS)
+ {
+ self->monsterinfo.monster_slots = 6;
+ }
+
+ /* commander sounds */
+ commander_sound_idle1 = gi.soundindex("medic_commander/medidle.wav");
+ commander_sound_pain1 = gi.soundindex("medic_commander/medpain1.wav");
+ commander_sound_pain2 = gi.soundindex("medic_commander/medpain2.wav");
+ commander_sound_die = gi.soundindex("medic_commander/meddeth.wav");
+ commander_sound_sight = gi.soundindex("medic_commander/medsght.wav");
+ commander_sound_search = gi.soundindex("medic_commander/medsrch.wav");
+ commander_sound_hook_launch = gi.soundindex("medic_commander/medatck2c.wav");
+ commander_sound_hook_hit = gi.soundindex("medic_commander/medatck3a.wav");
+ commander_sound_hook_heal = gi.soundindex("medic_commander/medatck4a.wav");
+ commander_sound_hook_retract = gi.soundindex("medic_commander/medatck5a.wav");
+ commander_sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav");
+ gi.soundindex("tank/tnkatck3.wav");
+ }
+ else
+ {
+ sound_idle1 = gi.soundindex("medic/idle.wav");
+ sound_pain1 = gi.soundindex("medic/medpain1.wav");
+ sound_pain2 = gi.soundindex("medic/medpain2.wav");
+ sound_die = gi.soundindex("medic/meddeth1.wav");
+ sound_sight = gi.soundindex("medic/medsght1.wav");
+ sound_search = gi.soundindex("medic/medsrch1.wav");
+ sound_hook_launch = gi.soundindex("medic/medatck2.wav");
+ sound_hook_hit = gi.soundindex("medic/medatck3.wav");
+ sound_hook_heal = gi.soundindex("medic/medatck4.wav");
+ sound_hook_retract = gi.soundindex("medic/medatck5.wav");
+ gi.soundindex("medic/medatck1.wav");
+
+ self->s.skinnum = 0;
+ }
+}
diff --git a/rogue/src/monster/medic/medic.h b/rogue/src/monster/medic/medic.h
new file mode 100644
index 0000000..814ed8c
--- /dev/null
+++ b/rogue/src/monster/medic/medic.h
@@ -0,0 +1,245 @@
+/* =======================================================================
+ *
+ * Medic and Medic Commander animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_walk1 0
+#define FRAME_walk2 1
+#define FRAME_walk3 2
+#define FRAME_walk4 3
+#define FRAME_walk5 4
+#define FRAME_walk6 5
+#define FRAME_walk7 6
+#define FRAME_walk8 7
+#define FRAME_walk9 8
+#define FRAME_walk10 9
+#define FRAME_walk11 10
+#define FRAME_walk12 11
+#define FRAME_wait1 12
+#define FRAME_wait2 13
+#define FRAME_wait3 14
+#define FRAME_wait4 15
+#define FRAME_wait5 16
+#define FRAME_wait6 17
+#define FRAME_wait7 18
+#define FRAME_wait8 19
+#define FRAME_wait9 20
+#define FRAME_wait10 21
+#define FRAME_wait11 22
+#define FRAME_wait12 23
+#define FRAME_wait13 24
+#define FRAME_wait14 25
+#define FRAME_wait15 26
+#define FRAME_wait16 27
+#define FRAME_wait17 28
+#define FRAME_wait18 29
+#define FRAME_wait19 30
+#define FRAME_wait20 31
+#define FRAME_wait21 32
+#define FRAME_wait22 33
+#define FRAME_wait23 34
+#define FRAME_wait24 35
+#define FRAME_wait25 36
+#define FRAME_wait26 37
+#define FRAME_wait27 38
+#define FRAME_wait28 39
+#define FRAME_wait29 40
+#define FRAME_wait30 41
+#define FRAME_wait31 42
+#define FRAME_wait32 43
+#define FRAME_wait33 44
+#define FRAME_wait34 45
+#define FRAME_wait35 46
+#define FRAME_wait36 47
+#define FRAME_wait37 48
+#define FRAME_wait38 49
+#define FRAME_wait39 50
+#define FRAME_wait40 51
+#define FRAME_wait41 52
+#define FRAME_wait42 53
+#define FRAME_wait43 54
+#define FRAME_wait44 55
+#define FRAME_wait45 56
+#define FRAME_wait46 57
+#define FRAME_wait47 58
+#define FRAME_wait48 59
+#define FRAME_wait49 60
+#define FRAME_wait50 61
+#define FRAME_wait51 62
+#define FRAME_wait52 63
+#define FRAME_wait53 64
+#define FRAME_wait54 65
+#define FRAME_wait55 66
+#define FRAME_wait56 67
+#define FRAME_wait57 68
+#define FRAME_wait58 69
+#define FRAME_wait59 70
+#define FRAME_wait60 71
+#define FRAME_wait61 72
+#define FRAME_wait62 73
+#define FRAME_wait63 74
+#define FRAME_wait64 75
+#define FRAME_wait65 76
+#define FRAME_wait66 77
+#define FRAME_wait67 78
+#define FRAME_wait68 79
+#define FRAME_wait69 80
+#define FRAME_wait70 81
+#define FRAME_wait71 82
+#define FRAME_wait72 83
+#define FRAME_wait73 84
+#define FRAME_wait74 85
+#define FRAME_wait75 86
+#define FRAME_wait76 87
+#define FRAME_wait77 88
+#define FRAME_wait78 89
+#define FRAME_wait79 90
+#define FRAME_wait80 91
+#define FRAME_wait81 92
+#define FRAME_wait82 93
+#define FRAME_wait83 94
+#define FRAME_wait84 95
+#define FRAME_wait85 96
+#define FRAME_wait86 97
+#define FRAME_wait87 98
+#define FRAME_wait88 99
+#define FRAME_wait89 100
+#define FRAME_wait90 101
+#define FRAME_run1 102
+#define FRAME_run2 103
+#define FRAME_run3 104
+#define FRAME_run4 105
+#define FRAME_run5 106
+#define FRAME_run6 107
+#define FRAME_paina1 108
+#define FRAME_paina2 109
+#define FRAME_paina3 110
+#define FRAME_paina4 111
+#define FRAME_paina5 112
+#define FRAME_paina6 113
+#define FRAME_paina7 114
+#define FRAME_paina8 115
+#define FRAME_painb1 116
+#define FRAME_painb2 117
+#define FRAME_painb3 118
+#define FRAME_painb4 119
+#define FRAME_painb5 120
+#define FRAME_painb6 121
+#define FRAME_painb7 122
+#define FRAME_painb8 123
+#define FRAME_painb9 124
+#define FRAME_painb10 125
+#define FRAME_painb11 126
+#define FRAME_painb12 127
+#define FRAME_painb13 128
+#define FRAME_painb14 129
+#define FRAME_painb15 130
+#define FRAME_duck1 131
+#define FRAME_duck2 132
+#define FRAME_duck3 133
+#define FRAME_duck4 134
+#define FRAME_duck5 135
+#define FRAME_duck6 136
+#define FRAME_duck7 137
+#define FRAME_duck8 138
+#define FRAME_duck9 139
+#define FRAME_duck10 140
+#define FRAME_duck11 141
+#define FRAME_duck12 142
+#define FRAME_duck13 143
+#define FRAME_duck14 144
+#define FRAME_duck15 145
+#define FRAME_duck16 146
+#define FRAME_death1 147
+#define FRAME_death2 148
+#define FRAME_death3 149
+#define FRAME_death4 150
+#define FRAME_death5 151
+#define FRAME_death6 152
+#define FRAME_death7 153
+#define FRAME_death8 154
+#define FRAME_death9 155
+#define FRAME_death10 156
+#define FRAME_death11 157
+#define FRAME_death12 158
+#define FRAME_death13 159
+#define FRAME_death14 160
+#define FRAME_death15 161
+#define FRAME_death16 162
+#define FRAME_death17 163
+#define FRAME_death18 164
+#define FRAME_death19 165
+#define FRAME_death20 166
+#define FRAME_death21 167
+#define FRAME_death22 168
+#define FRAME_death23 169
+#define FRAME_death24 170
+#define FRAME_death25 171
+#define FRAME_death26 172
+#define FRAME_death27 173
+#define FRAME_death28 174
+#define FRAME_death29 175
+#define FRAME_death30 176
+#define FRAME_attack1 177
+#define FRAME_attack2 178
+#define FRAME_attack3 179
+#define FRAME_attack4 180
+#define FRAME_attack5 181
+#define FRAME_attack6 182
+#define FRAME_attack7 183
+#define FRAME_attack8 184
+#define FRAME_attack9 185
+#define FRAME_attack10 186
+#define FRAME_attack11 187
+#define FRAME_attack12 188
+#define FRAME_attack13 189
+#define FRAME_attack14 190
+#define FRAME_attack15 191
+#define FRAME_attack16 192
+#define FRAME_attack17 193
+#define FRAME_attack18 194
+#define FRAME_attack19 195
+#define FRAME_attack20 196
+#define FRAME_attack21 197
+#define FRAME_attack22 198
+#define FRAME_attack23 199
+#define FRAME_attack24 200
+#define FRAME_attack25 201
+#define FRAME_attack26 202
+#define FRAME_attack27 203
+#define FRAME_attack28 204
+#define FRAME_attack29 205
+#define FRAME_attack30 206
+#define FRAME_attack31 207
+#define FRAME_attack32 208
+#define FRAME_attack33 209
+#define FRAME_attack34 210
+#define FRAME_attack35 211
+#define FRAME_attack36 212
+#define FRAME_attack37 213
+#define FRAME_attack38 214
+#define FRAME_attack39 215
+#define FRAME_attack40 216
+#define FRAME_attack41 217
+#define FRAME_attack42 218
+#define FRAME_attack43 219
+#define FRAME_attack44 220
+#define FRAME_attack45 221
+#define FRAME_attack46 222
+#define FRAME_attack47 223
+#define FRAME_attack48 224
+#define FRAME_attack49 225
+#define FRAME_attack50 226
+#define FRAME_attack51 227
+#define FRAME_attack52 228
+#define FRAME_attack53 229
+#define FRAME_attack54 230
+#define FRAME_attack55 231
+#define FRAME_attack56 232
+#define FRAME_attack57 233
+#define FRAME_attack58 234
+#define FRAME_attack59 235
+#define FRAME_attack60 236
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/misc/move.c b/rogue/src/monster/misc/move.c
new file mode 100644
index 0000000..e4e79e7
--- /dev/null
+++ b/rogue/src/monster/misc/move.c
@@ -0,0 +1,855 @@
+/* =======================================================================
+ *
+ * Monster movement support functions.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+
+#define DI_NODIR -1
+#define STEPSIZE 18
+
+/* this is used for communications out of
+ * sv_movestep to say what entity is blocking us */
+edict_t *new_bad;
+
+/*
+ * Returns false if any part of the bottom of the
+ * entity is off an edge that is not a staircase.
+ */
+
+qboolean
+M_CheckBottom(edict_t *ent)
+{
+ vec3_t mins, maxs, start, stop;
+ trace_t trace;
+ int x, y;
+ float mid, bottom;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ VectorAdd(ent->s.origin, ent->mins, mins);
+ VectorAdd(ent->s.origin, ent->maxs, maxs);
+
+ /* if all of the points under the corners are solid world, don't bother
+ with the tougher checksthe corners must be within 16 of the midpoint */
+
+ start[2] = mins[2] - 1;
+
+ if (ent->gravityVector[2] > 0)
+ {
+ start[2] = maxs[2] + 1;
+ }
+
+ for (x = 0; x <= 1; x++)
+ {
+ for (y = 0; y <= 1; y++)
+ {
+ start[0] = x ? maxs[0] : mins[0];
+ start[1] = y ? maxs[1] : mins[1];
+
+ if (gi.pointcontents(start) != CONTENTS_SOLID)
+ {
+ goto realcheck;
+ }
+ }
+ }
+
+ return true; /* we got out easy */
+
+realcheck:
+ start[2] = mins[2];
+
+ /* the midpoint must be within 16 of the bottom */
+ start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5;
+ start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5;
+
+ if (ent->gravityVector[2] < 0)
+ {
+ start[2] = mins[2];
+ stop[2] = start[2] - STEPSIZE - STEPSIZE;
+ }
+ else
+ {
+ start[2] = maxs[2];
+ stop[2] = start[2] + STEPSIZE + STEPSIZE;
+ }
+
+ trace = gi.trace(start, vec3_origin, vec3_origin,
+ stop, ent, MASK_MONSTERSOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return false;
+ }
+
+ mid = bottom = trace.endpos[2];
+
+ /* the corners must be within 16 of the midpoint */
+ for (x = 0; x <= 1; x++)
+ {
+ for (y = 0; y <= 1; y++)
+ {
+ start[0] = stop[0] = x ? maxs[0] : mins[0];
+ start[1] = stop[1] = y ? maxs[1] : mins[1];
+
+ trace = gi.trace(start, vec3_origin, vec3_origin,
+ stop, ent, MASK_MONSTERSOLID);
+
+ if (ent->gravityVector[2] > 0)
+ {
+ if ((trace.fraction != 1.0) && (trace.endpos[2] < bottom))
+ {
+ bottom = trace.endpos[2];
+ }
+
+ if ((trace.fraction == 1.0) ||
+ (trace.endpos[2] - mid > STEPSIZE))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if ((trace.fraction != 1.0) && (trace.endpos[2] > bottom))
+ {
+ bottom = trace.endpos[2];
+ }
+
+ if ((trace.fraction == 1.0) ||
+ (mid - trace.endpos[2] > STEPSIZE))
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+qboolean
+IsBadAhead(edict_t *self, edict_t *bad, vec3_t move)
+{
+ vec3_t dir;
+ vec3_t forward;
+ float dp_bad, dp_move;
+ vec3_t move_copy;
+
+ if (!self || !bad)
+ {
+ return false;
+ }
+
+ VectorCopy(move, move_copy);
+
+ VectorSubtract(bad->s.origin, self->s.origin, dir);
+ VectorNormalize(dir);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ dp_bad = DotProduct(forward, dir);
+
+ VectorNormalize(move_copy);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ dp_move = DotProduct(forward, move_copy);
+
+ if ((dp_bad < 0) && (dp_move < 0))
+ {
+ return true;
+ }
+
+ if ((dp_bad > 0) && (dp_move > 0))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * The move will be adjusted for slopes and stairs, but if the move isn't
+ * possible, no move is done, false is returned, and
+ * pr_global_struct->trace_normal is set to the normal of the blocking wall
+ */
+qboolean
+SV_movestep(edict_t *ent, vec3_t move, qboolean relink)
+{
+ float dz;
+ vec3_t oldorg, neworg, end;
+ trace_t trace;
+ int i;
+ float stepsize;
+ vec3_t test;
+ int contents;
+ edict_t *current_bad = NULL;
+ float minheight;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (ent->health > 0)
+ {
+ current_bad = CheckForBadArea(ent);
+
+ if (current_bad)
+ {
+ ent->bad_area = current_bad;
+
+ if (ent->enemy && !strcmp(ent->enemy->classname, "tesla"))
+ {
+ /* if the tesla is in front of us, back up... */
+ if (IsBadAhead(ent, current_bad, move))
+ {
+ VectorScale(move, -1, move);
+ }
+ }
+ }
+ else if (ent->bad_area)
+ {
+ /* if we're no longer in a bad area, get back to business. */
+ ent->bad_area = NULL;
+
+ if (ent->oldenemy)
+ {
+ ent->enemy = ent->oldenemy;
+ ent->goalentity = ent->oldenemy;
+ FoundTarget(ent);
+ return true;
+ }
+ }
+ }
+
+ /* try the move */
+ VectorCopy(ent->s.origin, oldorg);
+ VectorAdd(ent->s.origin, move, neworg);
+
+ /* flying monsters don't step up */
+ if (ent->flags & (FL_SWIM | FL_FLY))
+ {
+ /* try one move with vertical motion, then one without */
+ for (i = 0; i < 2; i++)
+ {
+ VectorAdd(ent->s.origin, move, neworg);
+
+ if ((i == 0) && ent->enemy)
+ {
+ if (!ent->goalentity)
+ {
+ ent->goalentity = ent->enemy;
+ }
+
+ dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
+
+ if (ent->goalentity->client)
+ {
+ /* we want the carrier to stay a certain distance off the ground,
+ to help prevent him from shooting his fliers, who spawn in below him */
+ if (!strcmp(ent->classname, "monster_carrier"))
+ {
+ minheight = 104;
+ }
+ else
+ {
+ minheight = 40;
+ }
+
+ if (dz > minheight)
+ {
+ neworg[2] -= 8;
+ }
+
+ if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
+ {
+ if (dz < (minheight - 10))
+ {
+ neworg[2] += 8;
+ }
+ }
+ }
+ else
+ {
+ if (dz > 8)
+ {
+ neworg[2] -= 8;
+ }
+ else if (dz > 0)
+ {
+ neworg[2] -= dz;
+ }
+ else if (dz < -8)
+ {
+ neworg[2] += 8;
+ }
+ else
+ {
+ neworg[2] += dz;
+ }
+ }
+ }
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, neworg,
+ ent, MASK_MONSTERSOLID);
+
+ /* fly monsters don't enter water voluntarily */
+ if (ent->flags & FL_FLY)
+ {
+ if (!ent->waterlevel)
+ {
+ test[0] = trace.endpos[0];
+ test[1] = trace.endpos[1];
+ test[2] = trace.endpos[2] + ent->mins[2] + 1;
+ contents = gi.pointcontents(test);
+
+ if (contents & MASK_WATER)
+ {
+ return false;
+ }
+ }
+ }
+
+ /* swim monsters don't exit water voluntarily */
+ if (ent->flags & FL_SWIM)
+ {
+ if (ent->waterlevel < 2)
+ {
+ test[0] = trace.endpos[0];
+ test[1] = trace.endpos[1];
+ test[2] = trace.endpos[2] + ent->mins[2] + 1;
+ contents = gi.pointcontents(test);
+
+ if (!(contents & MASK_WATER))
+ {
+ return false;
+ }
+ }
+ }
+
+ if ((trace.fraction == 1) && (!trace.allsolid) && (!trace.startsolid))
+ {
+ VectorCopy(trace.endpos, ent->s.origin);
+
+ if (!current_bad && CheckForBadArea(ent))
+ {
+ VectorCopy(oldorg, ent->s.origin);
+ }
+ else
+ {
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ return true;
+ }
+ }
+
+ if (!ent->enemy)
+ {
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ /* push down from a step height above the wished position */
+ if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
+ {
+ stepsize = STEPSIZE;
+ }
+ else
+ {
+ stepsize = 1;
+ }
+
+ /* trace from 1 stepsize gravityUp to 2 stepsize gravityDown. */
+ VectorMA(neworg, -1 * stepsize, ent->gravityVector, neworg);
+ VectorMA(neworg, 2 * stepsize, ent->gravityVector, end);
+
+ trace = gi.trace(neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+ if (trace.allsolid)
+ {
+ return false;
+ }
+
+ if (trace.startsolid)
+ {
+ neworg[2] -= stepsize;
+ trace = gi.trace(neworg, ent->mins, ent->maxs, end,
+ ent, MASK_MONSTERSOLID);
+
+ if (trace.allsolid || trace.startsolid)
+ {
+ return false;
+ }
+ }
+
+ /* don't go in to water */
+ if (ent->waterlevel == 0)
+ {
+ test[0] = trace.endpos[0];
+ test[1] = trace.endpos[1];
+
+ if (ent->gravityVector[2] > 0)
+ {
+ test[2] = trace.endpos[2] + ent->maxs[2] - 1;
+ }
+ else
+ {
+ test[2] = trace.endpos[2] + ent->mins[2] + 1;
+ }
+
+ contents = gi.pointcontents(test);
+
+ if (contents & MASK_WATER)
+ {
+ return false;
+ }
+ }
+
+ if (trace.fraction == 1)
+ {
+ /* if monster had the ground pulled out, go ahead and fall */
+ if (ent->flags & FL_PARTIALGROUND)
+ {
+ VectorAdd(ent->s.origin, move, ent->s.origin);
+
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ ent->groundentity = NULL;
+
+ return true;
+ }
+
+ return false;/* walked off an edge */
+ }
+
+ /* check point traces down for dangling corners */
+ VectorCopy(trace.endpos, ent->s.origin);
+
+ if (ent->health > 0)
+ {
+ /* use AI_BLOCKED to tell the calling layer that we're now mad at a tesla */
+ new_bad = CheckForBadArea(ent);
+
+ if (!current_bad && new_bad)
+ {
+ if (new_bad->owner && !strcmp(new_bad->owner->classname, "tesla"))
+ {
+ if (!ent->enemy || !ent->enemy->inuse ||
+ !ent->enemy->client || !visible(ent, ent->enemy))
+ {
+ TargetTesla(ent, new_bad->owner);
+ ent->monsterinfo.aiflags |= AI_BLOCKED;
+ }
+ }
+
+ VectorCopy(oldorg, ent->s.origin);
+ return false;
+ }
+ }
+
+ if (!M_CheckBottom(ent))
+ {
+ if (ent->flags & FL_PARTIALGROUND)
+ {
+ /* entity had floor mostly pulled out from
+ nderneath it and is trying to correct */
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ return true;
+ }
+
+ VectorCopy(oldorg, ent->s.origin);
+ return false;
+ }
+
+ if (ent->flags & FL_PARTIALGROUND)
+ {
+ ent->flags &= ~FL_PARTIALGROUND;
+ }
+
+ ent->groundentity = trace.ent;
+ ent->groundentity_linkcount = trace.ent->linkcount;
+
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ return true;
+}
+
+/* ============================================================================ */
+
+void
+M_ChangeYaw(edict_t *ent)
+{
+ float ideal;
+ float current;
+ float move;
+ float speed;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ current = anglemod(ent->s.angles[YAW]);
+ ideal = ent->ideal_yaw;
+
+ if (current == ideal)
+ {
+ return;
+ }
+
+ move = ideal - current;
+ speed = ent->yaw_speed;
+
+ if (ideal > current)
+ {
+ if (move >= 180)
+ {
+ move = move - 360;
+ }
+ }
+ else
+ {
+ if (move <= -180)
+ {
+ move = move + 360;
+ }
+ }
+
+ if (move > 0)
+ {
+ if (move > speed)
+ {
+ move = speed;
+ }
+ }
+ else
+ {
+ if (move < -speed)
+ {
+ move = -speed;
+ }
+ }
+
+ ent->s.angles[YAW] = anglemod(current + move);
+}
+
+/*
+ * Turns to the movement direction, and
+ * walks the current distance if facing it.
+ */
+qboolean
+SV_StepDirection(edict_t *ent, float yaw, float dist)
+{
+ vec3_t move, oldorigin;
+ float delta;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!ent->inuse)
+ {
+ return true;
+ }
+
+ ent->ideal_yaw = yaw;
+ M_ChangeYaw(ent);
+
+ yaw = yaw * M_PI * 2 / 360;
+ move[0] = cos(yaw) * dist;
+ move[1] = sin(yaw) * dist;
+ move[2] = 0;
+
+ VectorCopy(ent->s.origin, oldorigin);
+
+ if (SV_movestep(ent, move, false))
+ {
+ ent->monsterinfo.aiflags &= ~AI_BLOCKED;
+
+ if (!ent->inuse)
+ {
+ return true;
+ }
+
+ delta = ent->s.angles[YAW] - ent->ideal_yaw;
+
+ if (strncmp(ent->classname, "monster_widow", 13))
+ {
+ if ((delta > 45) && (delta < 315))
+ {
+ /* not turned far enough, so don't take the step */
+ VectorCopy(oldorigin, ent->s.origin);
+ }
+ }
+
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ return true;
+ }
+
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ return false;
+}
+
+void
+SV_FixCheckBottom(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->flags |= FL_PARTIALGROUND;
+}
+
+void
+SV_NewChaseDir(edict_t *actor, edict_t *enemy, float dist)
+{
+ float deltax, deltay;
+ float d[3];
+ float tdir, olddir, turnaround;
+
+ if (!actor || !enemy)
+ {
+ return;
+ }
+
+ olddir = anglemod((int)(actor->ideal_yaw / 45) * 45);
+ turnaround = anglemod(olddir - 180);
+
+ deltax = enemy->s.origin[0] - actor->s.origin[0];
+ deltay = enemy->s.origin[1] - actor->s.origin[1];
+
+ if (deltax > 10)
+ {
+ d[1] = 0;
+ }
+ else if (deltax < -10)
+ {
+ d[1] = 180;
+ }
+ else
+ {
+ d[1] = DI_NODIR;
+ }
+
+ if (deltay < -10)
+ {
+ d[2] = 270;
+ }
+ else if (deltay > 10)
+ {
+ d[2] = 90;
+ }
+ else
+ {
+ d[2] = DI_NODIR;
+ }
+
+ /* try direct route */
+ if ((d[1] != DI_NODIR) && (d[2] != DI_NODIR))
+ {
+ if (d[1] == 0)
+ {
+ tdir = d[2] == 90 ? 45 : 315;
+ }
+ else
+ {
+ tdir = d[2] == 90 ? 135 : 215;
+ }
+
+ if ((tdir != turnaround) && SV_StepDirection(actor, tdir, dist))
+ {
+ return;
+ }
+ }
+
+ /* try other directions */
+ if (((rand() & 3) & 1) || (fabsf(deltay) > fabsf(deltax)))
+ {
+ tdir = d[1];
+ d[1] = d[2];
+ d[2] = tdir;
+ }
+
+ if ((d[1] != DI_NODIR) && (d[1] != turnaround) &&
+ SV_StepDirection(actor, d[1], dist))
+ {
+ return;
+ }
+
+ if ((d[2] != DI_NODIR) && (d[2] != turnaround) &&
+ SV_StepDirection(actor, d[2], dist))
+ {
+ return;
+ }
+
+ if (actor->monsterinfo.blocked)
+ {
+ if ((actor->inuse) && (actor->health > 0))
+ {
+ if ((actor->monsterinfo.blocked)(actor, dist))
+ {
+ return;
+ }
+ }
+ }
+
+ if ((olddir != DI_NODIR) && SV_StepDirection(actor, olddir, dist))
+ {
+ return;
+ }
+
+ if (rand() & 1) /*randomly determine direction of search*/
+ {
+ for (tdir = 0; tdir <= 315; tdir += 45)
+ {
+ if ((tdir != turnaround) && SV_StepDirection(actor, tdir, dist))
+ {
+ return;
+ }
+ }
+ }
+ else
+ {
+ for (tdir = 315; tdir >= 0; tdir -= 45)
+ {
+ if ((tdir != turnaround) && SV_StepDirection(actor, tdir, dist))
+ {
+ return;
+ }
+ }
+ }
+
+ if ((turnaround != DI_NODIR) && SV_StepDirection(actor, turnaround, dist))
+ {
+ return;
+ }
+
+ actor->ideal_yaw = olddir; /* can't move */
+
+ /* if a bridge was pulled out from underneath a monster,
+ it may not have a valid standing position at all */
+
+ if (!M_CheckBottom(actor))
+ {
+ SV_FixCheckBottom(actor);
+ }
+}
+
+qboolean
+SV_CloseEnough(edict_t *ent, edict_t *goal, float dist)
+{
+ int i;
+
+ if (!ent || !goal)
+ {
+ return false;
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ if (goal->absmin[i] > ent->absmax[i] + dist)
+ {
+ return false;
+ }
+
+ if (goal->absmax[i] < ent->absmin[i] - dist)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+M_MoveToGoal(edict_t *ent, float dist)
+{
+ edict_t *goal;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ goal = ent->goalentity;
+
+ if (!ent->groundentity && !(ent->flags & (FL_FLY | FL_SWIM)))
+ {
+ return;
+ }
+
+ /* if the next step hits the enemy, return immediately */
+ if (ent->enemy && SV_CloseEnough(ent, ent->enemy, dist))
+ {
+ return;
+ }
+
+ if ((((rand() & 3) == 1) &&
+ !(ent->monsterinfo.aiflags & AI_CHARGING)) ||
+ !SV_StepDirection(ent, ent->ideal_yaw, dist))
+ {
+ if (ent->monsterinfo.aiflags & AI_BLOCKED)
+ {
+ ent->monsterinfo.aiflags &= ~AI_BLOCKED;
+ return;
+ }
+
+ if (ent->inuse)
+ {
+ SV_NewChaseDir(ent, goal, dist);
+ }
+ }
+}
+
+qboolean
+M_walkmove(edict_t *ent, float yaw, float dist)
+{
+ vec3_t move;
+ qboolean retval;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!ent->groundentity && !(ent->flags & (FL_FLY | FL_SWIM)))
+ {
+ return false;
+ }
+
+ yaw = yaw * M_PI * 2 / 360;
+
+ move[0] = cos(yaw) * dist;
+ move[1] = sin(yaw) * dist;
+ move[2] = 0;
+
+ retval = SV_movestep(ent, move, true);
+ ent->monsterinfo.aiflags &= ~AI_BLOCKED;
+ return retval;
+}
diff --git a/rogue/src/monster/misc/player.h b/rogue/src/monster/misc/player.h
new file mode 100644
index 0000000..2758c23
--- /dev/null
+++ b/rogue/src/monster/misc/player.h
@@ -0,0 +1,207 @@
+/* =======================================================================
+ *
+ * Player (the arm and the weapons) animation.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_stand31 30
+#define FRAME_stand32 31
+#define FRAME_stand33 32
+#define FRAME_stand34 33
+#define FRAME_stand35 34
+#define FRAME_stand36 35
+#define FRAME_stand37 36
+#define FRAME_stand38 37
+#define FRAME_stand39 38
+#define FRAME_stand40 39
+#define FRAME_run1 40
+#define FRAME_run2 41
+#define FRAME_run3 42
+#define FRAME_run4 43
+#define FRAME_run5 44
+#define FRAME_run6 45
+#define FRAME_attack1 46
+#define FRAME_attack2 47
+#define FRAME_attack3 48
+#define FRAME_attack4 49
+#define FRAME_attack5 50
+#define FRAME_attack6 51
+#define FRAME_attack7 52
+#define FRAME_attack8 53
+#define FRAME_pain101 54
+#define FRAME_pain102 55
+#define FRAME_pain103 56
+#define FRAME_pain104 57
+#define FRAME_pain201 58
+#define FRAME_pain202 59
+#define FRAME_pain203 60
+#define FRAME_pain204 61
+#define FRAME_pain301 62
+#define FRAME_pain302 63
+#define FRAME_pain303 64
+#define FRAME_pain304 65
+#define FRAME_jump1 66
+#define FRAME_jump2 67
+#define FRAME_jump3 68
+#define FRAME_jump4 69
+#define FRAME_jump5 70
+#define FRAME_jump6 71
+#define FRAME_flip01 72
+#define FRAME_flip02 73
+#define FRAME_flip03 74
+#define FRAME_flip04 75
+#define FRAME_flip05 76
+#define FRAME_flip06 77
+#define FRAME_flip07 78
+#define FRAME_flip08 79
+#define FRAME_flip09 80
+#define FRAME_flip10 81
+#define FRAME_flip11 82
+#define FRAME_flip12 83
+#define FRAME_salute01 84
+#define FRAME_salute02 85
+#define FRAME_salute03 86
+#define FRAME_salute04 87
+#define FRAME_salute05 88
+#define FRAME_salute06 89
+#define FRAME_salute07 90
+#define FRAME_salute08 91
+#define FRAME_salute09 92
+#define FRAME_salute10 93
+#define FRAME_salute11 94
+#define FRAME_taunt01 95
+#define FRAME_taunt02 96
+#define FRAME_taunt03 97
+#define FRAME_taunt04 98
+#define FRAME_taunt05 99
+#define FRAME_taunt06 100
+#define FRAME_taunt07 101
+#define FRAME_taunt08 102
+#define FRAME_taunt09 103
+#define FRAME_taunt10 104
+#define FRAME_taunt11 105
+#define FRAME_taunt12 106
+#define FRAME_taunt13 107
+#define FRAME_taunt14 108
+#define FRAME_taunt15 109
+#define FRAME_taunt16 110
+#define FRAME_taunt17 111
+#define FRAME_wave01 112
+#define FRAME_wave02 113
+#define FRAME_wave03 114
+#define FRAME_wave04 115
+#define FRAME_wave05 116
+#define FRAME_wave06 117
+#define FRAME_wave07 118
+#define FRAME_wave08 119
+#define FRAME_wave09 120
+#define FRAME_wave10 121
+#define FRAME_wave11 122
+#define FRAME_point01 123
+#define FRAME_point02 124
+#define FRAME_point03 125
+#define FRAME_point04 126
+#define FRAME_point05 127
+#define FRAME_point06 128
+#define FRAME_point07 129
+#define FRAME_point08 130
+#define FRAME_point09 131
+#define FRAME_point10 132
+#define FRAME_point11 133
+#define FRAME_point12 134
+#define FRAME_crstnd01 135
+#define FRAME_crstnd02 136
+#define FRAME_crstnd03 137
+#define FRAME_crstnd04 138
+#define FRAME_crstnd05 139
+#define FRAME_crstnd06 140
+#define FRAME_crstnd07 141
+#define FRAME_crstnd08 142
+#define FRAME_crstnd09 143
+#define FRAME_crstnd10 144
+#define FRAME_crstnd11 145
+#define FRAME_crstnd12 146
+#define FRAME_crstnd13 147
+#define FRAME_crstnd14 148
+#define FRAME_crstnd15 149
+#define FRAME_crstnd16 150
+#define FRAME_crstnd17 151
+#define FRAME_crstnd18 152
+#define FRAME_crstnd19 153
+#define FRAME_crwalk1 154
+#define FRAME_crwalk2 155
+#define FRAME_crwalk3 156
+#define FRAME_crwalk4 157
+#define FRAME_crwalk5 158
+#define FRAME_crwalk6 159
+#define FRAME_crattak1 160
+#define FRAME_crattak2 161
+#define FRAME_crattak3 162
+#define FRAME_crattak4 163
+#define FRAME_crattak5 164
+#define FRAME_crattak6 165
+#define FRAME_crattak7 166
+#define FRAME_crattak8 167
+#define FRAME_crattak9 168
+#define FRAME_crpain1 169
+#define FRAME_crpain2 170
+#define FRAME_crpain3 171
+#define FRAME_crpain4 172
+#define FRAME_crdeath1 173
+#define FRAME_crdeath2 174
+#define FRAME_crdeath3 175
+#define FRAME_crdeath4 176
+#define FRAME_crdeath5 177
+#define FRAME_death101 178
+#define FRAME_death102 179
+#define FRAME_death103 180
+#define FRAME_death104 181
+#define FRAME_death105 182
+#define FRAME_death106 183
+#define FRAME_death201 184
+#define FRAME_death202 185
+#define FRAME_death203 186
+#define FRAME_death204 187
+#define FRAME_death205 188
+#define FRAME_death206 189
+#define FRAME_death301 190
+#define FRAME_death302 191
+#define FRAME_death303 192
+#define FRAME_death304 193
+#define FRAME_death305 194
+#define FRAME_death306 195
+#define FRAME_death307 196
+#define FRAME_death308 197
+#define MODEL_SCALE 1.000000
+
diff --git a/rogue/src/monster/mutant/mutant.c b/rogue/src/monster/mutant/mutant.c
new file mode 100644
index 0000000..025cf89
--- /dev/null
+++ b/rogue/src/monster/mutant/mutant.c
@@ -0,0 +1,980 @@
+/* =======================================================================
+ *
+ * Mutant.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "mutant.h"
+
+static int sound_swing;
+static int sound_hit;
+static int sound_hit2;
+static int sound_death;
+static int sound_idle;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_sight;
+static int sound_search;
+static int sound_step1;
+static int sound_step2;
+static int sound_step3;
+static int sound_thud;
+
+void mutant_walk(edict_t *self);
+
+void
+mutant_step(edict_t *self)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ n = (rand() + 1) % 3;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0);
+ }
+ else if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+mutant_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+mutant_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void
+mutant_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t mutant_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 10 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 20 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 30 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 40 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 50 */
+
+ {ai_stand, 0, NULL}
+};
+
+mmove_t mutant_move_stand = {
+ FRAME_stand101,
+ FRAME_stand151,
+ mutant_frames_stand,
+ NULL
+};
+
+void
+mutant_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_stand;
+}
+
+void
+mutant_idle_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.75)
+ {
+ self->monsterinfo.nextframe = FRAME_stand155;
+ }
+}
+
+mframe_t mutant_frames_idle[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* scratch loop start */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, mutant_idle_loop}, /* scratch loop end */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t mutant_move_idle = {
+ FRAME_stand152,
+ FRAME_stand164,
+ mutant_frames_idle,
+ mutant_stand
+};
+
+void
+mutant_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_idle;
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t mutant_frames_walk[] = {
+ {ai_walk, 3, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 13, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 16, NULL},
+ {ai_walk, 15, NULL},
+ {ai_walk, 6, NULL}
+};
+
+mmove_t mutant_move_walk = {
+ FRAME_walk05,
+ FRAME_walk16,
+ mutant_frames_walk,
+ NULL
+};
+
+void
+mutant_walk_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_walk;
+}
+
+mframe_t mutant_frames_start_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, -2, NULL},
+ {ai_walk, 1, NULL}
+};
+
+mmove_t mutant_move_start_walk = {
+ FRAME_walk01,
+ FRAME_walk04,
+ mutant_frames_start_walk,
+ mutant_walk_loop
+};
+
+void
+mutant_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_start_walk;
+}
+
+mframe_t mutant_frames_run[] = {
+ {ai_run, 40, NULL},
+ {ai_run, 40, mutant_step},
+ {ai_run, 24, NULL},
+ {ai_run, 5, mutant_step},
+ {ai_run, 17, NULL},
+ {ai_run, 10, NULL}
+};
+
+mmove_t mutant_move_run = {
+ FRAME_run03,
+ FRAME_run08,
+ mutant_frames_run,
+ NULL
+};
+
+void
+mutant_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &mutant_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &mutant_move_run;
+ }
+}
+
+void
+mutant_hit_left(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8);
+
+ if (fire_hit(self, aim, (10 + (rand() % 5)), 100))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+mutant_hit_right(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8);
+
+ if (fire_hit(self, aim, (10 + (rand() % 5)), 100))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+mutant_check_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse || (self->enemy->health <= 0))
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attack09;
+ }
+}
+
+mframe_t mutant_frames_attack[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, mutant_hit_left},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, mutant_hit_right},
+ {ai_charge, 0, mutant_check_refire}
+};
+
+mmove_t mutant_move_attack = {
+ FRAME_attack09,
+ FRAME_attack15,
+ mutant_frames_attack,
+ mutant_run
+};
+
+void
+mutant_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_attack;
+}
+
+void
+mutant_jump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->health <= 0)
+ {
+ self->touch = NULL;
+ return;
+ }
+
+ if (other->takedamage)
+ {
+ if (VectorLength(self->velocity) > 400)
+ {
+ vec3_t point;
+ vec3_t normal;
+ int damage;
+
+ VectorCopy(self->velocity, normal);
+ VectorNormalize(normal);
+ VectorMA(self->s.origin, self->maxs[0], normal, point);
+ damage = 40 + 10 * random();
+ T_Damage(other, self, self, self->velocity, point,
+ normal, damage, damage, 0, MOD_UNKNOWN);
+ }
+ }
+
+ if (!M_CheckBottom(self))
+ {
+ if (self->groundentity)
+ {
+ self->monsterinfo.nextframe = FRAME_attack02;
+ self->touch = NULL;
+ }
+
+ return;
+ }
+
+ self->touch = NULL;
+}
+
+void
+mutant_jump_takeoff(edict_t *self)
+{
+ vec3_t forward;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ self->s.origin[2] += 1;
+ VectorScale(forward, 600, self->velocity);
+ self->velocity[2] = 250;
+ self->groundentity = NULL;
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->monsterinfo.attack_finished = level.time + 3;
+ self->touch = mutant_jump_touch;
+}
+
+void
+mutant_check_landing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
+ self->monsterinfo.attack_finished = 0;
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ return;
+ }
+
+ if (level.time > self->monsterinfo.attack_finished)
+ {
+ self->monsterinfo.nextframe = FRAME_attack02;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attack05;
+ }
+}
+
+mframe_t mutant_frames_jump[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 17, NULL},
+ {ai_charge, 15, mutant_jump_takeoff},
+ {ai_charge, 15, NULL},
+ {ai_charge, 15, mutant_check_landing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t mutant_move_jump = {
+ FRAME_attack01,
+ FRAME_attack08,
+ mutant_frames_jump,
+ mutant_run
+};
+
+void
+mutant_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_jump;
+}
+
+qboolean
+mutant_check_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+mutant_check_jump(edict_t *self)
+{
+ vec3_t v;
+ float distance;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2]))
+ {
+ return false;
+ }
+
+ if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2]))
+ {
+ return false;
+ }
+
+ v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+ v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+ v[2] = 0;
+ distance = VectorLength(v);
+
+ if (distance < 100)
+ {
+ return false;
+ }
+
+ if (distance > 100)
+ {
+ if (random() < 0.9)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+qboolean
+mutant_checkattack(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy || (self->enemy->health <= 0))
+ {
+ return false;
+ }
+
+ if (mutant_check_melee(self))
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ return true;
+ }
+
+ if (mutant_check_jump(self))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ return false;
+}
+
+mframe_t mutant_frames_pain1[] = {
+ {ai_move, 4, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 5, NULL}
+};
+
+mmove_t mutant_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain105,
+ mutant_frames_pain1,
+ mutant_run
+};
+
+mframe_t mutant_frames_pain2[] = {
+ {ai_move, -24, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 4, NULL}
+};
+
+mmove_t mutant_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain206,
+ mutant_frames_pain2,
+ mutant_run
+};
+
+mframe_t mutant_frames_pain3[] = {
+ {ai_move, -22, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t mutant_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain311,
+ mutant_frames_pain3,
+ mutant_run
+};
+
+void
+mutant_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &mutant_move_pain1;
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &mutant_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &mutant_move_pain3;
+ }
+}
+
+void
+mutant_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ gi.linkentity(self);
+
+ M_FlyCheck(self);
+}
+
+mframe_t mutant_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t mutant_move_death1 = {
+ FRAME_death101,
+ FRAME_death109,
+ mutant_frames_death1,
+ mutant_dead
+};
+
+mframe_t mutant_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t mutant_move_death2 = {
+ FRAME_death201,
+ FRAME_death210,
+ mutant_frames_death2,
+ mutant_dead
+};
+
+void
+mutant_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unsued */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum = 1;
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &mutant_move_death1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &mutant_move_death2;
+ }
+}
+
+void
+mutant_jump_down(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 100, forward, self->velocity);
+ VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void
+mutant_jump_up(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 200, forward, self->velocity);
+ VectorMA(self->velocity, 450, up, self->velocity);
+}
+
+void
+mutant_jump_wait_land(edict_t *self)
+{
+ if (self->groundentity == NULL)
+ {
+ self->monsterinfo.nextframe = self->s.frame;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+}
+
+mframe_t mutant_frames_jump_up[] = {
+ {ai_move, -8, NULL},
+ {ai_move, -8, mutant_jump_up},
+ {ai_move, 0, mutant_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t mutant_move_jump_up = {
+ FRAME_jump01,
+ FRAME_jump05,
+ mutant_frames_jump_up,
+ mutant_run
+};
+
+mframe_t mutant_frames_jump_down[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, mutant_jump_down},
+ {ai_move, 0, mutant_jump_wait_land},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t mutant_move_jump_down = {
+ FRAME_jump01,
+ FRAME_jump05,
+ mutant_frames_jump_down,
+ mutant_run
+};
+
+void
+mutant_jump_updown(edict_t *self)
+{
+ if (!self || !self->enemy)
+ {
+ return;
+ }
+
+ if (self->enemy->absmin[2] > self->absmin[2])
+ {
+ self->monsterinfo.currentmove = &mutant_move_jump_up;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &mutant_move_jump_down;
+ }
+}
+
+qboolean
+mutant_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkjump(self, dist, 256, 68))
+ {
+ mutant_jump_updown(self);
+ return true;
+ }
+
+ if (blocked_checkplat(self, dist))
+ return true;
+
+ return false;
+}
+
+/*
+ * QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_mutant(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_swing = gi.soundindex("mutant/mutatck1.wav");
+ sound_hit = gi.soundindex("mutant/mutatck2.wav");
+ sound_hit2 = gi.soundindex("mutant/mutatck3.wav");
+ sound_death = gi.soundindex("mutant/mutdeth1.wav");
+ sound_idle = gi.soundindex("mutant/mutidle1.wav");
+ sound_pain1 = gi.soundindex("mutant/mutpain1.wav");
+ sound_pain2 = gi.soundindex("mutant/mutpain2.wav");
+ sound_sight = gi.soundindex("mutant/mutsght1.wav");
+ sound_search = gi.soundindex("mutant/mutsrch1.wav");
+ sound_step1 = gi.soundindex("mutant/step1.wav");
+ sound_step2 = gi.soundindex("mutant/step2.wav");
+ sound_step3 = gi.soundindex("mutant/step3.wav");
+ sound_thud = gi.soundindex("mutant/thud1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/mutant/tris.md2");
+ VectorSet(self->mins, -32, -32, -24);
+ VectorSet(self->maxs, 32, 32, 48);
+
+ self->health = 300;
+ self->gib_health = -120;
+ self->mass = 300;
+
+ self->pain = mutant_pain;
+ self->die = mutant_die;
+
+ self->monsterinfo.stand = mutant_stand;
+ self->monsterinfo.walk = mutant_walk;
+ self->monsterinfo.run = mutant_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = mutant_jump;
+ self->monsterinfo.melee = mutant_melee;
+ self->monsterinfo.sight = mutant_sight;
+ self->monsterinfo.search = mutant_search;
+ self->monsterinfo.idle = mutant_idle;
+ self->monsterinfo.checkattack = mutant_checkattack;
+ self->monsterinfo.blocked = mutant_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &mutant_move_stand;
+
+ self->monsterinfo.scale = MODEL_SCALE;
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/mutant/mutant.h b/rogue/src/monster/mutant/mutant.h
new file mode 100644
index 0000000..49dce42
--- /dev/null
+++ b/rogue/src/monster/mutant/mutant.h
@@ -0,0 +1,162 @@
+/* =======================================================================
+ *
+ * Mutant animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attack01 0
+#define FRAME_attack02 1
+#define FRAME_attack03 2
+#define FRAME_attack04 3
+#define FRAME_attack05 4
+#define FRAME_attack06 5
+#define FRAME_attack07 6
+#define FRAME_attack08 7
+#define FRAME_attack09 8
+#define FRAME_attack10 9
+#define FRAME_attack11 10
+#define FRAME_attack12 11
+#define FRAME_attack13 12
+#define FRAME_attack14 13
+#define FRAME_attack15 14
+#define FRAME_death101 15
+#define FRAME_death102 16
+#define FRAME_death103 17
+#define FRAME_death104 18
+#define FRAME_death105 19
+#define FRAME_death106 20
+#define FRAME_death107 21
+#define FRAME_death108 22
+#define FRAME_death109 23
+#define FRAME_death201 24
+#define FRAME_death202 25
+#define FRAME_death203 26
+#define FRAME_death204 27
+#define FRAME_death205 28
+#define FRAME_death206 29
+#define FRAME_death207 30
+#define FRAME_death208 31
+#define FRAME_death209 32
+#define FRAME_death210 33
+#define FRAME_pain101 34
+#define FRAME_pain102 35
+#define FRAME_pain103 36
+#define FRAME_pain104 37
+#define FRAME_pain105 38
+#define FRAME_pain201 39
+#define FRAME_pain202 40
+#define FRAME_pain203 41
+#define FRAME_pain204 42
+#define FRAME_pain205 43
+#define FRAME_pain206 44
+#define FRAME_pain301 45
+#define FRAME_pain302 46
+#define FRAME_pain303 47
+#define FRAME_pain304 48
+#define FRAME_pain305 49
+#define FRAME_pain306 50
+#define FRAME_pain307 51
+#define FRAME_pain308 52
+#define FRAME_pain309 53
+#define FRAME_pain310 54
+#define FRAME_pain311 55
+#define FRAME_run03 56
+#define FRAME_run04 57
+#define FRAME_run05 58
+#define FRAME_run06 59
+#define FRAME_run07 60
+#define FRAME_run08 61
+#define FRAME_stand101 62
+#define FRAME_stand102 63
+#define FRAME_stand103 64
+#define FRAME_stand104 65
+#define FRAME_stand105 66
+#define FRAME_stand106 67
+#define FRAME_stand107 68
+#define FRAME_stand108 69
+#define FRAME_stand109 70
+#define FRAME_stand110 71
+#define FRAME_stand111 72
+#define FRAME_stand112 73
+#define FRAME_stand113 74
+#define FRAME_stand114 75
+#define FRAME_stand115 76
+#define FRAME_stand116 77
+#define FRAME_stand117 78
+#define FRAME_stand118 79
+#define FRAME_stand119 80
+#define FRAME_stand120 81
+#define FRAME_stand121 82
+#define FRAME_stand122 83
+#define FRAME_stand123 84
+#define FRAME_stand124 85
+#define FRAME_stand125 86
+#define FRAME_stand126 87
+#define FRAME_stand127 88
+#define FRAME_stand128 89
+#define FRAME_stand129 90
+#define FRAME_stand130 91
+#define FRAME_stand131 92
+#define FRAME_stand132 93
+#define FRAME_stand133 94
+#define FRAME_stand134 95
+#define FRAME_stand135 96
+#define FRAME_stand136 97
+#define FRAME_stand137 98
+#define FRAME_stand138 99
+#define FRAME_stand139 100
+#define FRAME_stand140 101
+#define FRAME_stand141 102
+#define FRAME_stand142 103
+#define FRAME_stand143 104
+#define FRAME_stand144 105
+#define FRAME_stand145 106
+#define FRAME_stand146 107
+#define FRAME_stand147 108
+#define FRAME_stand148 109
+#define FRAME_stand149 110
+#define FRAME_stand150 111
+#define FRAME_stand151 112
+#define FRAME_stand152 113
+#define FRAME_stand153 114
+#define FRAME_stand154 115
+#define FRAME_stand155 116
+#define FRAME_stand156 117
+#define FRAME_stand157 118
+#define FRAME_stand158 119
+#define FRAME_stand159 120
+#define FRAME_stand160 121
+#define FRAME_stand161 122
+#define FRAME_stand162 123
+#define FRAME_stand163 124
+#define FRAME_stand164 125
+#define FRAME_walk01 126
+#define FRAME_walk02 127
+#define FRAME_walk03 128
+#define FRAME_walk04 129
+#define FRAME_walk05 130
+#define FRAME_walk06 131
+#define FRAME_walk07 132
+#define FRAME_walk08 133
+#define FRAME_walk09 134
+#define FRAME_walk10 135
+#define FRAME_walk11 136
+#define FRAME_walk12 137
+#define FRAME_walk13 138
+#define FRAME_walk14 139
+#define FRAME_walk15 140
+#define FRAME_walk16 141
+#define FRAME_walk17 142
+#define FRAME_walk18 143
+#define FRAME_walk19 144
+#define FRAME_walk20 145
+#define FRAME_walk21 146
+#define FRAME_walk22 147
+#define FRAME_walk23 148
+#define FRAME_jump01 149
+#define FRAME_jump02 150
+#define FRAME_jump03 151
+#define FRAME_jump04 152
+#define FRAME_jump05 153
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/parasite/parasite.c b/rogue/src/monster/parasite/parasite.c
new file mode 100644
index 0000000..bb31830
--- /dev/null
+++ b/rogue/src/monster/parasite/parasite.c
@@ -0,0 +1,998 @@
+/* =======================================================================
+ *
+ * Parasite.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "parasite.h"
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die;
+static int sound_launch;
+static int sound_impact;
+static int sound_suck;
+static int sound_reelin;
+static int sound_sight;
+static int sound_tap;
+static int sound_scratch;
+static int sound_search;
+
+void parasite_stand(edict_t *self);
+void parasite_start_run(edict_t *self);
+void parasite_run(edict_t *self);
+void parasite_walk(edict_t *self);
+void parasite_start_walk(edict_t *self);
+void parasite_end_fidget(edict_t *self);
+void parasite_do_fidget(edict_t *self);
+void parasite_refidget(edict_t *self);
+
+void
+parasite_launch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0);
+}
+
+void
+parasite_reel_in(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0);
+}
+
+void
+parasite_sight(edict_t *self, edict_t *other)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+parasite_tap(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0);
+}
+
+void
+parasite_scratch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0);
+}
+
+void
+parasite_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0);
+}
+
+mframe_t parasite_frames_start_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t parasite_move_start_fidget = {
+ FRAME_stand18,
+ FRAME_stand21,
+ parasite_frames_start_fidget,
+ parasite_do_fidget
+};
+
+mframe_t parasite_frames_fidget[] = {
+ {ai_stand, 0, parasite_scratch},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_scratch},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t parasite_move_fidget = {
+ FRAME_stand22,
+ FRAME_stand27,
+ parasite_frames_fidget,
+ parasite_refidget
+};
+
+mframe_t parasite_frames_end_fidget[] = {
+ {ai_stand, 0, parasite_scratch},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t parasite_move_end_fidget = {
+ FRAME_stand28,
+ FRAME_stand35,
+ parasite_frames_end_fidget,
+ parasite_stand
+};
+
+void
+parasite_end_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_end_fidget;
+}
+
+void
+parasite_do_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_fidget;
+}
+
+void
+parasite_refidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.8)
+ {
+ self->monsterinfo.currentmove = &parasite_move_fidget;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &parasite_move_end_fidget;
+ }
+}
+
+void
+parasite_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_start_fidget;
+}
+
+mframe_t parasite_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap}
+};
+
+mmove_t parasite_move_stand = {
+ FRAME_stand01,
+ FRAME_stand17,
+ parasite_frames_stand,
+ parasite_stand
+};
+
+void
+parasite_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_stand;
+}
+
+mframe_t parasite_frames_run[] = {
+ {ai_run, 30, NULL},
+ {ai_run, 30, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 19, NULL},
+ {ai_run, 24, NULL},
+ {ai_run, 28, NULL},
+ {ai_run, 25, NULL}
+};
+
+mmove_t parasite_move_run = {
+ FRAME_run03,
+ FRAME_run09,
+ parasite_frames_run,
+ NULL
+};
+
+mframe_t parasite_frames_start_run[] = {
+ {ai_run, 0, NULL},
+ {ai_run, 30, NULL},
+};
+
+mmove_t parasite_move_start_run = {
+ FRAME_run01,
+ FRAME_run02,
+ parasite_frames_start_run,
+ parasite_run
+};
+
+mframe_t parasite_frames_stop_run[] = {
+ {ai_run, 20, NULL},
+ {ai_run, 20, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL}
+};
+
+mmove_t parasite_move_stop_run = {
+ FRAME_run10,
+ FRAME_run15,
+ parasite_frames_stop_run,
+ NULL
+};
+
+void
+parasite_start_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &parasite_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &parasite_move_start_run;
+ }
+}
+
+void
+parasite_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &parasite_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &parasite_move_run;
+ }
+}
+
+mframe_t parasite_frames_walk[] = {
+ {ai_walk, 30, NULL},
+ {ai_walk, 30, NULL},
+ {ai_walk, 22, NULL},
+ {ai_walk, 19, NULL},
+ {ai_walk, 24, NULL},
+ {ai_walk, 28, NULL},
+ {ai_walk, 25, NULL}
+};
+
+mmove_t parasite_move_walk = {
+ FRAME_run03,
+ FRAME_run09,
+ parasite_frames_walk,
+ parasite_walk
+};
+
+mframe_t parasite_frames_start_walk[] = {
+ {ai_walk, 0, NULL},
+ {ai_walk, 30, parasite_walk}
+};
+
+mmove_t parasite_move_start_walk = {
+ FRAME_run01,
+ FRAME_run02,
+ parasite_frames_start_walk,
+ NULL
+};
+
+mframe_t parasite_frames_stop_walk[] = {
+ {ai_walk, 20, NULL},
+ {ai_walk, 20, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t parasite_move_stop_walk = {
+ FRAME_run10,
+ FRAME_run15,
+ parasite_frames_stop_walk,
+ NULL
+};
+
+void
+parasite_start_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_start_walk;
+}
+
+void
+parasite_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_walk;
+}
+
+mframe_t parasite_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 16, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t parasite_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain111,
+ parasite_frames_pain1,
+ parasite_start_run
+};
+
+void
+parasite_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_pain1;
+}
+
+qboolean
+parasite_drain_attack_ok(vec3_t start, vec3_t end)
+{
+ vec3_t dir, angles;
+
+ /* check for max distance */
+ VectorSubtract(start, end, dir);
+
+ if (VectorLength(dir) > 256)
+ {
+ return false;
+ }
+
+ /* check for min/max pitch */
+ vectoangles(dir, angles);
+
+ if (angles[0] < -180)
+ {
+ angles[0] += 360;
+ }
+
+ if (fabs(angles[0]) > 30)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+parasite_drain_attack(edict_t *self)
+{
+ vec3_t offset, start, origStart, f, r, end, dir;
+ trace_t tr;
+ int damage;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorSet(offset, 24, 0, 6);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ VectorSubtract(end, start, dir);
+
+ {
+ // will use the original startPoint for the actual effect etc,
+ // the modified start is just for the traces
+ VectorCopy(start, origStart);
+ vec3_t dir2; // need normalized dir for offset
+ VectorCopy(dir, dir2);
+ VectorNormalize(dir2);
+ // start = start - 8*dir => move start back a bit
+ // so trace doesn't start in wall in case parasite is too close to wall
+ VectorMA(start, -8.0f, dir2, start);
+ }
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ return;
+ }
+ }
+ }
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
+
+ if (tr.ent != self->enemy)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_drain03)
+ {
+ damage = 5;
+ gi.sound(self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ if (self->s.frame == FRAME_drain04)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0);
+ }
+
+ damage = 2;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_PARASITE_ATTACK);
+ gi.WriteShort(self - g_edicts);
+ gi.WritePosition(origStart);
+ gi.WritePosition(end);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin,
+ damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+}
+
+mframe_t parasite_frames_drain[] = {
+ {ai_charge, 0, parasite_launch},
+ {ai_charge, 0, NULL},
+ {ai_charge, 15, parasite_drain_attack}, /* Target hits */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, -2, parasite_drain_attack}, /* drain */
+ {ai_charge, -2, parasite_drain_attack}, /* drain */
+ {ai_charge, -3, parasite_drain_attack}, /* drain */
+ {ai_charge, -2, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, -1, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_reel_in}, /* let go */
+ {ai_charge, -2, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t parasite_move_drain = {
+ FRAME_drain01,
+ FRAME_drain18,
+ parasite_frames_drain,
+ parasite_start_run
+};
+
+mframe_t parasite_frames_break[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -18, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 9, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -18, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 8, NULL},
+ {ai_charge, 9, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -18, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* airborne */
+ {ai_charge, 0, NULL}, /* airborne */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 4, NULL},
+ {ai_charge, 11, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -5, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t parasite_move_break = {
+ FRAME_break01,
+ FRAME_break32,
+ parasite_frames_break,
+ parasite_start_run
+};
+
+void
+parasite_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_drain;
+}
+
+void
+parasite_jump_down(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 100, forward, self->velocity);
+ VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void
+parasite_jump_up(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 200, forward, self->velocity);
+ VectorMA(self->velocity, 450, up, self->velocity);
+}
+
+void
+parasite_jump_wait_land(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity == NULL)
+ {
+ self->monsterinfo.nextframe = self->s.frame;
+
+ if (monster_jump_finished(self))
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+ }
+ else
+ {
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+}
+
+mframe_t parasite_frames_jump_up[] = {
+ {ai_move, -8, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, -8, parasite_jump_up},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, parasite_jump_wait_land},
+ {ai_move, 0, NULL}
+};
+
+mmove_t parasite_move_jump_up = {
+ FRAME_jump01,
+ FRAME_jump08,
+ parasite_frames_jump_up,
+ parasite_run
+};
+
+mframe_t parasite_frames_jump_down[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, parasite_jump_down},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, parasite_jump_wait_land},
+ {ai_move, 0, NULL}
+};
+
+mmove_t parasite_move_jump_down = {
+ FRAME_jump01,
+ FRAME_jump08,
+ parasite_frames_jump_down,
+ parasite_run
+};
+
+void
+parasite_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->enemy->absmin[2] > self->absmin[2])
+ {
+ self->monsterinfo.currentmove = &parasite_move_jump_up;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &parasite_move_jump_down;
+ }
+}
+
+qboolean
+parasite_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy && self->enemy->client && random() >= (0.25 + (0.05 * skill->value)))
+ {
+ vec3_t f, r, offset, start, end;
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorSet(offset, 24, 0, 6);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ return false;
+ }
+ }
+ }
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (visible(self, self->enemy))
+ {
+ parasite_attack(self);
+ return true;
+ }
+ }
+
+ if (blocked_checkjump(self, dist, 256, 68))
+ {
+ parasite_jump(self);
+ return true;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+parasite_checkattack(edict_t *self)
+{
+ vec3_t f, r, offset, start, end;
+ trace_t tr;
+ qboolean retval;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ retval = M_CheckAttack(self);
+
+ if (!retval)
+ {
+ return false;
+ }
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorSet(offset, 24, 0, 6);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ return false;
+ }
+ }
+ }
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
+
+ if (tr.ent != self->enemy)
+ {
+ self->monsterinfo.aiflags |= AI_BLOCKED;
+
+ if (self->monsterinfo.attack)
+ {
+ self->monsterinfo.attack(self);
+ }
+
+ self->monsterinfo.aiflags &= ~AI_BLOCKED;
+ return true;
+ }
+
+ return true;
+}
+
+void
+parasite_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t parasite_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t parasite_move_death = {
+ FRAME_death101,
+ FRAME_death107,
+ parasite_frames_death,
+ parasite_dead
+};
+
+void
+parasite_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unsued */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &parasite_move_death;
+}
+
+/*
+ * QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_parasite(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("parasite/parpain1.wav");
+ sound_pain2 = gi.soundindex("parasite/parpain2.wav");
+ sound_die = gi.soundindex("parasite/pardeth1.wav");
+ sound_launch = gi.soundindex("parasite/paratck1.wav");
+ sound_impact = gi.soundindex("parasite/paratck2.wav");
+ sound_suck = gi.soundindex("parasite/paratck3.wav");
+ sound_reelin = gi.soundindex("parasite/paratck4.wav");
+ sound_sight = gi.soundindex("parasite/parsght1.wav");
+ sound_tap = gi.soundindex("parasite/paridle1.wav");
+ sound_scratch = gi.soundindex("parasite/paridle2.wav");
+ sound_search = gi.soundindex("parasite/parsrch1.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/parasite/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 24);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->health = 175;
+ self->gib_health = -50;
+ self->mass = 250;
+ self->viewheight = 16;
+
+ self->pain = parasite_pain;
+ self->die = parasite_die;
+
+ self->monsterinfo.stand = parasite_stand;
+ self->monsterinfo.walk = parasite_start_walk;
+ self->monsterinfo.run = parasite_start_run;
+ self->monsterinfo.attack = parasite_attack;
+ self->monsterinfo.sight = parasite_sight;
+ self->monsterinfo.idle = parasite_idle;
+ self->monsterinfo.blocked = parasite_blocked;
+ self->monsterinfo.checkattack = parasite_checkattack;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &parasite_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/parasite/parasite.h b/rogue/src/monster/parasite/parasite.h
new file mode 100644
index 0000000..4310c87
--- /dev/null
+++ b/rogue/src/monster/parasite/parasite.h
@@ -0,0 +1,134 @@
+/* =======================================================================
+ *
+ * Parasite animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_break01 0
+#define FRAME_break02 1
+#define FRAME_break03 2
+#define FRAME_break04 3
+#define FRAME_break05 4
+#define FRAME_break06 5
+#define FRAME_break07 6
+#define FRAME_break08 7
+#define FRAME_break09 8
+#define FRAME_break10 9
+#define FRAME_break11 10
+#define FRAME_break12 11
+#define FRAME_break13 12
+#define FRAME_break14 13
+#define FRAME_break15 14
+#define FRAME_break16 15
+#define FRAME_break17 16
+#define FRAME_break18 17
+#define FRAME_break19 18
+#define FRAME_break20 19
+#define FRAME_break21 20
+#define FRAME_break22 21
+#define FRAME_break23 22
+#define FRAME_break24 23
+#define FRAME_break25 24
+#define FRAME_break26 25
+#define FRAME_break27 26
+#define FRAME_break28 27
+#define FRAME_break29 28
+#define FRAME_break30 29
+#define FRAME_break31 30
+#define FRAME_break32 31
+#define FRAME_death101 32
+#define FRAME_death102 33
+#define FRAME_death103 34
+#define FRAME_death104 35
+#define FRAME_death105 36
+#define FRAME_death106 37
+#define FRAME_death107 38
+#define FRAME_drain01 39
+#define FRAME_drain02 40
+#define FRAME_drain03 41
+#define FRAME_drain04 42
+#define FRAME_drain05 43
+#define FRAME_drain06 44
+#define FRAME_drain07 45
+#define FRAME_drain08 46
+#define FRAME_drain09 47
+#define FRAME_drain10 48
+#define FRAME_drain11 49
+#define FRAME_drain12 50
+#define FRAME_drain13 51
+#define FRAME_drain14 52
+#define FRAME_drain15 53
+#define FRAME_drain16 54
+#define FRAME_drain17 55
+#define FRAME_drain18 56
+#define FRAME_pain101 57
+#define FRAME_pain102 58
+#define FRAME_pain103 59
+#define FRAME_pain104 60
+#define FRAME_pain105 61
+#define FRAME_pain106 62
+#define FRAME_pain107 63
+#define FRAME_pain108 64
+#define FRAME_pain109 65
+#define FRAME_pain110 66
+#define FRAME_pain111 67
+#define FRAME_run01 68
+#define FRAME_run02 69
+#define FRAME_run03 70
+#define FRAME_run04 71
+#define FRAME_run05 72
+#define FRAME_run06 73
+#define FRAME_run07 74
+#define FRAME_run08 75
+#define FRAME_run09 76
+#define FRAME_run10 77
+#define FRAME_run11 78
+#define FRAME_run12 79
+#define FRAME_run13 80
+#define FRAME_run14 81
+#define FRAME_run15 82
+#define FRAME_stand01 83
+#define FRAME_stand02 84
+#define FRAME_stand03 85
+#define FRAME_stand04 86
+#define FRAME_stand05 87
+#define FRAME_stand06 88
+#define FRAME_stand07 89
+#define FRAME_stand08 90
+#define FRAME_stand09 91
+#define FRAME_stand10 92
+#define FRAME_stand11 93
+#define FRAME_stand12 94
+#define FRAME_stand13 95
+#define FRAME_stand14 96
+#define FRAME_stand15 97
+#define FRAME_stand16 98
+#define FRAME_stand17 99
+#define FRAME_stand18 100
+#define FRAME_stand19 101
+#define FRAME_stand20 102
+#define FRAME_stand21 103
+#define FRAME_stand22 104
+#define FRAME_stand23 105
+#define FRAME_stand24 106
+#define FRAME_stand25 107
+#define FRAME_stand26 108
+#define FRAME_stand27 109
+#define FRAME_stand28 110
+#define FRAME_stand29 111
+#define FRAME_stand30 112
+#define FRAME_stand31 113
+#define FRAME_stand32 114
+#define FRAME_stand33 115
+#define FRAME_stand34 116
+#define FRAME_stand35 117
+#define FRAME_jump01 118
+#define FRAME_jump02 119
+#define FRAME_jump03 120
+#define FRAME_jump04 121
+#define FRAME_jump05 122
+#define FRAME_jump06 123
+#define FRAME_jump07 124
+#define FRAME_jump08 125
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/soldier/soldier.c b/rogue/src/monster/soldier/soldier.c
new file mode 100644
index 0000000..6563869
--- /dev/null
+++ b/rogue/src/monster/soldier/soldier.c
@@ -0,0 +1,1887 @@
+/* =======================================================================
+ *
+ * Soldier aka "Guard". This is the most complex enemy in Quake 2, since
+ * it uses all AI features (dodging, sight, crouching, etc) and comes
+ * in a myriad of variants. In Rogue it's even more complex due to
+ * the blindfire stuff.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "soldier.h"
+
+static int sound_idle;
+static int sound_sight1;
+static int sound_sight2;
+static int sound_pain_light;
+static int sound_pain;
+static int sound_pain_ss;
+static int sound_death_light;
+static int sound_death;
+static int sound_death_ss;
+static int sound_cock;
+
+void soldier_duck_up(edict_t *self);
+void soldier_stand(edict_t *self);
+void soldier_run(edict_t *self);
+void soldier_fire(edict_t *self, int);
+void soldier_blind(edict_t *self);
+
+void
+soldier_start_charge(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_CHARGING;
+}
+
+void
+soldier_stop_charge(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_CHARGING;
+}
+
+void
+soldier_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.8)
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+ }
+}
+
+void
+soldier_cock(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_stand322)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t soldier_frames_stand1[] = {
+ {ai_stand, 0, soldier_idle},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t soldier_move_stand1 = {
+ FRAME_stand101,
+ FRAME_stand130,
+ soldier_frames_stand1,
+ soldier_stand
+};
+
+mframe_t soldier_frames_stand3[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, soldier_cock},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t soldier_move_stand3 = {
+ FRAME_stand301,
+ FRAME_stand339,
+ soldier_frames_stand3,
+ soldier_stand
+};
+
+void
+soldier_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &soldier_move_stand3) ||
+ (random() < 0.8))
+ {
+ self->monsterinfo.currentmove = &soldier_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_stand3;
+ }
+}
+
+void
+soldier_walk1_random(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.1)
+ {
+ self->monsterinfo.nextframe = FRAME_walk101;
+ }
+}
+
+mframe_t soldier_frames_walk1[] = {
+ {ai_walk, 3, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, -1, soldier_walk1_random},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t soldier_move_walk1 = {
+ FRAME_walk101,
+ FRAME_walk133,
+ soldier_frames_walk1,
+ NULL
+};
+
+mframe_t soldier_frames_walk2[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 7, NULL}
+};
+
+mmove_t soldier_move_walk2 = {
+ FRAME_walk209,
+ FRAME_walk218,
+ soldier_frames_walk2,
+ NULL
+};
+
+void
+soldier_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &soldier_move_walk1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_walk2;
+ }
+}
+
+mframe_t soldier_frames_start_run[] = {
+ {ai_run, 7, NULL},
+ {ai_run, 5, NULL}
+};
+
+mmove_t soldier_move_start_run = {
+ FRAME_run01,
+ FRAME_run02,
+ soldier_frames_start_run,
+ soldier_run
+};
+
+void
+soldier_fire_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->s.skinnum <= 1) && (self->enemy) && visible(self, self->enemy))
+ {
+ soldier_fire(self, 0);
+ }
+}
+
+mframe_t soldier_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 16, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 15, NULL}
+};
+
+mmove_t soldier_move_run = {
+ FRAME_run03,
+ FRAME_run08,
+ soldier_frames_run,
+ NULL
+};
+
+void
+soldier_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &soldier_move_stand1;
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &soldier_move_walk1) ||
+ (self->monsterinfo.currentmove == &soldier_move_walk2) ||
+ (self->monsterinfo.currentmove == &soldier_move_start_run))
+ {
+ self->monsterinfo.currentmove = &soldier_move_run;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_start_run;
+ }
+}
+
+mframe_t soldier_frames_pain1[] = {
+ {ai_move, -3, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain105,
+ soldier_frames_pain1,
+ soldier_run
+};
+
+mframe_t soldier_frames_pain2[] = {
+ {ai_move, -13, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t soldier_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain207,
+ soldier_frames_pain2,
+ soldier_run
+};
+
+mframe_t soldier_frames_pain3[] = {
+ {ai_move, -8, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t soldier_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain318,
+ soldier_frames_pain3,
+ soldier_run
+};
+
+mframe_t soldier_frames_pain4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, 8, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_pain4 = {
+ FRAME_pain401,
+ FRAME_pain417,
+ soldier_frames_pain4,
+ soldier_run
+};
+
+void
+soldier_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ float r;
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum |= 1;
+ }
+
+ monster_done_dodge(self);
+ soldier_stop_charge(self);
+
+ /* if we're blind firing, this needs to be turned off here */
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+ if (level.time < self->pain_debounce_time)
+ {
+ if ((self->velocity[2] > 100) &&
+ ((self->monsterinfo.currentmove == &soldier_move_pain1) ||
+ (self->monsterinfo.currentmove == &soldier_move_pain2) ||
+ (self->monsterinfo.currentmove == &soldier_move_pain3)))
+ {
+ /* clear duck flag */
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+
+ self->monsterinfo.currentmove = &soldier_move_pain4;
+ }
+
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ n = self->s.skinnum | 1;
+
+ if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0);
+ }
+ else if (n == 3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0);
+ }
+
+ if (self->velocity[2] > 100)
+ {
+ /* clear duck flag */
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+
+ self->monsterinfo.currentmove = &soldier_move_pain4;
+ return;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain1;
+ }
+ else if (r < 0.66)
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain3;
+ }
+
+ /* clear duck flag */
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ monster_duck_up(self);
+ }
+}
+
+static int blaster_flash[] =
+{MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3,
+ MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6,
+ MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8};
+
+static int shotgun_flash[] =
+{MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3,
+ MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6,
+ MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8};
+
+static int machinegun_flash[] =
+{MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3,
+ MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5,
+ MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7,
+ MZ2_SOLDIER_MACHINEGUN_8};
+
+void
+soldier_fire(edict_t *self, int in_flash_number)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t aim;
+ vec3_t dir;
+ vec3_t end;
+ float r, u;
+ int flash_index;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vec3_t aim_norm;
+ float angle;
+ trace_t tr;
+ vec3_t aim_good;
+
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ return;
+ }
+
+ if (in_flash_number < 0)
+ {
+ flash_number = -1 * in_flash_number;
+ }
+ else
+ {
+ flash_number = in_flash_number;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ flash_index = blaster_flash[flash_number];
+ }
+ else if (self->s.skinnum < 4)
+ {
+ flash_index = shotgun_flash[flash_number];
+ }
+ else
+ {
+ flash_index = machinegun_flash[flash_number];
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_index], forward,
+ right, start);
+
+ if ((flash_number == 5) || (flash_number == 6)) /* he's dead */
+ {
+ VectorCopy(forward, aim);
+ }
+ else
+ {
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, aim);
+ VectorCopy(end, aim_good);
+
+ if (in_flash_number < 0)
+ {
+ VectorCopy(aim, aim_norm);
+ VectorNormalize(aim_norm);
+ angle = DotProduct(aim_norm, forward);
+
+ if (angle < 0.9) /* ~25 degree angle */
+ {
+ return;
+ }
+ }
+
+ vectoangles(aim, dir);
+ AngleVectors(dir, forward, right, up);
+
+ if (skill->value < SKILL_HARD)
+ {
+ r = crandom() * 1000;
+ u = crandom() * 500;
+ }
+ else
+ {
+ r = crandom() * 500;
+ u = crandom() * 250;
+ }
+
+ VectorMA(start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+
+ VectorSubtract(end, start, aim);
+ VectorNormalize(aim);
+ }
+
+ if (!((flash_number == 5) || (flash_number == 6))) /* he's dead */
+ {
+ tr = gi.trace(start, NULL, NULL, aim_good, self, MASK_SHOT);
+
+ if ((tr.ent != self->enemy) && (tr.ent != world))
+ {
+ return;
+ }
+ }
+
+ if (self->s.skinnum <= 1)
+ {
+ monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER);
+ }
+ else if (self->s.skinnum <= 3)
+ {
+ monster_fire_shotgun(self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD,
+ DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index);
+ }
+ else
+ {
+ /* changed to wait from pausetime to not interfere with dodge code */
+ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+ {
+ self->wait = level.time + (3 + rand() % 8) * FRAMETIME;
+ }
+
+ monster_fire_bullet(self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_index);
+
+ if (level.time >= self->wait)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+ }
+}
+
+void
+soldier_fire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 0);
+}
+
+void
+soldier_attack1_refire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->s.skinnum > 1)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak102;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attak110;
+ }
+}
+
+void
+soldier_attack1_refire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak102;
+ }
+}
+
+mframe_t soldier_frames_attack1[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack1_refire1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_cock},
+ {ai_charge, 0, soldier_attack1_refire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak112,
+ soldier_frames_attack1,
+ soldier_run
+};
+
+void
+soldier_fire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 1);
+}
+
+void
+soldier_attack2_refire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->s.skinnum > 1)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak204;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attak216;
+ }
+}
+
+void
+soldier_attack2_refire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak204;
+ }
+}
+
+mframe_t soldier_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack2_refire1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_cock},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack2_refire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak218,
+ soldier_frames_attack2,
+ soldier_run
+};
+
+void
+soldier_fire3(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ monster_duck_down(self);
+ soldier_fire(self, 2);
+}
+
+void
+soldier_attack3_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((level.time + 0.4) < self->monsterinfo.duck_wait_time)
+ {
+ self->monsterinfo.nextframe = FRAME_attak303;
+ }
+}
+
+mframe_t soldier_frames_attack3[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire3},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack3_refire},
+ {ai_charge, 0, monster_duck_up},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack3 = {
+ FRAME_attak301,
+ FRAME_attak309,
+ soldier_frames_attack3,
+ soldier_run
+};
+
+void
+soldier_fire4(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 3);
+}
+
+mframe_t soldier_frames_attack4[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire4},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack4 = {
+ FRAME_attak401,
+ FRAME_attak406,
+ soldier_frames_attack4,
+ soldier_run
+};
+
+void
+soldier_fire8(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, -7);
+}
+
+void
+soldier_attack6_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* make sure dodge & charge bits are cleared */
+ monster_done_dodge(self);
+ soldier_stop_charge(self);
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (range(self, self->enemy) < RANGE_NEAR)
+ {
+ return;
+ }
+
+ if ((skill->value == SKILL_HARDPLUS) || ((random() < (0.25 * ((float)skill->value)))))
+ {
+ self->monsterinfo.nextframe = FRAME_runs03;
+ }
+}
+
+mframe_t soldier_frames_attack6[] = {
+ {ai_run, 10, soldier_start_charge},
+ {ai_run, 4, NULL},
+ {ai_run, 12, soldier_fire8},
+ {ai_run, 11, NULL},
+ {ai_run, 13, monster_done_dodge},
+ {ai_run, 18, NULL},
+ {ai_run, 15, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 17, soldier_attack6_refire}
+};
+
+mmove_t soldier_move_attack6 = {
+ FRAME_runs01,
+ FRAME_runs14,
+ soldier_frames_attack6,
+ soldier_run
+};
+
+void
+soldier_attack(edict_t *self)
+{
+ float r, chance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_done_dodge(self);
+
+ /* blindfire! */
+ if (self->monsterinfo.attack_state == AS_BLIND)
+ {
+ /* setup shot probabilities */
+ if (self->monsterinfo.blind_fire_delay < 1.0)
+ {
+ chance = 1.0;
+ }
+ else if (self->monsterinfo.blind_fire_delay < 7.5)
+ {
+ chance = 0.4;
+ }
+ else
+ {
+ chance = 0.1;
+ }
+
+ r = random();
+
+ /* minimum of 2 seconds, plus 0-3, after the shots are done */
+ self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random() * 3.0;
+
+ /* don't shoot at the origin */
+ if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin))
+ {
+ return;
+ }
+
+ /* don't shoot if the dice say not to */
+ if (r > chance)
+ {
+ return;
+ }
+
+ /* turn on manual steering to signal both manual steering and blindfire */
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &soldier_move_attack1;
+ self->monsterinfo.attack_finished = level.time + 1.5 + random();
+ return;
+ }
+
+ r = random();
+
+ if ((!(self->monsterinfo.aiflags & (AI_BLOCKED | AI_STAND_GROUND))) &&
+ (range(self, self->enemy) >= RANGE_NEAR) &&
+ ((r < (skill->value * 0.25)) &&
+ (self->s.skinnum <= 3)))
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack6;
+ }
+ else
+ {
+ if (self->s.skinnum < 4)
+ {
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack2;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack4;
+ }
+ }
+}
+
+void
+soldier_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
+ }
+
+ if ((skill->value > SKILL_EASY) && (self->enemy) &&
+ (range(self, self->enemy) >= RANGE_NEAR))
+ {
+ /* don't let machinegunners run & shoot */
+ if ((random() > 0.75) && (self->s.skinnum <= 3))
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack6;
+ }
+ }
+}
+
+mframe_t soldier_frames_duck[] = {
+ {ai_move, 5, monster_duck_down},
+ {ai_move, -1, monster_duck_hold},
+ {ai_move, 1, NULL},
+ {ai_move, 0, monster_duck_up},
+ {ai_move, 5, NULL}
+};
+
+mmove_t soldier_move_duck = {
+ FRAME_duck01,
+ FRAME_duck05,
+ soldier_frames_duck,
+ soldier_run
+};
+
+qboolean
+soldier_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ /* don't do anything if you're dodging */
+ if ((self->monsterinfo.aiflags & AI_DODGING) ||
+ (self->monsterinfo.aiflags & AI_DUCKED))
+ {
+ return false;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+soldier_fire6(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 5);
+}
+
+void
+soldier_fire7(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 6);
+}
+
+void
+soldier_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+soldier_dead2(edict_t *self)
+{
+ vec3_t tempmins, tempmaxs, temporg;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->s.origin, temporg);
+ /* this is because location traces done at the
+ floor are guaranteed to hit the floor (inside
+ the sv_trace code it grows the bbox by 1 in
+ all directions) */
+ temporg[2] += 1;
+
+ VectorSet(tempmins, -32, -32, -24);
+ VectorSet(tempmaxs, 32, 32, -8);
+
+ tr = gi.trace(temporg, tempmins, tempmaxs, temporg, self, MASK_SOLID);
+
+ if (tr.startsolid || tr.allsolid)
+ {
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ }
+ else
+ {
+ VectorCopy(tempmins, self->mins);
+ VectorCopy(tempmaxs, self->maxs);
+ }
+
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t soldier_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, soldier_fire6},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, soldier_fire7},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death1 = {
+ FRAME_death101,
+ FRAME_death136,
+ soldier_frames_death1,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death2[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death2 = {
+ FRAME_death201,
+ FRAME_death235,
+ soldier_frames_death2,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death3[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+};
+
+mmove_t soldier_move_death3 = {
+ FRAME_death301,
+ FRAME_death345,
+ soldier_frames_death3,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death4 = {
+ FRAME_death401,
+ FRAME_death453,
+ soldier_frames_death4,
+ soldier_dead2
+};
+
+mframe_t soldier_frames_death5[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death5 = {
+ FRAME_death501,
+ FRAME_death524,
+ soldier_frames_death5,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death6[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death6 = {
+ FRAME_death601,
+ FRAME_death610,
+ soldier_frames_death6,
+ soldier_dead
+};
+
+void
+soldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 3; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum |= 1;
+
+ if (self->s.skinnum == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0);
+ }
+ else if (self->s.skinnum == 3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0);
+ }
+
+ if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4)
+ {
+ /* head shot */
+ self->monsterinfo.currentmove = &soldier_move_death3;
+ return;
+ }
+
+ n = rand() % 5;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death1;
+ }
+ else if (n == 1)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death2;
+ }
+ else if (n == 2)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death4;
+ }
+ else if (n == 3)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death5;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_death6;
+ }
+}
+
+void
+soldier_sidestep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum <= 3)
+ {
+ if (self->monsterinfo.currentmove != &soldier_move_attack6)
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack6;
+ }
+ }
+ else
+ {
+ if (self->monsterinfo.currentmove != &soldier_move_start_run)
+ {
+ self->monsterinfo.currentmove = &soldier_move_start_run;
+ }
+ }
+}
+
+void
+soldier_duck(edict_t *self, float eta)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* has to be done immediately otherwise he can get stuck */
+ monster_duck_down(self);
+
+ if (skill->value == SKILL_EASY)
+ {
+ self->monsterinfo.currentmove = &soldier_move_duck;
+ self->monsterinfo.duck_wait_time = level.time + eta + 1;
+ return;
+ }
+
+ r = random();
+
+ if (r > (skill->value * 0.3))
+ {
+ self->monsterinfo.currentmove = &soldier_move_duck;
+ self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack3;
+ self->monsterinfo.duck_wait_time = level.time + eta + 1;
+ }
+}
+
+mframe_t soldier_frames_blind[] = {
+ {ai_move, 0, soldier_idle},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_blind = {
+ FRAME_stand101,
+ FRAME_stand130,
+ soldier_frames_blind,
+ soldier_blind
+};
+
+void
+soldier_blind(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &soldier_move_blind;
+}
+
+void
+SP_monster_soldier_x(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.modelindex = gi.modelindex("models/monsters/soldier/tris.md2");
+ self->monsterinfo.scale = MODEL_SCALE;
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ sound_idle = gi.soundindex("soldier/solidle1.wav");
+ sound_sight1 = gi.soundindex("soldier/solsght1.wav");
+ sound_sight2 = gi.soundindex("soldier/solsrch1.wav");
+ sound_cock = gi.soundindex("infantry/infatck3.wav");
+
+ self->mass = 100;
+
+ self->pain = soldier_pain;
+ self->die = soldier_die;
+
+ self->monsterinfo.stand = soldier_stand;
+ self->monsterinfo.walk = soldier_walk;
+ self->monsterinfo.run = soldier_run;
+ self->monsterinfo.dodge = M_MonsterDodge;
+ self->monsterinfo.attack = soldier_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = soldier_sight;
+
+ self->monsterinfo.blocked = soldier_blocked;
+ self->monsterinfo.duck = soldier_duck;
+ self->monsterinfo.unduck = monster_duck_up;
+ self->monsterinfo.sidestep = soldier_sidestep;
+
+ if (self->spawnflags & 8) /* blind */
+ {
+ self->monsterinfo.stand = soldier_blind;
+ }
+
+ gi.linkentity(self);
+
+ self->monsterinfo.stand(self);
+
+ walkmonster_start(self);
+}
+
+/*
+ * QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
+ *
+ * Blind - monster will just stand there until triggered
+ */
+void
+SP_monster_soldier_light(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 20;
+ self->gib_health = -30;
+
+ SP_monster_soldier_x(self);
+
+ sound_pain_light = gi.soundindex("soldier/solpain2.wav");
+ sound_death_light = gi.soundindex("soldier/soldeth2.wav");
+ gi.modelindex("models/objects/laser/tris.md2");
+ gi.soundindex("misc/lasfly.wav");
+ gi.soundindex("soldier/solatck2.wav");
+
+ self->s.skinnum = 0;
+ self->monsterinfo.blindfire = true;
+}
+
+/*
+ * QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
+ *
+ * Blind - monster will just stand there until triggered
+ */
+void
+SP_monster_soldier(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 30;
+ self->gib_health = -30;
+
+ SP_monster_soldier_x(self);
+
+ sound_pain = gi.soundindex("soldier/solpain1.wav");
+ sound_death = gi.soundindex("soldier/soldeth1.wav");
+ gi.soundindex("soldier/solatck1.wav");
+
+ self->s.skinnum = 2;
+}
+
+/*
+ * QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
+ *
+ * Blind - monster will just stand there until triggered
+ */
+void
+SP_monster_soldier_ss(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 40;
+ self->gib_health = -30;
+
+ SP_monster_soldier_x(self);
+
+ sound_pain_ss = gi.soundindex("soldier/solpain3.wav");
+ sound_death_ss = gi.soundindex("soldier/soldeth3.wav");
+ gi.soundindex("soldier/solatck3.wav");
+
+ self->s.skinnum = 4;
+}
diff --git a/rogue/src/monster/soldier/soldier.h b/rogue/src/monster/soldier/soldier.h
new file mode 100644
index 0000000..c047512
--- /dev/null
+++ b/rogue/src/monster/soldier/soldier.h
@@ -0,0 +1,483 @@
+/* =======================================================================
+ *
+ * Soldier aka "Guard" animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak201 12
+#define FRAME_attak202 13
+#define FRAME_attak203 14
+#define FRAME_attak204 15
+#define FRAME_attak205 16
+#define FRAME_attak206 17
+#define FRAME_attak207 18
+#define FRAME_attak208 19
+#define FRAME_attak209 20
+#define FRAME_attak210 21
+#define FRAME_attak211 22
+#define FRAME_attak212 23
+#define FRAME_attak213 24
+#define FRAME_attak214 25
+#define FRAME_attak215 26
+#define FRAME_attak216 27
+#define FRAME_attak217 28
+#define FRAME_attak218 29
+#define FRAME_attak301 30
+#define FRAME_attak302 31
+#define FRAME_attak303 32
+#define FRAME_attak304 33
+#define FRAME_attak305 34
+#define FRAME_attak306 35
+#define FRAME_attak307 36
+#define FRAME_attak308 37
+#define FRAME_attak309 38
+#define FRAME_attak401 39
+#define FRAME_attak402 40
+#define FRAME_attak403 41
+#define FRAME_attak404 42
+#define FRAME_attak405 43
+#define FRAME_attak406 44
+#define FRAME_duck01 45
+#define FRAME_duck02 46
+#define FRAME_duck03 47
+#define FRAME_duck04 48
+#define FRAME_duck05 49
+#define FRAME_pain101 50
+#define FRAME_pain102 51
+#define FRAME_pain103 52
+#define FRAME_pain104 53
+#define FRAME_pain105 54
+#define FRAME_pain201 55
+#define FRAME_pain202 56
+#define FRAME_pain203 57
+#define FRAME_pain204 58
+#define FRAME_pain205 59
+#define FRAME_pain206 60
+#define FRAME_pain207 61
+#define FRAME_pain301 62
+#define FRAME_pain302 63
+#define FRAME_pain303 64
+#define FRAME_pain304 65
+#define FRAME_pain305 66
+#define FRAME_pain306 67
+#define FRAME_pain307 68
+#define FRAME_pain308 69
+#define FRAME_pain309 70
+#define FRAME_pain310 71
+#define FRAME_pain311 72
+#define FRAME_pain312 73
+#define FRAME_pain313 74
+#define FRAME_pain314 75
+#define FRAME_pain315 76
+#define FRAME_pain316 77
+#define FRAME_pain317 78
+#define FRAME_pain318 79
+#define FRAME_pain401 80
+#define FRAME_pain402 81
+#define FRAME_pain403 82
+#define FRAME_pain404 83
+#define FRAME_pain405 84
+#define FRAME_pain406 85
+#define FRAME_pain407 86
+#define FRAME_pain408 87
+#define FRAME_pain409 88
+#define FRAME_pain410 89
+#define FRAME_pain411 90
+#define FRAME_pain412 91
+#define FRAME_pain413 92
+#define FRAME_pain414 93
+#define FRAME_pain415 94
+#define FRAME_pain416 95
+#define FRAME_pain417 96
+#define FRAME_run01 97
+#define FRAME_run02 98
+#define FRAME_run03 99
+#define FRAME_run04 100
+#define FRAME_run05 101
+#define FRAME_run06 102
+#define FRAME_run07 103
+#define FRAME_run08 104
+#define FRAME_run09 105
+#define FRAME_run10 106
+#define FRAME_run11 107
+#define FRAME_run12 108
+#define FRAME_runs01 109
+#define FRAME_runs02 110
+#define FRAME_runs03 111
+#define FRAME_runs04 112
+#define FRAME_runs05 113
+#define FRAME_runs06 114
+#define FRAME_runs07 115
+#define FRAME_runs08 116
+#define FRAME_runs09 117
+#define FRAME_runs10 118
+#define FRAME_runs11 119
+#define FRAME_runs12 120
+#define FRAME_runs13 121
+#define FRAME_runs14 122
+#define FRAME_runs15 123
+#define FRAME_runs16 124
+#define FRAME_runs17 125
+#define FRAME_runs18 126
+#define FRAME_runt01 127
+#define FRAME_runt02 128
+#define FRAME_runt03 129
+#define FRAME_runt04 130
+#define FRAME_runt05 131
+#define FRAME_runt06 132
+#define FRAME_runt07 133
+#define FRAME_runt08 134
+#define FRAME_runt09 135
+#define FRAME_runt10 136
+#define FRAME_runt11 137
+#define FRAME_runt12 138
+#define FRAME_runt13 139
+#define FRAME_runt14 140
+#define FRAME_runt15 141
+#define FRAME_runt16 142
+#define FRAME_runt17 143
+#define FRAME_runt18 144
+#define FRAME_runt19 145
+#define FRAME_stand101 146
+#define FRAME_stand102 147
+#define FRAME_stand103 148
+#define FRAME_stand104 149
+#define FRAME_stand105 150
+#define FRAME_stand106 151
+#define FRAME_stand107 152
+#define FRAME_stand108 153
+#define FRAME_stand109 154
+#define FRAME_stand110 155
+#define FRAME_stand111 156
+#define FRAME_stand112 157
+#define FRAME_stand113 158
+#define FRAME_stand114 159
+#define FRAME_stand115 160
+#define FRAME_stand116 161
+#define FRAME_stand117 162
+#define FRAME_stand118 163
+#define FRAME_stand119 164
+#define FRAME_stand120 165
+#define FRAME_stand121 166
+#define FRAME_stand122 167
+#define FRAME_stand123 168
+#define FRAME_stand124 169
+#define FRAME_stand125 170
+#define FRAME_stand126 171
+#define FRAME_stand127 172
+#define FRAME_stand128 173
+#define FRAME_stand129 174
+#define FRAME_stand130 175
+#define FRAME_stand301 176
+#define FRAME_stand302 177
+#define FRAME_stand303 178
+#define FRAME_stand304 179
+#define FRAME_stand305 180
+#define FRAME_stand306 181
+#define FRAME_stand307 182
+#define FRAME_stand308 183
+#define FRAME_stand309 184
+#define FRAME_stand310 185
+#define FRAME_stand311 186
+#define FRAME_stand312 187
+#define FRAME_stand313 188
+#define FRAME_stand314 189
+#define FRAME_stand315 190
+#define FRAME_stand316 191
+#define FRAME_stand317 192
+#define FRAME_stand318 193
+#define FRAME_stand319 194
+#define FRAME_stand320 195
+#define FRAME_stand321 196
+#define FRAME_stand322 197
+#define FRAME_stand323 198
+#define FRAME_stand324 199
+#define FRAME_stand325 200
+#define FRAME_stand326 201
+#define FRAME_stand327 202
+#define FRAME_stand328 203
+#define FRAME_stand329 204
+#define FRAME_stand330 205
+#define FRAME_stand331 206
+#define FRAME_stand332 207
+#define FRAME_stand333 208
+#define FRAME_stand334 209
+#define FRAME_stand335 210
+#define FRAME_stand336 211
+#define FRAME_stand337 212
+#define FRAME_stand338 213
+#define FRAME_stand339 214
+#define FRAME_walk101 215
+#define FRAME_walk102 216
+#define FRAME_walk103 217
+#define FRAME_walk104 218
+#define FRAME_walk105 219
+#define FRAME_walk106 220
+#define FRAME_walk107 221
+#define FRAME_walk108 222
+#define FRAME_walk109 223
+#define FRAME_walk110 224
+#define FRAME_walk111 225
+#define FRAME_walk112 226
+#define FRAME_walk113 227
+#define FRAME_walk114 228
+#define FRAME_walk115 229
+#define FRAME_walk116 230
+#define FRAME_walk117 231
+#define FRAME_walk118 232
+#define FRAME_walk119 233
+#define FRAME_walk120 234
+#define FRAME_walk121 235
+#define FRAME_walk122 236
+#define FRAME_walk123 237
+#define FRAME_walk124 238
+#define FRAME_walk125 239
+#define FRAME_walk126 240
+#define FRAME_walk127 241
+#define FRAME_walk128 242
+#define FRAME_walk129 243
+#define FRAME_walk130 244
+#define FRAME_walk131 245
+#define FRAME_walk132 246
+#define FRAME_walk133 247
+#define FRAME_walk201 248
+#define FRAME_walk202 249
+#define FRAME_walk203 250
+#define FRAME_walk204 251
+#define FRAME_walk205 252
+#define FRAME_walk206 253
+#define FRAME_walk207 254
+#define FRAME_walk208 255
+#define FRAME_walk209 256
+#define FRAME_walk210 257
+#define FRAME_walk211 258
+#define FRAME_walk212 259
+#define FRAME_walk213 260
+#define FRAME_walk214 261
+#define FRAME_walk215 262
+#define FRAME_walk216 263
+#define FRAME_walk217 264
+#define FRAME_walk218 265
+#define FRAME_walk219 266
+#define FRAME_walk220 267
+#define FRAME_walk221 268
+#define FRAME_walk222 269
+#define FRAME_walk223 270
+#define FRAME_walk224 271
+#define FRAME_death101 272
+#define FRAME_death102 273
+#define FRAME_death103 274
+#define FRAME_death104 275
+#define FRAME_death105 276
+#define FRAME_death106 277
+#define FRAME_death107 278
+#define FRAME_death108 279
+#define FRAME_death109 280
+#define FRAME_death110 281
+#define FRAME_death111 282
+#define FRAME_death112 283
+#define FRAME_death113 284
+#define FRAME_death114 285
+#define FRAME_death115 286
+#define FRAME_death116 287
+#define FRAME_death117 288
+#define FRAME_death118 289
+#define FRAME_death119 290
+#define FRAME_death120 291
+#define FRAME_death121 292
+#define FRAME_death122 293
+#define FRAME_death123 294
+#define FRAME_death124 295
+#define FRAME_death125 296
+#define FRAME_death126 297
+#define FRAME_death127 298
+#define FRAME_death128 299
+#define FRAME_death129 300
+#define FRAME_death130 301
+#define FRAME_death131 302
+#define FRAME_death132 303
+#define FRAME_death133 304
+#define FRAME_death134 305
+#define FRAME_death135 306
+#define FRAME_death136 307
+#define FRAME_death201 308
+#define FRAME_death202 309
+#define FRAME_death203 310
+#define FRAME_death204 311
+#define FRAME_death205 312
+#define FRAME_death206 313
+#define FRAME_death207 314
+#define FRAME_death208 315
+#define FRAME_death209 316
+#define FRAME_death210 317
+#define FRAME_death211 318
+#define FRAME_death212 319
+#define FRAME_death213 320
+#define FRAME_death214 321
+#define FRAME_death215 322
+#define FRAME_death216 323
+#define FRAME_death217 324
+#define FRAME_death218 325
+#define FRAME_death219 326
+#define FRAME_death220 327
+#define FRAME_death221 328
+#define FRAME_death222 329
+#define FRAME_death223 330
+#define FRAME_death224 331
+#define FRAME_death225 332
+#define FRAME_death226 333
+#define FRAME_death227 334
+#define FRAME_death228 335
+#define FRAME_death229 336
+#define FRAME_death230 337
+#define FRAME_death231 338
+#define FRAME_death232 339
+#define FRAME_death233 340
+#define FRAME_death234 341
+#define FRAME_death235 342
+#define FRAME_death301 343
+#define FRAME_death302 344
+#define FRAME_death303 345
+#define FRAME_death304 346
+#define FRAME_death305 347
+#define FRAME_death306 348
+#define FRAME_death307 349
+#define FRAME_death308 350
+#define FRAME_death309 351
+#define FRAME_death310 352
+#define FRAME_death311 353
+#define FRAME_death312 354
+#define FRAME_death313 355
+#define FRAME_death314 356
+#define FRAME_death315 357
+#define FRAME_death316 358
+#define FRAME_death317 359
+#define FRAME_death318 360
+#define FRAME_death319 361
+#define FRAME_death320 362
+#define FRAME_death321 363
+#define FRAME_death322 364
+#define FRAME_death323 365
+#define FRAME_death324 366
+#define FRAME_death325 367
+#define FRAME_death326 368
+#define FRAME_death327 369
+#define FRAME_death328 370
+#define FRAME_death329 371
+#define FRAME_death330 372
+#define FRAME_death331 373
+#define FRAME_death332 374
+#define FRAME_death333 375
+#define FRAME_death334 376
+#define FRAME_death335 377
+#define FRAME_death336 378
+#define FRAME_death337 379
+#define FRAME_death338 380
+#define FRAME_death339 381
+#define FRAME_death340 382
+#define FRAME_death341 383
+#define FRAME_death342 384
+#define FRAME_death343 385
+#define FRAME_death344 386
+#define FRAME_death345 387
+#define FRAME_death401 388
+#define FRAME_death402 389
+#define FRAME_death403 390
+#define FRAME_death404 391
+#define FRAME_death405 392
+#define FRAME_death406 393
+#define FRAME_death407 394
+#define FRAME_death408 395
+#define FRAME_death409 396
+#define FRAME_death410 397
+#define FRAME_death411 398
+#define FRAME_death412 399
+#define FRAME_death413 400
+#define FRAME_death414 401
+#define FRAME_death415 402
+#define FRAME_death416 403
+#define FRAME_death417 404
+#define FRAME_death418 405
+#define FRAME_death419 406
+#define FRAME_death420 407
+#define FRAME_death421 408
+#define FRAME_death422 409
+#define FRAME_death423 410
+#define FRAME_death424 411
+#define FRAME_death425 412
+#define FRAME_death426 413
+#define FRAME_death427 414
+#define FRAME_death428 415
+#define FRAME_death429 416
+#define FRAME_death430 417
+#define FRAME_death431 418
+#define FRAME_death432 419
+#define FRAME_death433 420
+#define FRAME_death434 421
+#define FRAME_death435 422
+#define FRAME_death436 423
+#define FRAME_death437 424
+#define FRAME_death438 425
+#define FRAME_death439 426
+#define FRAME_death440 427
+#define FRAME_death441 428
+#define FRAME_death442 429
+#define FRAME_death443 430
+#define FRAME_death444 431
+#define FRAME_death445 432
+#define FRAME_death446 433
+#define FRAME_death447 434
+#define FRAME_death448 435
+#define FRAME_death449 436
+#define FRAME_death450 437
+#define FRAME_death451 438
+#define FRAME_death452 439
+#define FRAME_death453 440
+#define FRAME_death501 441
+#define FRAME_death502 442
+#define FRAME_death503 443
+#define FRAME_death504 444
+#define FRAME_death505 445
+#define FRAME_death506 446
+#define FRAME_death507 447
+#define FRAME_death508 448
+#define FRAME_death509 449
+#define FRAME_death510 450
+#define FRAME_death511 451
+#define FRAME_death512 452
+#define FRAME_death513 453
+#define FRAME_death514 454
+#define FRAME_death515 455
+#define FRAME_death516 456
+#define FRAME_death517 457
+#define FRAME_death518 458
+#define FRAME_death519 459
+#define FRAME_death520 460
+#define FRAME_death521 461
+#define FRAME_death522 462
+#define FRAME_death523 463
+#define FRAME_death524 464
+#define FRAME_death601 465
+#define FRAME_death602 466
+#define FRAME_death603 467
+#define FRAME_death604 468
+#define FRAME_death605 469
+#define FRAME_death606 470
+#define FRAME_death607 471
+#define FRAME_death608 472
+#define FRAME_death609 473
+#define FRAME_death610 474
+#define MODEL_SCALE 1.200000
diff --git a/rogue/src/monster/stalker/stalker.c b/rogue/src/monster/stalker/stalker.c
new file mode 100644
index 0000000..dd5cbb1
--- /dev/null
+++ b/rogue/src/monster/stalker/stalker.c
@@ -0,0 +1,1496 @@
+/* =======================================================================
+ *
+ * Stalker.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "stalker.h"
+#include <float.h>
+
+static int sound_pain;
+static int sound_die;
+static int sound_sight;
+static int sound_punch_hit1;
+static int sound_punch_hit2;
+static int sound_idle;
+
+int stalker_do_pounce(edict_t *self, vec3_t dest);
+void stalker_stand(edict_t *self);
+void stalker_run(edict_t *self);
+void stalker_walk(edict_t *self);
+void stalker_jump(edict_t *self);
+void stalker_dodge_jump(edict_t *self);
+void stalker_swing_check_l(edict_t *self);
+void stalker_swing_check_r(edict_t *self);
+void stalker_swing_attack(edict_t *self);
+void stalker_jump_straightup(edict_t *self);
+void stalker_jump_wait_land(edict_t *self);
+void stalker_false_death(edict_t *self);
+void stalker_false_death_start(edict_t *self);
+qboolean stalker_ok_to_transition(edict_t *self);
+
+#define STALKER_ON_CEILING(ent) (ent->gravityVector[2] > 0 ? 1 : 0)
+
+#define PI 3.14159
+#define RAD2DEG(x) (x * (float)180.0 / (float)PI)
+#define DEG2RAD(x) (x * (float)PI / (float)180.0)
+#define FAUX_GRAVITY 800.0
+
+extern qboolean SV_PointCloseEnough(edict_t *ent, vec3_t goal, float dist);
+extern void drawbbox(edict_t *self);
+
+qboolean
+stalker_ok_to_transition(edict_t *self)
+{
+ trace_t trace;
+ vec3_t pt, start;
+ float max_dist;
+ float margin;
+ float end_height;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (STALKER_ON_CEILING(self))
+ {
+ max_dist = -384;
+ margin = self->mins[2] - 8;
+ }
+ else
+ {
+ /* her stalkers are just better */
+ if (self->monsterinfo.aiflags & AI_SPAWNED_WIDOW)
+ {
+ max_dist = 256;
+ }
+ else
+ {
+ max_dist = 180;
+ }
+
+ margin = self->maxs[2] + 8;
+ }
+
+ VectorCopy(self->s.origin, pt);
+ pt[2] += max_dist;
+ trace = gi.trace(self->s.origin, self->mins, self->maxs,
+ pt, self, MASK_MONSTERSOLID);
+
+ if ((trace.fraction == 1.0) ||
+ !(trace.contents & CONTENTS_SOLID) ||
+ (trace.ent != world))
+ {
+ if (STALKER_ON_CEILING(self))
+ {
+ if (trace.plane.normal[2] < 0.9)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (trace.plane.normal[2] > -0.9)
+ {
+ return false;
+ }
+ }
+ }
+
+ end_height = trace.endpos[2];
+
+ /* check the four corners, tracing only to the
+ endpoint of the center trace (vertically). */
+ pt[0] = self->absmin[0];
+ pt[1] = self->absmin[1];
+ pt[2] = trace.endpos[2] + margin; /* give a little margin of error to allow slight inclines */
+ VectorCopy(pt, start);
+ start[2] = self->s.origin[2];
+ trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+
+ if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) ||
+ (trace.ent != world))
+ {
+ return false;
+ }
+
+ if (fabsf(end_height + margin - trace.endpos[2]) > 8)
+ {
+ return false;
+ }
+
+ pt[0] = self->absmax[0];
+ pt[1] = self->absmin[1];
+ VectorCopy(pt, start);
+ start[2] = self->s.origin[2];
+ trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+
+ if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) ||
+ (trace.ent != world))
+ {
+ return false;
+ }
+
+ if (fabsf(end_height + margin - trace.endpos[2]) > 8)
+ {
+ return false;
+ }
+
+ pt[0] = self->absmax[0];
+ pt[1] = self->absmax[1];
+ VectorCopy(pt, start);
+ start[2] = self->s.origin[2];
+ trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+
+ if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world))
+ {
+ return false;
+ }
+
+ if (fabsf(end_height + margin - trace.endpos[2]) > 8)
+ {
+ return false;
+ }
+
+ pt[0] = self->absmin[0];
+ pt[1] = self->absmax[1];
+ VectorCopy(pt, start);
+ start[2] = self->s.origin[2];
+ trace = gi.trace(start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+
+ if ((trace.fraction == 1.0) || !(trace.contents & CONTENTS_SOLID) || (trace.ent != world))
+ {
+ return false;
+ }
+
+ if (fabsf(end_height + margin - trace.endpos[2]) > 8)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+stalker_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+stalker_idle_noise(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_idle, 0.5, ATTN_IDLE, 0);
+}
+
+mframe_t stalker_frames_idle[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, stalker_idle_noise},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL}
+};
+
+mmove_t stalker_move_idle = {
+ FRAME_idle01,
+ FRAME_idle21,
+ stalker_frames_idle,
+ stalker_stand
+};
+
+mframe_t stalker_frames_idle2[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t stalker_move_idle2 = {
+ FRAME_idle201,
+ FRAME_idle213,
+ stalker_frames_idle2,
+ stalker_stand
+};
+
+void
+stalker_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.35)
+ {
+ self->monsterinfo.currentmove = &stalker_move_idle;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &stalker_move_idle2;
+ }
+}
+
+mframe_t stalker_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, stalker_idle_noise},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL}
+};
+
+mmove_t stalker_move_stand = {
+ FRAME_idle01,
+ FRAME_idle21,
+ stalker_frames_stand,
+ stalker_stand
+};
+
+void
+stalker_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.25)
+ {
+ self->monsterinfo.currentmove = &stalker_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &stalker_move_idle2;
+ }
+}
+
+mframe_t stalker_frames_run[] = {
+ {ai_run, 13, NULL},
+ {ai_run, 17, NULL},
+ {ai_run, 21, NULL},
+ {ai_run, 18, NULL}
+};
+
+mmove_t stalker_move_run = {
+ FRAME_run01,
+ FRAME_run04,
+ stalker_frames_run,
+ NULL
+};
+
+void
+stalker_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &stalker_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &stalker_move_run;
+ }
+}
+
+mframe_t stalker_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 5, NULL},
+
+ {ai_walk, 4, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t stalker_move_walk = {
+ FRAME_walk01,
+ FRAME_walk08,
+ stalker_frames_walk,
+ stalker_walk
+};
+
+void
+stalker_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &stalker_move_walk;
+}
+
+mframe_t stalker_frames_reactivate[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t stalker_move_false_death_end = {
+ FRAME_reactive01,
+ FRAME_reactive04,
+ stalker_frames_reactivate,
+ stalker_run
+};
+
+void
+stalker_reactivate(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_STAND_GROUND;
+ self->monsterinfo.currentmove = &stalker_move_false_death_end;
+}
+
+void
+stalker_heal(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (skill->value == SKILL_HARD)
+ {
+ self->health += 2;
+ }
+ else if (skill->value == SKILL_HARDPLUS)
+ {
+ self->health += 3;
+ }
+ else
+ {
+ self->health++;
+ }
+
+ if (self->health > (self->max_health / 2))
+ {
+ self->s.skinnum = 0;
+ }
+
+ if (self->health >= self->max_health)
+ {
+ self->health = self->max_health;
+ stalker_reactivate(self);
+ }
+}
+
+mframe_t stalker_frames_false_death[] = {
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal},
+
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal},
+ {ai_move, 0, stalker_heal}
+};
+
+mmove_t stalker_move_false_death = {
+ FRAME_twitch01,
+ FRAME_twitch10,
+ stalker_frames_false_death,
+ stalker_false_death
+};
+
+void
+stalker_false_death(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &stalker_move_false_death;
+}
+
+mframe_t stalker_frames_false_death_start[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+};
+
+mmove_t stalker_move_false_death_start = {
+ FRAME_death01,
+ FRAME_death09,
+ stalker_frames_false_death_start,
+ stalker_false_death
+};
+
+void
+stalker_false_death_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.angles[2] = 0;
+ VectorSet(self->gravityVector, 0, 0, -1);
+
+ self->monsterinfo.aiflags |= AI_STAND_GROUND;
+ self->monsterinfo.currentmove = &stalker_move_false_death_start;
+}
+
+mframe_t stalker_frames_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t stalker_move_pain = {
+ FRAME_pain01,
+ FRAME_pain04,
+ stalker_frames_pain,
+ stalker_run
+};
+
+void
+stalker_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (self->groundentity == NULL)
+ {
+ return;
+ }
+
+ /* if we're reactivating or false dying, ignore the pain. */
+ if ((self->monsterinfo.currentmove == &stalker_move_false_death_end) ||
+ (self->monsterinfo.currentmove == &stalker_move_false_death_start))
+ {
+ return;
+ }
+
+ if (self->monsterinfo.currentmove == &stalker_move_false_death)
+ {
+ stalker_reactivate(self);
+ return;
+ }
+
+ if ((self->health > 0) && (self->health < (self->max_health / 4)))
+ {
+ if (random() < (0.2 * skill->value))
+ {
+ if (!STALKER_ON_CEILING(self) || stalker_ok_to_transition(self))
+ {
+ stalker_false_death_start(self);
+ return;
+ }
+ }
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (damage > 10) /* don't react unless the damage was significant */
+ {
+ /* stalker should dodge jump periodically to help avoid damage. */
+ if (self->groundentity && (random() < 0.5))
+ {
+ stalker_dodge_jump(self);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &stalker_move_pain;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_pain, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+stalker_shoot_attack(edict_t *self)
+{
+ vec3_t offset, start, f, r, dir;
+ vec3_t end;
+ float time, dist;
+ trace_t trace;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!has_valid_enemy(self))
+ {
+ return;
+ }
+
+ if (self->groundentity && (random() < 0.33))
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ dist = VectorLength(dir);
+
+ if ((dist > 256) || (random() < 0.5))
+ {
+ stalker_do_pounce(self, self->enemy->s.origin);
+ }
+ else
+ {
+ stalker_jump_straightup(self);
+ }
+ }
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorSet(offset, 24, 0, 6);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ VectorSubtract(self->enemy->s.origin, start, dir);
+
+ if (random() < (0.20 + 0.1 * skill->value))
+ {
+ dist = VectorLength(dir);
+ time = dist / 1000;
+ VectorMA(self->enemy->s.origin, time, self->enemy->velocity, end);
+ VectorSubtract(end, start, dir);
+ }
+ else
+ {
+ VectorCopy(self->enemy->s.origin, end);
+ }
+
+ trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
+
+ if ((trace.ent == self->enemy) || (trace.ent == world))
+ {
+ monster_fire_blaster2(self, start, dir, 15, 800, MZ2_STALKER_BLASTER, EF_BLASTER);
+ }
+}
+
+void
+stalker_shoot_attack2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < (0.4 + (0.1 * (float)skill->value)))
+ {
+ stalker_shoot_attack(self);
+ }
+}
+
+mframe_t stalker_frames_shoot[] = {
+ {ai_charge, 13, NULL},
+ {ai_charge, 17, stalker_shoot_attack},
+ {ai_charge, 21, NULL},
+ {ai_charge, 18, stalker_shoot_attack2}
+};
+
+mmove_t stalker_move_shoot = {
+ FRAME_run01,
+ FRAME_run04,
+ stalker_frames_shoot,
+ stalker_run
+};
+
+void
+stalker_attack_ranged(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!has_valid_enemy(self))
+ {
+ return;
+ }
+
+ /* circle strafe stuff */
+ if (random() > (1.0 - (0.5 / (float)(skill->value))))
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ else
+ {
+ if (random() <= 0.5) /* switch directions */
+ {
+ self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+ }
+
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+
+ self->monsterinfo.currentmove = &stalker_move_shoot;
+}
+
+void
+stalker_swing_attack(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 0);
+
+ if (fire_hit(self, aim, (5 + (rand() % 5)), 50))
+ {
+ if (self->s.frame < FRAME_attack08)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_punch_hit2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_punch_hit1, 1, ATTN_NORM, 0);
+ }
+ }
+}
+
+mframe_t stalker_frames_swing_l[] = {
+ {ai_charge, 2, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 5, stalker_swing_attack},
+ {ai_charge, 5, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 5, NULL} /* stalker_swing_check_l */
+};
+
+mmove_t stalker_move_swing_l = {
+ FRAME_attack01,
+ FRAME_attack08,
+ stalker_frames_swing_l,
+ stalker_run
+};
+
+mframe_t stalker_frames_swing_r[] = {
+ {ai_charge, 4, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 6, stalker_swing_attack},
+ {ai_charge, 10, NULL},
+ {ai_charge, 5, NULL} /* stalker_swing_check_r */
+};
+
+mmove_t stalker_move_swing_r = {
+ FRAME_attack11,
+ FRAME_attack15,
+ stalker_frames_swing_r,
+ stalker_run
+};
+
+void
+stalker_attack_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!has_valid_enemy(self))
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &stalker_move_swing_l;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &stalker_move_swing_r;
+ }
+}
+
+void
+calcJumpAngle(vec3_t start, vec3_t end, float velocity, vec3_t angles)
+{
+ float distV, distH;
+ float one, cosU;
+ float l, U;
+ vec3_t dist;
+
+ VectorSubtract(end, start, dist);
+ distH = (float)sqrt(dist[0] * dist[0] + dist[1] * dist[1]);
+ distV = dist[2];
+
+ if (distV < 0)
+ {
+ distV = 0 - distV;
+ }
+
+ if (distV)
+ {
+ l = (float)sqrt(distH * distH + distV * distV);
+ U = (float)atan(distV / distH);
+
+ if (dist[2] > 0)
+ {
+ U = (float)0.0 - U;
+ }
+
+ angles[2] = 0.0;
+
+ cosU = (float)cos(U);
+ one = l * FAUX_GRAVITY * (cosU * cosU);
+ one = one / (velocity * velocity);
+ one = one - (float)sin(U);
+ angles[0] = (float)asin(one);
+
+ if (_isnan(angles[0]))
+ {
+ angles[2] = 1.0;
+ }
+
+ angles[1] = (float)PI - angles[0];
+
+ if (_isnan(angles[1]))
+ {
+ angles[2] = 1.0;
+ }
+
+ angles[0] = RAD2DEG((angles[0] - U) / 2.0);
+ angles[1] = RAD2DEG((angles[1] - U) / 2.0);
+ }
+ else
+ {
+ l = (float)sqrt(distH * distH + distV * distV);
+
+ angles[2] = 0.0;
+
+ one = l * FAUX_GRAVITY;
+ one = one / (velocity * velocity);
+ angles[0] = (float)asin(one);
+
+ if (_isnan(angles[0]))
+ {
+ angles[2] = 1.0;
+ }
+
+ angles[1] = (float)PI - angles[0];
+
+ if (_isnan(angles[1]))
+ {
+ angles[2] = 1.0;
+ }
+
+ angles[0] = RAD2DEG((angles[0]) / 2.0);
+ angles[1] = RAD2DEG((angles[1]) / 2.0);
+ }
+}
+
+int
+stalker_check_lz(edict_t *self, edict_t *target, vec3_t dest)
+{
+ vec3_t jumpLZ;
+
+ if (!self || !target)
+ {
+ return 0;
+ }
+
+ if ((gi.pointcontents(dest) & MASK_WATER) || (target->waterlevel))
+ {
+ return false;
+ }
+
+ if (!target->groundentity)
+ {
+ return false;
+ }
+
+ /* check under the player's four corners
+ if they're not solid, bail. */
+ jumpLZ[0] = self->enemy->mins[0];
+ jumpLZ[1] = self->enemy->mins[1];
+ jumpLZ[2] = self->enemy->mins[2] - 0.25;
+
+ if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
+ {
+ return false;
+ }
+
+ jumpLZ[0] = self->enemy->maxs[0];
+ jumpLZ[1] = self->enemy->mins[1];
+
+ if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
+ {
+ return false;
+ }
+
+ jumpLZ[0] = self->enemy->maxs[0];
+ jumpLZ[1] = self->enemy->maxs[1];
+
+ if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
+ {
+ return false;
+ }
+
+ jumpLZ[0] = self->enemy->mins[0];
+ jumpLZ[1] = self->enemy->maxs[1];
+
+ if (!(gi.pointcontents(jumpLZ) & MASK_SOLID))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+int
+stalker_do_pounce(edict_t *self, vec3_t dest)
+{
+ vec3_t forward, right;
+ vec3_t dist;
+ vec_t length;
+ vec3_t jumpAngles;
+ vec3_t jumpLZ;
+ float velocity = 400.1;
+ trace_t trace;
+ int preferHighJump;
+
+ if (!self)
+ {
+ return 0;
+ }
+
+ /* don't pounce when we're on the ceiling */
+ if (STALKER_ON_CEILING(self))
+ {
+ return false;
+ }
+
+ if (!stalker_check_lz(self, self->enemy, dest))
+ {
+ return false;
+ }
+
+ VectorSubtract(dest, self->s.origin, dist);
+
+ /* make sure we're pointing in that direction 15deg margin of error. */
+ vectoangles2(dist, jumpAngles);
+
+ if (fabsf(jumpAngles[YAW] - self->s.angles[YAW]) > 45)
+ {
+ return false; /* not facing the player... */
+ }
+
+ self->ideal_yaw = jumpAngles[YAW];
+ M_ChangeYaw(self);
+
+ length = VectorLength(dist);
+
+ if (length > 450)
+ {
+ return false; /* can't jump that far... */
+ }
+
+ VectorCopy(dest, jumpLZ);
+
+ preferHighJump = 0;
+
+ /* if we're having to jump up a distance, jump a little too high to compensate. */
+ if (dist[2] >= 32.0)
+ {
+ preferHighJump = 1;
+ jumpLZ[2] += 32;
+ }
+
+ trace = gi.trace(self->s.origin, vec3_origin, vec3_origin, dest,
+ self, MASK_MONSTERSOLID);
+
+ if ((trace.fraction < 1) && (trace.ent != self->enemy))
+ {
+ preferHighJump = 1;
+ }
+
+ /* find a valid angle/velocity combination */
+ while (velocity <= 800)
+ {
+ calcJumpAngle(self->s.origin, jumpLZ, velocity, jumpAngles);
+
+ if ((!_isnan(jumpAngles[0])) || (!_isnan(jumpAngles[1])))
+ {
+ break;
+ }
+
+ velocity += 200;
+ }
+
+ if (!preferHighJump && (!_isnan(jumpAngles[0])))
+ {
+ AngleVectors(self->s.angles, forward, right, NULL);
+ VectorNormalize(forward);
+
+ VectorScale(forward, velocity * cos(DEG2RAD(jumpAngles[0])), self->velocity);
+ self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[0])) + (0.5 * sv_gravity->value * FRAMETIME);
+ return 1;
+ }
+
+ if (!_isnan(jumpAngles[1]))
+ {
+ AngleVectors(self->s.angles, forward, right, NULL);
+ VectorNormalize(forward);
+
+ VectorScale(forward, velocity * cos(DEG2RAD(jumpAngles[1])), self->velocity);
+ self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[1])) + (0.5 * sv_gravity->value * FRAMETIME);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+stalker_jump_straightup(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ if (STALKER_ON_CEILING(self))
+ {
+ if (stalker_ok_to_transition(self))
+ {
+ self->gravityVector[2] = -1;
+ self->s.angles[2] += 180.0;
+
+ if (self->s.angles[2] > 360.0)
+ {
+ self->s.angles[2] -= 360.0;
+ }
+
+ self->groundentity = NULL;
+ }
+ }
+ else if (self->groundentity) /* make sure we're standing on SOMETHING... */
+ {
+ self->velocity[0] += ((random() * 10) - 5);
+ self->velocity[1] += ((random() * 10) - 5);
+ self->velocity[2] += -400 * self->gravityVector[2];
+
+ if (stalker_ok_to_transition(self))
+ {
+ self->gravityVector[2] = 1;
+ self->s.angles[2] = 180.0;
+ self->groundentity = NULL;
+ }
+ }
+}
+
+mframe_t stalker_frames_jump_straightup[] = {
+ {ai_move, 1, stalker_jump_straightup},
+ {ai_move, 1, stalker_jump_wait_land},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL}
+};
+
+mmove_t stalker_move_jump_straightup = {
+ FRAME_jump04,
+ FRAME_jump07,
+ stalker_frames_jump_straightup,
+ stalker_run
+};
+
+void
+stalker_dodge_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &stalker_move_jump_straightup;
+}
+
+mframe_t stalker_frames_dodge_run[] = {
+ {ai_run, 13, NULL},
+ {ai_run, 17, NULL},
+ {ai_run, 21, NULL},
+ {ai_run, 18, monster_done_dodge}
+};
+
+mmove_t stalker_move_dodge_run = {
+ FRAME_run01,
+ FRAME_run04,
+ stalker_frames_dodge_run,
+ NULL
+};
+
+void
+stalker_dodge(edict_t *self, edict_t *attacker, float eta, trace_t *tr /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if (!self->groundentity || (self->health <= 0))
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ return;
+ }
+
+ if ((eta < 0.1) || (eta > 5))
+ {
+ return;
+ }
+
+ /* this will override the foundtarget call of stalker_run */
+ stalker_dodge_jump(self);
+}
+
+void
+stalker_jump_down(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 100, forward, self->velocity);
+ VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void
+stalker_jump_up(edict_t *self)
+{
+ vec3_t forward, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ monster_jump_start(self);
+
+ AngleVectors(self->s.angles, forward, NULL, up);
+ VectorMA(self->velocity, 200, forward, self->velocity);
+ VectorMA(self->velocity, 450, up, self->velocity);
+}
+
+void
+stalker_jump_wait_land(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((random() < (0.3 + (0.1 * (float)(skill->value)))) &&
+ (level.time >= self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.attack_finished = level.time + 0.3;
+ stalker_shoot_attack(self);
+ }
+
+ if (self->groundentity == NULL)
+ {
+ self->gravity = 1.3;
+ self->monsterinfo.nextframe = self->s.frame;
+
+ if (monster_jump_finished(self))
+ {
+ self->gravity = 1;
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+ }
+ else
+ {
+ self->gravity = 1;
+ self->monsterinfo.nextframe = self->s.frame + 1;
+ }
+}
+
+mframe_t stalker_frames_jump_up[] = {
+ {ai_move, -8, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, -8, NULL},
+
+ {ai_move, 0, stalker_jump_up},
+ {ai_move, 0, stalker_jump_wait_land},
+ {ai_move, 0, NULL}
+};
+
+mmove_t stalker_move_jump_up = {
+ FRAME_jump01,
+ FRAME_jump07,
+ stalker_frames_jump_up,
+ stalker_run
+};
+
+mframe_t stalker_frames_jump_down[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, stalker_jump_down},
+ {ai_move, 0, stalker_jump_wait_land},
+ {ai_move, 0, NULL}
+};
+
+mmove_t stalker_move_jump_down = {
+ FRAME_jump01,
+ FRAME_jump07,
+ stalker_frames_jump_down,
+ stalker_run
+};
+
+void
+stalker_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->enemy->absmin[2] >= self->absmin[2])
+ {
+ self->monsterinfo.currentmove = &stalker_move_jump_up;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &stalker_move_jump_down;
+ }
+}
+
+qboolean
+stalker_blocked(edict_t *self, float dist)
+{
+ qboolean onCeiling;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!has_valid_enemy(self))
+ {
+ return false;
+ }
+
+ onCeiling = false;
+
+ if (self->gravityVector[2] > 0)
+ {
+ onCeiling = true;
+ }
+
+ if (!onCeiling)
+ {
+ if (visible(self, self->enemy))
+ {
+ stalker_do_pounce(self, self->enemy->s.origin);
+ return true;
+ }
+
+ if (blocked_checkjump(self, dist, 256, 68))
+ {
+ stalker_jump(self);
+ return true;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (stalker_ok_to_transition(self))
+ {
+ self->gravityVector[2] = -1;
+ self->s.angles[2] += 180.0;
+
+ if (self->s.angles[2] > 360.0)
+ {
+ self->s.angles[2] -= 360.0;
+ }
+
+ self->groundentity = NULL;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+stalker_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -28, -28, -18);
+ VectorSet(self->maxs, 28, 28, -4);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t stalker_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -20, NULL},
+
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+
+ {ai_move, 0, NULL}
+};
+
+mmove_t stalker_move_death = {
+ FRAME_death01,
+ FRAME_death09,
+ stalker_frames_death,
+ stalker_dead
+};
+
+void
+stalker_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* dude bit it, make him fall! */
+ self->movetype = MOVETYPE_TOSS;
+ self->s.angles[2] = 0;
+ VectorSet(self->gravityVector, 0, 0, -1);
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &stalker_move_death;
+}
+
+/*
+ * QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof
+ * Spider Monster
+ *
+ * ONROOF - Monster starts sticking to the roof.
+ */
+void
+SP_monster_stalker(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain = gi.soundindex("stalker/pain.wav");
+ sound_die = gi.soundindex("stalker/death.wav");
+ sound_sight = gi.soundindex("stalker/sight.wav");
+ sound_punch_hit1 = gi.soundindex("stalker/melee1.wav");
+ sound_punch_hit2 = gi.soundindex("stalker/melee2.wav");
+ sound_idle = gi.soundindex("stalker/idle.wav");
+
+ gi.modelindex("models/proj/laser2/tris.md2");
+
+ self->s.modelindex = gi.modelindex("models/monsters/stalker/tris.md2");
+ VectorSet(self->mins, -28, -28, -18);
+ VectorSet(self->maxs, 28, 28, 18);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->health = 250;
+ self->gib_health = -50;
+ self->mass = 250;
+ self->viewheight = 15;
+
+ self->pain = stalker_pain;
+ self->die = stalker_die;
+
+ self->monsterinfo.stand = stalker_stand;
+ self->monsterinfo.walk = stalker_walk;
+ self->monsterinfo.run = stalker_run;
+ self->monsterinfo.attack = stalker_attack_ranged;
+ self->monsterinfo.sight = stalker_sight;
+ self->monsterinfo.idle = stalker_idle;
+ self->monsterinfo.dodge = stalker_dodge;
+ self->monsterinfo.blocked = stalker_blocked;
+ self->monsterinfo.melee = stalker_attack_melee;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &stalker_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ self->monsterinfo.aiflags |= AI_WALK_WALLS;
+
+ if (self->spawnflags & 8)
+ {
+ self->s.angles[2] = 180;
+ self->gravityVector[2] = 1;
+ }
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/stalker/stalker.h b/rogue/src/monster/stalker/stalker.h
new file mode 100644
index 0000000..6096c1c
--- /dev/null
+++ b/rogue/src/monster/stalker/stalker.h
@@ -0,0 +1,101 @@
+/* =======================================================================
+ *
+ * Stalker animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_idle01 0
+#define FRAME_idle02 1
+#define FRAME_idle03 2
+#define FRAME_idle04 3
+#define FRAME_idle05 4
+#define FRAME_idle06 5
+#define FRAME_idle07 6
+#define FRAME_idle08 7
+#define FRAME_idle09 8
+#define FRAME_idle10 9
+#define FRAME_idle11 10
+#define FRAME_idle12 11
+#define FRAME_idle13 12
+#define FRAME_idle14 13
+#define FRAME_idle15 14
+#define FRAME_idle16 15
+#define FRAME_idle17 16
+#define FRAME_idle18 17
+#define FRAME_idle19 18
+#define FRAME_idle20 19
+#define FRAME_idle21 20
+#define FRAME_idle201 21
+#define FRAME_idle202 22
+#define FRAME_idle203 23
+#define FRAME_idle204 24
+#define FRAME_idle205 25
+#define FRAME_idle206 26
+#define FRAME_idle207 27
+#define FRAME_idle208 28
+#define FRAME_idle209 29
+#define FRAME_idle210 30
+#define FRAME_idle211 31
+#define FRAME_idle212 32
+#define FRAME_idle213 33
+#define FRAME_walk01 34
+#define FRAME_walk02 35
+#define FRAME_walk03 36
+#define FRAME_walk04 37
+#define FRAME_walk05 38
+#define FRAME_walk06 39
+#define FRAME_walk07 40
+#define FRAME_walk08 41
+#define FRAME_jump01 42
+#define FRAME_jump02 43
+#define FRAME_jump03 44
+#define FRAME_jump04 45
+#define FRAME_jump05 46
+#define FRAME_jump06 47
+#define FRAME_jump07 48
+#define FRAME_run01 49
+#define FRAME_run02 50
+#define FRAME_run03 51
+#define FRAME_run04 52
+#define FRAME_attack01 53
+#define FRAME_attack02 54
+#define FRAME_attack03 55
+#define FRAME_attack04 56
+#define FRAME_attack05 57
+#define FRAME_attack06 58
+#define FRAME_attack07 59
+#define FRAME_attack08 60
+#define FRAME_attack11 61
+#define FRAME_attack12 62
+#define FRAME_attack13 63
+#define FRAME_attack14 64
+#define FRAME_attack15 65
+#define FRAME_pain01 66
+#define FRAME_pain02 67
+#define FRAME_pain03 68
+#define FRAME_pain04 69
+#define FRAME_death01 70
+#define FRAME_death02 71
+#define FRAME_death03 72
+#define FRAME_death04 73
+#define FRAME_death05 74
+#define FRAME_death06 75
+#define FRAME_death07 76
+#define FRAME_death08 77
+#define FRAME_death09 78
+#define FRAME_twitch01 79
+#define FRAME_twitch02 80
+#define FRAME_twitch03 81
+#define FRAME_twitch04 82
+#define FRAME_twitch05 83
+#define FRAME_twitch06 84
+#define FRAME_twitch07 85
+#define FRAME_twitch08 86
+#define FRAME_twitch09 87
+#define FRAME_twitch10 88
+#define FRAME_reactive01 89
+#define FRAME_reactive02 90
+#define FRAME_reactive03 91
+#define FRAME_reactive04 92
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/supertank/supertank.c b/rogue/src/monster/supertank/supertank.c
new file mode 100644
index 0000000..dd933f6
--- /dev/null
+++ b/rogue/src/monster/supertank/supertank.c
@@ -0,0 +1,901 @@
+/* =======================================================================
+ *
+ * Supertank aka "Boss1".
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "supertank.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_death;
+static int sound_search1;
+static int sound_search2;
+
+static int tread_sound;
+
+void BossExplode(edict_t *self);
+void supertank_dead(edict_t *self);
+void supertankRocket(edict_t *self);
+void supertankMachineGun(edict_t *self);
+void supertank_reattack1(edict_t *self);
+
+void
+TreadSound(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0);
+}
+
+void
+supertank_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t supertank_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t supertank_move_stand = {
+ FRAME_stand_1,
+ FRAME_stand_60,
+ supertank_frames_stand,
+ NULL
+};
+
+void
+supertank_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &supertank_move_stand;
+}
+
+mframe_t supertank_frames_run[] = {
+ {ai_run, 12, TreadSound},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL}
+};
+
+mmove_t supertank_move_run = {
+ FRAME_forwrd_1,
+ FRAME_forwrd_18,
+ supertank_frames_run,
+ NULL
+};
+
+mframe_t supertank_frames_forward[] = {
+ {ai_walk, 4, TreadSound},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t supertank_move_forward = {
+ FRAME_forwrd_1,
+ FRAME_forwrd_18,
+ supertank_frames_forward,
+ NULL
+};
+
+void
+supertank_forward(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void
+supertank_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void
+supertank_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &supertank_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_run;
+ }
+}
+
+mframe_t supertank_frames_turn_right[] = {
+ {ai_move, 0, TreadSound},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_turn_right = {
+ FRAME_right_1,
+ FRAME_right_18,
+ supertank_frames_turn_right,
+ supertank_run
+};
+
+mframe_t supertank_frames_turn_left[] = {
+ {ai_move, 0, TreadSound},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_turn_left = {
+ FRAME_left_1,
+ FRAME_left_18,
+ supertank_frames_turn_left,
+ supertank_run
+};
+
+mframe_t supertank_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_pain3 = {
+ FRAME_pain3_9,
+ FRAME_pain3_12,
+ supertank_frames_pain3,
+ supertank_run
+};
+
+mframe_t supertank_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_pain2 = {
+ FRAME_pain2_5,
+ FRAME_pain2_8,
+ supertank_frames_pain2,
+ supertank_run
+};
+
+mframe_t supertank_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_pain1 = {
+ FRAME_pain1_1,
+ FRAME_pain1_4,
+ supertank_frames_pain1,
+ supertank_run
+};
+
+mframe_t supertank_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, BossExplode}
+};
+
+mmove_t supertank_move_death = {
+ FRAME_death_1,
+ FRAME_death_24,
+ supertank_frames_death1,
+ supertank_dead
+};
+
+mframe_t supertank_frames_backward[] = {
+ {ai_walk, 0, TreadSound},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t supertank_move_backward = {
+ FRAME_backwd_1,
+ FRAME_backwd_18,
+ supertank_frames_backward,
+ NULL
+};
+
+mframe_t supertank_frames_attack4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_attack4 = {
+ FRAME_attak4_1,
+ FRAME_attak4_6,
+ supertank_frames_attack4,
+ supertank_run
+};
+
+mframe_t supertank_frames_attack3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_attack3 = {
+ FRAME_attak3_1,
+ FRAME_attak3_27,
+ supertank_frames_attack3,
+ supertank_run
+};
+
+mframe_t supertank_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, supertankRocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, supertankRocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, supertankRocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_attack2 = {
+ FRAME_attak2_1,
+ FRAME_attak2_27,
+ supertank_frames_attack2,
+ supertank_run
+};
+
+mframe_t supertank_frames_attack1[] = {
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+};
+
+mmove_t supertank_move_attack1 = {
+ FRAME_attak1_1,
+ FRAME_attak1_6,
+ supertank_frames_attack1,
+ supertank_reattack1
+};
+
+mframe_t supertank_frames_end_attack1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_end_attack1 = {
+ FRAME_attak1_7,
+ FRAME_attak1_20,
+ supertank_frames_end_attack1,
+ supertank_run
+};
+
+void
+supertank_reattack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ if (random() < 0.9)
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_end_attack1;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_end_attack1;
+ }
+}
+
+void
+supertank_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ /* Lessen the chance of him going into his pain frames */
+ if (damage <= 25)
+ {
+ if (random() < 0.2)
+ {
+ return;
+ }
+ }
+
+ /* Don't go into pain if he's firing his rockets */
+ if (skill->value >= SKILL_HARD)
+ {
+ if ((self->s.frame >= FRAME_attak2_1) &&
+ (self->s.frame <= FRAME_attak2_14))
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 10)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &supertank_move_pain1;
+ }
+ else if (damage <= 25)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &supertank_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &supertank_move_pain3;
+ }
+}
+
+void
+supertankRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+ int flash_number;
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak2_8)
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_1;
+ }
+ else if (self->s.frame == FRAME_attak2_11)
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_2;
+ }
+ else
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_3;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_rocket(self, start, dir, 50, 500, flash_number);
+}
+
+void
+supertankMachineGun(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t vec;
+ vec3_t start;
+ vec3_t forward, right;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ flash_number = MZ2_SUPERTANK_MACHINEGUN_1 +
+ (self->s.frame - FRAME_attak1_1);
+
+ dir[0] = 0;
+ dir[1] = self->s.angles[1];
+ dir[2] = 0;
+
+ AngleVectors(dir, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ VectorMA(vec, 0, self->enemy->velocity, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void
+supertank_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ if (range <= 160)
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack1;
+ }
+ else
+ {
+ /* fire rockets more often at distance */
+ if (random() < 0.3)
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack2;
+ }
+ }
+}
+
+void
+supertank_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -60, -60, 0);
+ VectorSet(self->maxs, 60, 60, 72);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+BossExplode(edict_t *self)
+{
+ vec3_t org;
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = BossExplode;
+ VectorCopy(self->s.origin, org);
+ org[2] += 24 + (rand() & 15);
+
+ switch (self->count++)
+ {
+ case 0:
+ org[0] -= 24;
+ org[1] -= 24;
+ break;
+ case 1:
+ org[0] += 24;
+ org[1] += 24;
+ break;
+ case 2:
+ org[0] += 24;
+ org[1] -= 24;
+ break;
+ case 3:
+ org[0] -= 24;
+ org[1] += 24;
+ break;
+ case 4:
+ org[0] -= 48;
+ org[1] -= 48;
+ break;
+ case 5:
+ org[0] += 48;
+ org[1] += 48;
+ break;
+ case 6:
+ org[0] -= 48;
+ org[1] += 48;
+ break;
+ case 7:
+ org[0] += 48;
+ org[1] -= 48;
+ break;
+ case 8:
+ self->s.sound = 0;
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 8; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(org);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ self->nextthink = level.time + 0.1;
+}
+
+void
+supertank_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ self->monsterinfo.currentmove = &supertank_move_death;
+}
+
+qboolean
+supertank_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_supertank(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("bosstank/btkpain1.wav");
+ sound_pain2 = gi.soundindex("bosstank/btkpain2.wav");
+ sound_pain3 = gi.soundindex("bosstank/btkpain3.wav");
+ sound_death = gi.soundindex("bosstank/btkdeth1.wav");
+ sound_search1 = gi.soundindex("bosstank/btkunqv1.wav");
+ sound_search2 = gi.soundindex("bosstank/btkunqv2.wav");
+
+ tread_sound = gi.soundindex("bosstank/btkengn1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss1/tris.md2");
+ VectorSet(self->mins, -64, -64, 0);
+ VectorSet(self->maxs, 64, 64, 112);
+
+ self->health = 1500;
+ self->gib_health = -500;
+ self->mass = 800;
+
+ self->pain = supertank_pain;
+ self->die = supertank_die;
+ self->monsterinfo.stand = supertank_stand;
+ self->monsterinfo.walk = supertank_walk;
+ self->monsterinfo.run = supertank_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = supertank_attack;
+ self->monsterinfo.search = supertank_search;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = NULL;
+ self->monsterinfo.blocked = supertank_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &supertank_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+
+ self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+}
diff --git a/rogue/src/monster/supertank/supertank.h b/rogue/src/monster/supertank/supertank.h
new file mode 100644
index 0000000..7c93286
--- /dev/null
+++ b/rogue/src/monster/supertank/supertank.h
@@ -0,0 +1,262 @@
+/* =======================================================================
+ *
+ * Supertank aka "Boss1" animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak1_1 0
+#define FRAME_attak1_2 1
+#define FRAME_attak1_3 2
+#define FRAME_attak1_4 3
+#define FRAME_attak1_5 4
+#define FRAME_attak1_6 5
+#define FRAME_attak1_7 6
+#define FRAME_attak1_8 7
+#define FRAME_attak1_9 8
+#define FRAME_attak1_10 9
+#define FRAME_attak1_11 10
+#define FRAME_attak1_12 11
+#define FRAME_attak1_13 12
+#define FRAME_attak1_14 13
+#define FRAME_attak1_15 14
+#define FRAME_attak1_16 15
+#define FRAME_attak1_17 16
+#define FRAME_attak1_18 17
+#define FRAME_attak1_19 18
+#define FRAME_attak1_20 19
+#define FRAME_attak2_1 20
+#define FRAME_attak2_2 21
+#define FRAME_attak2_3 22
+#define FRAME_attak2_4 23
+#define FRAME_attak2_5 24
+#define FRAME_attak2_6 25
+#define FRAME_attak2_7 26
+#define FRAME_attak2_8 27
+#define FRAME_attak2_9 28
+#define FRAME_attak2_10 29
+#define FRAME_attak2_11 30
+#define FRAME_attak2_12 31
+#define FRAME_attak2_13 32
+#define FRAME_attak2_14 33
+#define FRAME_attak2_15 34
+#define FRAME_attak2_16 35
+#define FRAME_attak2_17 36
+#define FRAME_attak2_18 37
+#define FRAME_attak2_19 38
+#define FRAME_attak2_20 39
+#define FRAME_attak2_21 40
+#define FRAME_attak2_22 41
+#define FRAME_attak2_23 42
+#define FRAME_attak2_24 43
+#define FRAME_attak2_25 44
+#define FRAME_attak2_26 45
+#define FRAME_attak2_27 46
+#define FRAME_attak3_1 47
+#define FRAME_attak3_2 48
+#define FRAME_attak3_3 49
+#define FRAME_attak3_4 50
+#define FRAME_attak3_5 51
+#define FRAME_attak3_6 52
+#define FRAME_attak3_7 53
+#define FRAME_attak3_8 54
+#define FRAME_attak3_9 55
+#define FRAME_attak3_10 56
+#define FRAME_attak3_11 57
+#define FRAME_attak3_12 58
+#define FRAME_attak3_13 59
+#define FRAME_attak3_14 60
+#define FRAME_attak3_15 61
+#define FRAME_attak3_16 62
+#define FRAME_attak3_17 63
+#define FRAME_attak3_18 64
+#define FRAME_attak3_19 65
+#define FRAME_attak3_20 66
+#define FRAME_attak3_21 67
+#define FRAME_attak3_22 68
+#define FRAME_attak3_23 69
+#define FRAME_attak3_24 70
+#define FRAME_attak3_25 71
+#define FRAME_attak3_26 72
+#define FRAME_attak3_27 73
+#define FRAME_attak4_1 74
+#define FRAME_attak4_2 75
+#define FRAME_attak4_3 76
+#define FRAME_attak4_4 77
+#define FRAME_attak4_5 78
+#define FRAME_attak4_6 79
+#define FRAME_backwd_1 80
+#define FRAME_backwd_2 81
+#define FRAME_backwd_3 82
+#define FRAME_backwd_4 83
+#define FRAME_backwd_5 84
+#define FRAME_backwd_6 85
+#define FRAME_backwd_7 86
+#define FRAME_backwd_8 87
+#define FRAME_backwd_9 88
+#define FRAME_backwd_10 89
+#define FRAME_backwd_11 90
+#define FRAME_backwd_12 91
+#define FRAME_backwd_13 92
+#define FRAME_backwd_14 93
+#define FRAME_backwd_15 94
+#define FRAME_backwd_16 95
+#define FRAME_backwd_17 96
+#define FRAME_backwd_18 97
+#define FRAME_death_1 98
+#define FRAME_death_2 99
+#define FRAME_death_3 100
+#define FRAME_death_4 101
+#define FRAME_death_5 102
+#define FRAME_death_6 103
+#define FRAME_death_7 104
+#define FRAME_death_8 105
+#define FRAME_death_9 106
+#define FRAME_death_10 107
+#define FRAME_death_11 108
+#define FRAME_death_12 109
+#define FRAME_death_13 110
+#define FRAME_death_14 111
+#define FRAME_death_15 112
+#define FRAME_death_16 113
+#define FRAME_death_17 114
+#define FRAME_death_18 115
+#define FRAME_death_19 116
+#define FRAME_death_20 117
+#define FRAME_death_21 118
+#define FRAME_death_22 119
+#define FRAME_death_23 120
+#define FRAME_death_24 121
+#define FRAME_death_31 122
+#define FRAME_death_32 123
+#define FRAME_death_33 124
+#define FRAME_death_45 125
+#define FRAME_death_46 126
+#define FRAME_death_47 127
+#define FRAME_forwrd_1 128
+#define FRAME_forwrd_2 129
+#define FRAME_forwrd_3 130
+#define FRAME_forwrd_4 131
+#define FRAME_forwrd_5 132
+#define FRAME_forwrd_6 133
+#define FRAME_forwrd_7 134
+#define FRAME_forwrd_8 135
+#define FRAME_forwrd_9 136
+#define FRAME_forwrd_10 137
+#define FRAME_forwrd_11 138
+#define FRAME_forwrd_12 139
+#define FRAME_forwrd_13 140
+#define FRAME_forwrd_14 141
+#define FRAME_forwrd_15 142
+#define FRAME_forwrd_16 143
+#define FRAME_forwrd_17 144
+#define FRAME_forwrd_18 145
+#define FRAME_left_1 146
+#define FRAME_left_2 147
+#define FRAME_left_3 148
+#define FRAME_left_4 149
+#define FRAME_left_5 150
+#define FRAME_left_6 151
+#define FRAME_left_7 152
+#define FRAME_left_8 153
+#define FRAME_left_9 154
+#define FRAME_left_10 155
+#define FRAME_left_11 156
+#define FRAME_left_12 157
+#define FRAME_left_13 158
+#define FRAME_left_14 159
+#define FRAME_left_15 160
+#define FRAME_left_16 161
+#define FRAME_left_17 162
+#define FRAME_left_18 163
+#define FRAME_pain1_1 164
+#define FRAME_pain1_2 165
+#define FRAME_pain1_3 166
+#define FRAME_pain1_4 167
+#define FRAME_pain2_5 168
+#define FRAME_pain2_6 169
+#define FRAME_pain2_7 170
+#define FRAME_pain2_8 171
+#define FRAME_pain3_9 172
+#define FRAME_pain3_10 173
+#define FRAME_pain3_11 174
+#define FRAME_pain3_12 175
+#define FRAME_right_1 176
+#define FRAME_right_2 177
+#define FRAME_right_3 178
+#define FRAME_right_4 179
+#define FRAME_right_5 180
+#define FRAME_right_6 181
+#define FRAME_right_7 182
+#define FRAME_right_8 183
+#define FRAME_right_9 184
+#define FRAME_right_10 185
+#define FRAME_right_11 186
+#define FRAME_right_12 187
+#define FRAME_right_13 188
+#define FRAME_right_14 189
+#define FRAME_right_15 190
+#define FRAME_right_16 191
+#define FRAME_right_17 192
+#define FRAME_right_18 193
+#define FRAME_stand_1 194
+#define FRAME_stand_2 195
+#define FRAME_stand_3 196
+#define FRAME_stand_4 197
+#define FRAME_stand_5 198
+#define FRAME_stand_6 199
+#define FRAME_stand_7 200
+#define FRAME_stand_8 201
+#define FRAME_stand_9 202
+#define FRAME_stand_10 203
+#define FRAME_stand_11 204
+#define FRAME_stand_12 205
+#define FRAME_stand_13 206
+#define FRAME_stand_14 207
+#define FRAME_stand_15 208
+#define FRAME_stand_16 209
+#define FRAME_stand_17 210
+#define FRAME_stand_18 211
+#define FRAME_stand_19 212
+#define FRAME_stand_20 213
+#define FRAME_stand_21 214
+#define FRAME_stand_22 215
+#define FRAME_stand_23 216
+#define FRAME_stand_24 217
+#define FRAME_stand_25 218
+#define FRAME_stand_26 219
+#define FRAME_stand_27 220
+#define FRAME_stand_28 221
+#define FRAME_stand_29 222
+#define FRAME_stand_30 223
+#define FRAME_stand_31 224
+#define FRAME_stand_32 225
+#define FRAME_stand_33 226
+#define FRAME_stand_34 227
+#define FRAME_stand_35 228
+#define FRAME_stand_36 229
+#define FRAME_stand_37 230
+#define FRAME_stand_38 231
+#define FRAME_stand_39 232
+#define FRAME_stand_40 233
+#define FRAME_stand_41 234
+#define FRAME_stand_42 235
+#define FRAME_stand_43 236
+#define FRAME_stand_44 237
+#define FRAME_stand_45 238
+#define FRAME_stand_46 239
+#define FRAME_stand_47 240
+#define FRAME_stand_48 241
+#define FRAME_stand_49 242
+#define FRAME_stand_50 243
+#define FRAME_stand_51 244
+#define FRAME_stand_52 245
+#define FRAME_stand_53 246
+#define FRAME_stand_54 247
+#define FRAME_stand_55 248
+#define FRAME_stand_56 249
+#define FRAME_stand_57 250
+#define FRAME_stand_58 251
+#define FRAME_stand_59 252
+#define FRAME_stand_60 253
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/tank/tank.c b/rogue/src/monster/tank/tank.c
new file mode 100644
index 0000000..deeb18b
--- /dev/null
+++ b/rogue/src/monster/tank/tank.c
@@ -0,0 +1,1242 @@
+/* =======================================================================
+ *
+ * Tank and Tank Commander.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "tank.h"
+
+void tank_refire_rocket(edict_t *self);
+void tank_doattack_rocket(edict_t *self);
+void tank_reattack_blaster(edict_t *self);
+void tank_walk(edict_t *self);
+void tank_run(edict_t *self);
+
+static int sound_thud;
+static int sound_pain;
+static int sound_idle;
+static int sound_die;
+static int sound_step;
+static int sound_sight;
+static int sound_windup;
+static int sound_strike;
+
+void
+tank_sight(edict_t *self, edict_t *other)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+tank_footstep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0);
+}
+
+void
+tank_thud(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0);
+}
+
+void
+tank_windup(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
+}
+
+void
+tank_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t tank_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t tank_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ tank_frames_stand,
+ NULL
+};
+
+void
+tank_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &tank_move_stand;
+}
+
+mframe_t tank_frames_start_walk[] = {
+ {ai_walk, 0, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 11, tank_footstep}
+};
+
+mmove_t tank_move_start_walk = {
+ FRAME_walk01,
+ FRAME_walk04,
+ tank_frames_start_walk,
+ tank_walk
+};
+
+mframe_t tank_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, tank_footstep},
+ {ai_walk, 3, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 6, tank_footstep}
+};
+
+mmove_t tank_move_walk = {
+ FRAME_walk05,
+ FRAME_walk20,
+ tank_frames_walk,
+ NULL
+};
+
+mframe_t tank_frames_stop_walk[] = {
+ {ai_walk, 3, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 4, tank_footstep}
+};
+
+mmove_t tank_move_stop_walk = {
+ FRAME_walk21,
+ FRAME_walk25,
+ tank_frames_stop_walk,
+ tank_stand
+};
+
+void
+tank_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &tank_move_walk;
+}
+
+mframe_t tank_frames_start_run[] = {
+ {ai_run, 0, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 11, tank_footstep}
+};
+
+mmove_t tank_move_start_run = {
+ FRAME_walk01,
+ FRAME_walk04,
+ tank_frames_start_run,
+ tank_run
+};
+
+mframe_t tank_frames_run[] = {
+ {ai_run, 4, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 4, NULL},
+ {ai_run, 4, tank_footstep},
+ {ai_run, 3, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 4, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 7, NULL},
+ {ai_run, 7, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 6, tank_footstep}
+};
+
+mmove_t tank_move_run = {
+ FRAME_walk05,
+ FRAME_walk20,
+ tank_frames_run,
+ NULL
+};
+
+mframe_t tank_frames_stop_run[] = {
+ {ai_run, 3, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 4, tank_footstep}
+};
+
+mmove_t tank_move_stop_run = {
+ FRAME_walk21,
+ FRAME_walk25,
+ tank_frames_stop_run,
+ tank_walk
+};
+
+void
+tank_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy && self->enemy->client)
+ {
+ self->monsterinfo.aiflags |= AI_BRUTAL;
+ }
+ else
+ {
+ self->monsterinfo.aiflags &= ~AI_BRUTAL;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &tank_move_stand;
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &tank_move_walk) ||
+ (self->monsterinfo.currentmove == &tank_move_start_run))
+ {
+ self->monsterinfo.currentmove = &tank_move_run;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_start_run;
+ }
+}
+
+mframe_t tank_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t tank_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain104,
+ tank_frames_pain1,
+ tank_run
+};
+
+mframe_t tank_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t tank_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain205,
+ tank_frames_pain2,
+ tank_run
+};
+
+mframe_t tank_frames_pain3[] = {
+ {ai_move, -7, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, tank_footstep}
+};
+
+mmove_t tank_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain316,
+ tank_frames_pain3,
+ tank_run
+};
+
+void
+tank_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum |= 1;
+ }
+
+ if (damage <= 10)
+ {
+ return;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ if (damage <= 30)
+ {
+ if (random() > 0.2)
+ {
+ return;
+ }
+ }
+
+ /* If hard or nightmare, don't go into pain while attacking */
+ if (skill->value >= SKILL_HARD)
+ {
+ if ((self->s.frame >= FRAME_attak301) &&
+ (self->s.frame <= FRAME_attak330))
+ {
+ return;
+ }
+
+ if ((self->s.frame >= FRAME_attak101) &&
+ (self->s.frame <= FRAME_attak116))
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+ if (damage <= 30)
+ {
+ self->monsterinfo.currentmove = &tank_move_pain1;
+ }
+ else if (damage <= 60)
+ {
+ self->monsterinfo.currentmove = &tank_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_pain3;
+ }
+}
+
+void
+TankBlaster(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t end;
+ vec3_t dir;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak110)
+ {
+ flash_number = MZ2_TANK_BLASTER_1;
+ }
+ else if (self->s.frame == FRAME_attak113)
+ {
+ flash_number = MZ2_TANK_BLASTER_2;
+ }
+ else
+ {
+ flash_number = MZ2_TANK_BLASTER_3;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], forward,
+ right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 30, 800, flash_number, EF_BLASTER);
+}
+
+void
+TankStrike(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0);
+}
+
+void
+TankRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+ int flash_number;
+ trace_t trace;
+ int rocketSpeed;
+ vec3_t target;
+ qboolean blindfire = false;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ blindfire = true;
+ }
+ else
+ {
+ blindfire = false;
+ }
+
+ if (self->s.frame == FRAME_attak324)
+ {
+ flash_number = MZ2_TANK_ROCKET_1;
+ }
+ else if (self->s.frame == FRAME_attak327)
+ {
+ flash_number = MZ2_TANK_ROCKET_2;
+ }
+ else
+ {
+ flash_number = MZ2_TANK_ROCKET_3;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+ rocketSpeed = 500 + (100 * skill->value);
+
+ if (blindfire)
+ {
+ VectorCopy (self->monsterinfo.blind_fire_target, target);
+ }
+ else
+ {
+ VectorCopy (self->enemy->s.origin, target);
+ }
+
+ if (blindfire)
+ {
+ VectorCopy(target, vec);
+ VectorSubtract(vec, start, dir);
+ }
+ else if(random() < 0.66 || (start[2] < self->enemy->absmin[2]))
+ {
+ // Don't shoot at the feed if enemy is above.
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ }
+ else
+ {
+ // Shoot at the feed.
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] = self->enemy->absmin[2];
+ VectorSubtract(vec, start, dir);
+ }
+
+ // Lead target: 20, 35, 50, 65 chance of leading.
+ if ((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15)))))
+ {
+ float dist;
+ float time;
+
+ dist = VectorLength(dir);
+ time = dist/rocketSpeed;
+ VectorMA(vec, time, self->enemy->velocity, vec);
+ VectorSubtract(vec, start, dir);
+ }
+
+ VectorNormalize(dir);
+
+ if (blindfire)
+ {
+ /* blindfire has different fail criteria for the trace */
+ if (!blind_rocket_ok(self, start, right, target, 20.0f, dir))
+ {
+ return;
+ }
+ }
+ else
+ {
+ trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+
+ if (((trace.ent != self->enemy) && (trace.ent != world)) ||
+ ((trace.fraction <= 0.5f) && !trace.ent->client))
+ {
+ return;
+ }
+ }
+
+ monster_fire_rocket (self, start, dir, 50, rocketSpeed, flash_number);
+}
+
+void
+TankMachineGun(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t vec;
+ vec3_t start;
+ vec3_t forward, right;
+ int flash_number;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, vec);
+ vectoangles(vec, vec);
+ dir[0] = vec[0];
+ }
+ else
+ {
+ dir[0] = 0;
+ }
+
+ if (self->s.frame <= FRAME_attak415)
+ {
+ dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411);
+ }
+ else
+ {
+ dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419);
+ }
+
+ dir[2] = 0;
+
+ AngleVectors(dir, forward, NULL, NULL);
+
+ monster_fire_bullet(self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+mframe_t tank_frames_attack_blast[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster}, /* 10 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster} /* 16 */
+};
+mmove_t tank_move_attack_blast = {
+ FRAME_attak101,
+ FRAME_attak116,
+ tank_frames_attack_blast,
+ tank_reattack_blaster
+};
+
+mframe_t tank_frames_reattack_blast[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster} /* 16 */
+};
+
+mmove_t tank_move_reattack_blast = {
+ FRAME_attak111,
+ FRAME_attak116,
+ tank_frames_reattack_blast,
+ tank_reattack_blaster
+};
+
+mframe_t tank_frames_attack_post_blast[] = {
+ {ai_move, 0, NULL}, /* 17 */
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -2, tank_footstep} /* 22 */
+};
+
+mmove_t tank_move_attack_post_blast = {
+ FRAME_attak117,
+ FRAME_attak122,
+ tank_frames_attack_post_blast,
+ tank_run
+};
+
+void
+tank_reattack_blaster(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (skill->value >= SKILL_HARD)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (self->enemy->health > 0)
+ {
+ if (random() <= 0.6)
+ {
+ self->monsterinfo.currentmove = &tank_move_reattack_blast;
+ return;
+ }
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &tank_move_attack_post_blast;
+}
+
+void
+tank_poststrike(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->enemy = NULL;
+ tank_run(self);
+}
+
+mframe_t tank_frames_attack_strike[] = {
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 9, tank_footstep},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, tank_footstep},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, tank_windup},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, TankStrike},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -2, tank_footstep}
+};
+
+mmove_t tank_move_attack_strike = {
+ FRAME_attak201,
+ FRAME_attak238,
+ tank_frames_attack_strike,
+ tank_poststrike
+};
+
+mframe_t tank_frames_attack_pre_rocket[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 10 */
+
+ {ai_charge, 0, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 7, NULL},
+ {ai_charge, 7, NULL},
+ {ai_charge, 7, tank_footstep},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 20 */
+
+ {ai_charge, -3, NULL}
+};
+
+mmove_t tank_move_attack_pre_rocket = {
+ FRAME_attak301,
+ FRAME_attak321,
+ tank_frames_attack_pre_rocket,
+ tank_doattack_rocket
+};
+
+mframe_t tank_frames_attack_fire_rocket[] = {
+ {ai_charge, -3, NULL}, /* Loop Start 22 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankRocket}, /* 24 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankRocket},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -1, TankRocket} /* 30 Loop End */
+};
+
+mmove_t tank_move_attack_fire_rocket = {
+ FRAME_attak322,
+ FRAME_attak330,
+ tank_frames_attack_fire_rocket,
+ tank_refire_rocket
+};
+
+mframe_t tank_frames_attack_post_rocket[] = {
+ {ai_charge, 0, NULL}, /* 31 */
+ {ai_charge, -1, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 40 */
+
+ {ai_charge, 0, NULL},
+ {ai_charge, -9, NULL},
+ {ai_charge, -8, NULL},
+ {ai_charge, -7, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -1, tank_footstep},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 50 */
+
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t tank_move_attack_post_rocket = {
+ FRAME_attak331,
+ FRAME_attak353,
+ tank_frames_attack_post_rocket,
+ tank_run
+};
+
+mframe_t tank_frames_attack_chain[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t tank_move_attack_chain = {
+ FRAME_attak401,
+ FRAME_attak429,
+ tank_frames_attack_chain,
+ tank_run
+};
+
+void
+tank_refire_rocket(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+ {
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &tank_move_attack_post_rocket;
+ return;
+ }
+
+ /* Only on hard or nightmare */
+ if (skill->value >= SKILL_HARD)
+ {
+ if (self->enemy->health > 0)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.4)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+ return;
+ }
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &tank_move_attack_post_rocket;
+}
+
+void
+tank_doattack_rocket(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+}
+
+void
+tank_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range;
+ float r;
+ float chance;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ if (self->enemy->health < 0)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_strike;
+ self->monsterinfo.aiflags &= ~AI_BRUTAL;
+ return;
+ }
+
+ if (self->monsterinfo.attack_state == AS_BLIND)
+ {
+ if (self->monsterinfo.blind_fire_delay < 1.0)
+ {
+ chance = 1.0;
+ }
+ else if (self->monsterinfo.blind_fire_delay < 7.5)
+ {
+ chance = 0.4;
+ }
+ else
+ {
+ chance = 0.1;
+ }
+
+ r = random();
+
+ self->monsterinfo.blind_fire_delay += 3.2 + 2.0 + random() * 3.0;
+
+ // Don't shoot at the origin.
+ if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
+ {
+ return;
+ }
+
+ // Don't shoot if the dice say not to.
+ if (r > chance)
+ {
+ return;
+ }
+
+ // turn on manual steering to signal both manual steering and blindfire
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+ self->monsterinfo.attack_finished = level.time + 3.0 + 2*random();
+ self->pain_debounce_time = level.time + 5.0; // no pain for a while
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ r = random();
+
+ if (range <= 125)
+ {
+ if (r < 0.4)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_chain;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_blast;
+ }
+ }
+ else if (range <= 250)
+ {
+ if (r < 0.5)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_chain;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_blast;
+ }
+ }
+ else
+ {
+ if (r < 0.33)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_chain;
+ }
+ else if (r < 0.66)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_pre_rocket;
+ self->pain_debounce_time = level.time + 5.0; /* no pain for a while */
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_blast;
+ }
+ }
+}
+
+void
+tank_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -16);
+ VectorSet(self->maxs, 16, 16, -0);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t tank_frames_death1[] = {
+ {ai_move, -7, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, -15, tank_thud},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t tank_move_death = {
+ FRAME_death101,
+ FRAME_death132,
+ tank_frames_death1,
+ tank_dead
+};
+
+void
+tank_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 1 /*4*/; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ self->monsterinfo.currentmove = &tank_move_death;
+}
+
+qboolean
+tank_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (blocked_checkplat(self, dist))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+ */
+
+/*
+ * QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_tank(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
+ VectorSet(self->mins, -32, -32, -16);
+ VectorSet(self->maxs, 32, 32, 72);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ sound_pain = gi.soundindex("tank/tnkpain2.wav");
+ sound_thud = gi.soundindex("tank/tnkdeth2.wav");
+ sound_idle = gi.soundindex("tank/tnkidle1.wav");
+ sound_die = gi.soundindex("tank/death.wav");
+ sound_step = gi.soundindex("tank/step.wav");
+ sound_windup = gi.soundindex("tank/tnkatck4.wav");
+ sound_strike = gi.soundindex("tank/tnkatck5.wav");
+ sound_sight = gi.soundindex("tank/sight1.wav");
+
+ gi.soundindex("tank/tnkatck1.wav");
+ gi.soundindex("tank/tnkatk2a.wav");
+ gi.soundindex("tank/tnkatk2b.wav");
+ gi.soundindex("tank/tnkatk2c.wav");
+ gi.soundindex("tank/tnkatk2d.wav");
+ gi.soundindex("tank/tnkatk2e.wav");
+ gi.soundindex("tank/tnkatck3.wav");
+
+ if (strcmp(self->classname, "monster_tank_commander") == 0)
+ {
+ self->health = 1000;
+ self->gib_health = -225;
+ }
+ else
+ {
+ self->health = 750;
+ self->gib_health = -200;
+ }
+
+ self->mass = 500;
+
+ self->pain = tank_pain;
+ self->die = tank_die;
+ self->monsterinfo.stand = tank_stand;
+ self->monsterinfo.walk = tank_walk;
+ self->monsterinfo.run = tank_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = tank_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = tank_sight;
+ self->monsterinfo.idle = tank_idle;
+ self->monsterinfo.blocked = tank_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &tank_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+
+ self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+ self->monsterinfo.blindfire = true;
+
+ if (strcmp(self->classname, "monster_tank_commander") == 0)
+ {
+ self->s.skinnum = 2;
+ }
+}
diff --git a/rogue/src/monster/tank/tank.h b/rogue/src/monster/tank/tank.h
new file mode 100644
index 0000000..3ba2d0f
--- /dev/null
+++ b/rogue/src/monster/tank/tank.h
@@ -0,0 +1,302 @@
+/* =======================================================================
+ *
+ * Tank and Tank Commander animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_walk01 30
+#define FRAME_walk02 31
+#define FRAME_walk03 32
+#define FRAME_walk04 33
+#define FRAME_walk05 34
+#define FRAME_walk06 35
+#define FRAME_walk07 36
+#define FRAME_walk08 37
+#define FRAME_walk09 38
+#define FRAME_walk10 39
+#define FRAME_walk11 40
+#define FRAME_walk12 41
+#define FRAME_walk13 42
+#define FRAME_walk14 43
+#define FRAME_walk15 44
+#define FRAME_walk16 45
+#define FRAME_walk17 46
+#define FRAME_walk18 47
+#define FRAME_walk19 48
+#define FRAME_walk20 49
+#define FRAME_walk21 50
+#define FRAME_walk22 51
+#define FRAME_walk23 52
+#define FRAME_walk24 53
+#define FRAME_walk25 54
+#define FRAME_attak101 55
+#define FRAME_attak102 56
+#define FRAME_attak103 57
+#define FRAME_attak104 58
+#define FRAME_attak105 59
+#define FRAME_attak106 60
+#define FRAME_attak107 61
+#define FRAME_attak108 62
+#define FRAME_attak109 63
+#define FRAME_attak110 64
+#define FRAME_attak111 65
+#define FRAME_attak112 66
+#define FRAME_attak113 67
+#define FRAME_attak114 68
+#define FRAME_attak115 69
+#define FRAME_attak116 70
+#define FRAME_attak117 71
+#define FRAME_attak118 72
+#define FRAME_attak119 73
+#define FRAME_attak120 74
+#define FRAME_attak121 75
+#define FRAME_attak122 76
+#define FRAME_attak201 77
+#define FRAME_attak202 78
+#define FRAME_attak203 79
+#define FRAME_attak204 80
+#define FRAME_attak205 81
+#define FRAME_attak206 82
+#define FRAME_attak207 83
+#define FRAME_attak208 84
+#define FRAME_attak209 85
+#define FRAME_attak210 86
+#define FRAME_attak211 87
+#define FRAME_attak212 88
+#define FRAME_attak213 89
+#define FRAME_attak214 90
+#define FRAME_attak215 91
+#define FRAME_attak216 92
+#define FRAME_attak217 93
+#define FRAME_attak218 94
+#define FRAME_attak219 95
+#define FRAME_attak220 96
+#define FRAME_attak221 97
+#define FRAME_attak222 98
+#define FRAME_attak223 99
+#define FRAME_attak224 100
+#define FRAME_attak225 101
+#define FRAME_attak226 102
+#define FRAME_attak227 103
+#define FRAME_attak228 104
+#define FRAME_attak229 105
+#define FRAME_attak230 106
+#define FRAME_attak231 107
+#define FRAME_attak232 108
+#define FRAME_attak233 109
+#define FRAME_attak234 110
+#define FRAME_attak235 111
+#define FRAME_attak236 112
+#define FRAME_attak237 113
+#define FRAME_attak238 114
+#define FRAME_attak301 115
+#define FRAME_attak302 116
+#define FRAME_attak303 117
+#define FRAME_attak304 118
+#define FRAME_attak305 119
+#define FRAME_attak306 120
+#define FRAME_attak307 121
+#define FRAME_attak308 122
+#define FRAME_attak309 123
+#define FRAME_attak310 124
+#define FRAME_attak311 125
+#define FRAME_attak312 126
+#define FRAME_attak313 127
+#define FRAME_attak314 128
+#define FRAME_attak315 129
+#define FRAME_attak316 130
+#define FRAME_attak317 131
+#define FRAME_attak318 132
+#define FRAME_attak319 133
+#define FRAME_attak320 134
+#define FRAME_attak321 135
+#define FRAME_attak322 136
+#define FRAME_attak323 137
+#define FRAME_attak324 138
+#define FRAME_attak325 139
+#define FRAME_attak326 140
+#define FRAME_attak327 141
+#define FRAME_attak328 142
+#define FRAME_attak329 143
+#define FRAME_attak330 144
+#define FRAME_attak331 145
+#define FRAME_attak332 146
+#define FRAME_attak333 147
+#define FRAME_attak334 148
+#define FRAME_attak335 149
+#define FRAME_attak336 150
+#define FRAME_attak337 151
+#define FRAME_attak338 152
+#define FRAME_attak339 153
+#define FRAME_attak340 154
+#define FRAME_attak341 155
+#define FRAME_attak342 156
+#define FRAME_attak343 157
+#define FRAME_attak344 158
+#define FRAME_attak345 159
+#define FRAME_attak346 160
+#define FRAME_attak347 161
+#define FRAME_attak348 162
+#define FRAME_attak349 163
+#define FRAME_attak350 164
+#define FRAME_attak351 165
+#define FRAME_attak352 166
+#define FRAME_attak353 167
+#define FRAME_attak401 168
+#define FRAME_attak402 169
+#define FRAME_attak403 170
+#define FRAME_attak404 171
+#define FRAME_attak405 172
+#define FRAME_attak406 173
+#define FRAME_attak407 174
+#define FRAME_attak408 175
+#define FRAME_attak409 176
+#define FRAME_attak410 177
+#define FRAME_attak411 178
+#define FRAME_attak412 179
+#define FRAME_attak413 180
+#define FRAME_attak414 181
+#define FRAME_attak415 182
+#define FRAME_attak416 183
+#define FRAME_attak417 184
+#define FRAME_attak418 185
+#define FRAME_attak419 186
+#define FRAME_attak420 187
+#define FRAME_attak421 188
+#define FRAME_attak422 189
+#define FRAME_attak423 190
+#define FRAME_attak424 191
+#define FRAME_attak425 192
+#define FRAME_attak426 193
+#define FRAME_attak427 194
+#define FRAME_attak428 195
+#define FRAME_attak429 196
+#define FRAME_pain101 197
+#define FRAME_pain102 198
+#define FRAME_pain103 199
+#define FRAME_pain104 200
+#define FRAME_pain201 201
+#define FRAME_pain202 202
+#define FRAME_pain203 203
+#define FRAME_pain204 204
+#define FRAME_pain205 205
+#define FRAME_pain301 206
+#define FRAME_pain302 207
+#define FRAME_pain303 208
+#define FRAME_pain304 209
+#define FRAME_pain305 210
+#define FRAME_pain306 211
+#define FRAME_pain307 212
+#define FRAME_pain308 213
+#define FRAME_pain309 214
+#define FRAME_pain310 215
+#define FRAME_pain311 216
+#define FRAME_pain312 217
+#define FRAME_pain313 218
+#define FRAME_pain314 219
+#define FRAME_pain315 220
+#define FRAME_pain316 221
+#define FRAME_death101 222
+#define FRAME_death102 223
+#define FRAME_death103 224
+#define FRAME_death104 225
+#define FRAME_death105 226
+#define FRAME_death106 227
+#define FRAME_death107 228
+#define FRAME_death108 229
+#define FRAME_death109 230
+#define FRAME_death110 231
+#define FRAME_death111 232
+#define FRAME_death112 233
+#define FRAME_death113 234
+#define FRAME_death114 235
+#define FRAME_death115 236
+#define FRAME_death116 237
+#define FRAME_death117 238
+#define FRAME_death118 239
+#define FRAME_death119 240
+#define FRAME_death120 241
+#define FRAME_death121 242
+#define FRAME_death122 243
+#define FRAME_death123 244
+#define FRAME_death124 245
+#define FRAME_death125 246
+#define FRAME_death126 247
+#define FRAME_death127 248
+#define FRAME_death128 249
+#define FRAME_death129 250
+#define FRAME_death130 251
+#define FRAME_death131 252
+#define FRAME_death132 253
+#define FRAME_recln101 254
+#define FRAME_recln102 255
+#define FRAME_recln103 256
+#define FRAME_recln104 257
+#define FRAME_recln105 258
+#define FRAME_recln106 259
+#define FRAME_recln107 260
+#define FRAME_recln108 261
+#define FRAME_recln109 262
+#define FRAME_recln110 263
+#define FRAME_recln111 264
+#define FRAME_recln112 265
+#define FRAME_recln113 266
+#define FRAME_recln114 267
+#define FRAME_recln115 268
+#define FRAME_recln116 269
+#define FRAME_recln117 270
+#define FRAME_recln118 271
+#define FRAME_recln119 272
+#define FRAME_recln120 273
+#define FRAME_recln121 274
+#define FRAME_recln122 275
+#define FRAME_recln123 276
+#define FRAME_recln124 277
+#define FRAME_recln125 278
+#define FRAME_recln126 279
+#define FRAME_recln127 280
+#define FRAME_recln128 281
+#define FRAME_recln129 282
+#define FRAME_recln130 283
+#define FRAME_recln131 284
+#define FRAME_recln132 285
+#define FRAME_recln133 286
+#define FRAME_recln134 287
+#define FRAME_recln135 288
+#define FRAME_recln136 289
+#define FRAME_recln137 290
+#define FRAME_recln138 291
+#define FRAME_recln139 292
+#define FRAME_recln140 293
+#define MODEL_SCALE 1.000000
diff --git a/rogue/src/monster/turret/turret.c b/rogue/src/monster/turret/turret.c
new file mode 100644
index 0000000..b0193f0
--- /dev/null
+++ b/rogue/src/monster/turret/turret.c
@@ -0,0 +1,1266 @@
+/* =======================================================================
+ *
+ * Wall mounted turrets.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "turret.h"
+
+#define SPAWN_BLASTER 0x0008
+#define SPAWN_MACHINEGUN 0x0010
+#define SPAWN_ROCKET 0x0020
+#define SPAWN_HEATBEAM 0x0040
+#define SPAWN_WEAPONCHOICE 0x0078
+#define SPAWN_INSTANT_WEAPON 0x0050
+#define SPAWN_WALL_UNIT 0x0080
+
+#define TURRET_BULLET_DAMAGE 4
+#define TURRET_HEAT_DAMAGE 4
+
+extern qboolean FindTarget(edict_t *self);
+
+void turret_run(edict_t *self);
+void TurretAim(edict_t *self);
+void turret_sight(edict_t *self, edict_t *other);
+void turret_search(edict_t *self);
+void turret_stand(edict_t *self);
+void turret_wake(edict_t *self);
+void turret_ready_gun(edict_t *self);
+void turret_run(edict_t *self);
+void turret_attack(edict_t *self);
+
+extern void Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *));
+
+mmove_t turret_move_fire;
+mmove_t turret_move_fire_blind;
+
+void
+TurretAim(edict_t *self)
+{
+ vec3_t end, dir;
+ vec3_t ang;
+ float move, idealPitch, idealYaw, current, speed;
+ int orientation;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || (self->enemy == world))
+ {
+ if (!FindTarget(self))
+ {
+ return;
+ }
+ }
+
+ /* if turret is still in inactive mode, ready the gun, but don't aim */
+ if (self->s.frame < FRAME_active01)
+ {
+ turret_ready_gun(self);
+ return;
+ }
+
+ /* if turret is still readying, don't aim. */
+ if (self->s.frame < FRAME_run01)
+ {
+ return;
+ }
+
+ /* blindfire aiming here */
+ if (self->monsterinfo.currentmove == &turret_move_fire_blind)
+ {
+ VectorCopy(self->monsterinfo.blind_fire_target, end);
+
+ if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
+ {
+ end[2] += self->enemy->viewheight + 10;
+ }
+ else
+ {
+ end[2] += self->enemy->mins[2] - 10;
+ }
+ }
+ else
+ {
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (self->enemy->client)
+ {
+ end[2] += self->enemy->viewheight;
+ }
+ }
+
+ VectorSubtract(end, self->s.origin, dir);
+ vectoangles2(dir, ang);
+
+ idealPitch = ang[PITCH];
+ idealYaw = ang[YAW];
+
+ orientation = self->offset[1];
+
+ switch (orientation)
+ {
+ case -1: /* up pitch: 0 to 90 */
+
+ if (idealPitch < -90)
+ {
+ idealPitch += 360;
+ }
+
+ if (idealPitch > -5)
+ {
+ idealPitch = -5;
+ }
+
+ break;
+ case -2: /* down pitch: -180 to -360 */
+
+ if (idealPitch > -90)
+ {
+ idealPitch -= 360;
+ }
+
+ if (idealPitch < -355)
+ {
+ idealPitch = -355;
+ }
+ else if (idealPitch > -185)
+ {
+ idealPitch = -185;
+ }
+
+ break;
+ case 0: /* +X pitch: 0 to -90, -270 to -360 (or 0 to 90) */
+
+ if (idealPitch < -180)
+ {
+ idealPitch += 360;
+ }
+
+ if (idealPitch > 85)
+ {
+ idealPitch = 85;
+ }
+ else if (idealPitch < -85)
+ {
+ idealPitch = -85;
+ }
+
+ if (idealYaw > 180)
+ {
+ idealYaw -= 360;
+ }
+
+ if (idealYaw > 85)
+ {
+ idealYaw = 85;
+ }
+ else if (idealYaw < -85)
+ {
+ idealYaw = -85;
+ }
+
+ break;
+ case 90: /* +Y pitch: 0 to 90, -270 to -360 (or 0 to 90) */
+
+ if (idealPitch < -180)
+ {
+ idealPitch += 360;
+ }
+
+ if (idealPitch > 85)
+ {
+ idealPitch = 85;
+ }
+ else if (idealPitch < -85)
+ {
+ idealPitch = -85;
+ }
+
+ if (idealYaw > 270)
+ {
+ idealYaw -= 360;
+ }
+
+ if (idealYaw > 175)
+ {
+ idealYaw = 175;
+ }
+ else if (idealYaw < 5)
+ {
+ idealYaw = 5;
+ }
+
+ break;
+ case 180: /* -X pitch: 0 to 90, -270 to -360 (or 0 to 90) */
+
+ if (idealPitch < -180)
+ {
+ idealPitch += 360;
+ }
+
+ if (idealPitch > 85)
+ {
+ idealPitch = 85;
+ }
+ else if (idealPitch < -85)
+ {
+ idealPitch = -85;
+ }
+
+ if (idealYaw > 265)
+ {
+ idealYaw = 265;
+ }
+ else if (idealYaw < 95)
+ {
+ idealYaw = 95;
+ }
+
+ break;
+ case 270: /* -Y pitch: 0 to 90, -270 to -360 (or 0 to 90) */
+
+ if (idealPitch < -180)
+ {
+ idealPitch += 360;
+ }
+
+ if (idealPitch > 85)
+ {
+ idealPitch = 85;
+ }
+ else if (idealPitch < -85)
+ {
+ idealPitch = -85;
+ }
+
+ if (idealYaw < 90)
+ {
+ idealYaw += 360;
+ }
+
+ if (idealYaw > 355)
+ {
+ idealYaw = 355;
+ }
+ else if (idealYaw < 185)
+ {
+ idealYaw = 185;
+ }
+
+ break;
+ }
+
+ current = self->s.angles[PITCH];
+ speed = self->yaw_speed;
+
+ if (idealPitch != current)
+ {
+ move = idealPitch - current;
+
+ while (move >= 360)
+ {
+ move -= 360;
+ }
+
+ if (move >= 90)
+ {
+ move = move - 360;
+ }
+
+ while (move <= -360)
+ {
+ move += 360;
+ }
+
+ if (move <= -90)
+ {
+ move = move + 360;
+ }
+
+ if (move > 0)
+ {
+ if (move > speed)
+ {
+ move = speed;
+ }
+ }
+ else
+ {
+ if (move < -speed)
+ {
+ move = -speed;
+ }
+ }
+
+ self->s.angles[PITCH] = anglemod(current + move);
+ }
+
+ current = self->s.angles[YAW];
+ speed = self->yaw_speed;
+
+ if (idealYaw != current)
+ {
+ move = idealYaw - current;
+
+ if (move >= 180)
+ {
+ move = move - 360;
+ }
+
+ if (move <= -180)
+ {
+ move = move + 360;
+ }
+
+ if (move > 0)
+ {
+ if (move > speed)
+ {
+ move = speed;
+ }
+ }
+ else
+ {
+ if (move < -speed)
+ {
+ move = -speed;
+ }
+ }
+
+ self->s.angles[YAW] = anglemod(current + move);
+ }
+}
+
+void
+turret_sight(edict_t *self, edict_t *other)
+{
+}
+
+void
+turret_search(edict_t *self)
+{
+}
+
+mframe_t turret_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t turret_move_stand = {
+ FRAME_stand01,
+ FRAME_stand02,
+ turret_frames_stand,
+ NULL
+};
+
+void
+turret_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &turret_move_stand;
+}
+
+mframe_t turret_frames_ready_gun[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL}
+};
+
+mmove_t turret_move_ready_gun = {
+ FRAME_active01,
+ FRAME_run01,
+ turret_frames_ready_gun,
+ turret_run
+};
+
+void
+turret_ready_gun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &turret_move_ready_gun;
+}
+
+mframe_t turret_frames_seek[] = {
+ {ai_walk, 0, TurretAim},
+ {ai_walk, 0, TurretAim}
+};
+
+mmove_t turret_move_seek = {
+ FRAME_run01,
+ FRAME_run02,
+ turret_frames_seek,
+ NULL
+};
+
+void
+turret_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame < FRAME_run01)
+ {
+ turret_ready_gun(self);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &turret_move_seek;
+ }
+}
+
+mframe_t turret_frames_run[] = {
+ {ai_run, 0, TurretAim},
+ {ai_run, 0, TurretAim}
+};
+
+mmove_t turret_move_run = {
+ FRAME_run01,
+ FRAME_run02,
+ turret_frames_run,
+ turret_run
+};
+
+void
+turret_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame < FRAME_run01)
+ {
+ turret_ready_gun(self);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &turret_move_run;
+ }
+}
+
+void
+TurretFire(edict_t *self)
+{
+ vec3_t forward;
+ vec3_t start, end, dir;
+ float time, dist, chance;
+ trace_t trace;
+ int rocketSpeed = 0;
+
+ if (!self)
+ {
+ return;
+ }
+
+ TurretAim(self);
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ VectorNormalize(dir);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ chance = DotProduct(dir, forward);
+
+ if (chance < 0.98)
+ {
+ return;
+ }
+
+ /* rockets fire less often than the others do. */
+ if (self->spawnflags & SPAWN_ROCKET)
+ {
+ rocketSpeed = 550;
+
+ if (skill->value == SKILL_HARD)
+ {
+ rocketSpeed += 200 * random();
+ }
+ else if (skill->value == SKILL_HARDPLUS)
+ {
+ rocketSpeed += 100 + (200 * random());
+ }
+ }
+ else if (self->spawnflags & SPAWN_BLASTER)
+ {
+ if (skill->value == SKILL_EASY)
+ {
+ rocketSpeed = 600;
+ }
+ else if (skill->value == SKILL_MEDIUM)
+ {
+ rocketSpeed = 800;
+ }
+ else
+ {
+ rocketSpeed = 1000;
+ }
+ }
+
+ if (visible(self, self->enemy))
+ {
+ VectorCopy(self->s.origin, start);
+ VectorCopy(self->enemy->s.origin, end);
+
+ /* aim for the head. */
+ if (self->enemy->client)
+ {
+ end[2] += self->enemy->viewheight;
+ }
+ else
+ {
+ end[2] += 22;
+ }
+
+ VectorSubtract(end, start, dir);
+ dist = VectorLength(dir);
+
+ /* check for predictive fire if distance less than 512 */
+ if (!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist < 512))
+ {
+ chance = random();
+
+ /* ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80% */
+ chance += (3 - skill->value) * 0.1;
+
+ if (chance < 0.8)
+ {
+ /* lead the target.... */
+ time = dist / 1000;
+ VectorMA(end, time, self->enemy->velocity, end);
+ VectorSubtract(end, start, dir);
+ }
+ }
+
+ VectorNormalize(dir);
+ trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
+
+ if ((trace.ent == self->enemy) || (trace.ent == world))
+ {
+ if (self->spawnflags & SPAWN_BLASTER)
+ {
+ monster_fire_blaster(self, start, dir, 20, rocketSpeed,
+ MZ2_TURRET_BLASTER, EF_BLASTER);
+ }
+ else if (self->spawnflags & SPAWN_MACHINEGUN)
+ {
+ monster_fire_bullet(self, start, dir, TURRET_BULLET_DAMAGE,
+ 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,
+ MZ2_TURRET_MACHINEGUN);
+ }
+ else if (self->spawnflags & SPAWN_ROCKET)
+ {
+ if (dist * trace.fraction > 72)
+ {
+ monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
+ }
+ }
+ }
+ }
+}
+
+void
+TurretFireBlind(edict_t *self)
+{
+ vec3_t forward;
+ vec3_t start, end, dir;
+ float chance;
+ int rocketSpeed = 0;
+
+ if (!self)
+ {
+ return;
+ }
+
+ TurretAim(self);
+
+ if (!self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, dir);
+ VectorNormalize(dir);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ chance = DotProduct(dir, forward);
+
+ if (chance < 0.98)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWN_ROCKET)
+ {
+ rocketSpeed = 550;
+
+ if (skill->value == SKILL_HARD)
+ {
+ rocketSpeed += 200 * random();
+ }
+ else if (skill->value == SKILL_HARDPLUS)
+ {
+ rocketSpeed += 100 + (200 * random());
+ }
+ }
+
+ VectorCopy(self->s.origin, start);
+ VectorCopy(self->monsterinfo.blind_fire_target, end);
+
+ if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
+ {
+ end[2] += self->enemy->viewheight + 10;
+ }
+ else
+ {
+ end[2] += self->enemy->mins[2] - 10;
+ }
+
+ VectorSubtract(end, start, dir);
+ VectorNormalize(dir);
+
+ if (self->spawnflags & SPAWN_BLASTER)
+ {
+ monster_fire_blaster(self, start, dir, 20, 1000,
+ MZ2_TURRET_BLASTER, EF_BLASTER);
+ }
+ else if (self->spawnflags & SPAWN_ROCKET)
+ {
+ monster_fire_rocket(self, start, dir, 50, rocketSpeed,
+ MZ2_TURRET_ROCKET);
+ }
+}
+
+mframe_t turret_frames_fire[] = {
+ {ai_run, 0, TurretFire},
+ {ai_run, 0, TurretAim},
+ {ai_run, 0, TurretAim},
+ {ai_run, 0, TurretAim}
+};
+
+mmove_t turret_move_fire = {
+ FRAME_pow01,
+ FRAME_pow04,
+ turret_frames_fire,
+ turret_run
+};
+
+/* the blind frames need to aim first */
+mframe_t turret_frames_fire_blind[] = {
+ {ai_run, 0, TurretAim},
+ {ai_run, 0, TurretAim},
+ {ai_run, 0, TurretAim},
+ {ai_run, 0, TurretFireBlind}
+};
+
+mmove_t turret_move_fire_blind = {
+ FRAME_pow01,
+ FRAME_pow04,
+ turret_frames_fire_blind,
+ turret_run
+};
+
+void
+turret_attack(edict_t *self)
+{
+ float r, chance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame < FRAME_run01)
+ {
+ turret_ready_gun(self);
+ }
+ else if (self->monsterinfo.attack_state != AS_BLIND)
+ {
+ self->monsterinfo.nextframe = FRAME_pow01;
+ self->monsterinfo.currentmove = &turret_move_fire;
+ }
+ else
+ {
+ /* setup shot probabilities */
+ if (self->monsterinfo.blind_fire_delay < 1.0)
+ {
+ chance = 1.0;
+ }
+ else if (self->monsterinfo.blind_fire_delay < 7.5)
+ {
+ chance = 0.4;
+ }
+ else
+ {
+ chance = 0.1;
+ }
+
+ r = random();
+
+ /* minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5 */
+ self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + random() * 4.0;
+
+ /* don't shoot at the origin */
+ if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin))
+ {
+ return;
+ }
+
+ /* don't shoot if the dice say not to */
+ if (r > chance)
+ {
+ return;
+ }
+
+ self->monsterinfo.nextframe = FRAME_pow01;
+ self->monsterinfo.currentmove = &turret_move_fire_blind;
+ }
+}
+
+void
+turret_pain(edict_t *self, edict_t *other, float kick, int damage)
+{
+ return;
+}
+
+void
+turret_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ vec3_t forward;
+ vec3_t start;
+ edict_t *base;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_PLAIN_EXPLOSION);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ VectorMA(self->s.origin, 1, forward, start);
+
+ ThrowDebris(self, "models/objects/debris1/tris.md2", 1, start);
+ ThrowDebris(self, "models/objects/debris1/tris.md2", 2, start);
+ ThrowDebris(self, "models/objects/debris1/tris.md2", 1, start);
+ ThrowDebris(self, "models/objects/debris1/tris.md2", 2, start);
+
+ if (self->teamchain)
+ {
+ base = self->teamchain;
+ base->solid = SOLID_BBOX;
+ base->takedamage = DAMAGE_NO;
+ base->movetype = MOVETYPE_NONE;
+ gi.linkentity(base);
+ }
+
+ if (self->target)
+ {
+ if (self->enemy && self->enemy->inuse)
+ {
+ G_UseTargets(self, self->enemy);
+ }
+ else
+ {
+ G_UseTargets(self, self);
+ }
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+turret_wall_spawn(edict_t *turret)
+{
+ edict_t *ent;
+ int angle;
+
+ if (!turret)
+ {
+ return;
+ }
+
+ ent = G_Spawn();
+ VectorCopy(turret->s.origin, ent->s.origin);
+ VectorCopy(turret->s.angles, ent->s.angles);
+
+ angle = ent->s.angles[1];
+
+ if (ent->s.angles[0] == 90)
+ {
+ angle = -1;
+ }
+ else if (ent->s.angles[0] == 270)
+ {
+ angle = -2;
+ }
+
+ switch (angle)
+ {
+ case -1:
+ VectorSet(ent->mins, -16, -16, -8);
+ VectorSet(ent->maxs, 16, 16, 0);
+ break;
+ case -2:
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 8);
+ break;
+ case 0:
+ VectorSet(ent->mins, -8, -16, -16);
+ VectorSet(ent->maxs, 0, 16, 16);
+ break;
+ case 90:
+ VectorSet(ent->mins, -16, -8, -16);
+ VectorSet(ent->maxs, 16, 0, 16);
+ break;
+ case 180:
+ VectorSet(ent->mins, 0, -16, -16);
+ VectorSet(ent->maxs, 8, 16, 16);
+ break;
+ case 270:
+ VectorSet(ent->mins, -16, 0, -16);
+ VectorSet(ent->maxs, 16, 8, 16);
+ break;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_NOT;
+
+ ent->teammaster = turret;
+ turret->teammaster = turret;
+ turret->teamchain = ent;
+ ent->teamchain = NULL;
+ ent->flags |= FL_TEAMSLAVE;
+ ent->owner = turret;
+
+ ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2");
+
+ gi.linkentity(ent);
+}
+
+void
+turret_wake(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* the wall section will call this when it stops moving. */
+ if (self->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ self->monsterinfo.stand = turret_stand;
+ self->monsterinfo.walk = turret_walk;
+ self->monsterinfo.run = turret_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = turret_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = turret_sight;
+ self->monsterinfo.search = turret_search;
+ self->monsterinfo.currentmove = &turret_move_stand;
+
+ self->takedamage = DAMAGE_AIM;
+ self->movetype = MOVETYPE_NONE;
+ self->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+
+ gi.linkentity(self);
+
+ stationarymonster_start(self);
+
+ if (self->think)
+ {
+ self->think(self);
+ }
+
+ if (self->spawnflags & SPAWN_MACHINEGUN)
+ {
+ self->s.skinnum = 1;
+ }
+ else if (self->spawnflags & SPAWN_ROCKET)
+ {
+ self->s.skinnum = 2;
+ }
+
+ /* but we do want the death to count */
+ self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT;
+}
+
+void
+turret_activate(edict_t *self, edict_t *other, edict_t *activator)
+{
+ vec3_t endpos;
+ vec3_t forward;
+ edict_t *base;
+
+ if (self->movetype == MOVETYPE_PUSH)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+
+ if (!self->speed)
+ {
+ self->speed = 15;
+ }
+
+ self->moveinfo.speed = self->speed;
+ self->moveinfo.accel = self->speed;
+ self->moveinfo.decel = self->speed;
+
+ if (self->s.angles[0] == 270)
+ {
+ VectorSet(forward, 0, 0, 1);
+ }
+ else if (self->s.angles[0] == 90)
+ {
+ VectorSet(forward, 0, 0, -1);
+ }
+ else if (self->s.angles[1] == 0)
+ {
+ VectorSet(forward, 1, 0, 0);
+ }
+ else if (self->s.angles[1] == 90)
+ {
+ VectorSet(forward, 0, 1, 0);
+ }
+ else if (self->s.angles[1] == 180)
+ {
+ VectorSet(forward, -1, 0, 0);
+ }
+ else if (self->s.angles[1] == 270)
+ {
+ VectorSet(forward, 0, -1, 0);
+ }
+ else
+ {
+ VectorClear(forward);
+ }
+
+ /* start up the turret */
+ VectorMA(self->s.origin, 32, forward, endpos);
+ Move_Calc(self, endpos, turret_wake);
+
+ base = self->teamchain;
+
+ if (base)
+ {
+ base->movetype = MOVETYPE_PUSH;
+ base->speed = self->speed;
+ base->moveinfo.speed = base->speed;
+ base->moveinfo.accel = base->speed;
+ base->moveinfo.decel = base->speed;
+
+ /* start up the wall section */
+ VectorMA(self->teamchain->s.origin, 32, forward, endpos);
+ Move_Calc(self->teamchain, endpos, turret_wake);
+ }
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex("world/dr_short.wav"), 1, ATTN_NORM, 0);
+}
+
+/* checkattack .. ignore range, just attack if available */
+qboolean
+turret_checkattack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ float chance, nexttime;
+ trace_t tr;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA |
+ CONTENTS_WINDOW);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ /* we want them to go ahead and shoot at info_notnulls if they can. */
+ if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0))
+ {
+ /* if we can't see our target, and we're not blocked by a monster, go into blind fire if available */
+ if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
+ {
+ if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0))
+ {
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
+ {
+ /* wait for our time */
+ return false;
+ }
+ else
+ {
+ /* make sure we're not going to shoot something we don't want to shoot */
+ tr = gi.trace(spot1, NULL, NULL, self->monsterinfo.blind_fire_target,
+ self, CONTENTS_MONSTER);
+
+ if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
+ {
+ return false;
+ }
+
+ self->monsterinfo.attack_state = AS_BLIND;
+ self->monsterinfo.attack_finished = level.time + 0.5 + 2 * random();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (self->spawnflags & SPAWN_ROCKET)
+ {
+ chance = 0.10;
+ nexttime = (1.8 - (0.2 * skill->value));
+ }
+ else if (self->spawnflags & SPAWN_BLASTER)
+ {
+ chance = 0.35;
+ nexttime = (1.2 - (0.2 * skill->value));
+ }
+ else
+ {
+ chance = 0.50;
+ nexttime = (0.8 - (0.1 * skill->value));
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ chance *= 0.5;
+ }
+ else if (skill->value > SKILL_MEDIUM)
+ {
+ chance *= 2;
+ }
+
+ if (((random() < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + nexttime;
+ return true;
+ }
+
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+
+ return false;
+}
+
+/*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit
+ *
+ * The automated defense turret that mounts on walls.
+ * Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam.
+ * Default weapon is blaster.
+ * When activated, wall units move 32 units in the direction they're facing.
+ */
+void
+SP_monster_turret(edict_t *self)
+{
+ int angle;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ /* pre-caches */
+ gi.soundindex("world/dr_short.wav");
+ gi.modelindex("models/objects/debris1/tris.md2");
+
+ self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2");
+
+ VectorSet(self->mins, -12, -12, -12);
+ VectorSet(self->maxs, 12, 12, 12);
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_BBOX;
+
+ self->health = 240;
+ self->gib_health = -100;
+ self->mass = 250;
+ self->yaw_speed = 45;
+
+ self->flags |= FL_MECHANICAL;
+
+ self->pain = turret_pain;
+ self->die = turret_die;
+
+ /* map designer didn't specify weapon type. set it now. */
+ if (!(self->spawnflags & SPAWN_WEAPONCHOICE))
+ {
+ self->spawnflags |= SPAWN_BLASTER;
+ }
+
+ if (self->spawnflags & SPAWN_HEATBEAM)
+ {
+ self->spawnflags &= ~SPAWN_HEATBEAM;
+ self->spawnflags |= SPAWN_BLASTER;
+ }
+
+ if (!(self->spawnflags & SPAWN_WALL_UNIT))
+ {
+ self->monsterinfo.stand = turret_stand;
+ self->monsterinfo.walk = turret_walk;
+ self->monsterinfo.run = turret_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = turret_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = turret_sight;
+ self->monsterinfo.search = turret_search;
+ self->monsterinfo.currentmove = &turret_move_stand;
+ }
+
+ /* PMM */
+ self->monsterinfo.checkattack = turret_checkattack;
+
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+ self->monsterinfo.scale = MODEL_SCALE;
+ self->gravity = 0;
+
+ VectorCopy(self->s.angles, self->offset);
+ angle = (int)self->s.angles[1];
+
+ switch (angle)
+ {
+ case -1: /* up */
+ self->s.angles[0] = 270;
+ self->s.angles[1] = 0;
+ self->s.origin[2] += 2;
+ break;
+ case -2: /* down */
+ self->s.angles[0] = 90;
+ self->s.angles[1] = 0;
+ self->s.origin[2] -= 2;
+ break;
+ case 0:
+ self->s.origin[0] += 2;
+ break;
+ case 90:
+ self->s.origin[1] += 2;
+ break;
+ case 180:
+ self->s.origin[0] -= 2;
+ break;
+ case 270:
+ self->s.origin[1] -= 2;
+ break;
+ default:
+ break;
+ }
+
+ gi.linkentity(self);
+
+ if (self->spawnflags & SPAWN_WALL_UNIT)
+ {
+ if (!self->targetname)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+ self->use = turret_activate;
+ turret_wall_spawn(self);
+
+ if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) &&
+ (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
+ {
+ level.total_monsters++;
+ }
+ }
+ else
+ {
+ stationarymonster_start(self);
+ }
+
+ if (self->spawnflags & SPAWN_MACHINEGUN)
+ {
+ gi.soundindex("infantry/infatck1.wav");
+ self->s.skinnum = 1;
+ }
+ else if (self->spawnflags & SPAWN_ROCKET)
+ {
+ gi.soundindex("weapons/rockfly.wav");
+ gi.modelindex("models/objects/rocket/tris.md2");
+ gi.soundindex("chick/chkatck2.wav");
+ self->s.skinnum = 2;
+ }
+ else
+ {
+ if (!(self->spawnflags & SPAWN_BLASTER))
+ {
+ self->spawnflags |= SPAWN_BLASTER;
+ }
+
+ gi.modelindex("models/objects/laser/tris.md2");
+ gi.soundindex("misc/lasfly.wav");
+ gi.soundindex("soldier/solatck2.wav");
+ }
+
+ /* turrets don't get mad at monsters, and visa versa */
+ self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+ /* blindfire */
+ if (self->spawnflags & (SPAWN_ROCKET | SPAWN_BLASTER))
+ {
+ self->monsterinfo.blindfire = true;
+ }
+}
diff --git a/rogue/src/monster/turret/turret.h b/rogue/src/monster/turret/turret.h
new file mode 100644
index 0000000..d6cd824
--- /dev/null
+++ b/rogue/src/monster/turret/turret.h
@@ -0,0 +1,24 @@
+/* =======================================================================
+ *
+ * Turret animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_active01 2
+#define FRAME_active02 3
+#define FRAME_active03 4
+#define FRAME_active04 5
+#define FRAME_active05 6
+#define FRAME_active06 7
+#define FRAME_run01 8
+#define FRAME_run02 9
+#define FRAME_pow01 10
+#define FRAME_pow02 11
+#define FRAME_pow03 12
+#define FRAME_pow04 13
+#define FRAME_death01 14
+#define FRAME_death02 15
+#define MODEL_SCALE 3.500000
diff --git a/rogue/src/monster/widow/widow.c b/rogue/src/monster/widow/widow.c
new file mode 100644
index 0000000..a9ef6e7
--- /dev/null
+++ b/rogue/src/monster/widow/widow.c
@@ -0,0 +1,1910 @@
+/* =======================================================================
+ *
+ * Black Window (stage 1).
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "widow.h"
+
+#define NUM_STALKERS_SPAWNED 6 /* max # of stalkers she can spawn */
+
+#define RAIL_TIME 3
+#define BLASTER_TIME 2
+#define BLASTER2_DAMAGE 10
+#define WIDOW_RAIL_DAMAGE 50
+#define VARIANCE 15.0
+
+void BossExplode(edict_t *self);
+qboolean infront(edict_t *self, edict_t *other);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_search1;
+static int sound_rail;
+
+static unsigned long shotsfired;
+
+static vec3_t spawnpoints[] = {
+ {30, 100, 16},
+ {30, -100, 16}
+};
+
+static vec3_t beameffects[] = {
+ {12.58, -43.71, 68.88},
+ {3.43, 58.72, 68.41}
+};
+
+static float sweep_angles[] = {
+ 32.0, 26.0, 20.0, 10.0, 0.0, -6.5, -13.0, -27.0, -41.0
+};
+
+vec3_t stalker_mins = {-28, -28, -18};
+vec3_t stalker_maxs = {28, 28, 18};
+
+mmove_t widow_move_attack_post_blaster;
+mmove_t widow_move_attack_post_blaster_r;
+mmove_t widow_move_attack_post_blaster_l;
+mmove_t widow_move_attack_blaster;
+mmove_t widow_move_attack_rail;
+mmove_t widow_move_attack_rail_l;
+mmove_t widow_move_attack_rail_r;
+
+unsigned int widow_damage_multiplier;
+
+void widow_run(edict_t *self);
+void widow_stand(edict_t *self);
+void widow_dead(edict_t *self);
+void widow_attack(edict_t *self);
+void widow_attack_blaster(edict_t *self);
+void widow_reattack_blaster(edict_t *self);
+void widow_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+void widow_start_spawn(edict_t *self);
+void widow_done_spawn(edict_t *self);
+void widow_spawn_check(edict_t *self);
+void widow_prep_spawn(edict_t *self);
+void widow_attack_rail(edict_t *self);
+
+void widow_start_run_5(edict_t *self);
+void widow_start_run_10(edict_t *self);
+void widow_start_run_12(edict_t *self);
+
+void WidowCalcSlots(edict_t *self);
+
+void drawbbox(edict_t *self);
+
+void
+showme(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.dprintf("frame %d\n", self->s.frame);
+}
+
+void
+widow_search(edict_t *self)
+{
+}
+
+void
+widow_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.pausetime = 0;
+}
+
+float
+target_angle(edict_t *self)
+{
+ vec3_t target;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return 0.0;
+ }
+
+ VectorSubtract(self->s.origin, self->enemy->s.origin, target);
+ enemy_yaw = self->s.angles[YAW] - vectoyaw2(target);
+
+ if (enemy_yaw < 0)
+ {
+ enemy_yaw += 360.0;
+ }
+
+ enemy_yaw -= 180.0;
+
+ return enemy_yaw;
+}
+
+int
+WidowTorso(edict_t *self)
+{
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return 0;
+ }
+
+ enemy_yaw = target_angle(self);
+
+ if (enemy_yaw >= 105)
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_post_blaster_r;
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ return 0;
+ }
+
+ if (enemy_yaw <= -75.0)
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_post_blaster_l;
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ return 0;
+ }
+
+ if (enemy_yaw >= 95)
+ {
+ return FRAME_fired03;
+ }
+ else if (enemy_yaw >= 85)
+ {
+ return FRAME_fired04;
+ }
+ else if (enemy_yaw >= 75)
+ {
+ return FRAME_fired05;
+ }
+ else if (enemy_yaw >= 65)
+ {
+ return FRAME_fired06;
+ }
+ else if (enemy_yaw >= 55)
+ {
+ return FRAME_fired07;
+ }
+ else if (enemy_yaw >= 45)
+ {
+ return FRAME_fired08;
+ }
+ else if (enemy_yaw >= 35)
+ {
+ return FRAME_fired09;
+ }
+ else if (enemy_yaw >= 25)
+ {
+ return FRAME_fired10;
+ }
+ else if (enemy_yaw >= 15)
+ {
+ return FRAME_fired11;
+ }
+ else if (enemy_yaw >= 5)
+ {
+ return FRAME_fired12;
+ }
+ else if (enemy_yaw >= -5)
+ {
+ return FRAME_fired13;
+ }
+ else if (enemy_yaw >= -15)
+ {
+ return FRAME_fired14;
+ }
+ else if (enemy_yaw >= -25)
+ {
+ return FRAME_fired15;
+ }
+ else if (enemy_yaw >= -35)
+ {
+ return FRAME_fired16;
+ }
+ else if (enemy_yaw >= -45)
+ {
+ return FRAME_fired17;
+ }
+ else if (enemy_yaw >= -55)
+ {
+ return FRAME_fired18;
+ }
+ else if (enemy_yaw >= -65)
+ {
+ return FRAME_fired19;
+ }
+ else if (enemy_yaw >= -75)
+ {
+ return FRAME_fired20;
+ }
+
+ return 1;
+}
+
+void
+WidowBlaster(edict_t *self)
+{
+ vec3_t forward, right, target, vec, targ_angles;
+ vec3_t start;
+ int flashnum;
+ int effect;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ shotsfired++;
+
+ if (!(shotsfired % 4))
+ {
+ effect = EF_BLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+
+ if ((self->s.frame >= FRAME_spawn05) && (self->s.frame <= FRAME_spawn13))
+ {
+ /* sweep */
+ flashnum = MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - FRAME_spawn05;
+ G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward,
+ right, start);
+ VectorSubtract(self->enemy->s.origin, start, target);
+ vectoangles2(target, targ_angles);
+
+ VectorCopy(self->s.angles, vec);
+
+ vec[PITCH] += targ_angles[PITCH];
+ vec[YAW] -= sweep_angles[flashnum - MZ2_WIDOW_BLASTER_SWEEP1];
+
+ AngleVectors(vec, forward, NULL, NULL);
+ monster_fire_blaster2(self, start, forward, BLASTER2_DAMAGE * widow_damage_multiplier,
+ 1000, flashnum, effect);
+ }
+ else if ((self->s.frame >= FRAME_fired02a) && (self->s.frame <= FRAME_fired20))
+ {
+ vec3_t angles;
+ float aim_angle, target_angle;
+ float error;
+
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+
+ self->monsterinfo.nextframe = WidowTorso(self);
+
+ if (!self->monsterinfo.nextframe)
+ {
+ self->monsterinfo.nextframe = self->s.frame;
+ }
+
+ if (self->s.frame == FRAME_fired02a)
+ {
+ flashnum = MZ2_WIDOW_BLASTER_0;
+ }
+ else
+ {
+ flashnum = MZ2_WIDOW_BLASTER_100 + self->s.frame - FRAME_fired03;
+ }
+
+ G_ProjectSource(self->s.origin, monster_flash_offset[flashnum],
+ forward, right, start);
+
+ PredictAim(self->enemy, start, 1000, true, ((random() * 0.1) - 0.05), forward, NULL);
+
+ /* clamp it to within 10 degrees of the aiming angle (where she's facing) */
+ vectoangles2(forward, angles);
+ aim_angle = 100 - (10 * (flashnum - MZ2_WIDOW_BLASTER_100));
+
+ if (aim_angle <= 0)
+ {
+ aim_angle += 360;
+ }
+
+ target_angle = self->s.angles[YAW] - angles[YAW];
+
+ if (target_angle <= 0)
+ {
+ target_angle += 360;
+ }
+
+ error = aim_angle - target_angle;
+
+ /* positive error is to entity's left, aka positive direction in
+ engine unfortunately, I decided that for the aim_angle, positive
+ was right. *sigh* */
+ if (error > VARIANCE)
+ {
+ angles[YAW] = (self->s.angles[YAW] - aim_angle) + VARIANCE;
+ AngleVectors(angles, forward, NULL, NULL);
+ }
+ else if (error < -VARIANCE)
+ {
+ angles[YAW] = (self->s.angles[YAW] - aim_angle) - VARIANCE;
+ AngleVectors(angles, forward, NULL, NULL);
+ }
+
+ monster_fire_blaster2(self, start, forward, BLASTER2_DAMAGE * widow_damage_multiplier,
+ 1000, flashnum, effect);
+ }
+ else if ((self->s.frame >= FRAME_run01) && (self->s.frame <= FRAME_run08))
+ {
+ flashnum = MZ2_WIDOW_RUN_1 + self->s.frame - FRAME_run01;
+ G_ProjectSource(self->s.origin, monster_flash_offset[flashnum],
+ forward, right, start);
+
+ VectorSubtract(self->enemy->s.origin, start, target);
+ target[2] += self->enemy->viewheight;
+
+ monster_fire_blaster2(self, start, target, BLASTER2_DAMAGE * widow_damage_multiplier,
+ 1000, flashnum, effect);
+ }
+}
+
+void
+WidowSpawn(edict_t *self)
+{
+ vec3_t f, r, u, offset, startpoint, spawnpoint;
+ edict_t *ent, *designated_enemy;
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+
+ for (i = 0; i < 2; i++)
+ {
+ VectorCopy(spawnpoints[i], offset);
+
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint,
+ 64))
+ {
+ ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins, stalker_maxs,
+ "monster_stalker", 256);
+
+ if (!ent)
+ {
+ continue;
+ }
+
+ self->monsterinfo.monster_used++;
+ ent->monsterinfo.commander = self;
+ ent->nextthink = level.time;
+ ent->think(ent);
+
+ ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW | AI_DO_NOT_COUNT | AI_IGNORE_SHOTS;
+
+ designated_enemy = self->enemy;
+
+ if ((designated_enemy->inuse) && (designated_enemy->health > 0))
+ {
+ ent->enemy = designated_enemy;
+ FoundTarget(ent);
+ ent->monsterinfo.attack(ent);
+ }
+ }
+ }
+}
+
+void
+widow_spawn_check(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ WidowBlaster(self);
+ WidowSpawn(self);
+}
+
+void
+widow_ready_spawn(edict_t *self)
+{
+ vec3_t f, r, u, offset, startpoint, spawnpoint;
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ WidowBlaster(self);
+ AngleVectors(self->s.angles, f, r, u);
+
+ for (i = 0; i < 2; i++)
+ {
+ VectorCopy(spawnpoints[i], offset);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint,
+ 64))
+ {
+ SpawnGrow_Spawn(spawnpoint, 1);
+ }
+ }
+}
+
+void
+widow_step(edict_t *self)
+{
+ gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep3.wav"), 1, ATTN_NORM, 0);
+}
+
+mframe_t widow_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t widow_move_stand = {
+ FRAME_idle01,
+ FRAME_idle11,
+ widow_frames_stand,
+ NULL
+};
+
+mframe_t widow_frames_walk[] = {
+ /* auto generated numbers */
+ {ai_walk, 2.79, widow_step},
+ {ai_walk, 2.77, NULL},
+ {ai_walk, 3.53, NULL},
+ {ai_walk, 3.97, NULL},
+ {ai_walk, 4.13, NULL}, /* 5 */
+ {ai_walk, 4.09, NULL},
+ {ai_walk, 3.84, NULL},
+ {ai_walk, 3.62, widow_step},
+ {ai_walk, 3.29, NULL},
+ {ai_walk, 6.08, NULL}, /* 10 */
+ {ai_walk, 6.94, NULL},
+ {ai_walk, 5.73, NULL},
+ {ai_walk, 2.85, NULL}
+};
+
+mmove_t widow_move_walk = {
+ FRAME_walk01,
+ FRAME_walk13,
+ widow_frames_walk,
+ NULL
+};
+
+mframe_t widow_frames_run[] = {
+ {ai_run, 2.79, widow_step},
+ {ai_run, 2.77, NULL},
+ {ai_run, 3.53, NULL},
+ {ai_run, 3.97, NULL},
+ {ai_run, 4.13, NULL}, /* 5 */
+ {ai_run, 4.09, NULL},
+ {ai_run, 3.84, NULL},
+ {ai_run, 3.62, widow_step},
+ {ai_run, 3.29, NULL},
+ {ai_run, 6.08, NULL}, /* 10 */
+ {ai_run, 6.94, NULL},
+ {ai_run, 5.73, NULL},
+ {ai_run, 2.85, NULL}
+};
+
+mmove_t widow_move_run = {
+ FRAME_walk01,
+ FRAME_walk13,
+ widow_frames_run,
+ NULL
+};
+
+void
+widow_stepshoot(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, gi.soundindex("widow/bwstep2.wav"), 1, ATTN_NORM, 0);
+ WidowBlaster(self);
+}
+
+mframe_t widow_frames_run_attack[] = {
+ {ai_charge, 13, widow_stepshoot},
+ {ai_charge, 11.72, WidowBlaster},
+ {ai_charge, 18.04, WidowBlaster},
+ {ai_charge, 14.58, WidowBlaster},
+ {ai_charge, 13, widow_stepshoot}, /* 5 */
+ {ai_charge, 12.12, WidowBlaster},
+ {ai_charge, 19.63, WidowBlaster},
+ {ai_charge, 11.37, WidowBlaster}
+};
+
+mmove_t widow_move_run_attack = {
+ FRAME_run01,
+ FRAME_run08,
+ widow_frames_run_attack,
+ widow_run
+};
+
+/* These three allow specific entry into the run sequence */
+
+void
+widow_start_run_5(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow_move_run;
+ self->monsterinfo.nextframe = FRAME_walk05;
+}
+
+void
+widow_start_run_10(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow_move_run;
+ self->monsterinfo.nextframe = FRAME_walk10;
+}
+
+void
+widow_start_run_12(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow_move_run;
+ self->monsterinfo.nextframe = FRAME_walk12;
+}
+
+mframe_t widow_frames_attack_pre_blaster[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_attack_blaster}
+};
+
+mmove_t widow_move_attack_pre_blaster = {
+ FRAME_fired01,
+ FRAME_fired02a,
+ widow_frames_attack_pre_blaster,
+ NULL
+};
+
+mframe_t widow_frames_attack_blaster[] = {
+ {ai_charge, 0, widow_reattack_blaster}, /* straight ahead */
+ {ai_charge, 0, widow_reattack_blaster}, /* 100 degrees right */
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster}, /* 50 degrees right */
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster}, /* straight */
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster}, /* 50 degrees left */
+ {ai_charge, 0, widow_reattack_blaster},
+ {ai_charge, 0, widow_reattack_blaster} /* 70 degrees left */
+};
+
+mmove_t widow_move_attack_blaster = {
+ FRAME_fired02a,
+ FRAME_fired20,
+ widow_frames_attack_blaster,
+ NULL
+};
+
+mframe_t widow_frames_attack_post_blaster[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t widow_move_attack_post_blaster = {
+ FRAME_fired21,
+ FRAME_fired22,
+ widow_frames_attack_post_blaster,
+ widow_run
+};
+
+mframe_t widow_frames_attack_post_blaster_r[] = {
+ {ai_charge, -2, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_start_run_12}
+};
+
+mmove_t widow_move_attack_post_blaster_r = {
+ FRAME_transa01,
+ FRAME_transa05,
+ widow_frames_attack_post_blaster_r,
+ NULL
+};
+
+mframe_t widow_frames_attack_post_blaster_l[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 14, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, 10, NULL},
+ {ai_charge, 10, widow_start_run_12}
+};
+
+mmove_t widow_move_attack_post_blaster_l = {
+ FRAME_transb01,
+ FRAME_transb05,
+ widow_frames_attack_post_blaster_l,
+ NULL
+};
+
+void
+WidowRail(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+ int flash = 0;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+
+ if (self->monsterinfo.currentmove == &widow_move_attack_rail)
+ {
+ flash = MZ2_WIDOW_RAIL;
+ }
+ else if (self->monsterinfo.currentmove == &widow_move_attack_rail_l)
+ {
+ flash = MZ2_WIDOW_RAIL_LEFT;
+ }
+ else if (self->monsterinfo.currentmove == &widow_move_attack_rail_r)
+ {
+ flash = MZ2_WIDOW_RAIL_RIGHT;
+ }
+
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash], forward,
+ right, start);
+
+ /* calc direction to where we targeted */
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_railgun(self, start, dir, WIDOW_RAIL_DAMAGE * widow_damage_multiplier,
+ 100, flash);
+ self->timestamp = level.time + RAIL_TIME;
+}
+
+void
+WidowSaveLoc(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+}
+
+void
+widow_start_rail(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+}
+
+void
+widow_rail_done(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+}
+
+mframe_t widow_frames_attack_pre_rail[] = {
+ {ai_charge, 0, widow_start_rail},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_attack_rail}
+};
+
+mmove_t widow_move_attack_pre_rail = {
+ FRAME_transc01,
+ FRAME_transc04,
+ widow_frames_attack_pre_rail,
+ NULL
+};
+
+mframe_t widow_frames_attack_rail[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, WidowSaveLoc},
+ {ai_charge, -10, WidowRail},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_rail_done}
+};
+
+mmove_t widow_move_attack_rail = {
+ FRAME_firea01,
+ FRAME_firea09,
+ widow_frames_attack_rail,
+ widow_run
+};
+
+mframe_t widow_frames_attack_rail_r[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, WidowSaveLoc},
+ {ai_charge, -10, WidowRail},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_rail_done}
+};
+mmove_t widow_move_attack_rail_r = {
+ FRAME_fireb01,
+ FRAME_fireb09,
+ widow_frames_attack_rail_r,
+ widow_run
+};
+
+mframe_t widow_frames_attack_rail_l[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, WidowSaveLoc},
+ {ai_charge, -10, WidowRail},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_rail_done}
+};
+
+mmove_t widow_move_attack_rail_l = {
+ FRAME_firec01,
+ FRAME_firec09,
+ widow_frames_attack_rail_l,
+ widow_run
+};
+
+void
+widow_attack_rail(edict_t *self)
+{
+ float enemy_angle;
+
+ if (!self)
+ {
+ return;
+ }
+
+ enemy_angle = target_angle(self);
+
+ if (enemy_angle < -15)
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_rail_l;
+ }
+ else if (enemy_angle > 15)
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_rail_r;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_rail;
+ }
+}
+
+void
+widow_start_spawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+}
+
+void
+widow_done_spawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+}
+
+mframe_t widow_frames_spawn[] = {
+ {ai_charge, 0, NULL}, /* 1 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_start_spawn},
+ {ai_charge, 0, NULL}, /* 5 */
+ {ai_charge, 0, WidowBlaster}, /* 6 */
+ {ai_charge, 0, widow_ready_spawn}, /* 7 */
+ {ai_charge, 0, WidowBlaster},
+ {ai_charge, 0, WidowBlaster}, /* 9 */
+ {ai_charge, 0, widow_spawn_check},
+ {ai_charge, 0, WidowBlaster}, /* 11 */
+ {ai_charge, 0, WidowBlaster},
+ {ai_charge, 0, WidowBlaster}, /* 13 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_done_spawn}
+};
+
+mmove_t widow_move_spawn = {
+ FRAME_spawn01,
+ FRAME_spawn18,
+ widow_frames_spawn,
+ widow_run
+};
+
+mframe_t widow_frames_pain_heavy[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t widow_move_pain_heavy = {
+ FRAME_pain01,
+ FRAME_pain13,
+ widow_frames_pain_heavy,
+ widow_run
+};
+
+mframe_t widow_frames_pain_light[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t widow_move_pain_light = {
+ FRAME_pain201,
+ FRAME_pain203,
+ widow_frames_pain_light,
+ widow_run
+};
+
+void
+spawn_out_start(edict_t *self)
+{
+ vec3_t startpoint, f, r, u;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->wait = level.time + 2.0;
+
+ AngleVectors(self->s.angles, f, r, u);
+
+ G_ProjectSource2(self->s.origin, beameffects[0], f, r, u, startpoint);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WIDOWBEAMOUT);
+ gi.WriteShort(20001);
+ gi.WritePosition(startpoint);
+ gi.multicast(startpoint, MULTICAST_ALL);
+
+ G_ProjectSource2(self->s.origin, beameffects[1], f, r, u, startpoint);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WIDOWBEAMOUT);
+ gi.WriteShort(20002);
+ gi.WritePosition(startpoint);
+ gi.multicast(startpoint, MULTICAST_ALL);
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/bwidowbeamout.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+spawn_out_do(edict_t *self)
+{
+ vec3_t startpoint, f, r, u;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, beameffects[0], f, r, u, startpoint);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WIDOWSPLASH);
+ gi.WritePosition(startpoint);
+ gi.multicast(startpoint, MULTICAST_ALL);
+
+ G_ProjectSource2(self->s.origin, beameffects[1], f, r, u, startpoint);
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WIDOWSPLASH);
+ gi.WritePosition(startpoint);
+ gi.multicast(startpoint, MULTICAST_ALL);
+
+ VectorCopy(self->s.origin, startpoint);
+ startpoint[2] += 36;
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BOSSTPORT);
+ gi.WritePosition(startpoint);
+ gi.multicast(startpoint, MULTICAST_PVS);
+
+ Widowlegs_Spawn(self->s.origin, self->s.angles);
+
+ G_FreeEdict(self);
+}
+
+mframe_t widow_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 5 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, spawn_out_start}, /* 10 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 15 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 20 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 25 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 30 */
+ {ai_move, 0, spawn_out_do}
+};
+
+mmove_t widow_move_death = {
+ FRAME_death01,
+ FRAME_death31,
+ widow_frames_death,
+ NULL
+};
+
+void
+widow_attack_kick(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, 100, 0, 4);
+
+ if (self->enemy->groundentity)
+ {
+ fire_hit(self, aim, (50 + (rand() % 6)), 500);
+ }
+ else /* not as much kick if they're in the air .. makes it harder to land on her head */
+ {
+ fire_hit(self, aim, (50 + (rand() % 6)), 250);
+ }
+}
+
+mframe_t widow_frames_attack_kick[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, widow_attack_kick},
+ {ai_move, 0, NULL}, /* 5 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t widow_move_attack_kick = {
+ FRAME_kick01,
+ FRAME_kick08,
+ widow_frames_attack_kick,
+ widow_run
+};
+
+void
+widow_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, gi.soundindex("widow/laugh.wav"), 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &widow_move_stand;
+}
+
+void
+widow_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &widow_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow_move_run;
+ }
+}
+
+void
+widow_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow_move_walk;
+}
+
+void
+widow_attack(edict_t *self)
+{
+ float luck;
+ qboolean rail_frames = false, blaster_frames = false, blocked = false, anger = false;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetarget = NULL;
+
+ if (self->monsterinfo.aiflags & AI_BLOCKED)
+ {
+ blocked = true;
+ self->monsterinfo.aiflags &= ~AI_BLOCKED;
+ }
+
+ if (self->monsterinfo.aiflags & AI_TARGET_ANGER)
+ {
+ anger = true;
+ self->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
+ }
+
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ return;
+ }
+
+ if (self->bad_area)
+ {
+ if ((random() < 0.1) || (level.time < self->timestamp))
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+ }
+
+ return;
+ }
+
+ if ((self->s.frame == FRAME_walk13) ||
+ ((self->s.frame >= FRAME_walk01) && (self->s.frame <= FRAME_walk03)))
+ {
+ rail_frames = true;
+ }
+
+ if ((self->s.frame >= FRAME_walk09) && (self->s.frame <= FRAME_walk12))
+ {
+ blaster_frames = true;
+ }
+
+ WidowCalcSlots(self);
+
+ /* if we can't see the target, spawn stuff regardless of frame */
+ if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2))
+ {
+ self->monsterinfo.currentmove = &widow_move_spawn;
+ return;
+ }
+
+ /* accept bias towards spawning regardless of frame */
+ if (blocked && (SELF_SLOTS_LEFT >= 2))
+ {
+ self->monsterinfo.currentmove = &widow_move_spawn;
+ return;
+ }
+
+ if ((realrange(self, self->enemy) > 300) && (!anger) && (random() < 0.5) &&
+ (!blocked))
+ {
+ self->monsterinfo.currentmove = &widow_move_run_attack;
+ return;
+ }
+
+ if (blaster_frames)
+ {
+ if (SELF_SLOTS_LEFT >= 2)
+ {
+ self->monsterinfo.currentmove = &widow_move_spawn;
+ return;
+ }
+ else if (self->monsterinfo.pausetime + BLASTER_TIME <= level.time)
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+ return;
+ }
+ }
+
+ if (rail_frames)
+ {
+ if (!(level.time < self->timestamp))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+ }
+ }
+
+ if ((rail_frames) || (blaster_frames))
+ {
+ return;
+ }
+
+ luck = random();
+
+ if (SELF_SLOTS_LEFT >= 2)
+ {
+ if ((luck <= 0.40) && (self->monsterinfo.pausetime + BLASTER_TIME <= level.time))
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+ }
+ else if ((luck <= 0.7) && !(level.time < self->timestamp))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow_move_spawn;
+ }
+ }
+ else
+ {
+ if (level.time < self->timestamp)
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+ }
+ else if ((luck <= 0.50) || (level.time + BLASTER_TIME >= self->monsterinfo.pausetime))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+ }
+ else /* holdout to blaster */
+ {
+ self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+ }
+ }
+}
+
+void
+widow_attack_blaster(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.pausetime = level.time + 1.0 + (2.0 * random());
+ self->monsterinfo.currentmove = &widow_move_attack_blaster;
+ self->monsterinfo.nextframe = WidowTorso(self);
+}
+
+void
+widow_reattack_blaster(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ WidowBlaster(self);
+
+ if ((self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l) ||
+ (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r))
+ {
+ return;
+ }
+
+ /* if we're not done with the attack, don't leave the sequence */
+ if (self->monsterinfo.pausetime >= level.time)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
+}
+
+void
+widow_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.pausetime == 100000000)
+ {
+ self->monsterinfo.pausetime = 0;
+ }
+
+ self->pain_debounce_time = level.time + 5;
+
+ if (damage < 15)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+ }
+ else if (damage < 75)
+ {
+ if ((skill->value < SKILL_HARDPLUS) && (random() < (0.6 - (0.2 * ((float)skill->value)))))
+ {
+ self->monsterinfo.currentmove = &widow_move_pain_light;
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+ }
+ else
+ {
+ if ((skill->value < SKILL_HARDPLUS) && (random() < (0.75 - (0.1 * ((float)skill->value)))))
+ {
+ self->monsterinfo.currentmove = &widow_move_pain_heavy;
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+ }
+}
+
+void
+widow_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -56, -56, 0);
+ VectorSet(self->maxs, 56, 56, 80);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+widow_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ self->monsterinfo.quad_framenum = 0;
+ self->monsterinfo.double_framenum = 0;
+ self->monsterinfo.invincible_framenum = 0;
+ self->monsterinfo.currentmove = &widow_move_death;
+}
+
+void
+widow_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow_move_attack_kick;
+}
+
+void
+WidowGoinQuad(edict_t *self, float framenum)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.quad_framenum = framenum;
+ widow_damage_multiplier = 4;
+}
+
+void
+WidowDouble(edict_t *self, float framenum)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.double_framenum = framenum;
+ widow_damage_multiplier = 2;
+}
+
+void
+WidowPent(edict_t *self, float framenum)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.invincible_framenum = framenum;
+}
+
+void
+WidowPowerArmor(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+
+ /* I don't like this, but it works */
+ if (self->monsterinfo.power_armor_power <= 0)
+ {
+ self->monsterinfo.power_armor_power += 250 * skill->value;
+ }
+}
+
+void
+WidowRespondPowerup(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->s.effects & EF_QUAD)
+ {
+ if (skill->value == SKILL_MEDIUM)
+ {
+ WidowDouble(self, other->client->quad_framenum);
+ }
+ else if (skill->value == SKILL_HARD)
+ {
+ WidowGoinQuad(self, other->client->quad_framenum);
+ }
+ else if (skill->value == SKILL_HARDPLUS)
+ {
+ WidowGoinQuad(self, other->client->quad_framenum);
+ WidowPowerArmor(self);
+ }
+ }
+ else if (other->s.effects & EF_DOUBLE)
+ {
+ if (skill->value == SKILL_HARD)
+ {
+ WidowDouble(self, other->client->double_framenum);
+ }
+ else if (skill->value == SKILL_HARDPLUS)
+ {
+ WidowDouble(self, other->client->double_framenum);
+ WidowPowerArmor(self);
+ }
+ }
+ else
+ {
+ widow_damage_multiplier = 1;
+ }
+
+ if (other->s.effects & EF_PENT)
+ {
+ if (skill->value == SKILL_MEDIUM)
+ {
+ WidowPowerArmor(self);
+ }
+ else if (skill->value == SKILL_HARD)
+ {
+ WidowPent(self, other->client->invincible_framenum);
+ }
+ else if (skill->value == SKILL_HARDPLUS)
+ {
+ WidowPent(self, other->client->invincible_framenum);
+ WidowPowerArmor(self);
+ }
+ }
+}
+
+void
+WidowPowerups(edict_t *self)
+{
+ int player;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(coop && coop->value))
+ {
+ WidowRespondPowerup(self, self->enemy);
+ }
+ else
+ {
+ /* in coop, check for pents, then quads, then doubles */
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (ent->s.effects & EF_PENT)
+ {
+ WidowRespondPowerup(self, ent);
+ return;
+ }
+ }
+
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (ent->s.effects & EF_QUAD)
+ {
+ WidowRespondPowerup(self, ent);
+ return;
+ }
+ }
+
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (ent->s.effects & EF_DOUBLE)
+ {
+ WidowRespondPowerup(self, ent);
+ return;
+ }
+ }
+ }
+}
+
+qboolean
+Widow_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance = 0;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+ float real_enemy_range;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ WidowPowerups(self);
+
+ if (self->monsterinfo.currentmove == &widow_move_run)
+ {
+ /* if we're in run, make sure we're in a good frame for attacking before doing anything else */
+ switch (self->s.frame)
+ {
+ case FRAME_walk04:
+ case FRAME_walk05:
+ case FRAME_walk06:
+ case FRAME_walk07:
+ case FRAME_walk08:
+ case FRAME_walk12:
+ {
+ return false;
+ }
+ default:
+ break;
+ }
+ }
+
+ /* give a LARGE bias to spawning things when we have room
+ use AI_BLOCKED as a signal to attack to spawn */
+ if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) &&
+ (realrange(self, self->enemy) > 150))
+ {
+ self->monsterinfo.aiflags |= AI_BLOCKED;
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ /* go ahead and spawn stuff if we're mad a a client */
+ if (self->enemy->client && (SELF_SLOTS_LEFT >= 2))
+ {
+ self->monsterinfo.attack_state = AS_BLIND;
+ return true;
+ }
+
+ /* PGM - we want them to go ahead and shoot at info_notnulls if they can. */
+ if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0))
+ {
+ return false;
+ }
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw2(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ real_enemy_range = realrange(self, self->enemy);
+
+ if (real_enemy_range <= (MELEE_DISTANCE + 20))
+ {
+ /* don't always melee in easy mode */
+ if ((skill->value == SKILL_EASY) && (rand() & 3))
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_MELEE)
+ {
+ chance = 0.8;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.7;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.6;
+ }
+ else if (enemy_range == RANGE_FAR)
+ {
+ chance = 0.5;
+ }
+
+ /* go ahead and shoot every time if it's a info_notnull */
+ if ((random() < chance) || (self->enemy->solid == SOLID_NOT))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+widow_blocked(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.currentmove == &widow_move_run_attack)
+ {
+ self->monsterinfo.aiflags |= AI_TARGET_ANGER;
+
+ if (self->monsterinfo.checkattack(self))
+ {
+ self->monsterinfo.attack(self);
+ }
+ else
+ {
+ self->monsterinfo.run(self);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void
+WidowCalcSlots(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ switch ((int)skill->value)
+ {
+ case SKILL_EASY:
+ case SKILL_MEDIUM:
+ self->monsterinfo.monster_slots = 3;
+ break;
+ case SKILL_HARD:
+ self->monsterinfo.monster_slots = 4;
+ break;
+ case SKILL_HARDPLUS:
+ self->monsterinfo.monster_slots = 6;
+ break;
+ default:
+ self->monsterinfo.monster_slots = 3;
+ break;
+ }
+
+ if (coop->value)
+ {
+ self->monsterinfo.monster_slots = min(6, self->monsterinfo.monster_slots + ((skill->value) * (CountPlayers() - 1)));
+ }
+}
+
+void
+WidowPrecache()
+{
+ /* cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs */
+ gi.soundindex("stalker/pain.wav");
+ gi.soundindex("stalker/death.wav");
+ gi.soundindex("stalker/sight.wav");
+ gi.soundindex("stalker/melee1.wav");
+ gi.soundindex("stalker/melee2.wav");
+ gi.soundindex("stalker/idle.wav");
+
+ gi.soundindex("tank/tnkatck3.wav");
+ gi.modelindex("models/proj/laser2/tris.md2");
+
+ gi.modelindex("models/monsters/stalker/tris.md2");
+ gi.modelindex("models/items/spawngro2/tris.md2");
+ gi.modelindex("models/objects/gibs/sm_metal/tris.md2");
+ gi.modelindex("models/objects/gibs/gear/tris.md2");
+ gi.modelindex("models/monsters/blackwidow/gib1/tris.md2");
+ gi.modelindex("models/monsters/blackwidow/gib2/tris.md2");
+ gi.modelindex("models/monsters/blackwidow/gib3/tris.md2");
+ gi.modelindex("models/monsters/blackwidow/gib4/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib4/tris.md2");
+ gi.modelindex("models/monsters/legs/tris.md2");
+ gi.soundindex("misc/bwidowbeamout.wav");
+
+ gi.soundindex("misc/bigtele.wav");
+ gi.soundindex("widow/bwstep3.wav");
+ gi.soundindex("widow/bwstep2.wav");
+}
+
+/*
+ * QUAKED monster_widow (1 .5 0) (-40 -40 0) (40 40 144) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_widow(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("widow/bw1pain1.wav");
+ sound_pain2 = gi.soundindex("widow/bw1pain2.wav");
+ sound_pain3 = gi.soundindex("widow/bw1pain3.wav");
+ sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav");
+ sound_rail = gi.soundindex("gladiator/railgun.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/blackwidow/tris.md2");
+ VectorSet(self->mins, -40, -40, 0);
+ VectorSet(self->maxs, 40, 40, 144);
+
+ self->health = 2000 + 1000 * (skill->value);
+
+ if (coop->value)
+ {
+ self->health += 500 * (skill->value);
+ }
+
+ self->gib_health = -5000;
+ self->mass = 1500;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+ self->monsterinfo.power_armor_power = 500;
+ }
+
+ self->yaw_speed = 30;
+
+ self->flags |= FL_IMMUNE_LASER;
+ self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+ self->pain = widow_pain;
+ self->die = widow_die;
+
+ self->monsterinfo.melee = widow_melee;
+ self->monsterinfo.stand = widow_stand;
+ self->monsterinfo.walk = widow_walk;
+ self->monsterinfo.run = widow_run;
+ self->monsterinfo.attack = widow_attack;
+ self->monsterinfo.search = widow_search;
+ self->monsterinfo.checkattack = Widow_CheckAttack;
+ self->monsterinfo.sight = widow_sight;
+
+ self->monsterinfo.blocked = widow_blocked;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &widow_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ WidowPrecache();
+ WidowCalcSlots(self);
+ widow_damage_multiplier = 1;
+
+ walkmonster_start(self);
+}
diff --git a/rogue/src/monster/widow/widow.h b/rogue/src/monster/widow/widow.h
new file mode 100644
index 0000000..529efb0
--- /dev/null
+++ b/rogue/src/monster/widow/widow.h
@@ -0,0 +1,177 @@
+/* =======================================================================
+ *
+ * Black Widow (stage 1) animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_idle01 0
+#define FRAME_idle02 1
+#define FRAME_idle03 2
+#define FRAME_idle04 3
+#define FRAME_idle05 4
+#define FRAME_idle06 5
+#define FRAME_idle07 6
+#define FRAME_idle08 7
+#define FRAME_idle09 8
+#define FRAME_idle10 9
+#define FRAME_idle11 10
+#define FRAME_walk01 11
+#define FRAME_walk02 12
+#define FRAME_walk03 13
+#define FRAME_walk04 14
+#define FRAME_walk05 15
+#define FRAME_walk06 16
+#define FRAME_walk07 17
+#define FRAME_walk08 18
+#define FRAME_walk09 19
+#define FRAME_walk10 20
+#define FRAME_walk11 21
+#define FRAME_walk12 22
+#define FRAME_walk13 23
+#define FRAME_run01 24
+#define FRAME_run02 25
+#define FRAME_run03 26
+#define FRAME_run04 27
+#define FRAME_run05 28
+#define FRAME_run06 29
+#define FRAME_run07 30
+#define FRAME_run08 31
+#define FRAME_firea01 32
+#define FRAME_firea02 33
+#define FRAME_firea03 34
+#define FRAME_firea04 35
+#define FRAME_firea05 36
+#define FRAME_firea06 37
+#define FRAME_firea07 38
+#define FRAME_firea08 39
+#define FRAME_firea09 40
+#define FRAME_fireb01 41
+#define FRAME_fireb02 42
+#define FRAME_fireb03 43
+#define FRAME_fireb04 44
+#define FRAME_fireb05 45
+#define FRAME_fireb06 46
+#define FRAME_fireb07 47
+#define FRAME_fireb08 48
+#define FRAME_fireb09 49
+#define FRAME_firec01 50
+#define FRAME_firec02 51
+#define FRAME_firec03 52
+#define FRAME_firec04 53
+#define FRAME_firec05 54
+#define FRAME_firec06 55
+#define FRAME_firec07 56
+#define FRAME_firec08 57
+#define FRAME_firec09 58
+#define FRAME_fired01 59
+#define FRAME_fired02 60
+#define FRAME_fired02a 61
+#define FRAME_fired03 62
+#define FRAME_fired04 63
+#define FRAME_fired05 64
+#define FRAME_fired06 65
+#define FRAME_fired07 66
+#define FRAME_fired08 67
+#define FRAME_fired09 68
+#define FRAME_fired10 69
+#define FRAME_fired11 70
+#define FRAME_fired12 71
+#define FRAME_fired13 72
+#define FRAME_fired14 73
+#define FRAME_fired15 74
+#define FRAME_fired16 75
+#define FRAME_fired17 76
+#define FRAME_fired18 77
+#define FRAME_fired19 78
+#define FRAME_fired20 79
+#define FRAME_fired21 80
+#define FRAME_fired22 81
+#define FRAME_spawn01 82
+#define FRAME_spawn02 83
+#define FRAME_spawn03 84
+#define FRAME_spawn04 85
+#define FRAME_spawn05 86
+#define FRAME_spawn06 87
+#define FRAME_spawn07 88
+#define FRAME_spawn08 89
+#define FRAME_spawn09 90
+#define FRAME_spawn10 91
+#define FRAME_spawn11 92
+#define FRAME_spawn12 93
+#define FRAME_spawn13 94
+#define FRAME_spawn14 95
+#define FRAME_spawn15 96
+#define FRAME_spawn16 97
+#define FRAME_spawn17 98
+#define FRAME_spawn18 99
+#define FRAME_pain01 100
+#define FRAME_pain02 101
+#define FRAME_pain03 102
+#define FRAME_pain04 103
+#define FRAME_pain05 104
+#define FRAME_pain06 105
+#define FRAME_pain07 106
+#define FRAME_pain08 107
+#define FRAME_pain09 108
+#define FRAME_pain10 109
+#define FRAME_pain11 110
+#define FRAME_pain12 111
+#define FRAME_pain13 112
+#define FRAME_pain201 113
+#define FRAME_pain202 114
+#define FRAME_pain203 115
+#define FRAME_transa01 116
+#define FRAME_transa02 117
+#define FRAME_transa03 118
+#define FRAME_transa04 119
+#define FRAME_transa05 120
+#define FRAME_transb01 121
+#define FRAME_transb02 122
+#define FRAME_transb03 123
+#define FRAME_transb04 124
+#define FRAME_transb05 125
+#define FRAME_transc01 126
+#define FRAME_transc02 127
+#define FRAME_transc03 128
+#define FRAME_transc04 129
+#define FRAME_death01 130
+#define FRAME_death02 131
+#define FRAME_death03 132
+#define FRAME_death04 133
+#define FRAME_death05 134
+#define FRAME_death06 135
+#define FRAME_death07 136
+#define FRAME_death08 137
+#define FRAME_death09 138
+#define FRAME_death10 139
+#define FRAME_death11 140
+#define FRAME_death12 141
+#define FRAME_death13 142
+#define FRAME_death14 143
+#define FRAME_death15 144
+#define FRAME_death16 145
+#define FRAME_death17 146
+#define FRAME_death18 147
+#define FRAME_death19 148
+#define FRAME_death20 149
+#define FRAME_death21 150
+#define FRAME_death22 151
+#define FRAME_death23 152
+#define FRAME_death24 153
+#define FRAME_death25 154
+#define FRAME_death26 155
+#define FRAME_death27 156
+#define FRAME_death28 157
+#define FRAME_death29 158
+#define FRAME_death30 159
+#define FRAME_death31 160
+#define FRAME_kick01 161
+#define FRAME_kick02 162
+#define FRAME_kick03 163
+#define FRAME_kick04 164
+#define FRAME_kick05 165
+#define FRAME_kick06 166
+#define FRAME_kick07 167
+#define FRAME_kick08 168
+#define MODEL_SCALE 2.000000
diff --git a/rogue/src/monster/widow/widow2.c b/rogue/src/monster/widow/widow2.c
new file mode 100644
index 0000000..f9eac96
--- /dev/null
+++ b/rogue/src/monster/widow/widow2.c
@@ -0,0 +1,2272 @@
+/* =======================================================================
+ *
+ * Black Window (stage 2).
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "widow2.h"
+
+#define NUM_STALKERS_SPAWNED 6 /* max # of stalkers she can spawn */
+#define DISRUPT_TIME 3
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_death;
+static int sound_search1;
+static int sound_tentacles_retract;
+
+static vec3_t spawnpoints[] = {
+ {30, 135, 0},
+ {30, -135, 0}
+};
+
+static float sweep_angles[] = {
+ -40.0, -32.0, -24.0, -16.0, -8.0, 0.0, 8.0, 16.0, 24.0, 32.0, 40.0
+};
+
+extern vec3_t stalker_mins, stalker_maxs;
+
+qboolean infront(edict_t *self, edict_t *other);
+void WidowCalcSlots(edict_t *self);
+void WidowPowerups(edict_t *self);
+
+void widow2_run(edict_t *self);
+void widow2_stand(edict_t *self);
+void widow2_dead(edict_t *self);
+void widow2_attack(edict_t *self);
+void widow2_attack_beam(edict_t *self);
+void widow2_reattack_beam(edict_t *self);
+void widow2_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+void widow_start_spawn(edict_t *self);
+void widow_done_spawn(edict_t *self);
+void widow2_spawn_check(edict_t *self);
+void widow2_prep_spawn(edict_t *self);
+void Widow2SaveBeamTarget(edict_t *self);
+void widow2_start_searching(edict_t *self);
+void widow2_keep_searching(edict_t *self);
+void widow2_finaldeath(edict_t *self);
+
+void WidowExplode(edict_t *self);
+void gib_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+void gib_touch(edict_t *self, edict_t *other, cplane_t *plane,
+ csurface_t *surf);
+void ThrowWidowGibReal(edict_t *self, char *gibname, int damage, int type,
+ vec3_t startpos, qboolean large, int hitsound, qboolean fade);
+void ThrowWidowGibSized(edict_t *self, char *gibname, int damage, int type,
+ vec3_t startpos, int hitsound, qboolean fade);
+void ThrowWidowGibLoc(edict_t *self, char *gibname, int damage, int type,
+ vec3_t startpos, qboolean fade);
+void WidowExplosion1(edict_t *self);
+void WidowExplosion2(edict_t *self);
+void WidowExplosion3(edict_t *self);
+void WidowExplosion4(edict_t *self);
+void WidowExplosion5(edict_t *self);
+void WidowExplosion6(edict_t *self);
+void WidowExplosion7(edict_t *self);
+void WidowExplosionLeg(edict_t *self);
+void ThrowArm1(edict_t *self);
+void ThrowArm2(edict_t *self);
+void ClipGibVelocity(edict_t *ent);
+void showme(edict_t *self);
+
+/* these offsets used by the tongue */
+static vec3_t offsets[] = {
+ {17.48, 0.10, 68.92},
+ {17.47, 0.29, 68.91},
+ {17.45, 0.53, 68.87},
+ {17.42, 0.78, 68.81},
+ {17.39, 1.02, 68.75},
+ {17.37, 1.20, 68.70},
+ {17.36, 1.24, 68.71},
+ {17.37, 1.21, 68.72},
+};
+
+void
+pauseme(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void
+widow2_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+ }
+}
+
+void
+Widow2Beam(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start, targ_angles, vec;
+ int flashnum;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+
+ if ((self->s.frame >= FRAME_fireb05) && (self->s.frame <= FRAME_fireb09))
+ {
+ /* regular beam attack */
+ Widow2SaveBeamTarget(self);
+ flashnum = MZ2_WIDOW2_BEAMER_1 + self->s.frame - FRAME_fireb05;
+ G_ProjectSource(self->s.origin, monster_flash_offset[flashnum], forward,
+ right, start);
+ VectorCopy(self->pos2, target);
+ target[2] += self->enemy->viewheight - 10;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+ monster_fire_heat(self, start, forward, vec3_origin, 10, 50, flashnum);
+ }
+ else if ((self->s.frame >= FRAME_spawn04) && (self->s.frame <= FRAME_spawn14))
+ {
+ /* sweep */
+ flashnum = MZ2_WIDOW2_BEAM_SWEEP_1 + self->s.frame - FRAME_spawn04;
+ G_ProjectSource(self->s.origin, monster_flash_offset[flashnum],
+ forward, right, start);
+ VectorSubtract(self->enemy->s.origin, start, target);
+ vectoangles2(target, targ_angles);
+
+ VectorCopy(self->s.angles, vec);
+
+ vec[PITCH] += targ_angles[PITCH];
+ vec[YAW] -= sweep_angles[flashnum - MZ2_WIDOW2_BEAM_SWEEP_1];
+
+ AngleVectors(vec, forward, NULL, NULL);
+ monster_fire_heat(self, start, forward, vec3_origin, 10, 50, flashnum);
+ }
+ else
+ {
+ Widow2SaveBeamTarget(self);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW2_BEAMER_1],
+ forward, right, start);
+
+ VectorCopy(self->pos2, target);
+ target[2] += self->enemy->viewheight - 10;
+
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_heat(self, start, forward, vec3_origin, 10, 50, 0);
+ }
+}
+
+void
+Widow2Spawn(edict_t *self)
+{
+ vec3_t f, r, u, offset, startpoint, spawnpoint;
+ edict_t *ent, *designated_enemy;
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+
+ for (i = 0; i < 2; i++)
+ {
+ VectorCopy(spawnpoints[i], offset);
+
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
+ {
+ ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins,
+ stalker_maxs, "monster_stalker", 256);
+
+ if (!ent)
+ {
+ continue;
+ }
+
+ self->monsterinfo.monster_used++;
+ ent->monsterinfo.commander = self;
+
+ ent->nextthink = level.time;
+ ent->think(ent);
+
+ ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW | AI_DO_NOT_COUNT | AI_IGNORE_SHOTS;
+
+ if (!(coop && coop->value))
+ {
+ designated_enemy = self->enemy;
+ }
+ else
+ {
+ designated_enemy = PickCoopTarget(ent);
+
+ if (designated_enemy)
+ {
+ /* try to avoid using my enemy */
+ if (designated_enemy == self->enemy)
+ {
+ designated_enemy = PickCoopTarget(ent);
+
+ if (designated_enemy)
+ {
+ }
+ else
+ {
+ designated_enemy = self->enemy;
+ }
+ }
+ }
+ else
+ {
+ designated_enemy = self->enemy;
+ }
+ }
+
+ if ((designated_enemy->inuse) && (designated_enemy->health > 0))
+ {
+ ent->enemy = designated_enemy;
+ FoundTarget(ent);
+ ent->monsterinfo.attack(ent);
+ }
+ }
+ }
+}
+
+void
+widow2_spawn_check(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Widow2Beam(self);
+ Widow2Spawn(self);
+}
+
+void
+widow2_ready_spawn(edict_t *self)
+{
+ vec3_t f, r, u, offset, startpoint, spawnpoint;
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ Widow2Beam(self);
+ AngleVectors(self->s.angles, f, r, u);
+
+ for (i = 0; i < 2; i++)
+ {
+ VectorCopy(spawnpoints[i], offset);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
+ {
+ SpawnGrow_Spawn(spawnpoint, 1);
+ }
+ }
+}
+
+mframe_t widow2_frames_stand[] = {
+ {ai_stand, 0, NULL}
+};
+
+mmove_t widow2_move_stand = {
+ FRAME_blackwidow3,
+ FRAME_blackwidow3,
+ widow2_frames_stand,
+ NULL
+};
+
+mframe_t widow2_frames_walk[] = {
+ {ai_walk, 9.01, NULL},
+ {ai_walk, 7.55, NULL},
+ {ai_walk, 7.01, NULL},
+ {ai_walk, 6.66, NULL},
+ {ai_walk, 6.20, NULL},
+ {ai_walk, 5.78, NULL},
+ {ai_walk, 7.25, NULL},
+ {ai_walk, 8.37, NULL},
+ {ai_walk, 10.41, NULL}
+};
+
+mmove_t widow2_move_walk = {
+ FRAME_walk01,
+ FRAME_walk09,
+ widow2_frames_walk,
+ NULL
+};
+
+mframe_t widow2_frames_run[] = {
+ {ai_run, 9.01, NULL},
+ {ai_run, 7.55, NULL},
+ {ai_run, 7.01, NULL},
+ {ai_run, 6.66, NULL},
+ {ai_run, 6.20, NULL},
+ {ai_run, 5.78, NULL},
+ {ai_run, 7.25, NULL},
+ {ai_run, 8.37, NULL},
+ {ai_run, 10.41, NULL}
+};
+
+mmove_t widow2_move_run = {
+ FRAME_walk01,
+ FRAME_walk09,
+ widow2_frames_run,
+ NULL
+};
+
+mframe_t widow2_frames_attack_pre_beam[] = {
+ {ai_charge, 4, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 4, widow2_attack_beam}
+};
+
+mmove_t widow2_move_attack_pre_beam = {
+ FRAME_fireb01,
+ FRAME_fireb04,
+ widow2_frames_attack_pre_beam,
+ NULL
+};
+
+/* Loop this */
+mframe_t widow2_frames_attack_beam[] = {
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, widow2_reattack_beam}
+};
+
+mmove_t widow2_move_attack_beam = {
+ FRAME_fireb05,
+ FRAME_fireb09,
+ widow2_frames_attack_beam,
+ NULL
+};
+
+mframe_t widow2_frames_attack_post_beam[] = {
+ {ai_charge, 4, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 4, NULL}
+};
+
+mmove_t widow2_move_attack_post_beam = {
+ FRAME_fireb06,
+ FRAME_fireb07,
+ widow2_frames_attack_post_beam,
+ widow2_run
+};
+
+void
+WidowDisrupt(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+ float len;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_WIDOW_DISRUPTOR],
+ forward, right, start);
+
+ VectorSubtract(self->pos1, self->enemy->s.origin, dir);
+ len = VectorLength(dir);
+
+ if (len < 30)
+ {
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_tracker(self, start, dir, 20, 500, self->enemy, MZ2_WIDOW_DISRUPTOR);
+ }
+ else
+ {
+ PredictAim(self->enemy, start, 1200, true, 0, dir, NULL);
+ monster_fire_tracker(self, start, dir, 20, 1200, NULL, MZ2_WIDOW_DISRUPTOR);
+ }
+}
+
+void
+Widow2SaveDisruptLoc(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy && self->enemy->inuse)
+ {
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+ }
+ else
+ {
+ VectorCopy(vec3_origin, self->pos1);
+ }
+}
+
+void
+widow2_disrupt_reattack(edict_t *self)
+{
+ float luck;
+
+ if (!self)
+ {
+ return;
+ }
+
+ luck = random();
+
+ if (luck < (0.25 + ((float)(skill->value)) * 0.15))
+ {
+ self->monsterinfo.nextframe = FRAME_firea01;
+ }
+}
+
+mframe_t widow2_frames_attack_disrupt[] = {
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, Widow2SaveDisruptLoc},
+ {ai_charge, -20, WidowDisrupt},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 2, widow2_disrupt_reattack}
+};
+
+mmove_t widow2_move_attack_disrupt = {
+ FRAME_firea01,
+ FRAME_firea07,
+ widow2_frames_attack_disrupt,
+ widow2_run
+};
+
+void
+Widow2SaveBeamTarget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy && self->enemy->inuse)
+ {
+ VectorCopy(self->pos1, self->pos2);
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ }
+ else
+ {
+ VectorCopy(vec3_origin, self->pos1);
+ VectorCopy(vec3_origin, self->pos2);
+ }
+}
+
+void
+Widow2BeamTargetRemove(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(vec3_origin, self->pos1);
+ VectorCopy(vec3_origin, self->pos2);
+}
+
+void
+Widow2StartSweep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Widow2SaveBeamTarget(self);
+}
+
+mframe_t widow2_frames_spawn[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow_start_spawn},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam}, /* 5 */
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, widow2_ready_spawn}, /* 10 */
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, Widow2Beam},
+ {ai_charge, 0, widow2_spawn_check},
+ {ai_charge, 0, NULL}, /* 15 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, widow2_reattack_beam}
+};
+
+mmove_t widow2_move_spawn = {
+ FRAME_spawn01,
+ FRAME_spawn18,
+ widow2_frames_spawn,
+ NULL
+};
+
+qboolean
+widow2_tongue_attack_ok(vec3_t start, vec3_t end, float range)
+{
+ vec3_t dir, angles;
+
+ /* check for max distance */
+ VectorSubtract(start, end, dir);
+
+ if (VectorLength(dir) > range)
+ {
+ return false;
+ }
+
+ /* check for min/max pitch */
+ vectoangles(dir, angles);
+
+ if (angles[0] < -180)
+ {
+ angles[0] += 360;
+ }
+
+ if (fabs(angles[0]) > 30)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+Widow2Tongue(edict_t *self)
+{
+ vec3_t f, r, u;
+ vec3_t start, end, dir;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u, start);
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (!widow2_tongue_attack_ok(start, end, 256))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+
+ if (!widow2_tongue_attack_ok(start, end, 256))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+
+ if (!widow2_tongue_attack_ok(start, end, 256))
+ {
+ return;
+ }
+ }
+ }
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
+
+ if (tr.ent != self->enemy)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_PARASITE_ATTACK);
+ gi.WriteShort(self - g_edicts);
+ gi.WritePosition(start);
+ gi.WritePosition(end);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ VectorSubtract(start, end, dir);
+ T_Damage(self->enemy, self, self, dir, self->enemy->s.origin,
+ vec3_origin, 2, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+}
+
+void
+Widow2TonguePull(edict_t *self)
+{
+ vec3_t vec;
+ vec3_t f, r, u;
+ vec3_t start, end;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ self->monsterinfo.run(self);
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offsets[self->s.frame - FRAME_tongs01],
+ f, r, u, start);
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (!widow2_tongue_attack_ok(start, end, 256))
+ {
+ return;
+ }
+
+ if (self->enemy->groundentity)
+ {
+ self->enemy->s.origin[2] += 1;
+ self->enemy->groundentity = NULL;
+ }
+
+ VectorSubtract(self->s.origin, self->enemy->s.origin, vec);
+
+ if (self->enemy->client)
+ {
+ VectorNormalize(vec);
+ VectorMA(self->enemy->velocity, 1000, vec, self->enemy->velocity);
+ }
+ else
+ {
+ self->enemy->ideal_yaw = vectoyaw(vec);
+ M_ChangeYaw(self->enemy);
+ VectorScale(f, 1000, self->enemy->velocity);
+ }
+}
+
+void
+Widow2Crunch(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((!self->enemy) || (!self->enemy->inuse))
+ {
+ self->monsterinfo.run(self);
+ return;
+ }
+
+ Widow2TonguePull(self);
+
+ /* 70 + 32 */
+ VectorSet(aim, 150, 0, 4);
+
+ if (self->s.frame != FRAME_tongs07)
+ {
+ fire_hit(self, aim, 20 + (rand() % 6), 0);
+ }
+ else
+ {
+ if (self->enemy->groundentity)
+ {
+ fire_hit(self, aim, (20 + (rand() % 6)), 500);
+ }
+ else /* not as much kick if they're in the air .. makes it harder to land on her head */
+ {
+ fire_hit(self, aim, (20 + (rand() % 6)), 250);
+ }
+ }
+}
+
+void
+Widow2Toss(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->timestamp = level.time + 3;
+ return;
+}
+
+mframe_t widow2_frames_tongs[] = {
+ {ai_charge, 0, Widow2Tongue},
+ {ai_charge, 0, Widow2Tongue},
+ {ai_charge, 0, Widow2Tongue},
+ {ai_charge, 0, Widow2TonguePull},
+ {ai_charge, 0, Widow2TonguePull}, /* 5 */
+ {ai_charge, 0, Widow2TonguePull},
+ {ai_charge, 0, Widow2Crunch},
+ {ai_charge, 0, Widow2Toss}
+};
+
+mmove_t widow2_move_tongs = {
+ FRAME_tongs01,
+ FRAME_tongs08,
+ widow2_frames_tongs,
+ widow2_run
+};
+
+mframe_t widow2_frames_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t widow2_move_pain = {
+ FRAME_pain01,
+ FRAME_pain05,
+ widow2_frames_pain,
+ widow2_run
+};
+
+mframe_t widow2_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, WidowExplosion1}, /* 3 boom */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 5 */
+
+ {ai_move, 0, WidowExplosion2}, /* 6 boom */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 10 */
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 12 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 15 */
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, WidowExplosion3}, /* 18 */
+ {ai_move, 0, NULL}, /* 19 */
+ {ai_move, 0, NULL}, /* 20 */
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, WidowExplosion4}, /* 25 */
+
+ {ai_move, 0, NULL}, /* 26 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, WidowExplosion5},
+ {ai_move, 0, WidowExplosionLeg}, /* 30 */
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, WidowExplosion6},
+ {ai_move, 0, NULL}, /* 35 */
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, WidowExplosion7},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 40 */
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, WidowExplode} /* 44 */
+};
+
+mmove_t widow2_move_death = {
+ FRAME_death01,
+ FRAME_death44,
+ widow2_frames_death,
+ NULL
+};
+
+mframe_t widow2_frames_dead[] = {
+ {ai_move, 0, widow2_start_searching},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, widow2_keep_searching}
+};
+
+mmove_t widow2_move_dead = {
+ FRAME_dthsrh01,
+ FRAME_dthsrh15,
+ widow2_frames_dead,
+ NULL
+};
+
+mframe_t widow2_frames_really_dead[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, widow2_finaldeath}
+};
+
+mmove_t widow2_move_really_dead = {
+ FRAME_dthsrh16,
+ FRAME_dthsrh22,
+ widow2_frames_really_dead,
+ NULL
+};
+
+void
+widow2_start_searching(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->count = 0;
+}
+
+void
+widow2_keep_searching(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->count <= 2)
+ {
+ self->monsterinfo.currentmove = &widow2_move_dead;
+ self->s.frame = FRAME_dthsrh01;
+ self->count++;
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow2_move_really_dead;
+}
+
+void
+widow2_finaldeath(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -70, -70, 0);
+ VectorSet(self->maxs, 70, 70, 80);
+ self->movetype = MOVETYPE_TOSS;
+ self->takedamage = DAMAGE_YES;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+widow2_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow2_move_stand;
+}
+
+void
+widow2_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &widow2_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_run;
+ }
+}
+
+void
+widow2_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow2_move_walk;
+}
+
+void
+widow2_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow2_move_tongs;
+}
+
+void
+widow2_attack(edict_t *self)
+{
+ float range, luck;
+ qboolean blocked = false;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_BLOCKED)
+ {
+ blocked = true;
+ self->monsterinfo.aiflags &= ~AI_BLOCKED;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (self->bad_area)
+ {
+ if ((random() < 0.75) || (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+ }
+
+ return;
+ }
+
+ WidowCalcSlots(self);
+
+ /* if we can't see the target, spawn stuff */
+ if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2))
+ {
+ self->monsterinfo.currentmove = &widow2_move_spawn;
+ return;
+ }
+
+ /* accept bias towards spawning */
+ if (blocked && (SELF_SLOTS_LEFT >= 2))
+ {
+ self->monsterinfo.currentmove = &widow2_move_spawn;
+ return;
+ }
+
+ range = realrange(self, self->enemy);
+
+ if (range < 600)
+ {
+ luck = random();
+
+ if (SELF_SLOTS_LEFT >= 2)
+ {
+ if (luck <= 0.40)
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+ }
+ else if ((luck <= 0.7) && !(level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_spawn;
+ }
+ }
+ else
+ {
+ if ((luck <= 0.50) || (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+ }
+ }
+ }
+ else
+ {
+ luck = random();
+
+ if (SELF_SLOTS_LEFT >= 2)
+ {
+ if (luck < 0.3)
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+ }
+ else if ((luck < 0.65) || (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &widow2_move_spawn;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+ }
+ }
+ else
+ {
+ if ((luck < 0.45) || (level.time < self->monsterinfo.attack_finished))
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+ }
+ }
+ }
+}
+
+void
+widow2_attack_beam(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &widow2_move_attack_beam;
+}
+
+void
+widow2_reattack_beam(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+ if (infront(self, self->enemy))
+ {
+ if (random() <= 0.5)
+ {
+ if ((random() < 0.7) || (SELF_SLOTS_LEFT < 2))
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_beam;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_spawn;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_post_beam;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &widow2_move_attack_post_beam;
+ }
+}
+
+void
+widow2_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 5;
+
+ if (damage < 15)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+ }
+ else if (damage < 75)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+
+ if ((skill->value < SKILL_HARDPLUS) &&
+ (random() < (0.6 - (0.2 * ((float)skill->value)))))
+ {
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &widow2_move_pain;
+ }
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+
+ if ((skill->value < SKILL_HARDPLUS) &&
+ (random() < (0.75 - (0.1 * ((float)skill->value)))))
+ {
+ self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+ self->monsterinfo.currentmove = &widow2_move_pain;
+ }
+ }
+}
+
+void
+widow2_dead(edict_t *self)
+{
+}
+
+void
+KillChildren(edict_t *self)
+{
+ edict_t *ent;
+ int field;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = NULL;
+ field = FOFS(classname);
+
+ while (1)
+ {
+ ent = G_Find(ent, field, "monster_stalker");
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((ent->inuse) && (ent->health > 0))
+ {
+ T_Damage(ent, self, self, vec3_origin, self->enemy->s.origin, vec3_origin,
+ (ent->health + 1), 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+ }
+ }
+}
+
+void
+widow2_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+ int clipped;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ clipped = min(damage, 100);
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/bone/tris.md2", clipped, GIB_ORGANIC,
+ NULL, false);
+ }
+
+ for (n = 0; n < 3; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2", clipped, GIB_ORGANIC,
+ NULL, false);
+ }
+
+ for (n = 0; n < 3; n++)
+ {
+ ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib1/tris.md2", clipped,
+ GIB_METALLIC, NULL, 0, false);
+ ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib2/tris.md2", clipped,
+ GIB_METALLIC, NULL, gi.soundindex("misc/fhit3.wav"), false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib3/tris.md2", clipped,
+ GIB_METALLIC, NULL, 0, false);
+ ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", clipped,
+ GIB_METALLIC, NULL, 0, false);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", clipped, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", clipped, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ KillChildren(self);
+ self->monsterinfo.quad_framenum = 0;
+ self->monsterinfo.double_framenum = 0;
+ self->monsterinfo.invincible_framenum = 0;
+ self->monsterinfo.currentmove = &widow2_move_death;
+}
+
+qboolean
+Widow2_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance = 0;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+ float real_enemy_range;
+ vec3_t f, r, u;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ WidowPowerups(self);
+
+ if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) &&
+ (realrange(self, self->enemy) > 150))
+ {
+ self->monsterinfo.aiflags |= AI_BLOCKED;
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ /* go ahead and spawn stuff if we're mad a a client */
+ if (self->enemy->client && (SELF_SLOTS_LEFT >= 2))
+ {
+ self->monsterinfo.attack_state = AS_BLIND;
+ return true;
+ }
+
+ if ((self->enemy->solid != SOLID_NOT) || (tr.fraction < 1.0))
+ {
+ return false;
+ }
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw2(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* melee attack */
+ if (self->timestamp < level.time)
+ {
+ real_enemy_range = realrange(self, self->enemy);
+
+ if (real_enemy_range < 300)
+ {
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offsets[0], f, r, u, spot1);
+ VectorCopy(self->enemy->s.origin, spot2);
+
+ if (widow2_tongue_attack_ok(spot1, spot2, 256))
+ {
+ /* be nice in easy mode */
+ if ((skill->value == SKILL_EASY) && (rand() & 3))
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.8;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.8;
+ }
+ else if (enemy_range == RANGE_FAR)
+ {
+ chance = 0.5;
+ }
+
+ if ((random() < chance) || (self->enemy->solid == SOLID_NOT))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ return false;
+}
+
+void
+Widow2Precache()
+{
+ /* cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs */
+ gi.soundindex("parasite/parpain1.wav");
+ gi.soundindex("parasite/parpain2.wav");
+ gi.soundindex("parasite/pardeth1.wav");
+ gi.soundindex("parasite/paratck1.wav");
+ gi.soundindex("parasite/parsght1.wav");
+ gi.soundindex("infantry/melee2.wav");
+ gi.soundindex("misc/fhit3.wav");
+
+ gi.soundindex("tank/tnkatck3.wav");
+ gi.soundindex("weapons/disrupt.wav");
+ gi.soundindex("weapons/disint2.wav");
+
+ gi.modelindex("models/monsters/stalker/tris.md2");
+ gi.modelindex("models/items/spawngro2/tris.md2");
+ gi.modelindex("models/objects/gibs/sm_metal/tris.md2");
+ gi.modelindex("models/proj/laser2/tris.md2");
+ gi.modelindex("models/proj/disintegrator/tris.md2");
+
+ gi.modelindex("models/monsters/blackwidow/gib1/tris.md2");
+ gi.modelindex("models/monsters/blackwidow/gib2/tris.md2");
+ gi.modelindex("models/monsters/blackwidow/gib3/tris.md2");
+ gi.modelindex("models/monsters/blackwidow/gib4/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib1/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib3/tris.md2");
+ gi.modelindex("models/monsters/blackwidow2/gib4/tris.md2");
+}
+
+/*
+ * QUAKED monster_widow2 (1 .5 0) (-70 -70 0) (70 70 144) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_widow2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("widow/bw2pain1.wav");
+ sound_pain2 = gi.soundindex("widow/bw2pain2.wav");
+ sound_pain3 = gi.soundindex("widow/bw2pain3.wav");
+ sound_death = gi.soundindex("widow/death.wav");
+ sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav");
+ sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/blackwidow2/tris.md2");
+ VectorSet(self->mins, -70, -70, 0);
+ VectorSet(self->maxs, 70, 70, 144);
+
+ self->health = 2000 + 800 + 1000 * (skill->value);
+
+ if (coop->value)
+ {
+ self->health += 500 * (skill->value);
+ }
+
+ self->gib_health = -900;
+ self->mass = 2500;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+ self->monsterinfo.power_armor_power = 750;
+ }
+
+ self->yaw_speed = 30;
+
+ self->flags |= FL_IMMUNE_LASER;
+ self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+ self->pain = widow2_pain;
+ self->die = widow2_die;
+
+ self->monsterinfo.melee = widow2_melee;
+ self->monsterinfo.stand = widow2_stand;
+ self->monsterinfo.walk = widow2_walk;
+ self->monsterinfo.run = widow2_run;
+ self->monsterinfo.attack = widow2_attack;
+ self->monsterinfo.search = widow2_search;
+ self->monsterinfo.checkattack = Widow2_CheckAttack;
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &widow2_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ Widow2Precache();
+ WidowCalcSlots(self);
+ walkmonster_start(self);
+}
+
+void
+WidowVelocityForDamage(int damage, vec3_t v)
+{
+ v[0] = damage * crandom();
+ v[1] = damage * crandom();
+ v[2] = damage * crandom() + 200.0;
+}
+
+void
+widow_gib_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_NOT;
+ self->touch = NULL;
+ self->s.angles[PITCH] = 0;
+ self->s.angles[ROLL] = 0;
+ VectorClear(self->avelocity);
+
+ if (self->plat2flags)
+ {
+ gi.sound(self, CHAN_VOICE, self->plat2flags, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+ThrowWidowGib(edict_t *self, char *gibname, int damage, int type)
+{
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ ThrowWidowGibReal(self, gibname, damage, type, NULL, false, 0, true);
+}
+
+void
+ThrowWidowGibLoc(edict_t *self, char *gibname, int damage,
+ int type, vec3_t startpos, qboolean fade)
+{
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ ThrowWidowGibReal(self, gibname, damage, type, startpos, false, 0, fade);
+}
+
+void
+ThrowWidowGibSized(edict_t *self, char *gibname, int damage, int type,
+ vec3_t startpos, int hitsound, qboolean fade)
+{
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ ThrowWidowGibReal(self, gibname, damage, type, startpos,
+ true, hitsound, fade);
+}
+
+void
+ThrowWidowGibReal(edict_t *self, char *gibname, int damage, int type,
+ vec3_t startpos, qboolean sized, int hitsound, qboolean fade)
+{
+ edict_t *gib;
+ vec3_t vd;
+ vec3_t origin;
+ vec3_t size;
+ float vscale;
+
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ gib = G_Spawn();
+
+ if (startpos)
+ {
+ VectorCopy(startpos, gib->s.origin);
+ }
+ else
+ {
+ VectorScale(self->size, 0.5, size);
+ VectorAdd(self->absmin, size, origin);
+ gib->s.origin[0] = origin[0] + crandom() * size[0];
+ gib->s.origin[1] = origin[1] + crandom() * size[1];
+ gib->s.origin[2] = origin[2] + crandom() * size[2];
+ }
+
+ gib->solid = SOLID_NOT;
+ gib->s.effects |= EF_GIB;
+ gib->flags |= FL_NO_KNOCKBACK;
+ gib->takedamage = DAMAGE_YES;
+ gib->die = gib_die;
+ gib->s.renderfx |= RF_IR_VISIBLE;
+
+ if (fade)
+ {
+ gib->think = G_FreeEdict;
+
+ /* sized gibs last longer */
+ if (sized)
+ {
+ gib->nextthink = level.time + 20 + random() * 15;
+ }
+ else
+ {
+ gib->nextthink = level.time + 5 + random() * 10;
+ }
+ }
+ else
+ {
+ gib->think = G_FreeEdict;
+
+ /* sized gibs last longer */
+ if (sized)
+ {
+ gib->nextthink = level.time + 60 + random() * 15;
+ }
+ else
+ {
+ gib->nextthink = level.time + 25 + random() * 10;
+ }
+ }
+
+ if (type == GIB_ORGANIC)
+ {
+ gib->movetype = MOVETYPE_TOSS;
+ gib->touch = gib_touch;
+ vscale = 0.5;
+ }
+ else
+ {
+ gib->movetype = MOVETYPE_BOUNCE;
+ vscale = 1.0;
+ }
+
+ WidowVelocityForDamage(damage, vd);
+ VectorMA(self->velocity, vscale, vd, gib->velocity);
+ ClipGibVelocity(gib);
+
+ gi.setmodel(gib, gibname);
+
+ if (sized)
+ {
+ gib->plat2flags = hitsound;
+ gib->solid = SOLID_BBOX;
+ gib->avelocity[0] = random() * 400;
+ gib->avelocity[1] = random() * 400;
+ gib->avelocity[2] = random() * 200;
+
+ if (gib->velocity[2] < 0)
+ {
+ gib->velocity[2] *= -1;
+ }
+
+ gib->velocity[0] *= 2;
+ gib->velocity[1] *= 2;
+ ClipGibVelocity(gib);
+ gib->velocity[2] = max((350 + (random() * 100.0)), gib->velocity[2]);
+ gib->gravity = 0.25;
+ gib->touch = widow_gib_touch;
+ gib->owner = self;
+
+ if (gib->s.modelindex == gi.modelindex("models/monsters/blackwidow2/gib2/tris.md2"))
+ {
+ VectorSet(gib->mins, -10, -10, 0);
+ VectorSet(gib->maxs, 10, 10, 10);
+ }
+ else
+ {
+ VectorSet(gib->mins, -5, -5, 0);
+ VectorSet(gib->maxs, 5, 5, 5);
+ }
+ }
+ else
+ {
+ gib->velocity[0] *= 2;
+ gib->velocity[1] *= 2;
+ gib->avelocity[0] = random() * 600;
+ gib->avelocity[1] = random() * 600;
+ gib->avelocity[2] = random() * 600;
+ }
+
+ gi.linkentity(gib);
+}
+
+void
+ThrowSmallStuff(edict_t *self, vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, point, false);
+ }
+
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, point, false);
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, point, false);
+}
+
+void
+ThrowMoreStuff(edict_t *self, vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (coop && coop->value)
+ {
+ ThrowSmallStuff(self, point);
+ return;
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, point, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, point, false);
+ }
+
+ for (n = 0; n < 3; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, point, false);
+ }
+}
+
+void
+WidowExplode(edict_t *self)
+{
+ vec3_t org;
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = WidowExplode;
+ VectorCopy(self->s.origin, org);
+ org[2] += 24 + (rand() & 15);
+
+ if (self->count < 8)
+ {
+ org[2] += 24 + (rand() & 31);
+ }
+
+ switch (self->count)
+ {
+ case 0:
+ org[0] -= 24;
+ org[1] -= 24;
+ break;
+ case 1:
+ org[0] += 24;
+ org[1] += 24;
+ ThrowSmallStuff(self, org);
+ break;
+ case 2:
+ org[0] += 24;
+ org[1] -= 24;
+ break;
+ case 3:
+ org[0] -= 24;
+ org[1] += 24;
+ ThrowMoreStuff(self, org);
+ break;
+ case 4:
+ org[0] -= 48;
+ org[1] -= 48;
+ break;
+ case 5:
+ org[0] += 48;
+ org[1] += 48;
+ ThrowArm1(self);
+ break;
+ case 6:
+ org[0] -= 48;
+ org[1] += 48;
+ ThrowArm2(self);
+ break;
+ case 7:
+ org[0] += 48;
+ org[1] -= 48;
+ ThrowSmallStuff(self, org);
+ break;
+ case 8:
+ org[0] += 18;
+ org[1] += 18;
+ org[2] = self->s.origin[2] + 48;
+ ThrowMoreStuff(self, org);
+ break;
+ case 9:
+ org[0] -= 18;
+ org[1] += 18;
+ org[2] = self->s.origin[2] + 48;
+ break;
+ case 10:
+ org[0] += 18;
+ org[1] -= 18;
+ org[2] = self->s.origin[2] + 48;
+ break;
+ case 11:
+ org[0] -= 18;
+ org[1] -= 18;
+ org[2] = self->s.origin[2] + 48;
+ break;
+ case 12:
+ self->s.sound = 0;
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ 400, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGib(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGib(self, "models/objects/gibs/sm_metal/tris.md2",
+ 400, GIB_METALLIC);
+ }
+
+ self->deadflag = DEAD_DEAD;
+ self->think = monster_think;
+ self->nextthink = level.time + 0.1;
+ self->monsterinfo.currentmove = &widow2_move_dead;
+ return;
+ }
+
+ self->count++;
+
+ if ((self->count >= 9) && (self->count <= 12))
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1_BIG);
+ gi.WritePosition(org);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+ }
+ else
+ {
+ gi.WriteByte(svc_temp_entity);
+
+ if (self->count % 2)
+ {
+ gi.WriteByte(TE_EXPLOSION1);
+ }
+ else
+ {
+ gi.WriteByte(TE_EXPLOSION1_NP);
+ }
+
+ gi.WritePosition(org);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+ }
+
+ self->nextthink = level.time + 0.1;
+}
+
+void
+WidowExplosion1(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset = {23.74, -37.67, 76.96};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+WidowExplosion2(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset = {-20.49, 36.92, 73.52};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+WidowExplosion3(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset = {2.11, 0.05, 92.20};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+WidowExplosion4(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset = {-28.04, -35.57, -77.56};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+WidowExplosion5(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset = {-20.11, -1.11, 40.76};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+WidowExplosion6(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset = {-20.11, -1.11, 40.76};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+WidowExplosion7(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset = {-20.11, -1.11, 40.76};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ }
+
+ for (n = 0; n < 1; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 300, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+WidowExplosionLeg(edict_t *self)
+{
+ vec3_t f, r, u, startpoint;
+ vec3_t offset1 = {-31.89, -47.86, 67.02};
+ vec3_t offset2 = {-44.9, -82.14, 54.72};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset1, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1_BIG);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib2/tris.md2",
+ 200, GIB_METALLIC, startpoint, gi.soundindex("misc/fhit3.wav"),
+ false);
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+
+ G_ProjectSource2(self->s.origin, offset2, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib1/tris.md2",
+ 300, GIB_METALLIC, startpoint, gi.soundindex("misc/fhit3.wav"),
+ false);
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+}
+
+void
+ThrowArm1(edict_t *self)
+{
+ int n;
+ vec3_t f, r, u, startpoint;
+ vec3_t offset1 = {65.76, 17.52, 7.56};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset1, f, r, u, startpoint);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1_BIG);
+ gi.WritePosition(startpoint);
+ gi.multicast(self->s.origin, MULTICAST_ALL);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_metal/tris.md2",
+ 100, GIB_METALLIC, startpoint, false);
+ }
+}
+
+void
+ThrowArm2(edict_t *self)
+{
+ vec3_t f, r, u, startpoint;
+ vec3_t offset1 = {65.76, 17.52, 7.56};
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ G_ProjectSource2(self->s.origin, offset1, f, r, u, startpoint);
+
+ ThrowWidowGibSized(self, "models/monsters/blackwidow2/gib4/tris.md2",
+ 200, GIB_METALLIC, startpoint, gi.soundindex("misc/fhit3.wav"),
+ false);
+ ThrowWidowGibLoc(self, "models/objects/gibs/sm_meat/tris.md2",
+ 300, GIB_ORGANIC, startpoint, false);
+}
diff --git a/rogue/src/monster/widow/widow2.h b/rogue/src/monster/widow/widow2.h
new file mode 100644
index 0000000..1073235
--- /dev/null
+++ b/rogue/src/monster/widow/widow2.h
@@ -0,0 +1,134 @@
+/* =======================================================================
+ *
+ * Black Widow (stage 2) animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_blackwidow3 0
+#define FRAME_walk01 1
+#define FRAME_walk02 2
+#define FRAME_walk03 3
+#define FRAME_walk04 4
+#define FRAME_walk05 5
+#define FRAME_walk06 6
+#define FRAME_walk07 7
+#define FRAME_walk08 8
+#define FRAME_walk09 9
+#define FRAME_spawn01 10
+#define FRAME_spawn02 11
+#define FRAME_spawn03 12
+#define FRAME_spawn04 13
+#define FRAME_spawn05 14
+#define FRAME_spawn06 15
+#define FRAME_spawn07 16
+#define FRAME_spawn08 17
+#define FRAME_spawn09 18
+#define FRAME_spawn10 19
+#define FRAME_spawn11 20
+#define FRAME_spawn12 21
+#define FRAME_spawn13 22
+#define FRAME_spawn14 23
+#define FRAME_spawn15 24
+#define FRAME_spawn16 25
+#define FRAME_spawn17 26
+#define FRAME_spawn18 27
+#define FRAME_firea01 28
+#define FRAME_firea02 29
+#define FRAME_firea03 30
+#define FRAME_firea04 31
+#define FRAME_firea05 32
+#define FRAME_firea06 33
+#define FRAME_firea07 34
+#define FRAME_fireb01 35
+#define FRAME_fireb02 36
+#define FRAME_fireb03 37
+#define FRAME_fireb04 38
+#define FRAME_fireb05 39
+#define FRAME_fireb06 40
+#define FRAME_fireb07 41
+#define FRAME_fireb08 42
+#define FRAME_fireb09 43
+#define FRAME_fireb10 44
+#define FRAME_fireb11 45
+#define FRAME_fireb12 46
+#define FRAME_tongs01 47
+#define FRAME_tongs02 48
+#define FRAME_tongs03 49
+#define FRAME_tongs04 50
+#define FRAME_tongs05 51
+#define FRAME_tongs06 52
+#define FRAME_tongs07 53
+#define FRAME_tongs08 54
+#define FRAME_pain01 55
+#define FRAME_pain02 56
+#define FRAME_pain03 57
+#define FRAME_pain04 58
+#define FRAME_pain05 59
+#define FRAME_death01 60
+#define FRAME_death02 61
+#define FRAME_death03 62
+#define FRAME_death04 63
+#define FRAME_death05 64
+#define FRAME_death06 65
+#define FRAME_death07 66
+#define FRAME_death08 67
+#define FRAME_death09 68
+#define FRAME_death10 69
+#define FRAME_death11 70
+#define FRAME_death12 71
+#define FRAME_death13 72
+#define FRAME_death14 73
+#define FRAME_death15 74
+#define FRAME_death16 75
+#define FRAME_death17 76
+#define FRAME_death18 77
+#define FRAME_death19 78
+#define FRAME_death20 79
+#define FRAME_death21 80
+#define FRAME_death22 81
+#define FRAME_death23 82
+#define FRAME_death24 83
+#define FRAME_death25 84
+#define FRAME_death26 85
+#define FRAME_death27 86
+#define FRAME_death28 87
+#define FRAME_death29 88
+#define FRAME_death30 89
+#define FRAME_death31 90
+#define FRAME_death32 91
+#define FRAME_death33 92
+#define FRAME_death34 93
+#define FRAME_death35 94
+#define FRAME_death36 95
+#define FRAME_death37 96
+#define FRAME_death38 97
+#define FRAME_death39 98
+#define FRAME_death40 99
+#define FRAME_death41 100
+#define FRAME_death42 101
+#define FRAME_death43 102
+#define FRAME_death44 103
+#define FRAME_dthsrh01 104
+#define FRAME_dthsrh02 105
+#define FRAME_dthsrh03 106
+#define FRAME_dthsrh04 107
+#define FRAME_dthsrh05 108
+#define FRAME_dthsrh06 109
+#define FRAME_dthsrh07 110
+#define FRAME_dthsrh08 111
+#define FRAME_dthsrh09 112
+#define FRAME_dthsrh10 113
+#define FRAME_dthsrh11 114
+#define FRAME_dthsrh12 115
+#define FRAME_dthsrh13 116
+#define FRAME_dthsrh14 117
+#define FRAME_dthsrh15 118
+#define FRAME_dthsrh16 119
+#define FRAME_dthsrh17 120
+#define FRAME_dthsrh18 121
+#define FRAME_dthsrh19 122
+#define FRAME_dthsrh20 123
+#define FRAME_dthsrh21 124
+#define FRAME_dthsrh22 125
+#define MODEL_SCALE 2.000000
diff --git a/rogue/src/player/client.c b/rogue/src/player/client.c
new file mode 100644
index 0000000..8479040
--- /dev/null
+++ b/rogue/src/player/client.c
@@ -0,0 +1,2518 @@
+/* =======================================================================
+ *
+ * Interface between client <-> game and client calculations.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+#include "../monster/misc/player.h"
+
+edict_t *pm_passent;
+
+void ClientUserinfoChanged(edict_t *ent, char *userinfo);
+void SP_misc_teleporter_dest(edict_t *ent);
+void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+/*
+ * QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
+ *
+ * The normal starting point for a level.
+ */
+void
+SP_info_player_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!coop->value)
+ {
+ return;
+ }
+}
+
+/*
+ * QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
+ *
+ * potential spawning position for deathmatch games
+ */
+void
+SP_info_player_deathmatch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ SP_misc_teleporter_dest(self);
+}
+
+/*
+ * QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
+ * potential spawning position for coop games
+ */
+void
+SP_info_player_coop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!coop->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+}
+
+/*
+ * QUAKED info_player_coop_lava (1 0 1) (-16 -16 -24) (16 16 32)
+ *
+ * potential spawning position for coop games on rmine2 where lava level
+ * needs to be checked
+ */
+void
+SP_info_player_coop_lava(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!coop->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+}
+
+/*
+ * QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+ *
+ * The deathmatch intermission point will be at one of these
+ * Use 'angles' instead of 'angle', so you can set pitch or roll
+ * as well as yaw. 'pitch yaw roll'
+ */
+void
+SP_info_player_intermission(edict_t *ent)
+{
+}
+
+/* ======================================================================= */
+
+void
+player_pain(edict_t *self, edict_t *other, float kick, int damage)
+{
+}
+
+qboolean
+IsFemale(edict_t *ent)
+{
+ char *info;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!ent->client)
+ {
+ return false;
+ }
+
+ info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
+
+ if ((info[0] == 'f') || (info[0] == 'F'))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+IsNeutral(edict_t *ent)
+{
+ char *info;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!ent->client)
+ {
+ return false;
+ }
+
+ info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
+
+ if ((info[0] != 'f') && (info[0] != 'F') && (info[0] != 'm') &&
+ (info[0] != 'M'))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+ClientObituary(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker)
+{
+ int mod;
+ char *message;
+ char *message2;
+ qboolean ff;
+
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if (coop->value && attacker->client)
+ {
+ meansOfDeath |= MOD_FRIENDLY_FIRE;
+ }
+
+ if (deathmatch->value || coop->value)
+ {
+ ff = meansOfDeath & MOD_FRIENDLY_FIRE;
+ mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+ message = NULL;
+ message2 = "";
+
+ switch (mod)
+ {
+ case MOD_SUICIDE:
+ message = "suicides";
+ break;
+ case MOD_FALLING:
+ message = "cratered";
+ break;
+ case MOD_CRUSH:
+ message = "was squished";
+ break;
+ case MOD_WATER:
+ message = "sank like a rock";
+ break;
+ case MOD_SLIME:
+ message = "melted";
+ break;
+ case MOD_LAVA:
+ message = "does a back flip into the lava";
+ break;
+ case MOD_EXPLOSIVE:
+ case MOD_BARREL:
+ message = "blew up";
+ break;
+ case MOD_EXIT:
+ message = "found a way out";
+ break;
+ case MOD_TARGET_LASER:
+ message = "saw the light";
+ break;
+ case MOD_TARGET_BLASTER:
+ message = "got blasted";
+ break;
+ case MOD_BOMB:
+ case MOD_SPLASH:
+ case MOD_TRIGGER_HURT:
+ message = "was in the wrong place";
+ break;
+ default:
+ break;
+ }
+
+ if (attacker == self)
+ {
+ switch (mod)
+ {
+ case MOD_HELD_GRENADE:
+ message = "tried to put the pin back in";
+ break;
+ case MOD_HG_SPLASH:
+ case MOD_G_SPLASH:
+
+ if (IsNeutral(self))
+ {
+ message = "tripped on its own grenade";
+ }
+ else if (IsFemale(self))
+ {
+ message = "tripped on her own grenade";
+ }
+ else
+ {
+ message = "tripped on his own grenade";
+ }
+
+ break;
+ case MOD_R_SPLASH:
+
+ if (IsNeutral(self))
+ {
+ message = "blew itself up";
+ }
+ else if (IsFemale(self))
+ {
+ message = "blew herself up";
+ }
+ else
+ {
+ message = "blew himself up";
+ }
+
+ break;
+ case MOD_BFG_BLAST:
+ message = "should have used a smaller gun";
+ break;
+ case MOD_DOPPLE_EXPLODE:
+
+ if (IsNeutral(self))
+ {
+ message = "got caught in it's own trap";
+ }
+ else if (IsFemale(self))
+ {
+ message = "got caught in her own trap";
+ }
+ else
+ {
+ message = "got caught in his own trap";
+ }
+
+ break;
+ default:
+
+ if (IsNeutral(self))
+ {
+ message = "killed itself";
+ }
+ else if (IsFemale(self))
+ {
+ message = "killed herself";
+ }
+ else
+ {
+ message = "killed himself";
+ }
+
+ break;
+ }
+ }
+
+ if (message)
+ {
+ gi.bprintf(PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
+
+ if (deathmatch->value)
+ {
+ self->client->resp.score--;
+ }
+
+ self->enemy = NULL;
+ return;
+ }
+
+ self->enemy = attacker;
+
+ if (attacker->client)
+ {
+ switch (mod)
+ {
+ case MOD_BLASTER:
+ message = "was blasted by";
+ break;
+ case MOD_SHOTGUN:
+ message = "was gunned down by";
+ break;
+ case MOD_SSHOTGUN:
+ message = "was blown away by";
+ message2 = "'s super shotgun";
+ break;
+ case MOD_MACHINEGUN:
+ message = "was machinegunned by";
+ break;
+ case MOD_CHAINGUN:
+ message = "was cut in half by";
+ message2 = "'s chaingun";
+ break;
+ case MOD_GRENADE:
+ message = "was popped by";
+ message2 = "'s grenade";
+ break;
+ case MOD_G_SPLASH:
+ message = "was shredded by";
+ message2 = "'s shrapnel";
+ break;
+ case MOD_ROCKET:
+ message = "ate";
+ message2 = "'s rocket";
+ break;
+ case MOD_R_SPLASH:
+ message = "almost dodged";
+ message2 = "'s rocket";
+ break;
+ case MOD_HYPERBLASTER:
+ message = "was melted by";
+ message2 = "'s hyperblaster";
+ break;
+ case MOD_RAILGUN:
+ message = "was railed by";
+ break;
+ case MOD_BFG_LASER:
+ message = "saw the pretty lights from";
+ message2 = "'s BFG";
+ break;
+ case MOD_BFG_BLAST:
+ message = "was disintegrated by";
+ message2 = "'s BFG blast";
+ break;
+ case MOD_BFG_EFFECT:
+ message = "couldn't hide from";
+ message2 = "'s BFG";
+ break;
+ case MOD_HANDGRENADE:
+ message = "caught";
+ message2 = "'s handgrenade";
+ break;
+ case MOD_HG_SPLASH:
+ message = "didn't see";
+ message2 = "'s handgrenade";
+ break;
+ case MOD_HELD_GRENADE:
+ message = "feels";
+ message2 = "'s pain";
+ break;
+ case MOD_TELEFRAG:
+ message = "tried to invade";
+ message2 = "'s personal space";
+ break;
+ case MOD_CHAINFIST:
+ message = "was shredded by";
+ message2 = "'s ripsaw";
+ break;
+ case MOD_DISINTEGRATOR:
+ message = "lost his grip courtesy of";
+ message2 = "'s disintegrator";
+ break;
+ case MOD_ETF_RIFLE:
+ message = "was perforated by";
+ break;
+ case MOD_HEATBEAM:
+ message = "was scorched by";
+ message2 = "'s plasma beam";
+ break;
+ case MOD_TESLA:
+ message = "was enlightened by";
+ message2 = "'s tesla mine";
+ break;
+ case MOD_PROX:
+ message = "got too close to";
+ message2 = "'s proximity mine";
+ break;
+ case MOD_NUKE:
+ message = "was nuked by";
+ message2 = "'s antimatter bomb";
+ break;
+ case MOD_VENGEANCE_SPHERE:
+ message = "was purged by";
+ message2 = "'s vengeance sphere";
+ break;
+ case MOD_DEFENDER_SPHERE:
+ message = "had a blast with";
+ message2 = "'s defender sphere";
+ break;
+ case MOD_HUNTER_SPHERE:
+ message = "was killed like a dog by";
+ message2 = "'s hunter sphere";
+ break;
+ case MOD_TRACKER:
+ message = "was annihilated by";
+ message2 = "'s disruptor";
+ break;
+ case MOD_DOPPLE_EXPLODE:
+ message = "was blown up by";
+ message2 = "'s doppleganger";
+ break;
+ case MOD_DOPPLE_VENGEANCE:
+ message = "was purged by";
+ message2 = "'s doppleganger";
+ break;
+ case MOD_DOPPLE_HUNTER:
+ message = "was hunted down by";
+ message2 = "'s doppleganger";
+ break;
+ default:
+ break;
+ }
+
+ if (message)
+ {
+ gi.bprintf(PRINT_MEDIUM, "%s %s %s%s\n", self->client->pers.netname,
+ message, attacker->client->pers.netname, message2);
+
+ if (gamerules && gamerules->value)
+ {
+ if (DMGame.Score)
+ {
+ if (ff)
+ {
+ DMGame.Score(attacker, self, -1);
+ }
+ else
+ {
+ DMGame.Score(attacker, self, 1);
+ }
+ }
+
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ if (ff)
+ {
+ attacker->client->resp.score--;
+ }
+ else
+ {
+ attacker->client->resp.score++;
+ }
+ }
+
+ return;
+ }
+ }
+ }
+
+ gi.bprintf(PRINT_MEDIUM, "%s died.\n", self->client->pers.netname);
+
+ if (deathmatch->value)
+ {
+ if (gamerules && gamerules->value)
+ {
+ if (DMGame.Score)
+ {
+ DMGame.Score(self, self, -1);
+ }
+
+ return;
+ }
+ else
+ {
+ self->client->resp.score--;
+ }
+ }
+}
+
+void
+TossClientWeapon(edict_t *self)
+{
+ gitem_t *item;
+ edict_t *drop;
+ qboolean quad;
+ float spread;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ return;
+ }
+
+ item = self->client->pers.weapon;
+
+ if (!self->client->pers.inventory[self->client->ammo_index])
+ {
+ item = NULL;
+ }
+
+ if (item && (strcmp(item->pickup_name, "Blaster") == 0))
+ {
+ item = NULL;
+ }
+
+ if (!((int)(dmflags->value) & DF_QUAD_DROP))
+ {
+ quad = false;
+ }
+ else
+ {
+ quad = (self->client->quad_framenum > (level.framenum + 10));
+ }
+
+ if (item && quad)
+ {
+ spread = 22.5;
+ }
+ else
+ {
+ spread = 0.0;
+ }
+
+ if (item)
+ {
+ self->client->v_angle[YAW] -= spread;
+ drop = Drop_Item(self, item);
+ self->client->v_angle[YAW] += spread;
+ drop->spawnflags = DROPPED_PLAYER_ITEM;
+ }
+
+ if (quad)
+ {
+ self->client->v_angle[YAW] += spread;
+ drop = Drop_Item(self, FindItemByClassname("item_quad"));
+ self->client->v_angle[YAW] -= spread;
+ drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+ drop->touch = Touch_Item;
+ drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
+ drop->think = G_FreeEdict;
+ }
+}
+
+void
+LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker)
+{
+ vec3_t dir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (attacker && (attacker != world) && (attacker != self))
+ {
+ VectorSubtract(attacker->s.origin, self->s.origin, dir);
+ }
+ else if (inflictor && (inflictor != world) && (inflictor != self))
+ {
+ VectorSubtract(inflictor->s.origin, self->s.origin, dir);
+ }
+ else
+ {
+ self->client->killer_yaw = self->s.angles[YAW];
+ return;
+ }
+
+ if (dir[0])
+ {
+ self->client->killer_yaw = 180 / M_PI * atan2(dir[1], dir[0]);
+ }
+ else if (dir[1] > 0)
+ {
+ self->client->killer_yaw = 90;
+ }
+ else if (dir[1] < 0)
+ {
+ self->client->killer_yaw = 270;
+ }
+ else
+ {
+ self->client->killer_yaw = 0;
+ }
+}
+
+void
+player_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point)
+{
+ int n;
+
+ if (!self || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ VectorClear(self->avelocity);
+
+ self->takedamage = DAMAGE_YES;
+ self->movetype = MOVETYPE_TOSS;
+
+ self->s.modelindex2 = 0; /* remove linked weapon model */
+
+ self->s.angles[0] = 0;
+ self->s.angles[2] = 0;
+
+ self->s.sound = 0;
+ self->client->weapon_sound = 0;
+
+ self->maxs[2] = -8;
+
+ self->svflags |= SVF_DEADMONSTER;
+
+ if (!self->deadflag)
+ {
+ self->client->respawn_time = level.time + 1.0;
+ LookAtKiller(self, inflictor, attacker);
+ self->client->ps.pmove.pm_type = PM_DEAD;
+ ClientObituary(self, inflictor, attacker);
+ TossClientWeapon(self);
+
+ if (deathmatch->value)
+ {
+ Cmd_Help_f(self); /* show scores */
+ }
+
+ /* clear inventory. this is kind of ugly, but it's
+ how we want to handle keys in coop */
+ for (n = 0; n < game.num_items; n++)
+ {
+ if (coop->value && itemlist[n].flags & IT_KEY)
+ {
+ self->client->resp.coop_respawn.inventory[n] =
+ self->client->pers.inventory[n];
+ }
+
+ self->client->pers.inventory[n] = 0;
+ }
+ }
+
+ if (gamerules && gamerules->value) /* if we're in a dm game, alert the game */
+ {
+ if (DMGame.PlayerDeath)
+ {
+ DMGame.PlayerDeath(self, inflictor, attacker);
+ }
+ }
+
+ /* remove powerups */
+ self->client->quad_framenum = 0;
+ self->client->invincible_framenum = 0;
+ self->client->breather_framenum = 0;
+ self->client->enviro_framenum = 0;
+ self->flags &= ~FL_POWER_ARMOR;
+
+ self->client->double_framenum = 0;
+
+ if (self->client->owned_sphere)
+ {
+ edict_t *sphere;
+
+ sphere = self->client->owned_sphere;
+ sphere->die(sphere, self, self, 0, vec3_origin);
+ }
+
+ /* if we've been killed by the tracker, GIB! */
+ if ((meansOfDeath & ~MOD_FRIENDLY_FIRE) == MOD_TRACKER)
+ {
+ self->health = -100;
+ damage = 400;
+ }
+
+ /* make sure no trackers are still hurting us. */
+ if (self->client->tracker_pain_framenum)
+ {
+ RemoveAttackingPainDaemons(self);
+ }
+
+ /* if we got obliterated by the nuke, don't gib */
+ if ((self->health < -80) && (meansOfDeath == MOD_NUKE))
+ {
+ self->flags |= FL_NOGIB;
+ }
+
+ if (self->health < -40)
+ {
+ /* don't toss gibs if we got vaped by the nuke */
+ if (!(self->flags & FL_NOGIB))
+ {
+ /* gib (play sound at end of server frame) */
+ self->sounds = gi.soundindex( "misc/udeath.wav");
+
+ /* more meaty gibs for your dollar! */
+ if ((deathmatch->value) && (self->health < -80))
+ {
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+ }
+
+ self->flags &= ~FL_NOGIB;
+ ThrowClientHead(self, damage);
+ self->takedamage = DAMAGE_NO;
+ }
+ else
+ {
+ /* normal death */
+ if (!self->deadflag)
+ {
+ static int i;
+
+ i = (i + 1) % 3;
+
+ /* start a death animation */
+ self->client->anim_priority = ANIM_DEATH;
+
+ if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ self->s.frame = FRAME_crdeath1 - 1;
+ self->client->anim_end = FRAME_crdeath5;
+ }
+ else
+ {
+ switch (i)
+ {
+ case 0:
+ self->s.frame = FRAME_death101 - 1;
+ self->client->anim_end = FRAME_death106;
+ break;
+ case 1:
+ self->s.frame = FRAME_death201 - 1;
+ self->client->anim_end = FRAME_death206;
+ break;
+ case 2:
+ self->s.frame = FRAME_death301 - 1;
+ self->client->anim_end = FRAME_death308;
+ break;
+ }
+ }
+
+ /* play sound at end of server frame */
+ if (!self->sounds)
+ {
+ self->sounds = gi.soundindex(va("*death%i.wav", (rand() % 4) + 1));
+ }
+ }
+ }
+
+ self->deadflag = DEAD_DEAD;
+ gi.linkentity(self);
+}
+
+/* ======================================================================= */
+
+/*
+ * This is only called when the game first initializes in single player,
+ * but is called after each death and level change in deathmatch
+ */
+void
+InitClientPersistant(gclient_t *client)
+{
+ gitem_t *item;
+
+ if (!client)
+ {
+ return;
+ }
+
+ memset(&client->pers, 0, sizeof(client->pers));
+
+ item = FindItem("Blaster");
+ client->pers.selected_item = ITEM_INDEX(item);
+ client->pers.inventory[client->pers.selected_item] = 1;
+
+ client->pers.weapon = item;
+
+ client->pers.health = 100;
+ client->pers.max_health = 100;
+
+ client->pers.max_bullets = 200;
+ client->pers.max_shells = 100;
+ client->pers.max_rockets = 50;
+ client->pers.max_grenades = 50;
+ client->pers.max_cells = 200;
+ client->pers.max_slugs = 50;
+
+ client->pers.max_prox = 50;
+ client->pers.max_tesla = 50;
+ client->pers.max_flechettes = 200;
+ client->pers.max_rounds = 100;
+
+ client->pers.connected = true;
+}
+
+void
+InitClientResp(gclient_t *client)
+{
+ if (!client)
+ {
+ return;
+ }
+
+ memset(&client->resp, 0, sizeof(client->resp));
+ client->resp.enterframe = level.framenum;
+ client->resp.coop_respawn = client->pers;
+}
+
+/*
+ * Some information that should be persistant, like health,
+ * is still stored in the edict structure, so it needs to
+ * be mirrored out to the client structure before all the
+ * edicts are wiped.
+ */
+void
+SaveClientData(void)
+{
+ int i;
+ edict_t *ent;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = &g_edicts[1 + i];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ game.clients[i].pers.health = ent->health;
+ game.clients[i].pers.max_health = ent->max_health;
+ game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR | FL_DISGUISED));
+
+ if (coop->value)
+ {
+ game.clients[i].pers.score = ent->client->resp.score;
+ }
+ }
+}
+
+void
+FetchClientEntData(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->health = ent->client->pers.health;
+ ent->max_health = ent->client->pers.max_health;
+ ent->flags |= ent->client->pers.savedFlags;
+
+ if (coop->value)
+ {
+ ent->client->resp.score = ent->client->pers.score;
+ }
+}
+
+/*
+ * Returns the distance to the nearest player from the given spot
+ */
+float
+PlayersRangeFromSpot(edict_t *spot)
+{
+ edict_t *player;
+ float bestplayerdistance;
+ vec3_t v;
+ int n;
+ float playerdistance;
+
+ if (!spot)
+ {
+ return 0.0;
+ }
+
+ bestplayerdistance = 9999999;
+
+ for (n = 1; n <= maxclients->value; n++)
+ {
+ player = &g_edicts[n];
+
+ if (!player->inuse)
+ {
+ continue;
+ }
+
+ if (player->health <= 0)
+ {
+ continue;
+ }
+
+ VectorSubtract(spot->s.origin, player->s.origin, v);
+ playerdistance = VectorLength(v);
+
+ if (playerdistance < bestplayerdistance)
+ {
+ bestplayerdistance = playerdistance;
+ }
+ }
+
+ return bestplayerdistance;
+}
+
+/*
+ * go to a random point, but NOT the two points closest
+ * to other players
+ */
+edict_t *
+SelectRandomDeathmatchSpawnPoint(void)
+{
+ edict_t *spot, *spot1, *spot2;
+ int count = 0;
+ int selection;
+ float range, range1, range2;
+
+ spot = NULL;
+ range1 = range2 = 99999;
+ spot1 = spot2 = NULL;
+
+ while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+ {
+ count++;
+ range = PlayersRangeFromSpot(spot);
+
+ if (range < range1)
+ {
+ range1 = range;
+ spot1 = spot;
+ }
+ else if (range < range2)
+ {
+ range2 = range;
+ spot2 = spot;
+ }
+ }
+
+ if (!count)
+ {
+ return NULL;
+ }
+
+ if (count <= 2)
+ {
+ spot1 = spot2 = NULL;
+ }
+ else
+ {
+ count -= 2;
+ }
+
+ selection = rand() % count;
+
+ spot = NULL;
+
+ do
+ {
+ spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
+
+ if ((spot == spot1) || (spot == spot2))
+ {
+ selection++;
+ }
+ }
+ while (selection--);
+
+ return spot;
+}
+
+edict_t *
+SelectFarthestDeathmatchSpawnPoint(void)
+{
+ edict_t *bestspot;
+ float bestdistance, bestplayerdistance;
+ edict_t *spot;
+
+ spot = NULL;
+ bestspot = NULL;
+ bestdistance = 0;
+
+ while ((spot = G_Find(spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+ {
+ bestplayerdistance = PlayersRangeFromSpot(spot);
+
+ if (bestplayerdistance > bestdistance)
+ {
+ bestspot = spot;
+ bestdistance = bestplayerdistance;
+ }
+ }
+
+ if (bestspot)
+ {
+ return bestspot;
+ }
+
+ /* if there is a player just spawned on each and every start spot
+ we have no choice to turn one into a telefrag meltdown */
+ spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
+
+ return spot;
+}
+
+edict_t *
+SelectDeathmatchSpawnPoint(void)
+{
+ if ((int)(dmflags->value) & DF_SPAWN_FARTHEST)
+ {
+ return SelectFarthestDeathmatchSpawnPoint();
+ }
+ else
+ {
+ return SelectRandomDeathmatchSpawnPoint();
+ }
+}
+
+edict_t *
+SelectLavaCoopSpawnPoint(edict_t *ent)
+{
+ int index;
+ edict_t *spot = NULL;
+ float lavatop;
+ edict_t *lava;
+ edict_t *pointWithLeastLava;
+ float lowest;
+ edict_t *spawnPoints[64];
+ vec3_t center;
+ int numPoints;
+ edict_t *highestlava;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ lavatop = -99999;
+ highestlava = NULL;
+
+ lava = NULL;
+
+ while (1)
+ {
+ lava = G_Find(lava, FOFS(classname), "func_door");
+
+ if (!lava)
+ {
+ break;
+ }
+
+ VectorAdd(lava->absmax, lava->absmin, center);
+ VectorScale(center, 0.5, center);
+
+ if (lava->spawnflags & 2 && (gi.pointcontents(center) & MASK_WATER))
+ {
+ if (lava->absmax[2] > lavatop)
+ {
+ lavatop = lava->absmax[2];
+ highestlava = lava;
+ }
+ }
+ }
+
+ /* if we didn't find ANY lava, then return NULL */
+ if (!highestlava)
+ {
+ return NULL;
+ }
+
+ /* find the top of the lava and include a small margin of error (plus bbox size) */
+ lavatop = highestlava->absmax[2] + 64;
+
+ /* find all the lava spawn points and store them in spawnPoints[] */
+ spot = NULL;
+ numPoints = 0;
+
+ while ((spot = (G_Find(spot, FOFS(classname), "info_player_coop_lava"))))
+ {
+ if (numPoints == 64)
+ {
+ break;
+ }
+
+ spawnPoints[numPoints++] = spot;
+ }
+
+ if (numPoints < 1)
+ {
+ return NULL;
+ }
+
+ /* walk up the sorted list and return the lowest, open, non-lava spawn point */
+ spot = NULL;
+ lowest = 999999;
+ pointWithLeastLava = NULL;
+
+ for (index = 0; index < numPoints; index++)
+ {
+ if (spawnPoints[index]->s.origin[2] < lavatop)
+ {
+ continue;
+ }
+
+ if (PlayersRangeFromSpot(spawnPoints[index]) > 32)
+ {
+ if (spawnPoints[index]->s.origin[2] < lowest)
+ {
+ /* save the last point */
+ pointWithLeastLava = spawnPoints[index];
+ lowest = spawnPoints[index]->s.origin[2];
+ }
+ }
+ }
+
+ /* well, we may telefrag someone, but oh well... */
+ if (pointWithLeastLava)
+ {
+ return pointWithLeastLava;
+ }
+
+ return NULL;
+}
+
+edict_t *
+SelectCoopSpawnPoint(edict_t *ent)
+{
+ int index;
+ edict_t *spot = NULL;
+ char *target;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ if (!Q_stricmp(level.mapname, "rmine2p") || !Q_stricmp(level.mapname, "rmine2"))
+ {
+ return SelectLavaCoopSpawnPoint(ent);
+ }
+
+ index = ent->client - game.clients;
+
+ /* player 0 starts in normal player spawn point */
+ if (!index)
+ {
+ return NULL;
+ }
+
+ spot = NULL;
+
+ /* assume there are four coop spots at each spawnpoint */
+ while (1)
+ {
+ spot = G_Find(spot, FOFS(classname), "info_player_coop");
+
+ if (!spot)
+ {
+ return NULL; /* we didn't have enough... */
+ }
+
+ target = spot->targetname;
+
+ if (!target)
+ {
+ target = "";
+ }
+
+ if (Q_stricmp(game.spawnpoint, target) == 0)
+ {
+ /* this is a coop spawn point for one of the clients here */
+ index--;
+
+ if (!index)
+ {
+ return spot; /* this is it */
+ }
+ }
+ }
+
+ return spot;
+}
+
+/*
+ * Chooses a player start, deathmatch start, coop start, etc
+ */
+void
+SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles)
+{
+ edict_t *spot = NULL;
+ edict_t *coopspot = NULL;
+ int index;
+ int counter = 0;
+ vec3_t d;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ spot = SelectDeathmatchSpawnPoint();
+ }
+ else if (coop->value)
+ {
+ spot = SelectCoopSpawnPoint(ent);
+ }
+
+ /* find a single player start spot */
+ if (!spot)
+ {
+ while ((spot = G_Find(spot, FOFS(classname), "info_player_start")) != NULL)
+ {
+ if (!game.spawnpoint[0] && !spot->targetname)
+ {
+ break;
+ }
+
+ if (!game.spawnpoint[0] || !spot->targetname)
+ {
+ continue;
+ }
+
+ if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
+ {
+ break;
+ }
+ }
+
+ if (!spot)
+ {
+ if (!game.spawnpoint[0])
+ {
+ /* there wasn't a spawnpoint without a target, so use any */
+ spot = G_Find(spot, FOFS(classname), "info_player_start");
+ }
+
+ if (!spot)
+ {
+ gi.error("Couldn't find spawn point %s\n", game.spawnpoint);
+ }
+ }
+ }
+
+ /* If we are in coop and we didn't find a coop
+ spawnpoint due to map bugs (not correctly
+ connected or the map was loaded via console
+ and thus no previously map is known to the
+ client) use one in 550 units radius. */
+ if (coop->value)
+ {
+ index = ent->client - game.clients;
+
+ if (Q_stricmp(spot->classname, "info_player_start") == 0 && index != 0)
+ {
+ while(counter < 3)
+ {
+ coopspot = G_Find(coopspot, FOFS(classname), "info_player_coop");
+
+ if (!coopspot)
+ {
+ break;
+ }
+
+ VectorSubtract(coopspot->s.origin, spot->s.origin, d);
+
+ if ((VectorLength(d) < 550))
+ {
+ if (index == counter)
+ {
+ spot = coopspot;
+ break;
+ }
+ else
+ {
+ counter++;
+ }
+ }
+ }
+ }
+ }
+
+ VectorCopy(spot->s.origin, origin);
+ origin[2] += 9;
+ VectorCopy(spot->s.angles, angles);
+}
+
+/* ====================================================================== */
+
+void
+InitBodyQue(void)
+{
+ int i;
+ edict_t *ent;
+
+ level.body_que = 0;
+
+ for (i = 0; i < BODY_QUEUE_SIZE; i++)
+ {
+ ent = G_Spawn();
+ ent->classname = "bodyque";
+ }
+}
+
+void
+body_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < -40)
+ {
+ gi.sound(self, CHAN_BODY, gi.soundindex( "misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ self->s.origin[2] -= 48;
+ ThrowClientHead(self, damage);
+ self->takedamage = DAMAGE_NO;
+ }
+}
+
+void
+CopyToBodyQue(edict_t *ent)
+{
+ edict_t *body;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* grab a body que and cycle to the next one */
+ body = &g_edicts[(int)maxclients->value + level.body_que + 1];
+ level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
+
+ gi.unlinkentity(ent);
+ gi.unlinkentity(body);
+ body->s = ent->s;
+ body->s.number = body - g_edicts;
+
+ body->svflags = ent->svflags;
+ VectorCopy(ent->mins, body->mins);
+ VectorCopy(ent->maxs, body->maxs);
+ VectorCopy(ent->absmin, body->absmin);
+ VectorCopy(ent->absmax, body->absmax);
+ VectorCopy(ent->size, body->size);
+ body->solid = ent->solid;
+ body->clipmask = ent->clipmask;
+ body->owner = ent->owner;
+ body->movetype = ent->movetype;
+
+ body->die = body_die;
+ body->takedamage = DAMAGE_YES;
+
+ gi.linkentity(body);
+}
+
+void
+respawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value || coop->value)
+ {
+ /* spectators don't leave bodies */
+ if (self->movetype != MOVETYPE_NOCLIP)
+ {
+ CopyToBodyQue(self);
+ }
+
+ self->svflags &= ~SVF_NOCLIENT;
+ PutClientInServer(self);
+
+ /* add a teleportation effect */
+ self->s.event = EV_PLAYER_TELEPORT;
+
+ /* hold in place briefly */
+ self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+ self->client->ps.pmove.pm_time = 14;
+
+ self->client->respawn_time = level.time;
+
+ return;
+ }
+
+ /* restart the entire server */
+ gi.AddCommandString("menu_loadgame\n");
+}
+
+/*
+ * only called when pers.spectator changes
+ * note that resp.spectator should be the
+ * opposite of pers.spectator here
+ */
+void
+spectator_respawn(edict_t *ent)
+{
+ int i, numspec;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* if the user wants to become a spectator, make sure
+ he doesn't exceed max_spectators */
+ if (ent->client->pers.spectator)
+ {
+ char *value = Info_ValueForKey(ent->client->pers.userinfo, "spectator");
+
+ if (*spectator_password->string &&
+ strcmp(spectator_password->string, "none") &&
+ strcmp(spectator_password->string, value))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n");
+ ent->client->pers.spectator = false;
+ gi.WriteByte(svc_stufftext);
+ gi.WriteString("spectator 0\n");
+ gi.unicast(ent, true);
+ return;
+ }
+
+ /* count spectators */
+ for (i = 1, numspec = 0; i <= maxclients->value; i++)
+ {
+ if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
+ {
+ numspec++;
+ }
+ }
+
+ if (numspec >= maxspectators->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full.");
+ ent->client->pers.spectator = false;
+ gi.WriteByte(svc_stufftext);
+ gi.WriteString("spectator 0\n");
+ gi.unicast(ent, true);
+ return;
+ }
+ }
+ else
+ {
+ char *value = Info_ValueForKey(ent->client->pers.userinfo, "password");
+
+ if (*password->string && strcmp(password->string, "none") &&
+ strcmp(password->string, value))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n");
+ ent->client->pers.spectator = true;
+ gi.WriteByte(svc_stufftext);
+ gi.WriteString("spectator 1\n");
+ gi.unicast(ent, true);
+ return;
+ }
+ }
+
+ /* clear score on respawn */
+ ent->client->pers.score = ent->client->resp.score = 0;
+
+ ent->svflags &= ~SVF_NOCLIENT;
+ PutClientInServer(ent);
+
+ /* add a teleportation effect */
+ if (!ent->client->pers.spectator)
+ {
+ /* send effect */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGIN);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ /* hold in place briefly */
+ ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+ ent->client->ps.pmove.pm_time = 14;
+ }
+
+ ent->client->respawn_time = level.time;
+
+ if (ent->client->pers.spectator)
+ {
+ gi.bprintf(PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
+ }
+ else
+ {
+ gi.bprintf(PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
+ }
+}
+
+/* ============================================================== */
+
+/*
+ * Called when a player connects to a
+ * server or respawns in a deathmatch.
+ */
+void
+PutClientInServer(edict_t *ent)
+{
+ vec3_t mins = {-16, -16, -24};
+ vec3_t maxs = {16, 16, 32};
+ int index;
+ vec3_t spawn_origin, spawn_angles;
+ gclient_t *client;
+ int i;
+ client_persistant_t saved;
+ client_respawn_t resp;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* find a spawn point. do it before setting health back
+ up, so farthest ranging doesn't count this client */
+ if (gamerules && gamerules->value && DMGame.SelectSpawnPoint)
+ {
+ DMGame.SelectSpawnPoint(ent, spawn_origin, spawn_angles);
+ }
+ else
+ {
+ SelectSpawnPoint(ent, spawn_origin, spawn_angles);
+ }
+
+ index = ent - g_edicts - 1;
+ client = ent->client;
+
+ /* deathmatch wipes most client data every spawn */
+ if (deathmatch->value)
+ {
+ char userinfo[MAX_INFO_STRING];
+
+ resp = client->resp;
+ memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
+ InitClientPersistant(client);
+ ClientUserinfoChanged(ent, userinfo);
+ }
+ else if (coop->value)
+ {
+ char userinfo[MAX_INFO_STRING];
+
+ resp = client->resp;
+ memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
+ resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged;
+ resp.coop_respawn.helpchanged = client->pers.helpchanged;
+ client->pers = resp.coop_respawn;
+ ClientUserinfoChanged(ent, userinfo);
+
+ if (resp.score > client->pers.score)
+ {
+ client->pers.score = resp.score;
+ }
+ }
+ else
+ {
+ memset(&resp, 0, sizeof(resp));
+ }
+
+ /* clear everything but the persistant data */
+ saved = client->pers;
+ memset(client, 0, sizeof(*client));
+ client->pers = saved;
+
+ if (client->pers.health <= 0)
+ {
+ InitClientPersistant(client);
+ }
+
+ client->resp = resp;
+
+ /* copy some data from the client to the entity */
+ FetchClientEntData(ent);
+
+ /* clear entity values */
+ ent->groundentity = NULL;
+ ent->client = &game.clients[index];
+ ent->takedamage = DAMAGE_AIM;
+ ent->movetype = MOVETYPE_WALK;
+ ent->viewheight = 22;
+ ent->inuse = true;
+ ent->classname = "player";
+ ent->mass = 200;
+ ent->solid = SOLID_BBOX;
+ ent->deadflag = DEAD_NO;
+ ent->air_finished = level.time + 12;
+ ent->clipmask = MASK_PLAYERSOLID;
+ ent->model = "players/male/tris.md2";
+ ent->pain = player_pain;
+ ent->die = player_die;
+ ent->waterlevel = 0;
+ ent->watertype = 0;
+ ent->flags &= ~FL_NO_KNOCKBACK;
+ ent->svflags = 0;
+
+ VectorCopy(mins, ent->mins);
+ VectorCopy(maxs, ent->maxs);
+ VectorClear(ent->velocity);
+
+ /* clear playerstate values */
+ memset(&ent->client->ps, 0, sizeof(client->ps));
+
+ client->ps.pmove.origin[0] = spawn_origin[0] * 8;
+ client->ps.pmove.origin[1] = spawn_origin[1] * 8;
+ client->ps.pmove.origin[2] = spawn_origin[2] * 8;
+
+ if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+ {
+ client->ps.fov = 90;
+ }
+ else
+ {
+ client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
+
+ if (client->ps.fov < 1)
+ {
+ client->ps.fov = 90;
+ }
+ else if (client->ps.fov > 160)
+ {
+ client->ps.fov = 160;
+ }
+ }
+
+ if (client->pers.weapon)
+ {
+ client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
+ }
+ else
+ {
+ client->ps.gunindex = 0;
+ }
+
+
+ /* clear entity state values */
+ ent->s.effects = 0;
+ ent->s.modelindex = 255; /* will use the skin specified model */
+ ent->s.modelindex2 = 255; /* custom gun model */
+ ent->s.skinnum = ent - g_edicts - 1;
+
+ ent->s.frame = 0;
+ VectorCopy(spawn_origin, ent->s.origin);
+ ent->s.origin[2] += 1; /* make sure off ground */
+ VectorCopy(ent->s.origin, ent->s.old_origin);
+
+ /* set the delta angle */
+ for (i = 0; i < 3; i++)
+ {
+ client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
+ }
+
+ ent->s.angles[PITCH] = 0;
+ ent->s.angles[YAW] = spawn_angles[YAW];
+ ent->s.angles[ROLL] = 0;
+ VectorCopy(ent->s.angles, client->ps.viewangles);
+ VectorCopy(ent->s.angles, client->v_angle);
+
+ /* spawn a spectator */
+ if (client->pers.spectator)
+ {
+ client->chase_target = NULL;
+
+ client->resp.spectator = true;
+
+ ent->movetype = MOVETYPE_NOCLIP;
+ ent->solid = SOLID_NOT;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->client->ps.gunindex = 0;
+ gi.linkentity(ent);
+ return;
+ }
+ else
+ {
+ client->resp.spectator = false;
+ }
+
+ if (!KillBox(ent))
+ {
+ /* could't spawn in? */
+ }
+
+ gi.linkentity(ent);
+
+ /* my tribute to cash's level-specific hacks. I hope
+ * live up to his trailblazing cheese. */
+ if (Q_stricmp(level.mapname, "rboss") == 0)
+ {
+ /* if you get on to rboss in single player or coop, ensure
+ the player has the nuke key. (not in DM) */
+ if (!(deathmatch->value))
+ {
+ gitem_t *item;
+
+ item = FindItem("Antimatter Bomb");
+ client->pers.selected_item = ITEM_INDEX(item);
+ client->pers.inventory[client->pers.selected_item] = 1;
+ }
+ }
+
+ /* force the current weapon up */
+ client->newweapon = client->pers.weapon;
+ ChangeWeapon(ent);
+}
+
+/*
+ * A client has just connected to the server in
+ * deathmatch mode, so clear everything out before
+ * starting them.
+ */
+void
+ClientBeginDeathmatch(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ G_InitEdict(ent);
+ InitClientResp(ent->client);
+
+ if (gamerules && gamerules->value && DMGame.ClientBegin)
+ {
+ DMGame.ClientBegin(ent);
+ }
+
+ /* locate ent at a spawn point */
+ PutClientInServer(ent);
+
+ if (level.intermissiontime)
+ {
+ MoveClientToIntermission(ent);
+ }
+ else
+ {
+ /* send effect */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGIN);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+ }
+
+ gi.bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+
+ /* make sure all view stuff is valid */
+ ClientEndServerFrame(ent);
+}
+
+/*
+ * called when a client has finished connecting, and is ready
+ * to be placed into the game. This will happen every level load.
+ */
+void
+ClientBegin(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client = game.clients + (ent - g_edicts - 1);
+
+ if (deathmatch->value)
+ {
+ ClientBeginDeathmatch(ent);
+ return;
+ }
+
+ /* if there is already a body waiting for us (a loadgame),
+ just take it, otherwise spawn one from scratch */
+ if (ent->inuse == true)
+ {
+ /* the client has cleared the client side viewangles upon
+ connecting to the server, which is different than the
+ state when the game is saved, so we need to compensate
+ with deltaangles */
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]);
+ }
+ }
+ else
+ {
+ /* a spawn point will completely reinitialize the entity
+ except for the persistant data that was initialized at
+ ClientConnect() time */
+ G_InitEdict(ent);
+ ent->classname = "player";
+ InitClientResp(ent->client);
+ PutClientInServer(ent);
+ }
+
+ if (level.intermissiontime)
+ {
+ MoveClientToIntermission(ent);
+ }
+ else
+ {
+ /* send effect if in a multiplayer game */
+ if (game.maxclients > 1)
+ {
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGIN);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ gi.bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+ }
+ }
+
+ /* make sure all view stuff is valid */
+ ClientEndServerFrame(ent);
+}
+
+/*
+ * called whenever the player updates a userinfo variable.
+ *
+ * The game can override any of the settings in place
+ * (forcing skins or names, etc) before copying it off.
+ */
+void
+ClientUserinfoChanged(edict_t *ent, char *userinfo)
+{
+ char *s;
+ int playernum;
+
+ if (!ent || !userinfo)
+ {
+ return;
+ }
+
+ /* check for malformed or illegal info strings */
+ if (!Info_Validate(userinfo))
+ {
+ strcpy(userinfo, "\\name\\badinfo\\skin\\male/grunt");
+ }
+
+ /* set name */
+ s = Info_ValueForKey(userinfo, "name");
+ strncpy(ent->client->pers.netname, s, sizeof(ent->client->pers.netname) -
+ 1);
+
+ /* set spectator */
+ s = Info_ValueForKey(userinfo, "spectator");
+
+ /* spectators are only supported in deathmatch */
+ if (deathmatch->value && *s && strcmp(s, "0"))
+ {
+ ent->client->pers.spectator = true;
+ }
+ else
+ {
+ ent->client->pers.spectator = false;
+ }
+
+ /* set skin */
+ s = Info_ValueForKey(userinfo, "skin");
+
+ playernum = ent - g_edicts - 1;
+
+ /* combine name and skin into a configstring */
+ gi.configstring(CS_PLAYERSKINS + playernum, va("%s\\%s", ent->client->pers.netname, s));
+
+ /* fov */
+ if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+ {
+ ent->client->ps.fov = 90;
+ }
+ else
+ {
+ ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
+
+ if (ent->client->ps.fov < 1)
+ {
+ ent->client->ps.fov = 90;
+ }
+ else if (ent->client->ps.fov > 160)
+ {
+ ent->client->ps.fov = 160;
+ }
+ }
+
+ /* handedness */
+ s = Info_ValueForKey(userinfo, "hand");
+
+ if (strlen(s))
+ {
+ ent->client->pers.hand = atoi(s);
+ }
+
+ /* save off the userinfo in case we want to check something later */
+ strncpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo) - 1);
+}
+
+/*
+ * Called when a player begins connecting to the server.
+ * The game can refuse entrance to a client by returning false.
+ * If the client is allowed, the connection process will continue
+ * and eventually get to ClientBegin()
+ * Changing levels will NOT cause this to be called again, but
+ * loadgames will.
+ */
+qboolean
+ClientConnect(edict_t *ent, char *userinfo)
+{
+ char *value;
+
+ if (!ent || !userinfo)
+ {
+ return false;
+ }
+
+ /* check to see if they are on the banned IP list */
+ value = Info_ValueForKey(userinfo, "ip");
+
+ if (SV_FilterPacket(value))
+ {
+ Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
+ return false;
+ }
+
+ /* check for a spectator */
+ value = Info_ValueForKey(userinfo, "spectator");
+
+ if (deathmatch->value && *value && strcmp(value, "0"))
+ {
+ int i, numspec;
+
+ if (*spectator_password->string &&
+ strcmp(spectator_password->string, "none") &&
+ strcmp(spectator_password->string, value))
+ {
+ Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
+ return false;
+ }
+
+ /* count spectators */
+ for (i = numspec = 0; i < maxclients->value; i++)
+ {
+ if (g_edicts[i + 1].inuse && g_edicts[i + 1].client->pers.spectator)
+ {
+ numspec++;
+ }
+ }
+
+ if (numspec >= maxspectators->value)
+ {
+ Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
+ return false;
+ }
+ }
+ else
+ {
+ /* check for a password */
+ value = Info_ValueForKey(userinfo, "password");
+
+ if (*password->string && strcmp(password->string, "none") &&
+ strcmp(password->string, value))
+ {
+ Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
+ return false;
+ }
+ }
+
+ /* they can connect */
+ ent->client = game.clients + (ent - g_edicts - 1);
+
+ /* if there is already a body waiting for us (a loadgame),
+ just take it, otherwise spawn one from scratch */
+ if (ent->inuse == false)
+ {
+ /* clear the respawning variables */
+ InitClientResp(ent->client);
+
+ if (!game.autosaved || !ent->client->pers.weapon)
+ {
+ InitClientPersistant(ent->client);
+ }
+ }
+
+ ClientUserinfoChanged(ent, userinfo);
+
+ if (game.maxclients > 1)
+ {
+ gi.dprintf("%s connected\n", ent->client->pers.netname);
+ }
+
+ ent->svflags = 0; /* make sure we start with known default */
+ ent->client->pers.connected = true;
+ return true;
+}
+
+/*
+ * Called when a player drops from the server.
+ * Will not be called between levels.
+ */
+void
+ClientDisconnect(edict_t *ent)
+{
+ int playernum;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client)
+ {
+ return;
+ }
+
+ gi.bprintf(PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
+
+ /* make sure no trackers are still hurting us. */
+ if (ent->client->tracker_pain_framenum)
+ {
+ RemoveAttackingPainDaemons(ent);
+ }
+
+ if (ent->client->owned_sphere)
+ {
+ if (ent->client->owned_sphere->inuse)
+ {
+ G_FreeEdict(ent->client->owned_sphere);
+ }
+
+ ent->client->owned_sphere = NULL;
+ }
+
+ if (gamerules && gamerules->value)
+ {
+ if (DMGame.PlayerDisconnect)
+ {
+ DMGame.PlayerDisconnect(ent);
+ }
+ }
+
+ /* send effect */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGOUT);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ gi.unlinkentity(ent);
+ ent->s.modelindex = 0;
+ ent->solid = SOLID_NOT;
+ ent->inuse = false;
+ ent->classname = "disconnected";
+ ent->client->pers.connected = false;
+
+ playernum = ent - g_edicts - 1;
+ gi.configstring(CS_PLAYERSKINS + playernum, "");
+}
+
+/* ============================================================== */
+
+trace_t
+PM_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
+{
+ if (pm_passent->health > 0)
+ {
+ return gi.trace(start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
+ }
+ else
+ {
+ return gi.trace(start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
+ }
+}
+
+unsigned
+CheckBlock(void *b, int c)
+{
+ int v, i;
+
+ if (!b)
+ {
+ return 0;
+ }
+
+ v = 0;
+
+ for (i = 0; i < c; i++)
+ {
+ v += ((byte *)b)[i];
+ }
+
+ return v;
+}
+
+void
+PrintPmove(pmove_t *pm)
+{
+ unsigned c1, c2;
+
+ if (!pm)
+ {
+ return;
+ }
+
+ c1 = CheckBlock(&pm->s, sizeof(pm->s));
+ c2 = CheckBlock(&pm->cmd, sizeof(pm->cmd));
+ Com_Printf("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
+}
+
+/*
+ * This will be called once for each client frame, which will
+ * usually be a couple times for each server frame.
+ */
+void
+ClientThink(edict_t *ent, usercmd_t *ucmd)
+{
+ gclient_t *client;
+ edict_t *other;
+ int i, j;
+ pmove_t pm;
+
+ if (!ent || !ucmd)
+ {
+ return;
+ }
+
+ level.current_entity = ent;
+ client = ent->client;
+
+ if (level.intermissiontime)
+ {
+ client->ps.pmove.pm_type = PM_FREEZE;
+
+ /* can exit intermission after five seconds */
+ if ((level.time > level.intermissiontime + 5.0) &&
+ (ucmd->buttons & BUTTON_ANY))
+ {
+ level.exitintermission = true;
+ }
+
+ return;
+ }
+
+ pm_passent = ent;
+
+ if (ent->client->chase_target)
+ {
+ client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+ client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+ client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+ }
+ else
+ {
+ /* set up for pmove */
+ memset(&pm, 0, sizeof(pm));
+
+ if (ent->movetype == MOVETYPE_NOCLIP)
+ {
+ client->ps.pmove.pm_type = PM_SPECTATOR;
+ }
+ else if (ent->s.modelindex != 255)
+ {
+ client->ps.pmove.pm_type = PM_GIB;
+ }
+ else if (ent->deadflag)
+ {
+ client->ps.pmove.pm_type = PM_DEAD;
+ }
+ else
+ {
+ client->ps.pmove.pm_type = PM_NORMAL;
+ }
+
+ client->ps.pmove.gravity = sv_gravity->value * ent->gravity;
+ pm.s = client->ps.pmove;
+
+ for (i = 0; i < 3; i++)
+ {
+ pm.s.origin[i] = ent->s.origin[i] * 8;
+ /* save to an int first, in case the short overflows
+ * so we get defined behavior (at least with -fwrapv) */
+ int tmpVel = ent->velocity[i] * 8;
+ pm.s.velocity[i] = tmpVel;
+ }
+
+ if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
+ {
+ pm.snapinitial = true;
+ }
+
+ pm.cmd = *ucmd;
+
+ pm.trace = PM_trace; /* adds default parms */
+ pm.pointcontents = gi.pointcontents;
+
+ /* perform a pmove */
+ gi.Pmove(&pm);
+
+ /* save results of pmove */
+ client->ps.pmove = pm.s;
+ client->old_pmove = pm.s;
+
+ for (i = 0; i < 3; i++)
+ {
+ ent->s.origin[i] = pm.s.origin[i] * 0.125;
+ ent->velocity[i] = pm.s.velocity[i] * 0.125;
+ }
+
+ VectorCopy(pm.mins, ent->mins);
+ VectorCopy(pm.maxs, ent->maxs);
+
+ client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+ client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+ client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+
+ if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex(
+ "*jump1.wav"), 1, ATTN_NORM, 0);
+ PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
+ }
+
+ if (ent->flags & FL_SAM_RAIMI)
+ {
+ ent->viewheight = 8;
+ }
+ else
+ {
+ ent->viewheight = pm.viewheight;
+ }
+
+ ent->waterlevel = pm.waterlevel;
+ ent->watertype = pm.watertype;
+ ent->groundentity = pm.groundentity;
+
+ if (pm.groundentity)
+ {
+ ent->groundentity_linkcount = pm.groundentity->linkcount;
+ }
+
+ if (ent->deadflag)
+ {
+ client->ps.viewangles[ROLL] = 40;
+ client->ps.viewangles[PITCH] = -15;
+ client->ps.viewangles[YAW] = client->killer_yaw;
+ }
+ else
+ {
+ VectorCopy(pm.viewangles, client->v_angle);
+ VectorCopy(pm.viewangles, client->ps.viewangles);
+ }
+
+ gi.linkentity(ent);
+
+ ent->gravity = 1.0;
+
+ if (ent->movetype != MOVETYPE_NOCLIP)
+ {
+ G_TouchTriggers(ent);
+ }
+
+ /* touch other objects */
+ for (i = 0; i < pm.numtouch; i++)
+ {
+ other = pm.touchents[i];
+
+ for (j = 0; j < i; j++)
+ {
+ if (pm.touchents[j] == other)
+ {
+ break;
+ }
+ }
+
+ if (j != i)
+ {
+ continue; /* duplicated */
+ }
+
+ if (!other->touch)
+ {
+ continue;
+ }
+
+ other->touch(other, ent, NULL, NULL);
+ }
+ }
+
+ client->oldbuttons = client->buttons;
+ client->buttons = ucmd->buttons;
+ client->latched_buttons |= client->buttons & ~client->oldbuttons;
+
+ /* save light level the player is
+ tanding on for monster sighting AI */
+ ent->light_level = ucmd->lightlevel;
+
+ /* fire weapon from final position if needed */
+ if (client->latched_buttons & BUTTON_ATTACK)
+ {
+ if (client->resp.spectator)
+ {
+ client->latched_buttons = 0;
+
+ if (client->chase_target)
+ {
+ client->chase_target = NULL;
+ client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+ }
+ else
+ {
+ GetChaseTarget(ent);
+ }
+ }
+ else if (!client->weapon_thunk)
+ {
+ client->weapon_thunk = true;
+ Think_Weapon(ent);
+ }
+ }
+
+ if (client->resp.spectator)
+ {
+ if (ucmd->upmove >= 10)
+ {
+ if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD))
+ {
+ client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
+
+ if (client->chase_target)
+ {
+ ChaseNext(ent);
+ }
+ else
+ {
+ GetChaseTarget(ent);
+ }
+ }
+ }
+ else
+ {
+ client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
+ }
+ }
+
+ /* update chase cam if being followed */
+ for (i = 1; i <= maxclients->value; i++)
+ {
+ other = g_edicts + i;
+
+ if (other->inuse && (other->client->chase_target == ent))
+ {
+ UpdateChaseCam(other);
+ }
+ }
+}
+
+/*
+ * This will be called once for each server frame,
+ * before running any other entities in the world.
+ */
+void
+ClientBeginServerFrame(edict_t *ent)
+{
+ gclient_t *client;
+ int buttonMask;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return;
+ }
+
+ client = ent->client;
+
+ if (deathmatch->value &&
+ (client->pers.spectator != client->resp.spectator) &&
+ ((level.time - client->respawn_time) >= 5))
+ {
+ spectator_respawn(ent);
+ return;
+ }
+
+ /* run weapon animations if it hasn't been done by a ucmd_t */
+ if (!client->weapon_thunk && !client->resp.spectator)
+ {
+ Think_Weapon(ent);
+ }
+ else
+ {
+ client->weapon_thunk = false;
+ }
+
+ if (ent->deadflag)
+ {
+ /* wait for any button just going down */
+ if (level.time > client->respawn_time)
+ {
+ /* in deathmatch, only wait for attack button */
+ if (deathmatch->value)
+ {
+ buttonMask = BUTTON_ATTACK;
+ }
+ else
+ {
+ buttonMask = -1;
+ }
+
+ if ((client->latched_buttons & buttonMask) ||
+ (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN)))
+ {
+ respawn(ent);
+ client->latched_buttons = 0;
+ }
+ }
+
+ return;
+ }
+
+ /* add player trail so monsters can follow */
+ if (!deathmatch->value)
+ {
+ if (!visible(ent, PlayerTrail_LastSpot()))
+ {
+ PlayerTrail_Add(ent->s.old_origin);
+ }
+ }
+
+ client->latched_buttons = 0;
+}
+
+/*
+ * This is called to clean up the pain daemons that
+ * the disruptor attaches to clients to damage them.
+ */
+void
+RemoveAttackingPainDaemons(edict_t *self)
+{
+ edict_t *tracker;
+
+ if (!self)
+ {
+ return;
+ }
+
+ tracker = G_Find(NULL, FOFS(classname), "pain daemon");
+
+ while (tracker)
+ {
+ if (tracker->enemy == self)
+ {
+ G_FreeEdict(tracker);
+ }
+
+ tracker = G_Find(tracker, FOFS(classname), "pain daemon");
+ }
+
+ if (self->client)
+ {
+ self->client->tracker_pain_framenum = 0;
+ }
+}
diff --git a/rogue/src/player/hud.c b/rogue/src/player/hud.c
new file mode 100644
index 0000000..3b68beb
--- /dev/null
+++ b/rogue/src/player/hud.c
@@ -0,0 +1,657 @@
+/* =======================================================================
+ *
+ * HUD, deathmatch scoreboard, help computer and intermission stuff.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+
+void
+MoveClientToIntermission(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value || coop->value)
+ {
+ ent->client->showscores = true;
+ }
+
+ VectorCopy(level.intermission_origin, ent->s.origin);
+ ent->client->ps.pmove.origin[0] = level.intermission_origin[0] * 8;
+ ent->client->ps.pmove.origin[1] = level.intermission_origin[1] * 8;
+ ent->client->ps.pmove.origin[2] = level.intermission_origin[2] * 8;
+ VectorCopy(level.intermission_angle, ent->client->ps.viewangles);
+ ent->client->ps.pmove.pm_type = PM_FREEZE;
+ ent->client->ps.gunindex = 0;
+ ent->client->ps.blend[3] = 0;
+ ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+ /* clean up powerup info */
+ ent->client->quad_framenum = 0;
+ ent->client->invincible_framenum = 0;
+ ent->client->breather_framenum = 0;
+ ent->client->enviro_framenum = 0;
+ ent->client->grenade_blew_up = false;
+ ent->client->grenade_time = 0;
+
+ ent->client->ps.rdflags &= ~RDF_IRGOGGLES;
+ ent->client->ir_framenum = 0;
+ ent->client->nuke_framenum = 0;
+ ent->client->double_framenum = 0;
+
+ ent->viewheight = 0;
+ ent->s.modelindex = 0;
+ ent->s.modelindex2 = 0;
+ ent->s.modelindex3 = 0;
+ ent->s.modelindex = 0;
+ ent->s.effects = 0;
+ ent->s.sound = 0;
+ ent->solid = SOLID_NOT;
+
+ gi.linkentity(ent);
+
+ /* add the layout */
+ if (deathmatch->value || coop->value)
+ {
+ DeathmatchScoreboardMessage(ent, NULL);
+ gi.unicast(ent, true);
+ }
+}
+
+void
+BeginIntermission(edict_t *targ)
+{
+ int i, n;
+ edict_t *ent, *client;
+
+ if (!targ)
+ {
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return; /* already activated */
+ }
+
+ game.autosaved = false;
+
+ /* respawn any dead clients */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ client = g_edicts + 1 + i;
+
+ if (!client->inuse)
+ {
+ continue;
+ }
+
+ if (client->health <= 0)
+ {
+ respawn(client);
+ }
+ }
+
+ level.intermissiontime = level.time;
+ level.changemap = targ->map;
+
+ if (strstr(level.changemap, "*"))
+ {
+ if (coop->value)
+ {
+ for (i = 0; i < maxclients->value; i++)
+ {
+ client = g_edicts + 1 + i;
+
+ if (!client->inuse)
+ {
+ continue;
+ }
+
+ /* strip players of all keys between units */
+ for (n = 0; n < game.num_items; n++)
+ {
+ if (itemlist[n].flags & IT_KEY)
+ {
+ client->client->pers.inventory[n] = 0;
+ }
+ }
+
+ client->client->pers.power_cubes = 0;
+ }
+ }
+ }
+ else
+ {
+ if (!deathmatch->value)
+ {
+ level.exitintermission = 1; /* go immediately to the next level */
+ return;
+ }
+ }
+
+ level.exitintermission = 0;
+
+ /* find an intermission spot */
+ ent = G_Find(NULL, FOFS(classname), "info_player_intermission");
+
+ if (!ent)
+ {
+ /* the map creator forgot to put in an intermission point... */
+ ent = G_Find(NULL, FOFS(classname), "info_player_start");
+
+ if (!ent)
+ {
+ ent = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
+ }
+ }
+ else
+ {
+ /* chose one of four spots */
+ i = rand() & 3;
+
+ while (i--)
+ {
+ ent = G_Find(ent, FOFS(classname), "info_player_intermission");
+
+ if (!ent) /* wrap around the list */
+ {
+ ent = G_Find(ent, FOFS(classname), "info_player_intermission");
+ }
+ }
+ }
+
+ VectorCopy(ent->s.origin, level.intermission_origin);
+ VectorCopy(ent->s.angles, level.intermission_angle);
+
+ /* move all clients to the intermission point */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ client = g_edicts + 1 + i;
+
+ if (!client->inuse)
+ {
+ continue;
+ }
+
+ MoveClientToIntermission(client);
+ }
+}
+
+void
+DeathmatchScoreboardMessage(edict_t *ent, edict_t *killer /* can be NULL */)
+{
+ char entry[1024];
+ char string[1400];
+ int stringlength;
+ int i, j, k;
+ int sorted[MAX_CLIENTS];
+ int sortedscores[MAX_CLIENTS];
+ int score, total;
+ int x, y;
+ gclient_t *cl;
+ edict_t *cl_ent;
+ char *tag;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* sort the clients by score */
+ total = 0;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ cl_ent = g_edicts + 1 + i;
+
+ if (!cl_ent->inuse || game.clients[i].resp.spectator)
+ {
+ continue;
+ }
+
+ score = game.clients[i].resp.score;
+
+ for (j = 0; j < total; j++)
+ {
+ if (score > sortedscores[j])
+ {
+ break;
+ }
+ }
+
+ for (k = total; k > j; k--)
+ {
+ sorted[k] = sorted[k - 1];
+ sortedscores[k] = sortedscores[k - 1];
+ }
+
+ sorted[j] = i;
+ sortedscores[j] = score;
+ total++;
+ }
+
+ /* print level name and exit rules */
+ string[0] = 0;
+
+ stringlength = strlen(string);
+
+ /* add the clients in sorted order */
+ if (total > 12)
+ {
+ total = 12;
+ }
+
+ for (i = 0; i < total; i++)
+ {
+ cl = &game.clients[sorted[i]];
+ cl_ent = g_edicts + 1 + sorted[i];
+
+ x = (i >= 6) ? 160 : 0;
+ y = 32 + 32 * (i % 6);
+
+ /* add a dogtag */
+ if (cl_ent == ent)
+ {
+ tag = "tag1";
+ }
+ else if (cl_ent == killer)
+ {
+ tag = "tag2";
+ }
+ else
+ {
+ tag = NULL;
+ }
+
+ /* allow new DM games to override the tag picture */
+ if (gamerules && gamerules->value)
+ {
+ if (DMGame.DogTag)
+ {
+ DMGame.DogTag(cl_ent, killer, &tag);
+ }
+ }
+
+ if (tag)
+ {
+ Com_sprintf(entry, sizeof(entry), "xv %i yv %i picn %s ", x + 32, y, tag);
+ j = strlen(entry);
+
+ if (stringlength + j > 1024)
+ {
+ break;
+ }
+
+ strcpy(string + stringlength, entry);
+ stringlength += j;
+ }
+
+ /* send the layout */
+ Com_sprintf(entry, sizeof(entry), "client %i %i %i %i %i %i ",
+ x, y, sorted[i], cl->resp.score, cl->ping,
+ (level.framenum - cl->resp.enterframe) / 600);
+ j = strlen(entry);
+
+ if (stringlength + j > 1024)
+ {
+ break;
+ }
+
+ strcpy(string + stringlength, entry);
+ stringlength += j;
+ }
+
+ gi.WriteByte(svc_layout);
+ gi.WriteString(string);
+}
+
+/*
+ * Draw help computer.
+ */
+void
+HelpComputerMessage(edict_t *ent)
+{
+ char string[1024];
+ char *sk;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ sk = "easy";
+ }
+ else if (skill->value == SKILL_MEDIUM)
+ {
+ sk = "medium";
+ }
+ else if (skill->value == SKILL_HARD)
+ {
+ sk = "hard";
+ }
+ else
+ {
+ sk = "hard+";
+ }
+
+ /* send the layout */
+ Com_sprintf(string, sizeof(string),
+ "xv 32 yv 8 picn help " /* background */
+ "xv 202 yv 12 string2 \"%s\" " /* skill */
+ "xv 0 yv 24 cstring2 \"%s\" " /* level name */
+ "xv 0 yv 54 cstring2 \"%s\" " /* help 1 */
+ "xv 0 yv 110 cstring2 \"%s\" " /* help 2 */
+ "xv 50 yv 164 string2 \" kills goals secrets\" "
+ "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
+ sk, level.level_name,
+ game.helpmessage1,
+ game.helpmessage2,
+ level.killed_monsters, level.total_monsters,
+ level.found_goals, level.total_goals,
+ level.found_secrets, level.total_secrets);
+
+ gi.WriteByte(svc_layout);
+ gi.WriteString(string);
+}
+
+/*
+ * Display the current help message
+ */
+void
+InventoryMessage(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_inventory);
+
+ for (i = 0; i < MAX_ITEMS; i++)
+ {
+ gi.WriteShort(ent->client->pers.inventory[i]);
+ }
+}
+
+/* ======================================================================= */
+
+void
+G_SetStats(edict_t *ent)
+{
+ gitem_t *item;
+ int index, cells;
+ int power_armor_type;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* health */
+ ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
+ ent->client->ps.stats[STAT_HEALTH] = (ent->health < -99) ? -99 : ent->health;
+
+ /* ammo */
+ if (!ent->client->ammo_index)
+ {
+ ent->client->ps.stats[STAT_AMMO_ICON] = 0;
+ ent->client->ps.stats[STAT_AMMO] = 0;
+ }
+ else
+ {
+ item = &itemlist[ent->client->ammo_index];
+ ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex(item->icon);
+ ent->client->ps.stats[STAT_AMMO] =
+ ent->client->pers.inventory[ent->client->ammo_index];
+ }
+
+ /* armor */
+ power_armor_type = PowerArmorType(ent);
+
+ if (power_armor_type)
+ {
+ cells = ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))];
+
+ if (cells == 0)
+ {
+ /* ran out of cells for power armor */
+ ent->flags &= ~FL_POWER_ARMOR;
+ gi.sound(ent, CHAN_ITEM, gi.soundindex( "misc/power2.wav"), 1, ATTN_NORM, 0);
+ power_armor_type = 0;
+ }
+ }
+
+ index = ArmorIndex(ent);
+
+ if (power_armor_type && (!index || (level.framenum & 8)))
+ {
+ /* flash between power armor and other armor icon */
+ ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex("i_powershield");
+ ent->client->ps.stats[STAT_ARMOR] = cells;
+ }
+ else if (index)
+ {
+ item = GetItemByIndex(index);
+ ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex(item->icon);
+ ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
+ ent->client->ps.stats[STAT_ARMOR] = 0;
+ }
+
+ /* pickup message */
+ if (level.time > ent->client->pickup_msg_time)
+ {
+ ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
+ ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
+ }
+
+ /* timers */
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_quad");
+ ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->double_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_double");
+ ent->client->ps.stats[STAT_TIMER] =
+ (ent->client->double_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->invincible_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex( "p_invulnerability");
+ ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->enviro_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_envirosuit");
+ ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->breather_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_rebreather");
+ ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->owned_sphere)
+ {
+ if (ent->client->owned_sphere->spawnflags == 1) /* defender */
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] =
+ gi.imageindex("p_defender");
+ }
+ else if (ent->client->owned_sphere->spawnflags == 2) /* hunter */
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_hunter");
+ }
+ else if (ent->client->owned_sphere->spawnflags == 4) /* vengeance */
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] =
+ gi.imageindex("p_vengeance");
+ }
+ else /* error case */
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("i_fixme");
+ }
+
+ ent->client->ps.stats[STAT_TIMER] =
+ (int)(ent->client->owned_sphere->wait - level.time);
+ }
+ else if (ent->client->ir_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_ir");
+ ent->client->ps.stats[STAT_TIMER] =
+ (ent->client->ir_framenum - level.framenum) / 10; }
+ else
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = 0;
+ ent->client->ps.stats[STAT_TIMER] = 0;
+ }
+
+ /* selected item */
+ if (ent->client->pers.selected_item == -1)
+ {
+ ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex(itemlist[ent->client->pers.selected_item].icon);
+ }
+
+ ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
+
+ /* layouts */
+ ent->client->ps.stats[STAT_LAYOUTS] = 0;
+
+ if (deathmatch->value)
+ {
+ if ((ent->client->pers.health <= 0) || level.intermissiontime || ent->client->showscores)
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+ }
+
+ if (ent->client->showinventory && (ent->client->pers.health > 0))
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+ }
+ }
+ else
+ {
+ if (ent->client->showscores || ent->client->showhelp)
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+ }
+
+ if (ent->client->showinventory && (ent->client->pers.health > 0))
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+ }
+ }
+
+ /* frags */
+ ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
+
+ /* help icon / current weapon if not shown */
+ if (ent->client->pers.helpchanged && (level.framenum & 8))
+ {
+ ent->client->ps.stats[STAT_HELPICON] = gi.imageindex("i_help");
+ }
+ else if (((ent->client->pers.hand == CENTER_HANDED) ||
+ (ent->client->ps.fov > 91)) && ent->client->pers.weapon)
+ {
+ cvar_t *gun;
+ gun = gi.cvar("cl_gun", "2", 0);
+
+ if (gun->value != 2)
+ {
+ ent->client->ps.stats[STAT_HELPICON] = gi.imageindex(ent->client->pers.weapon->icon);
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_HELPICON] = 0;
+ }
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_HELPICON] = 0;
+ }
+
+ ent->client->ps.stats[STAT_SPECTATOR] = 0;
+}
+
+void
+G_CheckChaseStats(edict_t *ent)
+{
+ int i;
+ gclient_t *cl;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ for (i = 1; i <= maxclients->value; i++)
+ {
+ cl = g_edicts[i].client;
+
+ if (!g_edicts[i].inuse || (cl->chase_target != ent))
+ {
+ continue;
+ }
+
+ memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats));
+ G_SetSpectatorStats(g_edicts + i);
+ }
+}
+
+void
+G_SetSpectatorStats(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gclient_t *cl = ent->client;
+
+ if (!cl->chase_target)
+ {
+ G_SetStats(ent);
+ }
+
+ cl->ps.stats[STAT_SPECTATOR] = 1;
+
+ /* layouts are independant in spectator */
+ cl->ps.stats[STAT_LAYOUTS] = 0;
+
+ if ((cl->pers.health <= 0) || level.intermissiontime || cl->showscores)
+ {
+ cl->ps.stats[STAT_LAYOUTS] |= 1;
+ }
+
+ if (cl->showinventory && (cl->pers.health > 0))
+ {
+ cl->ps.stats[STAT_LAYOUTS] |= 2;
+ }
+
+ if (cl->chase_target && cl->chase_target->inuse)
+ {
+ cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS +
+ (cl->chase_target - g_edicts) - 1;
+ }
+ else
+ {
+ cl->ps.stats[STAT_CHASE] = 0;
+ }
+}
diff --git a/rogue/src/player/trail.c b/rogue/src/player/trail.c
new file mode 100644
index 0000000..eef2dd5
--- /dev/null
+++ b/rogue/src/player/trail.c
@@ -0,0 +1,156 @@
+/* =======================================================================
+ *
+ * The player trail, used by monsters to locate the player.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+
+/*
+ * This is a circular list containing the a list of points of where
+ * the player has been recently. It is used by monsters for pursuit.
+ *
+ * .origin the spot
+ * .owner forward link
+ * .aiment backward link
+ */
+
+#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1))
+#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1))
+
+#define TRAIL_LENGTH 8
+
+edict_t *trail[TRAIL_LENGTH];
+int trail_head;
+qboolean trail_active = false;
+
+void
+PlayerTrail_Init(void)
+{
+ int n;
+
+ if (deathmatch->value)
+ {
+ return;
+ }
+
+ for (n = 0; n < TRAIL_LENGTH; n++)
+ {
+ trail[n] = G_Spawn();
+ trail[n]->classname = "player_trail";
+ }
+
+ trail_head = 0;
+ trail_active = true;
+}
+
+void
+PlayerTrail_Add(vec3_t spot)
+{
+ vec3_t temp;
+
+ if (!trail_active)
+ {
+ return;
+ }
+
+ VectorCopy(spot, trail[trail_head]->s.origin);
+
+ trail[trail_head]->timestamp = level.time;
+
+ VectorSubtract(spot, trail[PREV(trail_head)]->s.origin, temp);
+ trail[trail_head]->s.angles[1] = vectoyaw(temp);
+
+ trail_head = NEXT(trail_head);
+}
+
+void
+PlayerTrail_New(vec3_t spot)
+{
+ if (!trail_active)
+ {
+ return;
+ }
+
+ PlayerTrail_Init();
+ PlayerTrail_Add(spot);
+}
+
+edict_t *
+PlayerTrail_PickFirst(edict_t *self)
+{
+ int marker;
+ int n;
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ if (!trail_active)
+ {
+ return NULL;
+ }
+
+ for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+ {
+ if (trail[marker]->timestamp <= self->monsterinfo.trail_time)
+ {
+ marker = NEXT(marker);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (visible(self, trail[marker]))
+ {
+ return trail[marker];
+ }
+
+ if (visible(self, trail[PREV(marker)]))
+ {
+ return trail[PREV(marker)];
+ }
+
+ return trail[marker];
+}
+
+edict_t *
+PlayerTrail_PickNext(edict_t *self)
+{
+ int marker;
+ int n;
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ if (!trail_active)
+ {
+ return NULL;
+ }
+
+ for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+ {
+ if (trail[marker]->timestamp <= self->monsterinfo.trail_time)
+ {
+ marker = NEXT(marker);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return trail[marker];
+}
+
+edict_t *
+PlayerTrail_LastSpot(void)
+{
+ return trail[PREV(trail_head)];
+}
diff --git a/rogue/src/player/view.c b/rogue/src/player/view.c
new file mode 100644
index 0000000..2d89b07
--- /dev/null
+++ b/rogue/src/player/view.c
@@ -0,0 +1,1482 @@
+/* =======================================================================
+ *
+ * The "camera" through that the player looks into the game.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+#include "../monster/misc/player.h"
+
+static edict_t *current_player;
+static gclient_t *current_client;
+
+static vec3_t forward, right, up;
+float xyspeed;
+
+float bobmove;
+int bobcycle;
+float bobfracsin;
+
+float
+SV_CalcRoll(vec3_t angles, vec3_t velocity)
+{
+ float sign;
+ float side;
+ float value;
+
+ side = DotProduct(velocity, right);
+ sign = side < 0 ? -1 : 1;
+ side = fabs(side);
+
+ value = sv_rollangle->value;
+
+ if (side < sv_rollspeed->value)
+ {
+ side = side * value / sv_rollspeed->value;
+ }
+ else
+ {
+ side = value;
+ }
+
+ return side * sign;
+}
+
+/*
+ * Handles color blends and view kicks
+ */
+void
+P_DamageFeedback(edict_t *player)
+{
+ gclient_t *client;
+ float side;
+ float realcount, count, kick;
+ vec3_t v;
+ int r, l;
+ static vec3_t power_color = {0.0, 1.0, 0.0};
+ static vec3_t acolor = {1.0, 1.0, 1.0};
+ static vec3_t bcolor = {1.0, 0.0, 0.0};
+
+ if (!player)
+ {
+ return;
+ }
+
+ /* death/gib sound is now aggregated and played here */
+ if (player->sounds)
+ {
+ gi.sound (player, CHAN_VOICE, player->sounds, 1, ATTN_NORM, 0);
+ player->sounds = 0;
+ }
+
+ client = player->client;
+
+ /* flash the backgrounds behind the status numbers */
+ client->ps.stats[STAT_FLASHES] = 0;
+
+ if (client->damage_blood)
+ {
+ client->ps.stats[STAT_FLASHES] |= 1;
+ }
+
+ if (client->damage_armor && !(player->flags & FL_GODMODE) &&
+ (client->invincible_framenum <= level.framenum))
+ {
+ client->ps.stats[STAT_FLASHES] |= 2;
+ }
+
+ /* total points of damage shot at the player this frame */
+ count = (client->damage_blood + client->damage_armor + client->damage_parmor);
+
+ if (count == 0)
+ {
+ return; /* didn't take any damage */
+ }
+
+ /* start a pain animation if still in the player model */
+ if ((client->anim_priority < ANIM_PAIN) && (player->s.modelindex == 255))
+ {
+ static int i;
+
+ client->anim_priority = ANIM_PAIN;
+
+ if (client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ player->s.frame = FRAME_crpain1 - 1;
+ client->anim_end = FRAME_crpain4;
+ }
+ else
+ {
+ i = (i + 1) % 3;
+
+ switch (i)
+ {
+ case 0:
+ player->s.frame = FRAME_pain101 - 1;
+ client->anim_end = FRAME_pain104;
+ break;
+ case 1:
+ player->s.frame = FRAME_pain201 - 1;
+ client->anim_end = FRAME_pain204;
+ break;
+ case 2:
+ player->s.frame = FRAME_pain301 - 1;
+ client->anim_end = FRAME_pain304;
+ break;
+ }
+ }
+ }
+
+ realcount = count;
+
+ if (count < 10)
+ {
+ count = 10; /* always make a visible effect */
+ }
+
+ /* play an apropriate pain sound */
+ if ((level.time > player->pain_debounce_time) &&
+ !(player->flags & FL_GODMODE) &&
+ (client->invincible_framenum <= level.framenum) &&
+ player->health > 0)
+ {
+ r = 1 + (rand() & 1);
+ player->pain_debounce_time = level.time + 0.7;
+
+ if (player->health < 25)
+ {
+ l = 25;
+ }
+ else if (player->health < 50)
+ {
+ l = 50;
+ }
+ else if (player->health < 75)
+ {
+ l = 75;
+ }
+ else
+ {
+ l = 100;
+ }
+
+ gi.sound(player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0);
+ }
+
+ /* the total alpha of the blend is always proportional to count */
+ if (client->damage_alpha < 0)
+ {
+ client->damage_alpha = 0;
+ }
+
+ client->damage_alpha += count * 0.01;
+
+ if (client->damage_alpha < 0.2)
+ {
+ client->damage_alpha = 0.2;
+ }
+
+ if (client->damage_alpha > 0.6)
+ {
+ client->damage_alpha = 0.6; /* don't go too saturated */
+ }
+
+ /* the color of the blend will vary based on how
+ much was absorbed by different armors */
+ VectorClear(v);
+
+ if (client->damage_parmor)
+ {
+ VectorMA(v, (float)client->damage_parmor / realcount, power_color, v);
+ }
+
+ if (client->damage_armor)
+ {
+ VectorMA(v, (float)client->damage_armor / realcount, acolor, v);
+ }
+
+ if (client->damage_blood)
+ {
+ VectorMA(v, (float)client->damage_blood / realcount, bcolor, v);
+ }
+
+ VectorCopy(v, client->damage_blend);
+
+ /* calculate view angle kicks */
+ kick = abs(client->damage_knockback);
+
+ if (kick && (player->health > 0)) /* kick of 0 means no view adjust at all */
+ {
+ kick = kick * 100 / player->health;
+
+ if (kick < count * 0.5)
+ {
+ kick = count * 0.5;
+ }
+
+ if (kick > 50)
+ {
+ kick = 50;
+ }
+
+ VectorSubtract(client->damage_from, player->s.origin, v);
+ VectorNormalize(v);
+
+ side = DotProduct(v, right);
+ client->v_dmg_roll = kick * side * 0.3;
+
+ side = -DotProduct(v, forward);
+ client->v_dmg_pitch = kick * side * 0.3;
+
+ client->v_dmg_time = level.time + DAMAGE_TIME;
+ }
+
+ /* clear totals */
+ client->damage_blood = 0;
+ client->damage_armor = 0;
+ client->damage_parmor = 0;
+ client->damage_knockback = 0;
+}
+
+/*
+ * Auto pitching on slopes?
+ *
+ * fall from 128: 400 = 160000
+ * fall from 256: 580 = 336400
+ * fall from 384: 720 = 518400
+ * fall from 512: 800 = 640000
+ * fall from 640: 960 =
+ *
+ * damage = deltavelocity*deltavelocity * 0.0001
+ */
+void
+SV_CalcViewOffset(edict_t *ent)
+{
+ float *angles;
+ float bob;
+ float ratio;
+ float delta;
+ vec3_t v;
+
+ if (!ent)
+ {
+ return;
+ }
+
+
+ /* base angles */
+ angles = ent->client->ps.kick_angles;
+
+ /* if dead, fix the angle and don't add any kick */
+ if (ent->deadflag)
+ {
+ VectorClear(angles);
+
+ if (ent->flags & FL_SAM_RAIMI)
+ {
+ ent->client->ps.viewangles[ROLL] = 0;
+ ent->client->ps.viewangles[PITCH] = 0;
+ }
+ else
+ {
+ ent->client->ps.viewangles[ROLL] = 40;
+ ent->client->ps.viewangles[PITCH] = -15;
+ }
+
+ ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;
+ }
+ else
+ {
+ /* add angles based on weapon kick */
+ VectorCopy(ent->client->kick_angles, angles);
+
+ /* add angles based on damage kick */
+ ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME;
+
+ if (ratio < 0)
+ {
+ ratio = 0;
+ ent->client->v_dmg_pitch = 0;
+ ent->client->v_dmg_roll = 0;
+ }
+
+ angles[PITCH] += ratio * ent->client->v_dmg_pitch;
+ angles[ROLL] += ratio * ent->client->v_dmg_roll;
+
+ /* add pitch based on fall kick */
+ ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+
+ if (ratio < 0)
+ {
+ ratio = 0;
+ }
+
+ angles[PITCH] += ratio * ent->client->fall_value;
+
+ /* add angles based on velocity */
+ delta = DotProduct(ent->velocity, forward);
+ angles[PITCH] += delta * run_pitch->value;
+
+ delta = DotProduct(ent->velocity, right);
+ angles[ROLL] += delta * run_roll->value;
+
+ /* add angles based on bob */
+ delta = bobfracsin * bob_pitch->value * xyspeed;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ delta *= 6; /* crouching */
+ }
+
+ angles[PITCH] += delta;
+ delta = bobfracsin * bob_roll->value * xyspeed;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ delta *= 6; /* crouching */
+ }
+
+ if (bobcycle & 1)
+ {
+ delta = -delta;
+ }
+
+ angles[ROLL] += delta;
+ }
+
+ /* =================================== */
+
+ /* base origin */
+ VectorClear(v);
+
+ /* add view height */
+ v[2] += ent->viewheight;
+
+ /* add fall height */
+ ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+
+ if (ratio < 0)
+ {
+ ratio = 0;
+ }
+
+ v[2] -= ratio * ent->client->fall_value * 0.4;
+
+ /* add bob height */
+ bob = bobfracsin * xyspeed * bob_up->value;
+
+ if (bob > 6)
+ {
+ bob = 6;
+ }
+
+ v[2] += bob;
+
+ /* add kick offset */
+ VectorAdd(v, ent->client->kick_origin, v);
+
+ /* absolutely bound offsets so the view can
+ never be outside the player box */
+
+ if (v[0] < -14)
+ {
+ v[0] = -14;
+ }
+ else if (v[0] > 14)
+ {
+ v[0] = 14;
+ }
+
+ if (v[1] < -14)
+ {
+ v[1] = -14;
+ }
+ else if (v[1] > 14)
+ {
+ v[1] = 14;
+ }
+
+ if (v[2] < -22)
+ {
+ v[2] = -22;
+ }
+ else if (v[2] > 30)
+ {
+ v[2] = 30;
+ }
+
+ VectorCopy(v, ent->client->ps.viewoffset);
+}
+
+void
+SV_CalcGunOffset(edict_t *ent)
+{
+ int i;
+ float delta;
+ static gitem_t *heatbeam;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!heatbeam)
+ {
+ heatbeam = FindItemByClassname("weapon_plasmabeam");
+ }
+
+ /* heatbeam shouldn't bob so the beam looks right */
+ if (ent->client->pers.weapon != heatbeam)
+ {
+ /* gun angles from bobbing */
+ ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005;
+ ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01;
+
+ if (bobcycle & 1)
+ {
+ ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL];
+ ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW];
+ }
+
+ ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005;
+
+ /* gun angles from delta movement */
+ for (i = 0; i < 3; i++)
+ {
+ delta = ent->client->oldviewangles[i] -
+ ent->client->ps.viewangles[i];
+
+ if (delta > 180)
+ {
+ delta -= 360;
+ }
+
+ if (delta < -180)
+ {
+ delta += 360;
+ }
+
+ if (delta > 45)
+ {
+ delta = 45;
+ }
+
+ if (delta < -45)
+ {
+ delta = -45;
+ }
+
+ if (i == YAW)
+ {
+ ent->client->ps.gunangles[ROLL] += 0.1 * delta;
+ }
+
+ ent->client->ps.gunangles[i] += 0.2 * delta;
+ }
+ }
+ else
+ {
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->ps.gunangles[i] = 0;
+ }
+ }
+
+ /* gun height */
+ VectorClear(ent->client->ps.gunoffset);
+
+ /* gun_x / gun_y / gun_z are development tools */
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->ps.gunoffset[i] += forward[i] * (gun_y->value);
+ ent->client->ps.gunoffset[i] += right[i] * gun_x->value;
+ ent->client->ps.gunoffset[i] += up[i] * (-gun_z->value);
+ }
+}
+
+void
+SV_AddBlend(float r, float g, float b, float a, float *v_blend)
+{
+ float a2, a3;
+
+ if (!v_blend)
+ {
+ return;
+ }
+
+ if (a <= 0)
+ {
+ return;
+ }
+
+ a2 = v_blend[3] + (1 - v_blend[3]) * a; /* new total alpha */
+ a3 = v_blend[3] / a2; /* fraction of color from old */
+
+ v_blend[0] = v_blend[0] * a3 + r * (1 - a3);
+ v_blend[1] = v_blend[1] * a3 + g * (1 - a3);
+ v_blend[2] = v_blend[2] * a3 + b * (1 - a3);
+ v_blend[3] = a2;
+}
+
+void
+SV_CalcBlend(edict_t *ent)
+{
+ int contents;
+ vec3_t vieworg;
+ int remaining;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->ps.blend[0] = ent->client->ps.blend[1] = ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0;
+
+ /* add for contents */
+ VectorAdd(ent->s.origin, ent->client->ps.viewoffset, vieworg);
+ contents = gi.pointcontents(vieworg);
+
+ if (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER))
+ {
+ ent->client->ps.rdflags |= RDF_UNDERWATER;
+ }
+ else
+ {
+ ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+ }
+
+ if (contents & (CONTENTS_SOLID | CONTENTS_LAVA))
+ {
+ SV_AddBlend(1.0, 0.3, 0.0, 0.6, ent->client->ps.blend);
+ }
+ else if (contents & CONTENTS_SLIME)
+ {
+ SV_AddBlend(0.0, 0.1, 0.05, 0.6, ent->client->ps.blend);
+ }
+ else if (contents & CONTENTS_WATER)
+ {
+ SV_AddBlend(0.5, 0.3, 0.2, 0.4, ent->client->ps.blend);
+ }
+
+ /* add for powerups */
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ remaining = ent->client->quad_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(0, 0, 1, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->double_framenum > level.framenum)
+ {
+ remaining = ent->client->double_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(0.9, 0.7, 0, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->invincible_framenum > level.framenum)
+ {
+ remaining = ent->client->invincible_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(1, 1, 0, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->enviro_framenum > level.framenum)
+ {
+ remaining = ent->client->enviro_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(0, 1, 0, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->breather_framenum > level.framenum)
+ {
+ remaining = ent->client->breather_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(0.4, 1, 0.4, 0.04, ent->client->ps.blend);
+ }
+ }
+
+ if (ent->client->nuke_framenum > level.framenum)
+ {
+ float brightness;
+ brightness = (ent->client->nuke_framenum - level.framenum) / 20.0;
+ SV_AddBlend(1, 1, 1, brightness, ent->client->ps.blend);
+ }
+
+ if (ent->client->ir_framenum > level.framenum)
+ {
+ remaining = ent->client->ir_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->client->ps.rdflags |= RDF_IRGOGGLES;
+ SV_AddBlend(1, 0, 0, 0.2, ent->client->ps.blend);
+ }
+ else
+ {
+ ent->client->ps.rdflags &= ~RDF_IRGOGGLES;
+ }
+ }
+ else
+ {
+ ent->client->ps.rdflags &= ~RDF_IRGOGGLES;
+ }
+
+ /* add for damage */
+ if (ent->client->damage_alpha > 0)
+ {
+ SV_AddBlend(ent->client->damage_blend[0], ent->client->damage_blend[1],
+ ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend);
+ }
+
+ if (ent->client->bonus_alpha > 0)
+ {
+ SV_AddBlend(0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend);
+ }
+
+ /* drop the damage value */
+ ent->client->damage_alpha -= 0.06;
+
+ if (ent->client->damage_alpha < 0)
+ {
+ ent->client->damage_alpha = 0;
+ }
+
+ /* drop the bonus value */
+ ent->client->bonus_alpha -= 0.1;
+
+ if (ent->client->bonus_alpha < 0)
+ {
+ ent->client->bonus_alpha = 0;
+ }
+}
+
+void
+P_FallingDamage(edict_t *ent)
+{
+ float delta;
+ int damage;
+ vec3_t dir;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.modelindex != 255)
+ {
+ return; /* not in the player model */
+ }
+
+ if (ent->movetype == MOVETYPE_NOCLIP)
+ {
+ return;
+ }
+
+ if ((ent->client->oldvelocity[2] < 0) &&
+ (ent->velocity[2] > ent->client->oldvelocity[2]) &&
+ (!ent->groundentity))
+ {
+ delta = ent->client->oldvelocity[2];
+ }
+ else
+ {
+ if (!ent->groundentity)
+ {
+ return;
+ }
+
+ delta = ent->velocity[2] - ent->client->oldvelocity[2];
+ }
+
+ delta = delta * delta * 0.0001;
+
+ /* never take falling damage if completely underwater */
+ if (ent->waterlevel == 3)
+ {
+ return;
+ }
+
+ if (ent->waterlevel == 2)
+ {
+ delta *= 0.25;
+ }
+
+ if (ent->waterlevel == 1)
+ {
+ delta *= 0.5;
+ }
+
+ if (delta < 1)
+ {
+ return;
+ }
+
+ if (delta < 15)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ return;
+ }
+
+ ent->client->fall_value = delta * 0.5;
+
+ if (ent->client->fall_value > 40)
+ {
+ ent->client->fall_value = 40;
+ }
+
+ ent->client->fall_time = level.time + FALL_TIME;
+
+ if (delta > 30)
+ {
+ if (ent->health > 0)
+ {
+ if (delta >= 55)
+ {
+ ent->s.event = EV_FALLFAR;
+ }
+ else
+ {
+ ent->s.event = EV_FALL;
+ }
+ }
+
+ ent->pain_debounce_time = level.time; /* no normal pain sound */
+ damage = (delta - 30) / 2;
+
+ if (damage < 1)
+ {
+ damage = 1;
+ }
+
+ VectorSet(dir, 0, 0, 1);
+
+ if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING))
+ {
+ T_Damage(ent, world, world, dir, ent->s.origin, vec3_origin,
+ damage, 0, 0, MOD_FALLING);
+ }
+ }
+ else
+ {
+ ent->s.event = EV_FALLSHORT;
+ return;
+ }
+}
+
+void
+P_WorldEffects(void)
+{
+ qboolean breather;
+ qboolean envirosuit;
+ int waterlevel, old_waterlevel;
+
+ if (current_player->movetype == MOVETYPE_NOCLIP)
+ {
+ current_player->air_finished = level.time + 12; /* don't need air */
+ return;
+ }
+
+ waterlevel = current_player->waterlevel;
+ old_waterlevel = current_client->old_waterlevel;
+ current_client->old_waterlevel = waterlevel;
+
+ breather = current_client->breather_framenum > level.framenum;
+ envirosuit = current_client->enviro_framenum > level.framenum;
+
+ /* if just entered a water volume, play a sound */
+ if (!old_waterlevel && waterlevel)
+ {
+ PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+
+ if (current_player->watertype & CONTENTS_LAVA)
+ {
+ gi.sound(current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (current_player->watertype & CONTENTS_SLIME)
+ {
+ gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (current_player->watertype & CONTENTS_WATER)
+ {
+ gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+ }
+
+ current_player->flags |= FL_INWATER;
+
+ /* clear damage_debounce, so the pain sound will play immediately */
+ current_player->damage_debounce_time = level.time - 1;
+ }
+
+ /* if just completely exited a water volume, play a sound */
+ if (old_waterlevel && !waterlevel)
+ {
+ PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+ gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+ current_player->flags &= ~FL_INWATER;
+ }
+
+ /* check for head just going under water */
+ if ((old_waterlevel != 3) && (waterlevel == 3))
+ {
+ gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0);
+ }
+
+ /* check for head just coming out of water */
+ if ((old_waterlevel == 3) && (waterlevel != 3))
+ {
+ if (current_player->air_finished < level.time)
+ {
+ /* gasp for air */
+ gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0);
+ PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+ }
+ else if (current_player->air_finished < level.time + 11)
+ {
+ /* just break surface */
+ gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+
+ /* check for drowning */
+ if (waterlevel == 3)
+ {
+ /* breather or envirosuit give air */
+ if (breather || envirosuit)
+ {
+ current_player->air_finished = level.time + 10;
+
+ if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0)
+ {
+ if (!current_client->breather_sound)
+ {
+ gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ current_client->breather_sound ^= 1;
+ PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+ }
+ }
+
+ /* if out of air, start drowning */
+ if (current_player->air_finished < level.time)
+ {
+ /* drown! */
+ if ((current_player->client->next_drown_time < level.time) &&
+ (current_player->health > 0))
+ {
+ current_player->client->next_drown_time = level.time + 1;
+
+ /* take more damage the longer underwater */
+ current_player->dmg += 2;
+
+ if (current_player->dmg > 15)
+ {
+ current_player->dmg = 15;
+ }
+
+ /* play a gurp sound instead of a normal pain sound */
+ if (current_player->health <= current_player->dmg)
+ {
+ gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (rand() & 1)
+ {
+ gi.sound(current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ current_player->pain_debounce_time = level.time;
+
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin,
+ current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+ }
+ }
+ }
+ else
+ {
+ current_player->air_finished = level.time + 12;
+ current_player->dmg = 2;
+ }
+
+ /* check for sizzle damage */
+ if (waterlevel && (current_player->watertype & (CONTENTS_LAVA | CONTENTS_SLIME)))
+ {
+ if (current_player->watertype & CONTENTS_LAVA)
+ {
+ if ((current_player->health > 0) &&
+ (current_player->pain_debounce_time <= level.time) &&
+ (current_client->invincible_framenum < level.framenum) &&
+ !(current_player->flags & FL_GODMODE))
+ {
+ if (rand() & 1)
+ {
+ gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ current_player->pain_debounce_time = level.time + 1;
+ }
+
+ if (envirosuit) /* take 1/3 damage with envirosuit */
+ {
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin,
+ vec3_origin, 1 * waterlevel, 0, 0, MOD_LAVA);
+ }
+ else
+ {
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin,
+ vec3_origin, 3 * waterlevel, 0, 0, MOD_LAVA);
+ }
+ }
+
+ if (current_player->watertype & CONTENTS_SLIME)
+ {
+ if (!envirosuit)
+ {
+ /* no damage from slime with envirosuit */
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin,
+ vec3_origin, 1 * waterlevel, 0, 0, MOD_SLIME);
+ }
+ }
+ }
+}
+
+void
+G_SetClientEffects(edict_t *ent)
+{
+ int pa_type;
+ int remaining;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.effects = 0;
+
+ /* player is always ir visible, even dead. */
+ ent->s.renderfx = RF_IR_VISIBLE;
+
+ if ((ent->health <= 0) || level.intermissiontime)
+ {
+ return;
+ }
+
+ if (ent->flags & FL_DISGUISED)
+ {
+ ent->s.renderfx |= RF_USE_DISGUISE;
+ }
+
+ if (gamerules && gamerules->value)
+ {
+ if (DMGame.PlayerEffects)
+ {
+ DMGame.PlayerEffects(ent);
+ }
+ }
+
+ if (ent->powerarmor_time > level.time)
+ {
+ pa_type = PowerArmorType(ent);
+
+ if (pa_type == POWER_ARMOR_SCREEN)
+ {
+ ent->s.effects |= EF_POWERSCREEN;
+ }
+ else if (pa_type == POWER_ARMOR_SHIELD)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= RF_SHELL_GREEN;
+ }
+ }
+
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ remaining = ent->client->quad_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_QUAD;
+ }
+ }
+
+ if (ent->client->double_framenum > level.framenum)
+ {
+ remaining = ent->client->double_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_DOUBLE;
+ }
+ }
+
+ if ((ent->client->owned_sphere) &&
+ (ent->client->owned_sphere->spawnflags == 1))
+ {
+ ent->s.effects |= EF_HALF_DAMAGE;
+ }
+
+ if (ent->client->tracker_pain_framenum > level.framenum)
+ {
+ ent->s.effects |= EF_TRACKERTRAIL;
+ }
+
+ if (ent->client->invincible_framenum > level.framenum)
+ {
+ remaining = ent->client->invincible_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_PENT;
+ }
+ }
+
+ /* show cheaters!!! */
+ if (ent->flags & FL_GODMODE)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= (RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE);
+ }
+}
+
+void
+G_SetClientEvent(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.event)
+ {
+ return;
+ }
+
+ if (ent->health <= 0)
+ {
+ return;
+ }
+
+ if (g_footsteps->value == 1)
+ {
+ if (ent->groundentity && (xyspeed > 225))
+ {
+ if ((int)(current_client->bobtime + bobmove) != bobcycle)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ }
+ }
+ }
+ else if (g_footsteps->value == 2)
+ {
+ if (ent->groundentity)
+ {
+ if ((int)(current_client->bobtime + bobmove) != bobcycle)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ }
+ }
+ }
+ else if (g_footsteps->value >= 3)
+ {
+ if ((int)(current_client->bobtime + bobmove) != bobcycle)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ }
+ }
+}
+
+void
+G_SetClientSound(edict_t *ent)
+{
+ char *weap;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->pers.game_helpchanged != game.helpchanged)
+ {
+ ent->client->pers.game_helpchanged = game.helpchanged;
+ ent->client->pers.helpchanged = 1;
+ }
+
+ /* help beep (no more than three times) */
+ if (ent->client->pers.helpchanged && (ent->client->pers.helpchanged <= 3) && !(level.framenum & 63))
+ {
+ ent->client->pers.helpchanged++;
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"), 1, ATTN_STATIC, 0);
+ }
+
+ if (ent->client->pers.weapon)
+ {
+ weap = ent->client->pers.weapon->classname;
+ }
+ else
+ {
+ weap = "";
+ }
+
+ if (ent->waterlevel && (ent->watertype & (CONTENTS_LAVA | CONTENTS_SLIME)))
+ {
+ ent->s.sound = snd_fry;
+ }
+ else if (strcmp(weap, "weapon_railgun") == 0)
+ {
+ ent->s.sound = gi.soundindex("weapons/rg_hum.wav");
+ }
+ else if (strcmp(weap, "weapon_bfg") == 0)
+ {
+ ent->s.sound = gi.soundindex("weapons/bfg_hum.wav");
+ }
+ else if (ent->client->weapon_sound)
+ {
+ ent->s.sound = ent->client->weapon_sound;
+ }
+ else
+ {
+ ent->s.sound = 0;
+ }
+}
+
+void
+G_SetClientFrame(edict_t *ent)
+{
+ gclient_t *client;
+ qboolean duck, run;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.modelindex != 255)
+ {
+ return; /* not in the player model */
+ }
+
+ client = ent->client;
+
+ if (client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ duck = true;
+ }
+ else
+ {
+ duck = false;
+ }
+
+ if (xyspeed)
+ {
+ run = true;
+ }
+ else
+ {
+ run = false;
+ }
+
+ /* check for stand/duck and stop/go transitions */
+ if ((duck != client->anim_duck) && (client->anim_priority < ANIM_DEATH))
+ {
+ goto newanim;
+ }
+
+ if ((run != client->anim_run) && (client->anim_priority == ANIM_BASIC))
+ {
+ goto newanim;
+ }
+
+ if (!ent->groundentity && (client->anim_priority <= ANIM_WAVE))
+ {
+ goto newanim;
+ }
+
+ if (client->anim_priority == ANIM_REVERSE)
+ {
+ if (ent->s.frame > client->anim_end)
+ {
+ ent->s.frame--;
+ return;
+ }
+ }
+ else if (ent->s.frame < client->anim_end)
+ {
+ /* continue an animation */
+ ent->s.frame++;
+ return;
+ }
+
+ if (client->anim_priority == ANIM_DEATH)
+ {
+ return; /* stay there */
+ }
+
+ if (client->anim_priority == ANIM_JUMP)
+ {
+ if (!ent->groundentity)
+ {
+ return; /* stay there */
+ }
+
+ ent->client->anim_priority = ANIM_WAVE;
+ ent->s.frame = FRAME_jump3;
+ ent->client->anim_end = FRAME_jump6;
+ return;
+ }
+
+newanim:
+
+ /* return to either a running or standing frame */
+ client->anim_priority = ANIM_BASIC;
+ client->anim_duck = duck;
+ client->anim_run = run;
+
+ if (!ent->groundentity)
+ {
+ client->anim_priority = ANIM_JUMP;
+
+ if (ent->s.frame != FRAME_jump2)
+ {
+ ent->s.frame = FRAME_jump1;
+ }
+
+ client->anim_end = FRAME_jump2;
+ }
+ else if (run)
+ {
+ /* running */
+ if (duck)
+ {
+ ent->s.frame = FRAME_crwalk1;
+ client->anim_end = FRAME_crwalk6;
+ }
+ else
+ {
+ ent->s.frame = FRAME_run1;
+ client->anim_end = FRAME_run6;
+ }
+ }
+ else
+ {
+ /* standing */
+ if (duck)
+ {
+ ent->s.frame = FRAME_crstnd01;
+ client->anim_end = FRAME_crstnd19;
+ }
+ else
+ {
+ ent->s.frame = FRAME_stand01;
+ client->anim_end = FRAME_stand40;
+ }
+ }
+}
+
+/*
+ * Called for each player at the end of
+ * the server frame and right after spawning
+ */
+void
+ClientEndServerFrame(edict_t *ent)
+{
+ float bobtime;
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ current_player = ent;
+ current_client = ent->client;
+
+ /* If the origin or velocity have changed since ClientThink(),
+ update the pmove values. This will happen when the client
+ is pushed by a bmodel or kicked by an explosion.
+ If it wasn't updated here, the view position would lag a frame
+ behind the body position when pushed -- "sinking into plats" */
+ for (i = 0; i < 3; i++)
+ {
+ current_client->ps.pmove.origin[i] = ent->s.origin[i] * 8.0;
+ current_client->ps.pmove.velocity[i] = ent->velocity[i] * 8.0;
+ }
+
+ /* If the end of unit layout is displayed, don't give
+ the player any normal movement attributes */
+ if (level.intermissiontime)
+ {
+ current_client->ps.blend[3] = 0;
+ current_client->ps.fov = 90;
+ G_SetStats(ent);
+ return;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, up);
+
+ /* burn from lava, etc */
+ P_WorldEffects();
+
+ /* set model angles from view angles so other things in
+ the world can tell which direction you are looking */
+ if (ent->client->v_angle[PITCH] > 180)
+ {
+ ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH]) / 3;
+ }
+ else
+ {
+ ent->s.angles[PITCH] = ent->client->v_angle[PITCH] / 3;
+ }
+
+ ent->s.angles[YAW] = ent->client->v_angle[YAW];
+ ent->s.angles[ROLL] = 0;
+ ent->s.angles[ROLL] = SV_CalcRoll(ent->s.angles, ent->velocity) * 4;
+
+ /* calculate speed and cycle to be
+ used for all cyclic walking effects */
+ xyspeed = sqrt(
+ ent->velocity[0] * ent->velocity[0] + ent->velocity[1] *
+ ent->velocity[1]);
+
+ if (xyspeed < 5)
+ {
+ bobmove = 0;
+ current_client->bobtime = 0; /* start at beginning of cycle again */
+ }
+ else if (ent->groundentity)
+ {
+ /* so bobbing only cycles when on ground */
+ if (xyspeed > 210)
+ {
+ bobmove = 0.25;
+ }
+ else if (xyspeed > 100)
+ {
+ bobmove = 0.125;
+ }
+ else
+ {
+ bobmove = 0.0625;
+ }
+ }
+
+ bobtime = (current_client->bobtime += bobmove);
+
+ if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ bobtime *= 4;
+ }
+
+ bobcycle = (int)bobtime;
+ bobfracsin = fabs(sin(bobtime * M_PI));
+
+ /* detect hitting the floor */
+ P_FallingDamage(ent);
+
+ /* apply all the damage taken this frame */
+ P_DamageFeedback(ent);
+
+ /* determine the view offsets */
+ SV_CalcViewOffset(ent);
+
+ /* determine the gun offsets */
+ SV_CalcGunOffset(ent);
+
+ /* determine the full screen color blend must be after viewoffset,
+ so eye contents can be accurately determined */
+ SV_CalcBlend(ent);
+
+ /* chase cam stuff */
+ if (ent->client->resp.spectator)
+ {
+ G_SetSpectatorStats(ent);
+ }
+ else
+ {
+ G_SetStats(ent);
+ }
+
+ G_CheckChaseStats(ent);
+ G_SetClientEvent(ent);
+ G_SetClientEffects(ent);
+ G_SetClientSound(ent);
+ G_SetClientFrame(ent);
+
+ VectorCopy(ent->velocity, ent->client->oldvelocity);
+ VectorCopy(ent->client->ps.viewangles, ent->client->oldviewangles);
+
+ /* clear weapon kicks */
+ VectorClear(ent->client->kick_origin);
+ VectorClear(ent->client->kick_angles);
+
+ if (!(level.framenum & 31))
+ {
+ /* if the scoreboard is up, update it */
+ if (ent->client->showscores)
+ {
+ DeathmatchScoreboardMessage(ent, ent->enemy);
+ gi.unicast(ent, false);
+ }
+
+ /* if the help computer is up, update it */
+ if (ent->client->showhelp)
+ {
+ ent->client->pers.helpchanged = 0;
+ HelpComputerMessage(ent);
+ gi.unicast(ent, false);
+ }
+ }
+
+ /* if the inventory is up, update it */
+ if (ent->client->showinventory)
+ {
+ InventoryMessage(ent);
+ gi.unicast(ent, false);
+ }
+}
diff --git a/rogue/src/player/weapon.c b/rogue/src/player/weapon.c
new file mode 100644
index 0000000..e8e3d9a
--- /dev/null
+++ b/rogue/src/player/weapon.c
@@ -0,0 +1,2654 @@
+/* =======================================================================
+ *
+ * Player weapons.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+#include "../monster/misc/player.h"
+#include <limits.h>
+
+#define PLAYER_NOISE_SELF 0
+#define PLAYER_NOISE_IMPACT 1
+
+#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1)
+#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1)
+#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1)
+
+#define GRENADE_TIMER 3.0
+#define GRENADE_MINSPEED 400
+#define GRENADE_MAXSPEED 800
+
+#define CHAINFIST_REACH 64
+
+#define HEATBEAM_DM_DMG 15
+#define HEATBEAM_SP_DMG 15
+
+static qboolean is_quad;
+static byte damage_multiplier;
+static byte is_silenced;
+
+void weapon_grenade_fire(edict_t *ent, qboolean held);
+
+byte
+P_DamageModifier(edict_t *ent)
+{
+ is_quad = 0;
+ damage_multiplier = 1;
+
+ if (!ent)
+ {
+ return 0;
+ }
+
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ damage_multiplier *= 4;
+ is_quad = 1;
+
+ /* if we're quad and DF_NO_STACK_DOUBLE is on, return now. */
+ if (((int)(dmflags->value) & DF_NO_STACK_DOUBLE))
+ {
+ return damage_multiplier;
+ }
+ }
+
+ if (ent->client->double_framenum > level.framenum)
+ {
+ if ((deathmatch->value) || (damage_multiplier == 1))
+ {
+ damage_multiplier *= 2;
+ is_quad = 1;
+ }
+ }
+
+ return damage_multiplier;
+}
+
+void
+P_ProjectSource(edict_t *ent, vec3_t distance, vec3_t forward,
+ vec3_t right, vec3_t result)
+{
+ gclient_t *client = ent->client;
+ float *point = ent->s.origin;
+ vec3_t _distance;
+
+ if (!client)
+ {
+ return;
+ }
+
+ VectorCopy(distance, _distance);
+
+ if (client->pers.hand == LEFT_HANDED)
+ {
+ _distance[1] *= -1;
+ }
+ else if (client->pers.hand == CENTER_HANDED)
+ {
+ _distance[1] = 0;
+ }
+
+ G_ProjectSource(point, _distance, forward, right, result);
+
+ // Berserker: fix - now the projectile hits exactly where the scope is pointing.
+ if (aimfix->value)
+ {
+ vec3_t start, end;
+ VectorSet(start, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] + ent->viewheight);
+ VectorMA(start, 8192, forward, end);
+
+ trace_t tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT);
+ if (tr.fraction < 1)
+ {
+ VectorSubtract(tr.endpos, result, forward);
+ VectorNormalize(forward);
+ }
+ }
+}
+
+void
+P_ProjectSource2(edict_t *ent, vec3_t point, vec3_t distance, vec3_t forward,
+ vec3_t right, vec3_t up, vec3_t result)
+{
+ gclient_t *client = ent->client;
+ vec3_t _distance;
+
+ if (!client)
+ {
+ return;
+ }
+
+ VectorCopy(distance, _distance);
+
+ if (client->pers.hand == LEFT_HANDED)
+ {
+ _distance[1] *= -1;
+ }
+ else if (client->pers.hand == CENTER_HANDED)
+ {
+ _distance[1] = 0;
+ }
+
+ G_ProjectSource2(point, _distance, forward, right, up, result);
+
+ // Berserker: fix - now the projectile hits exactly where the scope is pointing.
+ if (aimfix->value)
+ {
+ vec3_t start, end;
+ VectorSet(start, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] + ent->viewheight);
+ VectorMA(start, 8192, forward, end);
+
+ trace_t tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT);
+ if (tr.fraction < 1)
+ {
+ VectorSubtract(tr.endpos, result, forward);
+ VectorNormalize(forward);
+ }
+ }
+}
+
+/*
+ * Each player can have two noise objects associated with it:
+ * a personal noise (jumping, pain, weapon firing), and a weapon
+ * target noise (bullet wall impacts)
+ *
+ * Monsters that don't directly see the player can move
+ * to a noise in hopes of seeing the player from there.
+ */
+static edict_t *
+PlayerNoise_Spawn(edict_t *who, int type)
+{
+ edict_t *noise;
+
+ if (!who)
+ {
+ return NULL;
+ }
+
+ noise = G_SpawnOptional();
+ if (!noise)
+ {
+ return NULL;
+ }
+
+ noise->classname = "player_noise";
+ noise->spawnflags = type;
+ VectorSet (noise->mins, -8, -8, -8);
+ VectorSet (noise->maxs, 8, 8, 8);
+ noise->owner = who;
+ noise->svflags = SVF_NOCLIENT;
+
+ return noise;
+}
+
+static void
+PlayerNoise_Verify(edict_t *who)
+{
+ edict_t *e;
+ edict_t *n1;
+ edict_t *n2;
+
+ if (!who)
+ {
+ return;
+ }
+
+ n1 = who->mynoise;
+ n2 = who->mynoise2;
+
+ if (n1 && !n1->inuse)
+ {
+ n1 = NULL;
+ }
+
+ if (n2 && !n2->inuse)
+ {
+ n2 = NULL;
+ }
+
+ if (n1 && n2)
+ {
+ return;
+ }
+
+ for (e = g_edicts + 1 + game.maxclients; e < &g_edicts[globals.num_edicts]; e++)
+ {
+ if (!e->inuse || strcmp(e->classname, "player_noise") != 0)
+ {
+ continue;
+ }
+
+ if (e->owner && e->owner != who)
+ {
+ continue;
+ }
+
+ e->owner = who;
+
+ if (!n2 && (e->spawnflags == PLAYER_NOISE_IMPACT || n1))
+ {
+ n2 = e;
+ }
+ else
+ {
+ n1 = e;
+ }
+
+ if (n1 && n2)
+ {
+ break;
+ }
+ }
+
+ if (!n1)
+ {
+ n1 = PlayerNoise_Spawn(who, PLAYER_NOISE_SELF);
+ }
+
+ if (!n2)
+ {
+ n2 = PlayerNoise_Spawn(who, PLAYER_NOISE_IMPACT);
+ }
+
+ who->mynoise = n1;
+ who->mynoise2 = n2;
+}
+
+void
+PlayerNoise(edict_t *who, vec3_t where, int type)
+{
+ edict_t *noise;
+
+ if (!who || !who->client)
+ {
+ return;
+ }
+
+ if (type == PNOISE_WEAPON)
+ {
+ if (who->client->silencer_shots)
+ {
+ who->client->silencer_shots--;
+ return;
+ }
+ }
+
+ if (deathmatch->value)
+ {
+ return;
+ }
+
+ if (who->flags & FL_NOTARGET)
+ {
+ return;
+ }
+
+ if (who->flags & FL_DISGUISED)
+ {
+ if (type == PNOISE_WEAPON)
+ {
+ level.disguise_violator = who;
+ level.disguise_violation_framenum = level.framenum + 5;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ PlayerNoise_Verify(who);
+
+ if ((type == PNOISE_SELF) || (type == PNOISE_WEAPON))
+ {
+ if (level.framenum <= (level.sound_entity_framenum + 3))
+ {
+ return;
+ }
+
+ if (!who->mynoise)
+ {
+ return;
+ }
+
+ noise = who->mynoise;
+ level.sound_entity = noise;
+ level.sound_entity_framenum = level.framenum;
+ }
+ else
+ {
+ if (level.framenum <= (level.sound2_entity_framenum + 3))
+ {
+ return;
+ }
+
+ if (!who->mynoise2)
+ {
+ return;
+ }
+
+ noise = who->mynoise2;
+ level.sound2_entity = noise;
+ level.sound2_entity_framenum = level.framenum;
+ }
+
+ VectorCopy(where, noise->s.origin);
+ VectorSubtract(where, noise->maxs, noise->absmin);
+ VectorAdd(where, noise->maxs, noise->absmax);
+ noise->last_sound_time = level.time;
+ gi.linkentity(noise);
+}
+
+qboolean
+Pickup_Weapon(edict_t *ent, edict_t *other)
+{
+ int index;
+ gitem_t *ammo;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ index = ITEM_INDEX(ent->item);
+
+ if ((((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) && other->client->pers.inventory[index])
+ {
+ if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) &&
+ (!coop_pickup_weapons->value || (ent->flags & FL_COOP_TAKEN)))
+ {
+ return false; /* leave the weapon for others to pickup */
+ }
+ }
+
+ other->client->pers.inventory[index]++;
+
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ /* give them some ammo with it */
+ if (ent->item->ammo)
+ {
+ ammo = FindItem(ent->item->ammo);
+
+ if ((int)dmflags->value & DF_INFINITE_AMMO)
+ {
+ Add_Ammo(other, ammo, 1000);
+ }
+ else
+ {
+ Add_Ammo(other, ammo, ammo->quantity);
+ }
+ }
+
+ if (!(ent->spawnflags & DROPPED_PLAYER_ITEM))
+ {
+ if (deathmatch->value)
+ {
+ if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+ {
+ ent->flags |= FL_RESPAWN;
+ }
+ else
+ {
+ SetRespawn(ent, 30);
+ }
+ }
+
+ if (coop->value)
+ {
+ ent->flags |= FL_RESPAWN;
+ ent->flags |= FL_COOP_TAKEN;
+ }
+ }
+ }
+
+ if ((other->client->pers.weapon != ent->item) &&
+ (other->client->pers.inventory[index] == 1) &&
+ (!deathmatch->value || (other->client->pers.weapon == FindItem("blaster"))))
+ {
+ other->client->newweapon = ent->item;
+ }
+
+ return true;
+}
+
+/*
+ * The old weapon has been dropped all the
+ * way, so make the new one current
+ */
+void
+ChangeWeapon(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->grenade_time)
+ {
+ ent->client->grenade_time = level.time;
+ ent->client->weapon_sound = 0;
+ weapon_grenade_fire(ent, false);
+ ent->client->grenade_time = 0;
+ }
+
+ ent->client->pers.lastweapon = ent->client->pers.weapon;
+ ent->client->pers.weapon = ent->client->newweapon;
+ ent->client->newweapon = NULL;
+ ent->client->machinegun_shots = 0;
+
+ /* set visible model */
+ if (ent->s.modelindex == 255)
+ {
+ if (ent->client->pers.weapon)
+ {
+ i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8);
+ }
+ else
+ {
+ i = 0;
+ }
+
+ ent->s.skinnum = (ent - g_edicts - 1) | i;
+ }
+
+ if (ent->client->pers.weapon && ent->client->pers.weapon->ammo)
+ {
+ ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo));
+ }
+ else
+ {
+ ent->client->ammo_index = 0;
+ }
+
+ if (!ent->client->pers.weapon)
+ {
+ /* dead */
+ ent->client->ps.gunindex = 0;
+ return;
+ }
+
+ ent->client->weaponstate = WEAPON_ACTIVATING;
+ ent->client->ps.gunframe = 0;
+ ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
+
+ ent->client->anim_priority = ANIM_PAIN;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crpain1;
+ ent->client->anim_end = FRAME_crpain4;
+ }
+ else
+ {
+ ent->s.frame = FRAME_pain301;
+ ent->client->anim_end = FRAME_pain304;
+ }
+}
+
+void
+NoAmmoWeaponChange(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))])
+ {
+ ent->client->newweapon = FindItem("railgun");
+ return;
+ }
+
+ if ((ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] >= 2) &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("Plasma Beam"))])
+ {
+ ent->client->newweapon = FindItem("Plasma Beam");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("flechettes"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("etf rifle"))])
+ {
+ ent->client->newweapon = FindItem("etf rifle");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))])
+ {
+ ent->client->newweapon = FindItem("chaingun");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))])
+ {
+ ent->client->newweapon = FindItem("machinegun");
+ return;
+ }
+
+ if ((ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1) &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))])
+ {
+ ent->client->newweapon = FindItem("super shotgun");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))])
+ {
+ ent->client->newweapon = FindItem("shotgun");
+ return;
+ }
+
+ ent->client->newweapon = FindItem("blaster");
+}
+
+/*
+ * Called by ClientBeginServerFrame and ClientThink
+ */
+void
+Think_Weapon(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* if just died, put the weapon away */
+ if (ent->health < 1)
+ {
+ ent->client->newweapon = NULL;
+ ChangeWeapon(ent);
+ }
+
+ /* call active weapon think routine */
+ if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink)
+ {
+ P_DamageModifier(ent);
+
+ if (ent->client->silencer_shots)
+ {
+ is_silenced = MZ_SILENCED;
+ }
+ else
+ {
+ is_silenced = 0;
+ }
+
+ ent->client->pers.weapon->weaponthink(ent);
+ }
+}
+
+/*
+ * Client (player) animation for changing weapon
+ */
+static void
+Change_Weap_Animation(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->anim_priority = ANIM_REVERSE;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crpain4 + 1;
+ ent->client->anim_end = FRAME_crpain1;
+ }
+ else
+ {
+ ent->s.frame = FRAME_pain304 + 1;
+ ent->client->anim_end = FRAME_pain301;
+ }
+}
+
+/*
+ * Make the weapon ready if there is ammo
+ */
+void
+Use_Weapon(edict_t *ent, gitem_t *item)
+{
+ int ammo_index;
+ gitem_t *ammo_item;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ /* see if we're already using it */
+ if (item == ent->client->pers.weapon)
+ {
+ return;
+ }
+
+ if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO))
+ {
+ ammo_item = FindItem(item->ammo);
+ ammo_index = ITEM_INDEX(ammo_item);
+
+ if (!ent->client->pers.inventory[ammo_index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+ return;
+ }
+
+ if (ent->client->pers.inventory[ammo_index] < item->quantity)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Not enough %s for %s.\n",
+ ammo_item->pickup_name, item->pickup_name);
+ return;
+ }
+ }
+
+ /* change to this weapon when down */
+ ent->client->newweapon = item;
+}
+
+void
+Drop_Weapon(edict_t *ent, gitem_t *item)
+{
+ int index;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(item);
+
+ /* see if we're already using it */
+ if (((item == ent->client->pers.weapon) ||
+ (item == ent->client->newweapon)) &&
+ (ent->client->pers.inventory[index] == 1))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Can't drop current weapon\n");
+ return;
+ }
+
+ Drop_Item(ent, item);
+ ent->client->pers.inventory[index]--;
+}
+
+/*
+ * A generic function to handle the basics of weapon thinking
+ */
+
+void
+Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST,
+ int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
+{
+ int n;
+ const unsigned short int change_speed = (g_swap_speed->value > 1)?
+ (g_swap_speed->value < USHRT_MAX)? (unsigned short int)g_swap_speed->value : 1
+ : 1;
+
+ if (!ent || !fire)
+ {
+ return;
+ }
+
+ if (ent->deadflag || (ent->s.modelindex != 255)) /* VWep animations screw up corpses */
+ {
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_DROPPING)
+ {
+ if (ent->client->ps.gunframe >= FRAME_DEACTIVATE_LAST - change_speed + 1)
+ {
+ ChangeWeapon(ent);
+ return;
+ }
+ else if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) >= (4 * change_speed) )
+ {
+ unsigned short int remainder = FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe;
+ // "if (remainder == 4)" at change_speed == 1
+ if ( ( remainder <= (4 * change_speed) )
+ && ( remainder > (3 * change_speed) ) )
+ {
+ Change_Weap_Animation(ent);
+ }
+ }
+
+ ent->client->ps.gunframe += change_speed;
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_ACTIVATING)
+ {
+ if (ent->client->ps.gunframe >= FRAME_ACTIVATE_LAST - change_speed + 1)
+ {
+ ent->client->weaponstate = WEAPON_READY;
+ ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+ return;
+ }
+
+ ent->client->ps.gunframe += change_speed;
+ return;
+ }
+
+ if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
+ {
+ ent->client->weaponstate = WEAPON_DROPPING;
+ ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
+
+ if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < (4 * change_speed) )
+ {
+ Change_Weap_Animation(ent);
+ }
+
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_READY)
+ {
+ if (((ent->client->latched_buttons |
+ ent->client->buttons) & BUTTON_ATTACK))
+ {
+ ent->client->latched_buttons &= ~BUTTON_ATTACK;
+
+ if ((!ent->client->ammo_index) ||
+ (ent->client->pers.inventory[ent->client->ammo_index] >=
+ ent->client->pers.weapon->quantity))
+ {
+ ent->client->ps.gunframe = FRAME_FIRE_FIRST;
+ ent->client->weaponstate = WEAPON_FIRING;
+
+ /* start the animation */
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - 1;
+ ent->client->anim_end = FRAME_attack8;
+ }
+ }
+ else
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ }
+ }
+ else
+ {
+ if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
+ {
+ ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+ return;
+ }
+
+ if (pause_frames)
+ {
+ for (n = 0; pause_frames[n]; n++)
+ {
+ if (ent->client->ps.gunframe == pause_frames[n])
+ {
+ if (rand() & 15)
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ ent->client->ps.gunframe++;
+ return;
+ }
+ }
+
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ for (n = 0; fire_frames[n]; n++)
+ {
+ if (ent->client->ps.gunframe == fire_frames[n])
+ {
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->client->double_framenum > level.framenum)
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
+ }
+
+ fire(ent);
+ break;
+ }
+ }
+
+ if (!fire_frames[n])
+ {
+ ent->client->ps.gunframe++;
+ }
+
+ if (ent->client->ps.gunframe == FRAME_IDLE_FIRST + 1)
+ {
+ ent->client->weaponstate = WEAPON_READY;
+ }
+ }
+}
+
+/*
+ * ======================================================================
+ *
+ * GRENADE
+ *
+ * ======================================================================
+ */
+
+void
+weapon_grenade_fire(edict_t *ent, qboolean held)
+{
+ vec3_t offset;
+ vec3_t forward, right, up;
+ vec3_t start;
+ int damage = 125;
+ float timer;
+ int speed;
+ float radius;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ radius = damage + 40;
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+
+ if (damage_multiplier >= 4)
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (damage_multiplier == 2)
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, up);
+
+ if (ent->client->pers.weapon->tag == AMMO_TESLA)
+ {
+ VectorSet(offset, 0, -4, ent->viewheight - 22);
+ }
+ else
+ {
+ VectorSet(offset, 2, 6, ent->viewheight - 14);
+ }
+
+ P_ProjectSource2(ent, ent->s.origin, offset,
+ forward, right, up, start);
+
+ timer = ent->client->grenade_time - level.time;
+ speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER);
+
+ if (speed > GRENADE_MAXSPEED)
+ {
+ speed = GRENADE_MAXSPEED;
+ }
+
+ switch (ent->client->pers.weapon->tag)
+ {
+ case AMMO_GRENADES:
+ fire_grenade2(ent, start, forward, damage, speed,
+ timer, radius, held);
+ break;
+ default:
+ fire_tesla(ent, start, forward, damage_multiplier, speed);
+ break;
+ }
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+
+ ent->client->grenade_time = level.time + 1.0;
+
+ if (ent->deadflag || (ent->s.modelindex != 255)) /* VWep animations screw up corpses */
+ {
+ return;
+ }
+
+ if (ent->health <= 0)
+ {
+ return;
+ }
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->client->anim_priority = ANIM_ATTACK;
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak3;
+ }
+ else
+ {
+ ent->client->anim_priority = ANIM_REVERSE;
+ ent->s.frame = FRAME_wave08;
+ ent->client->anim_end = FRAME_wave01;
+ }
+}
+
+
+void
+Throw_Generic(edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_THROW_SOUND,
+ int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, int *pause_frames, int EXPLODE,
+ void (*fire)(edict_t *ent, qboolean held))
+{
+ int n;
+
+ if (!ent || !pause_frames || !fire)
+ {
+ return;
+ }
+
+ if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+ {
+ ChangeWeapon(ent);
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_ACTIVATING)
+ {
+ ent->client->weaponstate = WEAPON_READY;
+ ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_READY)
+ {
+ if (((ent->client->latched_buttons | ent->client->buttons) & BUTTON_ATTACK))
+ {
+ ent->client->latched_buttons &= ~BUTTON_ATTACK;
+
+ if (ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ ent->client->ps.gunframe = 1;
+ ent->client->weaponstate = WEAPON_FIRING;
+ ent->client->grenade_time = 0;
+ }
+ else
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ }
+
+ return;
+ }
+
+ if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
+ {
+ ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+ return;
+ }
+
+ if (pause_frames)
+ {
+ for (n = 0; pause_frames[n]; n++)
+ {
+ if (ent->client->ps.gunframe == pause_frames[n])
+ {
+ if (rand() & 15)
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ ent->client->ps.gunframe++;
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ if (ent->client->ps.gunframe == FRAME_THROW_SOUND)
+ {
+ gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0);
+ }
+
+ if (ent->client->ps.gunframe == FRAME_THROW_HOLD)
+ {
+ if (!ent->client->grenade_time)
+ {
+ ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+
+ switch (ent->client->pers.weapon->tag)
+ {
+ case AMMO_GRENADES:
+ ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
+ break;
+ }
+ }
+
+ /* they waited too long, detonate it in their hand */
+ if (EXPLODE && !ent->client->grenade_blew_up &&
+ (level.time >= ent->client->grenade_time))
+ {
+ ent->client->weapon_sound = 0;
+ fire(ent, true);
+ ent->client->grenade_blew_up = true;
+ }
+
+ if (ent->client->buttons & BUTTON_ATTACK)
+ {
+ return;
+ }
+
+ if (ent->client->grenade_blew_up)
+ {
+ if (level.time >= ent->client->grenade_time)
+ {
+ ent->client->ps.gunframe = FRAME_FIRE_LAST;
+ ent->client->grenade_blew_up = false;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+ if (ent->client->ps.gunframe == FRAME_THROW_FIRE)
+ {
+ ent->client->weapon_sound = 0;
+ fire(ent, true);
+ }
+
+ if ((ent->client->ps.gunframe == FRAME_FIRE_LAST) &&
+ (level.time < ent->client->grenade_time))
+ {
+ return;
+ }
+
+ ent->client->ps.gunframe++;
+
+ if (ent->client->ps.gunframe == FRAME_IDLE_FIRST)
+ {
+ ent->client->grenade_time = 0;
+ ent->client->weaponstate = WEAPON_READY;
+ }
+ }
+}
+
+void
+Weapon_Grenade(edict_t *ent)
+{
+ static int pause_frames[] = {29, 34, 39, 48, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Throw_Generic(ent, 15, 48, 5, 11, 12, pause_frames,
+ GRENADE_TIMER, weapon_grenade_fire);
+}
+
+void
+Weapon_Tesla(edict_t *ent)
+{
+ static int pause_frames[] = {21, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((ent->client->ps.gunframe > 1) && (ent->client->ps.gunframe < 9))
+ {
+ ent->client->ps.gunindex = gi.modelindex("models/weapons/v_tesla2/tris.md2");
+ }
+ else
+ {
+ ent->client->ps.gunindex = gi.modelindex("models/weapons/v_tesla/tris.md2");
+ }
+
+ Throw_Generic(ent, 8, 32, 99, 1, 2, pause_frames, 0, weapon_grenade_fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * GRENADE LAUNCHER
+ *
+ * ======================================================================
+ */
+
+void
+weapon_grenadelauncher_fire(edict_t *ent)
+{
+ vec3_t offset;
+ vec3_t forward, right;
+ vec3_t start;
+ int damage;
+ float radius;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ switch (ent->client->pers.weapon->tag)
+ {
+ case AMMO_PROX:
+ damage = 90;
+ break;
+ default:
+ damage = 120;
+ break;
+ }
+
+ radius = damage + 40;
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ }
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -1;
+
+ switch (ent->client->pers.weapon->tag)
+ {
+ case AMMO_PROX:
+ fire_prox(ent, start, forward, damage_multiplier, 600);
+ break;
+ default:
+ fire_grenade(ent, start, forward, damage, 600, 2.5, radius);
+ break;
+ }
+
+ gi.WriteByte (svc_muzzleflash);
+ gi.WriteShort (ent-g_edicts);
+ gi.WriteByte(MZ_GRENADE | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_GrenadeLauncher(edict_t *ent)
+{
+ static int pause_frames[] = {34, 51, 59, 0};
+ static int fire_frames[] = {6, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 5, 16, 59, 64, pause_frames,
+ fire_frames, weapon_grenadelauncher_fire);
+}
+
+void
+Weapon_ProxLauncher(edict_t *ent)
+{
+ static int pause_frames[] = {34, 51, 59, 0};
+ static int fire_frames[] = {6, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 5, 16, 59, 64, pause_frames,
+ fire_frames, weapon_grenadelauncher_fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * ROCKET
+ *
+ * ======================================================================
+ */
+
+void
+Weapon_RocketLauncher_Fire(edict_t *ent)
+{
+ vec3_t offset, start;
+ vec3_t forward, right;
+ int damage;
+ float damage_radius;
+ int radius_damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ damage = 100 + (int)(random() * 20.0);
+ radius_damage = 120;
+ damage_radius = 120;
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ radius_damage *= damage_multiplier;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -1;
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_rocket(ent, start, forward, damage, 650, damage_radius, radius_damage);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_ROCKET | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_RocketLauncher(edict_t *ent)
+{
+ static int pause_frames[] = {25, 33, 42, 50, 0};
+ static int fire_frames[] = {5, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 4, 12, 50, 54, pause_frames,
+ fire_frames, Weapon_RocketLauncher_Fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * BLASTER / HYPERBLASTER
+ *
+ * ======================================================================
+ */
+
+void
+Blaster_Fire(edict_t *ent, vec3_t g_offset, int damage,
+ qboolean hyper, int effect)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t offset;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ VectorSet(offset, 24, 8, ent->viewheight - 8);
+ VectorAdd(offset, g_offset, offset);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -1;
+
+ fire_blaster(ent, start, forward, damage, 1000, effect, hyper);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+
+ if (hyper)
+ {
+ gi.WriteByte(MZ_HYPERBLASTER | is_silenced);
+ }
+ else
+ {
+ gi.WriteByte(MZ_BLASTER | is_silenced);
+ }
+
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+}
+
+void
+Weapon_Blaster_Fire(edict_t *ent)
+{
+ int damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 15;
+ }
+ else
+ {
+ damage = 10;
+ }
+
+ Blaster_Fire(ent, vec3_origin, damage, false, EF_BLASTER);
+ ent->client->ps.gunframe++;
+}
+
+void
+Weapon_Blaster(edict_t *ent)
+{
+ static int pause_frames[] = {19, 32, 0};
+ static int fire_frames[] = {5, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 4, 8, 52, 55, pause_frames,
+ fire_frames, Weapon_Blaster_Fire);
+}
+
+void
+Weapon_HyperBlaster_Fire(edict_t *ent)
+{
+ float rotation;
+ vec3_t offset;
+ int effect;
+ int damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav");
+
+ if (!(ent->client->buttons & BUTTON_ATTACK))
+ {
+ ent->client->ps.gunframe++;
+ }
+ else
+ {
+ if (!ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ }
+ else
+ {
+ rotation = (ent->client->ps.gunframe - 5) * 2 * M_PI / 6;
+ offset[0] = -4 * sin(rotation);
+ offset[1] = 0;
+ offset[2] = 4 * cos(rotation);
+
+ if ((ent->client->ps.gunframe == 6) ||
+ (ent->client->ps.gunframe == 9))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 15;
+ }
+ else
+ {
+ damage = 20;
+ }
+
+ Blaster_Fire(ent, offset, damage, true, effect);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - 1;
+ ent->client->anim_end = FRAME_attack8;
+ }
+ }
+
+ ent->client->ps.gunframe++;
+
+ if ((ent->client->ps.gunframe == 12) &&
+ ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ ent->client->ps.gunframe = 6;
+ }
+ }
+
+ if (ent->client->ps.gunframe == 12)
+ {
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+ ent->client->weapon_sound = 0;
+ }
+}
+
+void
+Weapon_HyperBlaster(edict_t *ent)
+{
+ static int pause_frames[] = {0};
+ static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 5, 20, 49, 53, pause_frames,
+ fire_frames, Weapon_HyperBlaster_Fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * MACHINEGUN / CHAINGUN
+ *
+ * ======================================================================
+ */
+
+void
+Machinegun_Fire(edict_t *ent)
+{
+ int i;
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t angles;
+ int damage = 8;
+ int kick = 2;
+ vec3_t offset;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->client->buttons & BUTTON_ATTACK))
+ {
+ ent->client->machinegun_shots = 0;
+ ent->client->ps.gunframe++;
+ return;
+ }
+
+ if (ent->client->ps.gunframe == 5)
+ {
+ ent->client->ps.gunframe = 4;
+ }
+ else
+ {
+ ent->client->ps.gunframe = 5;
+ }
+
+ if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
+ {
+ ent->client->ps.gunframe = 6;
+
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ kick *= damage_multiplier;
+ }
+
+ for (i = 1; i < 3; i++)
+ {
+ ent->client->kick_origin[i] = crandom() * 0.35;
+ ent->client->kick_angles[i] = crandom() * 0.7;
+ }
+
+ ent->client->kick_origin[0] = crandom() * 0.35;
+ ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5;
+
+ /* raise the gun as it is firing */
+ if (!(deathmatch->value || g_machinegun_norecoil->value))
+ {
+ ent->client->machinegun_shots++;
+
+ if (ent->client->machinegun_shots > 9)
+ {
+ ent->client->machinegun_shots = 9;
+ }
+ }
+
+ /* get start / end positions */
+ VectorAdd(ent->client->v_angle, ent->client->kick_angles, angles);
+ AngleVectors(angles, forward, right, NULL);
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_bullet(ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
+
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_MACHINEGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - (int)(random() + 0.25);
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - (int)(random() + 0.25);
+ ent->client->anim_end = FRAME_attack8;
+ }
+}
+
+void
+Weapon_Machinegun(edict_t *ent)
+{
+ static int pause_frames[] = {23, 45, 0};
+ static int fire_frames[] = {4, 5, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 3, 5, 45, 49, pause_frames, fire_frames,
+ Machinegun_Fire);
+}
+
+void
+Chaingun_Fire(edict_t *ent)
+{
+ int i;
+ int shots;
+ vec3_t start;
+ vec3_t forward, right, up;
+ float r, u;
+ vec3_t offset;
+ int damage;
+ int kick = 2;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 6;
+ }
+ else
+ {
+ damage = 8;
+ }
+
+ if (ent->client->ps.gunframe == 5)
+ {
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0);
+ }
+
+ if ((ent->client->ps.gunframe == 14) &&
+ !(ent->client->buttons & BUTTON_ATTACK))
+ {
+ ent->client->ps.gunframe = 32;
+ ent->client->weapon_sound = 0;
+ return;
+ }
+ else if ((ent->client->ps.gunframe == 21) &&
+ (ent->client->buttons & BUTTON_ATTACK) &&
+ ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ ent->client->ps.gunframe = 15;
+ }
+ else
+ {
+ ent->client->ps.gunframe++;
+ }
+
+ if (ent->client->ps.gunframe == 22)
+ {
+ ent->client->weapon_sound = 0;
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav");
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1);
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1);
+ ent->client->anim_end = FRAME_attack8;
+ }
+
+ if (ent->client->ps.gunframe <= 9)
+ {
+ shots = 1;
+ }
+ else if (ent->client->ps.gunframe <= 14)
+ {
+ if (ent->client->buttons & BUTTON_ATTACK)
+ {
+ shots = 2;
+ }
+ else
+ {
+ shots = 1;
+ }
+ }
+ else
+ {
+ shots = 3;
+ }
+
+ if (ent->client->pers.inventory[ent->client->ammo_index] < shots)
+ {
+ shots = ent->client->pers.inventory[ent->client->ammo_index];
+ }
+
+ if (!shots)
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ kick *= damage_multiplier;
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->kick_origin[i] = crandom() * 0.35;
+ ent->client->kick_angles[i] = crandom() * 0.7;
+ }
+
+ for (i = 0; i < shots; i++)
+ {
+ /* get start / end positions */
+ AngleVectors(ent->client->v_angle, forward, right, up);
+ r = 7 + crandom() * 4;
+ u = crandom() * 4;
+ VectorSet(offset, 0, r, u + ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ fire_bullet(ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
+ }
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte((MZ_CHAINGUN1 + shots - 1) | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= shots;
+ }
+}
+
+void
+Weapon_Chaingun(edict_t *ent)
+{
+ static int pause_frames[] = {38, 43, 51, 61, 0};
+ static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 4, 31, 61, 64, pause_frames, fire_frames,
+ Chaingun_Fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * SHOTGUN / SUPERSHOTGUN
+ *
+ * ======================================================================
+ */
+
+void
+weapon_shotgun_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t offset;
+ int damage = 4;
+ int kick = 8;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->ps.gunframe == 9)
+ {
+ ent->client->ps.gunframe++;
+ return;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ kick *= damage_multiplier;
+ }
+
+ fire_shotgun(ent, start, forward, damage, kick, 500, 500,
+ DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_SHOTGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_Shotgun(edict_t *ent)
+{
+ static int pause_frames[] = {22, 28, 34, 0};
+ static int fire_frames[] = {8, 9, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 7, 18, 36, 39, pause_frames,
+ fire_frames, weapon_shotgun_fire);
+}
+
+void
+weapon_supershotgun_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t offset;
+ vec3_t v;
+ int damage = 6;
+ int kick = 12;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ kick *= damage_multiplier;
+ }
+
+ v[PITCH] = ent->client->v_angle[PITCH];
+ v[YAW] = ent->client->v_angle[YAW] - 5;
+ v[ROLL] = ent->client->v_angle[ROLL];
+ AngleVectors(v, forward, NULL, NULL);
+
+ if (aimfix->value)
+ {
+ AngleVectors(v, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ }
+
+ fire_shotgun(ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD,
+ DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN);
+ v[YAW] = ent->client->v_angle[YAW] + 5;
+ AngleVectors(v, forward, NULL, NULL);
+
+ if (aimfix->value)
+ {
+ AngleVectors(v, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ }
+
+
+ fire_shotgun(ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD,
+ DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_SSHOTGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= 2;
+ }
+}
+
+void
+Weapon_SuperShotgun(edict_t *ent)
+{
+ static int pause_frames[] = {29, 42, 57, 0};
+ static int fire_frames[] = {7, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 6, 17, 57, 61, pause_frames,
+ fire_frames, weapon_supershotgun_fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * RAILGUN
+ *
+ * ======================================================================
+ */
+
+void
+weapon_railgun_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t offset;
+ int damage;
+ int kick;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* normal damage is too extreme in dm */
+ damage = 100;
+ kick = 200;
+ }
+ else
+ {
+ damage = 150;
+ kick = 250;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ kick *= damage_multiplier;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -3, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -3;
+
+ VectorSet(offset, 0, 7, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_rail(ent, start, forward, damage, kick);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_RAILGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_Railgun(edict_t *ent)
+{
+ static int pause_frames[] = {56, 0};
+ static int fire_frames[] = {4, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 3, 18, 56, 61, pause_frames,
+ fire_frames, weapon_railgun_fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * BFG10K
+ *
+ * ======================================================================
+ */
+
+void
+weapon_bfg_fire(edict_t *ent)
+{
+ vec3_t offset, start;
+ vec3_t forward, right;
+ int damage;
+ float damage_radius = 1000;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 200;
+ }
+ else
+ {
+ damage = 500;
+ }
+
+ if (ent->client->ps.gunframe == 9)
+ {
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_BFG | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+ return;
+ }
+
+ /* cells can go down during windup (from power armor hits), so
+ check again and abort firing if we don't have enough now */
+ if (ent->client->pers.inventory[ent->client->ammo_index] < 50)
+ {
+ ent->client->ps.gunframe++;
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+
+ /* make a big pitch kick with an inverse fall */
+ ent->client->v_dmg_pitch = -40;
+ ent->client->v_dmg_roll = crandom() * 8;
+ ent->client->v_dmg_time = level.time + DAMAGE_TIME;
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_bfg(ent, start, forward, damage, 400, damage_radius);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= 50;
+ }
+}
+
+void
+Weapon_BFG(edict_t *ent)
+{
+ static int pause_frames[] = {39, 45, 50, 55, 0};
+ static int fire_frames[] = {9, 17, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 8, 32, 55, 58, pause_frames,
+ fire_frames, weapon_bfg_fire);
+}
+
+/* CHAINFIST */
+
+void
+weapon_chainfist_fire(edict_t *ent)
+{
+ vec3_t offset;
+ vec3_t forward, right, up;
+ vec3_t start;
+ int damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ damage = 15;
+
+ if (deathmatch->value)
+ {
+ damage = 30;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, up);
+
+ /* kick back */
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -1;
+
+ /* set start point */
+ VectorSet(offset, 0, 8, ent->viewheight - 4);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ fire_player_melee(ent, start, forward, CHAINFIST_REACH, damage,
+ 100, 1, MOD_CHAINFIST);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ ent->client->ps.gunframe++;
+ ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
+}
+
+/*
+ * this spits out some smoke from the motor. it's a two-stroke, you know.
+ */
+void
+chainfist_smoke(edict_t *ent)
+{
+ vec3_t tempVec, forward, right, up;
+ vec3_t offset;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, up);
+ VectorSet(offset, 8, 8, ent->viewheight - 4);
+ P_ProjectSource(ent, offset, forward, right, tempVec);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_CHAINFIST_SMOKE);
+ gi.WritePosition(tempVec);
+ gi.unicast(ent, 0);
+}
+
+void
+Weapon_ChainFist(edict_t *ent)
+{
+ static int pause_frames[] = {0};
+ static int fire_frames[] = {8, 9, 16, 17, 18, 30, 31, 0};
+
+ /* these are caches for the sound index. there's probably a better way to do this. */
+ float chance;
+ int last_sequence;
+
+ last_sequence = 0;
+
+ if ((ent->client->ps.gunframe == 13) ||
+ (ent->client->ps.gunframe == 23)) /* end of attack, go idle */
+ {
+ ent->client->ps.gunframe = 32;
+ }
+
+ /* holds for idle sequence */
+ else if ((ent->client->ps.gunframe == 42) && (rand() & 7))
+ {
+ if ((ent->client->pers.hand != CENTER_HANDED) && (random() < 0.4))
+ {
+ chainfist_smoke(ent);
+ }
+ }
+ else if ((ent->client->ps.gunframe == 51) && (rand() & 7))
+ {
+ if ((ent->client->pers.hand != CENTER_HANDED) && (random() < 0.4))
+ {
+ chainfist_smoke(ent);
+ }
+ }
+
+ /* set the appropriate weapon sound. */
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ ent->client->weapon_sound = gi.soundindex("weapons/sawhit.wav");
+ }
+ else if (ent->client->weaponstate == WEAPON_DROPPING)
+ {
+ ent->client->weapon_sound = 0;
+ }
+ else
+ {
+ ent->client->weapon_sound = gi.soundindex("weapons/sawidle.wav");
+ }
+
+ Weapon_Generic(ent, 4, 32, 57, 60, pause_frames,
+ fire_frames, weapon_chainfist_fire);
+
+ if ((ent->client->buttons) & BUTTON_ATTACK)
+ {
+ if ((ent->client->ps.gunframe == 13) ||
+ (ent->client->ps.gunframe == 23) ||
+ (ent->client->ps.gunframe == 32))
+ {
+ last_sequence = ent->client->ps.gunframe;
+ ent->client->ps.gunframe = 6;
+ }
+ }
+
+ if (ent->client->ps.gunframe == 6)
+ {
+ chance = random();
+
+ if (last_sequence == 13) /* if we just did sequence 1, do 2 or 3. */
+ {
+ chance -= 0.34;
+ }
+ else if (last_sequence == 23) /* if we just did sequence 2, do 1 or 3 */
+ {
+ chance += 0.33;
+ }
+ else if (last_sequence == 32) /* if we just did sequence 3, do 1 or 2 */
+ {
+ if (chance >= 0.33)
+ {
+ chance += 0.34;
+ }
+ }
+
+ if (chance < 0.33)
+ {
+ ent->client->ps.gunframe = 14;
+ }
+ else if (chance < 0.66)
+ {
+ ent->client->ps.gunframe = 24;
+ }
+ }
+}
+
+/* Disintegrator */
+
+void
+weapon_tracker_fire(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t end;
+ vec3_t offset;
+ edict_t *enemy;
+ trace_t tr;
+ int damage;
+ vec3_t mins, maxs;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 30;
+ }
+ else
+ {
+ damage = 45;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ }
+
+ VectorSet(mins, -16, -16, -16);
+ VectorSet(maxs, 16, 16, 16);
+ AngleVectors(self->client->v_angle, forward, right, NULL);
+ VectorSet(offset, 24, 8, self->viewheight - 8);
+ P_ProjectSource(self, offset, forward, right, start);
+
+ VectorMA(start, 8192, forward, end);
+ enemy = NULL;
+ tr = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
+
+ if (tr.ent != world)
+ {
+ if (tr.ent->svflags & SVF_MONSTER || tr.ent->client || tr.ent->svflags & SVF_DAMAGEABLE)
+ {
+ if (tr.ent->health > 0)
+ {
+ enemy = tr.ent;
+ }
+ }
+ }
+ else
+ {
+ tr = gi.trace(start, mins, maxs, end, self, MASK_SHOT);
+
+ if (tr.ent != world)
+ {
+ if (tr.ent->svflags & SVF_MONSTER || tr.ent->client ||
+ tr.ent->svflags & SVF_DAMAGEABLE)
+ {
+ if (tr.ent->health > 0)
+ {
+ enemy = tr.ent;
+ }
+ }
+ }
+ }
+
+ VectorScale(forward, -2, self->client->kick_origin);
+ self->client->kick_angles[0] = -1;
+
+ fire_tracker(self, start, forward, damage, 1000, enemy);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(MZ_TRACKER);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(self, start, PNOISE_WEAPON);
+
+ self->client->ps.gunframe++;
+ self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+}
+
+void
+Weapon_Disintegrator(edict_t *ent)
+{
+ static int pause_frames[] = {14, 19, 23, 0};
+ static int fire_frames[] = {5, 0};
+
+ Weapon_Generic(ent, 4, 9, 29, 34, pause_frames,
+ fire_frames, weapon_tracker_fire);
+}
+
+/*
+ * ======================================================================
+ *
+ * ETF RIFLE
+ *
+ * ======================================================================
+ */
+void
+weapon_etf_rifle_fire(edict_t *ent)
+{
+ vec3_t forward, right, up;
+ vec3_t start, tempPt;
+ int damage = 10;
+ int kick = 3;
+ int i;
+ vec3_t offset;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->pers.inventory[ent->client->ammo_index] < ent->client->pers.weapon->quantity)
+ {
+ VectorClear(ent->client->kick_origin);
+ VectorClear(ent->client->kick_angles);
+ ent->client->ps.gunframe = 8;
+
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ kick *= damage_multiplier;
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->kick_origin[i] = crandom() * 0.85;
+ ent->client->kick_angles[i] = crandom() * 0.85;
+ }
+
+ /* get start / end positions */
+ AngleVectors(ent->client->v_angle, forward, right, up);
+
+ if (ent->client->ps.gunframe == 6) /* right barrel */
+ {
+ VectorSet(offset, 15, 8, -8);
+ }
+ else /* left barrel */
+ {
+ VectorSet(offset, 15, 6, -8);
+ }
+
+ VectorCopy(ent->s.origin, tempPt);
+ tempPt[2] += ent->viewheight;
+ P_ProjectSource2(ent, tempPt, offset, forward, right, up, start);
+ fire_flechette(ent, start, forward, damage, 750, kick);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_ETF_RIFLE);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ ent->client->ps.gunframe++;
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - 1;
+ ent->client->anim_end = FRAME_attack8;
+ }
+}
+
+void
+Weapon_ETF_Rifle(edict_t *ent)
+{
+ static int pause_frames[] = {18, 28, 0};
+ static int fire_frames[] = {6, 7, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ if (ent->client->pers.inventory[ent->client->ammo_index] <= 0)
+ {
+ ent->client->ps.gunframe = 8;
+ }
+ }
+
+ Weapon_Generic(ent, 4, 7, 37, 41, pause_frames,
+ fire_frames, weapon_etf_rifle_fire);
+
+ if ((ent->client->ps.gunframe == 8) &&
+ (ent->client->buttons & BUTTON_ATTACK))
+ {
+ ent->client->ps.gunframe = 6;
+ }
+}
+
+void
+Heatbeam_Fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t offset;
+ int damage;
+ int kick;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = HEATBEAM_DM_DMG;
+ kick = 75; /* really knock 'em around in deathmatch */
+ }
+ else
+ {
+ damage = HEATBEAM_SP_DMG;
+ kick = 30;
+ }
+
+ ent->client->ps.gunframe++;
+ ent->client->ps.gunindex = gi.modelindex("models/weapons/v_beamer2/tris.md2");
+
+ if (is_quad)
+ {
+ damage *= damage_multiplier;
+ kick *= damage_multiplier;
+ }
+
+ VectorClear(ent->client->kick_origin);
+ VectorClear(ent->client->kick_angles);
+
+ /* get start / end positions */
+ AngleVectors(ent->client->v_angle, forward, right, up);
+
+ /* This offset is the "view" offset for the beam start (used by trace) */
+ VectorSet(offset, 7, 2, ent->viewheight - 3);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ /* This offset is the entity offset */
+ VectorSet(offset, 2, 7, -3);
+
+ fire_heat(ent, start, forward, offset, damage, kick, false);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_HEATBEAM | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - 1;
+ ent->client->anim_end = FRAME_attack8;
+ }
+}
+
+void
+Weapon_Heatbeam(edict_t *ent)
+{
+ static int pause_frames[] = {35, 0};
+ static int fire_frames[] = {9, 10, 11, 12, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ ent->client->weapon_sound = gi.soundindex("weapons/bfg__l1a.wav");
+
+ if ((ent->client->pers.inventory[ent->client->ammo_index] >= 2) &&
+ ((ent->client->buttons) & BUTTON_ATTACK))
+ {
+ if (ent->client->ps.gunframe >= 13)
+ {
+ ent->client->ps.gunframe = 9;
+ ent->client->ps.gunindex = gi.modelindex("models/weapons/v_beamer2/tris.md2");
+ }
+ else
+ {
+ ent->client->ps.gunindex = gi.modelindex("models/weapons/v_beamer2/tris.md2");
+ }
+ }
+ else
+ {
+ ent->client->ps.gunframe = 13;
+ ent->client->ps.gunindex = gi.modelindex("models/weapons/v_beamer/tris.md2");
+ }
+ }
+ else
+ {
+ ent->client->ps.gunindex = gi.modelindex("models/weapons/v_beamer/tris.md2");
+ ent->client->weapon_sound = 0;
+ }
+
+ Weapon_Generic(ent, 8, 12, 39, 44, pause_frames, fire_frames, Heatbeam_Fire);
+}
diff --git a/rogue/src/savegame/savegame.c b/rogue/src/savegame/savegame.c
new file mode 100644
index 0000000..c85b006
--- /dev/null
+++ b/rogue/src/savegame/savegame.c
@@ -0,0 +1,1189 @@
+/*
+ * =======================================================================
+ *
+ * The savegame system.
+ *
+ * =======================================================================
+ */
+
+/*
+ * This is the Quake 2 savegame system, fixed by Yamagi
+ * based on an idea by Knightmare of kmquake2. This major
+ * rewrite of the original g_save.c is much more robust
+ * and portable since it doesn't use any function pointers.
+ *
+ * Inner workings:
+ * When the game is saved all function pointers are
+ * translated into human readable function definition strings.
+ * The same way all mmove_t pointers are translated. This
+ * human readable strings are then written into the file.
+ * At game load the human readable strings are retranslated
+ * into the actual function pointers and struct pointers. The
+ * pointers are generated at each compilation / start of the
+ * client, thus the pointers are always correct.
+ *
+ * Limitations:
+ * While savegames survive recompilations of the game source
+ * and bigger changes in the source, there are some limitation
+ * which a nearly impossible to fix without a object orientated
+ * rewrite of the game.
+ * - If functions or mmove_t structs that a referencenced
+ * inside savegames are added or removed (e.g. the files
+ * in tables/ are altered) the load functions cannot
+ * reconnect all pointers and thus not restore the game.
+ * - If the operating system is changed internal structures
+ * may change in an unrepairable way.
+ * - If the architecture is changed pointer length and
+ * other internal datastructures change in an
+ * incompatible way.
+ * - If the edict_t struct is changed, savegames
+ * will break.
+ * This is not so bad as it looks since functions and
+ * struct won't be added and edict_t won't be changed
+ * if no big, sweeping changes are done. The operating
+ * system and architecture are in the hands of the user.
+ */
+
+#include "../header/local.h"
+
+/*
+ * When ever the savegame version is changed, q2 will refuse to
+ * load older savegames. This should be bumped if the files
+ * in tables/ are changed, otherwise strange things may happen.
+ */
+#define SAVEGAMEVER "YQ2-5"
+
+/*
+ * This macros are used to prohibit loading of savegames
+ * created on other systems or architectures. This will
+ * crash q2 in spectacular ways
+ */
+#ifndef YQ2OSTYPE
+#error YQ2OSTYPE should be defined by the build system
+#endif
+
+#ifndef YQ2ARCH
+#error YQ2ARCH should be defined by the build system
+#endif
+
+/*
+ * Older operating systen and architecture detection
+ * macros, implemented by savegame version YQ2-2.
+ */
+#if defined(__APPLE__)
+#define YQ2OSTYPE_1 "MacOS X"
+#elif defined(__FreeBSD__)
+#define YQ2OSTYPE_1 "FreeBSD"
+#elif defined(__OpenBSD__)
+#define YQ2OSTYPE_1 "OpenBSD"
+#elif defined(__linux__)
+ #define YQ2OSTYPE_1 "Linux"
+#elif defined(_WIN32)
+ #define YQ2OSTYPE_1 "Windows"
+#else
+ #define YQ2OSTYPE_1 "Unknown"
+#endif
+
+#if defined(__i386__)
+#define YQ2ARCH_1 "i386"
+#elif defined(__x86_64__)
+#define YQ2ARCH_1 "amd64"
+#elif defined(__sparc__)
+#define YQ2ARCH_1 "sparc64"
+#elif defined(__ia64__)
+ #define YQ2ARCH_1 "ia64"
+#else
+ #define YQ2ARCH_1 "unknown"
+#endif
+
+/*
+ * Connects a human readable
+ * function signature with
+ * the corresponding pointer
+ */
+typedef struct
+{
+ char *funcStr;
+ byte *funcPtr;
+} functionList_t;
+
+/*
+ * Connects a human readable
+ * mmove_t string with the
+ * correspondig pointer
+ * */
+typedef struct
+{
+ char *mmoveStr;
+ mmove_t *mmovePtr;
+} mmoveList_t;
+
+typedef struct
+{
+ char ver[32];
+ char game[32];
+ char os[32];
+ char arch[32];
+} savegameHeader_t;
+
+/* ========================================================= */
+
+/*
+ * Prototypes for forward
+ * declaration for all game
+ * functions.
+ */
+#include "tables/gamefunc_decs.h"
+
+/*
+ * List with function pointer
+ * to each of the functions
+ * prototyped above.
+ */
+functionList_t functionList[] = {
+ #include "tables/gamefunc_list.h"
+};
+
+/*
+ * Prtotypes for forward
+ * declaration for all game
+ * mmove_t functions.
+ */
+#include "tables/gamemmove_decs.h"
+
+/*
+ * List with pointers to
+ * each of the mmove_t
+ * functions prototyped
+ * above.
+ */
+mmoveList_t mmoveList[] = {
+ #include "tables/gamemmove_list.h"
+};
+
+/*
+ * Fields to be saved
+ */
+field_t fields[] = {
+ #include "tables/fields.h"
+};
+
+/*
+ * Level fields to
+ * be saved
+ */
+field_t levelfields[] = {
+ #include "tables/levelfields.h"
+};
+
+/*
+ * Client fields to
+ * be saved
+ */
+field_t clientfields[] = {
+ #include "tables/clientfields.h"
+};
+
+/* ========================================================= */
+
+/*
+ * This will be called when the dll is first loaded,
+ * which only happens when a new game is started or
+ * a save game is loaded.
+ */
+void
+InitGame(void)
+{
+ gi.dprintf("Game is starting up.\n");
+ gi.dprintf("Game is %s built on %s.\n", GAMEVERSION, __DATE__);
+
+ gun_x = gi.cvar ("gun_x", "0", 0);
+ gun_y = gi.cvar ("gun_y", "0", 0);
+ gun_z = gi.cvar ("gun_z", "0", 0);
+ sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
+ sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
+ sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
+ sv_gravity = gi.cvar ("sv_gravity", "800", 0);
+ sv_stopspeed = gi.cvar ("sv_stopspeed", "100", 0);
+ g_showlogic = gi.cvar ("g_showlogic", "0", 0);
+ huntercam = gi.cvar ("huntercam", "1", CVAR_SERVERINFO|CVAR_LATCH);
+ strong_mines = gi.cvar ("strong_mines", "0", 0);
+ randomrespawn = gi.cvar ("randomrespawn", "0", 0);
+
+ /* noset vars */
+ dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
+
+ /* latched vars */
+ sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
+ gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
+ gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH);
+ maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
+ maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO);
+ deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
+ coop = gi.cvar ("coop", "0", CVAR_LATCH);
+ coop_baseq2 = gi.cvar ("coop_baseq2", "0", CVAR_LATCH);
+ coop_elevator_delay = gi.cvar("coop_elevator_delay", "1.0", CVAR_ARCHIVE);
+ coop_pickup_weapons = gi.cvar("coop_pickup_weapons", "0", CVAR_ARCHIVE);
+ skill = gi.cvar ("skill", "1", CVAR_LATCH);
+ maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
+ gamerules = gi.cvar ("gamerules", "0", CVAR_LATCH); //PGM
+ g_footsteps = gi.cvar ("g_footsteps", "1", CVAR_LATCH);
+ g_fix_triggered = gi.cvar ("g_fix_triggered", "0", 0);
+
+ /* change anytime vars */
+ dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
+ fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
+ timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
+ password = gi.cvar ("password", "", CVAR_USERINFO);
+ spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO);
+ filterban = gi.cvar ("filterban", "1", 0);
+
+ g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
+
+ run_pitch = gi.cvar ("run_pitch", "0.002", 0);
+ run_roll = gi.cvar ("run_roll", "0.005", 0);
+ bob_up = gi.cvar ("bob_up", "0.005", 0);
+ bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
+ bob_roll = gi.cvar ("bob_roll", "0.002", 0);
+
+ /* flood control */
+ flood_msgs = gi.cvar ("flood_msgs", "4", 0);
+ flood_persecond = gi.cvar ("flood_persecond", "4", 0);
+ flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
+
+ /* dm map list */
+ sv_maplist = gi.cvar ("sv_maplist", "", 0);
+
+ /* disruptor availability */
+ g_disruptor = gi.cvar ("g_disruptor", "0", 0);
+
+ /* others */
+ aimfix = gi.cvar("aimfix", "0", CVAR_ARCHIVE);
+ g_machinegun_norecoil = gi.cvar("g_machinegun_norecoil", "0", CVAR_ARCHIVE);
+ g_swap_speed = gi.cvar("g_swap_speed", "1", 0);
+
+ /* items */
+ InitItems ();
+
+ game.helpmessage1[0] = 0;
+ game.helpmessage2[0] = 0;
+
+ /* initialize all entities for this game */
+ game.maxentities = maxentities->value;
+ g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+ globals.edicts = g_edicts;
+ globals.max_edicts = game.maxentities;
+
+ /* initialize all clients for this game */
+ game.maxclients = maxclients->value;
+ game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+ globals.num_edicts = game.maxclients+1;
+
+ if (gamerules)
+ {
+ InitGameRules();
+ }
+}
+
+/* ========================================================= */
+
+/*
+ * Helper function to get
+ * the human readable function
+ * definition by an address.
+ * Called by WriteField1 and
+ * WriteField2.
+ */
+functionList_t *
+GetFunctionByAddress(byte *adr)
+{
+ int i;
+
+ for (i = 0; functionList[i].funcStr; i++)
+ {
+ if (functionList[i].funcPtr == adr)
+ {
+ return &functionList[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper function to get the
+ * pointer to a function by
+ * it's human readable name.
+ * Called by WriteField1 and
+ * WriteField2.
+ */
+byte *
+FindFunctionByName(char *name)
+{
+ int i;
+
+ for (i = 0; functionList[i].funcStr; i++)
+ {
+ if (!strcmp(name, functionList[i].funcStr))
+ {
+ return functionList[i].funcPtr;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper function to get the
+ * human readable definition of
+ * a mmove_t struct by a pointer.
+ */
+mmoveList_t *
+GetMmoveByAddress(mmove_t *adr)
+{
+ int i;
+
+ for (i = 0; mmoveList[i].mmoveStr; i++)
+ {
+ if (mmoveList[i].mmovePtr == adr)
+ {
+ return &mmoveList[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper function to get the
+ * pointer to a mmove_t struct
+ * by a human readable definition.
+ */
+mmove_t *
+FindMmoveByName(char *name)
+{
+ int i;
+
+ for (i = 0; mmoveList[i].mmoveStr; i++)
+ {
+ if (!strcmp(name, mmoveList[i].mmoveStr))
+ {
+ return mmoveList[i].mmovePtr;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* ========================================================= */
+
+/*
+ * The following two functions are
+ * doing the dirty work to write the
+ * data generated by the functions
+ * below this block into files.
+ */
+void
+WriteField1(FILE *f, field_t *field, byte *base)
+{
+ void *p;
+ int len;
+ int index;
+ functionList_t *func;
+ mmoveList_t *mmove;
+
+ if (field->flags & FFL_SPAWNTEMP)
+ {
+ return;
+ }
+
+ p = (void *)(base + field->ofs);
+
+ switch (field->type)
+ {
+ case F_INT:
+ case F_FLOAT:
+ case F_ANGLEHACK:
+ case F_VECTOR:
+ case F_IGNORE:
+ break;
+
+ case F_LSTRING:
+ case F_GSTRING:
+
+ if (*(char **)p)
+ {
+ len = strlen(*(char **)p) + 1;
+ }
+ else
+ {
+ len = 0;
+ }
+
+ *(int *)p = len;
+ break;
+ case F_EDICT:
+
+ if (*(edict_t **)p == NULL)
+ {
+ index = -1;
+ }
+ else
+ {
+ index = *(edict_t **)p - g_edicts;
+ }
+
+ *(int *)p = index;
+ break;
+ case F_CLIENT:
+
+ if (*(gclient_t **)p == NULL)
+ {
+ index = -1;
+ }
+ else
+ {
+ index = *(gclient_t **)p - game.clients;
+ }
+
+ *(int *)p = index;
+ break;
+ case F_ITEM:
+
+ if (*(edict_t **)p == NULL)
+ {
+ index = -1;
+ }
+ else
+ {
+ index = *(gitem_t **)p - itemlist;
+ }
+
+ *(int *)p = index;
+ break;
+ case F_FUNCTION:
+
+ if (*(byte **)p == NULL)
+ {
+ len = 0;
+ }
+ else
+ {
+ func = GetFunctionByAddress (*(byte **)p);
+
+ if (!func)
+ {
+ gi.error ("WriteField1: function not in list, can't save game");
+ }
+
+ len = strlen(func->funcStr)+1;
+ }
+
+ *(int *)p = len;
+ break;
+ case F_MMOVE:
+
+ if (*(byte **)p == NULL)
+ {
+ len = 0;
+ }
+ else
+ {
+ mmove = GetMmoveByAddress (*(mmove_t **)p);
+
+ if (!mmove)
+ {
+ gi.error ("WriteField1: mmove not in list, can't save game");
+ }
+
+ len = strlen(mmove->mmoveStr)+1;
+ }
+
+ *(int *)p = len;
+ break;
+ default:
+ gi.error("WriteEdict: unknown field type");
+ }
+}
+
+void
+WriteField2(FILE *f, field_t *field, byte *base)
+{
+ int len;
+ void *p;
+ functionList_t *func;
+ mmoveList_t *mmove;
+
+ if (field->flags & FFL_SPAWNTEMP)
+ {
+ return;
+ }
+
+ p = (void *)(base + field->ofs);
+
+ switch (field->type)
+ {
+ case F_LSTRING:
+
+ if (*(char **)p)
+ {
+ len = strlen(*(char **)p) + 1;
+ fwrite(*(char **)p, len, 1, f);
+ }
+
+ break;
+ case F_FUNCTION:
+
+ if (*(byte **)p)
+ {
+ func = GetFunctionByAddress (*(byte **)p);
+
+ if (!func)
+ {
+ gi.error ("WriteField2: function not in list, can't save game");
+ }
+
+ len = strlen(func->funcStr)+1;
+ fwrite (func->funcStr, len, 1, f);
+ }
+
+ break;
+ case F_MMOVE:
+
+ if (*(byte **)p)
+ {
+ mmove = GetMmoveByAddress (*(mmove_t **)p);
+
+ if (!mmove)
+ {
+ gi.error ("WriteField2: mmove not in list, can't save game");
+ }
+
+ len = strlen(mmove->mmoveStr)+1;
+ fwrite (mmove->mmoveStr, len, 1, f);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+/* ========================================================= */
+
+/*
+ * This function does the dirty
+ * work to read the data from a
+ * file. The processing of the
+ * data is done in the functions
+ * below
+ */
+void
+ReadField(FILE *f, field_t *field, byte *base)
+{
+ void *p;
+ int len;
+ int index;
+ char funcStr[2048];
+
+ if (field->flags & FFL_SPAWNTEMP)
+ {
+ return;
+ }
+
+ p = (void *)(base + field->ofs);
+
+ switch (field->type)
+ {
+ case F_INT:
+ case F_FLOAT:
+ case F_ANGLEHACK:
+ case F_VECTOR:
+ case F_IGNORE:
+ break;
+
+ case F_LSTRING:
+ len = *(int *)p;
+
+ if (!len)
+ {
+ *(char **)p = NULL;
+ }
+ else
+ {
+ *(char **)p = gi.TagMalloc(32 + len, TAG_LEVEL);
+ fread(*(char **)p, len, 1, f);
+ }
+
+ break;
+ case F_EDICT:
+ index = *(int *)p;
+
+ if (index == -1)
+ {
+ *(edict_t **)p = NULL;
+ }
+ else
+ {
+ *(edict_t **)p = &g_edicts[index];
+ }
+
+ break;
+ case F_CLIENT:
+ index = *(int *)p;
+
+ if (index == -1)
+ {
+ *(gclient_t **)p = NULL;
+ }
+ else
+ {
+ *(gclient_t **)p = &game.clients[index];
+ }
+
+ break;
+ case F_ITEM:
+ index = *(int *)p;
+
+ if (index == -1)
+ {
+ *(gitem_t **)p = NULL;
+ }
+ else
+ {
+ *(gitem_t **)p = &itemlist[index];
+ }
+
+ break;
+ case F_FUNCTION:
+ len = *(int *)p;
+
+ if (!len)
+ {
+ *(byte **)p = NULL;
+ }
+ else
+ {
+ if (len > sizeof(funcStr))
+ {
+ gi.error ("ReadField: function name is longer than buffer (%i chars)",
+ (int)sizeof(funcStr));
+ }
+
+ fread (funcStr, len, 1, f);
+
+ if ( !(*(byte **)p = FindFunctionByName (funcStr)) )
+ {
+ gi.error ("ReadField: function %s not found in table, can't load game", funcStr);
+ }
+
+ }
+ break;
+ case F_MMOVE:
+ len = *(int *)p;
+
+ if (!len)
+ {
+ *(byte **)p = NULL;
+ }
+ else
+ {
+ if (len > sizeof(funcStr))
+ {
+ gi.error ("ReadField: mmove name is longer than buffer (%i chars)",
+ (int)sizeof(funcStr));
+ }
+
+ fread (funcStr, len, 1, f);
+
+ if ( !(*(mmove_t **)p = FindMmoveByName (funcStr)) )
+ {
+ gi.error ("ReadField: mmove %s not found in table, can't load game", funcStr);
+ }
+ }
+ break;
+
+ default:
+ gi.error("ReadEdict: unknown field type");
+ }
+}
+
+/* ========================================================= */
+
+/*
+ * Write the client struct into a file.
+ */
+void
+WriteClient(FILE *f, gclient_t *client)
+{
+ field_t *field;
+ gclient_t temp;
+
+ /* all of the ints, floats, and vectors stay as they are */
+ temp = *client;
+
+ /* change the pointers to indexes */
+ for (field = clientfields; field->name; field++)
+ {
+ WriteField1(f, field, (byte *)&temp);
+ }
+
+ /* write the block */
+ fwrite(&temp, sizeof(temp), 1, f);
+
+ /* now write any allocated data following the edict */
+ for (field = clientfields; field->name; field++)
+ {
+ WriteField2(f, field, (byte *)client);
+ }
+}
+
+/*
+ * Read the client struct from a file
+ */
+void
+ReadClient(FILE *f, gclient_t *client, short save_ver)
+{
+ field_t *field;
+
+ fread(client, sizeof(*client), 1, f);
+
+ for (field = clientfields; field->name; field++)
+ {
+ if (field->save_ver <= save_ver)
+ {
+ ReadField(f, field, (byte *)client);
+ }
+ }
+
+ if (save_ver < 4)
+ {
+ InitClientResp(client);
+ }
+}
+
+/* ========================================================= */
+
+/*
+ * Writes the game struct into
+ * a file. This is called when
+ * ever the games goes to e new
+ * level or the user saves the
+ * game. Saved informations are:
+ * - cross level data
+ * - client states
+ * - help computer info
+ */
+void
+WriteGame(const char *filename, qboolean autosave)
+{
+ savegameHeader_t sv;
+ FILE *f;
+ int i;
+
+ if (!autosave)
+ {
+ SaveClientData();
+ }
+
+ f = fopen(filename, "wb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* Savegame identification */
+ memset(&sv, 0, sizeof(sv));
+
+ Q_strlcpy(sv.ver, SAVEGAMEVER, sizeof(sv.ver) - 1);
+ Q_strlcpy(sv.game, GAMEVERSION, sizeof(sv.game) - 1);
+ Q_strlcpy(sv.os, YQ2OSTYPE, sizeof(sv.os) - 1);
+ Q_strlcpy(sv.arch, YQ2ARCH, sizeof(sv.arch) - 1);
+
+ fwrite(&sv, sizeof(sv), 1, f);
+
+ game.autosaved = autosave;
+ fwrite(&game, sizeof(game), 1, f);
+ game.autosaved = false;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ WriteClient(f, &game.clients[i]);
+ }
+
+ fclose(f);
+}
+
+/*
+ * Read the game structs from
+ * a file. Called when ever a
+ * savegames is loaded.
+ */
+void
+ReadGame(const char *filename)
+{
+ savegameHeader_t sv;
+ FILE *f;
+ int i;
+
+ short save_ver = 0;
+
+ gi.FreeTags(TAG_GAME);
+
+ f = fopen(filename, "rb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* Sanity checks */
+ fread(&sv, sizeof(sv), 1, f);
+
+ static const struct {
+ const char* verstr;
+ int vernum;
+ } version_mappings[] = {
+ {"YQ2-1", 1},
+ {"YQ2-2", 2},
+ {"YQ2-3", 3},
+ {"YQ2-4", 4},
+ {"YQ2-5", 5},
+ };
+
+ for (i=0; i < sizeof(version_mappings)/sizeof(version_mappings[0]); ++i)
+ {
+ if (strcmp(version_mappings[i].verstr, sv.ver) == 0)
+ {
+ save_ver = version_mappings[i].vernum;
+ break;
+ }
+ }
+
+ if(save_ver < 2)
+ {
+ fclose(f);
+ gi.error("Savegame from an incompatible version.\n");
+ }
+ else if (save_ver == 2)
+ {
+ if (strcmp(sv.game, GAMEVERSION))
+ {
+ fclose(f);
+ gi.error("Savegame from an other game.so.\n");
+ }
+ else if (strcmp(sv.os, YQ2OSTYPE_1))
+ {
+ fclose(f);
+ gi.error("Savegame from an other os.\n");
+ }
+
+#ifdef _WIN32
+ /* Windows was forced to i386 */
+ if (strcmp(sv.arch, "i386") != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another architecture.\n");
+ }
+#else
+ if (strcmp(sv.arch, YQ2ARCH_1) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another architecture.\n");
+ }
+#endif
+ }
+ else // all newer savegame versions
+ {
+ if (strcmp(sv.game, GAMEVERSION) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another game.so.\n");
+ }
+ else if (strcmp(sv.os, YQ2OSTYPE) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another os.\n");
+ }
+ else if (strcmp(sv.arch, YQ2ARCH) != 0)
+ {
+#if defined(_WIN32) && (defined(__i386__) || defined(_M_IX86))
+ // before savegame version "YQ2-5" (and after version 2),
+ // the official Win32 binaries accidentally had the YQ2ARCH "AMD64"
+ // instead of "i386" set due to a bug in the Makefile.
+ // This quirk allows loading those savegames anyway
+ if (save_ver >= 5 || strcmp(sv.arch, "AMD64") != 0)
+#endif
+ {
+ fclose(f);
+ gi.error("Savegame from another architecture.\n");
+ }
+ }
+ }
+
+ g_edicts = gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+ globals.edicts = g_edicts;
+
+ fread(&game, sizeof(game), 1, f);
+ game.clients = gi.TagMalloc(game.maxclients * sizeof(game.clients[0]),
+ TAG_GAME);
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ReadClient(f, &game.clients[i], save_ver);
+ }
+
+ fclose(f);
+}
+
+/* ========================================================== */
+
+/*
+ * Helper function to write the
+ * edict into a file. Called by
+ * WriteLevel.
+ */
+void
+WriteEdict(FILE *f, edict_t *ent)
+{
+ field_t *field;
+ edict_t temp;
+
+ /* all of the ints, floats, and vectors stay as they are */
+ temp = *ent;
+
+ /* change the pointers to lengths or indexes */
+ for (field = fields; field->name; field++)
+ {
+ WriteField1(f, field, (byte *)&temp);
+ }
+
+ /* write the block */
+ fwrite(&temp, sizeof(temp), 1, f);
+
+ /* now write any allocated data following the edict */
+ for (field = fields; field->name; field++)
+ {
+ WriteField2(f, field, (byte *)ent);
+ }
+}
+
+/*
+ * Helper fcuntion to write the
+ * level local data into a file.
+ * Called by WriteLevel.
+ */
+void
+WriteLevelLocals(FILE *f)
+{
+ field_t *field;
+ level_locals_t temp;
+
+ /* all of the ints, floats, and vectors stay as they are */
+ temp = level;
+
+ /* change the pointers to lengths or indexes */
+ for (field = levelfields; field->name; field++)
+ {
+ WriteField1(f, field, (byte *)&temp);
+ }
+
+ /* write the block */
+ fwrite(&temp, sizeof(temp), 1, f);
+
+ /* now write any allocated data following the edict */
+ for (field = levelfields; field->name; field++)
+ {
+ WriteField2(f, field, (byte *)&level);
+ }
+}
+
+/*
+ * Writes the current level
+ * into a file.
+ */
+void
+WriteLevel(const char *filename)
+{
+ int i;
+ edict_t *ent;
+ FILE *f;
+
+ f = fopen(filename, "wb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* write out edict size for checking */
+ i = sizeof(edict_t);
+ fwrite(&i, sizeof(i), 1, f);
+
+ /* write out level_locals_t */
+ WriteLevelLocals(f);
+
+ /* write out all the entities */
+ for (i = 0; i < globals.num_edicts; i++)
+ {
+ ent = &g_edicts[i];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ fwrite(&i, sizeof(i), 1, f);
+ WriteEdict(f, ent);
+ }
+
+ i = -1;
+ fwrite(&i, sizeof(i), 1, f);
+
+ fclose(f);
+}
+
+/* ========================================================== */
+
+/*
+ * A helper function to
+ * read the edict back
+ * into the memory. Called
+ * by ReadLevel.
+ */
+void
+ReadEdict(FILE *f, edict_t *ent)
+{
+ field_t *field;
+
+ fread(ent, sizeof(*ent), 1, f);
+
+ for (field = fields; field->name; field++)
+ {
+ ReadField(f, field, (byte *)ent);
+ }
+}
+
+/*
+ * A helper function to
+ * read the level local
+ * data from a file.
+ * Called by ReadLevel.
+ */
+void
+ReadLevelLocals(FILE *f)
+{
+ field_t *field;
+
+ fread(&level, sizeof(level), 1, f);
+
+ for (field = levelfields; field->name; field++)
+ {
+ ReadField(f, field, (byte *)&level);
+ }
+}
+
+/*
+ * Reads a level back into the memory.
+ * SpawnEntities were allready called
+ * in the same way when the level was
+ * saved. All world links were cleared
+ * befor this function was called. When
+ * this function is called, no clients
+ * are connected to the server.
+ */
+void
+ReadLevel(const char *filename)
+{
+ int entnum;
+ FILE *f;
+ int i;
+ edict_t *ent;
+
+ f = fopen(filename, "rb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* free any dynamic memory allocated by
+ loading the level base state */
+ gi.FreeTags(TAG_LEVEL);
+
+ /* wipe all the entities */
+ memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0]));
+ globals.num_edicts = maxclients->value + 1;
+
+ /* check edict size */
+ fread(&i, sizeof(i), 1, f);
+
+ if (i != sizeof(edict_t))
+ {
+ fclose(f);
+ gi.error("ReadLevel: mismatched edict size");
+ }
+
+ /* load the level locals */
+ ReadLevelLocals(f);
+
+ /* load all the entities */
+ while (1)
+ {
+ if (fread(&entnum, sizeof(entnum), 1, f) != 1)
+ {
+ fclose(f);
+ gi.error("ReadLevel: failed to read entnum");
+ }
+
+ if (entnum == -1)
+ {
+ break;
+ }
+
+ if (entnum >= globals.num_edicts)
+ {
+ globals.num_edicts = entnum + 1;
+ }
+
+ ent = &g_edicts[entnum];
+ ReadEdict(f, ent);
+
+ /* let the server rebuild world links for this ent */
+ memset(&ent->area, 0, sizeof(ent->area));
+ gi.linkentity(ent);
+ }
+
+ fclose(f);
+
+ /* mark all clients as unconnected */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ ent = &g_edicts[i + 1];
+ ent->client = game.clients + i;
+ ent->client->pers.connected = false;
+ }
+
+ /* do any load time things at this point */
+ for (i = 0; i < globals.num_edicts; i++)
+ {
+ ent = &g_edicts[i];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ /* fire any cross-level triggers */
+ if (ent->classname)
+ {
+ if (strcmp(ent->classname, "target_crosslevel_target") == 0)
+ {
+ ent->nextthink = level.time + ent->delay;
+ }
+ }
+ }
+}
+
diff --git a/rogue/src/savegame/tables/clientfields.h b/rogue/src/savegame/tables/clientfields.h
new file mode 100644
index 0000000..6ac8543
--- /dev/null
+++ b/rogue/src/savegame/tables/clientfields.h
@@ -0,0 +1,15 @@
+/*
+ * =======================================================================
+ *
+ * Fields of the client to be saved.
+ *
+ * =======================================================================
+ */
+
+{"pers.weapon", CLOFS(pers.weapon), F_ITEM},
+{"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM},
+{"newweapon", CLOFS(newweapon), F_ITEM},
+{"owned_sphere", CLOFS(owned_sphere), F_EDICT},
+{"resp.coop_respawn.weapon", CLOFS(resp.coop_respawn.weapon), F_ITEM, 0, 4},
+{"resp.coop_respawn.lastweapon", CLOFS(resp.coop_respawn.lastweapon), F_ITEM, 0, 4},
+{NULL, 0, F_INT}
diff --git a/rogue/src/savegame/tables/fields.h b/rogue/src/savegame/tables/fields.h
new file mode 100644
index 0000000..7436e2a
--- /dev/null
+++ b/rogue/src/savegame/tables/fields.h
@@ -0,0 +1,102 @@
+/*
+ * =======================================================================
+ *
+ * Game fields to be saved.
+ *
+ * =======================================================================
+ */
+
+{"classname", FOFS(classname), F_LSTRING},
+{"model", FOFS(model), F_LSTRING},
+{"spawnflags", FOFS(spawnflags), F_INT},
+{"speed", FOFS(speed), F_FLOAT},
+{"accel", FOFS(accel), F_FLOAT},
+{"decel", FOFS(decel), F_FLOAT},
+{"target", FOFS(target), F_LSTRING},
+{"targetname", FOFS(targetname), F_LSTRING},
+{"pathtarget", FOFS(pathtarget), F_LSTRING},
+{"deathtarget", FOFS(deathtarget), F_LSTRING},
+{"killtarget", FOFS(killtarget), F_LSTRING},
+{"combattarget", FOFS(combattarget), F_LSTRING},
+{"message", FOFS(message), F_LSTRING},
+{"team", FOFS(team), F_LSTRING},
+{"wait", FOFS(wait), F_FLOAT},
+{"delay", FOFS(delay), F_FLOAT},
+{"random", FOFS(random), F_FLOAT},
+{"move_origin", FOFS(move_origin), F_VECTOR},
+{"move_angles", FOFS(move_angles), F_VECTOR},
+{"style", FOFS(style), F_INT},
+{"count", FOFS(count), F_INT},
+{"health", FOFS(health), F_INT},
+{"sounds", FOFS(sounds), F_INT},
+{"light", 0, F_IGNORE},
+{"dmg", FOFS(dmg), F_INT},
+{"mass", FOFS(mass), F_INT},
+{"volume", FOFS(volume), F_FLOAT},
+{"attenuation", FOFS(attenuation), F_FLOAT},
+{"map", FOFS(map), F_LSTRING},
+{"origin", FOFS(s.origin), F_VECTOR},
+{"angles", FOFS(s.angles), F_VECTOR},
+{"angle", FOFS(s.angles), F_ANGLEHACK},
+{"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN},
+{"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN},
+{"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN},
+{"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN},
+{"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN},
+{"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN},
+{"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN},
+{"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN},
+{"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN},
+{"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN},
+{"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN},
+{"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN},
+{"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN},
+{"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN},
+{"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN},
+{"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN},
+{"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN},
+{"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN},
+{"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN},
+{"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN},
+{"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN},
+{"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN},
+{"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN},
+{"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN},
+{"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN},
+{"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN},
+{"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN},
+{"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN},
+{"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN},
+{"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN},
+{"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN},
+{"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN},
+{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
+{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
+{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
+{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
+{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
+{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
+{"item", FOFS(item), F_ITEM},
+{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
+{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
+{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
+{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
+{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
+{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
+{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
+{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
+{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP},
+{"bad_area", FOFS(bad_area), F_EDICT},
+{"hint_chain", FOFS(hint_chain), F_EDICT},
+{"monster_hint_chain", FOFS(monster_hint_chain), F_EDICT},
+{"target_hint_chain", FOFS(target_hint_chain), F_EDICT},
+{"goal_hint", FOFS(monsterinfo.goal_hint), F_EDICT},
+{"badMedic1", FOFS(monsterinfo.badMedic1), F_EDICT},
+{"badMedic2", FOFS(monsterinfo.badMedic2), F_EDICT},
+{"last_player_enemy", FOFS(monsterinfo.last_player_enemy), F_EDICT},
+{"commander", FOFS(monsterinfo.commander), F_EDICT},
+{"blocked", FOFS(monsterinfo.blocked), F_FUNCTION, FFL_NOSPAWN},
+{"duck", FOFS(monsterinfo.duck), F_FUNCTION, FFL_NOSPAWN},
+{"unduck", FOFS(monsterinfo.unduck), F_FUNCTION, FFL_NOSPAWN},
+{"sidestep", FOFS(monsterinfo.sidestep), F_FUNCTION, FFL_NOSPAWN},
+{0, 0, 0, 0}
diff --git a/rogue/src/savegame/tables/gamefunc_decs.h b/rogue/src/savegame/tables/gamefunc_decs.h
new file mode 100644
index 0000000..3791ef9
--- /dev/null
+++ b/rogue/src/savegame/tables/gamefunc_decs.h
@@ -0,0 +1,1491 @@
+/*
+ * =======================================================================
+ *
+ * Prototypes for every function in the game.so.
+ *
+ * =======================================================================
+ */
+
+extern void ReadLevel ( const char * filename ) ;
+extern void ReadLevelLocals ( FILE * f ) ;
+extern void ReadEdict ( FILE * f , edict_t * ent ) ;
+extern void WriteLevel ( const char * filename ) ;
+extern void WriteLevelLocals ( FILE * f ) ;
+extern void WriteEdict ( FILE * f , edict_t * ent ) ;
+extern void ReadGame ( const char * filename ) ;
+extern void WriteGame ( const char * filename , qboolean autosave ) ;
+extern void ReadClient ( FILE * f , gclient_t * client , short save_ver ) ;
+extern void WriteClient ( FILE * f , gclient_t * client ) ;
+extern void ReadField ( FILE * f , field_t * field , byte * base ) ;
+extern void WriteField2 ( FILE * f , field_t * field , byte * base ) ;
+extern void WriteField1 ( FILE * f , field_t * field , byte * base ) ;
+extern mmove_t * FindMmoveByName ( char * name ) ;
+extern mmoveList_t * GetMmoveByAddress ( mmove_t * adr ) ;
+extern byte * FindFunctionByName ( char * name ) ;
+extern functionList_t * GetFunctionByAddress ( byte * adr ) ;
+extern void InitGame ( void ) ;
+extern void Info_SetValueForKey ( char * s , char * key , char * value ) ;
+extern qboolean Info_Validate ( char * s ) ;
+extern void Info_RemoveKey ( char * s , char * key ) ;
+extern char * Info_ValueForKey ( char * s , char * key ) ;
+extern void Com_sprintf ( char * dest , int size , char * fmt , ... ) ;
+extern int Q_strcasecmp ( char * s1 , char * s2 ) ;
+extern int Q_strncasecmp ( char * s1 , char * s2 , int n ) ;
+extern int Q_stricmp ( const char * s1 , const char * s2 ) ;
+extern void Com_PageInMemory ( byte * buffer , int size ) ;
+extern char * COM_Parse ( char * * data_p ) ;
+extern char * va ( const char * format , ... ) ;
+extern void Swap_Init ( void ) ;
+extern float FloatNoSwap ( float f ) ;
+extern float FloatSwap ( float f ) ;
+extern int LongNoSwap ( int l ) ;
+extern int LongSwap ( int l ) ;
+extern short ShortNoSwap ( short l ) ;
+extern short ShortSwap ( short l ) ;
+extern float LittleFloat ( float l ) ;
+extern float BigFloat ( float l ) ;
+extern int LittleLong ( int l ) ;
+extern int BigLong ( int l ) ;
+extern short LittleShort ( short l ) ;
+extern short BigShort ( short l ) ;
+extern void COM_DefaultExtension ( char * path , const char * extension ) ;
+extern void COM_FilePath ( const char * in , char * out ) ;
+extern void COM_FileBase ( char * in , char * out ) ;
+extern const char * COM_FileExtension ( const char * in ) ;
+extern void COM_StripExtension ( char * in , char * out ) ;
+extern char * COM_SkipPath ( char * pathname ) ;
+extern int Q_log2 ( int val ) ;
+extern void VectorScale ( vec3_t in , vec_t scale , vec3_t out ) ;
+extern void VectorInverse ( vec3_t v ) ;
+extern vec_t VectorLength ( vec3_t v ) ;
+extern void CrossProduct ( vec3_t v1 , vec3_t v2 , vec3_t cross ) ;
+extern void _VectorCopy ( vec3_t in , vec3_t out ) ;
+extern void _VectorAdd ( vec3_t veca , vec3_t vecb , vec3_t out ) ;
+extern void _VectorSubtract ( vec3_t veca , vec3_t vecb , vec3_t out ) ;
+extern vec_t _DotProduct ( vec3_t v1 , vec3_t v2 ) ;
+extern void VectorMA ( vec3_t veca , float scale , vec3_t vecb , vec3_t vecc ) ;
+extern vec_t VectorNormalize2 ( vec3_t v , vec3_t out ) ;
+extern vec_t VectorNormalize ( vec3_t v ) ;
+extern int VectorCompare ( vec3_t v1 , vec3_t v2 ) ;
+extern void AddPointToBounds ( vec3_t v , vec3_t mins , vec3_t maxs ) ;
+extern void ClearBounds ( vec3_t mins , vec3_t maxs ) ;
+extern int BoxOnPlaneSide2 ( vec3_t emins , vec3_t emaxs , struct cplane_s * p ) ;
+extern float anglemod ( float a ) ;
+extern float LerpAngle ( float a2 , float a1 , float frac ) ;
+extern float Q_fabs ( float f ) ;
+extern void R_ConcatTransforms ( float in1 [ 3 ] [ 4 ] , float in2 [ 3 ] [ 4 ] , float out [ 3 ] [ 4 ] ) ;
+extern void R_ConcatRotations ( float in1 [ 3 ] [ 3 ] , float in2 [ 3 ] [ 3 ] , float out [ 3 ] [ 3 ] ) ;
+extern void PerpendicularVector ( vec3_t dst , const vec3_t src ) ;
+extern void ProjectPointOnPlane ( vec3_t dst , const vec3_t p , const vec3_t normal ) ;
+extern void AngleVectors ( vec3_t angles , vec3_t forward , vec3_t right , vec3_t up ) ;
+extern void RotatePointAroundVector ( vec3_t dst , const vec3_t dir , const vec3_t point , float degrees ) ;
+extern void Weapon_Heatbeam ( edict_t * ent ) ;
+extern void Heatbeam_Fire ( edict_t * ent ) ;
+extern void Weapon_ETF_Rifle ( edict_t * ent ) ;
+extern void weapon_etf_rifle_fire ( edict_t * ent ) ;
+extern void Weapon_Disintegrator ( edict_t * ent ) ;
+extern void weapon_tracker_fire ( edict_t * self ) ;
+extern void Weapon_ChainFist ( edict_t * ent ) ;
+extern void chainfist_smoke ( edict_t * ent ) ;
+extern void weapon_chainfist_fire ( edict_t * ent ) ;
+extern void Weapon_BFG ( edict_t * ent ) ;
+extern void weapon_bfg_fire ( edict_t * ent ) ;
+extern void Weapon_Railgun ( edict_t * ent ) ;
+extern void weapon_railgun_fire ( edict_t * ent ) ;
+extern void Weapon_SuperShotgun ( edict_t * ent ) ;
+extern void weapon_supershotgun_fire ( edict_t * ent ) ;
+extern void Weapon_Shotgun ( edict_t * ent ) ;
+extern void weapon_shotgun_fire ( edict_t * ent ) ;
+extern void Weapon_Chaingun ( edict_t * ent ) ;
+extern void Chaingun_Fire ( edict_t * ent ) ;
+extern void Weapon_Machinegun ( edict_t * ent ) ;
+extern void Machinegun_Fire ( edict_t * ent ) ;
+extern void Weapon_HyperBlaster ( edict_t * ent ) ;
+extern void Weapon_HyperBlaster_Fire ( edict_t * ent ) ;
+extern void Weapon_Blaster ( edict_t * ent ) ;
+extern void Weapon_Blaster_Fire ( edict_t * ent ) ;
+extern void Blaster_Fire ( edict_t * ent , vec3_t g_offset , int damage , qboolean hyper , int effect ) ;
+extern void Weapon_RocketLauncher ( edict_t * ent ) ;
+extern void Weapon_RocketLauncher_Fire ( edict_t * ent ) ;
+extern void Weapon_ProxLauncher ( edict_t * ent ) ;
+extern void Weapon_GrenadeLauncher ( edict_t * ent ) ;
+extern void weapon_grenadelauncher_fire ( edict_t * ent ) ;
+extern void Weapon_Tesla ( edict_t * ent ) ;
+extern void Weapon_Grenade ( edict_t * ent ) ;
+extern void Throw_Generic ( edict_t * ent , int FRAME_FIRE_LAST , int FRAME_IDLE_LAST , int FRAME_THROW_SOUND , int FRAME_THROW_HOLD , int FRAME_THROW_FIRE , int * pause_frames , int EXPLODE , void ( * fire ) ( edict_t * ent , qboolean held ) ) ;
+extern void weapon_grenade_fire ( edict_t * ent , qboolean held ) ;
+extern void Weapon_Generic ( edict_t * ent , int FRAME_ACTIVATE_LAST , int FRAME_FIRE_LAST , int FRAME_IDLE_LAST , int FRAME_DEACTIVATE_LAST , int * pause_frames , int * fire_frames , void ( * fire ) ( edict_t * ent ) ) ;
+extern void Drop_Weapon ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Weapon ( edict_t * ent , gitem_t * item ) ;
+extern void Think_Weapon ( edict_t * ent ) ;
+extern void NoAmmoWeaponChange ( edict_t * ent ) ;
+extern void ChangeWeapon ( edict_t * ent ) ;
+extern qboolean Pickup_Weapon ( edict_t * ent , edict_t * other ) ;
+extern void PlayerNoise ( edict_t * who , vec3_t where , int type ) ;
+extern void P_ProjectSource2 ( gclient_t * client , vec3_t point , vec3_t distance , vec3_t forward , vec3_t right , vec3_t up , vec3_t result ) ;
+extern void P_ProjectSource(edict_t *ent, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
+extern byte P_DamageModifier ( edict_t * ent ) ;
+extern void ClientEndServerFrame ( edict_t * ent ) ;
+extern void G_SetClientFrame ( edict_t * ent ) ;
+extern void G_SetClientSound ( edict_t * ent ) ;
+extern void G_SetClientEvent ( edict_t * ent ) ;
+extern void G_SetClientEffects ( edict_t * ent ) ;
+extern void P_WorldEffects ( void ) ;
+extern void P_FallingDamage ( edict_t * ent ) ;
+extern void SV_CalcBlend ( edict_t * ent ) ;
+extern void SV_AddBlend ( float r , float g , float b , float a , float * v_blend ) ;
+extern void SV_CalcGunOffset ( edict_t * ent ) ;
+extern void SV_CalcViewOffset ( edict_t * ent ) ;
+extern void P_DamageFeedback ( edict_t * player ) ;
+extern float SV_CalcRoll ( vec3_t angles , vec3_t velocity ) ;
+extern edict_t * PlayerTrail_LastSpot ( void ) ;
+extern edict_t * PlayerTrail_PickNext ( edict_t * self ) ;
+extern edict_t * PlayerTrail_PickFirst ( edict_t * self ) ;
+extern void PlayerTrail_New ( vec3_t spot ) ;
+extern void PlayerTrail_Add ( vec3_t spot ) ;
+extern void PlayerTrail_Init ( void ) ;
+extern void G_SetSpectatorStats ( edict_t * ent ) ;
+extern void G_CheckChaseStats ( edict_t * ent ) ;
+extern void G_SetStats ( edict_t * ent ) ;
+extern void InventoryMessage ( edict_t * ent ) ;
+extern void HelpComputerMessage ( edict_t * ent ) ;
+extern void DeathmatchScoreboardMessage ( edict_t * ent , edict_t * killer ) ;
+extern void BeginIntermission ( edict_t * targ ) ;
+extern void MoveClientToIntermission ( edict_t * ent ) ;
+extern void RemoveAttackingPainDaemons ( edict_t * self ) ;
+extern void ClientBeginServerFrame ( edict_t * ent ) ;
+extern void ClientThink ( edict_t * ent , usercmd_t * ucmd ) ;
+extern void PrintPmove ( pmove_t * pm ) ;
+extern unsigned CheckBlock ( void * b , int c ) ;
+extern trace_t PM_trace ( vec3_t start , vec3_t mins , vec3_t maxs , vec3_t end ) ;
+extern void ClientDisconnect ( edict_t * ent ) ;
+extern qboolean ClientConnect ( edict_t * ent , char * userinfo ) ;
+extern void ClientUserinfoChanged ( edict_t * ent , char * userinfo ) ;
+extern void ClientBegin ( edict_t * ent ) ;
+extern void ClientBeginDeathmatch ( edict_t * ent ) ;
+extern void PutClientInServer ( edict_t * ent ) ;
+extern void spectator_respawn ( edict_t * ent ) ;
+extern void respawn ( edict_t * self ) ;
+extern void CopyToBodyQue ( edict_t * ent ) ;
+extern void body_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void InitBodyQue ( void ) ;
+extern void SelectSpawnPoint ( edict_t * ent , vec3_t origin , vec3_t angles ) ;
+extern edict_t * SelectCoopSpawnPoint ( edict_t * ent ) ;
+extern edict_t * SelectLavaCoopSpawnPoint ( edict_t * ent ) ;
+extern edict_t * SelectDeathmatchSpawnPoint ( void ) ;
+extern edict_t * SelectFarthestDeathmatchSpawnPoint ( void ) ;
+extern edict_t * SelectRandomDeathmatchSpawnPoint ( void ) ;
+extern float PlayersRangeFromSpot ( edict_t * spot ) ;
+extern void FetchClientEntData ( edict_t * ent ) ;
+extern void SaveClientData ( void ) ;
+extern void InitClientResp ( gclient_t * client ) ;
+extern void InitClientPersistant ( gclient_t * client ) ;
+extern void player_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void LookAtKiller ( edict_t * self , edict_t * inflictor , edict_t * attacker ) ;
+extern void TossClientWeapon ( edict_t * self ) ;
+extern void ClientObituary ( edict_t * self , edict_t * inflictor , edict_t * attacker ) ;
+extern qboolean IsNeutral ( edict_t * ent ) ;
+extern qboolean IsFemale ( edict_t * ent ) ;
+extern void player_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void SP_info_player_intermission ( edict_t *ent );
+extern void SP_info_player_coop_lava ( edict_t * self ) ;
+extern void SP_info_player_coop ( edict_t * self ) ;
+extern void SP_info_player_deathmatch ( edict_t * self ) ;
+extern void SP_info_player_start ( edict_t * self ) ;
+extern void ThrowArm2 ( edict_t * self ) ;
+extern void ThrowArm1 ( edict_t * self ) ;
+extern void WidowExplosionLeg ( edict_t * self ) ;
+extern void WidowExplosion7 ( edict_t * self ) ;
+extern void WidowExplosion6 ( edict_t * self ) ;
+extern void WidowExplosion5 ( edict_t * self ) ;
+extern void WidowExplosion4 ( edict_t * self ) ;
+extern void WidowExplosion3 ( edict_t * self ) ;
+extern void WidowExplosion2 ( edict_t * self ) ;
+extern void WidowExplosion1 ( edict_t * self ) ;
+extern void WidowExplode ( edict_t * self ) ;
+extern void ThrowMoreStuff ( edict_t * self , vec3_t point ) ;
+extern void ThrowSmallStuff ( edict_t * self , vec3_t point ) ;
+extern void ThrowWidowGibReal ( edict_t * self , char * gibname , int damage , int type , vec3_t startpos , qboolean sized , int hitsound , qboolean fade ) ;
+extern void ThrowWidowGibSized ( edict_t * self , char * gibname , int damage , int type , vec3_t startpos , int hitsound , qboolean fade ) ;
+extern void ThrowWidowGibLoc ( edict_t * self , char * gibname , int damage , int type , vec3_t startpos , qboolean fade ) ;
+extern void ThrowWidowGib ( edict_t * self , char * gibname , int damage , int type ) ;
+extern void widow_gib_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void WidowVelocityForDamage ( int damage , vec3_t v ) ;
+extern void SP_monster_widow2 ( edict_t * self ) ;
+extern void Widow2Precache ( ) ;
+extern qboolean Widow2_CheckAttack ( edict_t * self ) ;
+extern void widow2_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void KillChildren ( edict_t * self ) ;
+extern void widow2_dead ( edict_t * self ) ;
+extern void widow2_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void widow2_reattack_beam ( edict_t * self ) ;
+extern void widow2_attack_beam ( edict_t * self ) ;
+extern void widow2_attack ( edict_t * self ) ;
+extern void widow2_melee ( edict_t * self ) ;
+extern void widow2_walk ( edict_t * self ) ;
+extern void widow2_run ( edict_t * self ) ;
+extern void widow2_stand ( edict_t * self ) ;
+extern void widow2_finaldeath ( edict_t * self ) ;
+extern void widow2_keep_searching ( edict_t * self ) ;
+extern void widow2_start_searching ( edict_t * self ) ;
+extern void Widow2Toss ( edict_t * self ) ;
+extern void Widow2Crunch ( edict_t * self ) ;
+extern void Widow2TonguePull ( edict_t * self ) ;
+extern void Widow2Tongue ( edict_t * self ) ;
+extern qboolean widow2_tongue_attack_ok ( vec3_t start , vec3_t end , float range ) ;
+extern void Widow2StartSweep ( edict_t * self ) ;
+extern void Widow2BeamTargetRemove ( edict_t * self ) ;
+extern void Widow2SaveBeamTarget ( edict_t * self ) ;
+extern void widow2_disrupt_reattack ( edict_t * self ) ;
+extern void Widow2SaveDisruptLoc ( edict_t * self ) ;
+extern void WidowDisrupt ( edict_t * self ) ;
+extern void widow2_ready_spawn ( edict_t * self ) ;
+extern void widow2_spawn_check ( edict_t * self ) ;
+extern void Widow2Spawn ( edict_t * self ) ;
+extern void Widow2Beam ( edict_t * self ) ;
+extern void widow2_search ( edict_t * self ) ;
+extern void pauseme ( edict_t * self ) ;
+extern void SP_monster_widow ( edict_t * self ) ;
+extern void WidowPrecache ( ) ;
+extern void WidowCalcSlots ( edict_t * self ) ;
+extern qboolean widow_blocked ( edict_t * self , float dist ) ;
+extern qboolean Widow_CheckAttack ( edict_t * self ) ;
+extern void WidowPowerups ( edict_t * self ) ;
+extern void WidowRespondPowerup ( edict_t * self , edict_t * other ) ;
+extern void WidowPowerArmor ( edict_t * self ) ;
+extern void WidowPent ( edict_t * self , float framenum ) ;
+extern void WidowDouble ( edict_t * self , float framenum ) ;
+extern void WidowGoinQuad ( edict_t * self , float framenum ) ;
+extern void widow_melee ( edict_t * self ) ;
+extern void widow_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void widow_dead ( edict_t * self ) ;
+extern void widow_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void widow_reattack_blaster ( edict_t * self ) ;
+extern void widow_attack_blaster ( edict_t * self ) ;
+extern void widow_attack ( edict_t * self ) ;
+extern void widow_walk ( edict_t * self ) ;
+extern void widow_run ( edict_t * self ) ;
+extern void widow_stand ( edict_t * self ) ;
+extern void widow_attack_kick ( edict_t * self ) ;
+extern void spawn_out_do ( edict_t * self ) ;
+extern void spawn_out_start ( edict_t * self ) ;
+extern void widow_done_spawn ( edict_t * self ) ;
+extern void widow_start_spawn ( edict_t * self ) ;
+extern void widow_attack_rail ( edict_t * self ) ;
+extern void widow_rail_done ( edict_t * self ) ;
+extern void widow_start_rail ( edict_t * self ) ;
+extern void WidowSaveLoc ( edict_t * self ) ;
+extern void WidowRail ( edict_t * self ) ;
+extern void widow_start_run_12 ( edict_t * self ) ;
+extern void widow_start_run_10 ( edict_t * self ) ;
+extern void widow_start_run_5 ( edict_t * self ) ;
+extern void widow_stepshoot ( edict_t * self ) ;
+extern void widow_step ( edict_t * self ) ;
+extern void widow_ready_spawn ( edict_t * self ) ;
+extern void widow_spawn_check ( edict_t * self ) ;
+extern void WidowSpawn ( edict_t * self ) ;
+extern void WidowBlaster ( edict_t * self ) ;
+extern int WidowTorso ( edict_t * self ) ;
+extern float target_angle ( edict_t * self ) ;
+extern void widow_sight ( edict_t * self , edict_t * other ) ;
+extern void widow_search ( edict_t * self ) ;
+extern void showme ( edict_t * self ) ;
+extern void SP_monster_turret ( edict_t * self ) ;
+extern qboolean turret_checkattack ( edict_t * self ) ;
+extern void turret_activate ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void turret_wake ( edict_t * self ) ;
+extern void turret_wall_spawn ( edict_t * turret ) ;
+extern void turret_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void turret_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void turret_attack ( edict_t * self ) ;
+extern void TurretFireBlind ( edict_t * self ) ;
+extern void TurretFire ( edict_t * self ) ;
+extern void turret_run ( edict_t * self ) ;
+extern void turret_walk ( edict_t * self ) ;
+extern void turret_ready_gun ( edict_t * self ) ;
+extern void turret_stand ( edict_t * self ) ;
+extern void turret_search ( edict_t * self ) ;
+extern void turret_sight ( edict_t * self , edict_t * other ) ;
+extern void TurretAim ( edict_t * self ) ;
+extern void SP_monster_tank ( edict_t * self ) ;
+extern void tank_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void tank_dead ( edict_t * self ) ;
+extern void tank_attack ( edict_t * self ) ;
+extern void tank_doattack_rocket ( edict_t * self ) ;
+extern void tank_refire_rocket ( edict_t * self ) ;
+extern void tank_poststrike ( edict_t * self ) ;
+extern void tank_reattack_blaster ( edict_t * self ) ;
+extern void TankMachineGun ( edict_t * self ) ;
+extern void TankRocket ( edict_t * self ) ;
+extern void TankStrike ( edict_t * self ) ;
+extern void TankBlaster ( edict_t * self ) ;
+extern void tank_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void tank_run ( edict_t * self ) ;
+extern void tank_walk ( edict_t * self ) ;
+extern void tank_stand ( edict_t * self ) ;
+extern void tank_idle ( edict_t * self ) ;
+extern void tank_windup ( edict_t * self ) ;
+extern void tank_thud ( edict_t * self ) ;
+extern void tank_footstep ( edict_t * self ) ;
+extern void tank_sight ( edict_t * self , edict_t * other ) ;
+extern qboolean tank_blocked( edict_t *self, float dist );
+extern void SP_monster_supertank ( edict_t * self ) ;
+extern qboolean supertank_blocked ( edict_t * self , float dist ) ;
+extern void supertank_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void BossExplode ( edict_t * self ) ;
+extern void supertank_dead ( edict_t * self ) ;
+extern void supertank_attack ( edict_t * self ) ;
+extern void supertankMachineGun ( edict_t * self ) ;
+extern void supertankRocket ( edict_t * self ) ;
+extern void supertank_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void supertank_reattack1 ( edict_t * self ) ;
+extern void supertank_run ( edict_t * self ) ;
+extern void supertank_walk ( edict_t * self ) ;
+extern void supertank_forward ( edict_t * self ) ;
+extern void supertank_stand ( edict_t * self ) ;
+extern void supertank_search ( edict_t * self ) ;
+extern void TreadSound ( edict_t * self ) ;
+extern void SP_monster_stalker ( edict_t * self ) ;
+extern void stalker_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void stalker_dead ( edict_t * self ) ;
+extern qboolean stalker_blocked ( edict_t * self , float dist ) ;
+extern void stalker_jump ( edict_t * self ) ;
+extern void stalker_jump_wait_land ( edict_t * self ) ;
+extern void stalker_jump_up ( edict_t * self ) ;
+extern void stalker_jump_down ( edict_t * self ) ;
+extern void stalker_dodge ( edict_t * self , edict_t * attacker , float eta , trace_t * tr ) ;
+extern void stalker_dodge_jump ( edict_t * self ) ;
+extern void stalker_jump_straightup ( edict_t * self ) ;
+extern int stalker_do_pounce ( edict_t * self , vec3_t dest ) ;
+extern int stalker_check_lz ( edict_t * self , edict_t * target , vec3_t dest ) ;
+extern void calcJumpAngle ( vec3_t start , vec3_t end , float velocity , vec3_t angles ) ;
+extern void stalker_attack_melee ( edict_t * self ) ;
+extern void stalker_swing_attack ( edict_t * self ) ;
+extern void stalker_attack_ranged ( edict_t * self ) ;
+extern void stalker_shoot_attack2 ( edict_t * self ) ;
+extern void stalker_shoot_attack ( edict_t * self ) ;
+extern void stalker_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void stalker_false_death_start ( edict_t * self ) ;
+extern void stalker_false_death ( edict_t * self ) ;
+extern void stalker_heal ( edict_t * self ) ;
+extern void stalker_reactivate ( edict_t * self ) ;
+extern void stalker_walk ( edict_t * self ) ;
+extern void stalker_run ( edict_t * self ) ;
+extern void stalker_stand ( edict_t * self ) ;
+extern void stalker_idle ( edict_t * self ) ;
+extern void stalker_idle_noise ( edict_t * self ) ;
+extern void stalker_sight ( edict_t * self , edict_t * other ) ;
+extern qboolean stalker_ok_to_transition ( edict_t * self ) ;
+extern void SP_monster_soldier_ss ( edict_t * self ) ;
+extern void SP_monster_soldier ( edict_t * self ) ;
+extern void SP_monster_soldier_light ( edict_t * self ) ;
+extern void SP_monster_soldier_x ( edict_t * self ) ;
+extern void soldier_blind ( edict_t * self ) ;
+extern void soldier_duck ( edict_t * self , float eta ) ;
+extern void soldier_sidestep ( edict_t * self ) ;
+extern void soldier_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void soldier_dead2 ( edict_t * self ) ;
+extern void soldier_dead ( edict_t * self ) ;
+extern void soldier_fire7 ( edict_t * self ) ;
+extern void soldier_fire6 ( edict_t * self ) ;
+extern qboolean soldier_blocked ( edict_t * self , float dist ) ;
+extern void soldier_sight ( edict_t * self , edict_t * other ) ;
+extern void soldier_attack ( edict_t * self ) ;
+extern void soldier_attack6_refire ( edict_t * self ) ;
+extern void soldier_fire8 ( edict_t * self ) ;
+extern void soldier_fire4 ( edict_t * self ) ;
+extern void soldier_attack3_refire ( edict_t * self ) ;
+extern void soldier_fire3 ( edict_t * self ) ;
+extern void soldier_attack2_refire2 ( edict_t * self ) ;
+extern void soldier_attack2_refire1 ( edict_t * self ) ;
+extern void soldier_fire2 ( edict_t * self ) ;
+extern void soldier_attack1_refire2 ( edict_t * self ) ;
+extern void soldier_attack1_refire1 ( edict_t * self ) ;
+extern void soldier_fire1 ( edict_t * self ) ;
+extern void soldier_fire ( edict_t * self , int in_flash_number ) ;
+extern void soldier_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void soldier_run ( edict_t * self ) ;
+extern void soldier_fire_run ( edict_t * self ) ;
+extern void soldier_walk ( edict_t * self ) ;
+extern void soldier_walk1_random ( edict_t * self ) ;
+extern void soldier_stand ( edict_t * self ) ;
+extern void soldier_cock ( edict_t * self ) ;
+extern void soldier_idle ( edict_t * self ) ;
+extern void soldier_stop_charge ( edict_t * self ) ;
+extern void soldier_start_charge ( edict_t * self ) ;
+extern void SP_monster_parasite ( edict_t * self ) ;
+extern void parasite_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void parasite_dead ( edict_t * self ) ;
+extern qboolean parasite_checkattack ( edict_t * self ) ;
+extern qboolean parasite_blocked ( edict_t * self , float dist ) ;
+extern void parasite_jump ( edict_t * self ) ;
+extern void parasite_jump_wait_land ( edict_t * self ) ;
+extern void parasite_jump_up ( edict_t * self ) ;
+extern void parasite_jump_down ( edict_t * self ) ;
+extern void parasite_attack ( edict_t * self ) ;
+extern void parasite_drain_attack ( edict_t * self ) ;
+extern qboolean parasite_drain_attack_ok ( vec3_t start , vec3_t end ) ;
+extern void parasite_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void parasite_walk ( edict_t * self ) ;
+extern void parasite_start_walk ( edict_t * self ) ;
+extern void parasite_run ( edict_t * self ) ;
+extern void parasite_start_run ( edict_t * self ) ;
+extern void parasite_stand ( edict_t * self ) ;
+extern void parasite_idle ( edict_t * self ) ;
+extern void parasite_refidget ( edict_t * self ) ;
+extern void parasite_do_fidget ( edict_t * self ) ;
+extern void parasite_end_fidget ( edict_t * self ) ;
+extern void parasite_search ( edict_t * self ) ;
+extern void parasite_scratch ( edict_t * self ) ;
+extern void parasite_tap ( edict_t * self ) ;
+extern void parasite_sight ( edict_t * self , edict_t * other ) ;
+extern void parasite_reel_in ( edict_t * self ) ;
+extern void parasite_launch ( edict_t * self ) ;
+extern void SP_monster_mutant ( edict_t * self ) ;
+extern qboolean mutant_blocked ( edict_t * self , float dist ) ;
+extern void mutant_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void mutant_dead ( edict_t * self ) ;
+extern void mutant_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern qboolean mutant_checkattack ( edict_t * self ) ;
+extern qboolean mutant_check_jump ( edict_t * self ) ;
+extern qboolean mutant_check_melee ( edict_t * self ) ;
+extern void mutant_jump ( edict_t * self ) ;
+extern void mutant_check_landing ( edict_t * self ) ;
+extern void mutant_jump_takeoff ( edict_t * self ) ;
+extern void mutant_jump_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void mutant_melee ( edict_t * self ) ;
+extern void mutant_check_refire ( edict_t * self ) ;
+extern void mutant_hit_right ( edict_t * self ) ;
+extern void mutant_hit_left ( edict_t * self ) ;
+extern void mutant_run ( edict_t * self ) ;
+extern void mutant_walk ( edict_t * self ) ;
+extern void mutant_walk_loop ( edict_t * self ) ;
+extern void mutant_idle ( edict_t * self ) ;
+extern void mutant_idle_loop ( edict_t * self ) ;
+extern void mutant_stand ( edict_t * self ) ;
+extern void mutant_swing ( edict_t * self ) ;
+extern void mutant_search ( edict_t * self ) ;
+extern void mutant_sight ( edict_t * self , edict_t * other ) ;
+extern void mutant_step ( edict_t * self ) ;
+extern qboolean M_walkmove ( edict_t * ent , float yaw , float dist ) ;
+extern void M_MoveToGoal ( edict_t * ent , float dist ) ;
+extern qboolean SV_CloseEnough ( edict_t * ent , edict_t * goal , float dist ) ;
+extern void SV_NewChaseDir ( edict_t * actor , edict_t * enemy , float dist ) ;
+extern void SV_FixCheckBottom ( edict_t * ent ) ;
+extern qboolean SV_StepDirection ( edict_t * ent , float yaw , float dist ) ;
+extern void M_ChangeYaw ( edict_t * ent ) ;
+extern qboolean SV_movestep ( edict_t * ent , vec3_t move , qboolean relink ) ;
+extern qboolean IsBadAhead ( edict_t * self , edict_t * bad , vec3_t move ) ;
+extern qboolean M_CheckBottom ( edict_t * ent ) ;
+extern void SP_monster_medic ( edict_t * self ) ;
+extern qboolean medic_blocked ( edict_t *self, float dist ) ;
+extern void medic_sidestep( edict_t *self ) ;
+extern void medic_duck( edict_t *self, float eta ) ;
+extern qboolean medic_checkattack ( edict_t * self ) ;
+extern void medic_finish_spawn ( edict_t *self );
+extern void medic_spawngrows ( edict_t *self );
+extern void medic_determine_spawn ( edict_t *self ) ;
+extern void medic_start_spawn ( edict_t *self ) ;
+extern void medic_attack ( edict_t * self ) ;
+extern void medic_hook_retract ( edict_t * self ) ;
+extern void medic_cable_attack ( edict_t * self ) ;
+extern void medic_hook_launch ( edict_t * self ) ;
+extern void medic_continue ( edict_t * self ) ;
+extern void medic_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void medic_dead ( edict_t * self ) ;
+extern void medic_fire_blaster ( edict_t * self ) ;
+extern void medic_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void medic_run ( edict_t * self ) ;
+extern void medic_walk ( edict_t * self ) ;
+extern void medic_stand ( edict_t * self ) ;
+extern void medic_sight ( edict_t * self , edict_t * other ) ;
+extern void medic_search ( edict_t * self ) ;
+extern void medic_idle ( edict_t * self ) ;
+extern void cleanupHeal( edict_t *self, qboolean change_frame ) ;
+extern void abortHeal( edict_t *self, qboolean change_frame, qboolean gib, qboolean mark ) ;
+extern qboolean canReach ( edict_t *self, edict_t *other ) ;
+extern edict_t * medic_FindDeadMonster ( edict_t * self ) ;
+extern void SP_misc_insane ( edict_t * self ) ;
+extern void insane_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void insane_dead ( edict_t * self ) ;
+extern void insane_stand ( edict_t * self ) ;
+extern void insane_checkup ( edict_t * self ) ;
+extern void insane_checkdown ( edict_t * self ) ;
+extern void insane_onground ( edict_t * self ) ;
+extern void insane_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void insane_run ( edict_t * self ) ;
+extern void insane_walk ( edict_t * self ) ;
+extern void insane_cross ( edict_t * self ) ;
+extern void insane_scream ( edict_t * self ) ;
+extern void insane_moan ( edict_t * self ) ;
+extern void insane_shake ( edict_t * self ) ;
+extern void insane_fist ( edict_t * self ) ;
+extern void SP_monster_infantry ( edict_t * self ) ;
+extern void infantry_sidestep ( edict_t * self ) ;
+extern void infantry_duck ( edict_t * self , float eta ) ;
+extern qboolean infantry_blocked ( edict_t * self , float dist ) ;
+extern void infantry_jump ( edict_t * self ) ;
+extern void infantry_jump_wait_land ( edict_t * self ) ;
+extern void infantry_jump2_now ( edict_t * self ) ;
+extern void infantry_jump_now ( edict_t * self ) ;
+extern void infantry_attack ( edict_t * self ) ;
+extern void infantry_smack ( edict_t * self ) ;
+extern void infantry_swing ( edict_t * self ) ;
+extern void infantry_fire_prep ( edict_t * self ) ;
+extern void infantry_fire ( edict_t * self ) ;
+extern void infantry_cock_gun ( edict_t * self ) ;
+extern void infantry_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void infantry_dead ( edict_t * self ) ;
+extern void infantry_sight ( edict_t * self , edict_t * other ) ;
+extern void InfantryMachineGun ( edict_t * self ) ;
+extern void infantry_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void infantry_run ( edict_t * self ) ;
+extern void infantry_walk ( edict_t * self ) ;
+extern void infantry_fidget ( edict_t * self ) ;
+extern void infantry_stand ( edict_t * self ) ;
+extern void SP_monster_hover ( edict_t * self ) ;
+extern qboolean hover_blocked ( edict_t *self, float dist ) ;
+extern void hover_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void hover_dead ( edict_t * self ) ;
+extern void hover_deadthink ( edict_t * self ) ;
+extern void hover_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void hover_attack ( edict_t * self ) ;
+extern void hover_start_attack ( edict_t * self ) ;
+extern void hover_walk ( edict_t * self ) ;
+extern void hover_run ( edict_t * self ) ;
+extern void hover_stand ( edict_t * self ) ;
+extern void hover_fire_blaster ( edict_t * self ) ;
+extern void hover_reattack ( edict_t * self ) ;
+extern void hover_search ( edict_t * self ) ;
+extern void hover_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_gunner ( edict_t * self ) ;
+extern void gunner_sidestep ( edict_t * self ) ;
+extern void gunner_duck ( edict_t * self , float eta ) ;
+extern qboolean gunner_blocked ( edict_t * self , float dist ) ;
+extern void gunner_jump ( edict_t * self ) ;
+extern void gunner_jump_wait_land ( edict_t * self ) ;
+extern void gunner_jump2_now ( edict_t * self ) ;
+extern void gunner_jump_now ( edict_t * self ) ;
+extern void gunner_refire_chain ( edict_t * self ) ;
+extern void gunner_fire_chain ( edict_t * self ) ;
+extern void gunner_attack ( edict_t * self ) ;
+extern void gunner_blind_check ( edict_t * self ) ;
+extern void GunnerGrenade ( edict_t * self ) ;
+extern qboolean gunner_grenade_check ( edict_t * self ) ;
+extern void GunnerFire ( edict_t * self ) ;
+extern void gunner_opengun ( edict_t * self ) ;
+extern void gunner_duck_down ( edict_t * self ) ;
+extern void gunner_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void gunner_dead ( edict_t * self ) ;
+extern void gunner_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void gunner_runandshoot ( edict_t * self ) ;
+extern void gunner_run ( edict_t * self ) ;
+extern void gunner_walk ( edict_t * self ) ;
+extern void gunner_stand ( edict_t * self ) ;
+extern void gunner_fidget ( edict_t * self ) ;
+extern void gunner_search ( edict_t * self ) ;
+extern void gunner_sight ( edict_t * self , edict_t * other ) ;
+extern void gunner_idlesound ( edict_t * self ) ;
+extern void SP_monster_gladiator ( edict_t * self ) ;
+qboolean gladiator_blocked ( edict_t *self , float dist ) ;
+extern void gladiator_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void gladiator_dead ( edict_t * self ) ;
+extern void gladiator_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void gladiator_attack ( edict_t * self ) ;
+extern void GladiatorGun ( edict_t * self ) ;
+extern void gladiator_melee ( edict_t * self ) ;
+extern void GaldiatorMelee ( edict_t * self ) ;
+extern void gladiator_run ( edict_t * self ) ;
+extern void gladiator_walk ( edict_t * self ) ;
+extern void gladiator_stand ( edict_t * self ) ;
+extern void gladiator_cleaver_swing ( edict_t * self ) ;
+extern void gladiator_search ( edict_t * self ) ;
+extern void gladiator_sight ( edict_t * self , edict_t * other ) ;
+extern void gladiator_idle ( edict_t * self ) ;
+extern void SP_monster_kamikaze ( edict_t * self ) ;
+extern void SP_monster_flyer ( edict_t * self ) ;
+extern int flyer_blocked ( edict_t * self , float dist ) ;
+extern void flyer_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void flyer_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void flyer_check_melee ( edict_t * self ) ;
+extern void flyer_melee ( edict_t * self ) ;
+extern void flyer_nextmove ( edict_t * self ) ;
+extern void flyer_setstart ( edict_t * self ) ;
+extern void flyer_attack ( edict_t * self ) ;
+extern void flyer_loop_melee ( edict_t * self ) ;
+extern void flyer_slash_right ( edict_t * self ) ;
+extern void flyer_slash_left ( edict_t * self ) ;
+extern void flyer_fireright ( edict_t * self ) ;
+extern void flyer_fireleft ( edict_t * self ) ;
+extern void flyer_fire ( edict_t * self , int flash_number ) ;
+extern void flyer_start ( edict_t * self ) ;
+extern void flyer_stop ( edict_t * self ) ;
+extern void flyer_kamikaze_check ( edict_t * self ) ;
+extern void flyer_kamikaze ( edict_t * self ) ;
+extern void flyer_kamikaze_explode ( edict_t * self ) ;
+extern void flyer_stand ( edict_t * self ) ;
+extern void flyer_walk ( edict_t * self ) ;
+extern void flyer_run ( edict_t * self ) ;
+extern void flyer_pop_blades ( edict_t * self ) ;
+extern void flyer_idle ( edict_t * self ) ;
+extern void flyer_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_floater ( edict_t * self ) ;
+extern qboolean floater_blocked ( edict_t * self , float dist ) ;
+extern void floater_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void floater_dead ( edict_t * self ) ;
+extern void floater_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void floater_melee ( edict_t * self ) ;
+extern void floater_attack ( edict_t * self ) ;
+extern void floater_zap ( edict_t * self ) ;
+extern void floater_wham ( edict_t * self ) ;
+extern void floater_walk ( edict_t * self ) ;
+extern void floater_run ( edict_t * self ) ;
+extern void floater_stand ( edict_t * self ) ;
+extern void floater_fire_blaster ( edict_t * self ) ;
+extern void floater_idle ( edict_t * self ) ;
+extern void floater_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_flipper ( edict_t * self ) ;
+extern void flipper_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void flipper_sight ( edict_t * self , edict_t * other ) ;
+extern void flipper_dead ( edict_t * self ) ;
+extern void flipper_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void flipper_melee ( edict_t * self ) ;
+extern void flipper_preattack ( edict_t * self ) ;
+extern void flipper_bite ( edict_t * self ) ;
+extern void flipper_start_run ( edict_t * self ) ;
+extern void flipper_walk ( edict_t * self ) ;
+extern void flipper_run ( edict_t * self ) ;
+extern void flipper_run_loop ( edict_t * self ) ;
+extern void flipper_stand ( edict_t * self ) ;
+extern void SP_monster_chick ( edict_t * self ) ;
+extern void chick_sidestep ( edict_t * self ) ;
+extern void chick_duck ( edict_t * self , float eta ) ;
+extern qboolean chick_blocked ( edict_t * self , float dist ) ;
+extern void chick_sight ( edict_t * self , edict_t * other ) ;
+extern void chick_attack ( edict_t * self ) ;
+extern void chick_melee ( edict_t * self ) ;
+extern void chick_slash ( edict_t * self ) ;
+extern void chick_reslash ( edict_t * self ) ;
+extern void chick_attack1 ( edict_t * self ) ;
+extern void chick_rerocket ( edict_t * self ) ;
+extern void ChickReload ( edict_t * self ) ;
+extern void Chick_PreAttack1 ( edict_t * self ) ;
+extern void ChickRocket ( edict_t * self ) ;
+extern void ChickSlash ( edict_t * self ) ;
+extern void chick_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void chick_dead ( edict_t * self ) ;
+extern void chick_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void chick_run ( edict_t * self ) ;
+extern void chick_walk ( edict_t * self ) ;
+extern void chick_stand ( edict_t * self ) ;
+extern void chick_fidget ( edict_t * self ) ;
+extern void ChickMoan ( edict_t * self ) ;
+extern void SP_monster_carrier ( edict_t * self ) ;
+extern void CarrierPrecache ( ) ;
+extern qboolean Carrier_CheckAttack ( edict_t * self ) ;
+extern void carrier_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void carrier_dead ( edict_t * self ) ;
+extern void carrier_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void carrier_reattack_gren ( edict_t * self ) ;
+extern void carrier_attack_gren ( edict_t * self ) ;
+extern void carrier_reattack_mg ( edict_t * self ) ;
+extern void carrier_attack_mg ( edict_t * self ) ;
+extern void carrier_attack ( edict_t * self ) ;
+extern void CarrierMachineGunHold ( edict_t * self ) ;
+extern void carrier_walk ( edict_t * self ) ;
+extern void carrier_run ( edict_t * self ) ;
+extern void carrier_stand ( edict_t * self ) ;
+extern void CarrierSaveLoc ( edict_t * self ) ;
+extern void CarrierRail ( edict_t * self ) ;
+extern void carrier_start_spawn ( edict_t * self ) ;
+extern void carrier_ready_spawn ( edict_t * self ) ;
+extern void carrier_spawn_check ( edict_t * self ) ;
+extern void carrier_prep_spawn ( edict_t * self ) ;
+extern void CarrierSpawn ( edict_t * self ) ;
+extern void CarrierMachineGun ( edict_t * self ) ;
+extern void carrier_firebullet_left ( edict_t * self ) ;
+extern void carrier_firebullet_right ( edict_t * self ) ;
+extern void CarrierRocket ( edict_t * self ) ;
+extern void CarrierPredictiveRocket ( edict_t * self ) ;
+extern void CarrierGrenade ( edict_t * self ) ;
+extern void CarrierCoopCheck ( edict_t * self ) ;
+extern void carrier_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_brain ( edict_t * self ) ;
+extern void brain_duck ( edict_t * self , float eta ) ;
+extern void brain_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void brain_dead ( edict_t * self ) ;
+extern void brain_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void brain_run ( edict_t * self ) ;
+extern void brain_melee ( edict_t * self ) ;
+extern void brain_chest_closed ( edict_t * self ) ;
+extern void brain_tentacle_attack ( edict_t * self ) ;
+extern void brain_chest_open ( edict_t * self ) ;
+extern void brain_hit_left ( edict_t * self ) ;
+extern void brain_swing_left ( edict_t * self ) ;
+extern void brain_hit_right ( edict_t * self ) ;
+extern void brain_swing_right ( edict_t * self ) ;
+extern void brain_walk ( edict_t * self ) ;
+extern void brain_idle ( edict_t * self ) ;
+extern void brain_stand ( edict_t * self ) ;
+extern void brain_search ( edict_t * self ) ;
+extern void brain_sight ( edict_t * self , edict_t * other ) ;
+extern void MakronToss ( edict_t * self ) ;
+extern void MakronSpawn ( edict_t * self ) ;
+extern void SP_monster_makron ( edict_t * self ) ;
+extern void MakronPrecache ( void ) ;
+extern qboolean Makron_CheckAttack ( edict_t * self ) ;
+extern void makron_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void makron_torso_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void makron_dead ( edict_t * self ) ;
+extern void makron_torso ( edict_t * ent ) ;
+extern void makron_torso_think ( edict_t * self ) ;
+extern void makron_attack ( edict_t * self ) ;
+extern void makron_sight ( edict_t * self , edict_t * other ) ;
+extern void makron_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void MakronHyperblaster ( edict_t * self ) ;
+extern void MakronRailgun ( edict_t * self ) ;
+extern void MakronSaveloc ( edict_t * self ) ;
+extern void makronBFG ( edict_t * self ) ;
+extern void makron_run ( edict_t * self ) ;
+extern void makron_walk ( edict_t * self ) ;
+extern void makron_prerailgun ( edict_t * self ) ;
+extern void makron_brainsplorch ( edict_t * self ) ;
+extern void makron_step_right ( edict_t * self ) ;
+extern void makron_step_left ( edict_t * self ) ;
+extern void makron_popup ( edict_t * self ) ;
+extern void makron_hit ( edict_t * self ) ;
+extern void makron_stand ( edict_t * self ) ;
+extern void makron_taunt ( edict_t * self ) ;
+extern void SP_monster_jorg ( edict_t * self ) ;
+extern qboolean Jorg_CheckAttack ( edict_t * self ) ;
+extern void jorg_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void jorg_dead ( edict_t * self ) ;
+extern void jorg_attack ( edict_t * self ) ;
+extern void jorg_firebullet ( edict_t * self ) ;
+extern void jorg_firebullet_left ( edict_t * self ) ;
+extern void jorg_firebullet_right ( edict_t * self ) ;
+extern void jorgBFG ( edict_t * self ) ;
+extern void jorg_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void jorg_attack1 ( edict_t * self ) ;
+extern void jorg_reattack1 ( edict_t * self ) ;
+extern void jorg_run ( edict_t * self ) ;
+extern void jorg_walk ( edict_t * self ) ;
+extern void jorg_stand ( edict_t * self ) ;
+extern void jorg_step_right ( edict_t * self ) ;
+extern void jorg_step_left ( edict_t * self ) ;
+extern void jorg_death_hit ( edict_t * self ) ;
+extern void jorg_idle ( edict_t * self ) ;
+extern void jorg_search ( edict_t * self ) ;
+extern void SP_monster_boss3_stand ( edict_t * self ) ;
+extern void Think_Boss3Stand ( edict_t * ent ) ;
+extern void Use_Boss3 ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_monster_boss2 ( edict_t * self ) ;
+extern qboolean Boss2_CheckAttack ( edict_t * self ) ;
+extern void boss2_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void boss2_dead ( edict_t * self ) ;
+extern void boss2_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void boss2_reattack_mg ( edict_t * self ) ;
+extern void boss2_attack_mg ( edict_t * self ) ;
+extern void boss2_attack ( edict_t * self ) ;
+extern void boss2_walk ( edict_t * self ) ;
+extern void boss2_run ( edict_t * self ) ;
+extern void boss2_stand ( edict_t * self ) ;
+extern void Boss2MachineGun ( edict_t * self ) ;
+extern void boss2_firebullet_left ( edict_t * self ) ;
+extern void boss2_firebullet_right ( edict_t * self ) ;
+extern void Boss2Rocket ( edict_t * self ) ;
+extern void boss2_search ( edict_t * self ) ;
+extern void SP_monster_berserk ( edict_t * self ) ;
+extern void berserk_sidestep ( edict_t * self ) ;
+extern qboolean berserk_blocked ( edict_t * self , float dist ) ;
+extern void berserk_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void berserk_dead ( edict_t * self ) ;
+extern void berserk_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void berserk_melee ( edict_t * self ) ;
+extern void berserk_strike ( edict_t * self ) ;
+extern void berserk_attack_club ( edict_t * self ) ;
+extern void berserk_swing ( edict_t * self ) ;
+extern void berserk_attack_spike ( edict_t * self ) ;
+extern void berserk_run ( edict_t * self ) ;
+extern void berserk_walk ( edict_t * self ) ;
+extern void berserk_fidget ( edict_t * self ) ;
+extern void berserk_stand ( edict_t * self ) ;
+extern void berserk_search ( edict_t * self ) ;
+extern void berserk_sight ( edict_t * self , edict_t * other ) ;
+extern void fire_bfg ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , float damage_radius ) ;
+extern void bfg_think ( edict_t * self ) ;
+extern void bfg_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void bfg_explode ( edict_t * self ) ;
+extern void fire_rail ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick ) ;
+extern void fire_rocket ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , float damage_radius , int radius_damage ) ;
+extern void rocket_touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void fire_grenade2 ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , float timer , float damage_radius , qboolean held ) ;
+extern void fire_grenade ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , float timer , float damage_radius ) ;
+extern void Grenade_Touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Grenade_Explode ( edict_t * ent ) ;
+extern void fire_blaster ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int effect , qboolean hyper ) ;
+extern void blaster_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void fire_shotgun ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int hspread , int vspread , int count , int mod ) ;
+extern void fire_bullet ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int hspread , int vspread , int mod ) ;
+extern void fire_lead ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int te_impact , int hspread , int vspread , int mod ) ;
+extern qboolean fire_hit ( edict_t * self , vec3_t aim , int damage , int kick ) ;
+extern void check_dodge ( edict_t * self , vec3_t start , vec3_t dir , int speed ) ;
+extern qboolean KillBox ( edict_t * ent ) ;
+extern void G_TouchSolids ( edict_t * ent ) ;
+extern void G_TouchTriggers ( edict_t * ent ) ;
+extern void G_FreeEdict ( edict_t * ed ) ;
+extern edict_t * G_Spawn ( void ) ;
+extern void G_InitEdict ( edict_t * e ) ;
+extern char * G_CopyString ( char * in ) ;
+extern void vectoangles2 ( vec3_t value1 , vec3_t angles ) ;
+extern void vectoangles ( vec3_t value1 , vec3_t angles ) ;
+extern float vectoyaw2 ( vec3_t vec ) ;
+extern float vectoyaw ( vec3_t vec ) ;
+extern void G_SetMovedir ( vec3_t angles , vec3_t movedir ) ;
+extern char * vtos ( vec3_t v ) ;
+extern float * tv ( float x , float y , float z ) ;
+extern void G_UseTargets ( edict_t * ent , edict_t * activator ) ;
+extern void Think_Delay ( edict_t * ent ) ;
+extern edict_t * G_PickTarget ( char * targetname ) ;
+extern edict_t * findradius2 ( edict_t * from , vec3_t org , float rad ) ;
+extern edict_t * findradius ( edict_t * from , vec3_t org , float rad ) ;
+extern edict_t * G_Find ( edict_t * from , int fieldofs , char * match ) ;
+extern void G_ProjectSource2 ( vec3_t point , vec3_t distance , vec3_t forward , vec3_t right , vec3_t up , vec3_t result ) ;
+extern void G_ProjectSource ( vec3_t point , vec3_t distance , vec3_t forward , vec3_t right , vec3_t result ) ;
+extern void SP_turret_invisible_brain ( edict_t * self ) ;
+extern void turret_brain_activate ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void turret_brain_deactivate ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void turret_brain_link ( edict_t * self ) ;
+extern void turret_brain_think ( edict_t * self ) ;
+extern void SP_turret_driver ( edict_t * self ) ;
+extern void turret_driver_link ( edict_t * self ) ;
+extern void turret_driver_think ( edict_t * self ) ;
+extern void turret_driver_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void SP_turret_base ( edict_t * self ) ;
+extern void SP_turret_breach ( edict_t * self ) ;
+extern void turret_breach_finish_init ( edict_t * self ) ;
+extern void turret_breach_think ( edict_t * self ) ;
+extern void turret_breach_fire ( edict_t * self ) ;
+extern void turret_blocked ( edict_t * self , edict_t * other ) ;
+extern float SnapToEights ( float x ) ;
+extern void AnglesNormalize ( vec3_t vec ) ;
+extern void SP_trigger_monsterjump ( edict_t * self ) ;
+extern void trigger_monsterjump_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_trigger_gravity ( edict_t * self ) ;
+extern void trigger_gravity_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void trigger_gravity_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_hurt ( edict_t * self ) ;
+extern void hurt_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void hurt_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_push ( edict_t * self ) ;
+extern void trigger_push_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void trigger_push_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_trigger_always ( edict_t * ent ) ;
+extern void SP_trigger_counter ( edict_t * self ) ;
+extern void trigger_counter_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_key ( edict_t * self ) ;
+extern void trigger_key_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_relay ( edict_t * self ) ;
+extern void trigger_relay_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_once ( edict_t * ent ) ;
+extern void SP_trigger_multiple ( edict_t * ent ) ;
+extern void trigger_enable ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void Touch_Multi ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Use_Multi ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void multi_trigger ( edict_t * ent ) ;
+extern void multi_wait ( edict_t * ent ) ;
+extern void InitTrigger ( edict_t * self ) ;
+extern void SP_target_earthquake ( edict_t * self ) ;
+extern void target_earthquake_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_earthquake_think ( edict_t * self ) ;
+extern void SP_target_lightramp ( edict_t * self ) ;
+extern void target_lightramp_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_lightramp_think ( edict_t * self ) ;
+extern void SP_target_laser ( edict_t * self ) ;
+extern void target_laser_start ( edict_t * self ) ;
+extern void target_laser_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_laser_off ( edict_t * self ) ;
+extern void target_laser_on ( edict_t * self ) ;
+extern void target_laser_think ( edict_t * self ) ;
+extern void SP_target_crosslevel_target ( edict_t * self ) ;
+extern void target_crosslevel_target_think ( edict_t * self ) ;
+extern void SP_target_crosslevel_trigger ( edict_t * self ) ;
+extern void trigger_crosslevel_trigger_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_blaster ( edict_t * self ) ;
+extern void use_target_blaster ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_spawner ( edict_t * self ) ;
+extern void use_target_spawner ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_splash ( edict_t * self ) ;
+extern void use_target_splash ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_changelevel ( edict_t * ent ) ;
+extern void use_target_changelevel ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_explosion ( edict_t * ent ) ;
+extern void use_target_explosion ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_explosion_explode ( edict_t * self ) ;
+extern void SP_target_goal ( edict_t * ent ) ;
+extern void use_target_goal ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_secret ( edict_t * ent ) ;
+extern void use_target_secret ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_help ( edict_t * ent ) ;
+extern void Use_Target_Help ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_speaker ( edict_t * ent ) ;
+extern void Use_Target_Speaker ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_temp_entity ( edict_t * ent ) ;
+extern void Use_Target_Tent ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void ServerCommand ( void ) ;
+extern void SVCmd_WriteIP_f ( void ) ;
+extern void SVCmd_ListIP_f ( void ) ;
+extern void SVCmd_RemoveIP_f ( void ) ;
+extern void SVCmd_AddIP_f ( void ) ;
+extern qboolean SV_FilterPacket ( char * from ) ;
+extern void Svcmd_Test_f ( void ) ;
+extern void Vengeance_Launch ( edict_t * self ) ;
+extern void Hunter_Launch ( edict_t * self ) ;
+extern void Defender_Launch ( edict_t * self ) ;
+extern void Own_Sphere ( edict_t * self , edict_t * sphere ) ;
+extern edict_t * Sphere_Spawn ( edict_t * owner , int spawnflags ) ;
+extern void vengeance_think ( edict_t * self ) ;
+extern void hunter_think ( edict_t * self ) ;
+extern void defender_think ( edict_t * self ) ;
+extern void vengeance_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void defender_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void hunter_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void body_gib ( edict_t * self ) ;
+extern void defender_shoot ( edict_t * self , edict_t * enemy ) ;
+extern void hunter_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void vengeance_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void sphere_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf , int mod ) ;
+extern void sphere_fire ( edict_t * self , edict_t * enemy ) ;
+extern void sphere_chase ( edict_t * self , int stupidChase ) ;
+extern void sphere_fly ( edict_t * self ) ;
+extern void sphere_if_idle_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void sphere_explode ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void sphere_think_explode ( edict_t * self ) ;
+extern void Widowlegs_Spawn ( vec3_t startpos , vec3_t angles ) ;
+extern void widowlegs_think ( edict_t * self ) ;
+extern void SpawnGrow_Spawn ( vec3_t startpos , int size ) ;
+extern void spawngrow_think ( edict_t * self ) ;
+extern void DetermineBBox ( char * classname , vec3_t mins , vec3_t maxs ) ;
+extern qboolean CheckGroundSpawnPoint ( vec3_t origin , vec3_t entMins , vec3_t entMaxs , float height , float gravity ) ;
+extern qboolean CheckSpawnPoint ( vec3_t origin , vec3_t mins , vec3_t maxs ) ;
+extern qboolean FindSpawnPoint ( vec3_t startpoint , vec3_t mins , vec3_t maxs , vec3_t spawnpoint , float maxMoveUp ) ;
+extern edict_t * CreateGroundMonster ( vec3_t origin , vec3_t angles , vec3_t entMins , vec3_t entMaxs , char * classname , int height ) ;
+extern edict_t * CreateFlyMonster ( vec3_t origin , vec3_t angles , vec3_t mins , vec3_t maxs , char * classname ) ;
+extern edict_t * CreateMonster ( vec3_t origin , vec3_t angles , char * classname ) ;
+extern void SP_worldspawn ( edict_t * ent ) ;
+extern void SpawnEntities ( const char * mapname , char * entities , const char * spawnpoint ) ;
+extern void G_FindTeams ( void ) ;
+extern void G_FixTeams ( void ) ;
+extern char * ED_ParseEdict ( char * data , edict_t * ent ) ;
+extern void ED_ParseField ( const char * key , const char * value , edict_t * ent ) ;
+extern char * ED_NewString ( const char * string ) ;
+extern void ED_CallSpawn ( edict_t * ent ) ;
+extern void SV_Physics_NewToss ( edict_t * ent ) ;
+extern void G_RunEntity ( edict_t * ent ) ;
+extern void SV_Physics_Step ( edict_t * ent ) ;
+extern void SV_AddRotationalFriction ( edict_t * ent ) ;
+extern void SV_Physics_Toss ( edict_t * ent ) ;
+extern void SV_Physics_Noclip ( edict_t * ent ) ;
+extern void SV_Physics_None ( edict_t * ent ) ;
+extern void SV_Physics_Pusher ( edict_t * ent ) ;
+extern qboolean SV_Push ( edict_t * pusher , vec3_t move , vec3_t amove ) ;
+extern trace_t SV_PushEntity ( edict_t * ent , vec3_t push ) ;
+extern void RealBoundingBox ( edict_t * ent , vec3_t mins , vec3_t maxs ) ;
+extern void SV_AddGravity ( edict_t * ent ) ;
+extern int SV_FlyMove ( edict_t * ent , float time , int mask ) ;
+extern int ClipVelocity ( vec3_t in , vec3_t normal , vec3_t out , float overbounce ) ;
+extern void SV_Impact ( edict_t * e1 , trace_t * trace ) ;
+extern qboolean SV_RunThink ( edict_t * ent ) ;
+extern void SV_CheckVelocity ( edict_t * ent ) ;
+extern edict_t * SV_TestEntityPosition ( edict_t * ent ) ;
+extern void fire_tracker ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , edict_t * enemy ) ;
+extern void tracker_fly ( edict_t * self ) ;
+extern void tracker_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void tracker_explode ( edict_t * self ) ;
+extern void tracker_pain_daemon_spawn ( edict_t * owner , edict_t * enemy , int damage ) ;
+extern void tracker_pain_daemon_think ( edict_t * self ) ;
+extern void fire_blaster2 ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int effect , qboolean hyper ) ;
+extern void blaster2_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void fire_heat ( edict_t * self , vec3_t start , vec3_t aimdir , vec3_t offset , int damage , int kick , qboolean monster ) ;
+extern void fire_beams ( edict_t * self , vec3_t start , vec3_t aimdir , vec3_t offset , int damage , int kick , int te_beam , int te_impact , int mod ) ;
+extern void fire_tesla ( edict_t * self , vec3_t start , vec3_t aimdir , int damage_multiplier , int speed ) ;
+extern void tesla_lava ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void tesla_think ( edict_t * ent ) ;
+extern void tesla_activate ( edict_t * self ) ;
+extern void tesla_think_active ( edict_t * self ) ;
+extern void tesla_zap ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void tesla_blow ( edict_t * self ) ;
+extern void tesla_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void tesla_remove ( edict_t * self ) ;
+extern void fire_nuke ( edict_t * self , vec3_t start , vec3_t aimdir , int speed ) ;
+extern void nuke_bounce ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Nuke_Think ( edict_t * ent ) ;
+extern void nuke_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void Nuke_Explode ( edict_t * ent ) ;
+extern void Nuke_Quake ( edict_t * self ) ;
+extern void fire_player_melee ( edict_t * self , vec3_t start , vec3_t aim , int reach , int damage , int kick , int quiet , int mod ) ;
+extern void fire_prox ( edict_t * self , vec3_t start , vec3_t aimdir , int damage_multiplier , int speed ) ;
+extern void prox_land ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void prox_open ( edict_t * ent ) ;
+extern void prox_seek ( edict_t * ent ) ;
+extern void Prox_Field_Touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void prox_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void Prox_Explode ( edict_t * ent ) ;
+extern void fire_flechette ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int kick ) ;
+extern void flechette_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_trigger_disguise ( edict_t * self ) ;
+extern void trigger_disguise_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void trigger_disguise_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_trigger_teleport ( edict_t * self ) ;
+extern void trigger_teleport_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void trigger_teleport_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_info_teleport_destination ( edict_t * self ) ;
+extern void SP_target_orb ( edict_t * ent ) ;
+extern void orb_think ( edict_t * self ) ;
+extern void SP_target_blacklight ( edict_t * ent ) ;
+extern void blacklight_think ( edict_t * self ) ;
+extern void SP_target_killplayers ( edict_t * self ) ;
+extern void target_killplayers_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_anger ( edict_t * self ) ;
+extern void target_anger_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_steam ( edict_t * self ) ;
+extern void target_steam_start ( edict_t * self ) ;
+extern void use_target_steam ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_force_wall ( edict_t * ent ) ;
+extern void force_wall_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void force_wall_think ( edict_t * self ) ;
+extern void SP_func_door_secret2 ( edict_t * ent ) ;
+extern void secret_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void secret_blocked ( edict_t * self , edict_t * other ) ;
+extern void fd_secret_done ( edict_t * self ) ;
+extern void fd_secret_move6 ( edict_t * self ) ;
+extern void fd_secret_move5 ( edict_t * self ) ;
+extern void fd_secret_move4 ( edict_t * self ) ;
+extern void fd_secret_move3 ( edict_t * self ) ;
+extern void fd_secret_move2 ( edict_t * self ) ;
+extern void fd_secret_move1 ( edict_t * self ) ;
+extern void fd_secret_killed ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void fd_secret_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void fire_doppleganger ( edict_t * ent , vec3_t start , vec3_t aimdir ) ;
+extern void body_think ( edict_t * self ) ;
+extern void doppleganger_timeout ( edict_t * self ) ;
+extern void doppleganger_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void doppleganger_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void PrecacheForRandomRespawn ( void ) ;
+extern edict_t * DoRandomRespawn ( edict_t * ent ) ;
+extern char * FindSubstituteItem ( edict_t * ent ) ;
+extern void InitGameRules ( void ) ;
+extern qboolean monster_jump_finished ( edict_t * self ) ;
+extern void monster_jump_start ( edict_t * self ) ;
+extern int CountPlayers ( void ) ;
+extern edict_t * PickCoopTarget ( edict_t * self ) ;
+extern void TargetTesla ( edict_t * self , edict_t * tesla ) ;
+extern qboolean has_valid_enemy ( edict_t * self ) ;
+extern void monster_duck_up ( edict_t * self ) ;
+extern void monster_duck_hold ( edict_t * self ) ;
+extern void monster_duck_down ( edict_t * self ) ;
+extern void M_MonsterDodge ( edict_t * self , edict_t * attacker , float eta , trace_t * tr ) ;
+extern void drawbbox ( edict_t * self ) ;
+extern qboolean below ( edict_t * self , edict_t * other ) ;
+extern void PredictAim ( edict_t * target , vec3_t start , float bolt_speed , qboolean eye_height , float offset , vec3_t aimdir , vec3_t aimpoint ) ;
+extern qboolean MarkTeslaArea ( edict_t * self , edict_t * tesla ) ;
+extern edict_t * CheckForBadArea ( edict_t * ent ) ;
+extern edict_t * SpawnBadArea ( vec3_t mins , vec3_t maxs , float lifespan , edict_t * owner ) ;
+extern void badarea_touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern qboolean face_wall ( edict_t * self ) ;
+extern float realrange ( edict_t * self , edict_t * other ) ;
+extern qboolean inback ( edict_t * self , edict_t * other ) ;
+extern void InitHintPaths ( void ) ;
+extern void SP_hint_path ( edict_t * self ) ;
+extern void hint_path_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern qboolean monsterlost_checkhint ( edict_t * self ) ;
+extern void hintpath_stop ( edict_t * self ) ;
+extern void hintpath_go ( edict_t * self , edict_t * point ) ;
+extern edict_t * hintpath_other_end ( edict_t * ent ) ;
+extern edict_t * hintpath_findstart ( edict_t * ent ) ;
+extern qboolean blocked_checknewenemy ( edict_t * self ) ;
+extern qboolean blocked_checkjump ( edict_t * self , float dist , float maxDown , float maxUp ) ;
+extern qboolean blocked_checkplat ( edict_t * self , float dist ) ;
+extern void monster_done_dodge ( edict_t * self ) ;
+extern void stationarymonster_start ( edict_t * self ) ;
+extern void stationarymonster_start_go ( edict_t * self ) ;
+extern void stationarymonster_triggered_start ( edict_t * self ) ;
+extern void stationarymonster_triggered_spawn_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void stationarymonster_triggered_spawn ( edict_t * self ) ;
+extern void swimmonster_start ( edict_t * self ) ;
+extern void swimmonster_start_go ( edict_t * self ) ;
+extern void flymonster_start ( edict_t * self ) ;
+extern void flymonster_start_go ( edict_t * self ) ;
+extern void walkmonster_start ( edict_t * self ) ;
+extern void walkmonster_start_go ( edict_t * self ) ;
+extern void monster_start_go ( edict_t * self ) ;
+extern qboolean monster_start ( edict_t * self ) ;
+extern void monster_death_use ( edict_t * self ) ;
+extern void monster_triggered_start ( edict_t * self ) ;
+extern void monster_triggered_spawn_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void monster_triggered_spawn ( edict_t * self ) ;
+extern void monster_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void monster_think ( edict_t * self ) ;
+extern void M_MoveFrame ( edict_t * self ) ;
+extern void M_SetEffects ( edict_t * ent ) ;
+extern void M_droptofloor ( edict_t * ent ) ;
+extern void M_WorldEffects ( edict_t * ent ) ;
+extern void M_CatagorizePosition ( edict_t * ent ) ;
+extern void M_CheckGround ( edict_t * ent ) ;
+extern void AttackFinished ( edict_t * self , float time ) ;
+extern void M_FlyCheck ( edict_t * self ) ;
+extern void M_FliesOn ( edict_t * self ) ;
+extern void M_FliesOff ( edict_t * self ) ;
+extern void monster_fire_bfg ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , int kick , float damage_radius , int flashtype ) ;
+extern void monster_fire_railgun ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int flashtype ) ;
+extern void monster_fire_rocket ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype ) ;
+extern void monster_fire_grenade ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , int flashtype ) ;
+extern void monster_fire_heat ( edict_t * self , vec3_t start , vec3_t dir , vec3_t offset , int damage , int kick , int flashtype ) ;
+extern void monster_fire_tracker ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , edict_t * enemy , int flashtype ) ;
+extern void monster_fire_blaster2 ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype , int effect ) ;
+extern void monster_fire_blaster ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype , int effect ) ;
+extern void monster_fire_shotgun ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int hspread , int vspread , int count , int flashtype ) ;
+extern void monster_fire_bullet ( edict_t * self , vec3_t start , vec3_t dir , int damage , int kick , int hspread , int vspread , int flashtype ) ;
+extern void SP_misc_nuke_core ( edict_t * ent ) ;
+extern void misc_nuke_core_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_teleporter_dest ( edict_t * ent ) ;
+extern void SP_misc_teleporter ( edict_t * ent ) ;
+extern void teleporter_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_func_clock ( edict_t * self ) ;
+extern void func_clock_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_clock_think ( edict_t * self ) ;
+extern void func_clock_format_countdown ( edict_t * self ) ;
+extern void func_clock_reset ( edict_t * self ) ;
+extern void SP_target_string ( edict_t * self ) ;
+extern void target_string_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_character ( edict_t * self ) ;
+extern void SP_misc_gib_head ( edict_t * ent ) ;
+extern void SP_misc_gib_leg ( edict_t * ent ) ;
+extern void SP_misc_gib_arm ( edict_t * ent ) ;
+extern void SP_light_mine2 ( edict_t * ent ) ;
+extern void SP_light_mine1 ( edict_t * ent ) ;
+extern void SP_misc_satellite_dish ( edict_t * ent ) ;
+extern void misc_satellite_dish_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void misc_satellite_dish_think ( edict_t * self ) ;
+extern void SP_misc_strogg_ship ( edict_t * ent ) ;
+extern void misc_strogg_ship_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_viper_bomb ( edict_t * self ) ;
+extern void misc_viper_bomb_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void misc_viper_bomb_prethink ( edict_t * self ) ;
+extern void misc_viper_bomb_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_misc_bigviper ( edict_t * ent ) ;
+extern void SP_misc_viper ( edict_t * ent ) ;
+extern void misc_viper_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_deadsoldier ( edict_t * ent ) ;
+extern void misc_deadsoldier_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void SP_misc_banner ( edict_t * ent ) ;
+extern void misc_banner_think ( edict_t * ent ) ;
+extern void SP_monster_commander_body ( edict_t * self ) ;
+extern void commander_body_drop ( edict_t * self ) ;
+extern void commander_body_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void commander_body_think ( edict_t * self ) ;
+extern void SP_misc_easterchick2 ( edict_t * ent ) ;
+extern void misc_easterchick2_think ( edict_t * self ) ;
+extern void SP_misc_easterchick ( edict_t * ent ) ;
+extern void misc_easterchick_think ( edict_t * self ) ;
+extern void SP_misc_eastertank ( edict_t * ent ) ;
+extern void misc_eastertank_think ( edict_t * self ) ;
+extern void SP_misc_blackhole ( edict_t * ent ) ;
+extern void misc_blackhole_think ( edict_t * self ) ;
+extern void misc_blackhole_use ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_explobox ( edict_t * self ) ;
+extern void barrel_start ( edict_t * self ) ;
+extern void barrel_think ( edict_t * self ) ;
+extern void barrel_delay ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void barrel_explode ( edict_t * self ) ;
+extern void barrel_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_func_explosive ( edict_t * self ) ;
+extern void func_explosive_spawn ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_explosive_activate ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_explosive_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_explosive_explode ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void SP_func_object ( edict_t * self ) ;
+extern void func_object_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_object_release ( edict_t * self ) ;
+extern void func_object_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_func_wall ( edict_t * self ) ;
+extern void func_wall_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_light ( edict_t * self ) ;
+extern void light_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_info_notnull ( edict_t * self ) ;
+extern void SP_info_null ( edict_t * self ) ;
+extern void SP_viewthing ( edict_t * ent ) ;
+extern void TH_viewthing ( edict_t * ent ) ;
+extern void SP_point_combat ( edict_t * self ) ;
+extern void point_combat_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_path_corner ( edict_t * self ) ;
+extern void path_corner_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void BecomeExplosion2 ( edict_t * self ) ;
+extern void BecomeExplosion1 ( edict_t * self ) ;
+extern void ThrowDebris ( edict_t * self , char * modelname , float speed , vec3_t origin ) ;
+extern void debris_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void ThrowClientHead ( edict_t * self , int damage ) ;
+extern void ThrowHead ( edict_t * self , char * gibname , int damage , int type ) ;
+extern void ThrowGib ( edict_t * self , char * gibname , int damage , int type ) ;
+extern void gib_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void gib_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void gib_think ( edict_t * self ) ;
+extern void ClipGibVelocity ( edict_t * ent ) ;
+extern void VelocityForDamage ( int damage , vec3_t v ) ;
+extern void SP_func_areaportal ( edict_t * ent ) ;
+extern void Use_Areaportal ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void G_RunFrame ( void ) ;
+extern void ExitLevel ( void ) ;
+extern void CheckDMRules ( void ) ;
+extern void CheckNeedPass ( void ) ;
+extern void EndDMLevel ( void ) ;
+extern edict_t * CreateTargetChangeLevel ( char * map ) ;
+extern void ClientEndServerFrames ( void ) ;
+extern void Com_Printf ( char * msg , ... ) ;
+extern void Sys_Error ( char * error , ... ) ;
+extern game_export_t * GetGameAPI ( game_import_t * import ) ;
+extern void ShutdownGame ( void ) ;
+extern void SP_xatrix_item ( edict_t * self ) ;
+extern void SetItemNames ( void ) ;
+extern void InitItems ( void ) ;
+extern void SP_item_health_mega ( edict_t * self ) ;
+extern void SP_item_health_large ( edict_t * self ) ;
+extern void SP_item_health_small ( edict_t * self ) ;
+extern void SP_item_health ( edict_t * self ) ;
+extern void SpawnItem ( edict_t * ent , gitem_t * item ) ;
+extern void SetTriggeredSpawn ( edict_t * ent ) ;
+extern void Item_TriggeredSpawn ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void PrecacheItem ( gitem_t * it ) ;
+extern void droptofloor ( edict_t * ent ) ;
+extern void Use_Item ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern edict_t * Drop_Item ( edict_t * ent , gitem_t * item ) ;
+extern void drop_make_touchable ( edict_t * ent ) ;
+extern void drop_temp_touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Touch_Item ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Drop_PowerArmor ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_PowerArmor ( edict_t * ent , edict_t * other ) ;
+extern void Use_PowerArmor ( edict_t * ent , gitem_t * item ) ;
+extern int PowerArmorType ( edict_t * ent ) ;
+extern qboolean Pickup_Armor ( edict_t * ent , edict_t * other ) ;
+extern int ArmorIndex ( edict_t * ent ) ;
+extern qboolean Pickup_Health ( edict_t * ent , edict_t * other ) ;
+extern void MegaHealth_think ( edict_t * self ) ;
+extern void Drop_Ammo ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_Ammo ( edict_t * ent , edict_t * other ) ;
+extern qboolean Add_Ammo ( edict_t * ent , gitem_t * item , int count ) ;
+extern qboolean Pickup_Key ( edict_t * ent , edict_t * other ) ;
+extern void Use_Silencer ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Invulnerability ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Envirosuit ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Breather ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Quad ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Vengeance ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Hunter ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Defender ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_Sphere ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_Doppleganger ( edict_t * ent , edict_t * other ) ;
+extern void Use_Doppleganger ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Nuke ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Compass ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Double ( edict_t * ent , gitem_t * item ) ;
+extern void Use_IR ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_Nuke ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_Pack ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_Bandolier ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_AncientHead ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_Adrenaline ( edict_t * ent , edict_t * other ) ;
+extern void Drop_General ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_Powerup ( edict_t * ent , edict_t * other ) ;
+extern void SetRespawn ( edict_t * ent , float delay ) ;
+extern void DoRespawn ( edict_t * ent ) ;
+extern gitem_t * FindItem ( char * pickup_name ) ;
+extern gitem_t * FindItemByClassname ( char * classname ) ;
+extern gitem_t * GetItemByIndex ( int index ) ;
+extern void SP_func_killbox ( edict_t * ent ) ;
+extern void use_killbox ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_door_secret ( edict_t * ent ) ;
+extern void door_secret_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void door_secret_blocked ( edict_t * self , edict_t * other ) ;
+extern void door_secret_done ( edict_t * self ) ;
+extern void door_secret_move6 ( edict_t * self ) ;
+extern void door_secret_move5 ( edict_t * self ) ;
+extern void door_secret_move4 ( edict_t * self ) ;
+extern void door_secret_move3 ( edict_t * self ) ;
+extern void door_secret_move2 ( edict_t * self ) ;
+extern void door_secret_move1 ( edict_t * self ) ;
+extern void door_secret_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_conveyor ( edict_t * self ) ;
+extern void func_conveyor_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_timer ( edict_t * self ) ;
+extern void func_timer_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_timer_think ( edict_t * self ) ;
+extern void SP_trigger_elevator ( edict_t * self ) ;
+extern void trigger_elevator_init ( edict_t * self ) ;
+extern void trigger_elevator_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_train ( edict_t * self ) ;
+extern void train_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_train_find ( edict_t * self ) ;
+extern void train_resume ( edict_t * self ) ;
+extern void train_next ( edict_t * self ) ;
+extern void train_piece_wait ( edict_t * self ) ;
+extern void train_wait ( edict_t * self ) ;
+extern void train_blocked ( edict_t * self , edict_t * other ) ;
+extern void SP_func_water ( edict_t * self ) ;
+extern void smart_water_blocked ( edict_t * self , edict_t * other ) ;
+extern void SP_func_door_rotating ( edict_t * ent ) ;
+extern void Door_Activate ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_door ( edict_t * ent ) ;
+extern void door_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void door_killed ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void door_blocked ( edict_t * self , edict_t * other ) ;
+extern void Think_SpawnDoorTrigger ( edict_t * ent ) ;
+extern void Think_CalcMoveSpeed ( edict_t * self ) ;
+extern void Touch_DoorTrigger ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void door_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void smart_water_go_up ( edict_t * self ) ;
+extern void door_go_up ( edict_t * self , edict_t * activator ) ;
+extern void door_go_down ( edict_t * self ) ;
+extern void door_hit_bottom ( edict_t * self ) ;
+extern void door_hit_top ( edict_t * self ) ;
+extern void door_use_areaportals ( edict_t * self , qboolean open ) ;
+extern void SP_func_button ( edict_t * ent ) ;
+extern void button_killed ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void button_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void button_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void button_fire ( edict_t * self ) ;
+extern void button_wait ( edict_t * self ) ;
+extern void button_return ( edict_t * self ) ;
+extern void button_done ( edict_t * self ) ;
+extern void SP_func_rotating ( edict_t * ent ) ;
+extern void rotating_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void rotating_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void rotating_blocked ( edict_t * self , edict_t * other ) ;
+extern void rotating_decel ( edict_t * self ) ;
+extern void rotating_accel ( edict_t * self ) ;
+extern void SP_func_plat2 ( edict_t * ent ) ;
+extern void plat2_activate ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void Use_Plat2 ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void plat2_blocked ( edict_t * self , edict_t * other ) ;
+extern void Touch_Plat_Center2 ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void plat2_operate ( edict_t * ent , edict_t * other ) ;
+extern void plat2_go_up ( edict_t * ent ) ;
+extern void plat2_go_down ( edict_t * ent ) ;
+extern void plat2_hit_bottom ( edict_t * ent ) ;
+extern void plat2_hit_top ( edict_t * ent ) ;
+extern void plat2_kill_danger_area ( edict_t * ent ) ;
+extern void plat2_spawn_danger_area ( edict_t * ent ) ;
+extern void SP_func_plat ( edict_t * ent ) ;
+extern edict_t * plat_spawn_inside_trigger ( edict_t * ent ) ;
+extern void Touch_Plat_Center ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Use_Plat ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void plat_blocked ( edict_t * self , edict_t * other ) ;
+extern void plat_go_up ( edict_t * ent ) ;
+extern void plat_go_down ( edict_t * ent ) ;
+extern void plat_hit_bottom ( edict_t * ent ) ;
+extern void plat_hit_top ( edict_t * ent ) ;
+extern void Think_AccelMove ( edict_t * ent ) ;
+extern void plat_Accelerate ( moveinfo_t * moveinfo ) ;
+extern void plat_CalcAcceleratedMove ( moveinfo_t * moveinfo ) ;
+extern void AngleMove_Calc ( edict_t * ent , void ( * func ) ( edict_t * ) ) ;
+extern void AngleMove_Begin ( edict_t * ent ) ;
+extern void AngleMove_Final ( edict_t * ent ) ;
+extern void AngleMove_Done ( edict_t * ent ) ;
+extern void Move_Calc ( edict_t * ent , vec3_t dest , void ( * func ) ( edict_t * ) ) ;
+extern void Move_Begin ( edict_t * ent ) ;
+extern void Move_Final ( edict_t * ent ) ;
+extern void Move_Done ( edict_t * ent ) ;
+extern void T_RadiusClassDamage ( edict_t * inflictor , edict_t * attacker , float damage , char * ignoreClass , float radius , int mod ) ;
+extern void T_RadiusNukeDamage ( edict_t * inflictor , edict_t * attacker , float damage , edict_t * ignore , float radius , int mod ) ;
+extern void T_RadiusDamage ( edict_t * inflictor , edict_t * attacker , float damage , edict_t * ignore , float radius , int mod ) ;
+extern void T_Damage ( edict_t * targ , edict_t * inflictor , edict_t * attacker , vec3_t dir , vec3_t point , vec3_t normal , int damage , int knockback , int dflags , int mod ) ;
+extern qboolean CheckTeamDamage ( edict_t * targ , edict_t * attacker ) ;
+extern void M_ReactToDamage ( edict_t * targ , edict_t * attacker , edict_t * inflictor ) ;
+extern int CheckArmor ( edict_t * ent , vec3_t point , vec3_t normal , int damage , int te_sparks , int dflags ) ;
+extern int CheckPowerArmor ( edict_t * ent , vec3_t point , vec3_t normal , int damage , int dflags ) ;
+extern void SpawnDamage ( int type , vec3_t origin , vec3_t normal , int damage ) ;
+extern void Killed ( edict_t * targ , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern qboolean CanDamage ( edict_t * targ , edict_t * inflictor ) ;
+extern void cleanupHealTarget ( edict_t * ent ) ;
+extern void ClientCommand ( edict_t * ent ) ;
+extern void Cmd_PlayerList_f ( edict_t * ent ) ;
+extern void Cmd_Ent_Count_f ( edict_t * ent ) ;
+extern void Cmd_Say_f ( edict_t * ent , qboolean team , qboolean arg0 ) ;
+extern void Cmd_Wave_f ( edict_t * ent ) ;
+extern void Cmd_Players_f ( edict_t * ent ) ;
+extern int PlayerSort ( void const * a , void const * b ) ;
+extern void Cmd_PutAway_f ( edict_t * ent ) ;
+extern void Cmd_Kill_f ( edict_t * ent ) ;
+extern void Cmd_InvDrop_f ( edict_t * ent ) ;
+extern void Cmd_WeapLast_f ( edict_t * ent ) ;
+extern void Cmd_WeapNext_f ( edict_t * ent ) ;
+extern void Cmd_WeapPrev_f ( edict_t * ent ) ;
+extern void Cmd_InvUse_f ( edict_t * ent ) ;
+extern void Cmd_Inven_f ( edict_t * ent ) ;
+extern void Cmd_Drop_f ( edict_t * ent ) ;
+extern void Cmd_Use_f ( edict_t * ent ) ;
+extern void Cmd_Noclip_f ( edict_t * ent ) ;
+extern void Cmd_Notarget_f ( edict_t * ent ) ;
+extern void Cmd_God_f ( edict_t * ent ) ;
+extern void Cmd_Give_f ( edict_t * ent ) ;
+extern void ValidateSelectedItem ( edict_t * ent ) ;
+extern void SelectPrevItem ( edict_t * ent , int itflags ) ;
+extern void SelectNextItem ( edict_t * ent , int itflags ) ;
+extern qboolean OnSameTeam ( edict_t * ent1 , edict_t * ent2 ) ;
+extern void GetChaseTarget ( edict_t * ent ) ;
+extern void ChasePrev ( edict_t * ent ) ;
+extern void ChaseNext ( edict_t * ent ) ;
+extern void UpdateChaseCam ( edict_t * ent ) ;
+extern void ai_run ( edict_t * self , float dist ) ;
+extern qboolean ai_checkattack ( edict_t * self , float dist ) ;
+extern void ai_run_slide ( edict_t * self , float distance ) ;
+extern void ai_run_missile ( edict_t * self ) ;
+extern void ai_run_melee ( edict_t * self ) ;
+extern qboolean M_CheckAttack ( edict_t * self ) ;
+extern qboolean FacingIdeal ( edict_t * self ) ;
+extern qboolean FindTarget ( edict_t * self ) ;
+extern void FoundTarget ( edict_t * self ) ;
+extern void HuntTarget ( edict_t * self ) ;
+extern qboolean infront ( edict_t * self , edict_t * other ) ;
+extern qboolean visible ( edict_t * self , edict_t * other ) ;
+extern int range ( edict_t * self , edict_t * other ) ;
+extern void ai_turn ( edict_t * self , float dist ) ;
+extern void ai_charge ( edict_t * self , float dist ) ;
+extern void ai_walk ( edict_t * self , float dist ) ;
+extern void ai_stand ( edict_t * self , float dist ) ;
+extern void ai_move ( edict_t * self , float dist ) ;
+extern void AI_SetSightClient ( void ) ;
+extern void SP_dm_tag_token ( edict_t * self ) ;
+extern void Tag_PostInitSetup ( void ) ;
+extern void Tag_GameInit ( void ) ;
+extern int Tag_ChangeDamage ( edict_t * targ , edict_t * attacker , int damage , int mod ) ;
+extern void Tag_DogTag ( edict_t * ent , edict_t * killer , char * * pic ) ;
+extern void Tag_PlayerEffects ( edict_t * ent ) ;
+extern void Tag_DropToken ( edict_t * ent , gitem_t * item ) ;
+extern void Tag_MakeTouchable ( edict_t * ent ) ;
+extern void Tag_Respawn ( edict_t * ent ) ;
+extern qboolean Tag_PickupToken ( edict_t * ent , edict_t * other ) ;
+extern void Tag_Score ( edict_t * attacker , edict_t * victim , int scoreChange ) ;
+extern void Tag_PlayerDisconnect ( edict_t * self ) ;
+extern void Tag_KillItBonus ( edict_t * self ) ;
+extern void Tag_PlayerDeath ( edict_t * targ , edict_t * inflictor , edict_t * attacker ) ;
+extern void SP_dm_dball_goal ( edict_t * self ) ;
+extern void SP_dm_dball_speed_change ( edict_t * self ) ;
+extern void SP_dm_dball_ball_start ( edict_t * self ) ;
+extern void SP_dm_dball_team2_start ( edict_t * self ) ;
+extern void SP_dm_dball_team1_start ( edict_t * self ) ;
+extern void SP_dm_dball_ball ( edict_t * self ) ;
+extern void DBall_SpeedTouch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void DBall_BallRespawn ( edict_t * self ) ;
+extern void DBall_BallDie ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void DBall_BallPain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void DBall_BallTouch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern edict_t * PickBallStart ( edict_t * ent ) ;
+extern void DBall_GoalTouch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern int DBall_ChangeKnockback ( edict_t * targ , edict_t * attacker , int knockback , int mod ) ;
+extern int DBall_ChangeDamage ( edict_t * targ , edict_t * attacker , int damage , int mod ) ;
+extern void DBall_PostInitSetup ( void ) ;
+extern void DBall_GameInit ( void ) ;
+extern void DBall_SelectSpawnPoint ( edict_t * ent , vec3_t origin , vec3_t angles ) ;
+extern void DBall_ClientBegin ( edict_t * ent ) ;
+extern int DBall_CheckDMRules ( void ) ;
+extern void wait_and_change_think(edict_t* ent);
diff --git a/rogue/src/savegame/tables/gamefunc_list.h b/rogue/src/savegame/tables/gamefunc_list.h
new file mode 100644
index 0000000..9b23798
--- /dev/null
+++ b/rogue/src/savegame/tables/gamefunc_list.h
@@ -0,0 +1,1493 @@
+/*
+ * =======================================================================
+ *
+ * Functionpointers to every function in the game.so.
+ *
+ * =======================================================================
+ */
+
+{"ReadLevel", (byte *)ReadLevel},
+{"ReadLevelLocals", (byte *)ReadLevelLocals},
+{"ReadEdict", (byte *)ReadEdict},
+{"WriteLevel", (byte *)WriteLevel},
+{"WriteLevelLocals", (byte *)WriteLevelLocals},
+{"WriteEdict", (byte *)WriteEdict},
+{"ReadGame", (byte *)ReadGame},
+{"WriteGame", (byte *)WriteGame},
+{"ReadClient", (byte *)ReadClient},
+{"WriteClient", (byte *)WriteClient},
+{"ReadField", (byte *)ReadField},
+{"WriteField2", (byte *)WriteField2},
+{"WriteField1", (byte *)WriteField1},
+{"FindMmoveByName", (byte *)FindMmoveByName},
+{"GetMmoveByAddress", (byte *)GetMmoveByAddress},
+{"FindFunctionByName", (byte *)FindFunctionByName},
+{"GetFunctionByAddress", (byte *)GetFunctionByAddress},
+{"InitGame", (byte *)InitGame},
+{"Info_SetValueForKey", (byte *)Info_SetValueForKey},
+{"Info_Validate", (byte *)Info_Validate},
+{"Info_RemoveKey", (byte *)Info_RemoveKey},
+{"Info_ValueForKey", (byte *)Info_ValueForKey},
+{"Com_sprintf", (byte *)Com_sprintf},
+{"Q_strcasecmp", (byte *)Q_strcasecmp},
+{"Q_strncasecmp", (byte *)Q_strncasecmp},
+{"Q_stricmp", (byte *)Q_stricmp},
+{"Com_PageInMemory", (byte *)Com_PageInMemory},
+{"COM_Parse", (byte *)COM_Parse},
+{"va", (byte *)va},
+{"Swap_Init", (byte *)Swap_Init},
+{"FloatNoSwap", (byte *)FloatNoSwap},
+{"FloatSwap", (byte *)FloatSwap},
+{"LongNoSwap", (byte *)LongNoSwap},
+{"LongSwap", (byte *)LongSwap},
+{"ShortNoSwap", (byte *)ShortNoSwap},
+{"ShortSwap", (byte *)ShortSwap},
+{"LittleFloat", (byte *)LittleFloat},
+{"BigFloat", (byte *)BigFloat},
+{"LittleLong", (byte *)LittleLong},
+{"BigLong", (byte *)BigLong},
+{"LittleShort", (byte *)LittleShort},
+{"BigShort", (byte *)BigShort},
+{"COM_DefaultExtension", (byte *)COM_DefaultExtension},
+{"COM_FilePath", (byte *)COM_FilePath},
+{"COM_FileBase", (byte *)COM_FileBase},
+{"COM_FileExtension", (byte *)COM_FileExtension},
+{"COM_StripExtension", (byte *)COM_StripExtension},
+{"COM_SkipPath", (byte *)COM_SkipPath},
+{"Q_log2", (byte *)Q_log2},
+{"VectorScale", (byte *)VectorScale},
+{"VectorInverse", (byte *)VectorInverse},
+{"VectorLength", (byte *)VectorLength},
+{"CrossProduct", (byte *)CrossProduct},
+{"_VectorCopy", (byte *)_VectorCopy},
+{"_VectorAdd", (byte *)_VectorAdd},
+{"_VectorSubtract", (byte *)_VectorSubtract},
+{"_DotProduct", (byte *)_DotProduct},
+{"VectorMA", (byte *)VectorMA},
+{"VectorNormalize2", (byte *)VectorNormalize2},
+{"VectorNormalize", (byte *)VectorNormalize},
+{"VectorCompare", (byte *)VectorCompare},
+{"AddPointToBounds", (byte *)AddPointToBounds},
+{"ClearBounds", (byte *)ClearBounds},
+{"BoxOnPlaneSide2", (byte *)BoxOnPlaneSide2},
+{"anglemod", (byte *)anglemod},
+{"LerpAngle", (byte *)LerpAngle},
+{"Q_fabs", (byte *)Q_fabs},
+{"R_ConcatTransforms", (byte *)R_ConcatTransforms},
+{"R_ConcatRotations", (byte *)R_ConcatRotations},
+{"PerpendicularVector", (byte *)PerpendicularVector},
+{"ProjectPointOnPlane", (byte *)ProjectPointOnPlane},
+{"AngleVectors", (byte *)AngleVectors},
+{"RotatePointAroundVector", (byte *)RotatePointAroundVector},
+{"Weapon_Heatbeam", (byte *)Weapon_Heatbeam},
+{"Heatbeam_Fire", (byte *)Heatbeam_Fire},
+{"Weapon_ETF_Rifle", (byte *)Weapon_ETF_Rifle},
+{"weapon_etf_rifle_fire", (byte *)weapon_etf_rifle_fire},
+{"Weapon_Disintegrator", (byte *)Weapon_Disintegrator},
+{"weapon_tracker_fire", (byte *)weapon_tracker_fire},
+{"Weapon_ChainFist", (byte *)Weapon_ChainFist},
+{"chainfist_smoke", (byte *)chainfist_smoke},
+{"weapon_chainfist_fire", (byte *)weapon_chainfist_fire},
+{"Weapon_BFG", (byte *)Weapon_BFG},
+{"weapon_bfg_fire", (byte *)weapon_bfg_fire},
+{"Weapon_Railgun", (byte *)Weapon_Railgun},
+{"weapon_railgun_fire", (byte *)weapon_railgun_fire},
+{"Weapon_SuperShotgun", (byte *)Weapon_SuperShotgun},
+{"weapon_supershotgun_fire", (byte *)weapon_supershotgun_fire},
+{"Weapon_Shotgun", (byte *)Weapon_Shotgun},
+{"weapon_shotgun_fire", (byte *)weapon_shotgun_fire},
+{"Weapon_Chaingun", (byte *)Weapon_Chaingun},
+{"Chaingun_Fire", (byte *)Chaingun_Fire},
+{"Weapon_Machinegun", (byte *)Weapon_Machinegun},
+{"Machinegun_Fire", (byte *)Machinegun_Fire},
+{"Weapon_HyperBlaster", (byte *)Weapon_HyperBlaster},
+{"Weapon_HyperBlaster_Fire", (byte *)Weapon_HyperBlaster_Fire},
+{"Weapon_Blaster", (byte *)Weapon_Blaster},
+{"Weapon_Blaster_Fire", (byte *)Weapon_Blaster_Fire},
+{"Blaster_Fire", (byte *)Blaster_Fire},
+{"Weapon_RocketLauncher", (byte *)Weapon_RocketLauncher},
+{"Weapon_RocketLauncher_Fire", (byte *)Weapon_RocketLauncher_Fire},
+{"Weapon_ProxLauncher", (byte *)Weapon_ProxLauncher},
+{"Weapon_GrenadeLauncher", (byte *)Weapon_GrenadeLauncher},
+{"weapon_grenadelauncher_fire", (byte *)weapon_grenadelauncher_fire},
+{"Weapon_Tesla", (byte *)Weapon_Tesla},
+{"Weapon_Grenade", (byte *)Weapon_Grenade},
+{"Throw_Generic", (byte *)Throw_Generic},
+{"weapon_grenade_fire", (byte *)weapon_grenade_fire},
+{"Weapon_Generic", (byte *)Weapon_Generic},
+{"Drop_Weapon", (byte *)Drop_Weapon},
+{"Use_Weapon", (byte *)Use_Weapon},
+{"Think_Weapon", (byte *)Think_Weapon},
+{"NoAmmoWeaponChange", (byte *)NoAmmoWeaponChange},
+{"ChangeWeapon", (byte *)ChangeWeapon},
+{"Pickup_Weapon", (byte *)Pickup_Weapon},
+{"PlayerNoise", (byte *)PlayerNoise},
+{"P_ProjectSource2", (byte *)P_ProjectSource2},
+{"P_ProjectSource", (byte *)P_ProjectSource},
+{"P_DamageModifier", (byte *)P_DamageModifier},
+{"ClientEndServerFrame", (byte *)ClientEndServerFrame},
+{"G_SetClientFrame", (byte *)G_SetClientFrame},
+{"G_SetClientSound", (byte *)G_SetClientSound},
+{"G_SetClientEvent", (byte *)G_SetClientEvent},
+{"G_SetClientEffects", (byte *)G_SetClientEffects},
+{"P_WorldEffects", (byte *)P_WorldEffects},
+{"P_FallingDamage", (byte *)P_FallingDamage},
+{"SV_CalcBlend", (byte *)SV_CalcBlend},
+{"SV_AddBlend", (byte *)SV_AddBlend},
+{"SV_CalcGunOffset", (byte *)SV_CalcGunOffset},
+{"SV_CalcViewOffset", (byte *)SV_CalcViewOffset},
+{"P_DamageFeedback", (byte *)P_DamageFeedback},
+{"SV_CalcRoll", (byte *)SV_CalcRoll},
+{"PlayerTrail_LastSpot", (byte *)PlayerTrail_LastSpot},
+{"PlayerTrail_PickNext", (byte *)PlayerTrail_PickNext},
+{"PlayerTrail_PickFirst", (byte *)PlayerTrail_PickFirst},
+{"PlayerTrail_New", (byte *)PlayerTrail_New},
+{"PlayerTrail_Add", (byte *)PlayerTrail_Add},
+{"PlayerTrail_Init", (byte *)PlayerTrail_Init},
+{"G_SetSpectatorStats", (byte *)G_SetSpectatorStats},
+{"G_CheckChaseStats", (byte *)G_CheckChaseStats},
+{"G_SetStats", (byte *)G_SetStats},
+{"InventoryMessage", (byte *)InventoryMessage},
+{"HelpComputerMessage", (byte *)HelpComputerMessage},
+{"DeathmatchScoreboardMessage", (byte *)DeathmatchScoreboardMessage},
+{"BeginIntermission", (byte *)BeginIntermission},
+{"MoveClientToIntermission", (byte *)MoveClientToIntermission},
+{"RemoveAttackingPainDaemons", (byte *)RemoveAttackingPainDaemons},
+{"ClientBeginServerFrame", (byte *)ClientBeginServerFrame},
+{"ClientThink", (byte *)ClientThink},
+{"PrintPmove", (byte *)PrintPmove},
+{"CheckBlock", (byte *)CheckBlock},
+{"PM_trace", (byte *)PM_trace},
+{"ClientDisconnect", (byte *)ClientDisconnect},
+{"ClientConnect", (byte *)ClientConnect},
+{"ClientUserinfoChanged", (byte *)ClientUserinfoChanged},
+{"ClientBegin", (byte *)ClientBegin},
+{"ClientBeginDeathmatch", (byte *)ClientBeginDeathmatch},
+{"PutClientInServer", (byte *)PutClientInServer},
+{"spectator_respawn", (byte *)spectator_respawn},
+{"respawn", (byte *)respawn},
+{"CopyToBodyQue", (byte *)CopyToBodyQue},
+{"body_die", (byte *)body_die},
+{"InitBodyQue", (byte *)InitBodyQue},
+{"SelectSpawnPoint", (byte *)SelectSpawnPoint},
+{"SelectCoopSpawnPoint", (byte *)SelectCoopSpawnPoint},
+{"SelectLavaCoopSpawnPoint", (byte *)SelectLavaCoopSpawnPoint},
+{"SelectDeathmatchSpawnPoint", (byte *)SelectDeathmatchSpawnPoint},
+{"SelectFarthestDeathmatchSpawnPoint", (byte *)SelectFarthestDeathmatchSpawnPoint},
+{"SelectRandomDeathmatchSpawnPoint", (byte *)SelectRandomDeathmatchSpawnPoint},
+{"PlayersRangeFromSpot", (byte *)PlayersRangeFromSpot},
+{"FetchClientEntData", (byte *)FetchClientEntData},
+{"SaveClientData", (byte *)SaveClientData},
+{"InitClientResp", (byte *)InitClientResp},
+{"InitClientPersistant", (byte *)InitClientPersistant},
+{"player_die", (byte *)player_die},
+{"LookAtKiller", (byte *)LookAtKiller},
+{"TossClientWeapon", (byte *)TossClientWeapon},
+{"ClientObituary", (byte *)ClientObituary},
+{"IsNeutral", (byte *)IsNeutral},
+{"IsFemale", (byte *)IsFemale},
+{"player_pain", (byte *)player_pain},
+{"SP_info_player_intermission", (byte *)SP_info_player_intermission},
+{"SP_info_player_coop_lava", (byte *)SP_info_player_coop_lava},
+{"SP_info_player_coop", (byte *)SP_info_player_coop},
+{"SP_info_player_deathmatch", (byte *)SP_info_player_deathmatch},
+{"SP_info_player_start", (byte *)SP_info_player_start},
+{"ThrowArm2", (byte *)ThrowArm2},
+{"ThrowArm1", (byte *)ThrowArm1},
+{"WidowExplosionLeg", (byte *)WidowExplosionLeg},
+{"WidowExplosion7", (byte *)WidowExplosion7},
+{"WidowExplosion6", (byte *)WidowExplosion6},
+{"WidowExplosion5", (byte *)WidowExplosion5},
+{"WidowExplosion4", (byte *)WidowExplosion4},
+{"WidowExplosion3", (byte *)WidowExplosion3},
+{"WidowExplosion2", (byte *)WidowExplosion2},
+{"WidowExplosion1", (byte *)WidowExplosion1},
+{"WidowExplode", (byte *)WidowExplode},
+{"ThrowMoreStuff", (byte *)ThrowMoreStuff},
+{"ThrowSmallStuff", (byte *)ThrowSmallStuff},
+{"ThrowWidowGibReal", (byte *)ThrowWidowGibReal},
+{"ThrowWidowGibSized", (byte *)ThrowWidowGibSized},
+{"ThrowWidowGibLoc", (byte *)ThrowWidowGibLoc},
+{"ThrowWidowGib", (byte *)ThrowWidowGib},
+{"widow_gib_touch", (byte *)widow_gib_touch},
+{"WidowVelocityForDamage", (byte *)WidowVelocityForDamage},
+{"SP_monster_widow2", (byte *)SP_monster_widow2},
+{"Widow2Precache", (byte *)Widow2Precache},
+{"Widow2_CheckAttack", (byte *)Widow2_CheckAttack},
+{"widow2_die", (byte *)widow2_die},
+{"KillChildren", (byte *)KillChildren},
+{"widow2_dead", (byte *)widow2_dead},
+{"widow2_pain", (byte *)widow2_pain},
+{"widow2_reattack_beam", (byte *)widow2_reattack_beam},
+{"widow2_attack_beam", (byte *)widow2_attack_beam},
+{"widow2_attack", (byte *)widow2_attack},
+{"widow2_melee", (byte *)widow2_melee},
+{"widow2_walk", (byte *)widow2_walk},
+{"widow2_run", (byte *)widow2_run},
+{"widow2_stand", (byte *)widow2_stand},
+{"widow2_finaldeath", (byte *)widow2_finaldeath},
+{"widow2_keep_searching", (byte *)widow2_keep_searching},
+{"widow2_start_searching", (byte *)widow2_start_searching},
+{"Widow2Toss", (byte *)Widow2Toss},
+{"Widow2Crunch", (byte *)Widow2Crunch},
+{"Widow2TonguePull", (byte *)Widow2TonguePull},
+{"Widow2Tongue", (byte *)Widow2Tongue},
+{"widow2_tongue_attack_ok", (byte *)widow2_tongue_attack_ok},
+{"Widow2StartSweep", (byte *)Widow2StartSweep},
+{"Widow2BeamTargetRemove", (byte *)Widow2BeamTargetRemove},
+{"Widow2SaveBeamTarget", (byte *)Widow2SaveBeamTarget},
+{"widow2_disrupt_reattack", (byte *)widow2_disrupt_reattack},
+{"Widow2SaveDisruptLoc", (byte *)Widow2SaveDisruptLoc},
+{"WidowDisrupt", (byte *)WidowDisrupt},
+{"widow2_ready_spawn", (byte *)widow2_ready_spawn},
+{"widow2_spawn_check", (byte *)widow2_spawn_check},
+{"Widow2Spawn", (byte *)Widow2Spawn},
+{"Widow2Beam", (byte *)Widow2Beam},
+{"widow2_search", (byte *)widow2_search},
+{"pauseme", (byte *)pauseme},
+{"SP_monster_widow", (byte *)SP_monster_widow},
+{"WidowPrecache", (byte *)WidowPrecache},
+{"WidowCalcSlots", (byte *)WidowCalcSlots},
+{"widow_blocked", (byte *)widow_blocked},
+{"Widow_CheckAttack", (byte *)Widow_CheckAttack},
+{"WidowPowerups", (byte *)WidowPowerups},
+{"WidowRespondPowerup", (byte *)WidowRespondPowerup},
+{"WidowPowerArmor", (byte *)WidowPowerArmor},
+{"WidowPent", (byte *)WidowPent},
+{"WidowDouble", (byte *)WidowDouble},
+{"WidowGoinQuad", (byte *)WidowGoinQuad},
+{"widow_melee", (byte *)widow_melee},
+{"widow_die", (byte *)widow_die},
+{"widow_dead", (byte *)widow_dead},
+{"widow_pain", (byte *)widow_pain},
+{"widow_reattack_blaster", (byte *)widow_reattack_blaster},
+{"widow_attack_blaster", (byte *)widow_attack_blaster},
+{"widow_attack", (byte *)widow_attack},
+{"widow_walk", (byte *)widow_walk},
+{"widow_run", (byte *)widow_run},
+{"widow_stand", (byte *)widow_stand},
+{"widow_attack_kick", (byte *)widow_attack_kick},
+{"spawn_out_do", (byte *)spawn_out_do},
+{"spawn_out_start", (byte *)spawn_out_start},
+{"widow_done_spawn", (byte *)widow_done_spawn},
+{"widow_start_spawn", (byte *)widow_start_spawn},
+{"widow_attack_rail", (byte *)widow_attack_rail},
+{"widow_rail_done", (byte *)widow_rail_done},
+{"widow_start_rail", (byte *)widow_start_rail},
+{"WidowSaveLoc", (byte *)WidowSaveLoc},
+{"WidowRail", (byte *)WidowRail},
+{"widow_start_run_12", (byte *)widow_start_run_12},
+{"widow_start_run_10", (byte *)widow_start_run_10},
+{"widow_start_run_5", (byte *)widow_start_run_5},
+{"widow_stepshoot", (byte *)widow_stepshoot},
+{"widow_step", (byte *)widow_step},
+{"widow_ready_spawn", (byte *)widow_ready_spawn},
+{"widow_spawn_check", (byte *)widow_spawn_check},
+{"WidowSpawn", (byte *)WidowSpawn},
+{"WidowBlaster", (byte *)WidowBlaster},
+{"WidowTorso", (byte *)WidowTorso},
+{"target_angle", (byte *)target_angle},
+{"widow_sight", (byte *)widow_sight},
+{"widow_search", (byte *)widow_search},
+{"showme", (byte *)showme},
+{"SP_monster_turret", (byte *)SP_monster_turret},
+{"turret_checkattack", (byte *)turret_checkattack},
+{"turret_activate", (byte *)turret_activate},
+{"turret_wake", (byte *)turret_wake},
+{"turret_wall_spawn", (byte *)turret_wall_spawn},
+{"turret_die", (byte *)turret_die},
+{"turret_pain", (byte *)turret_pain},
+{"turret_attack", (byte *)turret_attack},
+{"TurretFireBlind", (byte *)TurretFireBlind},
+{"TurretFire", (byte *)TurretFire},
+{"turret_run", (byte *)turret_run},
+{"turret_walk", (byte *)turret_walk},
+{"turret_ready_gun", (byte *)turret_ready_gun},
+{"turret_stand", (byte *)turret_stand},
+{"turret_search", (byte *)turret_search},
+{"turret_sight", (byte *)turret_sight},
+{"TurretAim", (byte *)TurretAim},
+{"SP_monster_tank", (byte *)SP_monster_tank},
+{"tank_die", (byte *)tank_die},
+{"tank_dead", (byte *)tank_dead},
+{"tank_attack", (byte *)tank_attack},
+{"tank_doattack_rocket", (byte *)tank_doattack_rocket},
+{"tank_refire_rocket", (byte *)tank_refire_rocket},
+{"tank_poststrike", (byte *)tank_poststrike},
+{"tank_reattack_blaster", (byte *)tank_reattack_blaster},
+{"TankMachineGun", (byte *)TankMachineGun},
+{"TankRocket", (byte *)TankRocket},
+{"TankStrike", (byte *)TankStrike},
+{"TankBlaster", (byte *)TankBlaster},
+{"tank_pain", (byte *)tank_pain},
+{"tank_run", (byte *)tank_run},
+{"tank_walk", (byte *)tank_walk},
+{"tank_stand", (byte *)tank_stand},
+{"tank_idle", (byte *)tank_idle},
+{"tank_windup", (byte *)tank_windup},
+{"tank_thud", (byte *)tank_thud},
+{"tank_footstep", (byte *)tank_footstep},
+{"tank_sight", (byte *)tank_sight},
+{"tank_blocked", (byte *)tank_blocked},
+{"SP_monster_supertank", (byte *)SP_monster_supertank},
+{"supertank_blocked", (byte *)supertank_blocked},
+{"supertank_die", (byte *)supertank_die},
+{"BossExplode", (byte *)BossExplode},
+{"supertank_dead", (byte *)supertank_dead},
+{"supertank_attack", (byte *)supertank_attack},
+{"supertankMachineGun", (byte *)supertankMachineGun},
+{"supertankRocket", (byte *)supertankRocket},
+{"supertank_pain", (byte *)supertank_pain},
+{"supertank_reattack1", (byte *)supertank_reattack1},
+{"supertank_run", (byte *)supertank_run},
+{"supertank_walk", (byte *)supertank_walk},
+{"supertank_forward", (byte *)supertank_forward},
+{"supertank_stand", (byte *)supertank_stand},
+{"supertank_search", (byte *)supertank_search},
+{"TreadSound", (byte *)TreadSound},
+{"SP_monster_stalker", (byte *)SP_monster_stalker},
+{"stalker_die", (byte *)stalker_die},
+{"stalker_dead", (byte *)stalker_dead},
+{"stalker_blocked", (byte *)stalker_blocked},
+{"stalker_jump", (byte *)stalker_jump},
+{"stalker_jump_wait_land", (byte *)stalker_jump_wait_land},
+{"stalker_jump_up", (byte *)stalker_jump_up},
+{"stalker_jump_down", (byte *)stalker_jump_down},
+{"stalker_dodge", (byte *)stalker_dodge},
+{"stalker_dodge_jump", (byte *)stalker_dodge_jump},
+{"stalker_jump_straightup", (byte *)stalker_jump_straightup},
+{"stalker_do_pounce", (byte *)stalker_do_pounce},
+{"stalker_check_lz", (byte *)stalker_check_lz},
+{"calcJumpAngle", (byte *)calcJumpAngle},
+{"stalker_attack_melee", (byte *)stalker_attack_melee},
+{"stalker_swing_attack", (byte *)stalker_swing_attack},
+{"stalker_attack_ranged", (byte *)stalker_attack_ranged},
+{"stalker_shoot_attack2", (byte *)stalker_shoot_attack2},
+{"stalker_shoot_attack", (byte *)stalker_shoot_attack},
+{"stalker_pain", (byte *)stalker_pain},
+{"stalker_false_death_start", (byte *)stalker_false_death_start},
+{"stalker_false_death", (byte *)stalker_false_death},
+{"stalker_heal", (byte *)stalker_heal},
+{"stalker_reactivate", (byte *)stalker_reactivate},
+{"stalker_walk", (byte *)stalker_walk},
+{"stalker_run", (byte *)stalker_run},
+{"stalker_stand", (byte *)stalker_stand},
+{"stalker_idle", (byte *)stalker_idle},
+{"stalker_idle_noise", (byte *)stalker_idle_noise},
+{"stalker_sight", (byte *)stalker_sight},
+{"stalker_ok_to_transition", (byte *)stalker_ok_to_transition},
+{"SP_monster_soldier_ss", (byte *)SP_monster_soldier_ss},
+{"SP_monster_soldier", (byte *)SP_monster_soldier},
+{"SP_monster_soldier_light", (byte *)SP_monster_soldier_light},
+{"SP_monster_soldier_x", (byte *)SP_monster_soldier_x},
+{"soldier_blind", (byte *)soldier_blind},
+{"soldier_duck", (byte *)soldier_duck},
+{"soldier_sidestep", (byte *)soldier_sidestep},
+{"soldier_die", (byte *)soldier_die},
+{"soldier_dead2", (byte *)soldier_dead2},
+{"soldier_dead", (byte *)soldier_dead},
+{"soldier_fire7", (byte *)soldier_fire7},
+{"soldier_fire6", (byte *)soldier_fire6},
+{"soldier_blocked", (byte *)soldier_blocked},
+{"soldier_sight", (byte *)soldier_sight},
+{"soldier_attack", (byte *)soldier_attack},
+{"soldier_attack6_refire", (byte *)soldier_attack6_refire},
+{"soldier_fire8", (byte *)soldier_fire8},
+{"soldier_fire4", (byte *)soldier_fire4},
+{"soldier_attack3_refire", (byte *)soldier_attack3_refire},
+{"soldier_fire3", (byte *)soldier_fire3},
+{"soldier_attack2_refire2", (byte *)soldier_attack2_refire2},
+{"soldier_attack2_refire1", (byte *)soldier_attack2_refire1},
+{"soldier_fire2", (byte *)soldier_fire2},
+{"soldier_attack1_refire2", (byte *)soldier_attack1_refire2},
+{"soldier_attack1_refire1", (byte *)soldier_attack1_refire1},
+{"soldier_fire1", (byte *)soldier_fire1},
+{"soldier_fire", (byte *)soldier_fire},
+{"soldier_pain", (byte *)soldier_pain},
+{"soldier_run", (byte *)soldier_run},
+{"soldier_fire_run", (byte *)soldier_fire_run},
+{"soldier_walk", (byte *)soldier_walk},
+{"soldier_walk1_random", (byte *)soldier_walk1_random},
+{"soldier_stand", (byte *)soldier_stand},
+{"soldier_cock", (byte *)soldier_cock},
+{"soldier_idle", (byte *)soldier_idle},
+{"soldier_stop_charge", (byte *)soldier_stop_charge},
+{"soldier_start_charge", (byte *)soldier_start_charge},
+{"SP_monster_parasite", (byte *)SP_monster_parasite},
+{"parasite_die", (byte *)parasite_die},
+{"parasite_dead", (byte *)parasite_dead},
+{"parasite_checkattack", (byte *)parasite_checkattack},
+{"parasite_blocked", (byte *)parasite_blocked},
+{"parasite_jump", (byte *)parasite_jump},
+{"parasite_jump_wait_land", (byte *)parasite_jump_wait_land},
+{"parasite_jump_up", (byte *)parasite_jump_up},
+{"parasite_jump_down", (byte *)parasite_jump_down},
+{"parasite_attack", (byte *)parasite_attack},
+{"parasite_drain_attack", (byte *)parasite_drain_attack},
+{"parasite_drain_attack_ok", (byte *)parasite_drain_attack_ok},
+{"parasite_pain", (byte *)parasite_pain},
+{"parasite_walk", (byte *)parasite_walk},
+{"parasite_start_walk", (byte *)parasite_start_walk},
+{"parasite_run", (byte *)parasite_run},
+{"parasite_start_run", (byte *)parasite_start_run},
+{"parasite_stand", (byte *)parasite_stand},
+{"parasite_idle", (byte *)parasite_idle},
+{"parasite_refidget", (byte *)parasite_refidget},
+{"parasite_do_fidget", (byte *)parasite_do_fidget},
+{"parasite_end_fidget", (byte *)parasite_end_fidget},
+{"parasite_search", (byte *)parasite_search},
+{"parasite_scratch", (byte *)parasite_scratch},
+{"parasite_tap", (byte *)parasite_tap},
+{"parasite_sight", (byte *)parasite_sight},
+{"parasite_reel_in", (byte *)parasite_reel_in},
+{"parasite_launch", (byte *)parasite_launch},
+{"SP_monster_mutant", (byte *)SP_monster_mutant},
+{"mutant_blocked", (byte *)mutant_blocked},
+{"mutant_die", (byte *)mutant_die},
+{"mutant_dead", (byte *)mutant_dead},
+{"mutant_pain", (byte *)mutant_pain},
+{"mutant_checkattack", (byte *)mutant_checkattack},
+{"mutant_check_jump", (byte *)mutant_check_jump},
+{"mutant_check_melee", (byte *)mutant_check_melee},
+{"mutant_jump", (byte *)mutant_jump},
+{"mutant_check_landing", (byte *)mutant_check_landing},
+{"mutant_jump_takeoff", (byte *)mutant_jump_takeoff},
+{"mutant_jump_touch", (byte *)mutant_jump_touch},
+{"mutant_melee", (byte *)mutant_melee},
+{"mutant_check_refire", (byte *)mutant_check_refire},
+{"mutant_hit_right", (byte *)mutant_hit_right},
+{"mutant_hit_left", (byte *)mutant_hit_left},
+{"mutant_run", (byte *)mutant_run},
+{"mutant_walk", (byte *)mutant_walk},
+{"mutant_walk_loop", (byte *)mutant_walk_loop},
+{"mutant_idle", (byte *)mutant_idle},
+{"mutant_idle_loop", (byte *)mutant_idle_loop},
+{"mutant_stand", (byte *)mutant_stand},
+{"mutant_swing", (byte *)mutant_swing},
+{"mutant_search", (byte *)mutant_search},
+{"mutant_sight", (byte *)mutant_sight},
+{"mutant_step", (byte *)mutant_step},
+{"M_walkmove", (byte *)M_walkmove},
+{"M_MoveToGoal", (byte *)M_MoveToGoal},
+{"SV_CloseEnough", (byte *)SV_CloseEnough},
+{"SV_NewChaseDir", (byte *)SV_NewChaseDir},
+{"SV_FixCheckBottom", (byte *)SV_FixCheckBottom},
+{"SV_StepDirection", (byte *)SV_StepDirection},
+{"M_ChangeYaw", (byte *)M_ChangeYaw},
+{"SV_movestep", (byte *)SV_movestep},
+{"IsBadAhead", (byte *)IsBadAhead},
+{"M_CheckBottom", (byte *)M_CheckBottom},
+{"SP_monster_medic", (byte *)SP_monster_medic},
+{"medic_blocked", (byte *)medic_blocked},
+{"medic_sidestep", (byte *)medic_sidestep},
+{"medic_duck", (byte *)medic_duck},
+{"medic_checkattack", (byte *)medic_checkattack},
+{"medic_attack", (byte *)medic_attack},
+{"medic_finish_spawn", (byte *)medic_finish_spawn},
+{"medic_spawngrows", (byte *)medic_spawngrows},
+{"medic_determine_spawn", (byte *)medic_determine_spawn},
+{"medic_start_spawn", (byte *)medic_start_spawn},
+{"medic_hook_retract", (byte *)medic_hook_retract},
+{"medic_cable_attack", (byte *)medic_cable_attack},
+{"medic_hook_launch", (byte *)medic_hook_launch},
+{"medic_continue", (byte *)medic_continue},
+{"medic_die", (byte *)medic_die},
+{"medic_dead", (byte *)medic_dead},
+{"medic_fire_blaster", (byte *)medic_fire_blaster},
+{"medic_pain", (byte *)medic_pain},
+{"medic_run", (byte *)medic_run},
+{"medic_walk", (byte *)medic_walk},
+{"medic_stand", (byte *)medic_stand},
+{"medic_sight", (byte *)medic_sight},
+{"medic_search", (byte *)medic_search},
+{"medic_idle", (byte *)medic_idle},
+{"cleanupHeal", (byte *)cleanupHeal},
+{"abortHeal", (byte *)abortHeal},
+{"canReach", (byte *)canReach},
+{"medic_FindDeadMonster", (byte *)medic_FindDeadMonster},
+{"SP_misc_insane", (byte *)SP_misc_insane},
+{"insane_die", (byte *)insane_die},
+{"insane_dead", (byte *)insane_dead},
+{"insane_stand", (byte *)insane_stand},
+{"insane_checkup", (byte *)insane_checkup},
+{"insane_checkdown", (byte *)insane_checkdown},
+{"insane_onground", (byte *)insane_onground},
+{"insane_pain", (byte *)insane_pain},
+{"insane_run", (byte *)insane_run},
+{"insane_walk", (byte *)insane_walk},
+{"insane_cross", (byte *)insane_cross},
+{"insane_scream", (byte *)insane_scream},
+{"insane_moan", (byte *)insane_moan},
+{"insane_shake", (byte *)insane_shake},
+{"insane_fist", (byte *)insane_fist},
+{"SP_monster_infantry", (byte *)SP_monster_infantry},
+{"infantry_sidestep", (byte *)infantry_sidestep},
+{"infantry_duck", (byte *)infantry_duck},
+{"infantry_blocked", (byte *)infantry_blocked},
+{"infantry_jump", (byte *)infantry_jump},
+{"infantry_jump_wait_land", (byte *)infantry_jump_wait_land},
+{"infantry_jump2_now", (byte *)infantry_jump2_now},
+{"infantry_jump_now", (byte *)infantry_jump_now},
+{"infantry_attack", (byte *)infantry_attack},
+{"infantry_smack", (byte *)infantry_smack},
+{"infantry_swing", (byte *)infantry_swing},
+{"infantry_fire_prep", (byte *)infantry_fire_prep},
+{"infantry_fire", (byte *)infantry_fire},
+{"infantry_cock_gun", (byte *)infantry_cock_gun},
+{"infantry_die", (byte *)infantry_die},
+{"infantry_dead", (byte *)infantry_dead},
+{"infantry_sight", (byte *)infantry_sight},
+{"InfantryMachineGun", (byte *)InfantryMachineGun},
+{"infantry_pain", (byte *)infantry_pain},
+{"infantry_run", (byte *)infantry_run},
+{"infantry_walk", (byte *)infantry_walk},
+{"infantry_fidget", (byte *)infantry_fidget},
+{"infantry_stand", (byte *)infantry_stand},
+{"SP_monster_hover", (byte *)SP_monster_hover},
+{"hover_blocked", (byte *)hover_blocked},
+{"hover_die", (byte *)hover_die},
+{"hover_dead", (byte *)hover_dead},
+{"hover_deadthink", (byte *)hover_deadthink},
+{"hover_pain", (byte *)hover_pain},
+{"hover_attack", (byte *)hover_attack},
+{"hover_start_attack", (byte *)hover_start_attack},
+{"hover_walk", (byte *)hover_walk},
+{"hover_run", (byte *)hover_run},
+{"hover_stand", (byte *)hover_stand},
+{"hover_fire_blaster", (byte *)hover_fire_blaster},
+{"hover_reattack", (byte *)hover_reattack},
+{"hover_search", (byte *)hover_search},
+{"hover_sight", (byte *)hover_sight},
+{"SP_monster_gunner", (byte *)SP_monster_gunner},
+{"gunner_sidestep", (byte *)gunner_sidestep},
+{"gunner_duck", (byte *)gunner_duck},
+{"gunner_blocked", (byte *)gunner_blocked},
+{"gunner_jump", (byte *)gunner_jump},
+{"gunner_jump_wait_land", (byte *)gunner_jump_wait_land},
+{"gunner_jump2_now", (byte *)gunner_jump2_now},
+{"gunner_jump_now", (byte *)gunner_jump_now},
+{"gunner_refire_chain", (byte *)gunner_refire_chain},
+{"gunner_fire_chain", (byte *)gunner_fire_chain},
+{"gunner_attack", (byte *)gunner_attack},
+{"gunner_blind_check", (byte *)gunner_blind_check},
+{"GunnerGrenade", (byte *)GunnerGrenade},
+{"gunner_grenade_check", (byte *)gunner_grenade_check},
+{"GunnerFire", (byte *)GunnerFire},
+{"gunner_opengun", (byte *)gunner_opengun},
+{"gunner_duck_down", (byte *)gunner_duck_down},
+{"gunner_die", (byte *)gunner_die},
+{"gunner_dead", (byte *)gunner_dead},
+{"gunner_pain", (byte *)gunner_pain},
+{"gunner_runandshoot", (byte *)gunner_runandshoot},
+{"gunner_run", (byte *)gunner_run},
+{"gunner_walk", (byte *)gunner_walk},
+{"gunner_stand", (byte *)gunner_stand},
+{"gunner_fidget", (byte *)gunner_fidget},
+{"gunner_search", (byte *)gunner_search},
+{"gunner_sight", (byte *)gunner_sight},
+{"gunner_idlesound", (byte *)gunner_idlesound},
+{"SP_monster_gladiator", (byte *)SP_monster_gladiator},
+{"gladiator_blocked", (byte *)gladiator_blocked},
+{"gladiator_die", (byte *)gladiator_die},
+{"gladiator_dead", (byte *)gladiator_dead},
+{"gladiator_pain", (byte *)gladiator_pain},
+{"gladiator_attack", (byte *)gladiator_attack},
+{"GladiatorGun", (byte *)GladiatorGun},
+{"gladiator_melee", (byte *)gladiator_melee},
+{"GaldiatorMelee", (byte *)GaldiatorMelee},
+{"gladiator_run", (byte *)gladiator_run},
+{"gladiator_walk", (byte *)gladiator_walk},
+{"gladiator_stand", (byte *)gladiator_stand},
+{"gladiator_cleaver_swing", (byte *)gladiator_cleaver_swing},
+{"gladiator_search", (byte *)gladiator_search},
+{"gladiator_sight", (byte *)gladiator_sight},
+{"gladiator_idle", (byte *)gladiator_idle},
+{"SP_monster_kamikaze", (byte *)SP_monster_kamikaze},
+{"SP_monster_flyer", (byte *)SP_monster_flyer},
+{"flyer_blocked", (byte *)flyer_blocked},
+{"flyer_die", (byte *)flyer_die},
+{"flyer_pain", (byte *)flyer_pain},
+{"flyer_check_melee", (byte *)flyer_check_melee},
+{"flyer_melee", (byte *)flyer_melee},
+{"flyer_nextmove", (byte *)flyer_nextmove},
+{"flyer_setstart", (byte *)flyer_setstart},
+{"flyer_attack", (byte *)flyer_attack},
+{"flyer_loop_melee", (byte *)flyer_loop_melee},
+{"flyer_slash_right", (byte *)flyer_slash_right},
+{"flyer_slash_left", (byte *)flyer_slash_left},
+{"flyer_fireright", (byte *)flyer_fireright},
+{"flyer_fireleft", (byte *)flyer_fireleft},
+{"flyer_fire", (byte *)flyer_fire},
+{"flyer_start", (byte *)flyer_start},
+{"flyer_stop", (byte *)flyer_stop},
+{"flyer_kamikaze_check", (byte *)flyer_kamikaze_check},
+{"flyer_kamikaze", (byte *)flyer_kamikaze},
+{"flyer_kamikaze_explode", (byte *)flyer_kamikaze_explode},
+{"flyer_stand", (byte *)flyer_stand},
+{"flyer_walk", (byte *)flyer_walk},
+{"flyer_run", (byte *)flyer_run},
+{"flyer_pop_blades", (byte *)flyer_pop_blades},
+{"flyer_idle", (byte *)flyer_idle},
+{"flyer_sight", (byte *)flyer_sight},
+{"SP_monster_floater", (byte *)SP_monster_floater},
+{"floater_blocked", (byte *)floater_blocked},
+{"floater_die", (byte *)floater_die},
+{"floater_dead", (byte *)floater_dead},
+{"floater_pain", (byte *)floater_pain},
+{"floater_melee", (byte *)floater_melee},
+{"floater_attack", (byte *)floater_attack},
+{"floater_zap", (byte *)floater_zap},
+{"floater_wham", (byte *)floater_wham},
+{"floater_walk", (byte *)floater_walk},
+{"floater_run", (byte *)floater_run},
+{"floater_stand", (byte *)floater_stand},
+{"floater_fire_blaster", (byte *)floater_fire_blaster},
+{"floater_idle", (byte *)floater_idle},
+{"floater_sight", (byte *)floater_sight},
+{"SP_monster_flipper", (byte *)SP_monster_flipper},
+{"flipper_die", (byte *)flipper_die},
+{"flipper_sight", (byte *)flipper_sight},
+{"flipper_dead", (byte *)flipper_dead},
+{"flipper_pain", (byte *)flipper_pain},
+{"flipper_melee", (byte *)flipper_melee},
+{"flipper_preattack", (byte *)flipper_preattack},
+{"flipper_bite", (byte *)flipper_bite},
+{"flipper_start_run", (byte *)flipper_start_run},
+{"flipper_walk", (byte *)flipper_walk},
+{"flipper_run", (byte *)flipper_run},
+{"flipper_run_loop", (byte *)flipper_run_loop},
+{"flipper_stand", (byte *)flipper_stand},
+{"SP_monster_chick", (byte *)SP_monster_chick},
+{"chick_sidestep", (byte *)chick_sidestep},
+{"chick_duck", (byte *)chick_duck},
+{"chick_blocked", (byte *)chick_blocked},
+{"chick_sight", (byte *)chick_sight},
+{"chick_attack", (byte *)chick_attack},
+{"chick_melee", (byte *)chick_melee},
+{"chick_slash", (byte *)chick_slash},
+{"chick_reslash", (byte *)chick_reslash},
+{"chick_attack1", (byte *)chick_attack1},
+{"chick_rerocket", (byte *)chick_rerocket},
+{"ChickReload", (byte *)ChickReload},
+{"Chick_PreAttack1", (byte *)Chick_PreAttack1},
+{"ChickRocket", (byte *)ChickRocket},
+{"ChickSlash", (byte *)ChickSlash},
+{"chick_die", (byte *)chick_die},
+{"chick_dead", (byte *)chick_dead},
+{"chick_pain", (byte *)chick_pain},
+{"chick_run", (byte *)chick_run},
+{"chick_walk", (byte *)chick_walk},
+{"chick_stand", (byte *)chick_stand},
+{"chick_fidget", (byte *)chick_fidget},
+{"ChickMoan", (byte *)ChickMoan},
+{"SP_monster_carrier", (byte *)SP_monster_carrier},
+{"CarrierPrecache", (byte *)CarrierPrecache},
+{"Carrier_CheckAttack", (byte *)Carrier_CheckAttack},
+{"carrier_die", (byte *)carrier_die},
+{"carrier_dead", (byte *)carrier_dead},
+{"carrier_pain", (byte *)carrier_pain},
+{"carrier_reattack_gren", (byte *)carrier_reattack_gren},
+{"carrier_attack_gren", (byte *)carrier_attack_gren},
+{"carrier_reattack_mg", (byte *)carrier_reattack_mg},
+{"carrier_attack_mg", (byte *)carrier_attack_mg},
+{"carrier_attack", (byte *)carrier_attack},
+{"CarrierMachineGunHold", (byte *)CarrierMachineGunHold},
+{"carrier_walk", (byte *)carrier_walk},
+{"carrier_run", (byte *)carrier_run},
+{"carrier_stand", (byte *)carrier_stand},
+{"CarrierSaveLoc", (byte *)CarrierSaveLoc},
+{"CarrierRail", (byte *)CarrierRail},
+{"carrier_start_spawn", (byte *)carrier_start_spawn},
+{"carrier_ready_spawn", (byte *)carrier_ready_spawn},
+{"carrier_spawn_check", (byte *)carrier_spawn_check},
+{"carrier_prep_spawn", (byte *)carrier_prep_spawn},
+{"CarrierSpawn", (byte *)CarrierSpawn},
+{"CarrierMachineGun", (byte *)CarrierMachineGun},
+{"carrier_firebullet_left", (byte *)carrier_firebullet_left},
+{"carrier_firebullet_right", (byte *)carrier_firebullet_right},
+{"CarrierRocket", (byte *)CarrierRocket},
+{"CarrierPredictiveRocket", (byte *)CarrierPredictiveRocket},
+{"CarrierGrenade", (byte *)CarrierGrenade},
+{"CarrierCoopCheck", (byte *)CarrierCoopCheck},
+{"carrier_sight", (byte *)carrier_sight},
+{"SP_monster_brain", (byte *)SP_monster_brain},
+{"brain_duck", (byte *)brain_duck},
+{"brain_die", (byte *)brain_die},
+{"brain_dead", (byte *)brain_dead},
+{"brain_pain", (byte *)brain_pain},
+{"brain_run", (byte *)brain_run},
+{"brain_melee", (byte *)brain_melee},
+{"brain_chest_closed", (byte *)brain_chest_closed},
+{"brain_tentacle_attack", (byte *)brain_tentacle_attack},
+{"brain_chest_open", (byte *)brain_chest_open},
+{"brain_hit_left", (byte *)brain_hit_left},
+{"brain_swing_left", (byte *)brain_swing_left},
+{"brain_hit_right", (byte *)brain_hit_right},
+{"brain_swing_right", (byte *)brain_swing_right},
+{"brain_walk", (byte *)brain_walk},
+{"brain_idle", (byte *)brain_idle},
+{"brain_stand", (byte *)brain_stand},
+{"brain_search", (byte *)brain_search},
+{"brain_sight", (byte *)brain_sight},
+{"MakronToss", (byte *)MakronToss},
+{"MakronSpawn", (byte *)MakronSpawn},
+{"SP_monster_makron", (byte *)SP_monster_makron},
+{"MakronPrecache", (byte *)MakronPrecache},
+{"Makron_CheckAttack", (byte *)Makron_CheckAttack},
+{"makron_die", (byte *)makron_die},
+{"makron_torso_die", (byte *)makron_torso_die},
+{"makron_dead", (byte *)makron_dead},
+{"makron_torso", (byte *)makron_torso},
+{"makron_torso_think", (byte *)makron_torso_think},
+{"makron_attack", (byte *)makron_attack},
+{"makron_sight", (byte *)makron_sight},
+{"makron_pain", (byte *)makron_pain},
+{"MakronHyperblaster", (byte *)MakronHyperblaster},
+{"MakronRailgun", (byte *)MakronRailgun},
+{"MakronSaveloc", (byte *)MakronSaveloc},
+{"makronBFG", (byte *)makronBFG},
+{"makron_run", (byte *)makron_run},
+{"makron_walk", (byte *)makron_walk},
+{"makron_prerailgun", (byte *)makron_prerailgun},
+{"makron_brainsplorch", (byte *)makron_brainsplorch},
+{"makron_step_right", (byte *)makron_step_right},
+{"makron_step_left", (byte *)makron_step_left},
+{"makron_popup", (byte *)makron_popup},
+{"makron_hit", (byte *)makron_hit},
+{"makron_stand", (byte *)makron_stand},
+{"makron_taunt", (byte *)makron_taunt},
+{"SP_monster_jorg", (byte *)SP_monster_jorg},
+{"Jorg_CheckAttack", (byte *)Jorg_CheckAttack},
+{"jorg_die", (byte *)jorg_die},
+{"jorg_dead", (byte *)jorg_dead},
+{"jorg_attack", (byte *)jorg_attack},
+{"jorg_firebullet", (byte *)jorg_firebullet},
+{"jorg_firebullet_left", (byte *)jorg_firebullet_left},
+{"jorg_firebullet_right", (byte *)jorg_firebullet_right},
+{"jorgBFG", (byte *)jorgBFG},
+{"jorg_pain", (byte *)jorg_pain},
+{"jorg_attack1", (byte *)jorg_attack1},
+{"jorg_reattack1", (byte *)jorg_reattack1},
+{"jorg_run", (byte *)jorg_run},
+{"jorg_walk", (byte *)jorg_walk},
+{"jorg_stand", (byte *)jorg_stand},
+{"jorg_step_right", (byte *)jorg_step_right},
+{"jorg_step_left", (byte *)jorg_step_left},
+{"jorg_death_hit", (byte *)jorg_death_hit},
+{"jorg_idle", (byte *)jorg_idle},
+{"jorg_search", (byte *)jorg_search},
+{"SP_monster_boss3_stand", (byte *)SP_monster_boss3_stand},
+{"Think_Boss3Stand", (byte *)Think_Boss3Stand},
+{"Use_Boss3", (byte *)Use_Boss3},
+{"SP_monster_boss2", (byte *)SP_monster_boss2},
+{"Boss2_CheckAttack", (byte *)Boss2_CheckAttack},
+{"boss2_die", (byte *)boss2_die},
+{"boss2_dead", (byte *)boss2_dead},
+{"boss2_pain", (byte *)boss2_pain},
+{"boss2_reattack_mg", (byte *)boss2_reattack_mg},
+{"boss2_attack_mg", (byte *)boss2_attack_mg},
+{"boss2_attack", (byte *)boss2_attack},
+{"boss2_walk", (byte *)boss2_walk},
+{"boss2_run", (byte *)boss2_run},
+{"boss2_stand", (byte *)boss2_stand},
+{"Boss2MachineGun", (byte *)Boss2MachineGun},
+{"boss2_firebullet_left", (byte *)boss2_firebullet_left},
+{"boss2_firebullet_right", (byte *)boss2_firebullet_right},
+{"Boss2Rocket", (byte *)Boss2Rocket},
+{"boss2_search", (byte *)boss2_search},
+{"SP_monster_berserk", (byte *)SP_monster_berserk},
+{"berserk_sidestep", (byte *)berserk_sidestep},
+{"berserk_blocked", (byte *)berserk_blocked},
+{"berserk_die", (byte *)berserk_die},
+{"berserk_dead", (byte *)berserk_dead},
+{"berserk_pain", (byte *)berserk_pain},
+{"berserk_melee", (byte *)berserk_melee},
+{"berserk_strike", (byte *)berserk_strike},
+{"berserk_attack_club", (byte *)berserk_attack_club},
+{"berserk_swing", (byte *)berserk_swing},
+{"berserk_attack_spike", (byte *)berserk_attack_spike},
+{"berserk_run", (byte *)berserk_run},
+{"berserk_walk", (byte *)berserk_walk},
+{"berserk_fidget", (byte *)berserk_fidget},
+{"berserk_stand", (byte *)berserk_stand},
+{"berserk_search", (byte *)berserk_search},
+{"berserk_sight", (byte *)berserk_sight},
+{"fire_bfg", (byte *)fire_bfg},
+{"bfg_think", (byte *)bfg_think},
+{"bfg_touch", (byte *)bfg_touch},
+{"bfg_explode", (byte *)bfg_explode},
+{"fire_rail", (byte *)fire_rail},
+{"fire_rocket", (byte *)fire_rocket},
+{"rocket_touch", (byte *)rocket_touch},
+{"fire_grenade2", (byte *)fire_grenade2},
+{"fire_grenade", (byte *)fire_grenade},
+{"Grenade_Touch", (byte *)Grenade_Touch},
+{"Grenade_Explode", (byte *)Grenade_Explode},
+{"fire_blaster", (byte *)fire_blaster},
+{"blaster_touch", (byte *)blaster_touch},
+{"fire_shotgun", (byte *)fire_shotgun},
+{"fire_bullet", (byte *)fire_bullet},
+{"fire_lead", (byte *)fire_lead},
+{"fire_hit", (byte *)fire_hit},
+{"check_dodge", (byte *)check_dodge},
+{"KillBox", (byte *)KillBox},
+{"G_TouchSolids", (byte *)G_TouchSolids},
+{"G_TouchTriggers", (byte *)G_TouchTriggers},
+{"G_FreeEdict", (byte *)G_FreeEdict},
+{"G_Spawn", (byte *)G_Spawn},
+{"G_InitEdict", (byte *)G_InitEdict},
+{"G_CopyString", (byte *)G_CopyString},
+{"vectoangles2", (byte *)vectoangles2},
+{"vectoangles", (byte *)vectoangles},
+{"vectoyaw2", (byte *)vectoyaw2},
+{"vectoyaw", (byte *)vectoyaw},
+{"G_SetMovedir", (byte *)G_SetMovedir},
+{"vtos", (byte *)vtos},
+{"tv", (byte *)tv},
+{"G_UseTargets", (byte *)G_UseTargets},
+{"Think_Delay", (byte *)Think_Delay},
+{"G_PickTarget", (byte *)G_PickTarget},
+{"findradius2", (byte *)findradius2},
+{"findradius", (byte *)findradius},
+{"G_Find", (byte *)G_Find},
+{"G_ProjectSource2", (byte *)G_ProjectSource2},
+{"G_ProjectSource", (byte *)G_ProjectSource},
+{"SP_turret_invisible_brain", (byte *)SP_turret_invisible_brain},
+{"turret_brain_activate", (byte *)turret_brain_activate},
+{"turret_brain_deactivate", (byte *)turret_brain_deactivate},
+{"turret_brain_link", (byte *)turret_brain_link},
+{"turret_brain_think", (byte *)turret_brain_think},
+{"SP_turret_driver", (byte *)SP_turret_driver},
+{"turret_driver_link", (byte *)turret_driver_link},
+{"turret_driver_think", (byte *)turret_driver_think},
+{"turret_driver_die", (byte *)turret_driver_die},
+{"SP_turret_base", (byte *)SP_turret_base},
+{"SP_turret_breach", (byte *)SP_turret_breach},
+{"turret_breach_finish_init", (byte *)turret_breach_finish_init},
+{"turret_breach_think", (byte *)turret_breach_think},
+{"turret_breach_fire", (byte *)turret_breach_fire},
+{"turret_blocked", (byte *)turret_blocked},
+{"SnapToEights", (byte *)SnapToEights},
+{"AnglesNormalize", (byte *)AnglesNormalize},
+{"SP_trigger_monsterjump", (byte *)SP_trigger_monsterjump},
+{"trigger_monsterjump_touch", (byte *)trigger_monsterjump_touch},
+{"SP_trigger_gravity", (byte *)SP_trigger_gravity},
+{"trigger_gravity_touch", (byte *)trigger_gravity_touch},
+{"trigger_gravity_use", (byte *)trigger_gravity_use},
+{"SP_trigger_hurt", (byte *)SP_trigger_hurt},
+{"hurt_touch", (byte *)hurt_touch},
+{"hurt_use", (byte *)hurt_use},
+{"SP_trigger_push", (byte *)SP_trigger_push},
+{"trigger_push_use", (byte *)trigger_push_use},
+{"trigger_push_touch", (byte *)trigger_push_touch},
+{"SP_trigger_always", (byte *)SP_trigger_always},
+{"SP_trigger_counter", (byte *)SP_trigger_counter},
+{"trigger_counter_use", (byte *)trigger_counter_use},
+{"SP_trigger_key", (byte *)SP_trigger_key},
+{"trigger_key_use", (byte *)trigger_key_use},
+{"SP_trigger_relay", (byte *)SP_trigger_relay},
+{"trigger_relay_use", (byte *)trigger_relay_use},
+{"SP_trigger_once", (byte *)SP_trigger_once},
+{"SP_trigger_multiple", (byte *)SP_trigger_multiple},
+{"trigger_enable", (byte *)trigger_enable},
+{"Touch_Multi", (byte *)Touch_Multi},
+{"Use_Multi", (byte *)Use_Multi},
+{"multi_trigger", (byte *)multi_trigger},
+{"multi_wait", (byte *)multi_wait},
+{"InitTrigger", (byte *)InitTrigger},
+{"SP_target_earthquake", (byte *)SP_target_earthquake},
+{"target_earthquake_use", (byte *)target_earthquake_use},
+{"target_earthquake_think", (byte *)target_earthquake_think},
+{"SP_target_lightramp", (byte *)SP_target_lightramp},
+{"target_lightramp_use", (byte *)target_lightramp_use},
+{"target_lightramp_think", (byte *)target_lightramp_think},
+{"SP_target_laser", (byte *)SP_target_laser},
+{"target_laser_start", (byte *)target_laser_start},
+{"target_laser_use", (byte *)target_laser_use},
+{"target_laser_off", (byte *)target_laser_off},
+{"target_laser_on", (byte *)target_laser_on},
+{"target_laser_think", (byte *)target_laser_think},
+{"SP_target_crosslevel_target", (byte *)SP_target_crosslevel_target},
+{"target_crosslevel_target_think", (byte *)target_crosslevel_target_think},
+{"SP_target_crosslevel_trigger", (byte *)SP_target_crosslevel_trigger},
+{"trigger_crosslevel_trigger_use", (byte *)trigger_crosslevel_trigger_use},
+{"SP_target_blaster", (byte *)SP_target_blaster},
+{"use_target_blaster", (byte *)use_target_blaster},
+{"SP_target_spawner", (byte *)SP_target_spawner},
+{"use_target_spawner", (byte *)use_target_spawner},
+{"SP_target_splash", (byte *)SP_target_splash},
+{"use_target_splash", (byte *)use_target_splash},
+{"SP_target_changelevel", (byte *)SP_target_changelevel},
+{"use_target_changelevel", (byte *)use_target_changelevel},
+{"SP_target_explosion", (byte *)SP_target_explosion},
+{"use_target_explosion", (byte *)use_target_explosion},
+{"target_explosion_explode", (byte *)target_explosion_explode},
+{"SP_target_goal", (byte *)SP_target_goal},
+{"use_target_goal", (byte *)use_target_goal},
+{"SP_target_secret", (byte *)SP_target_secret},
+{"use_target_secret", (byte *)use_target_secret},
+{"SP_target_help", (byte *)SP_target_help},
+{"Use_Target_Help", (byte *)Use_Target_Help},
+{"SP_target_speaker", (byte *)SP_target_speaker},
+{"Use_Target_Speaker", (byte *)Use_Target_Speaker},
+{"SP_target_temp_entity", (byte *)SP_target_temp_entity},
+{"Use_Target_Tent", (byte *)Use_Target_Tent},
+{"ServerCommand", (byte *)ServerCommand},
+{"SVCmd_WriteIP_f", (byte *)SVCmd_WriteIP_f},
+{"SVCmd_ListIP_f", (byte *)SVCmd_ListIP_f},
+{"SVCmd_RemoveIP_f", (byte *)SVCmd_RemoveIP_f},
+{"SVCmd_AddIP_f", (byte *)SVCmd_AddIP_f},
+{"SV_FilterPacket", (byte *)SV_FilterPacket},
+{"Svcmd_Test_f", (byte *)Svcmd_Test_f},
+{"Vengeance_Launch", (byte *)Vengeance_Launch},
+{"Hunter_Launch", (byte *)Hunter_Launch},
+{"Defender_Launch", (byte *)Defender_Launch},
+{"Own_Sphere", (byte *)Own_Sphere},
+{"Sphere_Spawn", (byte *)Sphere_Spawn},
+{"vengeance_think", (byte *)vengeance_think},
+{"hunter_think", (byte *)hunter_think},
+{"defender_think", (byte *)defender_think},
+{"vengeance_pain", (byte *)vengeance_pain},
+{"defender_pain", (byte *)defender_pain},
+{"hunter_pain", (byte *)hunter_pain},
+{"body_gib", (byte *)body_gib},
+{"defender_shoot", (byte *)defender_shoot},
+{"hunter_touch", (byte *)hunter_touch},
+{"vengeance_touch", (byte *)vengeance_touch},
+{"sphere_touch", (byte *)sphere_touch},
+{"sphere_fire", (byte *)sphere_fire},
+{"sphere_chase", (byte *)sphere_chase},
+{"sphere_fly", (byte *)sphere_fly},
+{"sphere_if_idle_die", (byte *)sphere_if_idle_die},
+{"sphere_explode", (byte *)sphere_explode},
+{"sphere_think_explode", (byte *)sphere_think_explode},
+{"Widowlegs_Spawn", (byte *)Widowlegs_Spawn},
+{"widowlegs_think", (byte *)widowlegs_think},
+{"SpawnGrow_Spawn", (byte *)SpawnGrow_Spawn},
+{"spawngrow_think", (byte *)spawngrow_think},
+{"DetermineBBox", (byte *)DetermineBBox},
+{"CheckGroundSpawnPoint", (byte *)CheckGroundSpawnPoint},
+{"CheckSpawnPoint", (byte *)CheckSpawnPoint},
+{"FindSpawnPoint", (byte *)FindSpawnPoint},
+{"CreateGroundMonster", (byte *)CreateGroundMonster},
+{"CreateFlyMonster", (byte *)CreateFlyMonster},
+{"CreateMonster", (byte *)CreateMonster},
+{"SP_worldspawn", (byte *)SP_worldspawn},
+{"SpawnEntities", (byte *)SpawnEntities},
+{"G_FindTeams", (byte *)G_FindTeams},
+{"G_FixTeams", (byte *)G_FixTeams},
+{"ED_ParseEdict", (byte *)ED_ParseEdict},
+{"ED_ParseField", (byte *)ED_ParseField},
+{"ED_NewString", (byte *)ED_NewString},
+{"ED_CallSpawn", (byte *)ED_CallSpawn},
+{"SV_Physics_NewToss", (byte *)SV_Physics_NewToss},
+{"G_RunEntity", (byte *)G_RunEntity},
+{"SV_Physics_Step", (byte *)SV_Physics_Step},
+{"SV_AddRotationalFriction", (byte *)SV_AddRotationalFriction},
+{"SV_Physics_Toss", (byte *)SV_Physics_Toss},
+{"SV_Physics_Noclip", (byte *)SV_Physics_Noclip},
+{"SV_Physics_None", (byte *)SV_Physics_None},
+{"SV_Physics_Pusher", (byte *)SV_Physics_Pusher},
+{"SV_Push", (byte *)SV_Push},
+{"SV_PushEntity", (byte *)SV_PushEntity},
+{"RealBoundingBox", (byte *)RealBoundingBox},
+{"SV_AddGravity", (byte *)SV_AddGravity},
+{"SV_FlyMove", (byte *)SV_FlyMove},
+{"ClipVelocity", (byte *)ClipVelocity},
+{"SV_Impact", (byte *)SV_Impact},
+{"SV_RunThink", (byte *)SV_RunThink},
+{"SV_CheckVelocity", (byte *)SV_CheckVelocity},
+{"SV_TestEntityPosition", (byte *)SV_TestEntityPosition},
+{"fire_tracker", (byte *)fire_tracker},
+{"tracker_fly", (byte *)tracker_fly},
+{"tracker_touch", (byte *)tracker_touch},
+{"tracker_explode", (byte *)tracker_explode},
+{"tracker_pain_daemon_spawn", (byte *)tracker_pain_daemon_spawn},
+{"tracker_pain_daemon_think", (byte *)tracker_pain_daemon_think},
+{"fire_blaster2", (byte *)fire_blaster2},
+{"blaster2_touch", (byte *)blaster2_touch},
+{"fire_heat", (byte *)fire_heat},
+{"fire_beams", (byte *)fire_beams},
+{"fire_tesla", (byte *)fire_tesla},
+{"tesla_lava", (byte *)tesla_lava},
+{"tesla_think", (byte *)tesla_think},
+{"tesla_activate", (byte *)tesla_activate},
+{"tesla_think_active", (byte *)tesla_think_active},
+{"tesla_zap", (byte *)tesla_zap},
+{"tesla_blow", (byte *)tesla_blow},
+{"tesla_die", (byte *)tesla_die},
+{"tesla_remove", (byte *)tesla_remove},
+{"fire_nuke", (byte *)fire_nuke},
+{"nuke_bounce", (byte *)nuke_bounce},
+{"Nuke_Think", (byte *)Nuke_Think},
+{"nuke_die", (byte *)nuke_die},
+{"Nuke_Explode", (byte *)Nuke_Explode},
+{"Nuke_Quake", (byte *)Nuke_Quake},
+{"fire_player_melee", (byte *)fire_player_melee},
+{"fire_prox", (byte *)fire_prox},
+{"prox_land", (byte *)prox_land},
+{"prox_open", (byte *)prox_open},
+{"prox_seek", (byte *)prox_seek},
+{"Prox_Field_Touch", (byte *)Prox_Field_Touch},
+{"prox_die", (byte *)prox_die},
+{"Prox_Explode", (byte *)Prox_Explode},
+{"fire_flechette", (byte *)fire_flechette},
+{"flechette_touch", (byte *)flechette_touch},
+{"SP_trigger_disguise", (byte *)SP_trigger_disguise},
+{"trigger_disguise_use", (byte *)trigger_disguise_use},
+{"trigger_disguise_touch", (byte *)trigger_disguise_touch},
+{"SP_trigger_teleport", (byte *)SP_trigger_teleport},
+{"trigger_teleport_use", (byte *)trigger_teleport_use},
+{"trigger_teleport_touch", (byte *)trigger_teleport_touch},
+{"SP_info_teleport_destination", (byte *)SP_info_teleport_destination},
+{"SP_target_orb", (byte *)SP_target_orb},
+{"orb_think", (byte *)orb_think},
+{"SP_target_blacklight", (byte *)SP_target_blacklight},
+{"blacklight_think", (byte *)blacklight_think},
+{"SP_target_killplayers", (byte *)SP_target_killplayers},
+{"target_killplayers_use", (byte *)target_killplayers_use},
+{"SP_target_anger", (byte *)SP_target_anger},
+{"target_anger_use", (byte *)target_anger_use},
+{"SP_target_steam", (byte *)SP_target_steam},
+{"target_steam_start", (byte *)target_steam_start},
+{"use_target_steam", (byte *)use_target_steam},
+{"SP_func_force_wall", (byte *)SP_func_force_wall},
+{"force_wall_use", (byte *)force_wall_use},
+{"force_wall_think", (byte *)force_wall_think},
+{"SP_func_door_secret2", (byte *)SP_func_door_secret2},
+{"secret_touch", (byte *)secret_touch},
+{"secret_blocked", (byte *)secret_blocked},
+{"fd_secret_done", (byte *)fd_secret_done},
+{"fd_secret_move6", (byte *)fd_secret_move6},
+{"fd_secret_move5", (byte *)fd_secret_move5},
+{"fd_secret_move4", (byte *)fd_secret_move4},
+{"fd_secret_move3", (byte *)fd_secret_move3},
+{"fd_secret_move2", (byte *)fd_secret_move2},
+{"fd_secret_move1", (byte *)fd_secret_move1},
+{"fd_secret_killed", (byte *)fd_secret_killed},
+{"fd_secret_use", (byte *)fd_secret_use},
+{"fire_doppleganger", (byte *)fire_doppleganger},
+{"body_think", (byte *)body_think},
+{"doppleganger_timeout", (byte *)doppleganger_timeout},
+{"doppleganger_pain", (byte *)doppleganger_pain},
+{"doppleganger_die", (byte *)doppleganger_die},
+{"PrecacheForRandomRespawn", (byte *)PrecacheForRandomRespawn},
+{"DoRandomRespawn", (byte *)DoRandomRespawn},
+{"FindSubstituteItem", (byte *)FindSubstituteItem},
+{"InitGameRules", (byte *)InitGameRules},
+{"monster_jump_finished", (byte *)monster_jump_finished},
+{"monster_jump_start", (byte *)monster_jump_start},
+{"CountPlayers", (byte *)CountPlayers},
+{"PickCoopTarget", (byte *)PickCoopTarget},
+{"TargetTesla", (byte *)TargetTesla},
+{"has_valid_enemy", (byte *)has_valid_enemy},
+{"monster_duck_up", (byte *)monster_duck_up},
+{"monster_duck_hold", (byte *)monster_duck_hold},
+{"monster_duck_down", (byte *)monster_duck_down},
+{"M_MonsterDodge", (byte *)M_MonsterDodge},
+{"drawbbox", (byte *)drawbbox},
+{"below", (byte *)below},
+{"PredictAim", (byte *)PredictAim},
+{"MarkTeslaArea", (byte *)MarkTeslaArea},
+{"CheckForBadArea", (byte *)CheckForBadArea},
+{"SpawnBadArea", (byte *)SpawnBadArea},
+{"badarea_touch", (byte *)badarea_touch},
+{"face_wall", (byte *)face_wall},
+{"realrange", (byte *)realrange},
+{"inback", (byte *)inback},
+{"InitHintPaths", (byte *)InitHintPaths},
+{"SP_hint_path", (byte *)SP_hint_path},
+{"hint_path_touch", (byte *)hint_path_touch},
+{"monsterlost_checkhint", (byte *)monsterlost_checkhint},
+{"hintpath_stop", (byte *)hintpath_stop},
+{"hintpath_go", (byte *)hintpath_go},
+{"hintpath_other_end", (byte *)hintpath_other_end},
+{"hintpath_findstart", (byte *)hintpath_findstart},
+{"blocked_checknewenemy", (byte *)blocked_checknewenemy},
+{"blocked_checkjump", (byte *)blocked_checkjump},
+{"blocked_checkplat", (byte *)blocked_checkplat},
+{"monster_done_dodge", (byte *)monster_done_dodge},
+{"stationarymonster_start", (byte *)stationarymonster_start},
+{"stationarymonster_start_go", (byte *)stationarymonster_start_go},
+{"stationarymonster_triggered_start", (byte *)stationarymonster_triggered_start},
+{"stationarymonster_triggered_spawn_use", (byte *)stationarymonster_triggered_spawn_use},
+{"stationarymonster_triggered_spawn", (byte *)stationarymonster_triggered_spawn},
+{"swimmonster_start", (byte *)swimmonster_start},
+{"swimmonster_start_go", (byte *)swimmonster_start_go},
+{"flymonster_start", (byte *)flymonster_start},
+{"flymonster_start_go", (byte *)flymonster_start_go},
+{"walkmonster_start", (byte *)walkmonster_start},
+{"walkmonster_start_go", (byte *)walkmonster_start_go},
+{"monster_start_go", (byte *)monster_start_go},
+{"monster_start", (byte *)monster_start},
+{"monster_death_use", (byte *)monster_death_use},
+{"monster_triggered_start", (byte *)monster_triggered_start},
+{"monster_triggered_spawn_use", (byte *)monster_triggered_spawn_use},
+{"monster_triggered_spawn", (byte *)monster_triggered_spawn},
+{"monster_use", (byte *)monster_use},
+{"monster_think", (byte *)monster_think},
+{"M_MoveFrame", (byte *)M_MoveFrame},
+{"M_SetEffects", (byte *)M_SetEffects},
+{"M_droptofloor", (byte *)M_droptofloor},
+{"M_WorldEffects", (byte *)M_WorldEffects},
+{"M_CatagorizePosition", (byte *)M_CatagorizePosition},
+{"M_CheckGround", (byte *)M_CheckGround},
+{"AttackFinished", (byte *)AttackFinished},
+{"M_FlyCheck", (byte *)M_FlyCheck},
+{"M_FliesOn", (byte *)M_FliesOn},
+{"M_FliesOff", (byte *)M_FliesOff},
+{"monster_fire_bfg", (byte *)monster_fire_bfg},
+{"monster_fire_railgun", (byte *)monster_fire_railgun},
+{"monster_fire_rocket", (byte *)monster_fire_rocket},
+{"monster_fire_grenade", (byte *)monster_fire_grenade},
+{"monster_fire_heat", (byte *)monster_fire_heat},
+{"monster_fire_tracker", (byte *)monster_fire_tracker},
+{"monster_fire_blaster2", (byte *)monster_fire_blaster2},
+{"monster_fire_blaster", (byte *)monster_fire_blaster},
+{"monster_fire_shotgun", (byte *)monster_fire_shotgun},
+{"monster_fire_bullet", (byte *)monster_fire_bullet},
+{"SP_misc_nuke_core", (byte *)SP_misc_nuke_core},
+{"misc_nuke_core_use", (byte *)misc_nuke_core_use},
+{"SP_misc_teleporter_dest", (byte *)SP_misc_teleporter_dest},
+{"SP_misc_teleporter", (byte *)SP_misc_teleporter},
+{"teleporter_touch", (byte *)teleporter_touch},
+{"SP_func_clock", (byte *)SP_func_clock},
+{"func_clock_use", (byte *)func_clock_use},
+{"func_clock_think", (byte *)func_clock_think},
+{"func_clock_format_countdown", (byte *)func_clock_format_countdown},
+{"func_clock_reset", (byte *)func_clock_reset},
+{"SP_target_string", (byte *)SP_target_string},
+{"target_string_use", (byte *)target_string_use},
+{"SP_target_character", (byte *)SP_target_character},
+{"SP_misc_gib_head", (byte *)SP_misc_gib_head},
+{"SP_misc_gib_leg", (byte *)SP_misc_gib_leg},
+{"SP_misc_gib_arm", (byte *)SP_misc_gib_arm},
+{"SP_light_mine2", (byte *)SP_light_mine2},
+{"SP_light_mine1", (byte *)SP_light_mine1},
+{"SP_misc_satellite_dish", (byte *)SP_misc_satellite_dish},
+{"misc_satellite_dish_use", (byte *)misc_satellite_dish_use},
+{"misc_satellite_dish_think", (byte *)misc_satellite_dish_think},
+{"SP_misc_strogg_ship", (byte *)SP_misc_strogg_ship},
+{"misc_strogg_ship_use", (byte *)misc_strogg_ship_use},
+{"SP_misc_viper_bomb", (byte *)SP_misc_viper_bomb},
+{"misc_viper_bomb_use", (byte *)misc_viper_bomb_use},
+{"misc_viper_bomb_prethink", (byte *)misc_viper_bomb_prethink},
+{"misc_viper_bomb_touch", (byte *)misc_viper_bomb_touch},
+{"SP_misc_bigviper", (byte *)SP_misc_bigviper},
+{"SP_misc_viper", (byte *)SP_misc_viper},
+{"misc_viper_use", (byte *)misc_viper_use},
+{"SP_misc_deadsoldier", (byte *)SP_misc_deadsoldier},
+{"misc_deadsoldier_die", (byte *)misc_deadsoldier_die},
+{"SP_misc_banner", (byte *)SP_misc_banner},
+{"misc_banner_think", (byte *)misc_banner_think},
+{"SP_monster_commander_body", (byte *)SP_monster_commander_body},
+{"commander_body_drop", (byte *)commander_body_drop},
+{"commander_body_use", (byte *)commander_body_use},
+{"commander_body_think", (byte *)commander_body_think},
+{"SP_misc_easterchick2", (byte *)SP_misc_easterchick2},
+{"misc_easterchick2_think", (byte *)misc_easterchick2_think},
+{"SP_misc_easterchick", (byte *)SP_misc_easterchick},
+{"misc_easterchick_think", (byte *)misc_easterchick_think},
+{"SP_misc_eastertank", (byte *)SP_misc_eastertank},
+{"misc_eastertank_think", (byte *)misc_eastertank_think},
+{"SP_misc_blackhole", (byte *)SP_misc_blackhole},
+{"misc_blackhole_think", (byte *)misc_blackhole_think},
+{"misc_blackhole_use", (byte *)misc_blackhole_use},
+{"SP_misc_explobox", (byte *)SP_misc_explobox},
+{"barrel_start", (byte *)barrel_start},
+{"barrel_think", (byte *)barrel_think},
+{"barrel_delay", (byte *)barrel_delay},
+{"barrel_explode", (byte *)barrel_explode},
+{"barrel_touch", (byte *)barrel_touch},
+{"SP_func_explosive", (byte *)SP_func_explosive},
+{"func_explosive_spawn", (byte *)func_explosive_spawn},
+{"func_explosive_activate", (byte *)func_explosive_activate},
+{"func_explosive_use", (byte *)func_explosive_use},
+{"func_explosive_explode", (byte *)func_explosive_explode},
+{"SP_func_object", (byte *)SP_func_object},
+{"func_object_use", (byte *)func_object_use},
+{"func_object_release", (byte *)func_object_release},
+{"func_object_touch", (byte *)func_object_touch},
+{"SP_func_wall", (byte *)SP_func_wall},
+{"func_wall_use", (byte *)func_wall_use},
+{"SP_light", (byte *)SP_light},
+{"light_use", (byte *)light_use},
+{"SP_info_notnull", (byte *)SP_info_notnull},
+{"SP_info_null", (byte *)SP_info_null},
+{"SP_viewthing", (byte *)SP_viewthing},
+{"TH_viewthing", (byte *)TH_viewthing},
+{"SP_point_combat", (byte *)SP_point_combat},
+{"point_combat_touch", (byte *)point_combat_touch},
+{"SP_path_corner", (byte *)SP_path_corner},
+{"path_corner_touch", (byte *)path_corner_touch},
+{"BecomeExplosion2", (byte *)BecomeExplosion2},
+{"BecomeExplosion1", (byte *)BecomeExplosion1},
+{"ThrowDebris", (byte *)ThrowDebris},
+{"debris_die", (byte *)debris_die},
+{"ThrowClientHead", (byte *)ThrowClientHead},
+{"ThrowHead", (byte *)ThrowHead},
+{"ThrowGib", (byte *)ThrowGib},
+{"gib_die", (byte *)gib_die},
+{"gib_touch", (byte *)gib_touch},
+{"gib_think", (byte *)gib_think},
+{"ClipGibVelocity", (byte *)ClipGibVelocity},
+{"VelocityForDamage", (byte *)VelocityForDamage},
+{"SP_func_areaportal", (byte *)SP_func_areaportal},
+{"Use_Areaportal", (byte *)Use_Areaportal},
+{"G_RunFrame", (byte *)G_RunFrame},
+{"ExitLevel", (byte *)ExitLevel},
+{"CheckDMRules", (byte *)CheckDMRules},
+{"CheckNeedPass", (byte *)CheckNeedPass},
+{"EndDMLevel", (byte *)EndDMLevel},
+{"CreateTargetChangeLevel", (byte *)CreateTargetChangeLevel},
+{"ClientEndServerFrames", (byte *)ClientEndServerFrames},
+{"Com_Printf", (byte *)Com_Printf},
+{"Sys_Error", (byte *)Sys_Error},
+{"GetGameAPI", (byte *)GetGameAPI},
+{"ShutdownGame", (byte *)ShutdownGame},
+{"SP_xatrix_item", (byte *)SP_xatrix_item},
+{"SetItemNames", (byte *)SetItemNames},
+{"InitItems", (byte *)InitItems},
+{"SP_item_health_mega", (byte *)SP_item_health_mega},
+{"SP_item_health_large", (byte *)SP_item_health_large},
+{"SP_item_health_small", (byte *)SP_item_health_small},
+{"SP_item_health", (byte *)SP_item_health},
+{"SpawnItem", (byte *)SpawnItem},
+{"SetTriggeredSpawn", (byte *)SetTriggeredSpawn},
+{"Item_TriggeredSpawn", (byte *)Item_TriggeredSpawn},
+{"PrecacheItem", (byte *)PrecacheItem},
+{"droptofloor", (byte *)droptofloor},
+{"Use_Item", (byte *)Use_Item},
+{"Drop_Item", (byte *)Drop_Item},
+{"drop_make_touchable", (byte *)drop_make_touchable},
+{"drop_temp_touch", (byte *)drop_temp_touch},
+{"Touch_Item", (byte *)Touch_Item},
+{"Drop_PowerArmor", (byte *)Drop_PowerArmor},
+{"Pickup_PowerArmor", (byte *)Pickup_PowerArmor},
+{"Use_PowerArmor", (byte *)Use_PowerArmor},
+{"PowerArmorType", (byte *)PowerArmorType},
+{"Pickup_Armor", (byte *)Pickup_Armor},
+{"ArmorIndex", (byte *)ArmorIndex},
+{"Pickup_Health", (byte *)Pickup_Health},
+{"MegaHealth_think", (byte *)MegaHealth_think},
+{"Drop_Ammo", (byte *)Drop_Ammo},
+{"Pickup_Ammo", (byte *)Pickup_Ammo},
+{"Add_Ammo", (byte *)Add_Ammo},
+{"Pickup_Key", (byte *)Pickup_Key},
+{"Use_Silencer", (byte *)Use_Silencer},
+{"Use_Invulnerability", (byte *)Use_Invulnerability},
+{"Use_Envirosuit", (byte *)Use_Envirosuit},
+{"Use_Breather", (byte *)Use_Breather},
+{"Use_Quad", (byte *)Use_Quad},
+{"Use_Vengeance", (byte *)Use_Vengeance},
+{"Use_Hunter", (byte *)Use_Hunter},
+{"Use_Defender", (byte *)Use_Defender},
+{"Pickup_Sphere", (byte *)Pickup_Sphere},
+{"Pickup_Doppleganger", (byte *)Pickup_Doppleganger},
+{"Use_Doppleganger", (byte *)Use_Doppleganger},
+{"Use_Nuke", (byte *)Use_Nuke},
+{"Use_Compass", (byte *)Use_Compass},
+{"Use_Double", (byte *)Use_Double},
+{"Use_IR", (byte *)Use_IR},
+{"Pickup_Nuke", (byte *)Pickup_Nuke},
+{"Pickup_Pack", (byte *)Pickup_Pack},
+{"Pickup_Bandolier", (byte *)Pickup_Bandolier},
+{"Pickup_AncientHead", (byte *)Pickup_AncientHead},
+{"Pickup_Adrenaline", (byte *)Pickup_Adrenaline},
+{"Drop_General", (byte *)Drop_General},
+{"Pickup_Powerup", (byte *)Pickup_Powerup},
+{"SetRespawn", (byte *)SetRespawn},
+{"DoRespawn", (byte *)DoRespawn},
+{"FindItem", (byte *)FindItem},
+{"FindItemByClassname", (byte *)FindItemByClassname},
+{"GetItemByIndex", (byte *)GetItemByIndex},
+{"SP_func_killbox", (byte *)SP_func_killbox},
+{"use_killbox", (byte *)use_killbox},
+{"SP_func_door_secret", (byte *)SP_func_door_secret},
+{"door_secret_die", (byte *)door_secret_die},
+{"door_secret_blocked", (byte *)door_secret_blocked},
+{"door_secret_done", (byte *)door_secret_done},
+{"door_secret_move6", (byte *)door_secret_move6},
+{"door_secret_move5", (byte *)door_secret_move5},
+{"door_secret_move4", (byte *)door_secret_move4},
+{"door_secret_move3", (byte *)door_secret_move3},
+{"door_secret_move2", (byte *)door_secret_move2},
+{"door_secret_move1", (byte *)door_secret_move1},
+{"door_secret_use", (byte *)door_secret_use},
+{"SP_func_conveyor", (byte *)SP_func_conveyor},
+{"func_conveyor_use", (byte *)func_conveyor_use},
+{"SP_func_timer", (byte *)SP_func_timer},
+{"func_timer_use", (byte *)func_timer_use},
+{"func_timer_think", (byte *)func_timer_think},
+{"SP_trigger_elevator", (byte *)SP_trigger_elevator},
+{"trigger_elevator_init", (byte *)trigger_elevator_init},
+{"trigger_elevator_use", (byte *)trigger_elevator_use},
+{"SP_func_train", (byte *)SP_func_train},
+{"train_use", (byte *)train_use},
+{"func_train_find", (byte *)func_train_find},
+{"train_resume", (byte *)train_resume},
+{"train_next", (byte *)train_next},
+{"train_piece_wait", (byte *)train_piece_wait},
+{"train_wait", (byte *)train_wait},
+{"train_blocked", (byte *)train_blocked},
+{"SP_func_water", (byte *)SP_func_water},
+{"smart_water_blocked", (byte *)smart_water_blocked},
+{"SP_func_door_rotating", (byte *)SP_func_door_rotating},
+{"Door_Activate", (byte *)Door_Activate},
+{"SP_func_door", (byte *)SP_func_door},
+{"door_touch", (byte *)door_touch},
+{"door_killed", (byte *)door_killed},
+{"door_blocked", (byte *)door_blocked},
+{"Think_SpawnDoorTrigger", (byte *)Think_SpawnDoorTrigger},
+{"Think_CalcMoveSpeed", (byte *)Think_CalcMoveSpeed},
+{"Touch_DoorTrigger", (byte *)Touch_DoorTrigger},
+{"door_use", (byte *)door_use},
+{"smart_water_go_up", (byte *)smart_water_go_up},
+{"door_go_up", (byte *)door_go_up},
+{"door_go_down", (byte *)door_go_down},
+{"door_hit_bottom", (byte *)door_hit_bottom},
+{"door_hit_top", (byte *)door_hit_top},
+{"door_use_areaportals", (byte *)door_use_areaportals},
+{"SP_func_button", (byte *)SP_func_button},
+{"button_killed", (byte *)button_killed},
+{"button_touch", (byte *)button_touch},
+{"button_use", (byte *)button_use},
+{"button_fire", (byte *)button_fire},
+{"button_wait", (byte *)button_wait},
+{"button_return", (byte *)button_return},
+{"button_done", (byte *)button_done},
+{"SP_func_rotating", (byte *)SP_func_rotating},
+{"rotating_use", (byte *)rotating_use},
+{"rotating_touch", (byte *)rotating_touch},
+{"rotating_blocked", (byte *)rotating_blocked},
+{"rotating_decel", (byte *)rotating_decel},
+{"rotating_accel", (byte *)rotating_accel},
+{"SP_func_plat2", (byte *)SP_func_plat2},
+{"plat2_activate", (byte *)plat2_activate},
+{"Use_Plat2", (byte *)Use_Plat2},
+{"plat2_blocked", (byte *)plat2_blocked},
+{"Touch_Plat_Center2", (byte *)Touch_Plat_Center2},
+{"plat2_operate", (byte *)plat2_operate},
+{"plat2_go_up", (byte *)plat2_go_up},
+{"plat2_go_down", (byte *)plat2_go_down},
+{"plat2_hit_bottom", (byte *)plat2_hit_bottom},
+{"plat2_hit_top", (byte *)plat2_hit_top},
+{"plat2_kill_danger_area", (byte *)plat2_kill_danger_area},
+{"plat2_spawn_danger_area", (byte *)plat2_spawn_danger_area},
+{"SP_func_plat", (byte *)SP_func_plat},
+{"plat_spawn_inside_trigger", (byte *)plat_spawn_inside_trigger},
+{"Touch_Plat_Center", (byte *)Touch_Plat_Center},
+{"Use_Plat", (byte *)Use_Plat},
+{"plat_blocked", (byte *)plat_blocked},
+{"plat_go_up", (byte *)plat_go_up},
+{"plat_go_down", (byte *)plat_go_down},
+{"plat_hit_bottom", (byte *)plat_hit_bottom},
+{"plat_hit_top", (byte *)plat_hit_top},
+{"Think_AccelMove", (byte *)Think_AccelMove},
+{"plat_Accelerate", (byte *)plat_Accelerate},
+{"plat_CalcAcceleratedMove", (byte *)plat_CalcAcceleratedMove},
+{"AngleMove_Calc", (byte *)AngleMove_Calc},
+{"AngleMove_Begin", (byte *)AngleMove_Begin},
+{"AngleMove_Final", (byte *)AngleMove_Final},
+{"AngleMove_Done", (byte *)AngleMove_Done},
+{"Move_Calc", (byte *)Move_Calc},
+{"Move_Begin", (byte *)Move_Begin},
+{"Move_Final", (byte *)Move_Final},
+{"Move_Done", (byte *)Move_Done},
+{"T_RadiusClassDamage", (byte *)T_RadiusClassDamage},
+{"T_RadiusNukeDamage", (byte *)T_RadiusNukeDamage},
+{"T_RadiusDamage", (byte *)T_RadiusDamage},
+{"T_Damage", (byte *)T_Damage},
+{"CheckTeamDamage", (byte *)CheckTeamDamage},
+{"M_ReactToDamage", (byte *)M_ReactToDamage},
+{"CheckArmor", (byte *)CheckArmor},
+{"CheckPowerArmor", (byte *)CheckPowerArmor},
+{"SpawnDamage", (byte *)SpawnDamage},
+{"Killed", (byte *)Killed},
+{"CanDamage", (byte *)CanDamage},
+{"cleanupHealTarget", (byte *)cleanupHealTarget},
+{"ClientCommand", (byte *)ClientCommand},
+{"Cmd_PlayerList_f", (byte *)Cmd_PlayerList_f},
+{"Cmd_Ent_Count_f", (byte *)Cmd_Ent_Count_f},
+{"Cmd_Say_f", (byte *)Cmd_Say_f},
+{"Cmd_Wave_f", (byte *)Cmd_Wave_f},
+{"Cmd_Players_f", (byte *)Cmd_Players_f},
+{"PlayerSort", (byte *)PlayerSort},
+{"Cmd_PutAway_f", (byte *)Cmd_PutAway_f},
+{"Cmd_Kill_f", (byte *)Cmd_Kill_f},
+{"Cmd_InvDrop_f", (byte *)Cmd_InvDrop_f},
+{"Cmd_WeapLast_f", (byte *)Cmd_WeapLast_f},
+{"Cmd_WeapNext_f", (byte *)Cmd_WeapNext_f},
+{"Cmd_WeapPrev_f", (byte *)Cmd_WeapPrev_f},
+{"Cmd_InvUse_f", (byte *)Cmd_InvUse_f},
+{"Cmd_Inven_f", (byte *)Cmd_Inven_f},
+{"Cmd_Help_f", (byte *)Cmd_Help_f},
+{"Cmd_Drop_f", (byte *)Cmd_Drop_f},
+{"Cmd_Use_f", (byte *)Cmd_Use_f},
+{"Cmd_Noclip_f", (byte *)Cmd_Noclip_f},
+{"Cmd_Notarget_f", (byte *)Cmd_Notarget_f},
+{"Cmd_God_f", (byte *)Cmd_God_f},
+{"Cmd_Give_f", (byte *)Cmd_Give_f},
+{"ValidateSelectedItem", (byte *)ValidateSelectedItem},
+{"SelectPrevItem", (byte *)SelectPrevItem},
+{"SelectNextItem", (byte *)SelectNextItem},
+{"OnSameTeam", (byte *)OnSameTeam},
+{"GetChaseTarget", (byte *)GetChaseTarget},
+{"ChasePrev", (byte *)ChasePrev},
+{"ChaseNext", (byte *)ChaseNext},
+{"UpdateChaseCam", (byte *)UpdateChaseCam},
+{"ai_run", (byte *)ai_run},
+{"ai_checkattack", (byte *)ai_checkattack},
+{"ai_run_slide", (byte *)ai_run_slide},
+{"ai_run_missile", (byte *)ai_run_missile},
+{"ai_run_melee", (byte *)ai_run_melee},
+{"M_CheckAttack", (byte *)M_CheckAttack},
+{"FacingIdeal", (byte *)FacingIdeal},
+{"FindTarget", (byte *)FindTarget},
+{"FoundTarget", (byte *)FoundTarget},
+{"HuntTarget", (byte *)HuntTarget},
+{"infront", (byte *)infront},
+{"visible", (byte *)visible},
+{"range", (byte *)range},
+{"ai_turn", (byte *)ai_turn},
+{"ai_charge", (byte *)ai_charge},
+{"ai_walk", (byte *)ai_walk},
+{"ai_stand", (byte *)ai_stand},
+{"ai_move", (byte *)ai_move},
+{"AI_SetSightClient", (byte *)AI_SetSightClient},
+{"SP_dm_tag_token", (byte *)SP_dm_tag_token},
+{"Tag_PostInitSetup", (byte *)Tag_PostInitSetup},
+{"Tag_GameInit", (byte *)Tag_GameInit},
+{"Tag_ChangeDamage", (byte *)Tag_ChangeDamage},
+{"Tag_DogTag", (byte *)Tag_DogTag},
+{"Tag_PlayerEffects", (byte *)Tag_PlayerEffects},
+{"Tag_DropToken", (byte *)Tag_DropToken},
+{"Tag_MakeTouchable", (byte *)Tag_MakeTouchable},
+{"Tag_Respawn", (byte *)Tag_Respawn},
+{"Tag_PickupToken", (byte *)Tag_PickupToken},
+{"Tag_Score", (byte *)Tag_Score},
+{"Tag_PlayerDisconnect", (byte *)Tag_PlayerDisconnect},
+{"Tag_KillItBonus", (byte *)Tag_KillItBonus},
+{"Tag_PlayerDeath", (byte *)Tag_PlayerDeath},
+{"SP_dm_dball_goal", (byte *)SP_dm_dball_goal},
+{"SP_dm_dball_speed_change", (byte *)SP_dm_dball_speed_change},
+{"SP_dm_dball_ball_start", (byte *)SP_dm_dball_ball_start},
+{"SP_dm_dball_team2_start", (byte *)SP_dm_dball_team2_start},
+{"SP_dm_dball_team1_start", (byte *)SP_dm_dball_team1_start},
+{"SP_dm_dball_ball", (byte *)SP_dm_dball_ball},
+{"DBall_SpeedTouch", (byte *)DBall_SpeedTouch},
+{"DBall_BallRespawn", (byte *)DBall_BallRespawn},
+{"DBall_BallDie", (byte *)DBall_BallDie},
+{"DBall_BallPain", (byte *)DBall_BallPain},
+{"DBall_BallTouch", (byte *)DBall_BallTouch},
+{"PickBallStart", (byte *)PickBallStart},
+{"DBall_GoalTouch", (byte *)DBall_GoalTouch},
+{"DBall_ChangeKnockback", (byte *)DBall_ChangeKnockback},
+{"DBall_ChangeDamage", (byte *)DBall_ChangeDamage},
+{"DBall_PostInitSetup", (byte *)DBall_PostInitSetup},
+{"DBall_GameInit", (byte *)DBall_GameInit},
+{"DBall_SelectSpawnPoint", (byte *)DBall_SelectSpawnPoint},
+{"DBall_ClientBegin", (byte *)DBall_ClientBegin},
+{"DBall_CheckDMRules", (byte *)DBall_CheckDMRules},
+{"wait_and_change_think", (byte *)wait_and_change_think},
+{0, 0}
diff --git a/rogue/src/savegame/tables/gamemmove_decs.h b/rogue/src/savegame/tables/gamemmove_decs.h
new file mode 100644
index 0000000..eab7c3a
--- /dev/null
+++ b/rogue/src/savegame/tables/gamemmove_decs.h
@@ -0,0 +1,364 @@
+/*
+ * =======================================================================
+ *
+ * Prototypes for every mmove_t in the game.so.
+ *
+ * =======================================================================
+ */
+
+extern mmove_t widow2_move_really_dead ;
+extern mmove_t widow2_move_dead ;
+extern mmove_t widow2_move_death ;
+extern mmove_t widow2_move_pain ;
+extern mmove_t widow2_move_tongs ;
+extern mmove_t widow2_move_spawn ;
+extern mmove_t widow2_move_attack_disrupt ;
+extern mmove_t widow2_move_attack_post_beam ;
+extern mmove_t widow2_move_attack_beam ;
+extern mmove_t widow2_move_attack_pre_beam ;
+extern mmove_t widow2_move_run ;
+extern mmove_t widow2_move_walk ;
+extern mmove_t widow2_move_stand ;
+extern mmove_t widow_move_attack_kick ;
+extern mmove_t widow_move_death ;
+extern mmove_t widow_move_pain_light ;
+extern mmove_t widow_move_pain_heavy ;
+extern mmove_t widow_move_spawn ;
+extern mmove_t widow_move_attack_pre_rail ;
+extern mmove_t widow_move_attack_rail_r ;
+extern mmove_t widow_move_attack_rail_l ;
+extern mmove_t widow_move_attack_rail ;
+extern mmove_t widow_move_attack_pre_blaster ;
+extern mmove_t widow_move_run_attack ;
+extern mmove_t widow_move_run ;
+extern mmove_t widow_move_walk ;
+extern mmove_t widow_move_stand ;
+extern mmove_t widow_move_attack_blaster ;
+extern mmove_t widow_move_attack_post_blaster_l ;
+extern mmove_t widow_move_attack_post_blaster_r ;
+extern mmove_t widow_move_attack_post_blaster ;
+extern mmove_t turret_move_run ;
+extern mmove_t turret_move_seek ;
+extern mmove_t turret_move_ready_gun ;
+extern mmove_t turret_move_stand ;
+extern mmove_t turret_move_fire_blind ;
+extern mmove_t turret_move_fire ;
+extern mmove_t tank_move_death ;
+extern mmove_t tank_move_attack_chain ;
+extern mmove_t tank_move_attack_post_rocket ;
+extern mmove_t tank_move_attack_fire_rocket ;
+extern mmove_t tank_move_attack_pre_rocket ;
+extern mmove_t tank_move_attack_strike ;
+extern mmove_t tank_move_attack_post_blast ;
+extern mmove_t tank_move_reattack_blast ;
+extern mmove_t tank_move_attack_blast ;
+extern mmove_t tank_move_pain3 ;
+extern mmove_t tank_move_pain2 ;
+extern mmove_t tank_move_pain1 ;
+extern mmove_t tank_move_stop_run ;
+extern mmove_t tank_move_run ;
+extern mmove_t tank_move_start_run ;
+extern mmove_t tank_move_stop_walk ;
+extern mmove_t tank_move_walk ;
+extern mmove_t tank_move_start_walk ;
+extern mmove_t tank_move_stand ;
+extern mmove_t supertank_move_end_attack1 ;
+extern mmove_t supertank_move_attack1 ;
+extern mmove_t supertank_move_attack2 ;
+extern mmove_t supertank_move_attack3 ;
+extern mmove_t supertank_move_attack4 ;
+extern mmove_t supertank_move_backward ;
+extern mmove_t supertank_move_death ;
+extern mmove_t supertank_move_pain1 ;
+extern mmove_t supertank_move_pain2 ;
+extern mmove_t supertank_move_pain3 ;
+extern mmove_t supertank_move_turn_left ;
+extern mmove_t supertank_move_turn_right ;
+extern mmove_t supertank_move_forward ;
+extern mmove_t supertank_move_run ;
+extern mmove_t supertank_move_stand ;
+extern mmove_t stalker_move_death ;
+extern mmove_t stalker_move_jump_down ;
+extern mmove_t stalker_move_jump_up ;
+extern mmove_t stalker_move_dodge_run ;
+extern mmove_t stalker_move_jump_straightup ;
+extern mmove_t stalker_move_swing_r ;
+extern mmove_t stalker_move_swing_l ;
+extern mmove_t stalker_move_shoot ;
+extern mmove_t stalker_move_pain ;
+extern mmove_t stalker_move_false_death_start ;
+extern mmove_t stalker_move_false_death ;
+extern mmove_t stalker_move_false_death_end ;
+extern mmove_t stalker_move_walk ;
+extern mmove_t stalker_move_run ;
+extern mmove_t stalker_move_stand ;
+extern mmove_t stalker_move_idle2 ;
+extern mmove_t stalker_move_idle ;
+extern mmove_t soldier_move_blind ;
+extern mmove_t soldier_move_death6 ;
+extern mmove_t soldier_move_death5 ;
+extern mmove_t soldier_move_death4 ;
+extern mmove_t soldier_move_death3 ;
+extern mmove_t soldier_move_death2 ;
+extern mmove_t soldier_move_death1 ;
+extern mmove_t soldier_move_duck ;
+extern mmove_t soldier_move_attack6 ;
+extern mmove_t soldier_move_attack4 ;
+extern mmove_t soldier_move_attack3 ;
+extern mmove_t soldier_move_attack2 ;
+extern mmove_t soldier_move_attack1 ;
+extern mmove_t soldier_move_pain4 ;
+extern mmove_t soldier_move_pain3 ;
+extern mmove_t soldier_move_pain2 ;
+extern mmove_t soldier_move_pain1 ;
+extern mmove_t soldier_move_run ;
+extern mmove_t soldier_move_start_run ;
+extern mmove_t soldier_move_walk2 ;
+extern mmove_t soldier_move_walk1 ;
+extern mmove_t soldier_move_stand3 ;
+extern mmove_t soldier_move_stand1 ;
+extern mmove_t parasite_move_death ;
+extern mmove_t parasite_move_jump_down ;
+extern mmove_t parasite_move_jump_up ;
+extern mmove_t parasite_move_break ;
+extern mmove_t parasite_move_drain ;
+extern mmove_t parasite_move_pain1 ;
+extern mmove_t parasite_move_stop_walk ;
+extern mmove_t parasite_move_start_walk ;
+extern mmove_t parasite_move_walk ;
+extern mmove_t parasite_move_stop_run ;
+extern mmove_t parasite_move_start_run ;
+extern mmove_t parasite_move_run ;
+extern mmove_t parasite_move_stand ;
+extern mmove_t parasite_move_end_fidget ;
+extern mmove_t parasite_move_fidget ;
+extern mmove_t parasite_move_start_fidget ;
+extern mmove_t mutant_move_jump_down ;
+extern mmove_t mutant_move_jump_up ;
+extern mmove_t mutant_move_death2 ;
+extern mmove_t mutant_move_death1 ;
+extern mmove_t mutant_move_pain3 ;
+extern mmove_t mutant_move_pain2 ;
+extern mmove_t mutant_move_pain1 ;
+extern mmove_t mutant_move_jump ;
+extern mmove_t mutant_move_attack ;
+extern mmove_t mutant_move_run ;
+extern mmove_t mutant_move_start_walk ;
+extern mmove_t mutant_move_walk ;
+extern mmove_t mutant_move_idle ;
+extern mmove_t mutant_move_stand ;
+extern mmove_t medic_move_callReinforcements;
+extern mmove_t medic_move_attackCable ;
+extern mmove_t medic_move_attackBlaster ;
+extern mmove_t medic_move_attackHyperBlaster ;
+extern mmove_t medic_move_duck ;
+extern mmove_t medic_move_death ;
+extern mmove_t medic_move_pain2 ;
+extern mmove_t medic_move_pain1 ;
+extern mmove_t medic_move_run ;
+extern mmove_t medic_move_walk ;
+extern mmove_t medic_move_stand ;
+extern mmove_t insane_move_struggle_cross ;
+extern mmove_t insane_move_cross ;
+extern mmove_t insane_move_crawl_death ;
+extern mmove_t insane_move_crawl_pain ;
+extern mmove_t insane_move_runcrawl ;
+extern mmove_t insane_move_crawl ;
+extern mmove_t insane_move_stand_death ;
+extern mmove_t insane_move_stand_pain ;
+extern mmove_t insane_move_run_insane ;
+extern mmove_t insane_move_walk_insane ;
+extern mmove_t insane_move_run_normal ;
+extern mmove_t insane_move_walk_normal ;
+extern mmove_t insane_move_down ;
+extern mmove_t insane_move_jumpdown ;
+extern mmove_t insane_move_downtoup ;
+extern mmove_t insane_move_uptodown ;
+extern mmove_t insane_move_stand_insane ;
+extern mmove_t insane_move_stand_normal ;
+extern mmove_t infantry_move_jump2 ;
+extern mmove_t infantry_move_jump ;
+extern mmove_t infantry_move_attack2 ;
+extern mmove_t infantry_move_attack1 ;
+extern mmove_t infantry_move_duck ;
+extern mmove_t infantry_move_death3 ;
+extern mmove_t infantry_move_death2 ;
+extern mmove_t infantry_move_death1 ;
+extern mmove_t infantry_move_pain2 ;
+extern mmove_t infantry_move_pain1 ;
+extern mmove_t infantry_move_run ;
+extern mmove_t infantry_move_walk ;
+extern mmove_t infantry_move_fidget ;
+extern mmove_t infantry_move_stand ;
+extern mmove_t hover_move_start_attack2;
+extern mmove_t hover_move_attack2;
+extern mmove_t hover_move_end_attack2;
+extern mmove_t hover_move_end_attack ;
+extern mmove_t hover_move_attack1 ;
+extern mmove_t hover_move_start_attack ;
+extern mmove_t hover_move_death1 ;
+extern mmove_t hover_move_run ;
+extern mmove_t hover_move_walk ;
+extern mmove_t hover_move_pain1 ;
+extern mmove_t hover_move_pain2 ;
+extern mmove_t hover_move_pain3 ;
+extern mmove_t hover_move_stand ;
+extern mmove_t gunner_move_jump2 ;
+extern mmove_t gunner_move_jump ;
+extern mmove_t gunner_move_attack_grenade ;
+extern mmove_t gunner_move_endfire_chain ;
+extern mmove_t gunner_move_fire_chain ;
+extern mmove_t gunner_move_attack_chain ;
+extern mmove_t gunner_move_duck ;
+extern mmove_t gunner_move_death ;
+extern mmove_t gunner_move_pain1 ;
+extern mmove_t gunner_move_pain2 ;
+extern mmove_t gunner_move_pain3 ;
+extern mmove_t gunner_move_runandshoot ;
+extern mmove_t gunner_move_run ;
+extern mmove_t gunner_move_walk ;
+extern mmove_t gunner_move_stand ;
+extern mmove_t gunner_move_fidget ;
+extern mmove_t gladiator_move_death ;
+extern mmove_t gladiator_move_pain_air ;
+extern mmove_t gladiator_move_pain ;
+extern mmove_t gladiator_move_attack_gun ;
+extern mmove_t gladiator_move_attack_melee ;
+extern mmove_t gladiator_move_run ;
+extern mmove_t gladiator_move_walk ;
+extern mmove_t gladiator_move_stand ;
+extern mmove_t flyer_move_loop_melee ;
+extern mmove_t flyer_move_end_melee ;
+extern mmove_t flyer_move_start_melee ;
+extern mmove_t flyer_move_attack3 ;
+extern mmove_t flyer_move_attack2 ;
+extern mmove_t flyer_move_bankleft ;
+extern mmove_t flyer_move_bankright ;
+extern mmove_t flyer_move_defense ;
+extern mmove_t flyer_move_pain1 ;
+extern mmove_t flyer_move_pain2 ;
+extern mmove_t flyer_move_pain3 ;
+extern mmove_t flyer_move_rollleft ;
+extern mmove_t flyer_move_rollright ;
+extern mmove_t flyer_move_stop ;
+extern mmove_t flyer_move_start ;
+extern mmove_t flyer_move_kamikaze ;
+extern mmove_t flyer_move_run ;
+extern mmove_t flyer_move_walk ;
+extern mmove_t flyer_move_stand ;
+extern mmove_t floater_move_run ;
+extern mmove_t floater_move_walk ;
+extern mmove_t floater_move_pain3 ;
+extern mmove_t floater_move_pain2 ;
+extern mmove_t floater_move_pain1 ;
+extern mmove_t floater_move_death ;
+extern mmove_t floater_move_attack3 ;
+extern mmove_t floater_move_attack2 ;
+extern mmove_t floater_move_attack1a ;
+extern mmove_t floater_move_attack1 ;
+extern mmove_t floater_move_activate ;
+extern mmove_t floater_move_stand2 ;
+extern mmove_t floater_move_stand1 ;
+extern mmove_t flipper_move_death ;
+extern mmove_t flipper_move_attack ;
+extern mmove_t flipper_move_pain1 ;
+extern mmove_t flipper_move_pain2 ;
+extern mmove_t flipper_move_start_run ;
+extern mmove_t flipper_move_walk ;
+extern mmove_t flipper_move_run_start ;
+extern mmove_t flipper_move_run_loop ;
+extern mmove_t flipper_move_stand ;
+extern mmove_t chick_move_start_slash ;
+extern mmove_t chick_move_end_slash ;
+extern mmove_t chick_move_slash ;
+extern mmove_t chick_move_end_attack1 ;
+extern mmove_t chick_move_attack1 ;
+extern mmove_t chick_move_start_attack1 ;
+extern mmove_t chick_move_duck ;
+extern mmove_t chick_move_death1 ;
+extern mmove_t chick_move_death2 ;
+extern mmove_t chick_move_pain3 ;
+extern mmove_t chick_move_pain2 ;
+extern mmove_t chick_move_pain1 ;
+extern mmove_t chick_move_walk ;
+extern mmove_t chick_move_run ;
+extern mmove_t chick_move_start_run ;
+extern mmove_t chick_move_stand ;
+extern mmove_t chick_move_fidget ;
+extern mmove_t carrier_move_death ;
+extern mmove_t carrier_move_pain_light ;
+extern mmove_t carrier_move_pain_heavy ;
+extern mmove_t carrier_move_spawn ;
+extern mmove_t carrier_move_attack_rail ;
+extern mmove_t carrier_move_attack_rocket ;
+extern mmove_t carrier_move_attack_post_gren ;
+extern mmove_t carrier_move_attack_gren ;
+extern mmove_t carrier_move_attack_pre_gren ;
+extern mmove_t carrier_move_attack_post_mg ;
+extern mmove_t carrier_move_attack_mg ;
+extern mmove_t carrier_move_attack_pre_mg ;
+extern mmove_t carrier_move_run ;
+extern mmove_t carrier_move_walk ;
+extern mmove_t carrier_move_stand ;
+extern mmove_t brain_move_run ;
+extern mmove_t brain_move_attack2 ;
+extern mmove_t brain_move_attack1 ;
+extern mmove_t brain_move_death1 ;
+extern mmove_t brain_move_death2 ;
+extern mmove_t brain_move_duck ;
+extern mmove_t brain_move_pain1 ;
+extern mmove_t brain_move_pain2 ;
+extern mmove_t brain_move_pain3 ;
+extern mmove_t brain_move_defense ;
+extern mmove_t brain_move_walk1 ;
+extern mmove_t brain_move_idle ;
+extern mmove_t brain_move_stand ;
+extern mmove_t makron_move_attack5 ;
+extern mmove_t makron_move_attack4 ;
+extern mmove_t makron_move_attack3 ;
+extern mmove_t makron_move_sight ;
+extern mmove_t makron_move_death3 ;
+extern mmove_t makron_move_death2 ;
+extern mmove_t makron_move_pain4 ;
+extern mmove_t makron_move_pain5 ;
+extern mmove_t makron_move_pain6 ;
+extern mmove_t makron_move_walk ;
+extern mmove_t makron_move_run ;
+extern mmove_t makron_move_stand ;
+extern mmove_t jorg_move_end_attack1 ;
+extern mmove_t jorg_move_attack1 ;
+extern mmove_t jorg_move_start_attack1 ;
+extern mmove_t jorg_move_attack2 ;
+extern mmove_t jorg_move_death ;
+extern mmove_t jorg_move_pain1 ;
+extern mmove_t jorg_move_pain2 ;
+extern mmove_t jorg_move_pain3 ;
+extern mmove_t jorg_move_end_walk ;
+extern mmove_t jorg_move_walk ;
+extern mmove_t jorg_move_start_walk ;
+extern mmove_t jorg_move_run ;
+extern mmove_t jorg_move_stand ;
+extern mmove_t boss2_move_death ;
+extern mmove_t boss2_move_pain_light ;
+extern mmove_t boss2_move_pain_heavy ;
+extern mmove_t boss2_move_attack_rocket ;
+extern mmove_t boss2_move_attack_post_mg ;
+extern mmove_t boss2_move_attack_mg ;
+extern mmove_t boss2_move_attack_pre_mg ;
+extern mmove_t boss2_move_run ;
+extern mmove_t boss2_move_walk ;
+extern mmove_t boss2_move_fidget ;
+extern mmove_t boss2_move_stand ;
+extern mmove_t berserk_move_jump2 ;
+extern mmove_t berserk_move_jump ;
+extern mmove_t berserk_move_death2 ;
+extern mmove_t berserk_move_death1 ;
+extern mmove_t berserk_move_pain2 ;
+extern mmove_t berserk_move_pain1 ;
+extern mmove_t berserk_move_attack_strike ;
+extern mmove_t berserk_move_attack_club ;
+extern mmove_t berserk_move_attack_spike ;
+extern mmove_t berserk_move_run1 ;
+extern mmove_t berserk_move_walk ;
+extern mmove_t berserk_move_stand_fidget ;
+extern mmove_t berserk_move_stand ;
diff --git a/rogue/src/savegame/tables/gamemmove_list.h b/rogue/src/savegame/tables/gamemmove_list.h
new file mode 100644
index 0000000..b36f587
--- /dev/null
+++ b/rogue/src/savegame/tables/gamemmove_list.h
@@ -0,0 +1,365 @@
+/*
+ * =======================================================================
+ *
+ * Pointers to every mmove_t in the game.so.
+ *
+ * =======================================================================
+ */
+
+{"widow2_move_really_dead", &widow2_move_really_dead},
+{"widow2_move_dead", &widow2_move_dead},
+{"widow2_move_death", &widow2_move_death},
+{"widow2_move_pain", &widow2_move_pain},
+{"widow2_move_tongs", &widow2_move_tongs},
+{"widow2_move_spawn", &widow2_move_spawn},
+{"widow2_move_attack_disrupt", &widow2_move_attack_disrupt},
+{"widow2_move_attack_post_beam", &widow2_move_attack_post_beam},
+{"widow2_move_attack_beam", &widow2_move_attack_beam},
+{"widow2_move_attack_pre_beam", &widow2_move_attack_pre_beam},
+{"widow2_move_run", &widow2_move_run},
+{"widow2_move_walk", &widow2_move_walk},
+{"widow2_move_stand", &widow2_move_stand},
+{"widow_move_attack_kick", &widow_move_attack_kick},
+{"widow_move_death", &widow_move_death},
+{"widow_move_pain_light", &widow_move_pain_light},
+{"widow_move_pain_heavy", &widow_move_pain_heavy},
+{"widow_move_spawn", &widow_move_spawn},
+{"widow_move_attack_pre_rail", &widow_move_attack_pre_rail},
+{"widow_move_attack_rail_r", &widow_move_attack_rail_r},
+{"widow_move_attack_rail_l", &widow_move_attack_rail_l},
+{"widow_move_attack_rail", &widow_move_attack_rail},
+{"widow_move_attack_pre_blaster", &widow_move_attack_pre_blaster},
+{"widow_move_run_attack", &widow_move_run_attack},
+{"widow_move_run", &widow_move_run},
+{"widow_move_walk", &widow_move_walk},
+{"widow_move_stand", &widow_move_stand},
+{"widow_move_attack_blaster", &widow_move_attack_blaster},
+{"widow_move_attack_post_blaster_l", &widow_move_attack_post_blaster_l},
+{"widow_move_attack_post_blaster_r", &widow_move_attack_post_blaster_r},
+{"widow_move_attack_post_blaster", &widow_move_attack_post_blaster},
+{"turret_move_run", &turret_move_run},
+{"turret_move_seek", &turret_move_seek},
+{"turret_move_ready_gun", &turret_move_ready_gun},
+{"turret_move_stand", &turret_move_stand},
+{"turret_move_fire_blind", &turret_move_fire_blind},
+{"turret_move_fire", &turret_move_fire},
+{"tank_move_death", &tank_move_death},
+{"tank_move_attack_chain", &tank_move_attack_chain},
+{"tank_move_attack_post_rocket", &tank_move_attack_post_rocket},
+{"tank_move_attack_fire_rocket", &tank_move_attack_fire_rocket},
+{"tank_move_attack_pre_rocket", &tank_move_attack_pre_rocket},
+{"tank_move_attack_strike", &tank_move_attack_strike},
+{"tank_move_attack_post_blast", &tank_move_attack_post_blast},
+{"tank_move_reattack_blast", &tank_move_reattack_blast},
+{"tank_move_attack_blast", &tank_move_attack_blast},
+{"tank_move_pain3", &tank_move_pain3},
+{"tank_move_pain2", &tank_move_pain2},
+{"tank_move_pain1", &tank_move_pain1},
+{"tank_move_stop_run", &tank_move_stop_run},
+{"tank_move_run", &tank_move_run},
+{"tank_move_start_run", &tank_move_start_run},
+{"tank_move_stop_walk", &tank_move_stop_walk},
+{"tank_move_walk", &tank_move_walk},
+{"tank_move_start_walk", &tank_move_start_walk},
+{"tank_move_stand", &tank_move_stand},
+{"supertank_move_end_attack1", &supertank_move_end_attack1},
+{"supertank_move_attack1", &supertank_move_attack1},
+{"supertank_move_attack2", &supertank_move_attack2},
+{"supertank_move_attack3", &supertank_move_attack3},
+{"supertank_move_attack4", &supertank_move_attack4},
+{"supertank_move_backward", &supertank_move_backward},
+{"supertank_move_death", &supertank_move_death},
+{"supertank_move_pain1", &supertank_move_pain1},
+{"supertank_move_pain2", &supertank_move_pain2},
+{"supertank_move_pain3", &supertank_move_pain3},
+{"supertank_move_turn_left", &supertank_move_turn_left},
+{"supertank_move_turn_right", &supertank_move_turn_right},
+{"supertank_move_forward", &supertank_move_forward},
+{"supertank_move_run", &supertank_move_run},
+{"supertank_move_stand", &supertank_move_stand},
+{"stalker_move_death", &stalker_move_death},
+{"stalker_move_jump_down", &stalker_move_jump_down},
+{"stalker_move_jump_up", &stalker_move_jump_up},
+{"stalker_move_dodge_run", &stalker_move_dodge_run},
+{"stalker_move_jump_straightup", &stalker_move_jump_straightup},
+{"stalker_move_swing_r", &stalker_move_swing_r},
+{"stalker_move_swing_l", &stalker_move_swing_l},
+{"stalker_move_shoot", &stalker_move_shoot},
+{"stalker_move_pain", &stalker_move_pain},
+{"stalker_move_false_death_start", &stalker_move_false_death_start},
+{"stalker_move_false_death", &stalker_move_false_death},
+{"stalker_move_false_death_end", &stalker_move_false_death_end},
+{"stalker_move_walk", &stalker_move_walk},
+{"stalker_move_run", &stalker_move_run},
+{"stalker_move_stand", &stalker_move_stand},
+{"stalker_move_idle2", &stalker_move_idle2},
+{"stalker_move_idle", &stalker_move_idle},
+{"soldier_move_blind", &soldier_move_blind},
+{"soldier_move_death6", &soldier_move_death6},
+{"soldier_move_death5", &soldier_move_death5},
+{"soldier_move_death4", &soldier_move_death4},
+{"soldier_move_death3", &soldier_move_death3},
+{"soldier_move_death2", &soldier_move_death2},
+{"soldier_move_death1", &soldier_move_death1},
+{"soldier_move_duck", &soldier_move_duck},
+{"soldier_move_attack6", &soldier_move_attack6},
+{"soldier_move_attack4", &soldier_move_attack4},
+{"soldier_move_attack3", &soldier_move_attack3},
+{"soldier_move_attack2", &soldier_move_attack2},
+{"soldier_move_attack1", &soldier_move_attack1},
+{"soldier_move_pain4", &soldier_move_pain4},
+{"soldier_move_pain3", &soldier_move_pain3},
+{"soldier_move_pain2", &soldier_move_pain2},
+{"soldier_move_pain1", &soldier_move_pain1},
+{"soldier_move_run", &soldier_move_run},
+{"soldier_move_start_run", &soldier_move_start_run},
+{"soldier_move_walk2", &soldier_move_walk2},
+{"soldier_move_walk1", &soldier_move_walk1},
+{"soldier_move_stand3", &soldier_move_stand3},
+{"soldier_move_stand1", &soldier_move_stand1},
+{"parasite_move_death", &parasite_move_death},
+{"parasite_move_jump_down", &parasite_move_jump_down},
+{"parasite_move_jump_up", &parasite_move_jump_up},
+{"parasite_move_break", &parasite_move_break},
+{"parasite_move_drain", &parasite_move_drain},
+{"parasite_move_pain1", &parasite_move_pain1},
+{"parasite_move_stop_walk", &parasite_move_stop_walk},
+{"parasite_move_start_walk", &parasite_move_start_walk},
+{"parasite_move_walk", &parasite_move_walk},
+{"parasite_move_stop_run", &parasite_move_stop_run},
+{"parasite_move_start_run", &parasite_move_start_run},
+{"parasite_move_run", &parasite_move_run},
+{"parasite_move_stand", &parasite_move_stand},
+{"parasite_move_end_fidget", &parasite_move_end_fidget},
+{"parasite_move_fidget", &parasite_move_fidget},
+{"parasite_move_start_fidget", &parasite_move_start_fidget},
+{"mutant_move_jump_down", &mutant_move_jump_down},
+{"mutant_move_jump_up", &mutant_move_jump_up},
+{"mutant_move_death2", &mutant_move_death2},
+{"mutant_move_death1", &mutant_move_death1},
+{"mutant_move_pain3", &mutant_move_pain3},
+{"mutant_move_pain2", &mutant_move_pain2},
+{"mutant_move_pain1", &mutant_move_pain1},
+{"mutant_move_jump", &mutant_move_jump},
+{"mutant_move_attack", &mutant_move_attack},
+{"mutant_move_run", &mutant_move_run},
+{"mutant_move_start_walk", &mutant_move_start_walk},
+{"mutant_move_walk", &mutant_move_walk},
+{"mutant_move_idle", &mutant_move_idle},
+{"mutant_move_stand", &mutant_move_stand},
+{"medic_move_callReinforcements", &medic_move_callReinforcements},
+{"medic_move_attackCable", &medic_move_attackCable},
+{"medic_move_attackBlaster", &medic_move_attackBlaster},
+{"medic_move_attackHyperBlaster", &medic_move_attackHyperBlaster},
+{"medic_move_duck", &medic_move_duck},
+{"medic_move_death", &medic_move_death},
+{"medic_move_pain2", &medic_move_pain2},
+{"medic_move_pain1", &medic_move_pain1},
+{"medic_move_run", &medic_move_run},
+{"medic_move_walk", &medic_move_walk},
+{"medic_move_stand", &medic_move_stand},
+{"insane_move_struggle_cross", &insane_move_struggle_cross},
+{"insane_move_cross", &insane_move_cross},
+{"insane_move_crawl_death", &insane_move_crawl_death},
+{"insane_move_crawl_pain", &insane_move_crawl_pain},
+{"insane_move_runcrawl", &insane_move_runcrawl},
+{"insane_move_crawl", &insane_move_crawl},
+{"insane_move_stand_death", &insane_move_stand_death},
+{"insane_move_stand_pain", &insane_move_stand_pain},
+{"insane_move_run_insane", &insane_move_run_insane},
+{"insane_move_walk_insane", &insane_move_walk_insane},
+{"insane_move_run_normal", &insane_move_run_normal},
+{"insane_move_walk_normal", &insane_move_walk_normal},
+{"insane_move_down", &insane_move_down},
+{"insane_move_jumpdown", &insane_move_jumpdown},
+{"insane_move_downtoup", &insane_move_downtoup},
+{"insane_move_uptodown", &insane_move_uptodown},
+{"insane_move_stand_insane", &insane_move_stand_insane},
+{"insane_move_stand_normal", &insane_move_stand_normal},
+{"infantry_move_jump2", &infantry_move_jump2},
+{"infantry_move_jump", &infantry_move_jump},
+{"infantry_move_attack2", &infantry_move_attack2},
+{"infantry_move_attack1", &infantry_move_attack1},
+{"infantry_move_duck", &infantry_move_duck},
+{"infantry_move_death3", &infantry_move_death3},
+{"infantry_move_death2", &infantry_move_death2},
+{"infantry_move_death1", &infantry_move_death1},
+{"infantry_move_pain2", &infantry_move_pain2},
+{"infantry_move_pain1", &infantry_move_pain1},
+{"infantry_move_run", &infantry_move_run},
+{"infantry_move_walk", &infantry_move_walk},
+{"infantry_move_fidget", &infantry_move_fidget},
+{"infantry_move_stand", &infantry_move_stand},
+{"hover_move_start_attack2", &hover_move_start_attack2},
+{"hover_move_attack2", &hover_move_attack2},
+{"hover_move_end_attack2", &hover_move_end_attack2},
+{"hover_move_end_attack", &hover_move_end_attack},
+{"hover_move_attack1", &hover_move_attack1},
+{"hover_move_start_attack", &hover_move_start_attack},
+{"hover_move_death1", &hover_move_death1},
+{"hover_move_run", &hover_move_run},
+{"hover_move_walk", &hover_move_walk},
+{"hover_move_pain1", &hover_move_pain1},
+{"hover_move_pain2", &hover_move_pain2},
+{"hover_move_pain3", &hover_move_pain3},
+{"hover_move_stand", &hover_move_stand},
+{"gunner_move_jump2", &gunner_move_jump2},
+{"gunner_move_jump", &gunner_move_jump},
+{"gunner_move_attack_grenade", &gunner_move_attack_grenade},
+{"gunner_move_endfire_chain", &gunner_move_endfire_chain},
+{"gunner_move_fire_chain", &gunner_move_fire_chain},
+{"gunner_move_attack_chain", &gunner_move_attack_chain},
+{"gunner_move_duck", &gunner_move_duck},
+{"gunner_move_death", &gunner_move_death},
+{"gunner_move_pain1", &gunner_move_pain1},
+{"gunner_move_pain2", &gunner_move_pain2},
+{"gunner_move_pain3", &gunner_move_pain3},
+{"gunner_move_runandshoot", &gunner_move_runandshoot},
+{"gunner_move_run", &gunner_move_run},
+{"gunner_move_walk", &gunner_move_walk},
+{"gunner_move_stand", &gunner_move_stand},
+{"gunner_move_fidget", &gunner_move_fidget},
+{"gladiator_move_death", &gladiator_move_death},
+{"gladiator_move_pain_air", &gladiator_move_pain_air},
+{"gladiator_move_pain", &gladiator_move_pain},
+{"gladiator_move_attack_gun", &gladiator_move_attack_gun},
+{"gladiator_move_attack_melee", &gladiator_move_attack_melee},
+{"gladiator_move_run", &gladiator_move_run},
+{"gladiator_move_walk", &gladiator_move_walk},
+{"gladiator_move_stand", &gladiator_move_stand},
+{"flyer_move_loop_melee", &flyer_move_loop_melee},
+{"flyer_move_end_melee", &flyer_move_end_melee},
+{"flyer_move_start_melee", &flyer_move_start_melee},
+{"flyer_move_attack3", &flyer_move_attack3},
+{"flyer_move_attack2", &flyer_move_attack2},
+{"flyer_move_bankleft", &flyer_move_bankleft},
+{"flyer_move_bankright", &flyer_move_bankright},
+{"flyer_move_defense", &flyer_move_defense},
+{"flyer_move_pain1", &flyer_move_pain1},
+{"flyer_move_pain2", &flyer_move_pain2},
+{"flyer_move_pain3", &flyer_move_pain3},
+{"flyer_move_rollleft", &flyer_move_rollleft},
+{"flyer_move_rollright", &flyer_move_rollright},
+{"flyer_move_stop", &flyer_move_stop},
+{"flyer_move_start", &flyer_move_start},
+{"flyer_move_kamikaze", &flyer_move_kamikaze},
+{"flyer_move_run", &flyer_move_run},
+{"flyer_move_walk", &flyer_move_walk},
+{"flyer_move_stand", &flyer_move_stand},
+{"floater_move_run", &floater_move_run},
+{"floater_move_walk", &floater_move_walk},
+{"floater_move_pain3", &floater_move_pain3},
+{"floater_move_pain2", &floater_move_pain2},
+{"floater_move_pain1", &floater_move_pain1},
+{"floater_move_death", &floater_move_death},
+{"floater_move_attack3", &floater_move_attack3},
+{"floater_move_attack2", &floater_move_attack2},
+{"floater_move_attack1a", &floater_move_attack1a},
+{"floater_move_attack1", &floater_move_attack1},
+{"floater_move_activate", &floater_move_activate},
+{"floater_move_stand2", &floater_move_stand2},
+{"floater_move_stand1", &floater_move_stand1},
+{"flipper_move_death", &flipper_move_death},
+{"flipper_move_attack", &flipper_move_attack},
+{"flipper_move_pain1", &flipper_move_pain1},
+{"flipper_move_pain2", &flipper_move_pain2},
+{"flipper_move_start_run", &flipper_move_start_run},
+{"flipper_move_walk", &flipper_move_walk},
+{"flipper_move_run_start", &flipper_move_run_start},
+{"flipper_move_run_loop", &flipper_move_run_loop},
+{"flipper_move_stand", &flipper_move_stand},
+{"chick_move_start_slash", &chick_move_start_slash},
+{"chick_move_end_slash", &chick_move_end_slash},
+{"chick_move_slash", &chick_move_slash},
+{"chick_move_end_attack1", &chick_move_end_attack1},
+{"chick_move_attack1", &chick_move_attack1},
+{"chick_move_start_attack1", &chick_move_start_attack1},
+{"chick_move_duck", &chick_move_duck},
+{"chick_move_death1", &chick_move_death1},
+{"chick_move_death2", &chick_move_death2},
+{"chick_move_pain3", &chick_move_pain3},
+{"chick_move_pain2", &chick_move_pain2},
+{"chick_move_pain1", &chick_move_pain1},
+{"chick_move_walk", &chick_move_walk},
+{"chick_move_run", &chick_move_run},
+{"chick_move_start_run", &chick_move_start_run},
+{"chick_move_stand", &chick_move_stand},
+{"chick_move_fidget", &chick_move_fidget},
+{"carrier_move_death", &carrier_move_death},
+{"carrier_move_pain_light", &carrier_move_pain_light},
+{"carrier_move_pain_heavy", &carrier_move_pain_heavy},
+{"carrier_move_spawn", &carrier_move_spawn},
+{"carrier_move_attack_rail", &carrier_move_attack_rail},
+{"carrier_move_attack_rocket", &carrier_move_attack_rocket},
+{"carrier_move_attack_post_gren", &carrier_move_attack_post_gren},
+{"carrier_move_attack_gren", &carrier_move_attack_gren},
+{"carrier_move_attack_pre_gren", &carrier_move_attack_pre_gren},
+{"carrier_move_attack_post_mg", &carrier_move_attack_post_mg},
+{"carrier_move_attack_mg", &carrier_move_attack_mg},
+{"carrier_move_attack_pre_mg", &carrier_move_attack_pre_mg},
+{"carrier_move_run", &carrier_move_run},
+{"carrier_move_walk", &carrier_move_walk},
+{"carrier_move_stand", &carrier_move_stand},
+{"brain_move_run", &brain_move_run},
+{"brain_move_attack2", &brain_move_attack2},
+{"brain_move_attack1", &brain_move_attack1},
+{"brain_move_death1", &brain_move_death1},
+{"brain_move_death2", &brain_move_death2},
+{"brain_move_duck", &brain_move_duck},
+{"brain_move_pain1", &brain_move_pain1},
+{"brain_move_pain2", &brain_move_pain2},
+{"brain_move_pain3", &brain_move_pain3},
+{"brain_move_defense", &brain_move_defense},
+{"brain_move_walk1", &brain_move_walk1},
+{"brain_move_idle", &brain_move_idle},
+{"brain_move_stand", &brain_move_stand},
+{"makron_move_attack5", &makron_move_attack5},
+{"makron_move_attack4", &makron_move_attack4},
+{"makron_move_attack3", &makron_move_attack3},
+{"makron_move_sight", &makron_move_sight},
+{"makron_move_death3", &makron_move_death3},
+{"makron_move_death2", &makron_move_death2},
+{"makron_move_pain4", &makron_move_pain4},
+{"makron_move_pain5", &makron_move_pain5},
+{"makron_move_pain6", &makron_move_pain6},
+{"makron_move_walk", &makron_move_walk},
+{"makron_move_run", &makron_move_run},
+{"makron_move_stand", &makron_move_stand},
+{"jorg_move_end_attack1", &jorg_move_end_attack1},
+{"jorg_move_attack1", &jorg_move_attack1},
+{"jorg_move_start_attack1", &jorg_move_start_attack1},
+{"jorg_move_attack2", &jorg_move_attack2},
+{"jorg_move_death", &jorg_move_death},
+{"jorg_move_pain1", &jorg_move_pain1},
+{"jorg_move_pain2", &jorg_move_pain2},
+{"jorg_move_pain3", &jorg_move_pain3},
+{"jorg_move_end_walk", &jorg_move_end_walk},
+{"jorg_move_walk", &jorg_move_walk},
+{"jorg_move_start_walk", &jorg_move_start_walk},
+{"jorg_move_run", &jorg_move_run},
+{"jorg_move_stand", &jorg_move_stand},
+{"boss2_move_death", &boss2_move_death},
+{"boss2_move_pain_light", &boss2_move_pain_light},
+{"boss2_move_pain_heavy", &boss2_move_pain_heavy},
+{"boss2_move_attack_rocket", &boss2_move_attack_rocket},
+{"boss2_move_attack_post_mg", &boss2_move_attack_post_mg},
+{"boss2_move_attack_mg", &boss2_move_attack_mg},
+{"boss2_move_attack_pre_mg", &boss2_move_attack_pre_mg},
+{"boss2_move_run", &boss2_move_run},
+{"boss2_move_walk", &boss2_move_walk},
+{"boss2_move_fidget", &boss2_move_fidget},
+{"boss2_move_stand", &boss2_move_stand},
+{"berserk_move_jump2", &berserk_move_jump2},
+{"berserk_move_jump", &berserk_move_jump},
+{"berserk_move_death2", &berserk_move_death2},
+{"berserk_move_death1", &berserk_move_death1},
+{"berserk_move_pain2", &berserk_move_pain2},
+{"berserk_move_pain1", &berserk_move_pain1},
+{"berserk_move_attack_strike", &berserk_move_attack_strike},
+{"berserk_move_attack_club", &berserk_move_attack_club},
+{"berserk_move_attack_spike", &berserk_move_attack_spike},
+{"berserk_move_run1", &berserk_move_run1},
+{"berserk_move_walk", &berserk_move_walk},
+{"berserk_move_stand_fidget", &berserk_move_stand_fidget},
+{"berserk_move_stand", &berserk_move_stand},
+{0, 0}
diff --git a/rogue/src/savegame/tables/levelfields.h b/rogue/src/savegame/tables/levelfields.h
new file mode 100644
index 0000000..796fef8
--- /dev/null
+++ b/rogue/src/savegame/tables/levelfields.h
@@ -0,0 +1,15 @@
+/*
+ * =======================================================================
+ *
+ * Fields inside a level to be saved.
+ *
+ * =======================================================================
+ */
+
+{"changemap", LLOFS(changemap), F_LSTRING},
+{"sight_client", LLOFS(sight_client), F_EDICT},
+{"sight_entity", LLOFS(sight_entity), F_EDICT},
+{"sound_entity", LLOFS(sound_entity), F_EDICT},
+{"sound2_entity", LLOFS(sound2_entity), F_EDICT},
+{"disguise_violator", LLOFS(disguise_violator), F_EDICT},
+{NULL, 0, F_INT}
diff --git a/rogue/src/shared/flash.c b/rogue/src/shared/flash.c
new file mode 100644
index 0000000..943f591
--- /dev/null
+++ b/rogue/src/shared/flash.c
@@ -0,0 +1,463 @@
+/*
+ * =======================================================================
+ *
+ * Muzzle flash posititions.
+ *
+ * =======================================================================
+ */
+
+#include "../header/shared.h"
+
+vec3_t monster_flash_offset[] = {
+ /* flash 0 is not used */
+ {0.0, 0.0, 0.0},
+
+ /* MZ2_TANK_BLASTER_1 1 */
+ {20.7, -18.5, 28.7},
+ /* MZ2_TANK_BLASTER_2 2 */
+ {16.6, -21.5, 30.1},
+ /* MZ2_TANK_BLASTER_3 3 */
+ {11.8, -23.9, 32.1},
+ /* MZ2_TANK_MACHINEGUN_1 4 */
+ {22.9, -0.7, 25.3},
+ /* MZ2_TANK_MACHINEGUN_2 5 */
+ {22.2, 6.2, 22.3},
+ /* MZ2_TANK_MACHINEGUN_3 6 */
+ {19.4, 13.1, 18.6},
+ /* MZ2_TANK_MACHINEGUN_4 7 */
+ {19.4, 18.8, 18.6},
+ /* MZ2_TANK_MACHINEGUN_5 8 */
+ {17.9, 25.0, 18.6},
+ /* MZ2_TANK_MACHINEGUN_6 9 */
+ {14.1, 30.5, 20.6},
+ /* MZ2_TANK_MACHINEGUN_7 10 */
+ {9.3, 35.3, 22.1},
+ /* MZ2_TANK_MACHINEGUN_8 11 */
+ {4.7, 38.4, 22.1},
+ /* MZ2_TANK_MACHINEGUN_9 12 */
+ {-1.1, 40.4, 24.1},
+ /* MZ2_TANK_MACHINEGUN_10 13 */
+ {-6.5, 41.2, 24.1},
+ /* MZ2_TANK_MACHINEGUN_11 14 */
+ {3.2, 40.1, 24.7},
+ /* MZ2_TANK_MACHINEGUN_12 15 */
+ {11.7, 36.7, 26.0},
+ /* MZ2_TANK_MACHINEGUN_13 16 */
+ {18.9, 31.3, 26.0},
+ /* MZ2_TANK_MACHINEGUN_14 17 */
+ {24.4, 24.4, 26.4},
+ /* MZ2_TANK_MACHINEGUN_15 18 */
+ {27.1, 17.1, 27.2},
+ /* MZ2_TANK_MACHINEGUN_16 19 */
+ {28.5, 9.1, 28.0},
+ /* MZ2_TANK_MACHINEGUN_17 20 */
+ {27.1, 2.2, 28.0},
+ /* MZ2_TANK_MACHINEGUN_18 21 */
+ {24.9, -2.8, 28.0},
+ /* MZ2_TANK_MACHINEGUN_19 22 */
+ {21.6, -7.0, 26.4},
+ /* MZ2_TANK_ROCKET_1 23 */
+ {6.2, 29.1, 49.1},
+ /* MZ2_TANK_ROCKET_2 24 */
+ {6.9, 23.8, 49.1},
+ /* MZ2_TANK_ROCKET_3 25 */
+ {8.3, 17.8, 49.5},
+
+ /* MZ2_INFANTRY_MACHINEGUN_1 26 */
+ {26.6, 7.1, 13.1},
+ /* MZ2_INFANTRY_MACHINEGUN_2 27 */
+ {18.2, 7.5, 15.4},
+ /* MZ2_INFANTRY_MACHINEGUN_3 28 */
+ {17.2, 10.3, 17.9},
+ /* MZ2_INFANTRY_MACHINEGUN_4 29 */
+ {17.0, 12.8, 20.1},
+ /* MZ2_INFANTRY_MACHINEGUN_5 30 */
+ {15.1, 14.1, 21.8},
+ /* MZ2_INFANTRY_MACHINEGUN_6 31 */
+ {11.8, 17.2, 23.1},
+ /* MZ2_INFANTRY_MACHINEGUN_7 32 */
+ {11.4, 20.2, 21.0},
+ /* MZ2_INFANTRY_MACHINEGUN_8 33 */
+ {9.0, 23.0, 18.9},
+ /* MZ2_INFANTRY_MACHINEGUN_9 34 */
+ {13.9, 18.6, 17.7},
+ /* MZ2_INFANTRY_MACHINEGUN_10 35 */
+ {15.4, 15.6, 15.8},
+ /* MZ2_INFANTRY_MACHINEGUN_11 36 */
+ {10.2, 15.2, 25.1},
+ /* MZ2_INFANTRY_MACHINEGUN_12 37 */
+ {-1.9, 15.1, 28.2},
+ /* MZ2_INFANTRY_MACHINEGUN_13 38 */
+ {-12.4, 13.0, 20.2},
+
+ /* MZ2_SOLDIER_BLASTER_1 39 */
+ {10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_2 40 */
+ {21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_1 41 */
+ {10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_2 42 */
+ {21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_1 43 */
+ {10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_2 44 */
+ {21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2},
+
+ /* MZ2_GUNNER_MACHINEGUN_1 45 */
+ {30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_2 46 */
+ {29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_3 47 */
+ {28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_4 48 */
+ {28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_5 49 */
+ {26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_6 50 */
+ {26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_7 51 */
+ {26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_8 52 */
+ {29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15},
+ /* MZ2_GUNNER_GRENADE_1 53 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+ /* MZ2_GUNNER_GRENADE_2 54 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+ /* MZ2_GUNNER_GRENADE_3 55 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+ /* MZ2_GUNNER_GRENADE_4 56 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+
+ /* MZ2_CHICK_ROCKET_1 57 */
+ {24.8, -9.0, 39.0},
+
+ /* MZ2_FLYER_BLASTER_1 58 */
+ {12.1, 13.4, -14.5},
+ /* MZ2_FLYER_BLASTER_2 59 */
+ {12.1, -7.4, -14.5},
+
+ /* MZ2_MEDIC_BLASTER_1 60 */
+ {12.1, 5.4, 16.5},
+
+ /* MZ2_GLADIATOR_RAILGUN_1 61 */
+ {30.0, 18.0, 28.0},
+
+ /* MZ2_HOVER_BLASTER_1 62 */
+ {32.5, -0.8, 10.0},
+
+ /* MZ2_ACTOR_MACHINEGUN_1 63 */
+ {18.4, 7.4, 9.6},
+
+ /* MZ2_SUPERTANK_MACHINEGUN_1 64 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_2 65 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_3 66 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_4 67 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_5 68 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_6 69 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_ROCKET_1 70 */
+ {16.0, -22.5, 91.2},
+ /* MZ2_SUPERTANK_ROCKET_2 71 */
+ {16.0, -33.4, 86.7},
+ /* MZ2_SUPERTANK_ROCKET_3 72 */
+ {16.0, -42.8, 83.3},
+
+ /* MZ2_BOSS2_MACHINEGUN_L1 73 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L2 74 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L3 75 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L4 76 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L5 77 */
+ {32, -40, 70},
+
+ /* MZ2_BOSS2_ROCKET_1 78 */
+ {22.0, 16.0, 10.0},
+ /* MZ2_BOSS2_ROCKET_2 79 */
+ {22.0, 8.0, 10.0},
+ /* MZ2_BOSS2_ROCKET_3 80 */
+ {22.0, -8.0, 10.0},
+ /* MZ2_BOSS2_ROCKET_4 81 */
+ {22.0, -16.0, 10.0},
+
+ /* MZ2_FLOAT_BLASTER_1 82 */
+ {32.5, -0.8, 10},
+
+ /* MZ2_SOLDIER_BLASTER_3 83 */
+ {20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_3 84 */
+ {20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_3 85 */
+ {20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_4 86 */
+ {7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_4 87 */
+ {7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_4 88 */
+ {7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_5 89 */
+ {30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_5 90 */
+ {30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_5 91 */
+ {30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_6 92 */
+ {27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_6 93 */
+ {27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_6 94 */
+ {27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_7 95 */
+ {28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_7 96 */
+ {28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_7 97 */
+ {28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_8 98 */
+ {31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_8 99 */
+ {34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_8 100 */
+ {34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2},
+
+ /* MZ2_MAKRON_BFG 101 */
+ {17, -19.5, 62.9},
+ /* MZ2_MAKRON_BLASTER_1 102 */
+ {-3.6, -24.1, 59.5},
+ /* MZ2_MAKRON_BLASTER_2 103 */
+ {-1.6, -19.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_3 104 */
+ {-0.1, -14.4, 59.5},
+ /* MZ2_MAKRON_BLASTER_4 105 */
+ {2.0, -7.6, 59.5},
+ /* MZ2_MAKRON_BLASTER_5 106 */
+ {3.4, 1.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_6 107 */
+ {3.7, 11.1, 59.5},
+ /* MZ2_MAKRON_BLASTER_7 108 */
+ {-0.3, 22.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_8 109 */
+ {-6, 33, 59.5},
+ /* MZ2_MAKRON_BLASTER_9 110 */
+ {-9.3, 36.4, 59.5},
+ /* MZ2_MAKRON_BLASTER_10 111 */
+ {-7, 35, 59.5},
+ /* MZ2_MAKRON_BLASTER_11 112 */
+ {-2.1, 29, 59.5},
+ /* MZ2_MAKRON_BLASTER_12 113 */
+ {3.9, 17.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_13 114 */
+ {6.1, 5.8, 59.5},
+ /* MZ2_MAKRON_BLASTER_14 115 */
+ {5.9, -4.4, 59.5},
+ /* MZ2_MAKRON_BLASTER_15 116 */
+ {4.2, -14.1, 59.5},
+ /* MZ2_MAKRON_BLASTER_16 117 */
+ {2.4, -18.8, 59.5},
+ /* MZ2_MAKRON_BLASTER_17 118 */
+ {-1.8, -25.5, 59.5},
+ /* MZ2_MAKRON_RAILGUN_1 119 */
+ {-17.3, 7.8, 72.4},
+
+ /* MZ2_JORG_MACHINEGUN_L1 120 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L2 121 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L3 122 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L4 123 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L5 124 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L6 125 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_R1 126 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R2 127 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R3 128 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R4 129 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R5 130 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R6 131 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_BFG_1 132 */
+ {6.3, -9, 111.2},
+
+ /* MZ2_BOSS2_MACHINEGUN_R1 73 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R2 74 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R3 75 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R4 76 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R5 77 */
+ {32, 40, 70},
+
+
+ /* MZ2_CARRIER_MACHINEGUN_L1 */
+ {56, -32, 32},
+ /* MZ2_CARRIER_MACHINEGUN_R1 */
+ {56, 32, 32},
+ /* MZ2_CARRIER_GRENADE */
+ {42, 24, 50},
+ /* MZ2_TURRET_MACHINEGUN 141 */
+ {16, 0, 0},
+ /* MZ2_TURRET_ROCKET 142 */
+ {16, 0, 0},
+ /* MZ2_TURRET_BLASTER 143 */
+ {16, 0, 0},
+ /* MZ2_STALKER_BLASTER 144 */
+ {24, 0, 6},
+ /* MZ2_DAEDALUS_BLASTER 145 */
+ {32.5, -0.8, 10.0},
+ /* MZ2_MEDIC_BLASTER_2 146 */
+ {12.1, 5.4, 16.5},
+ /* MZ2_CARRIER_RAILGUN 147 */
+ {32, 0, 6},
+ /* MZ2_WIDOW_DISRUPTOR 148 */
+ {57.72, 14.50, 88.81},
+ /* MZ2_WIDOW_BLASTER 149 */
+ {56, 32, 32},
+ /* MZ2_WIDOW_RAIL 150 */
+ {62, -20, 84},
+ /* MZ2_WIDOW_PLASMABEAM 151 */
+ {32, 0, 6},
+ /* MZ2_CARRIER_MACHINEGUN_L2 152 */
+ {61, -32, 12},
+ /* MZ2_CARRIER_MACHINEGUN_R2 153 */
+ {61, 32, 12},
+ /* MZ2_WIDOW_RAIL_LEFT 154 */
+ {17, -62, 91},
+ /* MZ2_WIDOW_RAIL_RIGHT 155 */
+ {68, 12, 86},
+ /* MZ2_WIDOW_BLASTER_SWEEP1 156 */
+ {47.5, 56, 89},
+ /* MZ2_WIDOW_BLASTER_SWEEP2 157 */
+ {54, 52, 91},
+ /* MZ2_WIDOW_BLASTER_SWEEP3 158 */
+ {58, 40, 91},
+ /* MZ2_WIDOW_BLASTER_SWEEP4 159 */
+ {68, 30, 88},
+ /* MZ2_WIDOW_BLASTER_SWEEP5 160 */
+ {74, 20, 88},
+ /* MZ2_WIDOW_BLASTER_SWEEP6 161 */
+ {73, 11, 87},
+ /* MZ2_WIDOW_BLASTER_SWEEP7 162 */
+ {73, 3, 87},
+ /* MZ2_WIDOW_BLASTER_SWEEP8 163 */
+ {70, -12, 87},
+ /* MZ2_WIDOW_BLASTER_SWEEP9 164 */
+ {67, -20, 90},
+ /* MZ2_WIDOW_BLASTER_100 165 */
+ {-20, 76, 90},
+ /* MZ2_WIDOW_BLASTER_90 166 */
+ {-8, 74, 90},
+ /* MZ2_WIDOW_BLASTER_80 167 */
+ {0, 72, 90},
+ /* MZ2_WIDOW_BLASTER_70 168 d06 */
+ {10, 71, 89},
+ /* MZ2_WIDOW_BLASTER_60 169 d07 */
+ {23, 70, 87},
+ /* MZ2_WIDOW_BLASTER_50 170 d08 */
+ {32, 64, 85},
+ /* MZ2_WIDOW_BLASTER_40 171 */
+ {40, 58, 84},
+ /* MZ2_WIDOW_BLASTER_30 172 d10 */
+ {48, 50, 83},
+ /* MZ2_WIDOW_BLASTER_20 173 */
+ {54, 42, 82},
+ /* MZ2_WIDOW_BLASTER_10 174 d12 */
+ {56, 34, 82},
+ /* MZ2_WIDOW_BLASTER_0 175 */
+ {58, 26, 82},
+ /* MZ2_WIDOW_BLASTER_10L 176 d14 */
+ {60, 16, 82},
+ /* MZ2_WIDOW_BLASTER_20L 177 */
+ {59, 6, 81},
+ /* MZ2_WIDOW_BLASTER_30L 178 d16 */
+ {58, -2, 80},
+ /* MZ2_WIDOW_BLASTER_40L 179 */
+ {57, -10, 79},
+ /* MZ2_WIDOW_BLASTER_50L 180 d18 */
+ {54, -18, 78},
+ /* MZ2_WIDOW_BLASTER_60L 181 */
+ {42, -32, 80},
+ /* MZ2_WIDOW_BLASTER_70L 182 d20 */
+ {36, -40, 78},
+ /* MZ2_WIDOW_RUN_1 183 */
+ {68.4, 10.88, 82.08},
+ /* MZ2_WIDOW_RUN_2 184 */
+ {68.51, 8.64, 85.14},
+ /* MZ2_WIDOW_RUN_3 185 */
+ {68.66, 6.38, 88.78},
+ /* MZ2_WIDOW_RUN_4 186 */
+ {68.73, 5.1, 84.47},
+ /* MZ2_WIDOW_RUN_5 187 */
+ {68.82, 4.79, 80.52},
+ /* MZ2_WIDOW_RUN_6 188 */
+ {68.77, 6.11, 85.37},
+ /* MZ2_WIDOW_RUN_7 189 */
+ {68.67, 7.99, 90.24},
+ /* MZ2_WIDOW_RUN_8 190 */
+ {68.55, 9.54, 87.36},
+ /* MZ2_CARRIER_ROCKET_1 191 */
+ {0, 0, -5},
+ /* MZ2_CARRIER_ROCKET_2 192 */
+ {0, 0, -5},
+ /* MZ2_CARRIER_ROCKET_3 193 */
+ {0, 0, -5},
+ /* MZ2_CARRIER_ROCKET_4 194 */
+ {0, 0, -5},
+ /* MZ2_WIDOW2_BEAMER_1 195 */
+ /* { 72.13, -17.63, 93.77 }, */
+ {69.00, -17.63, 93.77},
+ /* MZ2_WIDOW2_BEAMER_2 196 */
+ /* { 71.46, -17.08, 89.82 }, */
+ {69.00, -17.08, 89.82},
+ /* MZ2_WIDOW2_BEAMER_3 197 */
+ /* { 71.47, -18.40, 90.70 }, */
+ {69.00, -18.40, 90.70},
+ /* MZ2_WIDOW2_BEAMER_4 198 */
+ /* { 71.96, -18.34, 94.32 }, */
+ {69.00, -18.34, 94.32},
+ /* MZ2_WIDOW2_BEAMER_5 199 */
+ /* { 72.25, -18.30, 97.98 }, */
+ {69.00, -18.30, 97.98},
+ /* MZ2_WIDOW2_BEAM_SWEEP_1 200 */
+ {45.04, -59.02, 92.24},
+ /* MZ2_WIDOW2_BEAM_SWEEP_2 201 */
+ {50.68, -54.70, 91.96},
+ /* MZ2_WIDOW2_BEAM_SWEEP_3 202 */
+ {56.57, -47.72, 91.65},
+ /* MZ2_WIDOW2_BEAM_SWEEP_4 203 */
+ {61.75, -38.75, 91.38},
+ /* MZ2_WIDOW2_BEAM_SWEEP_5 204 */
+ {65.55, -28.76, 91.24},
+ /* MZ2_WIDOW2_BEAM_SWEEP_6 205 */
+ {67.79, -18.90, 91.22},
+ /* MZ2_WIDOW2_BEAM_SWEEP_7 206 */
+ {68.60, -9.52, 91.23},
+ /* MZ2_WIDOW2_BEAM_SWEEP_8 207 */
+ {68.08, 0.18, 91.32},
+ /* MZ2_WIDOW2_BEAM_SWEEP_9 208 */
+ {66.14, 9.79, 91.44},
+ /* MZ2_WIDOW2_BEAM_SWEEP_10 209 */
+ {62.77, 18.91, 91.65},
+ /* MZ2_WIDOW2_BEAM_SWEEP_11 210 */
+ {58.29, 27.11, 92.00},
+
+ /* end of table */
+ {0.0, 0.0, 0.0}
+};
+
diff --git a/rogue/src/shared/rand.c b/rogue/src/shared/rand.c
new file mode 100644
index 0000000..22b4a71
--- /dev/null
+++ b/rogue/src/shared/rand.c
@@ -0,0 +1,97 @@
+/*
+ * KISS PRNG (c) 2011 Shinobu
+ *
+ * This file was optained from zuttobenkyou.wordpress.com
+ * and modified by the Yamagi Quake II developers.
+ *
+ * LICENSE: Public domain
+ *
+ * =======================================================================
+ *
+ * KISS PRNG, as devised by Dr. George Marsaglia
+ *
+ * =======================================================================
+ */
+
+#include <stdint.h>
+
+#define QSIZE 0x200000
+#define CNG (cng = 6906969069ULL * cng + 13579)
+#define XS (xs ^= (xs << 13), xs ^= (xs >> 17), xs ^= (xs << 43))
+#define KISS (B64MWC() + CNG + XS)
+
+static uint64_t QARY[QSIZE];
+static int j;
+static uint64_t carry;
+static uint64_t xs;
+static uint64_t cng;
+
+uint64_t
+B64MWC(void)
+{
+ uint64_t t, x;
+
+ j = (j + 1) & (QSIZE - 1);
+ x = QARY[j];
+ t = (x << 28) + carry;
+ carry = (x >> 36) - (t < x);
+ return QARY[j] = t - x;
+}
+
+/*
+ * Generate a pseudorandom
+ * integer >0.
+ */
+int
+randk(void)
+{
+ int r;
+
+ r = (int)KISS;
+ r = (r < 0) ? (r * -1) : r;
+
+ return r;
+}
+
+/*
+ * Generate a pseudorandom
+ * signed float between
+ * 0 and 1.
+ */
+float
+frandk(void)
+{
+ return (randk()&32767)* (1.0/32767);
+}
+
+/* Generate a pseudorandom
+ * float between -1 and 1.
+ */
+float
+crandk(void)
+{
+ return (randk()&32767)* (2.0/32767) - 1;
+}
+
+/*
+ * Seeds the PRNG
+ */
+void
+randk_seed(void)
+{
+ uint64_t i;
+
+ /* Seed QARY[] with CNG+XS: */
+ for (i = 0; i < QSIZE; i++)
+ {
+ QARY[i] = CNG + XS;
+ }
+
+ /* Run through several rounds
+ to warm up the state */
+ for (i = 0; i < 256; i++)
+ {
+ randk();
+ }
+}
+
diff --git a/rogue/src/shared/shared.c b/rogue/src/shared/shared.c
new file mode 100644
index 0000000..81eef9d
--- /dev/null
+++ b/rogue/src/shared/shared.c
@@ -0,0 +1,1347 @@
+/*
+ * =======================================================================
+ *
+ * Support functions, linked into client, server, renderer and game.
+ *
+ * =======================================================================
+ */
+
+#include <ctype.h>
+
+#include "../header/shared.h"
+
+#define DEG2RAD(a) (a * M_PI) / 180.0F
+
+vec3_t vec3_origin = {0, 0, 0};
+
+/* ============================================================================ */
+
+void
+RotatePointAroundVector(vec3_t dst, const vec3_t dir,
+ const vec3_t point, float degrees)
+{
+ float m[3][3];
+ float im[3][3];
+ float zrot[3][3];
+ float tmpmat[3][3];
+ float rot[3][3];
+ int i;
+ vec3_t vr, vup, vf;
+
+ vf[0] = dir[0];
+ vf[1] = dir[1];
+ vf[2] = dir[2];
+
+ PerpendicularVector(vr, dir);
+ CrossProduct(vr, vf, vup);
+
+ m[0][0] = vr[0];
+ m[1][0] = vr[1];
+ m[2][0] = vr[2];
+
+ m[0][1] = vup[0];
+ m[1][1] = vup[1];
+ m[2][1] = vup[2];
+
+ m[0][2] = vf[0];
+ m[1][2] = vf[1];
+ m[2][2] = vf[2];
+
+ memcpy(im, m, sizeof(im));
+
+ im[0][1] = m[1][0];
+ im[0][2] = m[2][0];
+ im[1][0] = m[0][1];
+ im[1][2] = m[2][1];
+ im[2][0] = m[0][2];
+ im[2][1] = m[1][2];
+
+ memset(zrot, 0, sizeof(zrot));
+ zrot[1][1] = zrot[2][2] = 1.0F;
+
+ zrot[0][0] = (float)cos(DEG2RAD(degrees));
+ zrot[0][1] = (float)sin(DEG2RAD(degrees));
+ zrot[1][0] = (float)-sin(DEG2RAD(degrees));
+ zrot[1][1] = (float)cos(DEG2RAD(degrees));
+
+ R_ConcatRotations(m, zrot, tmpmat);
+ R_ConcatRotations(tmpmat, im, rot);
+
+ for (i = 0; i < 3; i++)
+ {
+ dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] *
+ point[2];
+ }
+}
+
+void
+AngleVectors(vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
+{
+ float angle;
+ static float sr, sp, sy, cr, cp, cy;
+
+ angle = angles[YAW] * (M_PI * 2 / 360);
+ sy = (float)sin(angle);
+ cy = (float)cos(angle);
+ angle = angles[PITCH] * (M_PI * 2 / 360);
+ sp = (float)sin(angle);
+ cp = (float)cos(angle);
+ angle = angles[ROLL] * (M_PI * 2 / 360);
+ sr = (float)sin(angle);
+ cr = (float)cos(angle);
+
+ if (forward)
+ {
+ forward[0] = cp * cy;
+ forward[1] = cp * sy;
+ forward[2] = -sp;
+ }
+
+ if (right)
+ {
+ right[0] = (-1 * sr * sp * cy + - 1 * cr * -sy);
+ right[1] = (-1 * sr * sp * sy + - 1 * cr * cy);
+ right[2] = -1 * sr * cp;
+ }
+
+ if (up)
+ {
+ up[0] = (cr * sp * cy + - sr * -sy);
+ up[1] = (cr * sp * sy + - sr * cy);
+ up[2] = cr * cp;
+ }
+}
+
+void
+AngleVectors2(vec3_t value1, vec3_t angles)
+{
+ float forward;
+ float yaw, pitch;
+
+ if ((value1[1] == 0) && (value1[0] == 0))
+ {
+ yaw = 0;
+
+ if (value1[2] > 0)
+ {
+ pitch = 90;
+ }
+
+ else
+ {
+ pitch = 270;
+ }
+ }
+ else
+ {
+ if (value1[0])
+ {
+ yaw = ((float)atan2(value1[1], value1[0]) * 180 / M_PI);
+ }
+
+ else if (value1[1] > 0)
+ {
+ yaw = 90;
+ }
+
+ else
+ {
+ yaw = 270;
+ }
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+
+ forward = (float)sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
+ pitch = ((float)atan2(value1[2], forward) * 180 / M_PI);
+
+ if (pitch < 0)
+ {
+ pitch += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = 0;
+}
+
+void
+ProjectPointOnPlane(vec3_t dst, const vec3_t p, const vec3_t normal)
+{
+ float d;
+ vec3_t n;
+ float inv_denom;
+
+ inv_denom = 1.0F / DotProduct(normal, normal);
+
+ d = DotProduct(normal, p) * inv_denom;
+
+ n[0] = normal[0] * inv_denom;
+ n[1] = normal[1] * inv_denom;
+ n[2] = normal[2] * inv_denom;
+
+ dst[0] = p[0] - d * n[0];
+ dst[1] = p[1] - d * n[1];
+ dst[2] = p[2] - d * n[2];
+}
+
+/* assumes "src" is normalized */
+void
+PerpendicularVector(vec3_t dst, const vec3_t src)
+{
+ int pos;
+ int i;
+ float minelem = 1.0F;
+ vec3_t tempvec;
+
+ /* find the smallest magnitude axially aligned vector */
+ for (pos = 0, i = 0; i < 3; i++)
+ {
+ if (fabs(src[i]) < minelem)
+ {
+ pos = i;
+ minelem = (float)fabs(src[i]);
+ }
+ }
+
+ tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
+ tempvec[pos] = 1.0F;
+
+ /* project the point onto the plane defined by src */
+ ProjectPointOnPlane(dst, tempvec, src);
+
+ /* normalize the result */
+ VectorNormalize(dst);
+}
+
+void
+R_ConcatRotations(float in1[3][3], float in2[3][3], float out[3][3])
+{
+ out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+ in1[0][2] * in2[2][0];
+ out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+ in1[0][2] * in2[2][1];
+ out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+ in1[0][2] * in2[2][2];
+ out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+ in1[1][2] * in2[2][0];
+ out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+ in1[1][2] * in2[2][1];
+ out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+ in1[1][2] * in2[2][2];
+ out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+ in1[2][2] * in2[2][0];
+ out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+ in1[2][2] * in2[2][1];
+ out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+ in1[2][2] * in2[2][2];
+}
+
+void
+R_ConcatTransforms(float in1[3][4], float in2[3][4], float out[3][4])
+{
+ out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+ in1[0][2] * in2[2][0];
+ out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+ in1[0][2] * in2[2][1];
+ out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+ in1[0][2] * in2[2][2];
+ out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
+ in1[0][2] * in2[2][3] + in1[0][3];
+ out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+ in1[1][2] * in2[2][0];
+ out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+ in1[1][2] * in2[2][1];
+ out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+ in1[1][2] * in2[2][2];
+ out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
+ in1[1][2] * in2[2][3] + in1[1][3];
+ out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+ in1[2][2] * in2[2][0];
+ out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+ in1[2][2] * in2[2][1];
+ out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+ in1[2][2] * in2[2][2];
+ out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
+ in1[2][2] * in2[2][3] + in1[2][3];
+}
+
+/* ============================================================================ */
+
+float
+Q_fabs(float f)
+{
+ int tmp = *(int *)&f;
+
+ tmp &= 0x7FFFFFFF;
+ return *(float *)&tmp;
+}
+
+float
+LerpAngle(float a2, float a1, float frac)
+{
+ if (a1 - a2 > 180)
+ {
+ a1 -= 360;
+ }
+
+ if (a1 - a2 < -180)
+ {
+ a1 += 360;
+ }
+
+ return a2 + frac * (a1 - a2);
+}
+
+float
+anglemod(float a)
+{
+ a = (360.0 / 65536) * ((int)(a * (65536 / 360.0)) & 65535);
+ return a;
+}
+
+/*
+ * This is the slow, general version
+ */
+int
+BoxOnPlaneSide2(vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ int i;
+ float dist1, dist2;
+ int sides;
+ vec3_t corners[2];
+
+ for (i = 0; i < 3; i++)
+ {
+ if (p->normal[i] < 0)
+ {
+ corners[0][i] = emins[i];
+ corners[1][i] = emaxs[i];
+ }
+ else
+ {
+ corners[1][i] = emins[i];
+ corners[0][i] = emaxs[i];
+ }
+ }
+
+ dist1 = DotProduct(p->normal, corners[0]) - p->dist;
+ dist2 = DotProduct(p->normal, corners[1]) - p->dist;
+ sides = 0;
+
+ if (dist1 >= 0)
+ {
+ sides = 1;
+ }
+
+ if (dist2 < 0)
+ {
+ sides |= 2;
+ }
+
+ return sides;
+}
+
+/*
+ * Returns 1, 2, or 1 + 2
+ */
+int
+BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ float dist1, dist2;
+ int sides;
+
+ /* fast axial cases */
+ if (p->type < 3)
+ {
+ if (p->dist <= emins[p->type])
+ {
+ return 1;
+ }
+
+ if (p->dist >= emaxs[p->type])
+ {
+ return 2;
+ }
+
+ return 3;
+ }
+
+ /* general case */
+ switch (p->signbits)
+ {
+ case 0:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 1:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 2:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 3:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 4:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ case 5:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ case 6:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ case 7:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ default:
+ dist1 = dist2 = 0;
+ break;
+ }
+
+ sides = 0;
+
+ if (dist1 >= p->dist)
+ {
+ sides = 1;
+ }
+
+ if (dist2 < p->dist)
+ {
+ sides |= 2;
+ }
+
+ return sides;
+}
+
+void
+ClearBounds(vec3_t mins, vec3_t maxs)
+{
+ mins[0] = mins[1] = mins[2] = 99999;
+ maxs[0] = maxs[1] = maxs[2] = -99999;
+}
+
+void
+AddPointToBounds(vec3_t v, vec3_t mins, vec3_t maxs)
+{
+ int i;
+ vec_t val;
+
+ for (i = 0; i < 3; i++)
+ {
+ val = v[i];
+
+ if (val < mins[i])
+ {
+ mins[i] = val;
+ }
+
+ if (val > maxs[i])
+ {
+ maxs[i] = val;
+ }
+ }
+}
+
+int
+VectorCompare(vec3_t v1, vec3_t v2)
+{
+ if ((v1[0] != v2[0]) || (v1[1] != v2[1]) || (v1[2] != v2[2]))
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+vec_t
+VectorNormalize(vec3_t v)
+{
+ float length, ilength;
+
+ length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
+ length = (float)sqrt(length);
+
+ if (length)
+ {
+ ilength = 1 / length;
+ v[0] *= ilength;
+ v[1] *= ilength;
+ v[2] *= ilength;
+ }
+
+ return length;
+}
+
+vec_t
+VectorNormalize2(vec3_t v, vec3_t out)
+{
+ VectorCopy(v, out);
+
+ return VectorNormalize(out);
+}
+
+void
+VectorMA(vec3_t veca, float scale, vec3_t vecb, vec3_t vecc)
+{
+ vecc[0] = veca[0] + scale * vecb[0];
+ vecc[1] = veca[1] + scale * vecb[1];
+ vecc[2] = veca[2] + scale * vecb[2];
+}
+
+vec_t
+_DotProduct(vec3_t v1, vec3_t v2)
+{
+ return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+}
+
+void
+_VectorSubtract(vec3_t veca, vec3_t vecb, vec3_t out)
+{
+ out[0] = veca[0] - vecb[0];
+ out[1] = veca[1] - vecb[1];
+ out[2] = veca[2] - vecb[2];
+}
+
+void
+_VectorAdd(vec3_t veca, vec3_t vecb, vec3_t out)
+{
+ out[0] = veca[0] + vecb[0];
+ out[1] = veca[1] + vecb[1];
+ out[2] = veca[2] + vecb[2];
+}
+
+void
+_VectorCopy(vec3_t in, vec3_t out)
+{
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+}
+
+void
+CrossProduct(vec3_t v1, vec3_t v2, vec3_t cross)
+{
+ cross[0] = v1[1] * v2[2] - v1[2] * v2[1];
+ cross[1] = v1[2] * v2[0] - v1[0] * v2[2];
+ cross[2] = v1[0] * v2[1] - v1[1] * v2[0];
+}
+
+double sqrt(double x);
+
+vec_t
+VectorLength(vec3_t v)
+{
+ int i;
+ float length;
+
+ length = 0;
+
+ for (i = 0; i < 3; i++)
+ {
+ length += v[i] * v[i];
+ }
+
+ length = (float)sqrt(length);
+
+ return length;
+}
+
+void
+VectorInverse(vec3_t v)
+{
+ v[0] = -v[0];
+ v[1] = -v[1];
+ v[2] = -v[2];
+}
+
+void
+VectorScale(vec3_t in, vec_t scale, vec3_t out)
+{
+ out[0] = in[0] * scale;
+ out[1] = in[1] * scale;
+ out[2] = in[2] * scale;
+}
+
+int
+Q_log2(int val)
+{
+ int answer = 0;
+
+ while (val >>= 1)
+ {
+ answer++;
+ }
+
+ return answer;
+}
+
+/* ==================================================================================== */
+
+char *
+COM_SkipPath(char *pathname)
+{
+ char *last;
+
+ last = pathname;
+
+ while (*pathname)
+ {
+ if (*pathname == '/')
+ {
+ last = pathname + 1;
+ }
+
+ pathname++;
+ }
+
+ return last;
+}
+
+void
+COM_StripExtension(char *in, char *out)
+{
+ while (*in && *in != '.')
+ {
+ *out++ = *in++;
+ }
+
+ *out = 0;
+}
+
+const char *
+COM_FileExtension(const char *in)
+{
+ const char *ext = strrchr(in, '.');
+
+ if (!ext || ext == in)
+ {
+ return "";
+ }
+
+ return ext + 1;
+}
+
+void
+COM_FileBase(char *in, char *out)
+{
+ char *s, *s2;
+
+ s = in + strlen(in) - 1;
+
+ while (s != in && *s != '.')
+ {
+ s--;
+ }
+
+ for (s2 = s; s2 != in && *s2 != '/'; s2--)
+ {
+ }
+
+ if (s - s2 < 2)
+ {
+ out[0] = 0;
+ }
+ else
+ {
+ s--;
+ strncpy(out, s2 + 1, s - s2);
+ out[s - s2] = 0;
+ }
+}
+
+/*
+ * Returns the path up to, but not including the last /
+ */
+void
+COM_FilePath(const char *in, char *out)
+{
+ const char *s;
+
+ s = in + strlen(in) - 1;
+
+ while (s != in && *s != '/')
+ {
+ s--;
+ }
+
+ strncpy(out, in, s - in);
+ out[s - in] = 0;
+}
+
+void
+COM_DefaultExtension(char *path, const char *extension)
+{
+ char *src;
+
+ /* */
+ /* if path doesn't have a .EXT, append extension */
+ /* (extension should include the .) */
+ /* */
+ src = path + strlen(path) - 1;
+
+ while (*src != '/' && src != path)
+ {
+ if (*src == '.')
+ {
+ return; /* it has an extension */
+ }
+
+ src--;
+ }
+
+ strcat(path, extension);
+}
+
+/*
+ * ============================================================================
+ *
+ * BYTE ORDER FUNCTIONS
+ *
+ * ============================================================================
+ */
+
+qboolean bigendien;
+
+/* can't just use function pointers, or dll linkage can
+ mess up when qcommon is included in multiple places */
+short (*_BigShort)(short l);
+short (*_LittleShort)(short l);
+int (*_BigLong)(int l);
+int (*_LittleLong)(int l);
+float (*_BigFloat)(float l);
+float (*_LittleFloat)(float l);
+
+short
+BigShort(short l)
+{
+ return _BigShort(l);
+}
+
+short
+LittleShort(short l)
+{
+ return _LittleShort(l);
+}
+
+int
+BigLong(int l)
+{
+ return _BigLong(l);
+}
+
+int
+LittleLong(int l)
+{
+ return _LittleLong(l);
+}
+
+float
+BigFloat(float l)
+{
+ return _BigFloat(l);
+}
+
+float
+LittleFloat(float l)
+{
+ return _LittleFloat(l);
+}
+
+short
+ShortSwap(short l)
+{
+ byte b1, b2;
+
+ b1 = l & 255;
+ b2 = (l >> 8) & 255;
+
+ return (b1 << 8) + b2;
+}
+
+short
+ShortNoSwap(short l)
+{
+ return l;
+}
+
+int
+LongSwap(int l)
+{
+ byte b1, b2, b3, b4;
+
+ b1 = l & 255;
+ b2 = (l >> 8) & 255;
+ b3 = (l >> 16) & 255;
+ b4 = (l >> 24) & 255;
+
+ return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4;
+}
+
+int
+LongNoSwap(int l)
+{
+ return l;
+}
+
+float
+FloatSwap(float f)
+{
+ union
+ {
+ float f;
+ byte b[4];
+ } dat1, dat2;
+
+ dat1.f = f;
+ dat2.b[0] = dat1.b[3];
+ dat2.b[1] = dat1.b[2];
+ dat2.b[2] = dat1.b[1];
+ dat2.b[3] = dat1.b[0];
+ return dat2.f;
+}
+
+float
+FloatNoSwap(float f)
+{
+ return f;
+}
+
+void
+Swap_Init(void)
+{
+ byte swaptest[2] = {1, 0};
+
+ /* set the byte swapping variables in a portable manner */
+ /* PVS NOTE: maybe use memcpy instead? */
+ if (*(short *)swaptest == 1)
+ {
+ bigendien = false;
+ _BigShort = ShortSwap;
+ _LittleShort = ShortNoSwap;
+ _BigLong = LongSwap;
+ _LittleLong = LongNoSwap;
+ _BigFloat = FloatSwap;
+ _LittleFloat = FloatNoSwap;
+ Com_Printf("Byte ordering: little endian\n\n");
+ }
+ else
+ {
+ bigendien = true;
+ _BigShort = ShortNoSwap;
+ _LittleShort = ShortSwap;
+ _BigLong = LongNoSwap;
+ _LittleLong = LongSwap;
+ _BigFloat = FloatNoSwap;
+ _LittleFloat = FloatSwap;
+ Com_Printf("Byte ordering: big endian\n\n");
+ }
+
+ if (LittleShort(*(short *)swaptest) != 1)
+ assert("Error in the endian conversion!");
+}
+
+/*
+ * does a varargs printf into a temp buffer, so I don't
+ * need to have varargs versions of all text functions.
+ */
+char *
+va(const char *format, ...)
+{
+ va_list argptr;
+ static char string[1024];
+
+ va_start(argptr, format);
+ vsnprintf(string, 1024, format, argptr);
+ va_end(argptr);
+
+ return string;
+}
+
+char com_token[MAX_TOKEN_CHARS];
+
+/*
+ * Parse a token out of a string
+ */
+char *
+COM_Parse(char **data_p)
+{
+ int c;
+ int len;
+ char *data;
+
+ data = *data_p;
+ len = 0;
+ com_token[0] = 0;
+
+ if (!data)
+ {
+ *data_p = NULL;
+ return "";
+ }
+
+skipwhite:
+
+ while ((c = *data) <= ' ')
+ {
+ if (c == 0)
+ {
+ *data_p = NULL;
+ return "";
+ }
+
+ data++;
+ }
+
+ /* skip // comments */
+ if ((c == '/') && (data[1] == '/'))
+ {
+ while (*data && *data != '\n')
+ {
+ data++;
+ }
+
+ goto skipwhite;
+ }
+
+ /* handle quoted strings specially */
+ if (c == '\"')
+ {
+ data++;
+
+ while (1)
+ {
+ c = *data++;
+
+ if ((c == '\"') || !c)
+ {
+ com_token[len] = 0;
+ *data_p = data;
+ return com_token;
+ }
+
+ if (len < MAX_TOKEN_CHARS)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ }
+ }
+
+ /* parse a regular word */
+ do
+ {
+ if (len < MAX_TOKEN_CHARS)
+ {
+ com_token[len] = c;
+ len++;
+ }
+
+ data++;
+ c = *data;
+ }
+ while (c > 32);
+
+ if (len == MAX_TOKEN_CHARS)
+ {
+ len = 0;
+ }
+
+ com_token[len] = 0;
+
+ *data_p = data;
+ return com_token;
+}
+
+int paged_total;
+
+void
+Com_PageInMemory(byte *buffer, int size)
+{
+ int i;
+
+ for (i = size - 1; i > 0; i -= 4096)
+ {
+ paged_total += buffer[i];
+ }
+}
+
+/*
+ * ============================================================================
+ *
+ * LIBRARY REPLACEMENT FUNCTIONS
+ *
+ * ============================================================================
+ */
+
+int
+Q_stricmp(const char *s1, const char *s2)
+{
+ return strcasecmp(s1, s2);
+}
+
+int
+Q_strncasecmp(char *s1, char *s2, int n)
+{
+ int c1, c2;
+
+ do
+ {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (!n--)
+ {
+ return 0; /* strings are equal until end point */
+ }
+
+ if (c1 != c2)
+ {
+ if ((c1 >= 'a') && (c1 <= 'z'))
+ {
+ c1 -= ('a' - 'A');
+ }
+
+ if ((c2 >= 'a') && (c2 <= 'z'))
+ {
+ c2 -= ('a' - 'A');
+ }
+
+ if (c1 != c2)
+ {
+ return -1; /* strings not equal */
+ }
+ }
+ }
+ while (c1);
+
+ return 0; /* strings are equal */
+}
+
+int
+Q_strcasecmp(char *s1, char *s2)
+{
+ return Q_strncasecmp(s1, s2, 99999);
+}
+
+void
+Com_sprintf(char *dest, int size, char *fmt, ...)
+{
+ int len;
+ va_list argptr;
+ static char bigbuffer[0x10000];
+
+ va_start(argptr, fmt);
+ len = vsnprintf(bigbuffer, 0x10000, fmt, argptr);
+ va_end(argptr);
+
+ if (len >= size)
+ {
+ Com_Printf("Com_sprintf: overflow\n");
+
+ dest = NULL;
+ return;
+ }
+
+ bigbuffer[size - 1] = '\0';
+ strcpy(dest, bigbuffer);
+}
+
+char *
+strlwr ( char *s )
+{
+ char *p = s;
+
+ while ( *s )
+ {
+ *s = tolower( *s );
+ s++;
+ }
+
+ return ( p );
+}
+
+int
+Q_strlcpy(char *dst, const char *src, int size)
+{
+ const char *s = src;
+
+ while (*s)
+ {
+ if (size > 1)
+ {
+ *dst++ = *s;
+ size--;
+ }
+ s++;
+ }
+ if (size > 0)
+ {
+ *dst = '\0';
+ }
+
+ return s - src;
+}
+
+int
+Q_strlcat(char *dst, const char *src, int size)
+{
+ char *d = dst;
+
+ while (size > 0 && *d)
+ {
+ size--;
+ d++;
+ }
+
+ return (d - dst) + Q_strlcpy(d, src, size);
+}
+
+/*
+ * =====================================================================
+ *
+ * INFO STRINGS
+ *
+ * =====================================================================
+ */
+
+/*
+ * Searches the string for the given
+ * key and returns the associated value,
+ * or an empty string.
+ */
+char *
+Info_ValueForKey(char *s, char *key)
+{
+ char pkey[512];
+ static char value[2][512]; /* use two buffers so compares
+ work without stomping on each other */
+ static int valueindex;
+ char *o;
+
+ valueindex ^= 1;
+
+ if (*s == '\\')
+ {
+ s++;
+ }
+
+ while (1)
+ {
+ o = pkey;
+
+ while (*s != '\\')
+ {
+ if (!*s)
+ {
+ return "";
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+ s++;
+
+ o = value[valueindex];
+
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ {
+ return "";
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+
+ if (!strcmp(key, pkey))
+ {
+ return value[valueindex];
+ }
+
+ if (!*s)
+ {
+ return "";
+ }
+
+ s++;
+ }
+}
+
+void
+Info_RemoveKey(char *s, char *key)
+{
+ char *start;
+ char pkey[512];
+ char value[512];
+ char *o;
+
+ if (strstr(key, "\\"))
+ {
+ return;
+ }
+
+ while (1)
+ {
+ start = s;
+
+ if (*s == '\\')
+ {
+ s++;
+ }
+
+ o = pkey;
+
+ while (*s != '\\')
+ {
+ if (!*s)
+ {
+ return;
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+ s++;
+
+ o = value;
+
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ {
+ return;
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+
+ if (!strcmp(key, pkey))
+ {
+ memmove(start, s, strlen(s) + 1); /* remove this part */
+ return;
+ }
+
+ if (!*s)
+ {
+ return;
+ }
+ }
+}
+
+/*
+ * Some characters are illegal in info strings
+ * because they can mess up the server's parsing
+ */
+qboolean
+Info_Validate(char *s)
+{
+ if (strstr(s, "\""))
+ {
+ return false;
+ }
+
+ if (strstr(s, ";"))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+Info_SetValueForKey(char *s, char *key, char *value)
+{
+ char newi[MAX_INFO_STRING], *v;
+ int c;
+ int maxsize = MAX_INFO_STRING;
+
+ if (strstr(key, "\\") || strstr(value, "\\"))
+ {
+ Com_Printf("Can't use keys or values with a \\\n");
+ return;
+ }
+
+ if (strstr(key, ";"))
+ {
+ Com_Printf("Can't use keys or values with a semicolon\n");
+ return;
+ }
+
+ if (strstr(key, "\"") || strstr(value, "\""))
+ {
+ Com_Printf("Can't use keys or values with a \"\n");
+ return;
+ }
+
+ if ((strlen(key) > MAX_INFO_KEY - 1) || (strlen(value) > MAX_INFO_KEY - 1))
+ {
+ Com_Printf("Keys and values must be < 64 characters.\n");
+ return;
+ }
+
+ Info_RemoveKey(s, key);
+
+ if (*value == '\0')
+ {
+ return;
+ }
+
+ Com_sprintf(newi, sizeof(newi), "\\%s\\%s", key, value);
+
+ if (strlen(newi) + strlen(s) >= maxsize)
+ {
+ Com_Printf("Info string length exceeded\n");
+ return;
+ }
+
+ /* only copy ascii values */
+ s += strlen(s);
+ v = newi;
+
+ while (*v)
+ {
+ c = *v++;
+ c &= 127; /* strip high bits */
+
+ if ((c >= 32) && (c < 127))
+ {
+ *s++ = c;
+ }
+ }
+
+ *s = 0;
+}
diff --git a/rogue/stuff/mapfixes/rammo1.ent b/rogue/stuff/mapfixes/rammo1.ent
new file mode 100644
index 0000000..757bd80
--- /dev/null
+++ b/rogue/stuff/mapfixes/rammo1.ent
@@ -0,0 +1,6123 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Fixed unreachable monster_soldier (2803)
+//
+// He is triggered spawn but has no targetname. Gave him
+// targetname t153, so he triggers when you return from rammo2.
+//
+// 2. Removed target-less trigger_relay (2423)
+//
+// Changed its spawnflags from 2048 to 7936 (never spawns).
+//
+// 3. Removed target-less trigger_once (2401)
+//
+// Changed its spawnflags from 2048 to 7936 (never spawns).
+{
+"sounds" "2"
+"sky" "rogue2"
+"nextmap" "rammo2"
+"message" "Munitions Plant"
+"classname" "worldspawn"
+}
+{
+"classname" "info_player_coop"
+"angle" "270"
+"origin" "-592 -744 1448"
+}
+{
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "-624 -708 1448"
+}
+{
+"classname" "info_player_coop"
+"angle" "270"
+"origin" "-592 -664 1448"
+}
+{
+"spawnflags" "1536"
+"classname" "item_health_large"
+"origin" "-488 -896 1424"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "1024"
+"origin" "-488 -856 1424"
+}
+{
+"classname" "func_timer"
+"target" "t237"
+"wait" "5"
+"random" "2.5"
+"targetname" "t108"
+"origin" "512 -464 1760"
+}
+{
+"classname" "target_splash"
+"angle" "90"
+"targetname" "t237"
+"origin" "552 -448 1760"
+}
+{
+"model" "*1"
+"classname" "func_explosive"
+"dmg" "100"
+"health" "5"
+}
+{
+"targetname" "lasers"
+"spawnflags" "1"
+"classname" "target_speaker"
+"noise" "world/fan1.wav"
+"origin" "-1792 144 1568"
+}
+{
+"origin" "-1920 152 1544"
+"targetname" "t236"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-1792 288 1856"
+"classname" "misc_explobox"
+}
+{
+"targetname" "t235"
+"classname" "target_explosion"
+"origin" "-352 1336 928"
+}
+{
+"targetname" "t235"
+"origin" "-360 1272 876"
+"classname" "target_explosion"
+}
+{
+"targetname" "lasers"
+"origin" "-1952 120 1568"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "lasers"
+"origin" "-1744 144 1568"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "lasers"
+"classname" "target_speaker"
+"noise" "world/lasoff1.wav"
+"origin" "-1152 104 1568"
+}
+{
+"targetname" "lasers"
+"classname" "target_speaker"
+"noise" "world/lasoff1.wav"
+"origin" "-1952 128 1624"
+}
+{
+"targetname" "lasers"
+"classname" "target_speaker"
+"noise" "world/lasoff1.wav"
+"origin" "-1832 144 1568"
+}
+{
+"origin" "-1152 112 1608"
+"targetname" "lasers"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "lasers"
+"spawnflags" "0"
+"origin" "-1768 80 1568"
+"random" "2"
+"wait" "4"
+"target" "t234"
+"classname" "func_timer"
+}
+{
+"origin" "-1780 104 1568"
+"dmg" "5"
+"sounds" "1"
+"targetname" "t234"
+"angle" "135"
+"classname" "target_splash"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*2"
+"spawnflags" "5888"
+"target" "t233"
+"classname" "trigger_once"
+}
+{
+"model" "*3"
+"spawnflags" "5888"
+"target" "t233"
+"classname" "trigger_once"
+}
+{
+"origin" "-888 1272 1016"
+"angle" "315"
+"classname" "info_player_deathmatch"
+}
+{
+"model" "*4"
+"target" "t233"
+"classname" "trigger_once"
+"spawnflags" "5888"
+}
+{
+"model" "*5"
+"target" "t233"
+"spawnflags" "5888"
+"classname" "trigger_once"
+}
+{
+"model" "*6"
+"spawnflags" "5888"
+"target" "t233"
+"classname" "trigger_once"
+}
+{
+"model" "*7"
+"targetname" "t233"
+"target" "t153"
+"spawnflags" "5888"
+"classname" "trigger_once"
+}
+{
+"origin" "-624 -708 1448"
+"targetname" "unitstart"
+"classname" "info_player_coop"
+"angle" "270"
+}
+{
+"origin" "-592 -744 1448"
+"angle" "270"
+"targetname" "unitstart"
+"classname" "info_player_coop"
+}
+{
+"origin" "-592 -664 1448"
+"targetname" "unitstart"
+"angle" "270"
+"classname" "info_player_coop"
+}
+{
+"targetname" "t167"
+"message" "Use Anti-matter Bomb\nto destroy Gravity Well."
+"origin" "920 616 1528"
+"spawnflags" "2049"
+"classname" "target_help"
+}
+{
+"origin" "-1192 -332 1624"
+"targetname" "t143"
+"spawnflags" "256"
+"target" "t142"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"model" "*8"
+"target" "t232"
+"spawnflags" "256"
+"classname" "trigger_once"
+}
+{
+"origin" "1048 -104 1496"
+"targetname" "t232"
+"spawnflags" "257"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"targetname" "t153"
+"origin" "408 376 1624"
+"angle" "315"
+"spawnflags" "770"
+"classname" "monster_gunner"
+}
+{
+"origin" "-960 1696 880"
+"spawnflags" "5888"
+"classname" "ammo_shells"
+}
+{
+"origin" "-968 1800 912"
+"spawnflags" "5888"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-608 -712 1440"
+"spawnflags" "5888"
+"classname" "weapon_heatbeam"
+}
+{
+"origin" "-1056 -352 1616"
+"spawnflags" "5888"
+"classname" "weapon_chainfist"
+}
+{
+"origin" "-1184 704 1552"
+"spawnflags" "5888"
+"classname" "item_double"
+}
+{
+"angle" "90"
+"origin" "-736 928 1008"
+"spawnflags" "5888"
+"classname" "item_health_mega"
+}
+{
+"origin" "376 536 1488"
+"spawnflags" "5888"
+"classname" "ammo_slugs"
+}
+{
+"origin" "928 352 1488"
+"spawnflags" "5888"
+"classname" "ammo_cells"
+}
+{
+"origin" "888 288 1496"
+"spawnflags" "5888"
+"classname" "weapon_hyperblaster"
+}
+{
+"origin" "608 1568 1496"
+"target" "t231"
+"spawnflags" "5888"
+"classname" "misc_teleporter"
+}
+{
+"origin" "152 104 1872"
+"spawnflags" "5888"
+"classname" "ammo_grenades"
+}
+{
+"origin" "224 -224 1872"
+"spawnflags" "5888"
+"classname" "weapon_grenadelauncher"
+}
+{
+"origin" "-488 -232 1360"
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "-1376 -64 1560"
+"targetname" "t230"
+"angle" "0"
+"spawnflags" "5888"
+"classname" "misc_teleporter_dest"
+}
+{
+"angle" "270"
+"origin" "-736 2592 844"
+"target" "t230"
+"spawnflags" "5888"
+"classname" "misc_teleporter"
+}
+{
+"origin" "-736 2784 836"
+"classname" "ammo_nuke"
+}
+{
+"angle" "90"
+"origin" "-736 1872 884"
+"target" "t229"
+"spawnflags" "5888"
+"classname" "misc_teleporter"
+}
+{
+"origin" "832 -480 1768"
+"spawnflags" "5888"
+"classname" "item_sphere_defender"
+}
+{
+"angle" "0"
+"targetname" "t231"
+"origin" "160 896 1656"
+"spawnflags" "5888"
+"classname" "misc_teleporter_dest"
+}
+{
+"origin" "768 896 1648"
+"spawnflags" "5888"
+"classname" "item_sphere_hunter"
+}
+{
+"model" "*9"
+"spawnflags" "5888"
+"classname" "func_water"
+}
+{
+"origin" "-432 -280 1360"
+"spawnflags" "5888"
+"classname" "weapon_etf_rifle"
+}
+{
+"origin" "-736 1288 1008"
+"spawnflags" "5888"
+"classname" "weapon_bfg"
+}
+{
+"origin" "-1664 632 1872"
+"spawnflags" "5888"
+"classname" "weapon_railgun"
+}
+{
+"origin" "-2016 224 1552"
+"spawnflags" "5888"
+"classname" "ammo_shells"
+}
+{
+"origin" "-2016 288 1552"
+"spawnflags" "5888"
+"classname" "weapon_supershotgun"
+}
+{
+"angle" "180"
+"classname" "info_player_deathmatch"
+"origin" "832 288 1624"
+}
+{
+"origin" "-1056 -224 1560"
+"classname" "info_player_deathmatch"
+"angle" "90"
+}
+{
+"origin" "952 -328 1768"
+"spawnflags" "5888"
+"classname" "weapon_rocketlauncher"
+}
+{
+"angle" "0"
+"classname" "info_player_deathmatch"
+"origin" "-864 -1056 1624"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "608 568 1496"
+}
+{
+"angle" "90"
+"classname" "info_player_deathmatch"
+"origin" "-288 -1184 1432"
+}
+{
+"angle" "45"
+"classname" "info_player_deathmatch"
+"origin" "288 -256 1768"
+}
+{
+"angle" "0"
+"classname" "info_player_deathmatch"
+"origin" "-224 -288 1368"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "-1504 288 1560"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "-1632 288 1880"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "-1184 -224 1880"
+}
+{
+"origin" "-384 1504 888"
+"classname" "info_player_deathmatch"
+"angle" "180"
+}
+{
+"origin" "-544 480 1368"
+"classname" "info_player_deathmatch"
+"angle" "0"
+}
+{
+"origin" "480 96 1624"
+"classname" "info_player_deathmatch"
+"angle" "0"
+}
+{
+"origin" "-608 -632 1448"
+"classname" "info_player_deathmatch"
+"angle" "270"
+}
+{
+"origin" "96 800 1592"
+"angle" "315"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-224 -936 1616"
+"spawnflags" "5888"
+"classname" "ammo_cells"
+}
+{
+"model" "*10"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*11"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*12"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"targetname" "t229"
+"angle" "270"
+"origin" "-736 2944 844"
+"spawnflags" "5888"
+"classname" "misc_teleporter_dest"
+}
+{
+"origin" "-744 2776 984"
+"classname" "light"
+}
+{
+"model" "*13"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*14"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*15"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "1120 -312 1760"
+"spawnflags" "5888"
+"classname" "ammo_rockets"
+}
+{
+"model" "*16"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"classname" "light"
+"origin" "704 -480 1808"
+"light" "125"
+"spawnflags" "5888"
+}
+{
+"classname" "light"
+"origin" "832 -480 1808"
+"light" "125"
+"spawnflags" "5888"
+}
+{
+"spawnflags" "5888"
+"light" "125"
+"origin" "576 -480 1808"
+"classname" "light"
+}
+{
+"origin" "576 -480 1768"
+"spawnflags" "5888"
+"classname" "item_bandolier"
+}
+{
+"model" "*17"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*18"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"model" "*19"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*20"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*21"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "-1376 -640 1624"
+"angle" "270"
+"spawnflags" "771"
+"targetname" "t1"
+"classname" "monster_parasite"
+}
+{
+"spawnflags" "2048"
+"origin" "-624 -616 1448"
+"angle" "270"
+"targetname" "unitstart"
+"classname" "info_player_start"
+}
+{
+"origin" "224 -256 1880"
+"item" "ammo_grenades"
+"angle" "135"
+"spawnflags" "3"
+"targetname" "t153"
+"classname" "monster_gunner"
+}
+{
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_daedalus"
+"origin" "-1376 -168 1936"
+}
+{
+"origin" "160 896 1808"
+"classname" "monster_daedalus"
+"angle" "270"
+"spawnflags" "1"
+}
+{
+"item" "ammo_cells"
+"origin" "-224 544 1560"
+"targetname" "t228"
+"spawnflags" "769"
+"angle" "180"
+"classname" "monster_daedalus"
+}
+{
+"model" "*22"
+"spawnflags" "2816"
+"classname" "func_wall"
+}
+{
+"origin" "704 1072 1552"
+"classname" "item_armor_combat"
+}
+{
+"angles" "-20 135 0"
+"origin" "-96 352 1376"
+"classname" "info_player_intermission"
+}
+{
+"origin" "-1112 1152 1024"
+"target" "t227"
+"spawnflags" "2048"
+"targetname" "t226"
+"classname" "hint_path"
+}
+{
+"origin" "-1104 1560 896"
+"targetname" "t227"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "-712 1152 1024"
+"target" "t226"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t224"
+"targetname" "t223"
+"spawnflags" "2048"
+"classname" "hint_path"
+"origin" "352 -96 1632"
+}
+{
+"target" "t225"
+"targetname" "t224"
+"spawnflags" "2048"
+"classname" "hint_path"
+"origin" "1048 -104 1504"
+}
+{
+"targetname" "t225"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "1064 328 1504"
+}
+{
+"target" "t223"
+"origin" "440 336 1632"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t217"
+"targetname" "t216"
+"origin" "704 64 1640"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t218"
+"targetname" "t217"
+"origin" "1040 56 1640"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t219"
+"targetname" "t218"
+"origin" "1056 -168 1640"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t220"
+"targetname" "t219"
+"origin" "360 -144 1760"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t221"
+"targetname" "t220"
+"origin" "376 64 1824"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t222"
+"targetname" "t221"
+"origin" "160 -32 1872"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"targetname" "t222"
+"origin" "-480 -32 1872"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t213"
+"targetname" "t212"
+"spawnflags" "2048"
+"classname" "hint_path"
+"origin" "-552 200 1872"
+}
+{
+"target" "t216"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "680 336 1640"
+}
+{
+"targetname" "t213"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "-512 -24 1872"
+}
+{
+"target" "t214"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "-512 -40 1872"
+}
+{
+"target" "t215"
+"targetname" "t214"
+"origin" "-536 -288 1872"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"targetname" "t215"
+"origin" "-1072 -304 1872"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t212"
+"origin" "-1928 240 1872"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-1952 -272 1872"
+"target" "t209"
+"targetname" "t208"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1960 -184 1568"
+"target" "t210"
+"targetname" "t209"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1984 224 1560"
+"target" "t211"
+"targetname" "t210"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1184 208 1560"
+"targetname" "t211"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-1064 -288 1872"
+"target" "t208"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t207"
+"targetname" "t206"
+"origin" "-320 816 1376"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"targetname" "t207"
+"origin" "-320 440 1376"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t206"
+"spawnflags" "1"
+"classname" "hint_path"
+"origin" "-320 1136 1376"
+}
+{
+"targetname" "t205"
+"origin" "616 688 1504"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t205"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "608 1144 1504"
+}
+{
+"origin" "168 576 1504"
+"target" "t203"
+"targetname" "t202"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "152 -144 1376"
+"target" "t204"
+"targetname" "t203"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "-112 -136 1376"
+"targetname" "t204"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "664 696 1504"
+"target" "t202"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-160 448 1376"
+"target" "t199"
+"targetname" "t198"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-464 456 1376"
+"target" "t200"
+"targetname" "t199"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-528 -104 1376"
+"target" "t201"
+"targetname" "t200"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-160 -136 1376"
+"target" "t198"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "-1344 -88 1576"
+"targetname" "t201"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "-1664 -72 1576"
+"target" "t193"
+"targetname" "t192"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1664 -320 1616"
+"target" "t194"
+"targetname" "t193"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1376 -320 1640"
+"target" "t195"
+"targetname" "t194"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1376 -832 1640"
+"target" "t196"
+"targetname" "t195"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-800 -832 1640"
+"targetname" "t196"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-1344 -64 1576"
+"target" "t192"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"pathtarget" "medic"
+"spawnflags" "2049"
+"origin" "608 728 1480"
+"targetname" "t162"
+"classname" "point_combat"
+}
+{
+"classname" "item_health_large"
+"origin" "-88 -360 1360"
+}
+{
+"classname" "item_health_large"
+"origin" "-424 224 1360"
+}
+{
+"origin" "-1144 1856 880"
+"classname" "item_health"
+}
+{
+"origin" "-1096 1856 880"
+"classname" "item_health"
+}
+{
+"spawnflags" "2048"
+"origin" "-392 1320 872"
+"targetname" "t191"
+"classname" "target_secret"
+}
+{
+"model" "*23"
+"spawnflags" "2048"
+"message" "You found a secret!"
+"target" "t191"
+"classname" "trigger_once"
+}
+{
+"origin" "-320 1272 880"
+"classname" "item_armor_body"
+}
+{
+"origin" "-320 1352 880"
+"classname" "item_power_shield"
+}
+{
+"model" "*24"
+"target" "t235"
+"spawnflags" "2048"
+"health" "1"
+"classname" "func_explosive"
+}
+{
+"origin" "-392 1672 880"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-392 1712 880"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-392 1752 880"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-392 1632 880"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-912 1056 1008"
+"classname" "item_health_large"
+}
+{
+"origin" "-872 1056 1008"
+"classname" "item_health_large"
+}
+{
+"origin" "-960 1352 880"
+"classname" "ammo_slugs"
+}
+{
+"origin" "-1152 1120 1008"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-832 1320 1008"
+"classname" "ammo_prox"
+}
+{
+"origin" "-552 1248 1008"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "-936 1232 1008"
+"classname" "ammo_cells"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-272 856 1392"
+"classname" "ammo_tesla"
+}
+{
+"volume" ".5"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1128 288 1512"
+"noise" "world/comp_hum2.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1056 400 1512"
+"noise" "world/comp_hum3.wav"
+}
+{
+"noise" "world/comp_hum1.wav"
+"origin" "912 288 1512"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"volume" ".5"
+}
+{
+"noise" "world/comp_hum2.wav"
+"origin" "576 272 1632"
+"classname" "target_speaker"
+"spawnflags" "1"
+"volume" ".5"
+}
+{
+"noise" "world/comp_hum3.wav"
+"origin" "296 320 1672"
+"classname" "target_speaker"
+"spawnflags" "1"
+"volume" ".5"
+}
+{
+"noise" "world/train1.wav"
+"origin" "1120 -480 1760"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"volume" ".25"
+}
+{
+"noise" "world/train1.wav"
+"origin" "896 -328 1760"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"volume" ".25"
+}
+{
+"noise" "world/train1.wav"
+"origin" "512 -328 1760"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"volume" ".25"
+}
+{
+"noise" "world/train1.wav"
+"origin" "160 -416 1760"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"volume" ".25"
+}
+{
+"spawnflags" "2048"
+"targetname" "red1"
+"noise" "world/land.wav"
+"origin" "576 -424 1800"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "2048"
+"targetname" "red2"
+"classname" "target_speaker"
+"origin" "704 -424 1800"
+"noise" "world/land.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"origin" "608 480 1624"
+"noise" "world/comp_hum1.wav"
+}
+{
+"spawnflags" "2048"
+"targetname" "red3"
+"noise" "world/land.wav"
+"origin" "832 -424 1800"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"targetname" "t153"
+"origin" "680 480 1616"
+"classname" "ammo_bullets"
+}
+{
+"origin" "840 -64 1528"
+"classname" "item_armor_shard"
+}
+{
+"origin" "880 -64 1520"
+"classname" "item_armor_shard"
+}
+{
+"origin" "800 -64 1536"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1072 384 1472"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "1120 352 1488"
+"classname" "item_silencer"
+}
+{
+"origin" "1120 48 1632"
+"classname" "item_health"
+}
+{
+"origin" "1120 96 1632"
+"classname" "item_health"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "352 320 1616"
+"classname" "weapon_chaingun"
+}
+{
+"origin" "304 336 1600"
+"spawnflags" "2052"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "608 32 1616"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "496 32 1616"
+"classname" "weapon_nailgun"
+}
+{
+"origin" "496 96 1600"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "1104 96 1488"
+"classname" "item_health"
+}
+{
+"origin" "1104 48 1488"
+"classname" "item_health"
+}
+{
+"origin" "992 -288 1632"
+"classname" "ammo_grenades"
+}
+{
+"origin" "1120 -352 1632"
+"classname" "item_armor_combat"
+}
+{
+"origin" "288 -128 1760"
+"classname" "ammo_rockets"
+}
+{
+"origin" "1120 -224 1760"
+"classname" "item_health_large"
+}
+{
+"origin" "576 -224 1760"
+"classname" "item_health_small"
+}
+{
+"origin" "528 -224 1760"
+"classname" "item_health_small"
+}
+{
+"origin" "480 -224 1760"
+"classname" "item_health_small"
+}
+{
+"origin" "624 -224 1760"
+"classname" "item_health_small"
+}
+{
+"origin" "96 96 1872"
+"classname" "ammo_bullets"
+}
+{
+"origin" "96 -352 1936"
+"classname" "item_ir_goggles"
+}
+{
+"origin" "-32 -72 1872"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "-352 8 1872"
+"classname" "ammo_prox"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-1400 152 1872"
+"classname" "ammo_tesla"
+}
+{
+"spawnflags" "1"
+"targetname" "t153"
+"origin" "-1736 160 1872"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "1"
+"targetname" "t153"
+"origin" "-1776 160 1872"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "1"
+"targetname" "t153"
+"origin" "-1816 160 1872"
+"classname" "item_armor_shard"
+}
+{
+"classname" "ammo_cells"
+"origin" "-1400 296 1552"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"classname" "item_health_large"
+"origin" "-1768 296 1552"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"classname" "item_health"
+"origin" "-1728 296 1552"
+}
+{
+"origin" "-640 264 1872"
+"classname" "item_health_large"
+}
+{
+"origin" "-688 264 1872"
+"classname" "item_health_large"
+}
+{
+"origin" "-544 -328 1872"
+"classname" "item_health_small"
+}
+{
+"origin" "-584 -328 1872"
+"classname" "item_health_small"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-624 -328 1872"
+"classname" "item_health_small"
+}
+{
+"origin" "-504 -328 1872"
+"classname" "item_health_small"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-1008 -224 1872"
+"classname" "ammo_prox"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-1120 -320 1872"
+"classname" "weapon_proxlauncher"
+}
+{
+"origin" "-1056 -352 1856"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"model" "*25"
+"spawnflags" "2048"
+"target" "t190"
+"message" "Secondary Ammo Depot entrance opened."
+"wait" "-1"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-1824 -352 1872"
+"classname" "item_health"
+}
+{
+"targetname" "t153"
+"origin" "-1384 -216 1872"
+"classname" "item_health"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-1728 296 1872"
+"classname" "item_health"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-1768 296 1872"
+"classname" "item_health_large"
+}
+{
+"targetname" "t153"
+"origin" "-2008 -344 1936"
+"classname" "ammo_prox"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-2024 32 1552"
+"classname" "ammo_slugs"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-1400 296 1872"
+"classname" "ammo_cells"
+}
+{
+"origin" "-1200 152 1552"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1248 152 1552"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1152 152 1552"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-992 240 1584"
+"classname" "item_adrenaline"
+}
+{
+"origin" "496 1272 1488"
+"classname" "ammo_grenades"
+}
+{
+"origin" "496 1320 1488"
+"classname" "ammo_grenades"
+}
+{
+"origin" "712 1304 1488"
+"classname" "ammo_tesla"
+}
+{
+"origin" "712 1168 1488"
+"classname" "item_health_large"
+}
+{
+"origin" "1128 640 1488"
+"classname" "item_health"
+}
+{
+"origin" "1128 592 1488"
+"classname" "item_health_large"
+}
+{
+"origin" "864 472 1488"
+"classname" "ammo_prox"
+}
+{
+"origin" "552 560 1488"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "272 808 1488"
+"classname" "ammo_cells"
+}
+{
+"origin" "320 808 1488"
+"classname" "ammo_cells"
+}
+{
+"origin" "320 480 1488"
+"classname" "weapon_railgun"
+}
+{
+"origin" "88 320 1460"
+"classname" "item_health_small"
+}
+{
+"origin" "88 368 1472"
+"classname" "item_health_small"
+}
+{
+"origin" "88 416 1488"
+"classname" "item_health_small"
+}
+{
+"origin" "88 272 1444"
+"classname" "item_health_small"
+}
+{
+"origin" "224 -160 1360"
+"classname" "ammo_bullets"
+}
+{
+"origin" "224 -112 1360"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-96 560 1360"
+"classname" "ammo_prox"
+}
+{
+"origin" "-552 568 1360"
+"classname" "ammo_nails"
+}
+{
+"origin" "-216 -352 1360"
+"classname" "ammo_tesla"
+}
+{
+"origin" "-216 -104 1360"
+"classname" "ammo_shells"
+}
+{
+"origin" "-96 128 1360"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-96 176 1360"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-96 80 1360"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-96 224 1360"
+"classname" "item_armor_shard"
+}
+{
+"classname" "item_health_large"
+"origin" "-424 288 1360"
+}
+{
+"targetname" "t153"
+"spawnflags" "2049"
+"origin" "-360 1016 1392"
+"classname" "weapon_bfg"
+}
+{
+"origin" "-352 968 1376"
+"spawnflags" "2056"
+"classname" "misc_deadsoldier"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-400 1104 1392"
+"classname" "item_health_small"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-400 1200 1392"
+"classname" "item_health_small"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-240 1200 1392"
+"classname" "item_health_small"
+}
+{
+"targetname" "t153"
+"spawnflags" "1"
+"origin" "-240 1104 1392"
+"classname" "item_health_small"
+}
+{
+"origin" "-136 -360 1360"
+"classname" "item_health_large"
+}
+{
+"origin" "-544 -352 1424"
+"classname" "item_pack"
+}
+{
+"origin" "-320 504 1360"
+"classname" "weapon_rocketlauncher"
+}
+{
+"origin" "-496 32 1344"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"spawnflags" "2048"
+"origin" "-536 8 1360"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-304 464 1344"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "-992 -168 1524"
+"classname" "item_health"
+}
+{
+"origin" "-944 -168 1508"
+"classname" "item_health"
+}
+{
+"origin" "-1072 48 1536"
+"angle" "270"
+"spawnflags" "2052"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "-1120 48 1552"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1032 32 1552"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1088 16 1552"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1688 -168 1584"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-1000 72 1552"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-1696 72 1552"
+"classname" "ammo_slugs"
+}
+{
+"spawnflags" "2048"
+"origin" "-1440 -280 1616"
+"classname" "item_armor_combat"
+}
+{
+"origin" "-1480 -280 1600"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "-1080 -280 1616"
+"classname" "item_health_small"
+}
+{
+"origin" "-1112 -280 1616"
+"classname" "item_health_small"
+}
+{
+"origin" "-1144 -280 1616"
+"classname" "item_health_small"
+}
+{
+"origin" "-1408 -592 1616"
+"classname" "item_health"
+}
+{
+"origin" "-1408 -544 1616"
+"classname" "item_health_large"
+}
+{
+"targetname" "t1"
+"spawnflags" "1"
+"origin" "-1128 -800 1616"
+"classname" "ammo_nails"
+}
+{
+"targetname" "t1"
+"spawnflags" "1"
+"origin" "-1184 -800 1616"
+"classname" "ammo_nails"
+}
+{
+"origin" "-32 -1128 1616"
+"classname" "ammo_prox"
+}
+{
+"origin" "-864 -608 1616"
+"classname" "ammo_tesla"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1792 112 1568"
+"targetname" "lasers"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1792 160 1608"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1880 0 1592"
+}
+{
+"origin" "-120 -1008 1616"
+"classname" "weapon_hyperblaster"
+}
+{
+"spawnflags" "2048"
+"origin" "-88 -1048 1600"
+"classname" "misc_deadsoldier"
+}
+{
+"spawnflags" "2048"
+"origin" "-800 -496 1616"
+"classname" "weapon_heatbeam"
+}
+{
+"origin" "-848 -464 1600"
+"angle" "270"
+"spawnflags" "2064"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "-544 -472 1616"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-592 -472 1616"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-640 -472 1616"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-496 -472 1616"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-112 -784 1488"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-56 -920 1424"
+"classname" "item_health"
+}
+{
+"origin" "-96 -920 1424"
+"classname" "item_health"
+}
+{
+"origin" "-488 -1048 1424"
+"classname" "item_health_large"
+}
+{
+"origin" "608 1360 1736"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-32 -928 1488"
+"light" "75"
+"classname" "light"
+}
+{
+"targetname" "t153"
+"item" "ammo_bullets"
+"origin" "-216 796 1400"
+"spawnflags" "259"
+"classname" "monster_soldier_ss"
+"angle" "180"
+}
+{
+"targetname" "t153"
+"item" "ammo_bullets"
+"origin" "-424 796 1400"
+"spawnflags" "771"
+"angle" "0"
+"classname" "monster_soldier_ss"
+}
+{
+"spawnflags" "2048"
+"targetname" "t189"
+"classname" "target_help"
+"origin" "-1472 744 1576"
+"message" "Return to Anti-matter\nCore creator for\nCore."
+}
+{
+"target" "t189"
+"classname" "target_crosslevel_target"
+"spawnflags" "2050"
+"origin" "-1440 752 1544"
+}
+{
+"spawnflags" "2048"
+"origin" "-1424 656 1552"
+"targetname" "t188"
+"target" "t172"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2049"
+"message" "Use Containment\nPod to house\n Anti-matter Core."
+"origin" "-1512 744 1568"
+"targetname" "t189"
+"classname" "target_help"
+}
+{
+"origin" "1144 352 1528"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "1056 400 1568"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "888 352 1568"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+"origin" "1144 224 1528"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "680 928 1552"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "536 928 1552"
+}
+{
+"targetname" "t153"
+"origin" "992 -352 1640"
+"spawnflags" "771"
+"angle" "90"
+"classname" "monster_parasite"
+}
+{
+"spawnflags" "2048"
+"origin" "-1768 168 1576"
+"message" "Laser power interrupted."
+"targetname" "lasers"
+"classname" "trigger_relay"
+}
+{
+"model" "*26"
+"_minlight" ".2"
+"spawnflags" "2048"
+"target" "lasers"
+"health" "1"
+"classname" "func_explosive"
+}
+{
+"model" "*27"
+"spawnflags" "2048"
+"targetname" "t187"
+"classname" "func_explosive"
+}
+{
+"spawnflags" "2048"
+"target" "t186"
+"targetname" "t185"
+"classname" "trigger_relay"
+"origin" "-1352 680 1600"
+}
+{
+"spawnflags" "2048"
+"target" "t186"
+"delay" "4"
+"targetname" "t185"
+"origin" "-1312 680 1600"
+"classname" "trigger_relay"
+}
+{
+"model" "*28"
+"spawnflags" "2048"
+"target" "t185"
+"targetname" "t172"
+"classname" "trigger_once"
+}
+{
+"targetname" "t186"
+"origin" "-1184 280 1584"
+"attenuation" "-1"
+"spawnflags" "2050"
+"noise" "world/fish.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-32 -1024 1664"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-544 -1024 1808"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-192 -1248 1664"
+}
+{
+"model" "*29"
+"classname" "func_explosive"
+"dmg" "75"
+"health" "5"
+}
+{
+"classname" "monster_parasite"
+"angle" "0"
+"spawnflags" "258"
+"origin" "-1264 -832 1624"
+"targetname" "t1"
+}
+{
+"target" "t228"
+"classname" "monster_parasite"
+"spawnflags" "769"
+"angle" "270"
+"origin" "-544 528 1368"
+}
+{
+"spawnflags" "1"
+"angle" "315"
+"classname" "monster_parasite"
+"origin" "-632 1296 1016"
+}
+{
+"classname" "monster_parasite"
+"angle" "0"
+"spawnflags" "769"
+"origin" "-928 1120 1016"
+}
+{
+"model" "*30"
+"classname" "trigger_multiple"
+"target" "t21"
+"spawnflags" "2048"
+}
+{
+"origin" "552 540 1656"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "608 548 1528"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "32"
+"angle" "-2"
+"origin" "512 64 1720"
+}
+{
+"model" "*31"
+"classname" "func_wall"
+"spawnflags" "2816"
+}
+{
+"origin" "352 -96 1720"
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+}
+{
+"classname" "target_explosion"
+"targetname" "t177"
+"origin" "-1896 304 1864"
+}
+{
+"model" "*32"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t177"
+"target" "walloff"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1192 96 1636"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1192 96 1596"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1192 96 1556"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"classname" "light"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "-1208 96 1592"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "-1176 96 1592"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "-1128 96 1592"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1112 96 1596"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"classname" "light"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"origin" "-1096 96 1592"
+"noise" "world/l_hum1.wav"
+"classname" "target_speaker"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1112 96 1636"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"classname" "light"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1112 96 1556"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1992 112 1636"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1992 112 1596"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1992 112 1556"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"classname" "light"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "-2008 112 1592"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "-1976 112 1592"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "-1928 112 1592"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1912 112 1596"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"classname" "light"
+"targetname" "lasers"
+}
+{
+"spawnflags" "2049"
+"origin" "-1896 112 1592"
+"noise" "world/l_hum1.wav"
+"classname" "target_speaker"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1912 112 1636"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"classname" "light"
+"targetname" "lasers"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"origin" "-1912 112 1556"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"targetname" "lasers"
+}
+{
+"model" "*33"
+"targetname" "lasers"
+"spawnflags" "7"
+"classname" "func_wall"
+}
+{
+"model" "*34"
+"spawnflags" "7"
+"classname" "func_wall"
+}
+{
+"targetname" "lasers"
+"classname" "target_laser"
+"angle" "180"
+"spawnflags" "2145"
+"origin" "-1096 96 1556"
+"dmg" "10000"
+}
+{
+"targetname" "lasers"
+"classname" "target_laser"
+"angle" "180"
+"spawnflags" "2145"
+"origin" "-1096 96 1596"
+"dmg" "10000"
+}
+{
+"targetname" "lasers"
+"classname" "target_laser"
+"angle" "180"
+"spawnflags" "2145"
+"origin" "-1096 96 1636"
+"dmg" "10000"
+}
+{
+"targetname" "lasers"
+"classname" "target_laser"
+"angle" "180"
+"spawnflags" "2145"
+"origin" "-1864 112 1596"
+"dmg" "10000"
+}
+{
+"targetname" "lasers"
+"classname" "target_laser"
+"angle" "180"
+"spawnflags" "2145"
+"origin" "-1864 112 1636"
+"dmg" "10000"
+}
+{
+"targetname" "lasers"
+"classname" "target_laser"
+"angle" "180"
+"spawnflags" "2145"
+"origin" "-1864 112 1556"
+"dmg" "10000"
+}
+{
+"model" "*35"
+"classname" "func_wall"
+"spawnflags" "2816"
+}
+{
+"origin" "-1376 -184 1824"
+"angle" "90"
+"spawnflags" "776"
+"classname" "monster_turret"
+}
+{
+"targetname" "t153"
+"origin" "-544 -80 1896"
+"spawnflags" "3"
+"angle" "270"
+"classname" "monster_parasite"
+}
+{
+"spawnflags" "2049"
+"origin" "-1232 384 1608"
+"noise" "world/scan1.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/scan1.wav"
+"origin" "-1136 384 1608"
+"spawnflags" "2049"
+}
+{
+"model" "*36"
+"message" "Lift activated."
+"classname" "func_button"
+"angle" "90"
+"target" "t183"
+}
+{
+"model" "*37"
+"message" "Lift activated."
+"classname" "func_button"
+"angle" "0"
+"target" "t183"
+}
+{
+"targetname" "t153"
+"classname" "monster_parasite"
+"angle" "45"
+"spawnflags" "3"
+"origin" "-1440 160 1880"
+}
+{
+"targetname" "t153"
+"classname" "monster_parasite"
+"angle" "90"
+"spawnflags" "3"
+"origin" "-1952 -160 1560"
+}
+{
+"model" "*38"
+"spawnflags" "768"
+"classname" "func_wall"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "800"
+"angle" "-2"
+"origin" "-480 -320 1528"
+}
+{
+"model" "*39"
+"spawnflags" "2816"
+"classname" "func_wall"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "776"
+"angle" "180"
+"origin" "-968 160 2024"
+}
+{
+"model" "*40"
+"classname" "func_explosive"
+"targetname" "t144"
+}
+{
+"classname" "monster_turret"
+"angle" "0"
+"spawnflags" "160"
+"targetname" "t144"
+"origin" "-1760 -96 1824"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t179"
+"killtarget" "boxe"
+"origin" "-496 -528 1632"
+}
+{
+"spawnflags" "2048"
+"classname" "info_notnull"
+"targetname" "boxe"
+"origin" "-388 -548 1628"
+}
+{
+"spawnflags" "2048"
+"classname" "target_anger"
+"killtarget" "boxe"
+"targetname" "t180"
+"target" "t181"
+"origin" "-464 -488 1608"
+}
+{
+"model" "*41"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t180"
+}
+{
+"model" "*42"
+"classname" "func_wall"
+"spawnflags" "2816"
+}
+{
+"origin" "-384 -544 1720"
+"classname" "monster_turret"
+"spawnflags" "32"
+"angle" "-2"
+}
+{
+"model" "*43"
+"spawnflags" "768"
+"classname" "func_wall"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "800"
+"angle" "-2"
+"origin" "-128 -32 1976"
+}
+{
+"model" "*44"
+"classname" "func_wall"
+"spawnflags" "2816"
+}
+{
+"targetname" "t153"
+"classname" "monster_turret"
+"spawnflags" "10"
+"angle" "-2"
+"origin" "-544 -32 1976"
+}
+{
+"classname" "monster_parasite"
+"angle" "180"
+"origin" "-1056 -352 1624"
+}
+{
+"model" "*45"
+"classname" "func_wall"
+"spawnflags" "2816"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "8"
+"angle" "90"
+"origin" "-1376 -376 1768"
+}
+{
+"classname" "monster_parasite"
+"angle" "225"
+"spawnflags" "769"
+"origin" "-56 -952 1624"
+}
+{
+"classname" "monster_hover"
+"spawnflags" "1025"
+"angle" "0"
+"origin" "-984 -832 1680"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_daedalus"
+"spawnflags" "769"
+"angle" "0"
+"origin" "-984 -832 1680"
+}
+{
+"target" "t178"
+"origin" "-1864 360 1896"
+"random" ".5"
+"wait" "2"
+"targetname" "t177"
+"classname" "func_timer"
+}
+{
+"targetname" "t178"
+"origin" "-1896 312 1904"
+"sounds" "1"
+"angle" "180"
+"classname" "target_splash"
+}
+{
+"model" "*46"
+"spawnflags" "2055"
+"classname" "trigger_disguise"
+}
+{
+"model" "*47"
+"item" "ammo_rockets"
+"target" "t187"
+"health" "5"
+"dmg" "75"
+"classname" "func_explosive"
+}
+{
+"model" "*48"
+"target" "t173"
+"targetname" "t177"
+"classname" "func_explosive"
+}
+{
+"spawnflags" "2048"
+"origin" "-1944 360 1592"
+"target" "t176"
+"targetname" "t172"
+"classname" "trigger_relay"
+}
+{
+"model" "*49"
+"spawnflags" "2048"
+"targetname" "t174"
+"target" "t173"
+"classname" "trigger_once"
+}
+{
+"model" "*50"
+"spawnflags" "7936"
+"targetname" "t176"
+"classname" "trigger_once"
+}
+{
+"target" "t188"
+"origin" "-1424 712 1544"
+"spawnflags" "2049"
+"classname" "target_crosslevel_target"
+}
+{
+"spawnflags" "2048"
+"target" "t168"
+"targetname" "t174"
+"origin" "-1496 680 1544"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "7936"
+"origin" "-496 -600 1480"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"targetname" "t174"
+"target" "exiter"
+"classname" "trigger_relay"
+"delay" "2"
+"message" "Security forcefields activated."
+"origin" "-1520 656 1544"
+}
+{
+"spawnflags" "2048"
+"target" "t175"
+"targetname" "t174"
+"origin" "-1488 712 1544"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"target" "t174"
+"origin" "-1520 712 1544"
+"delay" "2"
+"targetname" "t172"
+"classname" "trigger_relay"
+}
+{
+"model" "*51"
+"spawnflags" "2048"
+"target" "t172"
+"message" "Humanoid life form detected..."
+"classname" "trigger_once"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t137"
+"target" "exiter"
+"origin" "-480 -584 1480"
+}
+{
+"model" "*52"
+"targetname" "t175"
+"spawnflags" "2051"
+"classname" "func_wall"
+}
+{
+"classname" "item_armor_body"
+"targetname" "armor"
+"origin" "-1760 -64 1552"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t153"
+"target" "t168"
+"origin" "-1480 680 1568"
+}
+{
+"model" "*53"
+"spawnflags" "2055"
+"classname" "func_wall"
+"targetname" "walloff"
+}
+{
+"model" "*54"
+"targetname" "t175"
+"classname" "func_wall"
+"spawnflags" "2051"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "270"
+"spawnflags" "1"
+"origin" "-1536 -240 1880"
+}
+{
+"classname" "target_help"
+"targetname" "t167"
+"spawnflags" "2048"
+"origin" "888 664 1528"
+"message" "Find entrance to\nGravity Well."
+}
+{
+"spawnflags" "2048"
+"classname" "target_help"
+"targetname" "key"
+"message" "Anti-matter Core created.\nContainment Pod needed."
+"origin" "496 432 1696"
+}
+{
+"model" "*55"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*56"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"classname" "target_help"
+"origin" "976 352 1696"
+"spawnflags" "2049"
+"message" "Use Security Pass\nto retrieve\nContainment Pod."
+"targetname" "t166"
+}
+{
+"classname" "target_help"
+"origin" "984 272 1696"
+"spawnflags" "2048"
+"message" "Use secondary exit\nto return to\nAmmo Depot."
+"targetname" "t166"
+}
+{
+"spawnflags" "2048"
+"targetname" "t166"
+"classname" "target_goal"
+"origin" "936 304 1552"
+}
+{
+"spawnflags" "2048"
+"origin" "-112 -936 1624"
+"classname" "trigger_relay"
+"target" "t165"
+"targetname" "t1"
+}
+{
+"spawnflags" "2048"
+"message" "Security Pass accessible."
+"origin" "744 424 1624"
+"target" "t164"
+"delay" "2"
+"classname" "trigger_relay"
+"targetname" "key"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "888 296 1496"
+"targetname" "t164"
+"light" "150"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+}
+{
+"model" "*57"
+"targetname" "t164"
+"spawnflags" "2055"
+"classname" "func_wall"
+}
+{
+"spawnflags" "2048"
+"origin" "888 288 1496"
+"classname" "key_pass"
+"target" "t166"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "32"
+"angle" "180"
+"origin" "1144 544 1696"
+}
+{
+"targetname" "t163"
+"classname" "point_combat"
+"origin" "-1536 104 1864"
+"spawnflags" "1"
+"wait" "10"
+}
+{
+"targetname" "t153"
+"item" "ammo_shells"
+"angle" "90"
+"classname" "monster_soldier"
+"origin" "-1632 152 1560"
+"spawnflags" "3"
+}
+{
+"model" "*58"
+"spawnflags" "2816"
+"classname" "func_wall"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "8"
+"angle" "0"
+"origin" "72 544 1696"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+"spawnflags" "1"
+"angle" "270"
+"origin" "504 1368 1496"
+}
+{
+"classname" "monster_soldier_ss"
+"spawnflags" "1"
+"angle" "180"
+"origin" "368 480 1496"
+}
+{
+"targetname" "medic"
+"origin" "1056 496 1496"
+"spawnflags" "1"
+"angle" "135"
+"classname" "monster_medic_commander"
+}
+{
+"spawnflags" "2048"
+"origin" "168 600 1504"
+"target" "t161"
+"targetname" "t160"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"origin" "160 -96 1360"
+"targetname" "t161"
+"target" "t160"
+"classname" "path_corner"
+}
+{
+"item" "ammo_bullets"
+"origin" "168 632 1496"
+"spawnflags" "1"
+"target" "t160"
+"classname" "monster_soldier_ss"
+}
+{
+"item" "ammo_bullets"
+"origin" "496 1200 1496"
+"angle" "0"
+"spawnflags" "257"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "-464 -1088 1432"
+"spawnflags" "769"
+"angle" "45"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "-1536 -352 1664"
+"light" "125"
+"classname" "light"
+}
+{
+"targetname" "t153"
+"item" "ammo_slugs"
+"classname" "monster_gladiator"
+"angle" "270"
+"origin" "-544 216 1880"
+"spawnflags" "259"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "800"
+"angle" "-2"
+"origin" "-864 -1184 1720"
+}
+{
+"spawnflags" "2049"
+"origin" "-536 -1200 1608"
+"targetname" "t156"
+"classname" "point_combat"
+}
+{
+"spawnflags" "3"
+"targetname" "t153"
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+"angle" "45"
+"origin" "912 224 1496"
+}
+{
+"origin" "496 -144 1976"
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_daedalus"
+"angle" "225"
+"spawnflags" "1"
+"origin" "288 96 1992"
+}
+{
+"origin" "1056 120 1800"
+"angle" "270"
+"spawnflags" "32"
+"classname" "monster_turret"
+}
+{
+"model" "*59"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t154"
+}
+{
+"classname" "monster_gladiator"
+"angle" "90"
+"targetname" "t154"
+"origin" "1056 -344 1640"
+"target" "t155"
+"spawnflags" "1"
+}
+{
+"origin" "608 584 1760"
+"angle" "90"
+"spawnflags" "776"
+"classname" "monster_turret"
+}
+{
+"classname" "monster_medic_commander"
+"angle" "0"
+"spawnflags" "1"
+"origin" "736 64 1624"
+"targetname" "t155"
+}
+{
+"spawnflags" "3"
+"targetname" "t153"
+"item" "ammo_shells"
+"angle" "180"
+"classname" "monster_soldier_ss"
+"origin" "832 224 1624"
+}
+{
+"targetname" "t153"
+"classname" "monster_soldier_ss"
+"angle" "270"
+"spawnflags" "802"
+"origin" "608 424 1624"
+}
+{
+"origin" "608 1200 1784"
+"angle" "-2"
+"spawnflags" "800"
+"classname" "monster_turret"
+}
+{
+"origin" "-160 -128 1528"
+"angle" "-2"
+"spawnflags" "32"
+"classname" "monster_turret"
+}
+{
+"classname" "target_help"
+"origin" "-1392 744 1568"
+"targetname" "t153"
+"spawnflags" "2049"
+"message" "Activate Anti-matter\nCore creator."
+"lip" "12"
+}
+{
+"classname" "target_help"
+"targetname" "t153"
+"message" "Find Security Pass\nfor Ammo Depot."
+"origin" "-1344 736 1568"
+"spawnflags" "2048"
+}
+{
+"model" "*60"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t153"
+}
+{
+"target" "t236"
+"targetname" "t153"
+"item" "ammo_shells"
+"spawnflags" "3"
+"origin" "-2008 248 1560"
+"classname" "monster_soldier"
+"angle" "0"
+}
+{
+"targetname" "t153"
+"target" "t163"
+"spawnflags" "34"
+"origin" "-1744 280 1880"
+"classname" "monster_soldier"
+"angle" "0"
+}
+{
+"targetname" "t153"
+"spawnflags" "3"
+"item" "ammo_shells"
+"origin" "-1936 280 1560"
+"angle" "0"
+"classname" "monster_soldier"
+}
+{
+"origin" "608 1496 1568"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "608 1632 1568"
+"light" "100"
+"classname" "light"
+}
+{
+"item" "ammo_cells"
+"origin" "-816 224 1880"
+"targetname" "t153"
+"spawnflags" "3"
+"angle" "0"
+"classname" "monster_medic_commander"
+}
+{
+"model" "*61"
+"spawnflags" "2048"
+"target" "t149"
+"classname" "trigger_once"
+}
+{
+"targetname" "t153"
+"item" "ammo_slugs"
+"spawnflags" "3"
+"origin" "-1952 624 1880"
+"angle" "270"
+"classname" "monster_gladiator"
+}
+{
+"origin" "-1048 -280 1600"
+"classname" "misc_explobox"
+}
+{
+"origin" "264 -192 1784"
+"angle" "0"
+"spawnflags" "800"
+"classname" "monster_turret"
+}
+{
+"origin" "-432 -328 1368"
+"spawnflags" "257"
+"angle" "135"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "-320 376 1440"
+"angle" "90"
+"spawnflags" "776"
+"classname" "monster_turret"
+}
+{
+"origin" "-1112 1512 888"
+"targetname" "t146"
+"angle" "270"
+"classname" "monster_medic_commander"
+}
+{
+"origin" "-1120 1952 1344"
+"classname" "monster_daedalus"
+"angle" "315"
+"spawnflags" "1"
+}
+{
+"origin" "-352 1952 1344"
+"spawnflags" "1"
+"angle" "225"
+"classname" "monster_daedalus"
+}
+{
+"targetname" "t153"
+"classname" "monster_turret"
+"spawnflags" "802"
+"angle" "-2"
+"origin" "1056 288 1720"
+}
+{
+"targetname" "t153"
+"classname" "monster_turret"
+"spawnflags" "34"
+"angle" "-2"
+"origin" "-1952 224 2040"
+}
+{
+"item" "ammo_cells"
+"origin" "-160 -288 1368"
+"targetname" "t145"
+"classname" "monster_medic_commander"
+"spawnflags" "1"
+"angle" "90"
+}
+{
+"model" "*62"
+"spawnflags" "2048"
+"target" "t144"
+"classname" "trigger_once"
+}
+{
+"item" "ammo_slugs"
+"origin" "-496 128 1368"
+"targetname" "t144"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_gladiator"
+}
+{
+"targetname" "t153"
+"origin" "-1792 -288 1872"
+"spawnflags" "3"
+"angle" "180"
+"classname" "monster_tank"
+}
+{
+"item" "ammo_shells"
+"origin" "-1664 -320 1592"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_soldier"
+}
+{
+"model" "*63"
+"spawnflags" "2048"
+"target" "t143"
+"classname" "trigger_once"
+}
+{
+"origin" "-1376 -376 1608"
+"targetname" "t142"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"item" "ammo_bullets"
+"origin" "-1192 -336 1624"
+"spawnflags" "1537"
+"targetname" "t143"
+"angle" "180"
+"target" "t142"
+"classname" "monster_soldier_ss"
+}
+{
+"targetname" "t153"
+"origin" "-1536 256 1920"
+"classname" "monster_daedalus"
+"angle" "270"
+"spawnflags" "3"
+}
+{
+"origin" "-1024 0 1920"
+"spawnflags" "769"
+"angle" "225"
+"classname" "monster_daedalus"
+}
+{
+"origin" "-320 992 1544"
+"angle" "-2"
+"spawnflags" "32"
+"classname" "monster_turret"
+}
+{
+"angle" "-2"
+"spawnflags" "776"
+"classname" "monster_turret"
+"origin" "-32 -928 1528"
+}
+{
+"origin" "-864 -1184 1720"
+"angle" "-2"
+"spawnflags" "1032"
+"classname" "monster_turret"
+"targetname" "t181"
+}
+{
+"model" "*64"
+"classname" "func_door"
+"angle" "-2"
+}
+{
+"item" "ammo_rockets"
+"target" "t162"
+"spawnflags" "1"
+"origin" "608 904 1488"
+"angle" "270"
+"classname" "monster_tank"
+}
+{
+"item" "ammo_rockets"
+"spawnflags" "1"
+"target" "t145"
+"origin" "-160 480 1360"
+"angle" "180"
+"classname" "monster_tank_commander"
+}
+{
+"item" "ammo_slugs"
+"target" "t156"
+"origin" "-160 -1216 1624"
+"spawnflags" "257"
+"angle" "180"
+"classname" "monster_gladiator"
+}
+{
+"spawnflags" "2048"
+"target" "t140"
+"targetname" "t139"
+"origin" "-216 -800 1416"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"target" "t139"
+"targetname" "t138"
+"origin" "-424 -752 1416"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"targetname" "t141"
+"target" "t138"
+"origin" "-416 -1088 1416"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"target" "t141"
+"targetname" "t140"
+"origin" "-424 -768 1416"
+"classname" "path_corner"
+}
+{
+"item" "ammo_bullets"
+"target" "t141"
+"origin" "-416 -1120 1432"
+"angle" "90"
+"classname" "monster_soldier_ss"
+}
+{
+"item" "ammo_shells"
+"origin" "-400 -672 1728"
+"spawnflags" "257"
+"angle" "270"
+"classname" "monster_soldier"
+}
+{
+"origin" "-1376 -696 1720"
+"angle" "-2"
+"spawnflags" "264"
+"classname" "monster_turret"
+}
+{
+"origin" "-480 -808 1440"
+"message" "Obtain Anti-matter\nPod from Ammo Depot."
+"spawnflags" "2049"
+"targetname" "t137"
+"classname" "target_help"
+}
+{
+"spawnflags" "2048"
+"origin" "-400 -736 1440"
+"message" "Find Entrance to\nAmmo Depot."
+"targetname" "t137"
+"classname" "target_help"
+}
+{
+"model" "*65"
+"spawnflags" "2048"
+"target" "t137"
+"classname" "trigger_once"
+}
+{
+"model" "*66"
+"spawnflags" "2048"
+"wait" "-1"
+"message" "Shuttle inactive."
+"classname" "trigger_multiple"
+}
+{
+"origin" "-1880 632 1960"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1952 560 1960"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1952 400 1960"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1856 152 1912"
+"light" "100"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"origin" "-1768 600 1880"
+"targetname" "rammo2c"
+"angle" "180"
+"classname" "info_player_coop"
+}
+{
+"spawnflags" "2048"
+"origin" "-1720 664 1880"
+"targetname" "rammo2c"
+"angle" "180"
+"classname" "info_player_coop"
+}
+{
+"spawnflags" "2048"
+"origin" "-1816 664 1880"
+"targetname" "rammo2c"
+"classname" "info_player_coop"
+"angle" "180"
+}
+{
+"spawnflags" "2048"
+"origin" "-1680 600 1880"
+"targetname" "rammo2c"
+"angle" "180"
+"classname" "info_player_coop"
+}
+{
+"spawnflags" "2048"
+"origin" "-1648 640 1880"
+"classname" "info_player_start"
+"angle" "180"
+"targetname" "rammo2c"
+}
+{
+"spawnflags" "2048"
+"map" "rammo2$rammo1c"
+"origin" "-1656 632 1928"
+"targetname" "t136"
+"classname" "target_changelevel"
+}
+{
+"model" "*67"
+"spawnflags" "2048"
+"target" "t136"
+"classname" "trigger_multiple"
+}
+{
+"spawnflags" "2048"
+"origin" "-1216 584 1560"
+"targetname" "rammo2b"
+"classname" "info_player_coop"
+"angle" "270"
+}
+{
+"spawnflags" "2048"
+"origin" "-1152 624 1560"
+"targetname" "rammo2b"
+"classname" "info_player_coop"
+"angle" "270"
+}
+{
+"targetname" "rammo2b"
+"spawnflags" "2048"
+"origin" "-1216 664 1560"
+"classname" "info_player_coop"
+"angle" "270"
+}
+{
+"spawnflags" "2048"
+"origin" "-1160 528 1560"
+"targetname" "rammo2b"
+"angle" "270"
+"classname" "info_player_coop"
+}
+{
+"spawnflags" "2048"
+"origin" "-1168 680 1560"
+"targetname" "rammo2b"
+"angle" "270"
+"classname" "info_player_start"
+}
+{
+"spawnflags" "2048"
+"origin" "-1184 712 1600"
+"map" "rammo2$rammo1b"
+"targetname" "t135"
+"classname" "target_changelevel"
+}
+{
+"model" "*68"
+"target" "t135"
+"angle" "90"
+"classname" "trigger_multiple"
+"spawnflags" "2056"
+"targetname" "t168"
+}
+{
+"classname" "misc_explobox"
+"origin" "-1248 -360 1600"
+}
+{
+"classname" "misc_explobox"
+"origin" "-1336 -216 1856"
+}
+{
+"model" "*69"
+"classname" "func_plat2"
+"lip" "80"
+"targetname" "t183"
+"spawnflags" "2"
+}
+{
+"model" "*70"
+"health" "5"
+"dmg" "75"
+"classname" "func_explosive"
+}
+{
+"model" "*71"
+"item" "ammo_grenades"
+"dmg" "75"
+"health" "1"
+"classname" "func_explosive"
+}
+{
+"origin" "-720 -288 1952"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-880 -288 1952"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-880 224 1952"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-720 224 1952"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-544 224 1952"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-544 48 1952"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-368 -32 1952"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "544 -96 1672"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "696 -96 1632"
+"classname" "light"
+"light" "75"
+}
+{
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "-448 1152 1088"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-864 1216 1076"
+}
+{
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "-320 384 1504"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-320 800 1608"
+}
+{
+"origin" "-1116 1724 900"
+"classname" "misc_banner"
+"angle" "0"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-704 1808 976"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-760 1808 976"
+}
+{
+"model" "*72"
+"classname" "func_wall"
+"spawnflags" "3"
+"team" "beamer"
+"targetname" "t130"
+}
+{
+"model" "*73"
+"spawnflags" "2048"
+"classname" "func_train"
+"target" "t111"
+"targetname" "t130"
+"team" "beamer"
+}
+{
+"model" "*74"
+"classname" "func_wall"
+"spawnflags" "2051"
+"targetname" "t127"
+}
+{
+"spawnflags" "2048"
+"classname" "target_changelevel"
+"targetname" "t133"
+"map" "reu4_.cin+*rboss$unitstart"
+"origin" "-736 2760 1072"
+}
+{
+"model" "*75"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t133"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t129"
+"delay" "1.5"
+"target" "t132"
+"origin" "-640 1512 1016"
+}
+{
+"model" "*76"
+"wait" "3"
+"angle" "180"
+"classname" "func_button"
+"target" "t131"
+"message" "Lift activated."
+}
+{
+"model" "*77"
+"classname" "func_button"
+"angle" "90"
+"wait" "3"
+"target" "t131"
+"message" "Lift activated."
+}
+{
+"origin" "-320 1152 1208"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-320 1152 1336"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-320 1088 1528"
+"classname" "light"
+"light" "100"
+}
+{
+"model" "*78"
+"classname" "trigger_once"
+"spawnflags" "2052"
+"target" "t130"
+"targetname" "t129"
+"message" "Gondola activated."
+}
+{
+"spawnflags" "2048"
+"origin" "-688 1172 1000"
+"classname" "trigger_relay"
+"delay" "4"
+"targetname" "t123"
+"target" "t128"
+}
+{
+"spawnflags" "2048"
+"origin" "-660 1436 1000"
+"classname" "trigger_relay"
+"targetname" "t123"
+"delay" "6"
+"target" "t129"
+"message" "Gondola prep sequence complete."
+}
+{
+"classname" "target_speaker"
+"origin" "-608 1460 1100"
+"noise" "world/l_hum2.wav"
+"spawnflags" "2050"
+"targetname" "t129"
+}
+{
+"classname" "target_speaker"
+"origin" "-608 1264 1100"
+"noise" "world/l_hum2.wav"
+"spawnflags" "2050"
+"targetname" "t128"
+}
+{
+"origin" "-864 1268 1100"
+"classname" "target_speaker"
+"noise" "world/l_hum2.wav"
+"spawnflags" "2050"
+"targetname" "t128"
+}
+{
+"origin" "-864 1120 968"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"targetname" "t123"
+"noise" "world/l_hum2.wav"
+}
+{
+"classname" "target_speaker"
+"origin" "-608 1116 968"
+"spawnflags" "2050"
+"targetname" "t123"
+"noise" "world/l_hum2.wav"
+}
+{
+"origin" "-736 1480 1100"
+"classname" "target_speaker"
+"noise" "world/l_hum2.wav"
+"spawnflags" "2050"
+}
+{
+"style" "34"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "200"
+"spawnflags" "2049"
+"origin" "-608 1440 1100"
+"targetname" "t129"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "200"
+"spawnflags" "2049"
+"origin" "-608 1308 1100"
+"targetname" "t128"
+}
+{
+"style" "35"
+"light" "200"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"spawnflags" "2049"
+"origin" "-864 1316 1100"
+"targetname" "t128"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "200"
+"spawnflags" "1"
+"origin" "-608 1056 1100"
+"targetname" "t128"
+}
+{
+"style" "35"
+"light" "200"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"spawnflags" "1"
+"origin" "-864 1064 1100"
+"targetname" "t128"
+}
+{
+"style" "36"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "200"
+"targetname" "t123"
+"spawnflags" "1"
+"origin" "-608 1144 968"
+}
+{
+"style" "36"
+"light" "200"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"targetname" "t123"
+"spawnflags" "2049"
+"origin" "-864 1152 968"
+}
+{
+"style" "34"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "200"
+"spawnflags" "2049"
+"origin" "-864 1448 1100"
+"targetname" "t129"
+}
+{
+"classname" "target_speaker"
+"origin" "-864 1464 1100"
+"noise" "world/l_hum2.wav"
+"spawnflags" "2050"
+"targetname" "t129"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/lason1.wav"
+"origin" "-664 1592 992"
+"targetname" "t123"
+}
+{
+"spawnflags" "2048"
+"origin" "-816 1592 992"
+"noise" "world/lason1.wav"
+"classname" "target_speaker"
+"targetname" "t123"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/lason1.wav"
+"origin" "-816 1760 992"
+"targetname" "t123"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/lason1.wav"
+"origin" "-736 1280 1056"
+"targetname" "t123"
+}
+{
+"spawnflags" "2048"
+"origin" "-664 1760 992"
+"noise" "world/lason1.wav"
+"classname" "target_speaker"
+"targetname" "t123"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"targetname" "t127"
+"noise" "world/fusein.wav"
+"origin" "-736 912 1056"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"targetname" "t127"
+"noise" "world/fusein.wav"
+"origin" "-736 968 1056"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"targetname" "t127"
+"noise" "world/fusein.wav"
+"origin" "-728 1064 1056"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_key"
+"targetname" "t126"
+"item" "key_power_cube"
+"target" "t127"
+"origin" "-720 920 1040"
+}
+{
+"model" "*79"
+"spawnflags" "2048"
+"wait" "10"
+"classname" "trigger_multiple"
+"target" "t126"
+}
+{
+"spawnflags" "2048"
+"origin" "-72 -1016 1624"
+"classname" "trigger_relay"
+"target" "t134"
+"targetname" "t1"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"origin" "-656 1320 1024"
+"target" "t134"
+"targetname" "t127"
+}
+{
+"spawnflags" "2048"
+"classname" "target_goal"
+"targetname" "t123"
+"origin" "-792 1312 1000"
+}
+{
+"classname" "target_help"
+"targetname" "t123"
+"origin" "-664 1280 1016"
+"spawnflags" "2048"
+"message" "Use Gondola to enter\n Gravity Well."
+}
+{
+"model" "*80"
+"targetname" "t127"
+"classname" "trigger_once"
+"message" "Gondola systems activated."
+"target" "t123"
+"spawnflags" "2052"
+}
+{
+"model" "*81"
+"classname" "trigger_multiple"
+"message" "Console inactive, insufficient power."
+"spawnflags" "2056"
+"wait" "15"
+"targetname" "t134"
+}
+{
+"model" "*82"
+"spawnflags" "2048"
+"wait" "-1"
+"classname" "func_button"
+"angle" "90"
+"targetname" "t123"
+}
+{
+"model" "*83"
+"classname" "func_door"
+"angle" "-2"
+"spawnflags" "2049"
+"lip" "32"
+"wait" "-1"
+"_minlight" ".2"
+"targetname" "t123"
+"speed" "50"
+}
+{
+"model" "*84"
+"spawnflags" "2048"
+"classname" "func_button"
+"angle" "270"
+"target" "t122"
+"wait" "-1"
+"message" "Power center opened."
+}
+{
+"model" "*85"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-1"
+"wait" "-1"
+"speed" "15"
+"targetname" "t122"
+}
+{
+"origin" "-1120 1280 1076"
+"classname" "light"
+"light" "100"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t120"
+"speed" "280"
+"target" "t121"
+"origin" "-888 2704 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"speed" "260"
+"targetname" "t119"
+"target" "t120"
+"origin" "-888 2576 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t118"
+"speed" "240"
+"target" "t119"
+"origin" "-888 2448 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t117"
+"speed" "220"
+"target" "t118"
+"origin" "-888 2320 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t116"
+"speed" "200"
+"target" "t117"
+"origin" "-888 2192 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t115"
+"speed" "180"
+"target" "t116"
+"origin" "-888 2064 860"
+}
+{
+"spawnflags" "2048"
+"pathtarget" "holer"
+"classname" "path_corner"
+"speed" "160"
+"targetname" "t114"
+"target" "t115"
+"origin" "-888 1936 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t113"
+"speed" "140"
+"target" "t114"
+"origin" "-888 1808 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t111"
+"speed" "120"
+"target" "t113"
+"origin" "-888 1680 860"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t121"
+"speed" "300"
+"origin" "-888 2832 860"
+"wait" "-1"
+}
+{
+"angle" "90"
+"classname" "target_laser"
+"origin" "-608 1416 1100"
+"spawnflags" "2120"
+"dmg" "5000"
+"targetname" "t129"
+}
+{
+"angle" "90"
+"classname" "target_laser"
+"origin" "-608 1032 1100"
+"targetname" "t128"
+"spawnflags" "2120"
+"dmg" "5000"
+}
+{
+"classname" "target_laser"
+"angle" "90"
+"origin" "-864 1032 1100"
+"spawnflags" "2120"
+"dmg" "5000"
+"targetname" "t128"
+}
+{
+"angle" "90"
+"classname" "target_laser"
+"origin" "-864 1416 1100"
+"spawnflags" "2120"
+"dmg" "5000"
+"targetname" "t129"
+}
+{
+"classname" "target_laser"
+"angle" "90"
+"origin" "-736 1528 956"
+"spawnflags" "2120"
+"dmg" "5000"
+"targetname" "t129"
+}
+{
+"angle" "270"
+"classname" "target_laser"
+"origin" "-608 1208 968"
+"targetname" "t123"
+"spawnflags" "2120"
+"dmg" "5000"
+}
+{
+"classname" "target_laser"
+"angle" "270"
+"origin" "-864 1208 968"
+"targetname" "t123"
+"spawnflags" "2120"
+"dmg" "5000"
+}
+{
+"origin" "-544 1440 1140"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-736 1632 1140"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-480 1696 1140"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-992 1696 1140"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-608 1216 1076"
+}
+{
+"model" "*86"
+"origin" "-256 672 1472"
+"distance" "-90"
+"classname" "func_door_rotating"
+"spawnflags" "2050"
+"message" "Gravity Well Access denied!"
+"wait" "-1"
+"targetname" "t167"
+"team" "chapel"
+}
+{
+"model" "*87"
+"origin" "-736 1536 846"
+"classname" "func_door_rotating"
+"spawnflags" "64"
+"distance" "-90"
+"targetname" "t132"
+"wait" "-1"
+}
+{
+"model" "*88"
+"origin" "-928 1802 900"
+"classname" "func_door_rotating"
+"spawnflags" "2177"
+"distance" "-90"
+"targetname" "t123"
+"wait" "-1"
+}
+{
+"model" "*89"
+"lip" "16"
+"classname" "func_plat2"
+"_minlight" ".3"
+"targetname" "t131"
+"spawnflags" "2"
+}
+{
+"spawnflags" "0"
+"angle" "180"
+"classname" "misc_banner"
+"origin" "-356 1724 900"
+}
+{
+"model" "*90"
+"origin" "-384 672 1472"
+"classname" "func_door_rotating"
+"distance" "-90"
+"spawnflags" "2048"
+"message" "Gravity Well Access denied!"
+"wait" "-1"
+"targetname" "t167"
+"team" "chapel"
+}
+{
+"origin" "-320 608 1608"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-120 632 1368"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-160 600 1368"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-480 600 1368"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-320 1152 1080"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "192 640 1808"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+"origin" "-432 616 1600"
+}
+{
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "-528 616 1600"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+"origin" "-208 616 1600"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "175"
+"classname" "light"
+"origin" "-480 592 1600"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "175"
+"classname" "light"
+"origin" "-160 592 1600"
+}
+{
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "-112 616 1600"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "664 540 1656"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "608 384 1684"
+}
+{
+"spawnflags" "2048"
+"origin" "680 -328 1768"
+"target" "t109"
+"targetname" "red2"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "824 -328 1768"
+"target" "t110"
+"targetname" "red3"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "584 -312 1768"
+"target" "t108"
+"targetname" "red1"
+"classname" "trigger_relay"
+}
+{
+"style" "37"
+"spawnflags" "2049"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "175"
+"targetname" "red2"
+"origin" "688 -432 1836"
+}
+{
+"style" "38"
+"spawnflags" "2048"
+"targetname" "t109"
+"_color" "0.436170 1.000000 0.356383"
+"light" "175"
+"classname" "light"
+"origin" "720 -432 1836"
+}
+{
+"style" "39"
+"spawnflags" "2049"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "175"
+"targetname" "red1"
+"origin" "560 -432 1836"
+}
+{
+"style" "40"
+"spawnflags" "2048"
+"targetname" "t108"
+"_color" "0.436170 1.000000 0.356383"
+"light" "175"
+"classname" "light"
+"origin" "592 -432 1836"
+}
+{
+"style" "41"
+"light" "175"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"spawnflags" "2049"
+"targetname" "red3"
+"origin" "816 -432 1836"
+}
+{
+"style" "42"
+"spawnflags" "2048"
+"targetname" "t110"
+"classname" "light"
+"light" "175"
+"_color" "0.436170 1.000000 0.356383"
+"origin" "848 -432 1836"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"target" "t107"
+"targetname" "t50"
+"origin" "840 164 1480"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t1"
+"target" "t107"
+"origin" "-48 -928 1624"
+}
+{
+"targetname" "rammo2a"
+"spawnflags" "2048"
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "632 1512 1496"
+}
+{
+"targetname" "rammo2a"
+"spawnflags" "2048"
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "640 1592 1496"
+}
+{
+"targetname" "rammo2a"
+"spawnflags" "2048"
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "584 1624 1496"
+}
+{
+"spawnflags" "2048"
+"classname" "info_player_coop"
+"targetname" "rammo2a"
+"angle" "270"
+"origin" "584 1544 1496"
+}
+{
+"targetname" "rammo2a"
+"spawnflags" "2048"
+"origin" "608 1584 1496"
+"angle" "270"
+"classname" "info_player_start"
+}
+{
+"spawnflags" "2048"
+"classname" "target_changelevel"
+"targetname" "t106"
+"map" "rammo2$rammo1a"
+"origin" "608 1632 1568"
+}
+{
+"model" "*91"
+"classname" "trigger_multiple"
+"target" "t106"
+"angle" "90"
+"spawnflags" "2056"
+"targetname" "exiter"
+}
+{
+"origin" "608 1040 1736"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "568 1400 1592"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "648 1400 1592"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "736 1072 1688"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+}
+{
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "736 1200 1688"
+}
+{
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "736 1328 1688"
+}
+{
+"origin" "480 1200 1688"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+}
+{
+"origin" "480 1328 1688"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+}
+{
+"origin" "888 224 1568"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "608 1208 1736"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "768 672 1808"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "160 -96 1492"
+}
+{
+"origin" "160 384 1528"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-1952 -288 1760"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-1952 -288 1928"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-1632 -64 1936"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "160 176 1492"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-640 -96 1492"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "160 32 2004"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-192 -364 1464"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-84 -256 1464"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-84 448 1464"
+}
+{
+"origin" "-84 256 1464"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-84 64 1464"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "1056 -364 1660"
+"classname" "light"
+"light" "100"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t50"
+"target" "t105"
+"delay" "5"
+"origin" "680 480 1624"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t50"
+"target" "t105"
+"origin" "536 480 1624"
+}
+{
+"classname" "target_speaker"
+"origin" "552 448 1640"
+"noise" "world/klaxon2.wav"
+"targetname" "t105"
+"spawnflags" "2050"
+}
+{
+"classname" "target_speaker"
+"origin" "664 448 1640"
+"noise" "world/klaxon2.wav"
+"targetname" "t105"
+"spawnflags" "2050"
+}
+{
+"model" "*92"
+"spawnflags" "2048"
+"classname" "func_button"
+"target" "t50"
+"wait" "-1"
+"message" "Anti-matter core creation initiated."
+"angle" "90"
+"lip" "12"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-556 64 1464"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-556 256 1464"
+}
+{
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "480 1072 1688"
+}
+{
+"origin" "-556 448 1464"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-556 -256 1464"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-448 -364 1464"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "0 -20 1464"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "84 608 1528"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "352 -40 1704"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "408 -96 1696"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "-544 -288 1952"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "-208 -32 1952"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "848 -96 1600"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1000 -96 1568"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1056 -40 1568"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1056 120 1568"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "352 120 1704"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "448 672 1808"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1132 608 1528"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1132 288 1524"
+}
+{
+"origin" "352 288 1684"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-928 1440 1140"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "832 20 1660"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-736 1380 920"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "768 -68 1780"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-864 -96 1544"
+}
+{
+"origin" "160 -288 2004"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "576 20 1660"
+}
+{
+"origin" "776 -288 1796"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "1088 48 1812"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "896 -328 1824"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+}
+{
+"origin" "512 -328 1824"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+}
+{
+"origin" "352 -328 1824"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"origin" "-320 928 1520"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "280 320 1656"
+}
+{
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "1056 -328 1824"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "512 -68 1780"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "912 -160 1940"
+}
+{
+"model" "*93"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*94"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "640 -160 1940"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1088 -192 1812"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "352 -64 2004"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "864 288 1692"
+}
+{
+"origin" "416 816 1520"
+"classname" "light"
+"light" "100"
+}
+{
+"model" "*95"
+"spawnflags" "2048"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"classname" "misc_explobox"
+"origin" "-288 -992 1408"
+}
+{
+"classname" "misc_explobox"
+"origin" "-368 -1192 1408"
+}
+{
+"classname" "misc_explobox"
+"origin" "-168 -600 1600"
+}
+{
+"origin" "-1216 -288 1600"
+"classname" "misc_explobox"
+}
+{
+"model" "*96"
+"classname" "func_explosive"
+"health" "5"
+"dmg" "100"
+"target" "t179"
+}
+{
+"model" "*97"
+"health" "5"
+"dmg" "100"
+"classname" "func_explosive"
+}
+{
+"model" "*98"
+"dmg" "75"
+"health" "1"
+"classname" "func_explosive"
+}
+{
+"classname" "misc_explobox"
+"origin" "-864 -1248 1600"
+}
+{
+"model" "*99"
+"classname" "trigger_hurt"
+"targetname" "t52"
+"spawnflags" "2059"
+"dmg" "25"
+}
+{
+"origin" "304 896 1744"
+"classname" "light"
+"light" "100"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "561 855 1632"
+"pathtarget" "oredor3"
+"targetname" "t38"
+"target" "t57"
+"wait" "3"
+}
+{
+"model" "*100"
+"classname" "func_wall"
+"targetname" "t52"
+"spawnflags" "7"
+}
+{
+"_color" "1.000000 0.679487 0.089744"
+"light" "100"
+"classname" "light"
+"origin" "1040 752 1614"
+}
+{
+"model" "*101"
+"spawnflags" "2080"
+"classname" "func_door"
+"angle" "-1"
+"targetname" "oredor2"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/fusein.wav"
+"targetname" "t48"
+"origin" "1088 772 1520"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t30"
+"delay" "6"
+"target" "t56"
+"origin" "872 1048 1696"
+}
+{
+"spawnflags" "2048"
+"delay" "3.25"
+"killtarget" "t165"
+"classname" "trigger_relay"
+"origin" "996 1056 1496"
+"targetname" "t46"
+}
+{
+"style" "43"
+"light" "150"
+"classname" "light"
+"targetname" "t55"
+"spawnflags" "2049"
+"origin" "928 759 1576"
+"_color" "1.000000 0.000000 0.000000"
+"wait" "4.5"
+}
+{
+"style" "43"
+"classname" "light"
+"light" "150"
+"targetname" "t55"
+"spawnflags" "2049"
+"origin" "928 759 1528"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "43"
+"light" "150"
+"classname" "light"
+"targetname" "t55"
+"spawnflags" "2049"
+"origin" "736 759 1528"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "43"
+"classname" "light"
+"light" "150"
+"targetname" "t55"
+"spawnflags" "2049"
+"origin" "736 759 1576"
+"_color" "1.000000 0.000000 0.000000"
+"wait" "4.5"
+}
+{
+"spawnflags" "2048"
+"speed" "3"
+"message" "za"
+"classname" "target_lightramp"
+"origin" "712 696 1720"
+"target" "t55"
+"targetname" "steam2"
+}
+{
+"spawnflags" "2048"
+"message" "az"
+"speed" "3"
+"classname" "target_lightramp"
+"origin" "952 696 1720"
+"target" "t55"
+"targetname" "steamage"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"message" "Core creation complete."
+"origin" "1072 1072 1488"
+"targetname" "core"
+"target" "t165"
+}
+{
+"spawnflags" "2050"
+"noise" "world/lasburn1.wav"
+"origin" "576 648 1664"
+"classname" "target_speaker"
+"targetname" "t53"
+}
+{
+"spawnflags" "2050"
+"noise" "world/lasburn1.wav"
+"origin" "640 648 1664"
+"classname" "target_speaker"
+"targetname" "t53"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/x_light.wav"
+"targetname" "core"
+"origin" "1064 688 1568"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/x_light.wav"
+"targetname" "core"
+"origin" "1112 688 1568"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"delay" "4.5"
+"targetname" "steamage"
+"target" "t54"
+"origin" "1048 1076 1488"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "steamage"
+"origin" "1052 1056 1488"
+"target" "t54"
+"message" "Compression chamber activated."
+}
+{
+"classname" "target_speaker"
+"noise" "world/airhiss1.wav"
+"origin" "792 799 1488"
+"targetname" "t54"
+"spawnflags" "2050"
+}
+{
+"classname" "target_speaker"
+"noise" "world/airhiss1.wav"
+"origin" "872 799 1488"
+"targetname" "t54"
+"spawnflags" "2050"
+}
+{
+"classname" "target_speaker"
+"noise" "world/airhiss1.wav"
+"targetname" "t54"
+"origin" "920 632 1488"
+"spawnflags" "2050"
+}
+{
+"noise" "world/airhiss1.wav"
+"classname" "target_speaker"
+"origin" "728 632 1488"
+"targetname" "t54"
+"spawnflags" "2050"
+}
+{
+"spawnflags" "2048"
+"noise" "world/electro.wav"
+"classname" "target_speaker"
+"targetname" "t37"
+"origin" "776 743 1552"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/electro.wav"
+"targetname" "t37"
+"origin" "976 616 1552"
+}
+{
+"spawnflags" "2048"
+"noise" "world/electro.wav"
+"classname" "target_speaker"
+"targetname" "t37"
+"origin" "888 744 1552"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/electro.wav"
+"targetname" "t37"
+"origin" "696 616 1552"
+}
+{
+"spawnflags" "2048"
+"origin" "848 1076 1516"
+"wait" "7"
+"classname" "trigger_relay"
+"target" "t53"
+"delay" "7"
+"targetname" "t56"
+}
+{
+"classname" "target_speaker"
+"origin" "640 840 1696"
+"noise" "world/lasburn1.wav"
+"spawnflags" "2050"
+"targetname" "t53"
+}
+{
+"classname" "target_speaker"
+"origin" "576 840 1696"
+"noise" "world/lasburn1.wav"
+"spawnflags" "2050"
+"targetname" "t53"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"origin" "872 1076 1664"
+"targetname" "t56"
+"target" "t53"
+"message" "Ore cleansing in progress."
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "windor1"
+"target" "t52"
+"origin" "408 896 1664"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "windor1"
+"delay" "11"
+"target" "t52"
+"origin" "424 896 1664"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/amb14.wav"
+"targetname" "t52"
+"origin" "448 768 1672"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/amb14.wav"
+"targetname" "t52"
+"origin" "384 768 1672"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/amb14.wav"
+"targetname" "t52"
+"origin" "384 624 1616"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/amb14.wav"
+"targetname" "t52"
+"origin" "448 624 1616"
+}
+{
+"spawnflags" "2048"
+"delay" "25"
+"origin" "-40 984 1632"
+"classname" "trigger_relay"
+"target" "t51"
+"targetname" "t50"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"origin" "-72 984 1632"
+"target" "t51"
+"targetname" "t50"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+"team" "nuker"
+"origin" "-80 900 1664"
+"targetname" "t51"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+"team" "nuker"
+"origin" "-56 900 1664"
+"targetname" "t51"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "80 896 1744"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "648 1000 1592"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "832 896 1528"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1088 808 1520"
+}
+{
+"spawnflags" "2048"
+"origin" "996 1076 1496"
+"classname" "trigger_relay"
+"killtarget" "t44"
+"targetname" "t46"
+"delay" "4"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t46"
+"target" "t47"
+"origin" "1024 1076 1560"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"origin" "1000 1036 1504"
+"targetname" "t44"
+"target" "t43"
+"delay" "1"
+"message" "Core insertion module activated."
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"origin" "972 1076 1496"
+"targetname" "tain"
+"target" "t46"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"wait" "-1"
+"origin" "1064 720 1496"
+"targetname" "t40"
+"target" "t45"
+}
+{
+"model" "*102"
+"spawnflags" "2048"
+"classname" "func_train"
+"target" "t40"
+"targetname" "t43"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"wait" "6.5"
+"origin" "1064 772 1496"
+"targetname" "t45"
+"target" "t40"
+"pathtarget" "tain"
+}
+{
+"spawnflags" "2048"
+"delay" "3.5"
+"origin" "972 1056 1520"
+"classname" "trigger_relay"
+"targetname" "t46"
+"target" "t48"
+"message" "Core insertion complete."
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "windor2"
+"target" "t37"
+"origin" "848 1048 1520"
+"delay" "3.5"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "-104 856 1296"
+"targetname" "t36"
+"target" "t23"
+"speed" "1000"
+"pathtarget" "key"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "384 856 1632"
+"pathtarget" "oredor3"
+"targetname" "t31"
+"target" "t32"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "windor1"
+"origin" "928 1076 1720"
+"target" "t29"
+"message" "Ore stabilization in progress."
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "windor1"
+"origin" "904 1076 1720"
+"target" "t30"
+"delay" "7"
+}
+{
+"spawnflags" "2048"
+"pathtarget" "oredor3"
+"origin" "512 856 1632"
+"classname" "path_corner"
+"targetname" "t32"
+"target" "t38"
+}
+{
+"spawnflags" "2048"
+"classname" "target_steam"
+"angle" "270"
+"origin" "576 944 1696"
+"speed" "150"
+"sounds" "208"
+"wait" "7"
+"targetname" "t56"
+}
+{
+"spawnflags" "2048"
+"classname" "target_steam"
+"angle" "0"
+"origin" "776 799 1488"
+"targetname" "steamage"
+"sounds" "4"
+"wait" "5"
+}
+{
+"spawnflags" "2048"
+"origin" "640 944 1696"
+"angle" "270"
+"classname" "target_steam"
+"speed" "150"
+"sounds" "208"
+"wait" "7"
+"targetname" "t56"
+}
+{
+"model" "*103"
+"_minlight" ".2"
+"spawnflags" "2048"
+"classname" "func_train"
+"target" "t23"
+"targetname" "t50"
+"speed" "50"
+"team" "nuker"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "-72 856 1632"
+"targetname" "t24"
+"pathtarget" "oredor1"
+"target" "t25"
+}
+{
+"spawnflags" "2048"
+"origin" "64 856 1632"
+"classname" "path_corner"
+"pathtarget" "oredor1"
+"targetname" "t25"
+"target" "t26"
+}
+{
+"model" "*104"
+"origin" "1116 770 1544"
+"classname" "func_door_rotating"
+"spawnflags" "2055"
+"distance" "-115"
+"_minlight" ".3"
+"targetname" "t47"
+"sounds" "3"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_key"
+"targetname" "t21"
+"item" "key_nuke_container"
+"origin" "972 1036 1504"
+"target" "t44"
+}
+{
+"classname" "key_nuke"
+"spawnflags" "2049"
+"origin" "1088 808 1524"
+"targetname" "t48"
+"target" "t167"
+}
+{
+"classname" "key_nuke_container"
+"spawnflags" "2049"
+"origin" "1088 744 1524"
+"targetname" "t44"
+}
+{
+"spawnflags" "2048"
+"classname" "misc_nuke_core"
+"origin" "1088 808 1572"
+"targetname" "t165"
+}
+{
+"spawnflags" "2048"
+"origin" "832 896 1480"
+"count" "48"
+"speed" "200"
+"angle" "-1"
+"classname" "target_steam"
+"targetname" "steam2"
+"wait" "16"
+"sounds" "230"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "792 856 1480"
+"targetname" "t34"
+"pathtarget" "windor2"
+"wait" "10"
+"target" "t35"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "792 856 1632"
+"target" "t34"
+"targetname" "t57"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "376 856 1632"
+"targetname" "t28"
+"pathtarget" "windor1"
+"wait" "12"
+"target" "t31"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "344 856 1632"
+"targetname" "t27"
+"pathtarget" "oredor2"
+"target" "t28"
+}
+{
+"model" "*105"
+"spawnflags" "2048"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "oredor3"
+"speed" "125"
+}
+{
+"model" "*106"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "oredor1"
+"spawnflags" "32"
+"speed" "125"
+}
+{
+"spawnflags" "2048"
+"origin" "216 856 1632"
+"classname" "path_corner"
+"pathtarget" "oredor2"
+"targetname" "t26"
+"target" "t27"
+}
+{
+"spawnflags" "2048"
+"origin" "-104 856 1632"
+"classname" "path_corner"
+"targetname" "t23"
+"target" "t24"
+"wait" "-1"
+"speed" "100"
+}
+{
+"spawnflags" "2048"
+"origin" "792 856 1296"
+"classname" "path_corner"
+"targetname" "t35"
+"target" "t36"
+"pathtarget" "core"
+}
+{
+"origin" "1136 752 1614"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.679487 0.089744"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "568 1000 1592"
+}
+{
+"spawnflags" "2048"
+"origin" "432 936 1720"
+"target" "t18"
+"classname" "target_lightramp"
+"message" "za"
+"speed" "2"
+"targetname" "t30"
+}
+{
+"spawnflags" "2048"
+"origin" "400 936 1720"
+"target" "t18"
+"classname" "target_lightramp"
+"speed" "2"
+"message" "az"
+"targetname" "t29"
+}
+{
+"style" "44"
+"origin" "448 896 1720"
+"targetname" "t18"
+"spawnflags" "1"
+"light" "175"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "44"
+"origin" "384 896 1720"
+"targetname" "t18"
+"spawnflags" "1"
+"light" "175"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"spawnflags" "0"
+"light" "100"
+"classname" "light"
+"origin" "-1344 -64 1936"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1056 -64 1936"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-1952 -288 1632"
+}
+{
+"spawnflags" "2048"
+"classname" "target_steam"
+"angle" "270"
+"origin" "928 759 1488"
+"targetname" "steamage"
+"sounds" "4"
+"wait" "5"
+}
+{
+"spawnflags" "2048"
+"origin" "888 799 1488"
+"angle" "180"
+"classname" "target_steam"
+"targetname" "steamage"
+"sounds" "4"
+"wait" "5"
+}
+{
+"model" "*107"
+"origin" "416 804 1712"
+"classname" "func_door_rotating"
+"spawnflags" "2119"
+"distance" "120"
+"_minlight" ".3"
+"targetname" "windor1"
+"speed" "50"
+"wait" "6"
+"sounds" "4"
+}
+{
+"model" "*108"
+"team" "mush1"
+"target" "t17"
+"classname" "func_train"
+"targetname" "t37"
+}
+{
+"origin" "704 767 1536"
+"targetname" "t17"
+"target" "t15"
+"classname" "path_corner"
+}
+{
+"origin" "704 767 1536"
+"target" "t17"
+"targetname" "t16"
+"classname" "path_corner"
+"wait" "-1"
+"speed" "15"
+}
+{
+"origin" "704 767 1600"
+"target" "t16"
+"targetname" "t15"
+"classname" "path_corner"
+"speed" "150"
+"wait" ".5"
+}
+{
+"origin" "896 767 1536"
+"speed" "15"
+"target" "t14"
+"wait" "-1"
+"targetname" "t13"
+"classname" "path_corner"
+"pathtarget" "steam2"
+}
+{
+"origin" "896 767 1600"
+"speed" "150"
+"target" "t13"
+"targetname" "t12"
+"classname" "path_corner"
+"pathtarget" "steamage"
+"wait" ".5"
+}
+{
+"origin" "896 767 1536"
+"targetname" "t14"
+"target" "t12"
+"classname" "path_corner"
+}
+{
+"model" "*109"
+"team" "mush2"
+"target" "t14"
+"classname" "func_train"
+"targetname" "t37"
+}
+{
+"spawnflags" "2048"
+"origin" "736 759 1488"
+"angle" "270"
+"classname" "target_steam"
+"targetname" "steamage"
+"sounds" "4"
+"wait" "5"
+}
+{
+"model" "*110"
+"origin" "832 836 1560"
+"distance" "120"
+"spawnflags" "2119"
+"classname" "func_door_rotating"
+"_minlight" ".3"
+"targetname" "windor2"
+"speed" "50"
+"wait" "10"
+"sounds" "4"
+}
+{
+"origin" "-1632 152 1912"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1632 152 1592"
+}
+{
+"spawnflags" "2048"
+"origin" "-1760 -76 1544"
+"targetname" "t11"
+"classname" "target_secret"
+}
+{
+"model" "*111"
+"spawnflags" "2048"
+"target" "t11"
+"classname" "trigger_once"
+}
+{
+"model" "*112"
+"target" "t10"
+"angle" "90"
+"message" "You've opened a secret."
+"wait" "-1"
+"health" "1"
+"classname" "func_button"
+}
+{
+"model" "*113"
+"spawnflags" "2048"
+"health" "25"
+"classname" "func_explosive"
+}
+{
+"model" "*114"
+"spawnflags" "2048"
+"targetname" "t10"
+"lip" "-1"
+"angle" "-2"
+"classname" "func_door"
+"wait" "-1"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1248 152 1592"
+}
+{
+"origin" "-1440 152 1592"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1528 256 1608"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1056 152 1592"
+}
+{
+"origin" "-1248 152 1912"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-1216 -352 1664"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1440 152 1912"
+}
+{
+"origin" "-1472 -216 1912"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1280 -216 1912"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1056 152 1912"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1184 520 1632"
+"light" "125"
+"classname" "light"
+}
+{
+"model" "*115"
+"spawnflags" "2048"
+"target" "t1"
+"classname" "func_button"
+"angle" "0"
+"wait" "-1"
+"message" "Plant access granted."
+}
+{
+"spawnflags" "2048"
+"classname" "light"
+"light" "75"
+"origin" "-24 -960 1632"
+}
+{
+"origin" "-520 632 1368"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "640 -288 1796"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-544 -704 1808"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-192 -704 1808"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1024 640 1808"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*116"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-2"
+"wait" "-1"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-1184 680 1632"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1720 632 1960"
+}
+{
+"model" "*117"
+"spawnflags" "2048"
+"targetname" "t190"
+"message" "This door is locked."
+"wait" "-1"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"model" "*118"
+"spawnflags" "2048"
+"message" "Plant door is locked."
+"targetname" "t1"
+"classname" "func_door"
+"angle" "-2"
+"wait" "-1"
+}
+{
+"model" "*119"
+"classname" "func_plat2"
+"lip" "16"
+"spawnflags" "1"
+}
+{
+"origin" "-608 -608 1464"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-608 -732 1464"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-552 352 1344"
+"classname" "misc_explobox"
+}
+{
+"model" "*120"
+"classname" "func_explosive"
+"dmg" "75"
+"health" "5"
+}
+{
+"model" "*121"
+"target" "t177"
+"classname" "func_explosive"
+"dmg" "125"
+"health" "5"
+"mass" "200"
+}
+{
+"origin" "-552 296 1344"
+"classname" "misc_explobox"
+}
+{
+"origin" "-80 -1216 1416"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"classname" "target_speaker"
+}
+{
+"model" "*122"
+"classname" "trigger_hurt"
+"spawnflags" "4"
+"dmg" "1000"
+}
+{
+"model" "*123"
+"classname" "trigger_push"
+"angle" "270"
+}
+{
+"model" "*124"
+"origin" "-960 2522 1016"
+"targetname" "holer"
+"classname" "func_door_rotating"
+"speed" "110"
+"distance" "77.5"
+"_minlight" ".2"
+"spawnflags" "2208"
+"team" "hole"
+}
+{
+"model" "*125"
+"origin" "-932 2522 1128"
+"targetname" "holer"
+"classname" "func_door_rotating"
+"speed" "110"
+"distance" "77.5"
+"_minlight" ".2"
+"spawnflags" "2208"
+"team" "hole"
+}
+{
+"model" "*126"
+"origin" "-736 2522 1240"
+"team" "hole"
+"spawnflags" "2208"
+"_minlight" ".2"
+"distance" "77.5"
+"speed" "110"
+"classname" "func_door_rotating"
+"targetname" "holer"
+}
+{
+"model" "*127"
+"origin" "-540 2522 1128"
+"team" "hole"
+"spawnflags" "2208"
+"_minlight" ".2"
+"distance" "77.5"
+"speed" "110"
+"classname" "func_door_rotating"
+"targetname" "holer"
+}
+{
+"model" "*128"
+"origin" "-540 2522 904"
+"targetname" "holer"
+"classname" "func_door_rotating"
+"speed" "110"
+"distance" "77.5"
+"_minlight" ".2"
+"spawnflags" "2208"
+"team" "hole"
+}
+{
+"model" "*129"
+"origin" "-624 2522 1212"
+"targetname" "holer"
+"classname" "func_door_rotating"
+"speed" "110"
+"distance" "77.5"
+"_minlight" ".2"
+"spawnflags" "2208"
+"team" "hole"
+}
+{
+"model" "*130"
+"origin" "-512 2522 1016"
+"targetname" "holer"
+"classname" "func_door_rotating"
+"speed" "110"
+"distance" "77.5"
+"_minlight" ".2"
+"spawnflags" "2208"
+"team" "hole"
+}
+{
+"model" "*131"
+"origin" "-848 2522 1212"
+"team" "hole"
+"spawnflags" "2208"
+"_minlight" ".2"
+"distance" "77.5"
+"speed" "110"
+"classname" "func_door_rotating"
+"targetname" "holer"
+}
+{
+"model" "*132"
+"origin" "-848 2522 820"
+"targetname" "holer"
+"classname" "func_door_rotating"
+"speed" "110"
+"distance" "77.5"
+"_minlight" ".2"
+"spawnflags" "2208"
+"team" "hole"
+}
+{
+"model" "*133"
+"origin" "-932 2522 904"
+"team" "hole"
+"spawnflags" "2208"
+"_minlight" ".2"
+"distance" "77.5"
+"speed" "110"
+"classname" "func_door_rotating"
+"targetname" "holer"
+}
+{
+"model" "*134"
+"origin" "-624 2522 820"
+"team" "hole"
+"spawnflags" "2208"
+"_minlight" ".2"
+"distance" "77.5"
+"speed" "110"
+"classname" "func_door_rotating"
+"targetname" "holer"
+}
+{
+"model" "*135"
+"origin" "-736 2522 792"
+"targetname" "holer"
+"classname" "func_door_rotating"
+"speed" "110"
+"distance" "77.5"
+"_minlight" ".2"
+"spawnflags" "2208"
+"team" "hole"
+}
+{
+"origin" "-1280 384 1560"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.148649 0.148649"
+}
+{
+"origin" "-1264 384 1632"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.148649 0.148649"
+}
+{
+"origin" "-1184 384 1632"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.148649 0.148649"
+}
+{
+"origin" "-1104 384 1632"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.148649 0.148649"
+}
+{
+"origin" "-1088 384 1560"
+"_color" "1.000000 0.148649 0.148649"
+"light" "125"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"target" "t171"
+"targetname" "t170"
+"origin" "-1280 340 1638"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"target" "t170"
+"targetname" "t171"
+"origin" "-1280 340 1540"
+"classname" "path_corner"
+}
+{
+"model" "*136"
+"target" "t170"
+"spawnflags" "2049"
+"classname" "func_train"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t59"
+"target" "t60"
+"origin" "128 -448 1746"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t60"
+"target" "t61"
+"origin" "128 -360 1746"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t69"
+"target" "t59"
+"origin" "128 -448 1632"
+}
+{
+"spawnflags" "2048"
+"origin" "128 -360 1746"
+"classname" "path_corner"
+"targetname" "t70"
+"target" "t71"
+}
+{
+"spawnflags" "2048"
+"origin" "128 -448 1746"
+"classname" "path_corner"
+"target" "t70"
+"targetname" "t80"
+}
+{
+"spawnflags" "2048"
+"origin" "128 -512 1632"
+"classname" "path_corner"
+"targetname" "t78"
+"target" "t79"
+}
+{
+"spawnflags" "2048"
+"origin" "128 -448 1632"
+"classname" "path_corner"
+"targetname" "t79"
+"target" "t80"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "128 -360 1746"
+"targetname" "t81"
+"target" "t82"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "128 -448 1746"
+"target" "t81"
+"targetname" "t91"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "128 -448 1632"
+"targetname" "t90"
+"target" "t91"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "128 -512 1632"
+"targetname" "t89"
+"target" "t90"
+}
+{
+"spawnflags" "2048"
+"origin" "16 -352 1392"
+"targetname" "t1"
+"target" "t92"
+"classname" "trigger_relay"
+}
+{
+"model" "*137"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t92"
+"target" "t94"
+}
+{
+"model" "*138"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t92"
+"target" "t93"
+}
+{
+"model" "*139"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t92"
+"target" "t97"
+}
+{
+"spawnflags" "2048"
+"delay" "1"
+"origin" "136 -176 1880"
+"classname" "trigger_relay"
+"targetname" "t97"
+"target" "t98"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t93"
+"origin" "136 -200 1880"
+"delay" "4"
+"target" "t95"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t94"
+"origin" "136 -232 1880"
+"delay" "6"
+"target" "t96"
+}
+{
+"spawnflags" "2048"
+"delay" "22"
+"origin" "368 -200 1880"
+"classname" "trigger_relay"
+"targetname" "t101"
+"target" "t102"
+}
+{
+"spawnflags" "2048"
+"delay" "24"
+"origin" "408 -200 1880"
+"classname" "trigger_relay"
+"targetname" "t103"
+"target" "t104"
+}
+{
+"model" "*140"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t92"
+"target" "t103"
+}
+{
+"model" "*141"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t92"
+"target" "t101"
+}
+{
+"model" "*142"
+"spawnflags" "2048"
+"classname" "func_train"
+"targetname" "t98"
+"target" "t59"
+"_minlight" ".2"
+}
+{
+"model" "*143"
+"spawnflags" "2048"
+"classname" "func_train"
+"target" "t80"
+"targetname" "t102"
+"_minlight" ".2"
+}
+{
+"model" "*144"
+"classname" "func_train"
+"target" "t91"
+"spawnflags" "2048"
+"targetname" "t96"
+"_minlight" ".2"
+}
+{
+"model" "*145"
+"classname" "func_train"
+"target" "t80"
+"spawnflags" "2048"
+"targetname" "t95"
+"_minlight" ".2"
+}
+{
+"model" "*146"
+"spawnflags" "2048"
+"classname" "func_train"
+"target" "t91"
+"targetname" "t104"
+"_minlight" ".2"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t62"
+"wait" "-1"
+"target" "t63"
+"origin" "544 -512 1746"
+"pathtarget" "red1"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t61"
+"target" "t62"
+"origin" "544 -360 1746"
+}
+{
+"spawnflags" "2048"
+"origin" "672 -360 1746"
+"classname" "path_corner"
+"targetname" "t73"
+"target" "t74"
+"pathtarget" "red2"
+}
+{
+"spawnflags" "2048"
+"origin" "672 -360 1746"
+"classname" "path_corner"
+"targetname" "t71"
+"target" "t72"
+}
+{
+"spawnflags" "2048"
+"origin" "672 -512 1746"
+"wait" "4"
+"classname" "path_corner"
+"targetname" "t72"
+"target" "t73"
+"pathtarget" "red2"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "800 -360 1746"
+"targetname" "t84"
+"target" "t85"
+"pathtarget" "red3"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "800 -360 1746"
+"targetname" "t82"
+"target" "t83"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"wait" "4"
+"origin" "800 -512 1746"
+"targetname" "t83"
+"target" "t84"
+"pathtarget" "red3"
+}
+{
+"spawnflags" "2048"
+"origin" "1024 -360 1746"
+"classname" "path_corner"
+"targetname" "t74"
+"target" "t75"
+}
+{
+"spawnflags" "2048"
+"origin" "1024 -512 1746"
+"classname" "path_corner"
+"targetname" "t75"
+"target" "t76"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "1024 -512 1746"
+"targetname" "t86"
+"target" "t87"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "1024 -360 1746"
+"targetname" "t85"
+"target" "t86"
+}
+{
+"spawnflags" "2048"
+"origin" "1200 -512 1746"
+"classname" "path_corner"
+"targetname" "t76"
+"target" "t77"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "1200 -512 1746"
+"targetname" "t87"
+"target" "t88"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "1200 -512 1632"
+"targetname" "t77"
+"target" "t78"
+}
+{
+"spawnflags" "2048"
+"origin" "1200 -512 1632"
+"classname" "path_corner"
+"targetname" "t88"
+"target" "t89"
+} \ No newline at end of file
diff --git a/rogue/stuff/mapfixes/rbase1.ent b/rogue/stuff/mapfixes/rbase1.ent
new file mode 100644
index 0000000..ccc24ef
--- /dev/null
+++ b/rogue/stuff/mapfixes/rbase1.ent
@@ -0,0 +1,5423 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Fixed "WARNING: Entity used itself."
+// appearing during gameplay (b#1)
+//
+// A trigger_once that targets some monsters
+// also targets itself for some reason.
+//
+// 2. Fixed old classnames (b#2)
+//
+// 3. Fixed wrong/overlapping secret sound effects (b#3)
+//
+// 4. Added missing targetname to target_explosion (b#4)
+{
+"sounds" "10"
+"message" "Logistics Complex"
+"nextmap" "rbase2"
+"sky" "rogue1"
+"classname" "worldspawn"
+}
+{
+"classname" "misc_teleporter_dest"
+"spawnflags" "5888"
+"angle" "180"
+"targetname" "t221"
+"origin" "-1120 1216 216"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "5888"
+"target" "t221"
+"origin" "2560 128 -296"
+}
+{
+"classname" "misc_teleporter_dest"
+"spawnflags" "5888"
+"angle" "270"
+"targetname" "t220"
+"origin" "-2144 2336 -232"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "5888"
+"target" "t220"
+"origin" "1984 1792 24"
+}
+{
+"model" "*1"
+"message" "Doors locked."
+"wait" "4"
+"targetname" "t218"
+"spawnflags" "2052"
+"classname" "trigger_multiple"
+}
+{
+"model" "*2"
+"wait" "4"
+"target" "t219"
+"targetname" "t218"
+"spawnflags" "2052"
+"classname" "trigger_multiple"
+}
+{
+"killtarget" "4idiots"
+"target" "t218"
+"origin" "2184 128 -208"
+"targetname" "t39"
+"spawnflags" "2048"
+"classname" "trigger_relay"
+}
+{
+"classname" "item_sphere_vengeance"
+"spawnflags" "5888"
+"origin" "-416 -32 -496"
+}
+{
+"classname" "ammo_cells"
+"origin" "176 64 -496"
+}
+{
+"classname" "ammo_cells"
+"origin" "-264 -32 -496"
+}
+{
+"classname" "weapon_plasmabeam"
+"spawnflags" "5888"
+"origin" "0 128 -496"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_shells"
+"origin" "2272 -40 -304"
+}
+{
+"classname" "ammo_shells"
+"spawnflags" "5888"
+"origin" "2280 360 -304"
+}
+{
+"classname" "weapon_supershotgun"
+"spawnflags" "5888"
+"origin" "2272 128 -304"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "225"
+"origin" "1504 416 -296"
+}
+{
+"classname" "weapon_etf_rifle"
+"spawnflags" "5888"
+"origin" "1392 336 -296"
+}
+{
+"classname" "ammo_flechettes"
+"spawnflags" "5888"
+"origin" "1360 400 -296"
+}
+{
+"classname" "ammo_shells"
+"spawnflags" "5888"
+"origin" "-1440 2216 -240"
+}
+{
+"classname" "weapon_supershotgun"
+"spawnflags" "5888"
+"origin" "-1504 2200 -240"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "1504 -224 -296"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_rockets"
+"origin" "1592 -96 -496"
+}
+{
+"classname" "ammo_rockets"
+"spawnflags" "5888"
+"origin" "1216 -80 -496"
+}
+{
+"classname" "weapon_rocketlauncher"
+"origin" "1408 -72 -496"
+"spawnflags" "5888"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_prox"
+"origin" "-224 448 -304"
+}
+{
+"classname" "ammo_prox"
+"spawnflags" "5888"
+"origin" "160 536 -304"
+}
+{
+"classname" "weapon_proxlauncher"
+"spawnflags" "5888"
+"origin" "0 376 -304"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "0 -168 -488"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "45"
+"spawnflags" "5888"
+"origin" "-1248 456 -232"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "5888"
+"target" "t217"
+"origin" "928 72 -232"
+}
+{
+"classname" "misc_teleporter_dest"
+"spawnflags" "5888"
+"origin" "-672 760 24"
+"targetname" "t217"
+"angle" "90"
+}
+{
+"classname" "item_double"
+"spawnflags" "5888"
+"origin" "-672 840 24"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_cells"
+"origin" "-664 1576 -240"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "5888"
+"origin" "-832 912 -240"
+}
+{
+"classname" "weapon_hyperblaster"
+"spawnflags" "5888"
+"origin" "-680 1256 -240"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "1096 1464 -8"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "135"
+"origin" "1568 1672 24"
+}
+{
+"angle" "315"
+"classname" "info_player_deathmatch"
+"origin" "-840 1696 -232"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "-384 888 -232"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "0"
+"origin" "-1696 1368 -232"
+}
+{
+"classname" "item_sphere_defender"
+"spawnflags" "5888"
+"origin" "-1760 1312 16"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "0"
+"origin" "-1760 1472 24"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "-1024 1216 24"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_cells"
+"origin" "-1048 1320 208"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "5888"
+"origin" "-992 1880 112"
+}
+{
+"classname" "weapon_plasmabeam"
+"spawnflags" "5888"
+"origin" "-1208 1192 208"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "-1856 1696 24"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "0"
+"origin" "-2432 2080 -232"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "-1696 1688 -232"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "-1184 2048 -40"
+}
+{
+"classname" "weapon_rocketlauncher"
+"spawnflags" "5888"
+"origin" "-560 1696 16"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_rockets"
+"origin" "-680 1680 16"
+}
+{
+"classname" "weapon_chaingun"
+"spawnflags" "5888"
+"origin" "1312 1824 16"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_bullets"
+"origin" "1248 1768 16"
+}
+{
+"classname" "ammo_bullets"
+"spawnflags" "5888"
+"origin" "1632 2048 16"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "1632 2208 24"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "1848 -120 -296"
+}
+{
+"classname" "ammo_nuke"
+"spawnflags" "5888"
+"origin" "1024 376 -504"
+}
+{
+"model" "*3"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*4"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"target" "t216"
+"targetname" "t215"
+"origin" "-1472 1216 208"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t215"
+"targetname" "t214"
+"origin" "-1472 1560 208"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t214"
+"targetname" "t213"
+"origin" "-1472 1856 208"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t213"
+"targetname" "t212"
+"origin" "-1256 1856 208"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t212"
+"targetname" "t211"
+"origin" "-1024 1856 112"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t211"
+"targetname" "t210"
+"origin" "-1024 1712 64"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t210"
+"targetname" "t209"
+"origin" "-1056 1472 16"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t209"
+"targetname" "t208"
+"origin" "-1296 1472 16"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t208"
+"targetname" "t207"
+"origin" "-1472 1472 16"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t207"
+"origin" "-1616 1480 16"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t205"
+"targetname" "t204"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1856 1792 16"
+}
+{
+"target" "t206"
+"targetname" "t205"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1664 1792 16"
+}
+{
+"targetname" "t206"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-1664 1544 16"
+}
+{
+"target" "t204"
+"origin" "-2304 1792 16"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"targetname" "t203"
+"origin" "-1664 1408 16"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t203"
+"targetname" "t202"
+"origin" "-1664 1216 -48"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"targetname" "t216"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-1216 1216 208"
+}
+{
+"target" "t201"
+"targetname" "t200"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1944 1208 -128"
+}
+{
+"target" "t200"
+"targetname" "t199"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-2056 1344 -176"
+}
+{
+"target" "t199"
+"targetname" "t198"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1944 1472 -224"
+}
+{
+"target" "t198"
+"targetname" "t197"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1664 1472 -240"
+}
+{
+"target" "t197"
+"targetname" "t196"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1472 1408 -240"
+}
+{
+"target" "t196"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-1192 1408 -240"
+}
+{
+"targetname" "t195"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-1152 1472 -240"
+}
+{
+"target" "t195"
+"targetname" "t194"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1152 1792 -240"
+}
+{
+"target" "t194"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-1408 1792 -240"
+}
+{
+"target" "t193"
+"targetname" "t192"
+"origin" "-584 2056 16"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t192"
+"targetname" "t191"
+"origin" "-960 2048 16"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t191"
+"targetname" "t190"
+"origin" "-960 2176 16"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t190"
+"targetname" "t189"
+"origin" "-1256 2144 -48"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t189"
+"targetname" "t188"
+"origin" "-1472 2144 -240"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t188"
+"origin" "-1472 1832 -240"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"targetname" "t193"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-576 1728 16"
+}
+{
+"targetname" "t187"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-1536 1792 -240"
+}
+{
+"target" "t187"
+"targetname" "t186"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1856 1792 -240"
+}
+{
+"target" "t186"
+"targetname" "t185"
+"origin" "-2224 1792 -240"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t185"
+"origin" "-2144 2088 -240"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t202"
+"targetname" "t201"
+"origin" "-1792 1216 -48"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "1536 -448 -456"
+"target" "t184"
+"targetname" "t183"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "1408 -528 -432"
+"target" "t183"
+"targetname" "t182"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "1304 -456 -400"
+"target" "t182"
+"targetname" "t181"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "1280 -184 -304"
+"target" "t181"
+"targetname" "t180"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "1536 -200 -496"
+"targetname" "t184"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1408 -40 -304"
+"targetname" "t179"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1408 256 -304"
+"target" "t179"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1408 -88 -304"
+"target" "t180"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "1088 -64 -304"
+"targetname" "t178"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1368 -64 -304"
+"target" "t178"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t174"
+"targetname" "t173"
+"origin" "1856 512 -304"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t175"
+"targetname" "t174"
+"origin" "1696 640 -304"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t176"
+"targetname" "t175"
+"origin" "1216 640 -304"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t177"
+"targetname" "t176"
+"origin" "960 576 -304"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"targetname" "t177"
+"origin" "896 256 -304"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t173"
+"origin" "1856 256 -304"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "384 408 -304"
+"target" "t171"
+"targetname" "t170"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "400 104 -304"
+"target" "t172"
+"targetname" "t171"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "624 96 -304"
+"targetname" "t172"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-48 424 -304"
+"target" "t170"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t169"
+"targetname" "t168"
+"origin" "688 568 -424"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t168"
+"targetname" "t167"
+"origin" "248 560 -496"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t167"
+"origin" "32 560 -496"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"targetname" "t166"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "0 512 -496"
+}
+{
+"targetname" "t169"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "696 192 -304"
+}
+{
+"targetname" "t165"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "-24 560 -496"
+}
+{
+"target" "t165"
+"targetname" "t164"
+"origin" "-200 560 -496"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t164"
+"origin" "-416 512 -496"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "-488 152 -496"
+"target" "t163"
+"targetname" "t162"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-416 416 -496"
+"targetname" "t163"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t166"
+"origin" "0 216 -496"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-800 512 -240"
+"targetname" "t161"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-1216 512 -240"
+"target" "t161"
+"targetname" "t160"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1152 832 -240"
+"target" "t160"
+"targetname" "t159"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1152 1184 -240"
+"target" "t159"
+"targetname" "t158"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "-1152 1352 -240"
+"target" "t158"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "-424 -32 -496"
+"target" "t162"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"classname" "info_player_intermission"
+"angles" "10 135 0"
+"origin" "-128 1000 88"
+"spawnflags" "0"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "6945"
+"angle" "0"
+"origin" "-1720 1792 -96"
+}
+{
+"angle" "0"
+"spawnflags" "7177"
+"classname" "monster_turret"
+"origin" "520 576 -344"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "2016 1792 160"
+}
+{
+"origin" "-672 1728 24"
+"message" "Return and collect\n data disk."
+"targetname" "t12"
+"spawnflags" "2048"
+"classname" "target_help"
+}
+{
+"origin" "-1432 1664 280"
+"angle" "0"
+"spawnflags" "3841"
+"classname" "monster_daedalus"
+}
+{
+"origin" "-1624 1176 164"
+"targetname" "t126"
+"angle" "-2"
+"spawnflags" "4001"
+"classname" "monster_turret"
+}
+{
+"origin" "-1968 1464 -40"
+"angle" "180"
+"target" "t126"
+"item" "ammo_cells"
+"spawnflags" "6913"
+"classname" "monster_daedalus"
+}
+{
+"angle" "-2"
+"origin" "-1440 1376 -72"
+"spawnflags" "2057"
+"classname" "monster_turret"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "6913"
+"origin" "-1304 2144 -224"
+}
+{
+"targetname" "t118"
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "7073"
+"origin" "-1568 2080 100"
+}
+{
+"origin" "-1928 1832 -224"
+"targetname" "t113"
+"target" "t115"
+"angle" "180"
+"spawnflags" "3841"
+"classname" "monster_gunner"
+}
+{
+"origin" "-2048 1760 -80"
+"spawnflags" "3841"
+"angle" "90"
+"classname" "monster_daedalus"
+}
+{
+"origin" "1488 400 -288"
+"angle" "270"
+"spawnflags" "3841"
+"target" "t141"
+"item" "ammo_slugs"
+"classname" "monster_gladiator"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "6945"
+"origin" "1408 32 -136"
+"angle" "-2"
+}
+{
+"item" "ammo_grenades"
+"origin" "1216 -80 -480"
+"angle" "45"
+"classname" "monster_gunner"
+"spawnflags" "6913"
+}
+{
+"classname" "monster_turret"
+"angle" "270"
+"spawnflags" "6945"
+"origin" "1408 128 -424"
+}
+{
+"item" "ammo_grenades"
+"origin" "472 344 -288"
+"spawnflags" "6913"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"origin" "872 648 -80"
+"angle" "270"
+"spawnflags" "3841"
+"classname" "monster_daedalus"
+}
+{
+"origin" "872 648 -80"
+"angle" "270"
+"spawnflags" "6913"
+"classname" "monster_hover"
+}
+{
+"angle" "90"
+"target" "t65"
+"spawnflags" "3841"
+"origin" "656 -136 -288"
+"classname" "monster_gladiator"
+}
+{
+"origin" "712 616 -424"
+"classname" "item_health"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "3873"
+"angle" "0"
+"origin" "520 576 -344"
+}
+{
+"origin" "-384 -32 -480"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"origin" "-344 40 -480"
+"classname" "ammo_cells"
+}
+{
+"origin" "-552 416 -480"
+"item" "ammo_shells"
+"spawnflags" "6401"
+"angle" "45"
+"classname" "monster_soldier"
+}
+{
+"origin" "-928 608 -224"
+"angle" "270"
+"spawnflags" "6913"
+"classname" "monster_gunner"
+}
+{
+"targetname" "t61"
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "4001"
+"origin" "-1152 608 -28"
+}
+{
+"origin" "-1056 1056 -28"
+"targetname" "t60"
+"angle" "-2"
+"spawnflags" "3985"
+"classname" "monster_turret"
+}
+{
+"targetname" "t157"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-560 976 -240"
+}
+{
+"target" "t157"
+"targetname" "t156"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-568 1264 -280"
+}
+{
+"target" "t156"
+"targetname" "t155"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-280 1224 -304"
+}
+{
+"target" "t155"
+"targetname" "t154"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "112 968 -264"
+}
+{
+"target" "t154"
+"targetname" "t153"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "136 1144 -232"
+}
+{
+"target" "t153"
+"targetname" "t152"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-40 1280 -184"
+}
+{
+"target" "t152"
+"targetname" "t151"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-104 1544 -104"
+}
+{
+"target" "t151"
+"targetname" "t150"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "104 1752 -64"
+}
+{
+"target" "t147"
+"targetname" "t146"
+"spawnflags" "2048"
+"classname" "hint_path"
+"origin" "872 1304 -8"
+}
+{
+"target" "t148"
+"targetname" "t147"
+"spawnflags" "2048"
+"classname" "hint_path"
+"origin" "648 1440 -8"
+}
+{
+"target" "t150"
+"targetname" "t149"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "456 1864 -64"
+}
+{
+"target" "t149"
+"targetname" "t148"
+"origin" "656 1760 -24"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t146"
+"targetname" "t145"
+"origin" "1040 1472 -8"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1104 1736 -24"
+"target" "t145"
+"targetname" "t144"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1264 2032 -48"
+"target" "t144"
+"targetname" "t143"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1472 1856 16"
+"target" "t143"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "1536 2232 328"
+"item" "ammo_cells"
+"angle" "270"
+"spawnflags" "3841"
+"classname" "monster_daedalus"
+}
+{
+"classname" "monster_daedalus"
+"spawnflags" "6913"
+"angle" "270"
+"item" "ammo_cells"
+"origin" "1496 2232 328"
+}
+{
+"origin" "-2048 2208 -204"
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "-2240 2208 -204"
+"_color" "1.000000 0.000000 0.000000"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "2424 228 -268"
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "2424 28 -268"
+"_color" "1.000000 0.000000 0.000000"
+"light" "50"
+"classname" "light"
+}
+{
+"classname" "target_goal"
+"spawnflags" "2048"
+"targetname" "t142"
+"origin" "-1216 1640 104"
+}
+{
+"spawnflags" "2048"
+"classname" "target_goal"
+"targetname" "t140"
+"origin" "0 184 -368"
+}
+{
+"classname" "item_armor_body"
+"origin" "-1280 1144 24"
+}
+{
+"classname" "monster_soldier_ss"
+"spawnflags" "2049"
+"item" "ammo_bullets"
+"angle" "180"
+"origin" "1640 168 -288"
+}
+{
+"classname" "monster_soldier"
+"origin" "1480 392 -296"
+"spawnflags" "7681"
+"item" "ammo_shells"
+"target" "t141"
+"angle" "270"
+}
+{
+"classname" "monster_gunner"
+"origin" "1496 408 -296"
+"spawnflags" "7425"
+"item" "ammo_grenades"
+"target" "t141"
+"angle" "270"
+}
+{
+"classname" "monster_gladiator"
+"angle" "270"
+"spawnflags" "6913"
+"origin" "1488 400 -288"
+"item" "ammo_slugs"
+"target" "t141"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"origin" "1408 96 -304"
+"targetname" "t141"
+}
+{
+"spawnflags" "2337"
+"angle" "-2"
+"classname" "monster_turret"
+"origin" "1640 224 -136"
+}
+{
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "3593"
+"origin" "1640 224 -136"
+}
+{
+"classname" "light"
+"light" "60"
+"origin" "-1472 1904 -88"
+}
+{
+"classname" "target_help"
+"targetname" "t140"
+"spawnflags" "2049"
+"message" "Mission accomplished.\nReturn to Waterfront Storage"
+"origin" "-80 144 -400"
+}
+{
+"spawnflags" "2048"
+"classname" "target_help"
+"targetname" "t140"
+"message" "Use air vent to\n gain access to\n bridge controls."
+"origin" "80 144 -400"
+}
+{
+"classname" "key_commander_head"
+"origin" "0 144 -488"
+"targetname" "t99"
+"spawnflags" "2049"
+"target" "t140"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"angle" "90"
+"targetname" "t139"
+"origin" "0 32 -496"
+}
+{
+"model" "*5"
+"classname" "trigger_multiple"
+"wait" "4"
+"spawnflags" "2048"
+"message" "Security measures are offline."
+"targetname" "cehss"
+}
+{
+"model" "*6"
+"classname" "trigger_once"
+"target" "t138"
+"spawnflags" "2052"
+"targetname" "t35"
+}
+{
+"classname" "light"
+"light" "50"
+"origin" "1728 1680 80"
+}
+{
+"origin" "-1152 768 -56"
+"spawnflags" "2049"
+"noise" "world/fan1.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-1200 768 -40"
+"spawnflags" "2049"
+"noise" "world/fan1.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-1096 768 -40"
+"spawnflags" "2049"
+"noise" "world/fan1.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-2376 1792 -184"
+"target" "t137"
+"delay" "1"
+"targetname" "t4"
+"classname" "trigger_relay"
+}
+{
+"origin" "-1720 1792 -96"
+"angle" "0"
+"spawnflags" "3873"
+"classname" "monster_turret"
+}
+{
+"model" "*7"
+"classname" "func_explosive"
+"spawnflags" "2048"
+"targetname" "ex4"
+"health" "5"
+}
+{
+"model" "*8"
+"classname" "func_explosive"
+"spawnflags" "2048"
+"health" "5"
+"target" "t135"
+"targetname" "ex4"
+}
+{
+"origin" "520 576 -344"
+"classname" "monster_turret"
+"spawnflags" "6945"
+"angle" "0"
+}
+{
+"model" "*9"
+"target" "t39"
+"message" "Bridge enabled."
+"wait" "4"
+"angle" "0"
+"spawnflags" "5888"
+"classname" "func_button"
+"lip" "6"
+}
+{
+"origin" "1936 560 -296"
+"classname" "ammo_shells"
+}
+{
+"origin" "1368 544 -296"
+"classname" "ammo_slugs"
+}
+{
+"origin" "1296 536 -296"
+"classname" "item_health"
+}
+{
+"origin" "1512 536 -296"
+"classname" "item_health"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1152 608 -192"
+}
+{
+"light" "50"
+"classname" "light"
+"origin" "1632 608 -160"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1664 608 -192"
+}
+{
+"origin" "1408 128 -424"
+"classname" "monster_turret"
+"angle" "270"
+"spawnflags" "7177"
+}
+{
+"origin" "1408 128 -424"
+"spawnflags" "3873"
+"angle" "270"
+"classname" "monster_turret"
+}
+{
+"origin" "-776 512 -176"
+"target" "t136"
+"targetname" "t97"
+"delay" "1"
+"classname" "trigger_relay"
+}
+{
+"origin" "-856 512 -96"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "-992 552 -96"
+"message" "Dentention block located."
+"targetname" "t132"
+"spawnflags" "2049"
+"classname" "target_help"
+}
+{
+"model" "*10"
+"spawnflags" "2048"
+"targetname" "ex4"
+"classname" "func_explosive"
+"health" "5"
+}
+{
+"origin" "24 1792 -56"
+"classname" "item_health_large"
+}
+{
+"origin" "888 1264 -24"
+"classname" "item_health"
+}
+{
+"model" "*11"
+"targetname" "t57"
+"target" "t54"
+"spawnflags" "2048"
+"speed" "650"
+"classname" "func_train"
+}
+{
+"classname" "target_crosslevel_trigger"
+"targetname" "t110"
+"spawnflags" "2176"
+"origin" "-1424 1376 88"
+}
+{
+"targetname" "rb1b"
+"origin" "-2168 2440 -224"
+"classname" "info_player_coop"
+"angle" "270"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1b"
+"origin" "-2120 2488 -224"
+"classname" "info_player_coop"
+"angle" "270"
+"spawnflags" "3072"
+}
+{
+"targetname" "rb1b"
+"origin" "-2168 2504 -224"
+"classname" "info_player_coop"
+"angle" "270"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1b"
+"origin" "-2120 2408 -224"
+"angle" "270"
+"classname" "info_player_coop"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-2144 2368 -192"
+}
+{
+"targetname" "rb1a"
+"origin" "2696 160 -288"
+"classname" "info_player_coop"
+"angle" "180"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1a"
+"origin" "2680 96 -288"
+"classname" "info_player_coop"
+"angle" "180"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1a"
+"origin" "2632 160 -288"
+"angle" "180"
+"classname" "info_player_coop"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1a"
+"origin" "2608 96 -288"
+"angle" "180"
+"classname" "info_player_coop"
+"spawnflags" "2048"
+}
+{
+"origin" "2560 128 -256"
+"light" "125"
+"classname" "light"
+}
+{
+"targetname" "rb1"
+"angle" "180"
+"classname" "info_player_coop"
+"origin" "1992 1760 32"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1"
+"angle" "180"
+"classname" "info_player_coop"
+"origin" "2048 1728 32"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1"
+"angle" "180"
+"classname" "info_player_coop"
+"origin" "2048 1856 32"
+"spawnflags" "2048"
+}
+{
+"targetname" "rb1"
+"classname" "info_player_coop"
+"angle" "180"
+"origin" "1992 1824 32"
+"spawnflags" "2048"
+}
+{
+"origin" "-2112 2568 -136"
+"map" "rbase2$rb2a"
+"targetname" "t134"
+"classname" "target_changelevel"
+"spawnflags" "2048"
+}
+{
+"model" "*12"
+"angle" "90"
+"target" "t134"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+}
+{
+"origin" "-1408 1448 72"
+"delay" "2.3"
+"spawnflags" "2048"
+"target" "t133"
+"targetname" "t110"
+"classname" "trigger_relay"
+}
+{
+"origin" "-1408 1488 72"
+"spawnflags" "2048"
+"target" "t133"
+"targetname" "t110"
+"classname" "trigger_relay"
+}
+{
+"origin" "-1200 1688 72"
+"targetname" "t133"
+"noise" "world/dataspin.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "315"
+"spawnflags" "2049"
+"origin" "-552 288 -480"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "270"
+"spawnflags" "7169"
+"item" "ammo_bullets"
+"origin" "-936 616 -224"
+}
+{
+"classname" "monster_gunner"
+"spawnflags" "3841"
+"angle" "270"
+"origin" "-936 616 -224"
+}
+{
+"classname" "item_health_large"
+"origin" "2408 16 -296"
+}
+{
+"classname" "item_health_large"
+"origin" "2408 240 -296"
+}
+{
+"origin" "1640 168 -216"
+"message" "New orders will\n be forthcoming."
+"spawnflags" "2048"
+"classname" "target_help"
+"targetname" "t39"
+}
+{
+"classname" "target_help"
+"targetname" "t132"
+"spawnflags" "2048"
+"message" "Eliminate Tank Commander\n and retrieve its head."
+"origin" "-992 472 -96"
+}
+{
+"model" "*13"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t132"
+}
+{
+"targetname" "t135"
+"message" "Incoming marine D.O.A.\n Carry out his orders."
+"classname" "target_help"
+"spawnflags" "2049"
+"origin" "-792 1184 -224"
+}
+{
+"targetname" "t135"
+"classname" "target_help"
+"spawnflags" "2048"
+"origin" "-800 1328 -224"
+"message" "Access Logistics complex.\nLocate Dentention Block."
+}
+{
+"light" "50"
+"classname" "light"
+"origin" "1896 -128 -704"
+}
+{
+"light" "50"
+"classname" "light"
+"origin" "2048 -128 -704"
+}
+{
+"classname" "light"
+"light" "50"
+"origin" "1736 -128 -704"
+}
+{
+"model" "*14"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "1240 -168 -280"
+"light" "70"
+"classname" "light"
+}
+{
+"model" "*15"
+"targetname" "t90"
+"target" "t89"
+"spawnflags" "2052"
+"classname" "trigger_multiple"
+}
+{
+"origin" "-984 1896 120"
+"classname" "ammo_prox"
+}
+{
+"origin" "-984 1576 24"
+"classname" "item_health"
+}
+{
+"origin" "-984 1408 24"
+"classname" "item_health"
+}
+{
+"origin" "-1376 1632 24"
+"item" "ammo_bullets"
+"spawnflags" "2049"
+"angle" "0"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "-1488 1872 224"
+"targetname" "t131"
+"spawnflags" "2049"
+"angle" "315"
+"classname" "monster_gladiator"
+"item" "ammo_slugs"
+}
+{
+"origin" "-1016 1208 24"
+"classname" "monster_soldier"
+"spawnflags" "3585"
+"angle" "90"
+}
+{
+"origin" "-1056 1224 24"
+"spawnflags" "2305"
+"angle" "90"
+"classname" "monster_gunner"
+}
+{
+"origin" "-1280 1180 32"
+"targetname" "t130"
+"spawnflags" "2048"
+"classname" "target_explosion"
+}
+{
+"model" "*16"
+"targetname" "t13"
+"target" "t130"
+"spawnflags" "2048"
+"classname" "func_explosive"
+}
+{
+"origin" "-1432 1664 280"
+"angle" "0"
+"spawnflags" "7425"
+"classname" "monster_hover"
+}
+{
+"target" "t131"
+"origin" "-1440 1472 280"
+"classname" "monster_daedalus"
+"angle" "0"
+"spawnflags" "2049"
+}
+{
+"origin" "-1440 1664 280"
+"spawnflags" "6913"
+"angle" "0"
+"classname" "monster_daedalus"
+}
+{
+"angle" "270"
+"origin" "-1472 1336 352"
+"spawnflags" "2057"
+"classname" "monster_turret"
+}
+{
+"origin" "-1512 1120 216"
+"classname" "ammo_tesla"
+}
+{
+"origin" "-1432 1112 216"
+"classname" "item_health_large"
+}
+{
+"origin" "-1056 1216 224"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_medic_commander"
+"item" "ammo_cells"
+}
+{
+"origin" "-1624 1248 -240"
+"classname" "ammo_slugs"
+}
+{
+"origin" "-1632 1512 -240"
+"classname" "ammo_flechettes" // b#2: nails -> flechettes
+}
+{
+"origin" "-1376 1512 -232"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-1696 1528 -192"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum2.wav"
+}
+{
+"origin" "-1568 1528 -192"
+"noise" "world/comp_hum2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1440 1528 -192"
+"noise" "world/comp_hum2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1472 1296 -192"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum2.wav"
+}
+{
+"origin" "-1664 1296 -192"
+"noise" "world/comp_hum2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1432 1248 -232"
+"classname" "item_health_large"
+}
+{
+"origin" "-1512 1248 -232"
+"classname" "item_health_large"
+}
+{
+"origin" "-1696 1280 -224"
+"item" "ammo_cells"
+"spawnflags" "2049"
+"angle" "45"
+"classname" "monster_medic_commander"
+}
+{
+"origin" "-1600 1648 16"
+"targetname" "t128"
+"angle" "180"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "-1688 1896 32"
+"item" "ammo_grenades"
+"targetname" "t124"
+"target" "t128"
+"spawnflags" "2049"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1824 1888 24"
+}
+{
+"origin" "-1624 1176 -40"
+"classname" "item_health_large"
+}
+{
+"light" "130"
+"classname" "light"
+"origin" "-1664 1440 96"
+}
+{
+"origin" "-1760 1440 32"
+"item" "ammo_bullets"
+"spawnflags" "2049"
+"angle" "0"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "-1760 1504 32"
+"item" "ammo_shells"
+"angle" "0"
+"spawnflags" "2049"
+"classname" "monster_soldier"
+}
+{
+"model" "*17"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*18"
+"spawnflags" "2048"
+"targetname" "t126"
+"target" "t127"
+"classname" "func_explosive"
+}
+{
+"origin" "-1624 1176 132"
+"targetname" "t127"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"origin" "-1624 1176 164"
+"targetname" "t126"
+"classname" "monster_turret"
+"spawnflags" "7305"
+"angle" "-2"
+}
+{
+"origin" "-1624 1176 164"
+"targetname" "t126"
+"spawnflags" "7073"
+"classname" "monster_turret"
+"angle" "-2"
+}
+{
+"origin" "-1968 1472 -40"
+"target" "t126"
+"angle" "180"
+"spawnflags" "7169"
+"classname" "monster_hover"
+"item" "ammo_cells"
+}
+{
+"origin" "-1968 1456 -40"
+"target" "t126"
+"angle" "180"
+"spawnflags" "3841"
+"classname" "monster_daedalus"
+"item" "ammo_cells"
+}
+{
+"origin" "-1768 1320 32"
+"target" "t126"
+"angle" "315"
+"spawnflags" "2049"
+"item" "ammo_shells"
+"classname" "monster_soldier"
+}
+{
+"origin" "-1896 1800 8"
+"targetname" "t125"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "-1888 1696 32"
+"item" "ammo_bullets"
+"targetname" "t124"
+"spawnflags" "2049"
+"target" "t125"
+"classname" "monster_gunner"
+}
+{
+"origin" "-1568 1760 24"
+"classname" "ammo_prox"
+}
+{
+"origin" "-1632 1888 24"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1888 1888 24"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1568 1824 24"
+"classname" "item_armor_shard"
+}
+{
+"model" "*19"
+//"targetname" "t124" // b#1: self-target
+"target" "t124"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "-2008 1688 16"
+"classname" "item_health_large"
+}
+{
+"model" "*20"
+"target" "t122"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "-616 2064 16"
+"targetname" "t121"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "-576 1936 32"
+"angle" "90"
+"targetname" "t122"
+"target" "t121"
+"spawnflags" "7681"
+"classname" "monster_gunner"
+}
+{
+"origin" "-576 1936 32"
+"targetname" "t122"
+"target" "t121"
+"spawnflags" "2305"
+"angle" "90"
+"classname" "monster_gladiator"
+}
+{
+"item" "ammo_grenades"
+"origin" "-1304 2144 -224"
+"spawnflags" "3841"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"item" "ammo_bullets"
+"origin" "-1304 2144 -224"
+"angle" "180"
+"spawnflags" "7169"
+"classname" "monster_soldier_ss"
+}
+{
+"item" "ammo_shells"
+"origin" "-1184 2040 -32"
+"angle" "135"
+"spawnflags" "2049"
+"classname" "monster_soldier"
+}
+{
+"origin" "-1008 2176 16"
+"targetname" "t120"
+"classname" "point_combat"
+"spawnflags" "2048"
+}
+{
+"origin" "-960 2048 32"
+"target" "t120"
+"spawnflags" "2049"
+"angle" "90"
+"targetname" "t118"
+"classname" "monster_gunner"
+}
+{
+"origin" "-1568 2080 68"
+"spawnflags" "2048"
+"targetname" "t119"
+"classname" "target_explosion"
+}
+{
+"model" "*21"
+"target" "t119"
+"targetname" "t118"
+"spawnflags" "2048"
+"classname" "func_explosive"
+}
+{
+"model" "*22"
+"targetname" "t118"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"targetname" "t118"
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "7305"
+"origin" "-1568 2080 100"
+}
+{
+"targetname" "t118"
+"origin" "-1568 2080 100"
+"spawnflags" "4001"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"target" "t118"
+"origin" "-1128 2176 24"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_daedalus"
+}
+{
+"origin" "-1304 2088 -168"
+"classname" "item_health"
+}
+{
+"origin" "-1640 2008 -240"
+"classname" "ammo_slugs"
+}
+{
+"origin" "-1624 2208 -240"
+"classname" "item_health_small"
+}
+{
+"origin" "-1616 2104 -240"
+"classname" "item_health_small"
+}
+{
+"origin" "-1640 2160 -240"
+"classname" "item_health_small"
+}
+{
+"origin" "-1120 1864 -240"
+"classname" "ammo_tesla"
+}
+{
+"targetname" "t116"
+"spawnflags" "2049"
+"origin" "-1264 1736 -240"
+"classname" "point_combat"
+}
+{
+"targetname" "t117"
+"target" "t116"
+"origin" "-1184 1888 -224"
+"spawnflags" "2049"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"origin" "-1056 1704 -240"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-1584 1704 -168"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1608 1896 -168"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1464 1696 -168"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1384 1704 -168"
+"classname" "item_armor_jacket"
+}
+{
+"origin" "-1816 1728 -232"
+"classname" "item_health_large"
+}
+{
+"origin" "-1368 1888 -232"
+"classname" "item_health_large"
+}
+{
+"origin" "-1952 1832 -224"
+"angle" "180"
+"spawnflags" "7425"
+"targetname" "t113"
+"target" "t115"
+"classname" "monster_soldier_ss"
+}
+{
+"target" "t117"
+"classname" "monster_soldier"
+"item" "ammo_shells"
+"spawnflags" "2049"
+"origin" "-1704 1688 -232"
+"angle" "90"
+}
+{
+"angle" "270"
+"origin" "-1896 1896 -232"
+"spawnflags" "2049"
+"item" "ammo_shells"
+"classname" "monster_soldier"
+}
+{
+"classname" "monster_soldier"
+"spawnflags" "3585"
+"angle" "90"
+"origin" "1888 -144 -288"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "0"
+"spawnflags" "2049"
+"targetname" "t4"
+"origin" "-2456 1792 32"
+}
+{
+"classname" "monster_daedalus"
+"angle" "90"
+"spawnflags" "6913"
+"origin" "-2048 1760 -80"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2048"
+"targetname" "t115"
+"origin" "-2056 1824 -240"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "6913"
+"targetname" "t113"
+"target" "t115"
+"origin" "-1944 1832 -224"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2048"
+"targetname" "t112"
+"origin" "-2240 1840 16"
+}
+{
+"classname" "monster_medic_commander"
+"spawnflags" "2049"
+"angle" "0"
+"target" "t112"
+"targetname" "t113"
+"item" "ammo_cells"
+"origin" "-2336 1824 32"
+}
+{
+"model" "*23"
+"classname" "trigger_once"
+"target" "t113"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t12"
+"target" "t111"
+"delay" "10"
+"origin" "-616 1712 32"
+"spawnflags" "2048"
+}
+{
+"classname" "target_speaker"
+"targetname" "t111"
+"spawnflags" "2048"
+"noise" "world/uplink.wav"
+"origin" "-600 1680 32"
+}
+{
+"origin" "-1472 1376 264"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1024 1856 176"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t110"
+"target" "t14"
+"spawnflags" "2048"
+"origin" "-1448 1448 96"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t110"
+"target" "t106"
+"spawnflags" "2048"
+"origin" "-1480 1448 8"
+}
+{
+"targetname" "t133"
+"classname" "target_speaker"
+"noise" "world/dataspin.wav"
+"spawnflags" "2050"
+"origin" "-1296 1688 72"
+}
+{
+"classname" "target_help"
+"targetname" "t110"
+"message" "Collect disk and\nreturn to\nTactical Command."
+"spawnflags" "2048"
+"origin" "-1496 1552 64"
+}
+{
+"model" "*24"
+"classname" "trigger_once"
+"spawnflags" "2052"
+"targetname" "t12"
+"target" "t110"
+}
+{
+"classname" "target_speaker"
+"noise" "world/dish1.wav"
+"spawnflags" "2048"
+"targetname" "t12"
+"origin" "-656 1688 72"
+}
+{
+"classname" "target_speaker"
+"noise" "world/dish1.wav"
+"spawnflags" "2048"
+"targetname" "t12"
+"origin" "-544 1688 72"
+}
+{
+"classname" "target_speaker"
+"targetname" "t12"
+"noise" "world/dish1.wav"
+"spawnflags" "2050"
+"origin" "-800 912 24"
+}
+{
+"classname" "target_speaker"
+"targetname" "t12"
+"noise" "world/dish1.wav"
+"spawnflags" "2050"
+"origin" "-552 912 24"
+}
+{
+"model" "*25"
+"spawnflags" "0"
+"classname" "func_wall"
+}
+{
+"model" "*26"
+"targetname" "t98"
+"target" "t107"
+"classname" "trigger_multiple"
+"spawnflags" "2052"
+}
+{
+"origin" "-1288 1632 48"
+"target" "t106"
+"targetname" "t104"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"origin" "-1120 1120 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+}
+{
+"origin" "-1312 1112 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+}
+{
+"origin" "-1216 1096 256"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1296 1712 96"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "2049"
+"classname" "target_speaker"
+}
+{
+"origin" "-1200 1712 96"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "2049"
+"classname" "target_speaker"
+}
+{
+"origin" "-1248 1688 96"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "2049"
+"classname" "target_speaker"
+}
+{
+"origin" "-1184 1736 256"
+"targetname" "t101"
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-1184 1736 176"
+"targetname" "t101"
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-1312 1736 256"
+"targetname" "t101"
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-1312 1736 176"
+"targetname" "t101"
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "t105"
+"origin" "-1048 1184 312"
+"message" "Realign satillite array to\n interface with capital ship."
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"model" "*27"
+"message" "Data disk accepted"
+"targetname" "t104"
+"classname" "trigger_once"
+}
+{
+"message" "Bring uplink online using\n communications terminal."
+"origin" "-1232 1536 24"
+"targetname" "t104"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"model" "*28"
+"targetname" "carosel"
+"message" "Terminal locked, data disk required."
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+}
+{
+"model" "*29"
+"killtarget" "lesorac"
+"targetname" "t104"
+"spawnflags" "2052"
+"target" "t105"
+"classname" "trigger_once"
+}
+{
+"killtarget" "carosel"
+"origin" "-1288 1592 48"
+"target" "t104"
+"item" "key_data_cd"
+"targetname" "t103"
+"classname" "trigger_key"
+"spawnflags" "2048"
+}
+{
+"model" "*30"
+"target" "t103"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+}
+{
+"targetname" "t104"
+"spawnflags" "2049"
+"origin" "-1248 1728 44"
+"classname" "key_data_cd"
+"target" "t142"
+}
+{
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"origin" "-1272 1696 24"
+"targetname" "t102"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"origin" "-1224 1696 24"
+"targetname" "t102"
+"classname" "target_speaker"
+}
+{
+"targetname" "t101"
+"origin" "-1312 1770 256"
+"spawnflags" "2120"
+"angle" "-2"
+"classname" "target_laser"
+}
+{
+"origin" "-1200 1624 256"
+"target" "t101"
+"spawnflags" "2048"
+"delay" ".5"
+"targetname" "t14"
+"classname" "trigger_relay"
+}
+{
+"origin" "-1232 1624 256"
+"target" "t102"
+"spawnflags" "2048"
+"delay" "1"
+"targetname" "t14"
+"classname" "trigger_relay"
+}
+{
+"targetname" "t101"
+"origin" "-1184 1770 256"
+"spawnflags" "2120"
+"angle" "-2"
+"classname" "target_laser"
+}
+{
+"targetname" "t14"
+"origin" "-1056 1048 264"
+"spawnflags" "72"
+"angle" "-1"
+"classname" "target_laser"
+}
+{
+"model" "*31"
+"targetname" "t106"
+"spawnflags" "2050"
+"classname" "func_wall"
+}
+{
+"model" "*32"
+"targetname" "t105"
+"spawnflags" "2052"
+"target" "t100"
+"classname" "trigger_once"
+}
+{
+"model" "*33"
+"targetname" "lesorac"
+"message" "Array controls locked."
+"wait" "5"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1696 1504 -192"
+}
+{
+"origin" "-1568 1504 -192"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1792 1472 -184"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1712 1344 -192"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "-1664 1312 -120"
+"light" "100"
+"classname" "light"
+}
+{
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"targetname" "t14"
+"origin" "-1056 1072 308"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"targetname" "t14"
+"origin" "-1056 1072 232"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "2050"
+"noise" "world/l_hum2.wav"
+"targetname" "t14"
+"origin" "-1056 1072 384"
+"classname" "target_speaker"
+}
+{
+"origin" "-1312 1136 280"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-1472 1280 328"
+"light" "125"
+"classname" "light"
+}
+{
+"light" "130"
+"classname" "light"
+"origin" "-1664 1568 96"
+}
+{
+"origin" "-1856 1216 16"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-704 1280 -248"
+"target" "ex4"
+"targetname" "ex3"
+"classname" "trigger_relay"
+"delay" "2"
+"spawnflags" "2048"
+}
+{
+"classname" "info_player_start"
+"angle" "180"
+"targetname" "rb1"
+"origin" "2048 1792 32"
+}
+{
+"targetname" "rb1b"
+"classname" "info_player_start"
+"angle" "270"
+"origin" "-2144 2544 -224"
+}
+{
+"classname" "info_player_start"
+"angle" "180"
+"targetname" "rb1a"
+"origin" "2744 128 -288"
+}
+{
+"classname" "target_crosslevel_trigger"
+"targetname" "t99"
+"origin" "176 96 -280"
+"spawnflags" "2052"
+}
+{
+"classname" "monster_tank_commander"
+"angle" "270"
+"spawnflags" "2049"
+"targetname" "t35"
+"origin" "-8 88 -488"
+"deathtarget" "t99"
+"target" "t139"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-1696 1216 64"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-1472 1152 328"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-1280 1152 384"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-1088 1152 384"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1472 1216 232"
+}
+{
+"light" "80"
+"classname" "light"
+"origin" "-1056 1080 376"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-1056 1080 224"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-1504 1440 -120"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1504 1312 -120"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-1664 1440 -120"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1440 1504 -192"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-1376 1312 -120"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1376 1440 -120"
+}
+{
+"origin" "-2176 2568 -208"
+"message" "Uplink to capitol ship\n to reprogram data disk."
+"targetname" "t98"
+"spawnflags" "2049"
+"classname" "target_help"
+}
+{
+"message" "Locate and enable satellite\n uplink to capitol ship."
+"origin" "-2104 2568 -208"
+"targetname" "t98"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"model" "*34"
+"target" "t98"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"message" "Disable laser grid and\n obtain commander's head."
+"origin" "-224 480 -224"
+"targetname" "t35"
+"spawnflags" "2048"
+"classname" "target_help"
+}
+{
+"targetname" "t96"
+"origin" "-112 560 -448"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"origin" "-424 512 -448"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"origin" "-480 224 -448"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"origin" "-480 -32 -448"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"origin" "-128 -32 -344"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/klaxon1.wav"
+"origin" "160 560 -448"
+}
+{
+"targetname" "t96"
+"origin" "128 -32 -344"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/klaxon1.wav"
+"origin" "-128 192 -344"
+}
+{
+"targetname" "t96"
+"origin" "128 192 -344"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"origin" "0 416 -272"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t96"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/klaxon1.wav"
+"origin" "328 200 -272"
+}
+{
+"targetname" "t96"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/klaxon1.wav"
+"origin" "328 416 -272"
+}
+{
+"targetname" "t96"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/klaxon1.wav"
+"origin" "-208 416 -272"
+}
+{
+"origin" "-1352 1680 8"
+"targetname" "t13"
+"classname" "target_secret"
+"message" "You found a secret." // b#3: moved here
+"spawnflags" "2048"
+}
+{
+"origin" "-1280 1152 24"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "-800 552 -216"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "-552 416 -472"
+"light" "60"
+"classname" "light"
+}
+{
+"model" "*35"
+"target" "t97"
+"wait" "2"
+"_minlight" ".3"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"model" "*36"
+"wait" "2"
+"_minlight" ".3"
+"target" "t97"
+"angle" "180"
+"classname" "func_button"
+}
+{
+"origin" "800 376 -296"
+"classname" "item_health_large"
+}
+{
+"origin" "864 32 -232"
+"classname" "item_health_large"
+}
+{
+"origin" "-552 -32 -488"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-496 -96 -488"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-552 -88 -488"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-352 -104 -488"
+"classname" "ammo_flechettes" // b#2: nails -> flechettes
+}
+{
+"origin" "-416 168 -488"
+"classname" "item_health_large"
+}
+{
+"origin" "-352 168 -488"
+"classname" "ammo_tesla"
+}
+{
+"origin" "-552 88 -488"
+"classname" "ammo_prox"
+}
+{
+"origin" "-216 384 -272"
+"targetname" "t78"
+"target" "t96"
+"spawnflags" "2048"
+"classname" "trigger_relay"
+}
+{
+"origin" "-224 448 -272"
+"target" "t96"
+"spawnflags" "2048"
+"targetname" "t35"
+"classname" "trigger_relay"
+}
+{
+"targetname" "t96"
+"origin" "328 -24 -272"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "2048"
+"origin" "-136 0 -480"
+"item" "ammo_bullets"
+"targetname" "t35"
+"target" "t93"
+"angle" "270"
+"classname" "monster_soldier"
+}
+{
+"origin" "176 -24 -496"
+"targetname" "t95"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "-248 -32 -496"
+"targetname" "t94"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "0 -112 -496"
+"targetname" "t93"
+"angle" "90"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "160 256 -496"
+"targetname" "t92"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "-144 248 -496"
+"targetname" "t91"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "-2464 2176 -240"
+"classname" "ammo_flechettes" // b#2: nails -> flechettes
+}
+{
+"origin" "-1944 2120 -240"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-2464 1976 -240"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-1976 2040 -240"
+"classname" "weapon_grenadelauncher"
+}
+{
+"origin" "-1968 1968 -256"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "1544 -32 -296"
+"classname" "ammo_cells"
+}
+{
+"origin" "1360 424 -296"
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+}
+{
+"origin" "1328 360 -296"
+"classname" "weapon_rocketlauncher"
+"spawnflags" "2048"
+}
+{
+"origin" "1304 432 -320"
+"spawnflags" "2064"
+"classname" "misc_deadsoldier"
+"angle" "270"
+}
+{
+"origin" "1904 400 -24"
+"spawnflags" "2305"
+"angle" "315"
+"classname" "monster_daedalus"
+}
+{
+"origin" "1752 88 -288"
+"item" "ammo_shells"
+"angle" "45"
+"spawnflags" "3585"
+"classname" "monster_soldier"
+}
+{
+"angle" "90"
+"origin" "1824 -144 -288"
+"spawnflags" "2305"
+"classname" "monster_gunner"
+"item" "ammo_bullets"
+}
+{
+"angle" "-2"
+"origin" "1408 32 -136"
+"spawnflags" "3873"
+"classname" "monster_turret"
+}
+{
+"angle" "-2"
+"origin" "1632 608 -136"
+"spawnflags" "2849"
+"classname" "monster_turret"
+}
+{
+"item" "ammo_cells"
+"origin" "2368 128 -288"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_medic_commander"
+}
+{
+"origin" "1184 608 -160"
+"classname" "light"
+"light" "50"
+}
+{
+"origin" "1408 544 -192"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "func_group"
+}
+{
+"origin" "1472 192 -192"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1344 192 -192"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1344 384 -192"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1472 384 -192"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1600 224 -192"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1816 -128 -728"
+"classname" "item_health_small"
+}
+{
+"origin" "1896 -128 -728"
+"classname" "item_health_small"
+}
+{
+"origin" "1968 -128 -728"
+"classname" "item_health_small"
+}
+{
+"origin" "1720 -128 -728"
+"classname" "item_health_small"
+}
+{
+"origin" "352 -224 -296"
+"spawnflags" "2048"
+"classname" "item_armor_combat"
+}
+{
+"origin" "2048 -128 -728"
+"classname" "item_armor_combat"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*37"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*38"
+"classname" "func_button"
+"angle" "180"
+"spawnflags" "2048"
+"wait" "-1"
+"target" "t90"
+"message" "Door unlocked."
+}
+{
+"angle" "-2"
+"spawnflags" "2849"
+"classname" "monster_turret"
+"origin" "1184 608 -136"
+}
+{
+"angle" "-2"
+"classname" "monster_turret"
+"spawnflags" "3081"
+"origin" "1184 608 -136"
+}
+{
+"angle" "-2"
+"spawnflags" "3081"
+"classname" "monster_turret"
+"origin" "1632 608 -136"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "7177"
+"origin" "1408 32 -136"
+"angle" "-2"
+}
+{
+"classname" "light"
+"light" "75"
+"angle" "-2"
+"origin" "1088 432 -496"
+}
+{
+"origin" "1088 480 -496"
+"angle" "-2"
+"light" "90"
+"classname" "light"
+}
+{
+"origin" "960 432 -496"
+"angle" "-2"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "ammo_shells"
+"origin" "1232 -208 -496"
+}
+{
+"classname" "item_health_small"
+"origin" "1376 -224 -488"
+}
+{
+"classname" "item_health_small"
+"origin" "1432 -224 -488"
+}
+{
+"classname" "item_health_small"
+"origin" "1320 -224 -488"
+}
+{
+"spawnflags" "3841"
+"classname" "monster_gunner"
+"angle" "45"
+"origin" "1216 -80 -480"
+"item" "ammo_grenades"
+}
+{
+"angle" "135"
+"classname" "monster_gunner"
+"spawnflags" "3841"
+"origin" "1592 -96 -480"
+"item" "ammo_bullets"
+}
+{
+"classname" "monster_gunner"
+"angle" "90"
+"spawnflags" "2049"
+"origin" "1536 -248 -480"
+"item" "ammo_bullets"
+}
+{
+"classname" "monster_hover"
+"angle" "0"
+"spawnflags" "2049"
+"target" "t88"
+"origin" "1304 -480 -232"
+}
+{
+"classname" "item_health"
+"origin" "1440 -232 -296"
+}
+{
+"classname" "monster_gunner"
+"angle" "135"
+"spawnflags" "2049"
+"origin" "1512 -232 -288"
+}
+{
+"classname" "monster_turret"
+"origin" "1408 -612 -256"
+"targetname" "t88"
+"angle" "90"
+"spawnflags" "2177"
+}
+{
+"classname" "target_explosion"
+"origin" "1408 -580 -256"
+"targetname" "t87"
+"spawnflags" "2048"
+}
+{
+"model" "*39"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"model" "*40"
+"classname" "func_explosive"
+"spawnflags" "2048"
+"target" "t87"
+"targetname" "t88"
+}
+{
+"light" "60"
+"classname" "light"
+"origin" "1552 -112 -160"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1472 -64 -200"
+}
+{
+"classname" "item_health_large"
+"origin" "1248 32 -296"
+}
+{
+"classname" "target_help"
+"targetname" "t39"
+"spawnflags" "2049"
+"message" "Return to Waterfront\n Storage Facility."
+"origin" "1632 240 -216"
+}
+{
+"model" "*41"
+"lip" "6"
+"classname" "func_button"
+"spawnflags" "2048"
+"angle" "0"
+"wait" "-1"
+"message" "Bridge enabled."
+"target" "t39"
+}
+{
+"model" "*42"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-96 512 -280"
+}
+{
+"spawnflags" "5888"
+"classname" "item_armor_body"
+"origin" "352 -224 -296"
+}
+{
+"classname" "monster_soldier"
+"spawnflags" "7169"
+"origin" "472 352 -288"
+"item" "ammo_shells"
+"angle" "180"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "3841"
+"origin" "472 360 -288"
+"item" "ammo_grenades"
+}
+{
+"classname" "monster_medic"
+"angle" "90"
+"spawnflags" "2049"
+"targetname" "t86"
+"origin" "352 -176 -288"
+}
+{
+"classname" "item_armor_shard"
+"origin" "352 544 -304"
+}
+{
+"classname" "item_armor_shard"
+"origin" "288 544 -304"
+}
+{
+"classname" "item_armor_shard"
+"origin" "416 544 -304"
+}
+{
+"classname" "monster_gladiator"
+"angle" "0"
+"spawnflags" "2049"
+"origin" "-192 424 -288"
+"target" "t86"
+"item" "ammo_slugs"
+}
+{
+"classname" "item_health_small"
+"origin" "1056 200 -296"
+}
+{
+"classname" "item_health_small"
+"origin" "1056 248 -296"
+}
+{
+"classname" "item_health_small"
+"origin" "1056 296 -296"
+}
+{
+"classname" "item_health_small"
+"origin" "1056 152 -296"
+}
+{
+"model" "*43"
+"classname" "func_wall"
+"spawnflags" "0"
+}
+{
+"classname" "light"
+"light" "90"
+"origin" "352 -192 -280"
+}
+{
+"item" "ammo_bullets"
+"target" "t95"
+"targetname" "t35"
+"classname" "monster_soldier_ss"
+"angle" "270"
+"origin" "32 0 -480"
+"spawnflags" "2049"
+}
+{
+"item" "ammo_bullets"
+"target" "t94"
+"targetname" "t35"
+"classname" "monster_soldier_ss"
+"angle" "270"
+"origin" "-136 104 -480"
+"spawnflags" "2049"
+}
+{
+"target" "t91"
+"targetname" "t35"
+"classname" "monster_soldier_ss"
+"angle" "270"
+"origin" "-8 184 -480"
+"spawnflags" "2049"
+}
+{
+"target" "t92"
+"targetname" "t35"
+"classname" "monster_soldier_ss"
+"angle" "270"
+"origin" "112 152 -480"
+"spawnflags" "2049"
+}
+{
+"spawnflags" "2048"
+"classname" "info_notnull"
+"team" "rightlaser"
+"targetname" "t84"
+"origin" "176 -192 -448"
+}
+{
+"spawnflags" "2048"
+"classname" "info_notnull"
+"team" "rightlaser"
+"targetname" "t85"
+"origin" "176 -192 -480"
+}
+{
+"spawnflags" "2048"
+"classname" "info_notnull"
+"team" "rightlaser"
+"targetname" "t83"
+"origin" "176 -192 -416"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t79"
+"target" "t80"
+"origin" "176 312 -536"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t81"
+"target" "t82"
+"origin" "176 312 -536"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"target" "t79"
+"targetname" "t82"
+"origin" "176 -192 -536"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t80"
+"target" "t81"
+"origin" "-200 312 -536"
+}
+{
+"model" "*44"
+"classname" "func_train"
+"spawnflags" "2050"
+"target" "t82"
+"team" "rightlaser"
+"targetname" "t74"
+}
+{
+"classname" "trigger_relay"
+"origin" "-64 8 -376"
+"target" "t74"
+"targetname" "t78"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"target" "t73"
+"delay" "5"
+"origin" "56 8 -376"
+"targetname" "t78"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2048"
+"origin" "-176 -192 -448"
+"classname" "info_notnull"
+"team" "leftlaser"
+"targetname" "t76"
+}
+{
+"spawnflags" "2048"
+"origin" "-176 -192 -416"
+"classname" "info_notnull"
+"team" "leftlaser"
+"targetname" "t77"
+}
+{
+"spawnflags" "2048"
+"classname" "info_notnull"
+"origin" "-176 -192 -480"
+"team" "leftlaser"
+"targetname" "t75"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t69"
+"target" "t70"
+"origin" "-192 312 -536"
+}
+{
+"model" "*45"
+"spawnflags" "2050"
+"classname" "func_train"
+"target" "t72"
+"targetname" "t74"
+"team" "leftlaser"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t71"
+"target" "t72"
+"origin" "-192 312 -536"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"target" "t69"
+"targetname" "t72"
+"origin" "-192 -192 -536"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t70"
+"target" "t71"
+"origin" "184 312 -536"
+}
+{
+"classname" "trigger_relay"
+"origin" "-64 32 -344"
+"targetname" "t35"
+"delay" "5"
+"target" "t74"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"origin" "56 32 -344"
+"targetname" "t35"
+"target" "t73"
+"spawnflags" "2048"
+}
+{
+"model" "*46"
+"classname" "func_button"
+"angle" "90"
+"lip" "4"
+"_minlight" ".3"
+"wait" "-1"
+"spawnflags" "2048"
+"target" "t15"
+"message" "Containment fields deactivated."
+}
+{
+"origin" "-896 1280 -248"
+"delay" ".6"
+"spawnflags" "2048"
+"targetname" "ex5"
+"classname" "target_explosion"
+"dmg" "150"
+}
+{
+"target" "ex4"
+"dmg" "150"
+"origin" "-880 1248 -240"
+"spawnflags" "2048"
+"targetname" "ex5"
+"classname" "target_explosion"
+}
+{
+"origin" "-920 1208 -256"
+"delay" ".2"
+"spawnflags" "2048"
+"targetname" "ex5"
+"classname" "target_explosion"
+}
+{
+"origin" "-896 1216 -248"
+"delay" ".6"
+"spawnflags" "2048"
+"targetname" "ex5"
+"classname" "target_explosion"
+"dmg" "150"
+}
+{
+"origin" "-24 440 -480"
+"spawnflags" "2049"
+"angle" "90"
+"classname" "monster_soldier"
+"targetname" "t67"
+}
+{
+"origin" "392 8 -464"
+"classname" "item_health_large"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2056"
+"item" "ammo_rockets"
+"origin" "384 168 -488"
+"angle" "270"
+}
+{
+"origin" "384 216 -464"
+"spawnflags" "5888"
+"classname" "ammo_rockets"
+}
+{
+"model" "*47"
+"spawnflags" "2048"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "t86"
+"wait" "-1"
+}
+{
+"origin" "336 -112 -512"
+"item" "ammo_cells"
+"spawnflags" "2064"
+"classname" "misc_deadsoldier"
+"angle" "90"
+}
+{
+"origin" "992 608 -576"
+"angle" "-2"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "ammo_rockets"
+"origin" "424 464 -488"
+}
+{
+"model" "*48"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "t15"
+}
+{
+"spawnflags" "2049"
+"classname" "point_combat"
+"targetname" "t68"
+"origin" "232 560 -504"
+}
+{
+"classname" "monster_gunner"
+"angle" "135"
+"spawnflags" "2049"
+"origin" "280 472 -488"
+"item" "ammo_grenades"
+"target" "t68"
+"targetname" "t67"
+}
+{
+"model" "*49"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t67"
+}
+{
+"model" "*50"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"model" "*51"
+"classname" "func_explosive"
+"target" "t66"
+"spawnflags" "2048"
+"targetname" "t67"
+}
+{
+"classname" "target_explosion"
+"targetname" "t66"
+"spawnflags" "2048"
+"origin" "-544 512 -316"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "2185"
+"angle" "-2"
+"targetname" "t67"
+"origin" "-544 512 -284"
+}
+{
+"item" "ammo_shells"
+"spawnflags" "2049"
+"angle" "315"
+"classname" "monster_soldier"
+"origin" "-552 616 -480"
+}
+{
+"classname" "monster_soldier"
+"angle" "45"
+"spawnflags" "3841"
+"item" "ammo_shells"
+"origin" "-552 416 -480"
+}
+{
+"classname" "item_armor_jacket"
+"origin" "-1056 696 -168"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-344 616 -496"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-296 616 -496"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-392 616 -496"
+}
+{
+"classname" "item_health_large"
+"origin" "-296 424 -488"
+}
+{
+"classname" "monster_medic_commander"
+"spawnflags" "2049"
+"angle" "135"
+"targetname" "t65"
+"origin" "920 -88 -288"
+}
+{
+"classname" "monster_gunner"
+"spawnflags" "7169"
+"angle" "90"
+"target" "t65"
+"origin" "664 -136 -288"
+}
+{
+"classname" "monster_gladiator"
+"angle" "90"
+"spawnflags" "6913"
+"target" "t65"
+"origin" "656 -136 -288"
+}
+{
+"classname" "item_health_large"
+"origin" "-1048 1576 -240"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1240 1168 -232"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1240 1112 -232"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1240 1056 -232"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1240 1000 -232"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1248 1224 -232"
+}
+{
+"model" "*52"
+"classname" "func_wall"
+"spawnflags" "4096"
+}
+{
+"model" "*53"
+"classname" "func_explosive"
+"spawnflags" "3840"
+"targetname" "t60"
+"target" "t64"
+}
+{
+"classname" "target_explosion"
+"spawnflags" "3840"
+"targetname" "t64"
+"origin" "-1056 1504 -60"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "4001"
+"angle" "-2"
+"targetname" "t60"
+"origin" "-1056 1504 -28"
+}
+{
+"classname" "target_explosion"
+"targetname" "t63"
+"spawnflags" "2304"
+"origin" "-1056 1056 -60"
+}
+{
+"model" "*54"
+"classname" "func_explosive"
+"spawnflags" "2816"
+"target" "t63"
+"targetname" "t60"
+}
+{
+"model" "*55"
+"classname" "func_wall"
+"spawnflags" "5120"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "7049"
+"angle" "-2"
+"targetname" "t60"
+"origin" "-1056 1056 -28"
+}
+{
+"classname" "item_health_large"
+"origin" "-856 616 -232"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t62"
+"origin" "-936 432 -240"
+}
+{
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_soldier_ss"
+"origin" "-1048 568 -224"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "180"
+"spawnflags" "2049"
+"target" "t62"
+"item" "ammo_bullets"
+"origin" "-880 432 -224"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+"classname" "light"
+"origin" "-1216 432 -160"
+}
+{
+"model" "*56"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t61"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t59"
+"origin" "-1184 800 -248"
+}
+{
+"classname" "monster_gunner"
+"angle" "45"
+"spawnflags" "2049"
+"target" "t59"
+"targetname" "t60"
+"origin" "-1248 680 -224"
+"item" "ammo_grenades"
+}
+{
+"model" "*57"
+"classname" "func_wall"
+"spawnflags" "5632"
+}
+{
+"model" "*58"
+"classname" "func_explosive"
+"spawnflags" "2304"
+"targetname" "t61"
+}
+{
+"classname" "target_explosion"
+"targetname" "t61" // b#4: added this
+"spawnflags" "2304"
+"origin" "-1152 608 -60"
+}
+{
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "6537"
+"origin" "-1152 608 -28"
+"targetname" "t61"
+}
+{
+"classname" "monster_soldier"
+"angle" "90"
+"spawnflags" "2048"
+"origin" "-1056 1040 -224"
+"target" "t60"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_gunner"
+"angle" "315"
+"spawnflags" "2049"
+"origin" "-1232 1536 -224"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-1256 1696 -208"
+}
+{
+"model" "*59"
+"targetname" "t107"
+"classname" "func_door"
+"angle" "-2"
+"_minlight" ".3"
+"sounds" "2"
+"message" "Access denied."
+"spawnflags" "2048"
+"wait" "-1"
+}
+{
+"targetname" "ex3"
+"origin" "-640 1216 -200"
+"delay" ".2"
+"spawnflags" "2048"
+"classname" "target_explosion"
+}
+{
+"targetname" "ex2"
+"origin" "-336 1224 -32"
+"spawnflags" "2048"
+"classname" "target_explosion"
+}
+{
+"targetname" "ex1"
+"origin" "-24 1240 144"
+"spawnflags" "2048"
+"classname" "target_explosion"
+}
+{
+"origin" "128 1216 208"
+"delay" ".2"
+"targetname" "t57"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"origin" "128 1144 208"
+"delay" ".2"
+"targetname" "t57"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*60"
+"killtarget" "scardead"
+"health" "2"
+"target" "t57"
+"mass" "500"
+"spawnflags" "2048"
+"classname" "func_explosive"
+}
+{
+"origin" "188 1180 236"
+"spawnflags" "2048"
+"targetname" "scardead"
+"classname" "info_notnull"
+}
+{
+"origin" "-704 1480 8"
+"killtarget" "scardead"
+"target" "t56"
+"targetname" "t55"
+"classname" "target_anger"
+"spawnflags" "2048"
+}
+{
+"model" "*61"
+"target" "t55"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"pathtarget" "ex5"
+"speed" "850"
+"spawnflags" "2048"
+"targetname" "t53"
+"classname" "path_corner"
+"origin" "-970 1188 -320"
+}
+{
+"pathtarget" "ex3"
+"speed" "800"
+"spawnflags" "2048"
+"target" "t53"
+"targetname" "t52"
+"origin" "-642 1188 -200"
+"classname" "path_corner"
+}
+{
+"pathtarget" "ex2"
+"speed" "750"
+"spawnflags" "2048"
+"target" "t52"
+"targetname" "t51"
+"classname" "path_corner"
+"origin" "-330 1188 -32"
+}
+{
+"pathtarget" "ex1"
+"speed" "700"
+"spawnflags" "2048"
+"target" "t51"
+"targetname" "t50"
+"origin" "-18 1180 144"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"targetname" "t54"
+"target" "t50"
+"origin" "270 1172 240"
+"classname" "path_corner"
+}
+{
+"classname" "point_combat"
+"wait" "10"
+"targetname" "t49"
+"origin" "-800 1576 112"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "3585"
+"angle" "135"
+"classname" "monster_hover"
+"origin" "32 1336 168"
+"target" "t49"
+"item" "ammo_cells"
+}
+{
+"classname" "monster_hover"
+"angle" "135"
+"spawnflags" "2817"
+"origin" "128 960 -240"
+}
+{
+"classname" "monster_daedalus"
+"angle" "135"
+"spawnflags" "2305"
+"origin" "32 1344 168"
+"target" "t49"
+"item" "ammo_cells"
+}
+{
+"classname" "item_health_large"
+"origin" "-216 1472 -184"
+}
+{
+"classname" "ammo_slugs"
+"spawnflags" "0"
+"origin" "112 1304 -56"
+}
+{
+"classname" "weapon_railgun"
+"spawnflags" "5888"
+"origin" "56 1344 -56"
+}
+{
+"classname" "item_health_small"
+"origin" "296 1928 -56"
+}
+{
+"classname" "item_health_small"
+"origin" "368 1952 -56"
+}
+{
+"classname" "item_health_small"
+"origin" "224 1912 -56"
+}
+{
+"classname" "monster_medic_commander"
+"angle" "315"
+"spawnflags" "2049"
+"origin" "-848 1696 -224"
+"targetname" "t58"
+}
+{
+"model" "*62"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t47"
+}
+{
+"model" "*63"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"classname" "target_explosion"
+"spawnflags" "2048"
+"targetname" "t48"
+"origin" "1666 1980 240"
+}
+{
+"model" "*64"
+"classname" "func_explosive"
+"spawnflags" "2048"
+"targetname" "t47"
+"target" "t48"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "2185"
+"angle" "180"
+"origin" "1700 1984 240"
+"targetname" "t47"
+}
+{
+"origin" "800 1312 0"
+"item" "ammo_bullets"
+"spawnflags" "2049"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"origin" "1280 2208 16"
+"classname" "weapon_proxlauncher"
+}
+{
+"message" "Rendevous at entrance \n with incoming marine."
+"origin" "2096 1832 104"
+"targetname" "t46"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"message" "Locate entrance to \n Logistics Complex."
+"origin" "2096 1752 104"
+"spawnflags" "2049"
+"targetname" "t46"
+"classname" "target_help"
+}
+{
+"model" "*65"
+"target" "t46"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"model" "*66"
+"classname" "func_button"
+"angle" "270"
+"_minlight" ".3"
+"wait" "2"
+"message" "Lift activated."
+"target" "t4"
+}
+{
+"targetname" "t56"
+"spawnflags" "2049"
+"classname" "monster_gladiator"
+"angle" "0"
+"origin" "-584 1480 -240"
+"target" "t58"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_daedalus"
+"angle" "135"
+"spawnflags" "3841"
+"origin" "1568 1704 408"
+}
+{
+"classname" "item_sphere_hunter"
+"spawnflags" "5888"
+"origin" "1640 2112 80"
+}
+{
+"classname" "item_health_large"
+"origin" "1576 2128 24"
+}
+{
+"classname" "item_health_large"
+"origin" "1416 2240 24"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+"spawnflags" "2049"
+"target" "t43"
+"targetname" "t42"
+"origin" "1080 1496 8"
+}
+{
+"item" "ammo_shells"
+"classname" "monster_soldier"
+"spawnflags" "2305"
+"target" "t44"
+"targetname" "t42"
+"origin" "1024 1496 8"
+}
+{
+"classname" "point_combat"
+"origin" "1216 2080 -16"
+"spawnflags" "2048"
+"targetname" "t43"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2048"
+"origin" "1000 1736 -16"
+"targetname" "t44"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t41"
+"origin" "1616 1952 8"
+}
+{
+"classname" "monster_soldier"
+"angle" "45"
+"spawnflags" "2049"
+"target" "t41"
+"origin" "1512 1864 24"
+}
+{
+"model" "*67"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t42"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"angle" "225"
+"spawnflags" "2049"
+"origin" "1672 2272 32"
+}
+{
+"classname" "ammo_prox"
+"origin" "1224 2220 24"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2050"
+"angle" "135"
+"origin" "1248 2160 0"
+}
+{
+"classname" "item_armor_shard"
+"origin" "1184 2184 24"
+}
+{
+"classname" "item_armor_shard"
+"origin" "1168 2136 24"
+}
+{
+"classname" "target_changelevel"
+"targetname" "t40"
+"map" "rware2$rw2b"
+"origin" "2776 96 -208"
+"spawnflags" "2048"
+}
+{
+"model" "*68"
+"angle" "0"
+"classname" "trigger_multiple"
+"target" "t40"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2720 128 -256"
+}
+{
+"origin" "2016 568 -256"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "2016 672 -512"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "1904 672 -512"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "2016 568 -448"
+"light" "120"
+"classname" "light"
+}
+{
+"model" "*69"
+"targetname" "t219"
+"message" "Access denied."
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-2"
+"_minlight" ".3"
+}
+{
+"model" "*70"
+"origin" "1916 254 -328"
+"targetname" "t39"
+"_minlight" ".3"
+"speed" "35"
+"spawnflags" "161"
+"classname" "func_door_rotating"
+"distance" "90"
+}
+{
+"model" "*71"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*72"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*73"
+"_minlight" ".3"
+"angle" "-1"
+"classname" "func_door"
+"spawnflags" "2048"
+}
+{
+"model" "*74"
+"message" "Access denied."
+"_minlight" ".3"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "t89"
+"spawnflags" "2048"
+}
+{
+"origin" "1280 -224 -200"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "1472 -224 -200"
+"light" "120"
+"classname" "light"
+}
+{
+"origin" "1552 -16 -160"
+"classname" "light"
+"light" "60"
+}
+{
+"origin" "1344 -64 -200"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "272 288 -256"
+"classname" "light"
+"light" "80"
+}
+{
+"light" "80"
+"classname" "light"
+"origin" "272 96 -256"
+}
+{
+"origin" "224 496 -256"
+"classname" "light"
+"light" "80"
+}
+{
+"origin" "224 336 -256"
+"classname" "light"
+"light" "80"
+}
+{
+"origin" "432 288 -256"
+"classname" "light"
+"light" "80"
+}
+{
+"origin" "-32 496 -256"
+"light" "80"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "448 0 -240"
+}
+{
+"origin" "448 192 -240"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "288 480 -456"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "192 560 -456"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "416 480 -456"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "0 560 -456"
+}
+{
+"origin" "-1216 1360 264"
+"light" "80"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-1088 1360 264"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-192 416 -240"
+}
+{
+"origin" "96 512 -240"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "352 512 -240"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "448 416 -240"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "304 192 -240"
+}
+{
+"origin" "304 0 -240"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "352 -64 -240"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "544 128 -432"
+}
+{
+"model" "*75"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2180"
+"origin" "-154 -90 -480"
+"targetname" "t74"
+"dmg" "1000"
+"angle" "90"
+"classname" "target_laser"
+"target" "t85"
+}
+{
+"spawnflags" "2180"
+"origin" "-154 -90 -448"
+"targetname" "t74"
+"dmg" "1000"
+"angle" "90"
+"classname" "target_laser"
+"target" "t84"
+}
+{
+"spawnflags" "2180"
+"origin" "-154 -90 -416"
+"targetname" "t74"
+"classname" "target_laser"
+"angle" "90"
+"dmg" "1000"
+"target" "t83"
+}
+{
+"spawnflags" "2180"
+"origin" "154 -90 -448"
+"targetname" "t74"
+"classname" "target_laser"
+"angle" "90"
+"dmg" "1000"
+"target" "t76"
+}
+{
+"spawnflags" "2180"
+"origin" "154 -90 -480"
+"targetname" "t74"
+"classname" "target_laser"
+"angle" "90"
+"dmg" "1000"
+"target" "t75"
+}
+{
+"spawnflags" "2180"
+"origin" "154 -90 -416"
+"dmg" "1000"
+"targetname" "t74"
+"angle" "90"
+"classname" "target_laser"
+"target" "t77"
+}
+{
+"model" "*76"
+"lip" "-4"
+"wait" "-1"
+"speed" "30"
+"_minlight" ".3"
+"spawnflags" "2081"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "t73"
+}
+{
+"model" "*77"
+"target" "t35"
+"_minlight" ".3"
+"lip" "4"
+"wait" "-1"
+"angle" "180"
+"classname" "func_button"
+"spawnflags" "2048"
+"message" "Security counter measures activated."
+"killtarget" "cehss"
+}
+{
+"origin" "-520 96 -448"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-544 -32 -448"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-896 448 -128"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-896 576 -128"
+"light" "100"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"origin" "432 -32 -432"
+"targetname" "t34"
+"classname" "target_explosion"
+}
+{
+"origin" "0 424 -456"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-160 560 -456"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-264 -32 -448"
+"light" "60"
+"classname" "light"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-352 512 -280"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-480 512 -280"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-480 512 -400"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "-352 512 -400"
+}
+{
+"model" "*78"
+"spawnflags" "2048"
+"target" "t34"
+"classname" "func_explosive"
+"mass" "100"
+"_minlight" ".3"
+"health" "5"
+}
+{
+"model" "*79"
+"_minlight" ".3"
+"angle" "-2"
+"classname" "func_door"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-360 -32 -440"
+}
+{
+"model" "*80"
+"classname" "func_wall"
+"spawnflags" "2054"
+"_minlight" ".3"
+"targetname" "t15"
+}
+{
+"model" "*81"
+"classname" "func_wall"
+"spawnflags" "2054"
+"_minlight" ".3"
+"targetname" "t15"
+}
+{
+"origin" "-224 32 -456"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-224 -96 -456"
+}
+{
+"model" "*82"
+"classname" "func_button"
+"angle" "0"
+"_minlight" ".3"
+"wait" "-1"
+"spawnflags" "2048"
+"target" "t78"
+"message" "Security counter measures deactvated."
+"targetname" "t138"
+}
+{
+"origin" "-1024 512 -136"
+"classname" "light"
+"light" "100"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+"classname" "light"
+"origin" "-1056 432 -160"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1216 512 -136"
+}
+{
+"model" "*83"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*84"
+"targetname" "t136"
+"_minlight" ".3"
+"angle" "-1"
+"lip" "144"
+"classname" "func_plat2"
+"spawnflags" "36"
+}
+{
+"origin" "-688 512 -416"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "-688 512 -88"
+"classname" "light"
+"light" "80"
+}
+{
+"origin" "-688 512 -160"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "64 304 -240"
+}
+{
+"origin" "-64 304 -240"
+"light" "80"
+"classname" "light"
+}
+{
+"origin" "-1216 1352 264"
+"light" "80"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-1088 1352 264"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-1856 1792 56"
+}
+{
+"classname" "func_group"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-704 2064 56"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-576 1808 56"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1728 1728 96"
+}
+{
+"model" "*85"
+"team" "camrod"
+"_minlight" ".3"
+"angle" "90"
+"classname" "func_door"
+}
+{
+"model" "*86"
+"team" "camrod"
+"_minlight" ".3"
+"angle" "270"
+"classname" "func_door"
+}
+{
+"origin" "2016 1680 96"
+"light" "180"
+"classname" "light"
+}
+{
+"origin" "1952 1712 96"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1952 1872 96"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2080 1872 96"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "2016 1904 96"
+"classname" "light"
+"light" "180"
+}
+{
+"origin" "2080 1712 96"
+"light" "100"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1936 1792 160"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1792 1792 96"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "1728 1856 96"
+"light" "75"
+"classname" "light"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-512 1728 56"
+}
+{
+"classname" "target_changelevel"
+"targetname" "t9"
+"origin" "2072 1768 120"
+"map" "rware2$rw2a"
+"spawnflags" "2048"
+}
+{
+"model" "*87"
+"targetname" "4idiots"
+"classname" "trigger_multiple"
+"target" "t9"
+"angle" "0"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2048"
+"targetname" "t12"
+"classname" "misc_satellite_dish"
+"angle" "0"
+"origin" "-544 800 64"
+}
+{
+"origin" "-2144 2528 -192"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "140"
+"origin" "640 1504 48"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "896 1296 48"
+"light" "140"
+}
+{
+"classname" "light"
+"origin" "672 1312 48"
+"light" "140"
+}
+{
+"classname" "light"
+"origin" "664 1728 32"
+"light" "140"
+}
+{
+"classname" "light"
+"origin" "1032 1432 48"
+"light" "140"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"origin" "-2432 2144 -192"
+}
+{
+"origin" "-2240 2192 -72"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-2048 2192 -72"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-704 1728 56"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*88"
+"targetname" "t100"
+"target" "t12"
+"classname" "func_button"
+"angle" "270"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"model" "*89"
+"classname" "func_door"
+"angle" "-2"
+"_minlight" ".3"
+"sounds" "2"
+"spawnflags" "2048"
+}
+{
+"model" "*90"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "-576 1920 56"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-584 2056 56"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-832 2064 56"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-2448 1792 -152"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-2240 2048 32"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-2304 2048 -72"
+}
+{
+"spawnflags" "2048"
+"targetname" "t12"
+"origin" "-800 800 64"
+"angle" "0"
+"classname" "misc_satellite_dish"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-2048 2048 32"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-1984 2048 -72"
+}
+{
+"origin" "-2448 1792 152"
+"light" "80"
+"classname" "light"
+}
+{
+"origin" "-2144 2048 -72"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-2432 2016 -192"
+"classname" "light"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+}
+{
+"origin" "-2520 2112 -240"
+"_color" "0.000000 0.501961 1.000000"
+"light" "75"
+"classname" "light"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-2448 1792 104"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-2448 1792 -16"
+}
+{
+"origin" "-1152 1632 -192"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-1152 1472 -136"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-1472 1952 -192"
+}
+{
+"origin" "-1152 1792 -136"
+"light" "125"
+"classname" "light"
+}
+{
+"model" "*91"
+"targetname" "t98"
+"target" "t10"
+"spawnflags" "2052"
+"classname" "trigger_multiple"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-992 1696 312"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-992 1440 312"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "-1056 1568 312"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "-1152 1568 312"
+}
+{
+"origin" "-1184 1856 176"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1408 1856 312"
+"classname" "light"
+"light" "130"
+}
+{
+"origin" "-1216 1856 312"
+"classname" "light"
+"light" "130"
+}
+{
+"classname" "light"
+"light" "130"
+"origin" "-1024 1856 312"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-1152 1216 -136"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-1152 1344 -136"
+}
+{
+"origin" "-1152 1088 -136"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-1152 768 -120"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1080 696 -72"
+}
+{
+"origin" "-1080 840 -72"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*92"
+"origin" "-1152 768 32"
+"classname" "func_rotating"
+"_minlight" ".3"
+"spawnflags" "2051"
+"speed" "600"
+}
+{
+"origin" "-1152 928 -192"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1024 1248 56"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-1152 1312 56"
+}
+{
+"origin" "-1088 1280 256"
+"light" "120"
+"classname" "light"
+}
+{
+"targetname" "t102"
+"classname" "target_laser"
+"angle" "-2"
+"spawnflags" "2120"
+"origin" "-1248 1730 76"
+}
+{
+"model" "*93"
+"spawnflags" "2048"
+"targetname" "t105"
+"classname" "func_button"
+"angle" "270"
+"wait" "-1"
+"lip" "8"
+"target" "t14"
+"_minlight" ".3"
+}
+{
+"origin" "-1744 1632 96"
+"classname" "light"
+"light" "80"
+}
+{
+"origin" "-1472 1536 312"
+"classname" "light"
+"light" "130"
+}
+{
+"model" "*94"
+"target" "t13"
+"_minlight" ".3"
+"health" "1"
+"lip" "4"
+"wait" "-1"
+"angle" "0"
+"classname" "func_button"
+"spawnflags" "2048"
+}
+{
+"model" "*95"
+"targetname" "t137"
+"classname" "func_plat2"
+"lip" "144"
+"_minlight" ".2"
+"angle" "-2"
+"spawnflags" "32"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-1472 1792 -136"
+}
+{
+"origin" "-2048 1728 -192"
+"classname" "light"
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+"classname" "light"
+"origin" "-1856 1728 -192"
+}
+{
+"model" "*96"
+"targetname" "t10"
+"message" "Access denied."
+"sounds" "2"
+"_minlight" ".3"
+"angle" "-2"
+"classname" "func_door"
+"spawnflags" "2048"
+"wait" "-1"
+}
+{
+"model" "*97"
+"target" "t4"
+"angle" "270"
+"classname" "func_button"
+"message" "Lift activated."
+"wait" "2"
+"_minlight" ".3"
+}
+{
+"origin" "-2048 1792 56"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-2240 1792 56"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-1664 1792 56"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-2240 1728 -192"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-2208 1792 -192"
+"classname" "light"
+"light" "115"
+}
+{
+"origin" "-1984 1792 -192"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-2048 1792 -136"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1312 1696 312"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1312 1440 312"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "-1248 1568 312"
+}
+{
+"origin" "-1472 1408 64"
+"classname" "light"
+"light" "110"
+}
+{
+"origin" "-1472 1728 312"
+"classname" "light"
+"light" "130"
+}
+{
+"light" "110"
+"classname" "light"
+"origin" "-1472 1536 64"
+}
+{
+"origin" "-1224 696 -72"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1224 840 -72"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1280 1248 56"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-1120 1136 280"
+}
+{
+"origin" "-1280 1280 256"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "544 0 -432"
+"_color" "1.000000 0.000000 0.000000"
+"light" "80"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "80"
+"classname" "light"
+"origin" "704 72 -432"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "80"
+"classname" "light"
+"origin" "864 64 -432"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "80"
+"classname" "light"
+"origin" "1024 64 -504"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "80"
+"classname" "light"
+"origin" "1024 256 -504"
+}
+{
+"angle" "-2"
+"light" "125"
+"classname" "light"
+"origin" "1056 608 -576"
+}
+{
+"classname" "light"
+"light" "90"
+"angle" "-2"
+"origin" "960 496 -496"
+}
+{
+"angle" "-2"
+"light" "150"
+"classname" "light"
+"origin" "992 608 -448"
+}
+{
+"classname" "light"
+"light" "150"
+"angle" "-2"
+"origin" "1056 608 -448"
+}
+{
+"classname" "light"
+"light" "125"
+"angle" "-2"
+"origin" "1408 256 -584"
+}
+{
+"angle" "-2"
+"light" "150"
+"classname" "light"
+"origin" "1408 256 -456"
+}
+{
+"angle" "-2"
+"light" "80"
+"classname" "light"
+"origin" "1504 64 -584"
+}
+{
+"classname" "light"
+"light" "150"
+"angle" "-2"
+"origin" "1504 160 -456"
+}
+{
+"origin" "1312 160 -456"
+"classname" "light"
+"light" "150"
+"angle" "-2"
+}
+{
+"origin" "1408 64 -584"
+"angle" "-2"
+"light" "80"
+"classname" "light"
+}
+{
+"targetname" "t32"
+"origin" "1060 256 -496"
+"spawnflags" "2053"
+"angle" "180"
+"classname" "target_laser"
+"dmg" "1000"
+}
+{
+"origin" "1080 184 -504"
+"spawnflags" "2049"
+"wait" "2.5"
+"random" "2"
+"target" "t32"
+"classname" "func_timer"
+}
+{
+"origin" "1032 256 -496"
+"targetname" "t32"
+"spawnflags" "1"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "1008 256 -496"
+"targetname" "t32"
+"classname" "target_speaker"
+"noise" "world/l_hum2.wav"
+"spawnflags" "1"
+}
+{
+"model" "*98"
+"origin" "1088 468 -496"
+"speed" "400"
+"_minlight" ".3"
+"spawnflags" "9"
+"classname" "func_rotating"
+}
+{
+"light" "80"
+"origin" "1304 40 -536"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1280 -32 -640"
+"light" "100"
+}
+{
+"light" "100"
+"origin" "1280 -32 -440"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1536 -32 -440"
+"light" "100"
+}
+{
+"classname" "light"
+"origin" "1536 -32 -640"
+"light" "100"
+}
+{
+"classname" "light"
+"origin" "1408 -32 -640"
+"light" "100"
+}
+{
+"classname" "light"
+"origin" "1304 40 -640"
+"light" "80"
+}
+{
+"classname" "light"
+"light" "125"
+"angle" "-2"
+"origin" "1312 160 -584"
+}
+{
+"origin" "1504 160 -584"
+"classname" "light"
+"light" "125"
+"angle" "-2"
+}
+{
+"classname" "light"
+"light" "80"
+"angle" "-2"
+"origin" "1312 64 -584"
+} \ No newline at end of file
diff --git a/rogue/stuff/mapfixes/rhangar2.ent b/rogue/stuff/mapfixes/rhangar2.ent
new file mode 100644
index 0000000..3ed064d
--- /dev/null
+++ b/rogue/stuff/mapfixes/rhangar2.ent
@@ -0,0 +1,7436 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Made a monster_turret reachable (b#1)
+//
+// The turret spawns but is behind a func_door that never
+// opens. It has targetname t332 but is never targeted, so I set it
+// to t311 instead so it opens after pressing the console nearby.
+//
+// 2. Fixed nagging help message (b#2)
+//
+// This is more of a code bug but can be avoided by a mapfix too.
+// Basically trigger_always trigger during savegame loads when they
+// really shouldn't. It's harmless most of the time but target_help
+// modify the global game state which is not cleared at this point.
+//
+// 3. Fixed wrong/overlapping secret sound effect (b#3)
+//
+// 4. Fixed old classnames (b#4)
+{
+"spawnflags" "2"
+"angle" "0"
+"message" "Maintenance Hangars"
+"classname" "worldspawn"
+"sky" "rogue2"
+"nextmap" "rammo1"
+"sounds" "5"
+}
+{
+"classname" "item_enviro"
+"spawnflags" "2048"
+"origin" "776 1832 64"
+}
+{
+"model" "*1"
+"classname" "func_water"
+"spawnflags" "2048"
+"team" "argh1"
+}
+{
+"model" "*2"
+"classname" "trigger_hurt"
+"dmg" "4000"
+}
+{
+"model" "*3"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "arc"
+"target" "t148"
+}
+{
+"model" "*4"
+"classname" "func_train"
+"spawnflags" "2"
+"target" "t204"
+"targetname" "t206"
+}
+{
+"target" "t400"
+"origin" "-1272 1336 808"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t11"
+"delay" "5"
+"killtarget" "t399"
+"target" "t11help" // b#2: added this
+"origin" "96 -480 8"
+}
+{
+"origin" "-584 840 664"
+"spawnflags" "6144"
+"classname" "weapon_bfg"
+}
+{
+"origin" "-592 904 648"
+"angle" "90"
+"spawnflags" "6148"
+"classname" "misc_deadsoldier"
+}
+{
+"model" "*5"
+"classname" "func_explosive"
+"targetname" "t294"
+}
+{
+"model" "*6"
+"wait" "-1"
+"targetname" "t367"
+"speed" "200"
+"sounds" "1"
+"angle" "-1"
+"classname" "func_door"
+}
+{
+"origin" "584 1440 280"
+"classname" "item_health_large"
+}
+{
+"origin" "-960 672 536"
+"spawnflags" "2048"
+"target" "t119"
+"targetname" "t126"
+"delay" ".5"
+"classname" "trigger_relay"
+}
+{
+"model" "*7"
+"wait" "-1"
+"targetname" "t311"
+"spawnflags" "2048"
+"sounds" "1"
+"lip" "4"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"classname" "info_player_intermission"
+"angles" "15 135 0"
+"origin" "-1032 1176 984"
+}
+{
+"classname" "item_health_large"
+"origin" "-736 1232 280"
+}
+{
+"classname" "trigger_always"
+"spawnflags" "2048"
+"target" "t11"
+"origin" "88 -456 8"
+"targetname" "t399"
+}
+{
+"origin" "72 208 272"
+"classname" "item_health_large"
+}
+{
+"origin" "1000 1568 280"
+"classname" "item_health"
+}
+{
+"origin" "-1280 1520 912"
+"targetname" "t398"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "45"
+"origin" "-344 1800 104"
+}
+{
+"origin" "-568 576 664"
+"classname" "ammo_disruptor"
+}
+{
+"origin" "-584 840 664"
+"classname" "ammo_flechettes" // b#4: nails -> flechettes
+}
+{
+"origin" "-1136 1224 784"
+"classname" "ammo_flechettes" // b#4: nails -> flechettes
+}
+{
+"origin" "-1456 1224 784"
+"classname" "ammo_slugs"
+}
+{
+"origin" "-1120 1600 784"
+"classname" "ammo_prox"
+}
+{
+"origin" "-1448 1576 784"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-1280 1544 912"
+"spawnflags" "5888"
+"classname" "weapon_rocketlauncher"
+}
+{
+"origin" "-624 616 848"
+"classname" "item_health"
+}
+{
+"origin" "-592 1104 848"
+"classname" "ammo_cells"
+}
+{
+"origin" "-640 1000 848"
+"classname" "ammo_rockets"
+}
+{
+"origin" "128 672 656"
+"classname" "item_armor_jacket"
+}
+{
+"origin" "-72 1112 656"
+"classname" "ammo_shells"
+}
+{
+"origin" "-24 1112 656"
+"spawnflags" "5888"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-384 600 664"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-384 1184 664"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-384 856 664"
+"classname" "item_health"
+}
+{
+"origin" "-616 840 544"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-544 608 544"
+"classname" "ammo_cells"
+}
+{
+"origin" "-544 480 544"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-544 552 544"
+"spawnflags" "1792"
+"classname" "weapon_plasmabeam" // b#4: heat -> plasma
+}
+{
+"origin" "-288 480 552"
+"angle" "180"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "712 2768 -176"
+"classname" "item_doppleganger"
+"spawnflags" "5888"
+}
+{
+"origin" "40 2528 -176"
+"spawnflags" "5888"
+"classname" "weapon_etf_rifle" // b#4: nailgun -> etf_rifle
+}
+{
+"origin" "-96 2528 -176"
+"classname" "ammo_flechettes" // b#4: nails -> flechettes
+}
+{
+"origin" "-224 2656 -168"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-152 2208 -112"
+"spawnflags" "1536"
+"classname" "ammo_tesla"
+}
+{
+"origin" "168 2104 -112"
+"classname" "item_health_small"
+}
+{
+"origin" "144 2232 -112"
+"classname" "item_armor_combat"
+}
+{
+"origin" "-80 1768 96"
+"spawnflags" "1536"
+"classname" "weapon_plasmabeam" // b#4: heat -> plasma
+}
+{
+"origin" "-80 1832 96"
+"classname" "ammo_cells"
+}
+{
+"origin" "776 1840 64"
+"spawnflags" "5888"
+"classname" "item_sphere_defender"
+}
+{
+"origin" "640 2656 -48"
+"spawnflags" "5888"
+"classname" "item_quad"
+}
+{
+"origin" "416 2040 64"
+"classname" "ammo_tesla"
+}
+{
+"origin" "416 2128 64"
+"classname" "ammo_prox"
+}
+{
+"origin" "416 2232 64"
+"classname" "ammo_disruptor"
+}
+{
+"origin" "-392 1792 112"
+"spawnflags" "0"
+"classname" "item_health_large"
+}
+{
+"origin" "-792 1960 144"
+"spawnflags" "5888"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-808 1816 152"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-744 2016 144"
+"classname" "ammo_shells"
+}
+{
+"origin" "-736 1640 144"
+"classname" "ammo_cells"
+}
+{
+"origin" "272 -416 -64"
+"classname" "ammo_prox"
+}
+{
+"origin" "-736 1280 280"
+"spawnflags" "5888"
+"classname" "weapon_proxlauncher"
+}
+{
+"origin" "-736 1320 280"
+"classname" "ammo_prox"
+}
+{
+"origin" "-416 1248 280"
+"classname" "ammo_tesla"
+}
+{
+"origin" "480 288 280"
+"classname" "ammo_rockets"
+}
+{
+"origin" "208 320 280"
+"spawnflags" "5888"
+"classname" "weapon_rocketlauncher"
+}
+{
+"origin" "480 544 280"
+"classname" "item_health"
+}
+{
+"model" "*8"
+"targetname" "t274"
+"spawnflags" "17"
+"dmg" "20"
+"classname" "trigger_hurt"
+}
+{
+"origin" "-80 1176 280"
+"classname" "item_health"
+}
+{
+"origin" "160 1464 280"
+"classname" "item_health"
+}
+{
+"origin" "728 792 368"
+"classname" "ammo_tesla"
+}
+{
+"origin" "672 800 376"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "928 888 368"
+"classname" "item_health"
+}
+{
+"classname" "item_health_large"
+"origin" "1632 984 80"
+}
+{
+"classname" "item_armor_combat"
+"spawnflags" "1792"
+"origin" "1648 864 80"
+}
+{
+"classname" "item_armor_body"
+"spawnflags" "1792"
+"origin" "1648 864 80"
+}
+{
+"classname" "weapon_chainfist"
+"spawnflags" "5888"
+"origin" "1056 1560 280"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "1536"
+"origin" "1344 656 272"
+}
+{
+"classname" "ammo_disruptor"
+"spawnflags" "0"
+"origin" "1072 912 368"
+}
+{
+"classname" "weapon_disintegrator"
+"origin" "1072 960 368"
+"spawnflags" "5888"
+}
+{
+"classname" "item_bandolier"
+"spawnflags" "5888"
+"origin" "1288 920 408"
+}
+{
+"classname" "item_sphere_hunter"
+"origin" "1288 808 408"
+"spawnflags" "5888"
+}
+{
+"classname" "item_health_mega"
+"spawnflags" "5888"
+"origin" "1520 864 56"
+}
+{
+"classname" "item_health"
+"origin" "880 480 272"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "1792"
+"origin" "872 648 272"
+}
+{
+"classname" "weapon_hyperblaster"
+"spawnflags" "5888"
+"origin" "872 560 272"
+}
+{
+"classname" "ammo_flechettes" // b#4: nails -> flechettes
+"origin" "-272 -1040 -64"
+}
+{
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+"origin" "424 -176 184"
+}
+{
+"classname" "item_health"
+"spawnflags" "512"
+"origin" "416 -120 184"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "1024"
+"origin" "408 -64 184"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "-32 -16 280"
+}
+{
+"classname" "weapon_grenadelauncher"
+"origin" "-88 72 272"
+}
+{
+"classname" "ammo_grenades"
+"origin" "-144 72 272"
+}
+{
+"classname" "weapon_machinegun"
+"spawnflags" "5888"
+"origin" "112 16 -64"
+}
+{
+"classname" "ammo_bullets"
+"origin" "-120 48 -64"
+}
+{
+"classname" "ammo_shells"
+"origin" "-712 -136 -48"
+"spawnflags" "1024"
+}
+{
+"classname" "weapon_shotgun"
+"spawnflags" "1024"
+"origin" "-656 -136 -48"
+}
+{
+"classname" "item_armor_jacket"
+"spawnflags" "2048"
+"origin" "-456 -168 -256"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1048 -176 -240"
+}
+{
+"classname" "item_health"
+"spawnflags" "1792"
+"origin" "-240 -352 -256"
+}
+{
+"classname" "item_armor_jacket"
+"spawnflags" "1792"
+"origin" "-240 -416 -256"
+}
+{
+"classname" "weapon_railgun"
+"spawnflags" "5888"
+"origin" "-672 -736 -240"
+}
+{
+"classname" "ammo_slugs"
+"origin" "-744 -736 -240"
+}
+{
+"classname" "info_player_deathmatch"
+"origin" "-352 -416 -248"
+"angle" "180"
+}
+{
+"classname" "target_help"
+"targetname" "t397"
+"origin" "-152 1088 664"
+"message" "Destroy remaining resistance"
+}
+{
+"model" "*9"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t397"
+}
+{
+"classname" "target_help"
+"message" "Use fighter lift to\nenter upper levels."
+"targetname" "jam"
+"origin" "-192 1776 136"
+}
+{
+"classname" "target_goal"
+"targetname" "jam"
+"origin" "-168 1768 120"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "4"
+"attenuation" "-1"
+"noise" "world/yelforce.wav"
+"targetname" "jam"
+"origin" "-152 1768 120"
+}
+{
+"classname" "target_help"
+"origin" "168 2696 -104"
+"targetname" "t218"
+"spawnflags" "1"
+"message" "Gain access to\nResearch Hangar"
+}
+{
+"classname" "target_help"
+"message" "Return to sewer via pipe."
+"targetname" "t311"
+"origin" "-720 1912 184"
+}
+{
+"classname" "target_goal"
+"targetname" "t311"
+"origin" "-784 1944 168"
+}
+{
+"classname" "target_help"
+"origin" "48 -168 344"
+"spawnflags" "1"
+"message" "Gain entrance to waste\ndisposal control."
+"targetname" "t11help" // b#2: t11 -> t11help
+}
+{
+"model" "*10"
+"classname" "trigger_once"
+"target" "t315"
+"targetname" "t301"
+"spawnflags" "2052"
+}
+{
+"classname" "trigger_always"
+"target" "t386"
+"origin" "-448 392 464"
+}
+{
+"classname" "trigger_always"
+"spawnflags" "1792"
+"target" "t385"
+"origin" "832 968 88"
+}
+{
+"model" "*11"
+"classname" "func_train"
+"_minlight" ".2"
+"dmg" "1000"
+"spawnflags" "2"
+"target" "t40"
+"targetname" "t43"
+}
+{
+"classname" "trigger_always"
+"spawnflags" "1792"
+"target" "t395"
+"origin" "-752 -128 -16"
+}
+{
+"model" "*12"
+"classname" "func_button"
+"angle" "90"
+"target" "t395"
+}
+{
+"delay" "1"
+"classname" "trigger_relay"
+"killtarget" "t392"
+"targetname" "t302"
+"spawnflags" "2048"
+"origin" "512 -296 272"
+}
+{
+"classname" "trigger_relay"
+"spawnflags" "2048"
+"targetname" "t301"
+"target" "t392"
+"origin" "424 -288 272"
+}
+{
+"origin" "4000 -4068 -4068"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"origin" "-584 320 576"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-32 2312 80"
+"light" "65"
+"classname" "light"
+}
+{
+"targetname" "t389"
+"origin" "-928 656 656"
+"classname" "point_combat"
+}
+{
+"origin" "1000 1456 272"
+"spawnflags" "1792"
+"target" "t265"
+"classname" "trigger_always"
+}
+{
+"model" "*13"
+"angle" "270"
+"target" "t385"
+"wait" "4"
+"classname" "func_button"
+}
+{
+"targetname" "iliketrafficlights"
+"noise" "world/lite_on3.wav"
+"origin" "760 880 56"
+"classname" "target_speaker"
+}
+{
+"delay" "1"
+"origin" "1080 984 120"
+"targetname" "iliketrafficlights"
+"target" "t43"
+"classname" "trigger_relay"
+}
+{
+"origin" "1072 776 376"
+"targetname" "t374"
+"target" "t310"
+"spawnflags" "1"
+"classname" "monster_infantry"
+}
+{
+"origin" "-120 -336 -168"
+"message" "You found a secret." // b#3: moved here
+"targetname" "t384"
+"classname" "target_secret"
+}
+{
+"model" "*14"
+"target" "t384"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"model" "*15"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"origin" "-88 -352 -160"
+"spawnflags" "2048"
+"classname" "item_pack"
+}
+{
+"model" "*16"
+"spawnflags" "2048"
+"health" "50"
+"dmg" "10"
+"classname" "func_explosive"
+}
+{
+"model" "*17"
+"spawnflags" "2048"
+"target" "t383"
+"classname" "trigger_once"
+}
+{
+"style" "32"
+"origin" "984 -400 360"
+"spawnflags" "1"
+"targetname" "t302"
+"light" "65"
+"classname" "light"
+}
+{
+"model" "*18"
+"spawnflags" "3072"
+"classname" "func_wall"
+}
+{
+"classname" "point_combat"
+"targetname" "t375"
+"origin" "648 2344 -112"
+"target" "t376"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "1"
+"target" "t375"
+"targetname" "t218"
+"origin" "640 2656 -40"
+}
+{
+"classname" "trigger_relay"
+"killtarget" "t372"
+"delay" "6"
+"targetname" "t373"
+"origin" "1400 840 288"
+}
+{
+"classname" "target_anger"
+"killtarget" "t372"
+"targetname" "t373"
+"target" "t374"
+"origin" "1376 824 288"
+}
+{
+"model" "*19"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t373"
+}
+{
+"model" "*20"
+"classname" "func_train"
+"spawnflags" "2049"
+"team" "manlove"
+"target" "t371"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"origin" "1456 728 336"
+"target" "t370"
+"targetname" "t371"
+}
+{
+"classname" "info_notnull"
+"team" "manlove"
+"origin" "1268 752 372"
+"targetname" "t372"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t89"
+"target" "t369"
+"origin" "-744 744 616"
+}
+{
+"delay" "2"
+"classname" "target_speaker"
+"noise" "world/lasburn1.wav"
+"targetname" "t369"
+"spawnflags" "2050"
+"origin" "-704 704 568"
+}
+{
+"model" "*21"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "t218"
+}
+{
+"classname" "target_spawner"
+"target" "item_double"
+"spawnflags" "2048"
+"targetname" "t300"
+"origin" "-416 120 -40"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1656 1408 960"
+}
+{
+"classname" "light"
+"_color" "1.000000 1.000000 0.501961"
+"light" "80"
+"origin" "-1280 1472 832"
+}
+{
+"target" "t388"
+"classname" "monster_berserk"
+"spawnflags" "257"
+"angle" "270"
+"origin" "-960 816 672"
+"targetname" "t368"
+}
+{
+"item" "ammo_rockets"
+"classname" "monster_turret"
+"spawnflags" "32"
+"angle" "-2"
+"target" "t368"
+"origin" "-960 704 896"
+}
+{
+"model" "*22"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t367"
+}
+{
+"classname" "monster_berserk"
+"spawnflags" "1"
+"target" "t365"
+"angle" "270"
+"origin" "-384 1176 672"
+"targetname" "t367"
+}
+{
+"classname" "trigger_relay"
+"spawnflags" "2048"
+"target" "t363"
+"origin" "-344 1176 696"
+"targetname" "t365"
+}
+{
+"model" "*23"
+"classname" "func_explosive"
+"targetname" "t363"
+}
+{
+"classname" "monster_turret"
+"origin" "-384 1248 704"
+"spawnflags" "904"
+"angle" "270"
+"targetname" "t363"
+}
+{
+"classname" "item_adrenaline"
+"origin" "-320 1000 544"
+}
+{
+"target" "t387"
+"classname" "point_combat"
+"spawnflags" "0"
+"targetname" "t362"
+"origin" "-608 960 536"
+}
+{
+"classname" "monster_gunner"
+"angle" "270"
+"spawnflags" "1"
+"target" "t362"
+"origin" "-368 912 552"
+}
+{
+"classname" "light"
+"_color" "1.000000 1.000000 0.501961"
+"light" "70"
+"origin" "-264 768 536"
+}
+{
+"model" "*24"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "t293"
+}
+{
+"classname" "monster_berserk"
+"angle" "180"
+"spawnflags" "769"
+"origin" "-288 480 552"
+}
+{
+"style" "33"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "125"
+"origin" "-312 832 608"
+"targetname" "t293"
+}
+{
+"classname" "target_speaker"
+"origin" "-360 832 568"
+"noise" "world/l_hum2.wav"
+"spawnflags" "2049"
+"targetname" "t293"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "136"
+"angle" "0"
+"origin" "-680 2208 168"
+"targetname" "t218"
+}
+{
+"model" "*25"
+"classname" "func_explosive"
+"targetname" "t218"
+}
+{
+"classname" "point_combat"
+"targetname" "t346"
+"origin" "88 2176 104"
+"spawnflags" "1"
+}
+{
+"classname" "point_combat"
+"targetname" "t345"
+"spawnflags" "1"
+"origin" "-56 1896 88"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-320 -8 64"
+}
+{
+"target" "t331"
+"origin" "456 528 288"
+"angle" "180"
+"spawnflags" "260"
+"classname" "monster_berserk"
+}
+{
+"spawnflags" "0"
+"origin" "-64 440 272"
+"targetname" "t326"
+"classname" "point_combat"
+}
+{
+"item" "ammo_grenades"
+"origin" "880 1104 376"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_gunner"
+"targetname" "t396"
+}
+{
+"origin" "1312 864 240"
+"spawnflags" "8"
+"angle" "-2"
+"classname" "monster_turret"
+"target" "t385"
+}
+{
+"origin" "680 992 160"
+"spawnflags" "4"
+"angle" "0"
+"classname" "monster_floater"
+}
+{
+"origin" "672 768 160"
+"spawnflags" "4"
+"angle" "0"
+"classname" "monster_floater"
+}
+{
+"model" "*26"
+"spawnflags" "1"
+"classname" "func_plat2"
+"lip" "16"
+"_minlight" ".1"
+"targetname" "t395"
+}
+{
+"delay" "1"
+"target" "t325"
+"targetname" "t312"
+"origin" "-104 -1408 696"
+"classname" "trigger_relay"
+}
+{
+"item" "ammo_cells"
+"angle" "180"
+"origin" "-512 704 800"
+"spawnflags" "264"
+"classname" "monster_turret"
+}
+{
+"origin" "-1000 1144 1032"
+"angle" "135"
+"spawnflags" "800"
+"classname" "monster_turret"
+}
+{
+"spawnflags" "1"
+"targetname" "t321"
+"origin" "-536 1392 272"
+"classname" "point_combat"
+}
+{
+"target" "t321"
+"origin" "-408 1272 288"
+"angle" "180"
+"spawnflags" "5"
+"classname" "monster_gunner"
+"targetname" "t311"
+}
+{
+"target" "t318"
+"targetname" "t317"
+"origin" "-128 -840 704"
+"classname" "path_corner"
+}
+{
+"targetname" "t325"
+"target" "t317"
+"spawnflags" "2"
+"angle" "90"
+"origin" "-128 -1448 720"
+"classname" "monster_daedalus"
+}
+{
+"origin" "-448 328 320"
+"light" "75"
+"classname" "light"
+}
+{
+"spawnflags" "8"
+"angle" "270"
+"origin" "-576 1528 416"
+"classname" "monster_turret"
+}
+{
+"model" "*27"
+"lip" "24"
+"wait" "-1"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "t311" // b#1: t332 -> t311
+}
+{
+"spawnflags" "4"
+"classname" "monster_gunner"
+"angle" "270"
+"origin" "-448 1024 288"
+}
+{
+"model" "*28"
+"classname" "func_explosive"
+"targetname" "t315"
+}
+{
+"model" "*29"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t314"
+}
+{
+"model" "*30"
+"classname" "func_explosive"
+"targetname" "t314"
+}
+{
+"classname" "monster_turret"
+"origin" "-296 640 416"
+"targetname" "t314"
+"angle" "0"
+"spawnflags" "136"
+}
+{
+"classname" "info_player_start"
+"origin" "-352 -224 -360"
+}
+{
+"classname" "target_changelevel"
+"targetname" "t313"
+"spawnflags" "2048"
+"map" "rsewer2$rhangar2a"
+"origin" "-336 -608 -368"
+}
+{
+"model" "*31"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+"target" "t313"
+}
+{
+"targetname" "rsewer2a"
+"origin" "-472 -144 -24"
+"angle" "45"
+"classname" "info_player_coop"
+}
+{
+"item" "ammo_bullets"
+"targetname" "t383"
+"spawnflags" "1"
+"origin" "72 208 280"
+"angle" "90"
+"classname" "monster_infantry"
+}
+{
+"origin" "-32 928 664"
+"targetname" "hobbit"
+"spawnflags" "1537"
+"angle" "270"
+"classname" "monster_medic"
+}
+{
+"origin" "-32 848 664"
+"targetname" "hobbit"
+"spawnflags" "257"
+"angle" "270"
+"classname" "monster_medic_commander"
+}
+{
+"origin" "-800 1880 168"
+"targetname" "t311"
+"spawnflags" "2049"
+"classname" "target_crosslevel_trigger"
+}
+{
+"origin" "-824 1904 168"
+"target" "t312"
+"message" "Primary Entrance Door open!"
+"targetname" "t311"
+"classname" "trigger_relay"
+}
+{
+"model" "*32"
+"targetname" "t312"
+"classname" "func_explosive"
+}
+{
+"targetname" "t312"
+"origin" "224 800 552"
+"angle" "-2"
+"spawnflags" "136"
+"classname" "monster_turret"
+}
+{
+"model" "*33"
+"targetname" "t265"
+"classname" "func_explosive"
+}
+{
+"origin" "1136 872 360"
+"spawnflags" "1"
+"targetname" "t310"
+"classname" "point_combat"
+}
+{
+"model" "*34"
+"spawnflags" "2048"
+"target" "t309"
+"classname" "trigger_once"
+}
+{
+"spawnflags" "1"
+"targetname" "t308"
+"origin" "864 968 360"
+"classname" "point_combat"
+}
+{
+"targetname" "t309"
+"target" "t308"
+"origin" "672 800 376"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_gunner"
+}
+{
+"targetname" "t303"
+"origin" "1000 1552 272"
+"classname" "point_combat"
+}
+{
+"target" "t305"
+"origin" "1344 1136 288"
+"angle" "270"
+"spawnflags" "4"
+"classname" "monster_gunner"
+"item" "ammo_bullets"
+}
+{
+"targetname" "t265"
+"origin" "1120 1504 552"
+"spawnflags" "136"
+"angle" "-2"
+"classname" "monster_turret"
+"item" "ammo_cells"
+}
+{
+"origin" "1336 1094 438"
+"angle" "-2"
+"spawnflags" "264"
+"classname" "monster_turret"
+"item" "ammo_cells"
+}
+{
+"origin" "-88 -328 128"
+"killtarget" "donna"
+"targetname" "t11"
+"classname" "trigger_relay"
+}
+{
+"origin" "152 -408 128"
+"targetname" "donna"
+"target" "t301"
+"classname" "trigger_always"
+}
+{
+"model" "*35"
+"target" "t302"
+"targetname" "t392"
+"spawnflags" "2312"
+"classname" "trigger_multiple"
+}
+{
+"origin" "-416 124 -56"
+"targetname" "t300"
+"spawnflags" "2"
+"angle" "300"
+"classname" "monster_mutant"
+}
+{
+"origin" "-328 2152 -8"
+"targetname" "t300"
+"classname" "target_secret"
+"message" "You released the Mutant!" // b#3: moved this here
+}
+{
+"origin" "-408 8 16"
+"target" "t300"
+"targetname" "t299"
+"classname" "trigger_relay"
+"spawnflags" "7936" // b#3: added this
+}
+{
+"model" "*36"
+"mass" "800"
+"dmg" "1"
+"health" "101"
+"target" "t300" // b#3: t299 -> t300
+"targetname" "t218"
+"spawnflags" "2057"
+"classname" "func_explosive"
+}
+{
+"origin" "-392 2248 104"
+"killtarget" "t298"
+"targetname" "mhowell"
+"classname" "trigger_relay"
+}
+{
+"model" "*37"
+"targetname" "t298"
+"message" "Locked!"
+"wait" "7"
+"spawnflags" "2048"
+"classname" "trigger_multiple"
+}
+{
+"origin" "-160 2704 -168"
+"angle" "270"
+"targetname" "rsewer2b"
+"classname" "info_player_coop"
+}
+{
+"origin" "-24 2872 -168"
+"angle" "270"
+"targetname" "rsewer2b"
+"classname" "info_player_start"
+}
+{
+"model" "*38"
+"spawnflags" "2048"
+"target" "t297"
+"classname" "trigger_multiple"
+}
+{
+"spawnflags" "2048"
+"targetname" "t297"
+"map" "rsewer2$rhangar2b"
+"origin" "-8 2832 -160"
+"classname" "target_changelevel"
+}
+{
+"origin" "184 2160 -40"
+"_color" "1.000000 1.000000 0.501961"
+"light" "80"
+"classname" "light"
+}
+{
+"model" "*39"
+"wait" "-1"
+"spawnflags" "2048"
+"target" "mhowell"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"model" "*40"
+"target" "jam"
+"wait" "-1"
+"message" "Force fields deactivated."
+"spawnflags" "2048"
+"angle" "270"
+"classname" "func_button"
+}
+{
+"origin" "-272 704 672"
+"targetname" "t125"
+"spawnflags" "2"
+"angle" "180"
+"classname" "monster_gunner"
+"target" "t352"
+}
+{
+"model" "*41"
+"message" "Forcefield Deactivated"
+"wait" "-1"
+"target" "t120"
+"spawnflags" "2048"
+"angle" "0"
+"classname" "func_button"
+"_minlight" ".2"
+}
+{
+"targetname" "t237"
+"spawnflags" "770"
+"origin" "-1280 1880 920"
+"angle" "270"
+"classname" "monster_medic_commander"
+"deathtarget" "porkin"
+}
+{
+"origin" "-1308 1692 1028"
+"targetname" "t294"
+"target" "t237"
+"spawnflags" "2048"
+"delay" "1"
+"classname" "trigger_relay"
+}
+{
+"targetname" "t294"
+"origin" "-1280 1408 1064"
+"angle" "-2"
+"spawnflags" "128"
+"classname" "monster_turret"
+}
+{
+"origin" "-1128 1440 1064"
+"light" "140"
+"classname" "light"
+}
+{
+"model" "*42"
+"classname" "func_button"
+"angle" "0"
+"spawnflags" "0"
+"target" "t293"
+}
+{
+"dmg" "2000"
+"classname" "target_laser"
+"spawnflags" "2057"
+"angle" "180"
+"origin" "-281 832 564"
+"targetname" "t293"
+}
+{
+"map" "rhangar1$rhangar2"
+"origin" "-1224 1968 904"
+"targetname" "t292"
+"classname" "target_changelevel"
+}
+{
+"model" "*43"
+"angle" "90"
+"target" "t292"
+"spawnflags" "2048"
+"classname" "trigger_multiple"
+}
+{
+"origin" "-640 640 808"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-688 704 672"
+"spawnflags" "1"
+"classname" "point_combat"
+"targetname" "t352"
+}
+{
+"targetname" "t89"
+"origin" "-744 696 528"
+"spawnflags" "2048"
+"target" "t290"
+"classname" "func_timer"
+"wait" ".1"
+}
+{
+"model" "*44"
+"targetname" "t89"
+"target" "t288"
+"speed" "20"
+"spawnflags" "2048"
+"team" "bff"
+"classname" "func_train"
+}
+{
+"spawnflags" "2048"
+"targetname" "t288"
+"target" "t285"
+"origin" "-680 656 528"
+"classname" "path_corner"
+}
+{
+"team" "bff"
+"sounds" "224"
+"count" "64"
+"speed" "100"
+"spawnflags" "2048"
+"targetname" "t290"
+"origin" "-690 648 528"
+"classname" "target_steam"
+"angle" "180"
+}
+{
+"origin" "-896 704 664"
+"spawnflags" "2048"
+"classname" "item_armor_body"
+}
+{
+"model" "*45"
+"targetname" "t125"
+"wait" "-1"
+"spawnflags" "2048"
+"angle" "90"
+"classname" "func_door_secret"
+}
+{
+"model" "*46"
+"targetname" "t386"
+"spawnflags" "1"
+"lip" "16"
+"classname" "func_plat2"
+}
+{
+"model" "*47"
+"team" "dumbass"
+"targetname" "hobbit"
+"target" "t267"
+"spawnflags" "2"
+"_minlight" ".1"
+"classname" "func_train"
+}
+{
+"wait" "-1"
+"target" "t284"
+"targetname" "t283"
+"origin" "-496 635 548"
+"classname" "path_corner"
+}
+{
+"model" "*48"
+"targetname" "t276"
+"spawnflags" "2"
+"target" "t283"
+"classname" "func_train"
+}
+{
+"origin" "-464 568 544"
+"target" "t271"
+"classname" "trigger_always"
+}
+{
+"target" "t271"
+"targetname" "tpsp"
+"origin" "-96 880 672"
+"classname" "trigger_relay"
+}
+{
+"targetname" "t272"
+"team" "smartass"
+"origin" "-40 968 360"
+"spawnflags" "2"
+"noise" "world/lift1.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "296 1208 328"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "104 912 248"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "416 960 408"
+}
+{
+"classname" "point_combat"
+"targetname" "t241"
+"origin" "992 424 280"
+}
+{
+"model" "*49"
+"wait" "-1"
+"targetname" "t307"
+"classname" "func_door"
+"angle" "-1"
+}
+{
+"model" "*50"
+"wait" "-1"
+"targetname" "t307"
+"classname" "func_door"
+"angle" "-1"
+}
+{
+"model" "*51"
+"wait" "-1"
+"targetname" "t307"
+"classname" "func_door"
+"angle" "-1"
+}
+{
+"style" "1"
+"targetname" "t81"
+"classname" "func_areaportal"
+}
+{
+"model" "*52"
+"wait" "-1"
+"targetname" "t302"
+"angle" "-1"
+"classname" "func_door"
+"sounds" "1"
+}
+{
+"model" "*53"
+"wait" "-1"
+"targetname" "t302"
+"angle" "-1"
+"classname" "func_door"
+"sounds" "1"
+}
+{
+"model" "*54"
+"wait" "-1"
+"targetname" "t302"
+"angle" "-1"
+"classname" "func_door"
+"sounds" "1"
+}
+{
+"targetname" "t302"
+"angle" "90"
+"spawnflags" "416"
+"origin" "984 -448 360"
+"classname" "monster_turret"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "896 712 416"
+}
+{
+"model" "*55"
+"wait" "-1"
+"spawnflags" "2048"
+"angle" "180"
+"classname" "func_button"
+"target" "t265"
+"message" "Security and Access Doors Unlocked!"
+}
+{
+"origin" "608 744 396"
+"targetname" "t260"
+"classname" "info_null"
+}
+{
+"target" "t260"
+"origin" "608 796 506"
+"light" "240"
+"classname" "light"
+"_cone" "15"
+}
+{
+"origin" "608 744 480"
+"light" "80"
+"classname" "light"
+}
+{
+"origin" "632 768 416"
+"wait" ".5"
+"target" "t259"
+"spawnflags" "1"
+"classname" "func_timer"
+}
+{
+"style" "34"
+"light" "60"
+"targetname" "t259"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "592 712 416"
+"classname" "light"
+}
+{
+"origin" "992 1504 496"
+"light" "110"
+"classname" "light"
+}
+{
+"origin" "832 1088 184"
+"light" "110"
+"classname" "light"
+}
+{
+"model" "*56"
+"targetname" "t385"
+"lip" "16"
+"spawnflags" "1"
+"classname" "func_plat2"
+}
+{
+"origin" "1344 708 376"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "824 560 344"
+"_color" "1.000000 1.000000 0.000000"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "1024 672 320"
+"light" "120"
+"classname" "light"
+}
+{
+"origin" "1056 312 352"
+"light" "110"
+"classname" "light"
+}
+{
+"light" "100"
+"classname" "light"
+"_color" "1.000000 0.913725 0.615686"
+"origin" "-848 -160 48"
+}
+{
+"origin" "-480 -232 -272"
+"classname" "misc_explobox"
+"spawnflags" "2048"
+}
+{
+"model" "*57"
+"team" "nuk"
+"targetname" "t11"
+"target" "t10"
+"spawnflags" "2048"
+"classname" "func_train"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "448 320 280"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "0"
+"origin" "-800 -552 -232"
+}
+{
+"origin" "-128 1080 288"
+"targetname" "t257"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"model" "*58"
+"targetname" "t202"
+"spawnflags" "2050"
+"classname" "trigger_hurt"
+}
+{
+"classname" "trigger_relay"
+"target" "jimm"
+"spawnflags" "2048"
+"killtarget" "jimm"
+"targetname" "t240"
+"origin" "480 728 288"
+}
+{
+"classname" "info_notnull"
+"targetname" "jimm"
+"spawnflags" "2048"
+"killtarget" "jimm"
+"origin" "436 788 268"
+}
+{
+"item" "ammo_grenades"
+"spawnflags" "773"
+"origin" "-1448 1568 816"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"origin" "-1320 1768 904"
+"target" "t235"
+"targetname" "t237"
+"classname" "trigger_relay"
+}
+{
+"target" "t398"
+"deathtarget" "t294"
+"spawnflags" "5"
+"origin" "-1280 1728 920"
+"angle" "270"
+"classname" "monster_medic_commander"
+}
+{
+"origin" "-1280 1880 920"
+"targetname" "t237"
+"spawnflags" "1026"
+"angle" "270"
+"classname" "monster_medic"
+"deathtarget" "porkin"
+}
+{
+"style" "2"
+"targetname" "t236"
+"classname" "func_areaportal"
+}
+{
+"model" "*59"
+"targetname" "t237"
+"spawnflags" "9"
+"target" "t235"
+"classname" "trigger_multiple"
+}
+{
+"model" "*60"
+"target" "t236"
+"targetname" "t235"
+"angle" "-1"
+"classname" "func_door"
+}
+{
+"item" "ammo_bullets"
+"targetname" "t89"
+"spawnflags" "4"
+"angle" "180"
+"origin" "-672 824 864"
+"classname" "monster_infantry"
+}
+{
+"origin" "-640 704 552"
+"targetname" "t114"
+"spawnflags" "2"
+"angle" "180"
+"classname" "monster_gladiator"
+}
+{
+"spawnflags" "13"
+"angle" "180"
+"origin" "112 960 800"
+"classname" "monster_stalker"
+}
+{
+"model" "*61"
+"target" "t234"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "488 840 280"
+"targetname" "arm"
+"classname" "item_armor_combat"
+}
+{
+"model" "*62"
+"killtarget" "arm"
+"spawnflags" "2048"
+"mass" "1"
+"classname" "func_explosive"
+"target" "t240"
+}
+{
+"origin" "432 976 264"
+"classname" "misc_explobox"
+}
+{
+"target" "t257"
+"origin" "-144 1152 304"
+"angle" "270"
+"targetname" "t233"
+"classname" "monster_gladiator"
+"item" "ammo_slugs"
+}
+{
+"targetname" "t234"
+"killtarget" "jimm"
+"origin" "-40 1168 288"
+"target" "t233"
+"classname" "target_anger"
+}
+{
+"model" "*63"
+"target" "t231"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"model" "*64"
+"target" "t229"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"targetname" "t228"
+"origin" "192 -8 304"
+"classname" "point_combat"
+"spawnflags" "1"
+}
+{
+"angle" "270"
+"targetname" "t229"
+"target" "t228"
+"spawnflags" "1"
+"origin" "224 232 296"
+"classname" "monster_gladiator"
+"item" "ammo_slugs"
+}
+{
+"targetname" "t224"
+"origin" "-248 -528 -216"
+"angle" "180"
+"spawnflags" "269"
+"classname" "monster_stalker"
+}
+{
+"origin" "-800 -552 -216"
+"target" "t224"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"origin" "-200 2600 -144"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "184 1312 312"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-296 1832 -112"
+"targetname" "t202"
+"spawnflags" "2"
+"noise" "world/amb6.wav"
+"classname" "target_speaker"
+}
+{
+"delay" ".4"
+"targetname" "watergo"
+"origin" "0 2104 -32"
+"target" "t222"
+"classname" "trigger_relay"
+}
+{
+"targetname" "t222"
+"origin" "288 1888 -16"
+"spawnflags" "2054"
+"noise" "world/curnt1.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "t164"
+"spawnflags" "2048"
+"target" "t221"
+"origin" "104 2064 -32"
+"classname" "trigger_relay"
+}
+{
+"targetname" "t221"
+"origin" "160 2136 -32"
+"noise" "world/mach2.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "arc"
+"spawnflags" "2050"
+"origin" "424 1904 -32"
+"noise" "world/l_hum1.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-32 2368 216"
+"angle" "270"
+"spawnflags" "257"
+"classname" "monster_stalker"
+}
+{
+"item" "ammo_grenades"
+"origin" "-128 1840 104"
+"angle" "45"
+"classname" "monster_gunner"
+"spawnflags" "1"
+"target" "t345"
+}
+{
+"item" "ammo_slugs"
+"targetname" "t218"
+"origin" "584 2256 -104"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_gladiator"
+}
+{
+"model" "*65"
+"spawnflags" "2048"
+"target" "t218"
+"classname" "trigger_once"
+}
+{
+"origin" "520 2232 40"
+"angle" "315"
+"spawnflags" "9"
+"classname" "monster_stalker"
+}
+{
+"origin" "-808 1816 152"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"model" "*66"
+"spawnflags" "2048"
+"target" "t213"
+"wait" "6"
+"angle" "0"
+"classname" "func_button"
+}
+{
+"model" "*67"
+"targetname" "t213"
+"lip" "128"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"model" "*68"
+"target" "t210"
+"wait" "-1"
+"classname" "func_button"
+"angle" "90"
+}
+{
+"model" "*69"
+"lip" "16"
+"targetname" "t210"
+"spawnflags" "1"
+"classname" "func_plat2"
+}
+{
+"target" "t209"
+"targetname" "cup"
+"origin" "-408 2112 -24"
+"classname" "trigger_relay"
+}
+{
+"model" "*70"
+"_minlight" ".2"
+"target" "t208"
+"wait" "-1"
+"health" "1"
+"angle" "90"
+"spawnflags" "2048"
+"classname" "func_button"
+}
+{
+"targetname" "t208"
+"origin" "-328 2152 -8"
+"classname" "target_secret"
+"message" "Something just fell in a container." // b#3: moved here
+}
+{
+"model" "*71"
+"origin" "-428 2140 -28"
+"targetname" "t209"
+"sounds" "1"
+"distance" "90"
+"spawnflags" "2209"
+"classname" "func_door_rotating"
+}
+{
+"targetname" "t208"
+"origin" "-328 2136 -8"
+"spawnflags" "2048"
+"target" "item_invulnerability"
+"classname" "target_spawner"
+}
+{
+"pathtarget" "t219"
+"wait" "1"
+"speed" "300"
+"targetname" "t205"
+"target" "t204"
+"origin" "560 1688 0"
+"classname" "path_corner"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t160"
+"target" "t165"
+"origin" "256 1688 -16"
+}
+{
+"classname" "trigger_relay"
+"targetname" "topgo"
+"target" "t165"
+"origin" "240 1688 -16"
+}
+{
+"model" "*72"
+"classname" "trigger_hurt"
+"spawnflags" "2059"
+"dmg" "50"
+"targetname" "t165"
+}
+{
+"classname" "trigger_relay"
+"target" "t164"
+"delay" "6"
+"targetname" "watergo"
+"origin" "80 1760 -128"
+}
+{
+"model" "*73"
+"_minlight" ".1"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "t164"
+"target" "t163"
+}
+{
+"classname" "path_corner"
+"origin" "216 2088 -16"
+"target" "t161"
+"targetname" "t163"
+"wait" "-1"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t160"
+"origin" "400 1704 0"
+"target" "arc"
+}
+{
+"classname" "trigger_relay"
+"origin" "336 1944 8"
+"targetname" "t149"
+"target" "t150"
+"delay" ".5"
+}
+{
+"classname" "path_corner"
+"origin" "204 1672 0"
+"targetname" "t147"
+"target" "t148"
+"pathtarget" "zoiks"
+"wait" "-1"
+}
+{
+"classname" "trigger_relay"
+"origin" "32 1760 -128"
+"targetname" "watergo"
+"target" "fillw"
+"delay" ".4"
+}
+{
+"classname" "trigger_relay"
+"targetname" "sparky"
+"origin" "408 1952 8"
+"target" "t149"
+}
+{
+"classname" "path_corner"
+"origin" "224 1728 -16"
+"targetname" "t143"
+"wait" "-1"
+"pathtarget" "topgo"
+"target" "t183"
+}
+{
+"classname" "path_corner"
+"origin" "-96 1728 -128"
+"target" "t141"
+"wait" "-1"
+"targetname" "t174"
+}
+{
+"model" "*74"
+"_minlight" ".1"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "cup"
+"target" "t174"
+"team" "argh5"
+}
+{
+"model" "*75"
+"_minlight" ".1"
+"classname" "func_train"
+"spawnflags" "2050"
+"target" "t146"
+"targetname" "lid"
+}
+{
+"origin" "-960 448 660"
+"light" "85"
+"classname" "light"
+}
+{
+"model" "*76"
+"targetname" "t127"
+"accel" "20"
+"lip" "24"
+"spawnflags" "1"
+"classname" "func_plat2"
+"_minlight" ".1"
+}
+{
+"model" "*77"
+"targetname" "jam"
+"spawnflags" "2054"
+"classname" "func_wall"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-384 704 800"
+}
+{
+"model" "*78"
+"team" "nascar"
+"angle" "-1"
+"target" "t137"
+"wait" "1"
+"classname" "func_door"
+}
+{
+"model" "*79"
+"target" "t127"
+"wait" "-1"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"item" "ammo_cells"
+"targetname" "t126"
+"origin" "-896 946 576"
+"spawnflags" "136"
+"angle" "270"
+"classname" "monster_turret"
+}
+{
+"model" "*80"
+"target" "t125"
+"classname" "trigger_once"
+}
+{
+"model" "*81"
+"targetname" "t125"
+"spawnflags" "2048"
+"wait" "-1"
+"lip" "-118"
+"angle" "-2"
+"classname" "func_door"
+"delay" "1"
+}
+{
+"classname" "trigger_relay"
+"delay" "4"
+"origin" "-800 632 712"
+"target" "t89"
+"spawnflags" "2048"
+"targetname" "t119"
+}
+{
+"model" "*82"
+"dmg" "20"
+"mass" "800"
+"classname" "func_explosive"
+"targetname" "t114"
+"spawnflags" "2048"
+"target" "t369"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2049"
+"noise" "world/force1.wav"
+"targetname" "jam"
+"origin" "-448 536 320"
+}
+{
+"classname" "target_lightramp"
+"speed" ".5"
+"message" "za"
+"spawnflags" "2049"
+"target" "t85"
+"origin" "-448 568 288"
+"targetname" "jam"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "-448 544 272"
+"targetname" "t85"
+}
+{
+"model" "*83"
+"target" "t84"
+"classname" "func_door"
+"angle" "-1"
+"wait" "1"
+}
+{
+"style" "3"
+"targetname" "t84"
+"classname" "func_areaportal"
+}
+{
+"targetname" "t275"
+"team" "wiseass"
+"origin" "-104 832 672"
+"attenuation" "1"
+"noise" "switches/butn2.wav"
+"classname" "target_speaker"
+}
+{
+"model" "*84"
+"target" "tpgo"
+"targetname" "t271"
+"wait" "1"
+"spawnflags" "12"
+"classname" "trigger_multiple"
+}
+{
+"model" "*85"
+"target" "t277"
+"targetname" "t276"
+"team" "wiseass"
+"spawnflags" "2"
+"classname" "func_train"
+}
+{
+"model" "*86"
+"target" "iliketrafficlights"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"model" "*87"
+"target" "iliketrafficlights"
+"wait" "10"
+"angle" "270"
+"classname" "func_button"
+}
+{
+"classname" "item_health_small"
+"origin" "1184 864 60"
+}
+{
+"speed" "50"
+"wait" "-1"
+"targetname" "t42"
+"target" "t39"
+"origin" "640 800 32"
+"classname" "path_corner"
+}
+{
+"model" "*88"
+"targetname" "t43"
+"spawnflags" "2"
+"target" "t42"
+"classname" "func_train"
+"_minlight" ".2"
+}
+{
+"targetname" "t38"
+"noise" "world/brkglas.wav"
+"attenuation" "1"
+"origin" "1280 976 336"
+"classname" "target_speaker"
+}
+{
+"model" "*89"
+"target" "t31"
+"mass" "150"
+"health" "5"
+"classname" "func_explosive"
+}
+{
+"model" "*90"
+"origin" "992 408 565"
+"_minlight" ".1"
+"speed" "40"
+"team" "tarok"
+"distance" "16"
+"spawnflags" "130"
+"classname" "func_door_rotating"
+"target" "t81"
+}
+{
+"model" "*91"
+"targetname" "hobbit"
+"team" "smartass"
+"target" "t269"
+"spawnflags" "2"
+"speed" "50"
+"classname" "func_train"
+}
+{
+"model" "*92"
+"wait" "1"
+"angle" "-1"
+"target" "t17"
+"classname" "func_door"
+}
+{
+"style" "4"
+"targetname" "t17"
+"classname" "func_areaportal"
+}
+{
+"spawnflags" "6144"
+"speed" "300"
+"angle" "270"
+"origin" "-8 -136 320"
+"targetname" "t11"
+"target" "t10"
+"classname" "misc_strogg_ship"
+}
+{
+"spawnflags" "2048"
+"target" "t12"
+"targetname" "t9"
+"origin" "-32 -304 400"
+"classname" "path_corner"
+"speed" "350"
+}
+{
+"model" "*93"
+"targetname" "t8"
+"spawnflags" "2048"
+"target" "t7"
+"classname" "trigger_multiple"
+}
+{
+"model" "*94"
+"message" "Level 1 defences deactivated."
+"targetname" "t7"
+"lip" "16"
+"spawnflags" "2048"
+"angle" "180"
+"classname" "func_button"
+}
+{
+"origin" "-856 -600 -216"
+"targetname" "t5"
+"noise" "world/fuseout.wav"
+"attenuation" "1"
+"spawnflags" "2048"
+"classname" "target_speaker"
+}
+{
+"model" "*95"
+"killtarget" "t8"
+"target" "t5"
+"spawnflags" "2048"
+"mass" "50"
+"dmg" "10"
+"classname" "func_explosive"
+}
+{
+"origin" "-872 -616 -256"
+"spawnflags" "2048"
+"classname" "misc_explobox"
+}
+{
+"classname" "func_timer"
+"target" "t6"
+"random" ".5"
+"spawnflags" "2048"
+"targetname" "t5"
+"origin" "-840 -632 -248"
+}
+{
+"classname" "target_splash"
+"origin" "-884 -638 -248"
+"sounds" "1"
+"targetname" "t6"
+"spawnflags" "2048"
+"angle" "-2"
+}
+{
+"model" "*96"
+"targetname" "t5"
+"spawnflags" "6"
+"classname" "func_wall"
+}
+{
+"angle" "0"
+"classname" "misc_teleporter_dest"
+"spawnflags" "5888"
+"targetname" "t4"
+"origin" "-224 2528 -168"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "5888"
+"target" "t4"
+"origin" "-352 -608 -360"
+}
+{
+"classname" "func_timer"
+"target" "t2"
+"spawnflags" "1"
+"origin" "-328 -296 -224"
+}
+{
+"model" "*97"
+"classname" "trigger_push"
+"angle" "-1"
+"speed" "60"
+}
+{
+"classname" "target_steam"
+"origin" "-352 -224 -40"
+"targetname" "t2"
+"angle" "-1"
+"speed" "150"
+"count" "128"
+"sounds" "208"
+}
+{
+"model" "*98"
+"classname" "trigger_multiple"
+"target" "t1"
+}
+{
+"model" "*99"
+"origin" "-432 -248 -24"
+"_minlight" ".1"
+"classname" "func_door_rotating"
+"spawnflags" "130"
+"distance" "100"
+"speed" "300"
+"targetname" "t1"
+"wait" ".5"
+}
+{
+"classname" "target_steam"
+"origin" "-352 -224 -200"
+"targetname" "t2"
+"angle" "-1"
+"speed" "150"
+"count" "128"
+"sounds" "208"
+}
+{
+"sounds" "208"
+"count" "128"
+"speed" "150"
+"angle" "-1"
+"targetname" "t3"
+"origin" "-352 -224 -136"
+"classname" "target_steam"
+}
+{
+"classname" "target_steam"
+"origin" "-352 -224 -368"
+"targetname" "t2"
+"angle" "-1"
+"speed" "150"
+"count" "128"
+"sounds" "208"
+}
+{
+"model" "*100"
+"classname" "func_wall"
+}
+{
+"model" "*101"
+"origin" "-790 -639 -178"
+"sounds" "0"
+"classname" "func_door_rotating"
+"wait" "8"
+"distance" "45"
+"targetname" "t5"
+"spawnflags" "97"
+"speed" "55"
+"team" "tt1"
+}
+{
+"model" "*102"
+"origin" "-790 -638 -183"
+"sounds" "0"
+"classname" "func_door_rotating"
+"wait" "8"
+"distance" "60"
+"targetname" "t5"
+"spawnflags" "97"
+"speed" "65"
+"team" "tt1"
+}
+{
+"model" "*103"
+"origin" "-790 -627 -203"
+"sounds" "0"
+"classname" "func_door_rotating"
+"wait" "8"
+"distance" "120"
+"targetname" "t5"
+"spawnflags" "97"
+"speed" "100"
+"team" "tt1"
+}
+{
+"model" "*104"
+"origin" "-790 -640 -166"
+"sounds" "0"
+"classname" "func_door_rotating"
+"wait" "8"
+"distance" "10"
+"targetname" "t5"
+"spawnflags" "97"
+"team" "tt1"
+"speed" "50"
+}
+{
+"model" "*105"
+"origin" "-790 -633 -194"
+"sounds" "0"
+"classname" "func_door_rotating"
+"wait" "8"
+"distance" "90"
+"targetname" "t5"
+"spawnflags" "97"
+"speed" "80"
+"team" "tt1"
+}
+{
+"spawnflags" "2048"
+"targetname" "t10"
+"target" "t9"
+"classname" "path_corner"
+"origin" "-32 -128 376"
+"speed" "350"
+}
+{
+"spawnflags" "2048"
+"target" "t13"
+"classname" "path_corner"
+"origin" "-32 -448 488"
+"targetname" "t12"
+"speed" "350"
+}
+{
+"spawnflags" "2048"
+"target" "t14"
+"targetname" "t13"
+"origin" "-32 -656 576"
+"classname" "path_corner"
+"speed" "350"
+}
+{
+"spawnflags" "2048"
+"target" "t15"
+"classname" "path_corner"
+"origin" "-32 -960 680"
+"targetname" "t14"
+"speed" "500"
+}
+{
+"spawnflags" "2048"
+"target" "t16"
+"targetname" "t15"
+"origin" "-32 -1288 704"
+"classname" "path_corner"
+"speed" "500"
+}
+{
+"spawnflags" "2048"
+"speed" "500"
+"target" "t10"
+"classname" "path_corner"
+"origin" "-32 -2128 704"
+"targetname" "t16"
+"wait" "-1"
+}
+{
+"model" "*106"
+"origin" "992 408 565"
+"_minlight" ".1"
+"speed" "40"
+"team" "tarok"
+"classname" "func_door_rotating"
+"spawnflags" "128"
+"distance" "16"
+}
+{
+"model" "*107"
+"origin" "192 1256 573"
+"classname" "func_door_rotating"
+"spawnflags" "130"
+"distance" "16"
+"team" "mono"
+"target" "t82"
+"speed" "40"
+"_minlight" ".1"
+}
+{
+"model" "*108"
+"origin" "192 1256 573"
+"distance" "16"
+"spawnflags" "128"
+"classname" "func_door_rotating"
+"team" "mono"
+"speed" "40"
+"_minlight" ".1"
+}
+{
+"model" "*109"
+"target" "t30"
+"classname" "func_explosive"
+"mass" "150"
+"health" "5"
+}
+{
+"model" "*110"
+"target" "t33"
+"health" "5"
+"mass" "150"
+"classname" "func_explosive"
+}
+{
+"model" "*111"
+"target" "t37"
+"classname" "func_explosive"
+"mass" "150"
+"health" "5"
+}
+{
+"model" "*112"
+"target" "t32"
+"classname" "func_explosive"
+"health" "5"
+"mass" "150"
+}
+{
+"model" "*113"
+"target" "t38"
+"mass" "150"
+"health" "5"
+"classname" "func_explosive"
+}
+{
+"targetname" "t37"
+"noise" "world/brkglas.wav"
+"attenuation" "1"
+"classname" "target_speaker"
+"origin" "1280 968 320"
+}
+{
+"targetname" "t32"
+"noise" "world/brkglas.wav"
+"attenuation" "1"
+"classname" "target_speaker"
+"origin" "1280 864 336"
+}
+{
+"targetname" "t33"
+"noise" "world/brkglas.wav"
+"attenuation" "1"
+"origin" "1280 856 320"
+"classname" "target_speaker"
+}
+{
+"targetname" "t31"
+"noise" "world/brkglas.wav"
+"attenuation" "1"
+"origin" "1280 768 336"
+"classname" "target_speaker"
+}
+{
+"targetname" "t30"
+"noise" "world/brkglas.wav"
+"attenuation" "1"
+"classname" "target_speaker"
+"origin" "1280 760 320"
+}
+{
+"speed" "600"
+"wait" "1"
+"targetname" "t41"
+"target" "t40"
+"classname" "path_corner"
+"origin" "640 800 32"
+}
+{
+"pathtarget" "heybuddy"
+"speed" "50"
+"wait" "-1"
+"target" "t41"
+"targetname" "t40"
+"classname" "path_corner"
+"origin" "-168 800 32"
+}
+{
+"pathtarget" "heybuddy"
+"speed" "600"
+"wait" "1"
+"target" "t42"
+"targetname" "t39"
+"origin" "-168 800 32"
+"classname" "path_corner"
+}
+{
+"origin" "1312 864 60"
+"classname" "item_health_small"
+}
+{
+"classname" "item_health_small"
+"origin" "1440 864 60"
+}
+{
+"origin" "1056 864 60"
+"classname" "item_health_small"
+}
+{
+"model" "*114"
+"target" "btgo"
+"targetname" "t274"
+"classname" "trigger_multiple"
+"spawnflags" "12"
+"wait" "1"
+}
+{
+"style" "5"
+"targetname" "t82"
+"classname" "func_areaportal"
+}
+{
+"style" "6"
+"classname" "func_areaportal"
+"targetname" "t83"
+}
+{
+"style" "35"
+"origin" "-448 544 376"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"targetname" "t85"
+}
+{
+"style" "35"
+"origin" "-504 544 320"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"targetname" "t85"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "-392 544 320"
+"targetname" "t85"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "-504 544 376"
+"targetname" "t85"
+}
+{
+"style" "35"
+"origin" "-392 544 376"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"targetname" "t85"
+}
+{
+"style" "35"
+"origin" "-504 544 272"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"targetname" "t85"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "-392 544 272"
+"targetname" "t85"
+}
+{
+"killtarget" "t290"
+"classname" "trigger_relay"
+"origin" "-776 832 616"
+"targetname" "t113"
+"target" "t114"
+"spawnflags" "2048"
+}
+{
+"origin" "-960 464 568"
+"targetname" "t120"
+"noise" "world/force1.wav"
+"spawnflags" "2049"
+"classname" "target_speaker"
+}
+{
+"targetname" "t120"
+"origin" "-968 504 536"
+"target" "t121"
+"spawnflags" "2049"
+"message" "za"
+"speed" ".5"
+"classname" "target_lightramp"
+}
+{
+"style" "36"
+"targetname" "t121"
+"origin" "-960 480 520"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+}
+{
+"style" "36"
+"targetname" "t121"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"origin" "-960 480 624"
+}
+{
+"style" "36"
+"targetname" "t121"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"origin" "-1008 480 568"
+}
+{
+"style" "36"
+"targetname" "t121"
+"origin" "-912 480 568"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+}
+{
+"style" "36"
+"targetname" "t121"
+"origin" "-1008 480 624"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+}
+{
+"style" "36"
+"targetname" "t121"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"origin" "-912 480 624"
+}
+{
+"style" "36"
+"targetname" "t121"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"origin" "-1008 480 520"
+}
+{
+"style" "36"
+"targetname" "t121"
+"origin" "-912 480 520"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+}
+{
+"model" "*115"
+"target" "t126"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_turret"
+"angle" "90"
+"spawnflags" "136"
+"origin" "-800 462 576"
+"targetname" "t126"
+}
+{
+"style" "7"
+"targetname" "t128"
+"classname" "func_areaportal"
+}
+{
+"model" "*116"
+"origin" "-576 1152 565"
+"speed" "40"
+"target" "t128"
+"classname" "func_door_rotating"
+"spawnflags" "130"
+"distance" "16"
+"team" "humus"
+}
+{
+"model" "*117"
+"origin" "-576 1152 565"
+"speed" "40"
+"distance" "16"
+"spawnflags" "128"
+"classname" "func_door_rotating"
+"team" "humus"
+}
+{
+"origin" "-384 832 800"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-384 960 800"
+}
+{
+"origin" "-320 1056 800"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "-128 1032 736"
+}
+{
+"style" "8"
+"classname" "func_areaportal"
+"targetname" "t137"
+}
+{
+"model" "*118"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "t120"
+}
+{
+"classname" "light"
+"light" "85"
+"origin" "-960 320 660"
+}
+{
+"origin" "-832 320 660"
+"light" "85"
+"classname" "light"
+}
+{
+"classname" "target_speaker"
+"origin" "-720 832 864"
+"attenuation" "1"
+"noise" "world/brkglas.wav"
+"targetname" "t139"
+}
+{
+"targetname" "t140"
+"noise" "world/brkglas.wav"
+"attenuation" "1"
+"origin" "-728 576 864"
+"classname" "target_speaker"
+}
+{
+"origin" "224 1728 -128"
+"classname" "path_corner"
+"targetname" "t141"
+"wait" "-1"
+"pathtarget" "watergo"
+"target" "t142"
+}
+{
+"targetname" "t142"
+"classname" "path_corner"
+"origin" "544 1728 -128"
+"target" "t166"
+"wait" "-1"
+"spawnflags" "2050"
+}
+{
+"origin" "544 1728 -16"
+"classname" "path_corner"
+"targetname" "t183"
+"wait" "-1"
+"target" "t194"
+}
+{
+"classname" "path_corner"
+"origin" "224 1976 -16"
+"target" "t143"
+"targetname" "t146"
+"wait" "-1"
+"spawnflags" "0"
+}
+{
+"origin" "48 1760 -128"
+"classname" "trigger_relay"
+"targetname" "watergo"
+"target" "fullw"
+"wait" ".5"
+}
+{
+"classname" "trigger_relay"
+"origin" "64 1760 -128"
+"targetname" "watergo"
+"target" "lid"
+"delay" "6"
+}
+{
+"origin" "120 1672 -128"
+"classname" "trigger_relay"
+"target" "arc"
+"targetname" "topgo"
+}
+{
+"classname" "trigger_relay"
+"origin" "80 1704 -128"
+"targetname" "zoiks"
+"target" "sparky"
+}
+{
+"origin" "204 1672 64"
+"classname" "path_corner"
+"target" "t147"
+"targetname" "t148"
+"pathtarget" "wellduh"
+"wait" "-1"
+}
+{
+"origin" "64 1704 -128"
+"classname" "trigger_relay"
+"targetname" "zoiks"
+"target" "fullw"
+}
+{
+"classname" "trigger_relay"
+"origin" "48 1704 -128"
+"targetname" "zoiks"
+"target" "fillw"
+}
+{
+"origin" "320 1944 8"
+"classname" "trigger_relay"
+"targetname" "t153"
+"target" "t154"
+"delay" ".5"
+}
+{
+"classname" "trigger_relay"
+"origin" "304 1944 8"
+"targetname" "t157"
+"target" "t158"
+"delay" ".5"
+}
+{
+"origin" "440 1808 8"
+"classname" "trigger_relay"
+"targetname" "t152"
+"target" "t153"
+"delay" ".5"
+}
+{
+"classname" "trigger_relay"
+"origin" "440 1824 8"
+"targetname" "t156"
+"delay" ".5"
+"target" "t157"
+}
+{
+"classname" "trigger_relay"
+"origin" "304 1704 8"
+"targetname" "t151"
+"target" "t152"
+"delay" ".5"
+}
+{
+"origin" "320 1704 8"
+"classname" "trigger_relay"
+"targetname" "t155"
+"target" "t156"
+"delay" ".5"
+}
+{
+"classname" "trigger_relay"
+"origin" "336 1704 8"
+"targetname" "t159"
+"target" "t160"
+"delay" ".5"
+}
+{
+"origin" "200 1840 8"
+"classname" "trigger_relay"
+"targetname" "t150"
+"target" "t151"
+"delay" ".5"
+}
+{
+"classname" "trigger_relay"
+"origin" "200 1824 8"
+"targetname" "t154"
+"target" "t155"
+"delay" ".5"
+}
+{
+"origin" "200 1808 8"
+"classname" "trigger_relay"
+"targetname" "t158"
+"target" "t159"
+"delay" ".5"
+}
+{
+"origin" "304 1672 0"
+"targetname" "wellduh"
+"classname" "trigger_relay"
+"target" "lid"
+}
+{
+"classname" "trigger_relay"
+"targetname" "wellduh"
+"origin" "320 1672 0"
+"target" "cup"
+}
+{
+"pathtarget" "howe"
+"wait" ".2"
+"targetname" "t161"
+"target" "t163"
+"origin" "216 1840 -16"
+"classname" "path_corner"
+}
+{
+"origin" "800 1728 -128"
+"classname" "path_corner"
+"targetname" "t166"
+"target" "t172"
+}
+{
+"classname" "path_corner"
+"origin" "800 1728 -16"
+"targetname" "t191"
+"target" "t192"
+"pathtarget" "joke"
+}
+{
+"classname" "path_corner"
+"origin" "800 1728 -376"
+"targetname" "t172"
+"target" "t170"
+"speed" "300"
+}
+{
+"origin" "800 1728 -264"
+"classname" "path_corner"
+"speed" "300"
+"targetname" "t192"
+"target" "t193"
+}
+{
+"origin" "-416 2048 -376"
+"classname" "path_corner"
+"targetname" "t170"
+"spawnflags" "2048"
+"target" "t176"
+"speed" "1000"
+}
+{
+"wait" "-1"
+"targetname" "t175"
+"origin" "-416 1728 -128"
+"classname" "path_corner"
+"target" "t174"
+}
+{
+"pathtarget" "t209"
+"classname" "path_corner"
+"origin" "-416 2048 -128"
+"targetname" "t176"
+"spawnflags" "0"
+"wait" "-1"
+"target" "t175"
+"speed" "100"
+}
+{
+"model" "*119"
+"_minlight" ".1"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "cup"
+"target" "t176"
+"team" "argh4"
+}
+{
+"model" "*120"
+"_minlight" ".1"
+"target" "t177"
+"targetname" "cup"
+"spawnflags" "2051"
+"classname" "func_train"
+"team" "argh3"
+}
+{
+"origin" "-32 1792 -128"
+"classname" "path_corner"
+"target" "t141"
+"targetname" "t177"
+}
+{
+"model" "*121"
+"_minlight" ".1"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "cup"
+"target" "t175"
+"team" "argh2"
+}
+{
+"model" "*122"
+"_minlight" ".1"
+"spawnflags" "2050"
+"classname" "func_train"
+"target" "t184"
+"targetname" "lid"
+}
+{
+"model" "*123"
+"targetname" "t179"
+"dmg" "50"
+"spawnflags" "2059"
+"classname" "trigger_hurt"
+}
+{
+"model" "*124"
+"_minlight" ".1"
+"target" "t183"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "lid"
+}
+{
+"model" "*125"
+"_minlight" ".1"
+"targetname" "lid"
+"spawnflags" "2050"
+"classname" "func_train"
+"target" "t178"
+}
+{
+"wait" "-1"
+"origin" "224 1976 -32"
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t190"
+"target" "t146"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "224 1976 -33"
+"wait" "-1"
+"targetname" "t178"
+"target" "t190"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "224 1976 -48"
+"wait" "-1"
+"targetname" "t188"
+"target" "t178"
+}
+{
+"wait" "-1"
+"origin" "224 1976 -49"
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t187"
+"target" "t188"
+}
+{
+"wait" "-1"
+"origin" "224 1976 -64"
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t186"
+"target" "t187"
+}
+{
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "224 1976 -65"
+"wait" "-1"
+"targetname" "t185"
+"target" "t186"
+}
+{
+"speed" "100"
+"spawnflags" "2048"
+"classname" "path_corner"
+"origin" "224 1976 -80"
+"wait" "-1"
+"target" "t185"
+"targetname" "t195"
+}
+{
+"model" "*126"
+"_minlight" ".1"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "lid"
+"target" "t187"
+}
+{
+"model" "*127"
+"_minlight" ".1"
+"spawnflags" "2050"
+"classname" "func_train"
+"targetname" "lid"
+"target" "t185"
+}
+{
+"speed" "1000"
+"classname" "path_corner"
+"origin" "224 1976 -264"
+"targetname" "t193"
+"target" "t184"
+}
+{
+"model" "*128"
+"_minlight" ".1"
+"classname" "func_train"
+"spawnflags" "2050"
+"targetname" "cup"
+"target" "t142"
+"team" "argh1"
+}
+{
+"wait" "-1"
+"classname" "path_corner"
+"origin" "544 1728 -17"
+"target" "t191"
+"targetname" "t194"
+}
+{
+"target" "t195"
+"targetname" "t184"
+"wait" "-1"
+"origin" "224 1976 -81"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"style" "37"
+"origin" "768 1760 -104"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"origin" "768 1888 -104"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"spawnflags" "1"
+}
+{
+"targetname" "t202"
+"target" "xan"
+"classname" "target_lightramp"
+"speed" "1.5"
+"spawnflags" "2049"
+"message" "za"
+"origin" "-328 1832 -128"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "128 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "128 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "192 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "192 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "256 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "256 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "320 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "320 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "384 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "384 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "448 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "448 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "512 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "512 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "576 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "576 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "640 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "640 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "704 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "704 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "832 1888 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "832 1760 -104"
+"spawnflags" "1"
+}
+{
+"target" "t202"
+"targetname" "watergo"
+"origin" "16 1760 -128"
+"classname" "trigger_relay"
+}
+{
+"target" "t202"
+"origin" "336 1672 0"
+"targetname" "wellduh"
+"classname" "trigger_relay"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"targetname" "xan"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "0 1760 -104"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"targetname" "xan"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "0 1888 -104"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-320 1760 -104"
+"targetname" "xan"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-256 1760 -104"
+"targetname" "xan"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-192 1888 -104"
+"targetname" "xan"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-192 1760 -104"
+"targetname" "xan"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-128 1888 -104"
+"targetname" "xan"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-128 1760 -104"
+"targetname" "xan"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-64 1888 -104"
+"targetname" "xan"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "-64 1760 -104"
+"targetname" "xan"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "64 1888 -104"
+"targetname" "xan"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"style" "37"
+"spawnflags" "1"
+"origin" "64 1760 -104"
+"targetname" "xan"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "37"
+"origin" "-392 1888 -104"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"origin" "-264 1888 -104"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "-264 2208 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "-392 2208 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "-264 2144 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "-392 2144 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "-264 2080 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "-392 2080 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "-264 2016 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "-392 2016 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "-264 1952 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "-392 1952 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "xan"
+"origin" "-392 1760 -104"
+"spawnflags" "1"
+}
+{
+"style" "37"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "xan"
+"origin" "-392 1824 -104"
+"spawnflags" "1"
+}
+{
+"pathtarget" "t220"
+"speed" "30"
+"wait" "-1"
+"target" "t205"
+"targetname" "t204"
+"classname" "path_corner"
+"origin" "560 1696 104"
+}
+{
+"target" "t206"
+"delay" "5.7"
+"targetname" "watergo"
+"origin" "96 1760 -128"
+"classname" "trigger_relay"
+}
+{
+"style" "9"
+"classname" "func_areaportal"
+"targetname" "t212"
+}
+{
+"model" "*129"
+"spawnflags" "5888"
+"classname" "func_door"
+"angle" "-1"
+"speed" "300"
+"wait" "1"
+"target" "t212"
+}
+{
+"model" "*130"
+"target" "t311"
+"classname" "func_button"
+"angle" "180"
+"spawnflags" "2048"
+"lip" "16"
+"wait" "-1"
+}
+{
+"model" "*131"
+"classname" "func_wall"
+}
+{
+"targetname" "t218"
+"classname" "monster_gladiator"
+"angle" "90"
+"spawnflags" "769"
+"origin" "552 2144 -104"
+}
+{
+"classname" "monster_gunner"
+"angle" "270"
+"origin" "80 2200 120"
+"spawnflags" "257"
+"target" "t346"
+}
+{
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "416 1744 -32"
+"spawnflags" "2050"
+"targetname" "arc"
+}
+{
+"targetname" "arc"
+"spawnflags" "2050"
+"origin" "216 1736 -32"
+"noise" "world/l_hum1.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/l_hum1.wav"
+"origin" "216 1904 -32"
+"spawnflags" "2050"
+"targetname" "arc"
+}
+{
+"targetname" "t221"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/mach2.wav"
+"origin" "160 2072 -32"
+}
+{
+"targetname" "t221"
+"origin" "160 2008 -32"
+"noise" "world/mach2.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t221"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/mach2.wav"
+"origin" "488 2120 -32"
+}
+{
+"targetname" "t221"
+"origin" "488 2056 -32"
+"noise" "world/mach2.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"targetname" "t221"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/mach2.wav"
+"origin" "488 1992 -32"
+}
+{
+"targetname" "howe"
+"spawnflags" "2048"
+"target" "t221"
+"classname" "trigger_relay"
+"origin" "112 2024 -32"
+}
+{
+"targetname" "t222"
+"classname" "target_speaker"
+"noise" "world/curnt1.wav"
+"spawnflags" "2054"
+"origin" "376 1832 -32"
+}
+{
+"targetname" "t222"
+"origin" "328 1776 -32"
+"spawnflags" "2054"
+"noise" "world/curnt1.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "t164"
+"target" "t222"
+"classname" "trigger_relay"
+"origin" "-40 2080 -32"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb6.wav"
+"spawnflags" "2"
+"targetname" "t202"
+"origin" "-192 1832 -112"
+}
+{
+"origin" "-32 1832 -112"
+"targetname" "t202"
+"spawnflags" "2"
+"noise" "world/amb6.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb6.wav"
+"spawnflags" "2"
+"targetname" "t202"
+"origin" "128 1832 -112"
+}
+{
+"origin" "312 1832 -112"
+"targetname" "t202"
+"spawnflags" "2"
+"noise" "world/amb6.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb6.wav"
+"spawnflags" "2"
+"targetname" "t202"
+"origin" "512 1832 -112"
+}
+{
+"origin" "704 1832 -112"
+"targetname" "t202"
+"spawnflags" "2"
+"noise" "world/amb6.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"origin" "192 1432 312"
+}
+{
+"origin" "328 1440 312"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"origin" "512 1440 312"
+}
+{
+"origin" "672 1440 312"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"origin" "792 1440 312"
+}
+{
+"origin" "976 368 312"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"origin" "984 232 312"
+}
+{
+"origin" "984 48 312"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"origin" "984 -112 312"
+}
+{
+"origin" "984 -232 312"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"origin" "824 -256 312"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-56 2656 -144"
+}
+{
+"origin" "64 2560 -144"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "512 2576 -144"
+}
+{
+"origin" "320 2568 -144"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "648 2552 -144"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "512 2400 -72"
+}
+{
+"origin" "664 2336 -72"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-560 2168 136"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-584 1456 296"
+}
+{
+"origin" "-696 1288 296"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-448 1280 296"
+}
+{
+"origin" "-576 1072 296"
+"spawnflags" "1"
+"noise" "world/amb22.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb22.wav"
+"spawnflags" "1"
+"origin" "-704 1008 296"
+}
+{
+"origin" "-448 1008 296"
+"spawnflags" "1"
+"noise" "world/amb22.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb22.wav"
+"spawnflags" "1"
+"origin" "-448 856 296"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "-88 1112 296"
+}
+{
+"origin" "192 1112 296"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "264 864 296"
+}
+{
+"origin" "-32 840 296"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "264 672 296"
+}
+{
+"origin" "256 504 296"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "-64 504 296"
+}
+{
+"origin" "448 368 296"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "232 272 296"
+}
+{
+"origin" "-1280 1408 808"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-1480 1472 808"
+}
+{
+"origin" "-1480 1320 808"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-1096 1480 808"
+}
+{
+"origin" "-1104 1320 808"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-1296 1104 888"
+}
+{
+"origin" "-1536 1152 888"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-1616 1384 888"
+}
+{
+"origin" "-1600 1600 888"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-1312 1600 928"
+}
+{
+"origin" "-1360 1720 928"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-1160 1728 928"
+}
+{
+"origin" "-1016 1656 928"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb23.wav"
+"spawnflags" "1"
+"origin" "-960 1496 928"
+}
+{
+"origin" "-960 1280 928"
+"spawnflags" "1"
+"noise" "world/amb23.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "752 944 96"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "744 776 96"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "912 944 96"
+}
+{
+"origin" "904 776 96"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "1040 944 96"
+}
+{
+"origin" "1032 776 96"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "1200 944 96"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "1192 776 96"
+}
+{
+"origin" "1328 944 96"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "1320 776 96"
+}
+{
+"classname" "target_speaker"
+"noise" "world/amb24.wav"
+"spawnflags" "1"
+"origin" "1488 944 96"
+}
+{
+"origin" "1480 776 96"
+"spawnflags" "1"
+"noise" "world/amb24.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+"targetname" "t223"
+"origin" "-768 -424 -208"
+}
+{
+"origin" "-656 -424 -208"
+"targetname" "t223"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+"targetname" "t223"
+"origin" "-664 -688 -208"
+}
+{
+"origin" "-776 -688 -208"
+"targetname" "t223"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"targetname" "t224"
+"classname" "monster_stalker"
+"spawnflags" "781"
+"angle" "180"
+"origin" "-248 -336 -216"
+}
+{
+"angle" "180"
+"classname" "monster_gladiator"
+"origin" "856 -272 280"
+"spawnflags" "1"
+"targetname" "t229"
+"item" "ammo_slugs"
+}
+{
+"team" "nuk"
+"classname" "target_speaker"
+"origin" "-88 -280 128"
+"noise" "world/flyby3.wav"
+"targetname" "t11"
+}
+{
+"team" "nuk"
+"noise" "world/flyby3.wav"
+"origin" "-128 -120 128"
+"classname" "target_speaker"
+"targetname" "t11"
+}
+{
+"team" "nuk"
+"classname" "target_speaker"
+"origin" "-32 -208 128"
+"noise" "world/flyby3.wav"
+"targetname" "t11"
+}
+{
+"team" "nuk"
+"classname" "target_speaker"
+"origin" "-56 -408 128"
+"noise" "world/flyby3.wav"
+"targetname" "t11"
+}
+{
+"angle" "270"
+"targetname" "t231"
+"spawnflags" "1"
+"origin" "992 592 296"
+"classname" "monster_gladiator"
+"target" "t241"
+}
+{
+"classname" "misc_explobox"
+"origin" "432 896 264"
+}
+{
+"origin" "472 776 264"
+"classname" "misc_explobox"
+}
+{
+"classname" "misc_explobox"
+"origin" "432 728 264"
+}
+{
+"spawnflags" "13"
+"classname" "monster_stalker"
+"origin" "112 768 800"
+}
+{
+"item" "ammo_bullets"
+"targetname" "t89"
+"spawnflags" "4"
+"classname" "monster_infantry"
+"origin" "-680 576 864"
+"angle" "180"
+}
+{
+"item" "ammo_grenades"
+"spawnflags" "773"
+"classname" "monster_gunner"
+"angle" "270"
+"origin" "-1104 1576 816"
+}
+{
+"model" "*132"
+"targetname" "t202"
+"spawnflags" "2050"
+"classname" "trigger_hurt"
+}
+{
+"origin" "-352 -224 -360"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "1336 592 280"
+"angle" "180"
+"classname" "info_player_deathmatch"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "0"
+"origin" "696 760 88"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "1376 1440 288"
+}
+{
+"origin" "-128 1152 288"
+"angle" "315"
+"classname" "info_player_deathmatch"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "-40 208 280"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "-416 1312 288"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "225"
+"origin" "80 1112 664"
+}
+{
+"origin" "-592 896 672"
+"angle" "225"
+"classname" "info_player_deathmatch"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "225"
+"origin" "-616 1176 856"
+}
+{
+"origin" "-1440 1760 920"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"classname" "misc_explobox"
+"origin" "-240 -416 -272"
+"spawnflags" "2048"
+}
+{
+"light" "100"
+"origin" "-672 -160 48"
+"_color" "1.000000 0.913725 0.615686"
+"classname" "light"
+}
+{
+"light" "100"
+"classname" "light"
+"_color" "1.000000 0.913725 0.615686"
+"origin" "-672 -304 48"
+}
+{
+"light" "100"
+"origin" "-672 -416 48"
+"_color" "1.000000 0.913725 0.615686"
+"classname" "light"
+}
+{
+"light" "100"
+"classname" "light"
+"_color" "1.000000 0.913725 0.615686"
+"origin" "-552 -416 48"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "928 312 352"
+}
+{
+"origin" "1056 56 352"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "928 56 352"
+}
+{
+"origin" "1056 -200 352"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "792 -208 352"
+}
+{
+"origin" "792 -336 352"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "1152 672 320"
+}
+{
+"origin" "1280 520 320"
+"light" "120"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "1344 808 376"
+}
+{
+"origin" "1344 920 376"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "1344 1020 376"
+}
+{
+"classname" "light"
+"light" "150"
+"_color" "1.000000 1.000000 0.000000"
+"origin" "1060 1628 344"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "992 1376 496"
+}
+{
+"origin" "1144 1408 496"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "1312 1408 496"
+}
+{
+"origin" "1312 1248 496"
+"light" "110"
+"classname" "light"
+}
+{
+"origin" "1224 1152 320"
+"light" "120"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "1224 1280 320"
+}
+{
+"origin" "1216 1464 320"
+"light" "120"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.435294 0.435294"
+"classname" "light"
+"light" "65"
+"origin" "1120 1592 352"
+}
+{
+"origin" "1000 1592 352"
+"light" "65"
+"classname" "light"
+"_color" "1.000000 0.435294 0.435294"
+}
+{
+"style" "38"
+"targetname" "t262"
+"light" "60"
+"spawnflags" "1"
+"_color" "1.000000 0.435294 0.435294"
+"classname" "light"
+"origin" "656 712 416"
+}
+{
+"style" "34"
+"light" "60"
+"targetname" "t259"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "720 712 416"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "672 744 480"
+}
+{
+"origin" "736 744 480"
+"light" "80"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "608 712 452"
+}
+{
+"origin" "672 712 452"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "736 712 452"
+}
+{
+"target" "t261"
+"classname" "light"
+"light" "240"
+"origin" "736 796 506"
+"_cone" "15"
+}
+{
+"targetname" "t261"
+"classname" "info_null"
+"origin" "736 744 396"
+}
+{
+"target" "t262"
+"classname" "func_timer"
+"spawnflags" "1"
+"wait" ".5"
+"origin" "656 768 416"
+}
+{
+"model" "*133"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-1"
+"targetname" "t265"
+"message" "Access Door Locked"
+"wait" "-1"
+}
+{
+"model" "*134"
+"origin" "928 1440 573"
+"team" "cj"
+"distance" "16"
+"spawnflags" "12352"
+"classname" "func_door_rotating"
+"speed" "40"
+"targetname" "t265"
+}
+{
+"model" "*135"
+"origin" "928 1440 573"
+"target" "t83"
+"team" "cj"
+"classname" "func_door_rotating"
+"spawnflags" "8258"
+"distance" "16"
+"speed" "40"
+"targetname" "t265"
+"message" "Security Door Locked."
+}
+{
+"origin" "952 976 416"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "768 1016 416"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "584 976 416"
+}
+{
+"origin" "584 1232 416"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "840 1088 416"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "1144 1152 416"
+}
+{
+"_cone" "15"
+"origin" "672 796 506"
+"light" "240"
+"classname" "light"
+"target" "t266"
+}
+{
+"origin" "672 744 396"
+"classname" "info_null"
+"targetname" "t266"
+}
+{
+"classname" "light"
+"origin" "584 800 448"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "584 744 448"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "584 884 412"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "584 944 412"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "760 740 412"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "760 800 412"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"origin" "1044 1016 412"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "1104 1016 412"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "1032 992 416"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1032 872 408"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "840 768 408"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "800 840 404"
+"classname" "light"
+}
+{
+"origin" "776 1504 352"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "776 1376 352"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "520 1504 352"
+}
+{
+"origin" "520 1376 352"
+"light" "110"
+"classname" "light"
+}
+{
+"origin" "264 1504 352"
+"light" "110"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "110"
+"origin" "128 1368 352"
+}
+{
+"targetname" "t302"
+"classname" "monster_turret"
+"origin" "816 248 360"
+"spawnflags" "392"
+"angle" "0"
+}
+{
+"targetname" "t302"
+"angle" "180"
+"spawnflags" "392"
+"origin" "1168 -136 360"
+"classname" "monster_turret"
+}
+{
+"targetname" "t307"
+"angle" "0"
+"spawnflags" "136"
+"origin" "16 1432 368"
+"classname" "monster_turret"
+}
+{
+"targetname" "t307"
+"classname" "monster_turret"
+"origin" "712 1616 368"
+"spawnflags" "136"
+"angle" "270"
+}
+{
+"targetname" "t307"
+"angle" "90"
+"spawnflags" "136"
+"origin" "456 1264 368"
+"classname" "monster_turret"
+}
+{
+"origin" "416 832 408"
+"light" "80"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "416 704 408"
+}
+{
+"origin" "88 1208 328"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "304 392 328"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "176 392 328"
+}
+{
+"origin" "104 752 248"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "120 256 320"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-184 256 320"
+}
+{
+"target" "t268"
+"wait" "-1"
+"targetname" "t267"
+"origin" "-176 656 192"
+"classname" "path_corner"
+}
+{
+"target" "t267"
+"wait" "-1"
+"targetname" "t268"
+"classname" "path_corner"
+"origin" "-176 656 -192"
+}
+{
+"target" "t270"
+"wait" "-1"
+"targetname" "t269"
+"origin" "-64 704 192"
+"classname" "path_corner"
+}
+{
+"target" "t269"
+"wait" "-1"
+"targetname" "t270"
+"classname" "path_corner"
+"origin" "-64 704 0"
+}
+{
+"target" "t281"
+"wait" "-1"
+"targetname" "t280"
+"pathtarget" "btsp"
+"origin" "-124 816 272"
+"classname" "path_corner"
+}
+{
+"target" "t280"
+"targetname" "t279"
+"classname" "path_corner"
+"origin" "-128 816 272"
+}
+{
+"target" "t278"
+"wait" "-1"
+"targetname" "t277"
+"pathtarget" "tpsp"
+"origin" "-124 816 656"
+"classname" "path_corner"
+}
+{
+"targetname" "t282"
+"target" "t277"
+"origin" "-128 816 656"
+"classname" "path_corner"
+}
+{
+"target" "t279"
+"wait" ".1"
+"pathtarget" "hobbit"
+"targetname" "t278"
+"classname" "path_corner"
+"origin" "-128 816 656"
+}
+{
+"target" "t282"
+"pathtarget" "hobbit"
+"wait" ".1"
+"targetname" "t281"
+"origin" "-128 816 272"
+"classname" "path_corner"
+}
+{
+"targetname" "t273"
+"team" "smartass"
+"attenuation" "1"
+"classname" "target_speaker"
+"noise" "rotate/h_rot2.wav"
+"spawnflags" "0"
+"origin" "-24 968 360"
+}
+{
+"targetname" "t272"
+"team" "smartass"
+"classname" "target_speaker"
+"noise" "world/lift1.wav"
+"spawnflags" "2"
+"origin" "-40 696 360"
+}
+{
+"targetname" "t273"
+"team" "smartass"
+"origin" "-24 696 360"
+"spawnflags" "0"
+"noise" "rotate/h_rot2.wav"
+"classname" "target_speaker"
+"attenuation" "1"
+}
+{
+"targetname" "t272"
+"team" "dumbass"
+"classname" "target_speaker"
+"noise" "world/lift1.wav"
+"spawnflags" "2"
+"origin" "-40 968 648"
+}
+{
+"targetname" "t273"
+"team" "dumbass"
+"origin" "-24 968 648"
+"spawnflags" "0"
+"noise" "rotate/h_rot2.wav"
+"classname" "target_speaker"
+"attenuation" "1"
+}
+{
+"targetname" "t272"
+"team" "dumbass"
+"origin" "-40 696 648"
+"spawnflags" "2"
+"noise" "world/lift1.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "t273"
+"team" "dumbass"
+"attenuation" "1"
+"classname" "target_speaker"
+"noise" "rotate/h_rot2.wav"
+"spawnflags" "0"
+"origin" "-24 696 648"
+}
+{
+"target" "t271"
+"delay" ".5"
+"targetname" "tpgo"
+"classname" "trigger_relay"
+"origin" "-112 776 672"
+}
+{
+"target" "t272"
+"classname" "trigger_relay"
+"origin" "-80 880 672"
+"targetname" "tpsp"
+}
+{
+"target" "t273"
+"targetname" "tpsp"
+"origin" "-64 880 672"
+"classname" "trigger_relay"
+}
+{
+"target" "t274"
+"targetname" "btsp"
+"classname" "trigger_relay"
+"origin" "-96 880 288"
+}
+{
+"target" "t272"
+"targetname" "btsp"
+"origin" "-80 880 288"
+"classname" "trigger_relay"
+}
+{
+"target" "t273"
+"targetname" "btsp"
+"classname" "trigger_relay"
+"origin" "-64 880 288"
+}
+{
+"target" "t275"
+"origin" "-96 776 672"
+"classname" "trigger_relay"
+"targetname" "tpgo"
+}
+{
+"target" "t276"
+"origin" "-112 760 672"
+"classname" "trigger_relay"
+"targetname" "tpgo"
+}
+{
+"target" "t272"
+"delay" ".1"
+"targetname" "tpgo"
+"classname" "trigger_relay"
+"origin" "-96 760 672"
+}
+{
+"target" "t274"
+"targetname" "btgo"
+"origin" "-112 776 288"
+"classname" "trigger_relay"
+"delay" ".5"
+}
+{
+"target" "t275"
+"targetname" "btgo"
+"classname" "trigger_relay"
+"origin" "-96 776 288"
+}
+{
+"target" "t276"
+"targetname" "btgo"
+"classname" "trigger_relay"
+"origin" "-112 760 288"
+}
+{
+"target" "t272"
+"targetname" "btgo"
+"origin" "-96 760 288"
+"classname" "trigger_relay"
+"delay" ".1"
+}
+{
+"model" "*136"
+"message" "Fighter lift lowered"
+"classname" "trigger_multiple"
+"spawnflags" "12"
+"wait" "1"
+"targetname" "t271"
+"target" "tpgo"
+}
+{
+"model" "*137"
+"message" "Fighter lift raised"
+"wait" "1"
+"spawnflags" "12"
+"classname" "trigger_multiple"
+"targetname" "t274"
+"target" "btgo"
+}
+{
+"wait" "6"
+"targetname" "t284"
+"target" "t283"
+"classname" "path_corner"
+"origin" "-496 640 548"
+}
+{
+"classname" "target_speaker"
+"noise" "switches/butn2.wav"
+"attenuation" "1"
+"origin" "-476 604 560"
+"targetname" "t275"
+}
+{
+"origin" "-320 640 376"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-448 704 440"
+}
+{
+"origin" "-448 832 440"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-448 960 440"
+}
+{
+"origin" "-512 1024 440"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-576 1088 440"
+}
+{
+"origin" "-640 1024 440"
+"light" "100"
+"classname" "light"
+}
+{
+"pathtarget" "t113"
+"spawnflags" "2048"
+"wait" "-1"
+"target" "t288"
+"targetname" "t287"
+"classname" "path_corner"
+"origin" "-680 768 528"
+}
+{
+"spawnflags" "2048"
+"target" "t287"
+"targetname" "t286"
+"origin" "-680 760 632"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"target" "t286"
+"targetname" "t285"
+"classname" "path_corner"
+"origin" "-680 664 616"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-640 528 808"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-640 896 808"
+}
+{
+"origin" "-640 768 808"
+"light" "100"
+"classname" "light"
+}
+{
+"dmg" "2000"
+"origin" "-281 832 536"
+"angle" "180"
+"spawnflags" "9"
+"classname" "target_laser"
+"targetname" "t293"
+}
+{
+"dmg" "2000"
+"classname" "target_laser"
+"spawnflags" "2057"
+"angle" "180"
+"origin" "-281 832 596"
+"targetname" "t293"
+}
+{
+"dmg" "2000"
+"origin" "-281 832 624"
+"angle" "180"
+"spawnflags" "9"
+"classname" "target_laser"
+"targetname" "t293"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "-1128 1360 1064"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "-1240 1560 1064"
+}
+{
+"origin" "-1320 1560 1064"
+"light" "140"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "-1432 1376 1064"
+}
+{
+"origin" "-1432 1456 1064"
+"light" "140"
+"classname" "light"
+}
+{
+"origin" "-1320 1256 1064"
+"light" "140"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "-1240 1256 1064"
+}
+{
+"model" "*138"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "jam"
+}
+{
+"origin" "328 316 320"
+"targetname" "jam"
+"noise" "world/force1.wav"
+"spawnflags" "2049"
+"classname" "target_speaker"
+}
+{
+"targetname" "jam"
+"origin" "360 316 288"
+"target" "t296"
+"spawnflags" "2049"
+"message" "za"
+"speed" ".5"
+"classname" "target_lightramp"
+}
+{
+"style" "39"
+"targetname" "t296"
+"origin" "336 316 272"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "39"
+"targetname" "t296"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "336 316 376"
+}
+{
+"style" "39"
+"targetname" "t296"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "336 372 320"
+}
+{
+"style" "39"
+"targetname" "t296"
+"origin" "336 260 320"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "39"
+"targetname" "t296"
+"origin" "336 372 376"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "39"
+"targetname" "t296"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "336 260 376"
+}
+{
+"style" "39"
+"targetname" "t296"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "336 372 272"
+}
+{
+"style" "39"
+"targetname" "t296"
+"origin" "336 260 272"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-288 704 768"
+}
+{
+"model" "*139"
+"targetname" "mhowell"
+"target" "t212"
+"wait" "-1"
+"speed" "300"
+"angle" "-1"
+"classname" "func_door"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "80"
+"_color" "1.000000 1.000000 0.501961"
+"origin" "184 2032 -40"
+}
+{
+"origin" "-176 2024 -64"
+"_color" "1.000000 1.000000 0.501961"
+"light" "80"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"_color" "1.000000 1.000000 0.501961"
+"origin" "-176 2192 -64"
+}
+{
+"classname" "info_player_coop"
+"targetname" "rsewer2b"
+"angle" "270"
+"origin" "-72 2704 -168"
+}
+{
+"origin" "40 2736 -168"
+"angle" "315"
+"targetname" "rsewer2b"
+"classname" "info_player_coop"
+}
+{
+"target" "t301"
+"targetname" "t7"
+"classname" "trigger_relay"
+"origin" "-816 -528 -216"
+}
+{
+"model" "*140"
+"classname" "trigger_multiple"
+"spawnflags" "2316"
+"target" "t302"
+"wait" "5"
+"targetname" "t392"
+}
+{
+"targetname" "t306"
+"target" "t304"
+"classname" "monster_gunner"
+"spawnflags" "1"
+"angle" "270"
+"origin" "1120 1568 288"
+}
+{
+"targetname" "t306"
+"target" "t303"
+"origin" "1000 1568 288"
+"angle" "270"
+"spawnflags" "1"
+"classname" "monster_gunner"
+"item" "ammo_grenades"
+}
+{
+"targetname" "t304"
+"classname" "point_combat"
+"origin" "1120 1552 272"
+}
+{
+"spawnflags" "1"
+"targetname" "t305"
+"origin" "1048 1472 272"
+"classname" "point_combat"
+}
+{
+"model" "*141"
+"target" "t307"
+"classname" "trigger_multiple"
+"spawnflags" "2056"
+"targetname" "t394"
+}
+{
+"model" "*142"
+"spawnflags" "2056"
+"classname" "trigger_multiple"
+"target" "t307"
+"targetname" "t394"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_turret"
+"angle" "-2"
+"origin" "928 928 512"
+"spawnflags" "264"
+"target" "t396"
+}
+{
+"targetname" "t383"
+"spawnflags" "1"
+"target" "t330"
+"classname" "monster_infantry"
+"angle" "90"
+"origin" "-144 208 280"
+}
+{
+"targetname" "t383"
+"spawnflags" "769"
+"origin" "-40 208 280"
+"angle" "90"
+"classname" "monster_infantry"
+}
+{
+"targetname" "rsewer2a"
+"classname" "info_player_start"
+"origin" "-352 -224 -352"
+}
+{
+"targetname" "rsewer2a"
+"classname" "info_player_coop"
+"angle" "0"
+"origin" "-304 -360 -40"
+}
+{
+"targetname" "rsewer2a"
+"origin" "-488 -296 -16"
+"angle" "0"
+"classname" "info_player_coop"
+}
+{
+"targetname" "rsewer2a"
+"classname" "info_player_coop"
+"angle" "0"
+"origin" "-352 -224 -328"
+}
+{
+"origin" "-160 2704 -168"
+"angle" "270"
+"targetname" "rsewer2b"
+"classname" "info_player_coop"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-208 760 328"
+}
+{
+"origin" "448 568 328"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "504 352 328"
+}
+{
+"origin" "168 336 328"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-448 768 472"
+"angle" "-2"
+"spawnflags" "136"
+"classname" "monster_turret"
+"targetname" "t315"
+}
+{
+"item" "ammo_bullets"
+"targetname" "t315"
+"origin" "-688 1000 288"
+"angle" "0"
+"classname" "monster_gunner"
+"spawnflags" "1"
+}
+{
+"classname" "monster_turret"
+"origin" "-704 2048 264"
+"angle" "270"
+"spawnflags" "8"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-448 328 568"
+}
+{
+"targetname" "t325"
+"target" "t316"
+"angle" "90"
+"spawnflags" "2"
+"classname" "monster_daedalus"
+"origin" "64 -1448 720"
+}
+{
+"target" "t319"
+"targetname" "t316"
+"classname" "path_corner"
+"origin" "64 -840 704"
+}
+{
+"targetname" "t318"
+"classname" "path_corner"
+"origin" "-192 -704 448"
+}
+{
+"targetname" "t319"
+"origin" "128 -704 448"
+"classname" "path_corner"
+}
+{
+"classname" "path_corner"
+"origin" "-32 -840 704"
+"targetname" "t320"
+"target" "t320"
+}
+{
+"targetname" "t325"
+"classname" "monster_daedalus"
+"origin" "-32 -1448 720"
+"angle" "90"
+"spawnflags" "2"
+"target" "t320"
+}
+{
+"origin" "-32 -704 448"
+"classname" "path_corner"
+"targetname" "t320"
+}
+{
+"target" "t322"
+"classname" "monster_gunner"
+"spawnflags" "5"
+"angle" "0"
+"origin" "-736 1272 288"
+"targetname" "t311"
+}
+{
+"spawnflags" "1"
+"targetname" "t322"
+"classname" "point_combat"
+"origin" "-608 1360 272"
+}
+{
+"targetname" "t312"
+"target" "t325"
+"classname" "trigger_relay"
+"origin" "-88 -1408 696"
+}
+{
+"classname" "monster_gunner"
+"angle" "0"
+"origin" "872 768 376"
+"spawnflags" "0"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_gunner"
+"angle" "270"
+"spawnflags" "1"
+"origin" "944 1104 376"
+"targetname" "t396"
+}
+{
+"targetname" "t330"
+"target" "t326"
+"origin" "-448 728 288"
+"angle" "270"
+"classname" "monster_gunner"
+"spawnflags" "772"
+}
+{
+"spawnflags" "0"
+"classname" "point_combat"
+"targetname" "t328"
+"origin" "-128 528 272"
+}
+{
+"item" "ammo_grenades"
+"targetname" "t330"
+"spawnflags" "260"
+"classname" "monster_gunner"
+"angle" "270"
+"origin" "-448 800 288"
+"target" "t328"
+}
+{
+"spawnflags" "0"
+"origin" "-184 608 272"
+"targetname" "t329"
+"classname" "point_combat"
+}
+{
+"targetname" "t330"
+"target" "t329"
+"origin" "-448 840 288"
+"angle" "270"
+"classname" "monster_gunner"
+"spawnflags" "260"
+}
+{
+"targetname" "t331"
+"classname" "monster_berserk"
+"spawnflags" "260"
+"angle" "90"
+"origin" "440 320 288"
+}
+{
+"style" "40"
+"classname" "light"
+"origin" "-896 1888 272"
+"_color" "1.000000 0.435294 0.435294"
+"targetname" "t333"
+"light" "60"
+}
+{
+"origin" "-832 1968 228"
+"light" "75"
+"classname" "light"
+}
+{
+"style" "41"
+"light" "60"
+"targetname" "t334"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-896 1824 272"
+"classname" "light"
+}
+{
+"style" "42"
+"classname" "light"
+"origin" "-896 1760 272"
+"_color" "1.000000 0.435294 0.435294"
+"targetname" "t335"
+"light" "60"
+}
+{
+"style" "43"
+"light" "60"
+"targetname" "t336"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-896 1904 224"
+"classname" "light"
+}
+{
+"style" "44"
+"classname" "light"
+"origin" "-896 1840 224"
+"_color" "1.000000 0.435294 0.435294"
+"targetname" "t337"
+"light" "60"
+}
+{
+"style" "45"
+"light" "60"
+"targetname" "t338"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-896 1776 224"
+"classname" "light"
+}
+{
+"style" "46"
+"classname" "light"
+"origin" "-896 1888 200"
+"_color" "1.000000 0.435294 0.435294"
+"targetname" "t339"
+"light" "60"
+}
+{
+"style" "47"
+"light" "60"
+"targetname" "t340"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-896 1824 200"
+"classname" "light"
+}
+{
+"style" "48"
+"classname" "light"
+"origin" "-896 1760 200"
+"_color" "1.000000 0.435294 0.435294"
+"targetname" "t341"
+"light" "60"
+}
+{
+"style" "49"
+"light" "60"
+"targetname" "t342"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-848 1688 184"
+"classname" "light"
+}
+{
+"style" "50"
+"classname" "light"
+"origin" "-816 1664 184"
+"_color" "1.000000 0.435294 0.435294"
+"targetname" "t343"
+"light" "60"
+}
+{
+"style" "51"
+"light" "60"
+"targetname" "t344"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-824 1976 184"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-792 2008 284"
+}
+{
+"origin" "-856 1944 284"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-800 1640 284"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-864 1704 284"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-832 1672 228"
+}
+{
+"origin" "-856 1816 188"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+"origin" "-816 1712 184"
+}
+{
+"origin" "-824 1928 184"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-600 728 576"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+"origin" "-584 960 576"
+}
+{
+"origin" "-352 952 576"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+"origin" "-544 544 576"
+}
+{
+"origin" "680 768 448"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-16 -48 64"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "192 -200 64"
+}
+{
+"origin" "-88 -312 64"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-440 -400 64"
+}
+{
+"origin" "-80 -568 64"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "176 -472 64"
+}
+{
+"origin" "-320 -864 64"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "32 -760 64"
+}
+{
+"origin" "-152 16 368"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "136 24 368"
+}
+{
+"origin" "440 -152 368"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "544 -384 368"
+}
+{
+"origin" "392 -648 368"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "136 -888 368"
+}
+{
+"origin" "-216 -976 368"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-208 -984 368"
+}
+{
+"origin" "-544 1984 284"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-544 1824 284"
+}
+{
+"origin" "-544 1664 284"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-768 1856 284"
+}
+{
+"origin" "-544 2216 244"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "-160 2624 20"
+}
+{
+"origin" "96 2624 20"
+"light" "140"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "352 2624 20"
+}
+{
+"origin" "608 2624 20"
+"light" "140"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "584 2400 20"
+}
+{
+"origin" "488 2152 -56"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "224 2088 56"
+"targetname" "t347"
+"classname" "point_combat"
+"spawnflags" "1"
+}
+{
+"target" "t347"
+"spawnflags" "1"
+"origin" "320 2008 72"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"classname" "light"
+"origin" "-576 632 576"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-640 616 616"
+}
+{
+"origin" "-640 544 616"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-640 480 616"
+}
+{
+"origin" "-600 480 584"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-600 544 584"
+}
+{
+"origin" "-592 608 584"
+"light" "75"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-608 456 576"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-632 456 608"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-592 456 608"
+"classname" "light"
+}
+{
+"style" "33"
+"origin" "-392 832 608"
+"light" "125"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"targetname" "t293"
+}
+{
+"style" "33"
+"origin" "-312 832 544"
+"light" "125"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"targetname" "t293"
+}
+{
+"style" "33"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "125"
+"origin" "-392 832 544"
+"targetname" "t293"
+}
+{
+"origin" "-440 704 536"
+"light" "70"
+"_color" "1.000000 1.000000 0.501961"
+"classname" "light"
+}
+{
+"classname" "light"
+"_color" "1.000000 1.000000 0.501961"
+"light" "70"
+"origin" "-264 640 536"
+}
+{
+"origin" "-264 512 536"
+"light" "70"
+"_color" "1.000000 1.000000 0.501961"
+"classname" "light"
+}
+{
+"origin" "-320 1000 612"
+"light" "90"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "90"
+"origin" "-456 1000 612"
+}
+{
+"origin" "-576 1000 612"
+"light" "90"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-576 1016 572"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-456 1016 572"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-320 1016 572"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"light" "90"
+"origin" "-632 952 612"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-632 984 564"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-632 928 564"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"origin" "-520 864 564"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-520 808 564"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-552 656 564"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-616 656 564"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"origin" "-520 872 612"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-520 800 612"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "-520 744 612"
+}
+{
+"origin" "-520 672 612"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-344 600 696"
+"target" "t364"
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t366"
+}
+{
+"model" "*143"
+"targetname" "t364"
+"classname" "func_explosive"
+}
+{
+"item" "ammo_cells"
+"targetname" "t364"
+"angle" "90"
+"spawnflags" "136"
+"origin" "-384 528 704"
+"classname" "monster_turret"
+}
+{
+"origin" "-384 600 672"
+"angle" "90"
+"spawnflags" "1"
+"classname" "monster_berserk"
+"target" "t366"
+"targetname" "t367"
+}
+{
+"target" "t389"
+"origin" "-960 592 672"
+"angle" "90"
+"spawnflags" "769"
+"classname" "monster_berserk"
+"targetname" "t368"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-640 896 948"
+}
+{
+"origin" "-640 704 948"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-640 512 948"
+}
+{
+"origin" "-640 1112 948"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-768 1152 948"
+}
+{
+"origin" "-1024 1408 800"
+"light" "80"
+"_color" "1.000000 1.000000 0.501961"
+"classname" "light"
+}
+{
+"classname" "light"
+"_color" "1.000000 1.000000 0.501961"
+"light" "80"
+"origin" "-1536 1408 800"
+}
+{
+"origin" "-904 1408 960"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1408 1784 976"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-1152 1784 976"
+}
+{
+"origin" "-1280 1032 904"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-896 -472 -120"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-896 -536 -120"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-896 -600 -120"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"origin" "-800 -720 -104"
+"light" "80"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-808 -712 -160"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-800 -728 -184"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-856 -664 -200"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-848 -408 -160"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-856 -424 -184"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-800 -360 -200"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-848 -672 -104"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-808 -376 -104"
+}
+{
+"origin" "-856 -424 -104"
+"light" "80"
+"classname" "light"
+}
+{
+"origin" "-1080 -136 -248"
+"light" "65"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-1080 -56 -248"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-840 120 -248"
+}
+{
+"origin" "-760 120 -248"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-592 -152 -248"
+"light" "65"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-592 -232 -248"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-760 -152 -248"
+}
+{
+"origin" "-760 -232 -248"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-208 -408 -264"
+"light" "65"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-208 -488 -264"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-568 -408 -264"
+}
+{
+"origin" "-568 -504 -264"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-568 -640 -264"
+"light" "65"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-568 -720 -264"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-792 -200 -248"
+}
+{
+"origin" "-792 -120 -248"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "1456 992 336"
+"spawnflags" "2048"
+"classname" "path_corner"
+"targetname" "t370"
+"target" "t371"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-608 1984 284"
+}
+{
+"origin" "-608 1824 284"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-608 1664 284"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "-608 2216 244"
+}
+{
+"origin" "-768 1784 284"
+"light" "115"
+"classname" "light"
+}
+{
+"origin" "-160 2688 20"
+"light" "140"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "96 2688 20"
+}
+{
+"origin" "352 2688 20"
+"light" "140"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "608 2688 20"
+}
+{
+"origin" "648 2400 20"
+"light" "140"
+"classname" "light"
+}
+{
+"origin" "672 2192 -112"
+"targetname" "t376"
+"classname" "point_combat"
+"target" "t377"
+}
+{
+"classname" "point_combat"
+"targetname" "t377"
+"origin" "744 2216 -112"
+"spawnflags" "1"
+}
+{
+"origin" "-248 2072 88"
+"spawnflags" "1"
+"targetname" "t379"
+"classname" "point_combat"
+}
+{
+"target" "t379"
+"spawnflags" "769"
+"classname" "monster_gunner"
+"angle" "45"
+"origin" "-200 1872 104"
+}
+{
+"style" "32"
+"classname" "light"
+"light" "65"
+"targetname" "t302"
+"spawnflags" "1"
+"origin" "1120 -136 360"
+}
+{
+"style" "32"
+"origin" "864 248 360"
+"spawnflags" "1"
+"targetname" "t302"
+"light" "65"
+"classname" "light"
+}
+{
+"style" "52"
+"classname" "light"
+"light" "65"
+"targetname" "t307"
+"spawnflags" "1"
+"origin" "456 1312 368"
+}
+{
+"style" "52"
+"origin" "712 1568 368"
+"spawnflags" "1"
+"targetname" "t307"
+"light" "65"
+"classname" "light"
+}
+{
+"style" "52"
+"classname" "light"
+"light" "65"
+"targetname" "t307"
+"spawnflags" "1"
+"origin" "64 1432 368"
+}
+{
+"model" "*144"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "t383"
+"wait" "-1"
+}
+{
+"angle" "270"
+"spawnflags" "136"
+"origin" "200 1616 368"
+"classname" "monster_turret"
+"targetname" "t383"
+}
+{
+"style" "53"
+"classname" "light"
+"light" "65"
+"targetname" "t383"
+"spawnflags" "1"
+"origin" "200 1568 368"
+}
+{
+"classname" "light"
+"origin" "-208 -392 -120"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-208 -392 -96"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-208 -456 -120"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-208 -456 -96"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"origin" "-208 -328 -120"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-208 -328 -96"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-232 -264 -120"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-232 -264 -96"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"origin" "-264 -200 -120"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-264 -200 -96"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-288 -136 -120"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-288 -136 -96"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"origin" "-208 -520 -120"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-208 -520 -96"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-232 -584 -120"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-232 -584 -96"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"origin" "-264 -640 -120"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-264 -640 -96"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-208 -416 -200"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-208 -352 -200"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-208 -472 -200"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-216 -544 -200"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-240 -600 -200"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-272 -664 -200"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-216 -288 -200"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-248 -232 -200"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-272 -168 -200"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-352 -336 -104"
+}
+{
+"origin" "-352 -504 -104"
+"light" "80"
+"classname" "light"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-160 -352 -136"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-128 -352 -136"
+"_color" "1.000000 0.435294 0.435294"
+"light" "60"
+}
+{
+"light" "60"
+"_color" "1.000000 0.435294 0.435294"
+"origin" "-96 -352 -136"
+"classname" "light"
+}
+{
+"targetname" "t43"
+"noise" "world/airhiss2.wav"
+"classname" "target_speaker"
+"origin" "760 864 56"
+}
+{
+"spawnflags" "2"
+"targetname" "heybuddy"
+"noise" "world/mach3.wav"
+"origin" "760 848 56"
+"classname" "target_speaker"
+}
+{
+"targetname" "iliketrafficlights"
+"classname" "target_speaker"
+"origin" "960 880 56"
+"noise" "world/lite_on3.wav"
+}
+{
+"targetname" "t43"
+"origin" "960 864 56"
+"classname" "target_speaker"
+"noise" "world/airhiss2.wav"
+}
+{
+"targetname" "heybuddy"
+"spawnflags" "2"
+"classname" "target_speaker"
+"origin" "960 848 56"
+"noise" "world/mach3.wav"
+}
+{
+"targetname" "iliketrafficlights"
+"noise" "world/lite_on3.wav"
+"origin" "1152 880 56"
+"classname" "target_speaker"
+}
+{
+"targetname" "t43"
+"noise" "world/airhiss2.wav"
+"classname" "target_speaker"
+"origin" "1152 864 56"
+}
+{
+"targetname" "heybuddy"
+"spawnflags" "2"
+"noise" "world/mach3.wav"
+"origin" "1152 848 56"
+"classname" "target_speaker"
+}
+{
+"targetname" "iliketrafficlights"
+"classname" "target_speaker"
+"origin" "1408 880 56"
+"noise" "world/lite_on3.wav"
+}
+{
+"targetname" "t43"
+"origin" "1408 864 56"
+"classname" "target_speaker"
+"noise" "world/airhiss2.wav"
+}
+{
+"targetname" "heybuddy"
+"spawnflags" "2"
+"classname" "target_speaker"
+"origin" "1408 848 56"
+"noise" "world/mach3.wav"
+}
+{
+"origin" "-24 2824 -168"
+"angle" "270"
+"targetname" "rsewer2b"
+"classname" "info_player_coop"
+}
+{
+"model" "*145"
+"angle" "270"
+"classname" "func_button"
+"wait" "4"
+"target" "t386"
+}
+{
+"origin" "-552 840 536"
+"targetname" "t387"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"targetname" "t388"
+"classname" "point_combat"
+"origin" "-928 752 656"
+}
+{
+"origin" "-384 576 768"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-384 1184 768"
+}
+{
+"origin" "-264 896 536"
+"light" "70"
+"_color" "1.000000 1.000000 0.501961"
+"classname" "light"
+}
+{
+"classname" "light"
+"_color" "1.000000 1.000000 0.501961"
+"light" "70"
+"origin" "-480 904 536"
+}
+{
+"origin" "-632 832 536"
+"light" "70"
+"_color" "1.000000 1.000000 0.501961"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-32 2312 16"
+}
+{
+"origin" "-64 2328 72"
+"light" "65"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-64 2392 72"
+}
+{
+"origin" "0 2392 72"
+"light" "65"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "0 2328 72"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-584 320 672"
+}
+{
+"origin" "-584 320 768"
+"light" "65"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "65"
+"origin" "-584 320 864"
+}
+{
+"origin" "-1656 1472 944"
+"light" "140"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "-1656 1552 944"
+}
+{
+"classname" "light"
+"light" "140"
+"origin" "-904 1472 944"
+}
+{
+"origin" "-904 1552 944"
+"light" "140"
+"classname" "light"
+}
+{
+"model" "*146"
+"team" "nascar"
+"classname" "func_door"
+"wait" "1"
+"target" "t391"
+"angle" "-1"
+}
+{
+"style" "10"
+"targetname" "t391"
+"classname" "func_areaportal"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "4036 -4068 -4068"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"origin" "4072 -4068 -4068"
+"light" "100"
+"classname" "light"
+}
+{
+"delay" "1"
+"origin" "480 -224 272"
+"spawnflags" "2048"
+"killtarget" "t394"
+"classname" "trigger_relay"
+"targetname" "t307"
+}
+{
+"origin" "464 -200 272"
+"target" "t394"
+"targetname" "t301"
+"spawnflags" "2048"
+"classname" "trigger_relay"
+}
+{
+"message" "Open primary entrance."
+"spawnflags" "0"
+"origin" "80 -200 344"
+"classname" "target_help"
+"targetname" "t11help" // b#2: t11 -> t11help
+}
+{
+"origin" "168 2648 -104"
+"classname" "target_help"
+"targetname" "t218"
+"message" "Disarm Yellow Forcefields"
+}
+{
+"origin" "-600 -736 -240"
+"classname" "ammo_slugs"
+"spawnflags" "1792"
+}
+{
+"origin" "-240 -480 -256"
+"spawnflags" "1792"
+"classname" "item_health"
+}
+{
+"origin" "-1048 -144 -240"
+"classname" "item_armor_shard"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1048 -112 -240"
+}
+{
+"origin" "-1048 -80 -240"
+"classname" "item_armor_shard"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1048 -48 -240"
+}
+{
+"origin" "-1048 -16 -240"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-40 48 -64"
+"classname" "ammo_bullets"
+}
+{
+"origin" "432 -240 184"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+"origin" "440 -312 184"
+}
+{
+"origin" "456 -376 184"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-320 -1016 -64"
+"classname" "ammo_flechettes" // b#4: nails -> flechettes
+}
+{
+"origin" "1344 736 288"
+"spawnflags" "1536"
+"classname" "item_health_small"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "1536"
+"origin" "1344 864 288"
+}
+{
+"origin" "1344 992 288"
+"spawnflags" "1536"
+"classname" "item_health_small"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "1536"
+"origin" "1344 1072 280"
+}
+{
+"origin" "672 992 80"
+"classname" "item_health_large"
+"spawnflags" "1024"
+}
+{
+"classname" "item_health"
+"origin" "928 968 368"
+}
+{
+"spawnflags" "1792"
+"classname" "ammo_tesla"
+"origin" "1024 -304 272"
+}
+{
+"classname" "item_health"
+"origin" "-16 1176 280"
+}
+{
+"spawnflags" "1792"
+"classname" "ammo_prox"
+"origin" "232 -472 -64"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "1024"
+"origin" "-392 1920 112"
+}
+{
+"origin" "-392 2048 112"
+"spawnflags" "3584"
+"classname" "item_health_large"
+}
+{
+"classname" "ammo_flechettes" // b#4: nails -> flechettes
+"origin" "-32 2528 -176"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-616 896 544"
+}
+{
+"origin" "-616 960 544"
+"classname" "item_armor_shard"
+}
+{
+"classname" "item_armor_body"
+"spawnflags" "1792"
+"origin" "-896 704 512"
+}
+{
+"classname" "ammo_shells"
+"origin" "-120 1112 656"
+}
+{
+"classname" "item_health"
+"origin" "-624 544 848"
+}
+{
+"classname" "item_health"
+"origin" "1120 1568 280"
+}
+{
+"origin" "1632 744 80"
+"classname" "item_health_large"
+}
+{
+"spawnflags" "1024"
+"classname" "item_health_large"
+"origin" "-144 208 272"
+}
+{
+"classname" "item_health_large"
+"origin" "456 1440 280"
+}
+{
+"model" "*147"
+"origin" "-432 -632 -24"
+"_minlight" ".1"
+"speed" "300"
+"distance" "100"
+"spawnflags" "2210"
+"classname" "func_door_rotating"
+"targetname" "t311"
+}
+{
+"classname" "target_speaker"
+"noise" "world/unit3_05.wav"
+"spawnflags" "4"
+"origin" "-1352 1696 928"
+"targetname" "porkin"
+"attenuation" "-1"
+}
+{
+"target" "t401"
+"targetname" "t400"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-1288 1104 864"
+}
+{
+"target" "t402"
+"targetname" "t401"
+"origin" "-1120 1096 864"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t403"
+"targetname" "t402"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "-968 1152 864"
+}
+{
+"target" "t404"
+"targetname" "t403"
+"origin" "-640 1152 864"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"targetname" "t404"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-640 496 864"
+}
+{
+"model" "*148"
+"spawnflags" "2048"
+"classname" "func_water"
+"team" "argh5"
+}
+{
+"model" "*149"
+"classname" "func_water"
+"spawnflags" "2048"
+"team" "argh2"
+}
+{
+"model" "*150"
+"classname" "func_water"
+"spawnflags" "2048"
+"team" "argh3"
+}
+{
+"model" "*151"
+"classname" "func_water"
+"spawnflags" "2048"
+"team" "argh4"
+} \ No newline at end of file
diff --git a/rogue/stuff/mapfixes/rmine1.ent b/rogue/stuff/mapfixes/rmine1.ent
new file mode 100644
index 0000000..586d814
--- /dev/null
+++ b/rogue/stuff/mapfixes/rmine1.ent
@@ -0,0 +1,7107 @@
+{
+"sounds" "6"
+"sky" "rogue1"
+"message" "Lower Mines"
+"classname" "worldspawn"
+"nextmap" "rlava1"
+}
+{
+"model" "*1"
+"target" "t296"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"model" "*2"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*3"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*4"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"classname" "target_speaker"
+"noise" "world/drip_amb.wav"
+"spawnflags" "1"
+"origin" "2000 216 584"
+}
+{
+"classname" "target_speaker"
+"noise" "world/drip_amb.wav"
+"spawnflags" "1"
+"origin" "1472 -48 584"
+}
+{
+"classname" "target_speaker"
+"noise" "world/drip_amb.wav"
+"spawnflags" "1"
+"origin" "1544 -1152 392"
+}
+{
+"classname" "target_speaker"
+"noise" "world/drip_amb.wav"
+"spawnflags" "1"
+"origin" "808 -1360 608"
+}
+{
+"classname" "target_speaker"
+"noise" "world/drip_amb.wav"
+"spawnflags" "1"
+"origin" "72 -1384 984"
+}
+{
+"classname" "misc_teleporter_dest"
+"targetname" "tele2"
+"spawnflags" "5888"
+"angle" "0"
+"origin" "-784 -1984 8"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "5888"
+"angle" "225"
+"origin" "304 -240 736"
+"target" "tele2"
+}
+{
+"classname" "misc_teleporter_dest"
+"angle" "45"
+"spawnflags" "5888"
+"targetname" "t363"
+"origin" "1168 248 712"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "5888"
+"angle" "45"
+"target" "t363"
+"origin" "-560 -1024 8"
+}
+{
+"origin" "1192 -1424 544"
+"attenuation" "-1"
+"noise" "world/voice5.wav"
+"targetname" "t362"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"model" "*5"
+"target" "t362"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"model" "*6"
+"targetname" "gibber"
+"target" "t361"
+"classname" "trigger_once"
+}
+{
+"model" "*7"
+"target" "t361"
+"targetname" "t98"
+"classname" "trigger_once"
+}
+{
+"targetname" "t361"
+"noise" "world/mach2.wav"
+"origin" "656 0 520"
+"spawnflags" "2"
+"team" "boxcrane"
+"classname" "target_speaker"
+}
+{
+"targetname" "t361"
+"noise" "world/mach2.wav"
+"origin" "624 0 520"
+"spawnflags" "2"
+"team" "boxcrane"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"noise" "world/lava1.wav"
+"spawnflags" "1"
+"origin" "1632 360 360"
+}
+{
+"origin" "1992 176 504"
+"attenuation" "-1"
+"noise" "world/voice10.wav"
+"targetname" "t360"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"model" "*8"
+"target" "t360"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"origin" "-2152 -2200 56"
+"target" "t359"
+"delay" "25"
+"targetname" "t277"
+"classname" "trigger_relay"
+}
+{
+"origin" "-2144 -2136 56"
+"targetname" "t359"
+"noise" "world/voice1.wav"
+"attenuation" "-1"
+"classname" "target_speaker"
+}
+{
+"origin" "1168 704 520"
+"angle" "315"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "832 -288 536"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "512 -256 592"
+"spawnflags" "5888"
+"classname" "item_sphere_hunter"
+}
+{
+"spawnflags" "5888"
+"origin" "2096 -376 416"
+"classname" "item_sphere_vengeance"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "0"
+"origin" "1056 -408 528"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "-736 -704 8"
+}
+{
+"origin" "1968 992 576"
+"spawnflags" "5888"
+"classname" "item_sphere_defender"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "1696 -504 768"
+}
+{
+"origin" "920 -1112 880"
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "992 -1112 880"
+"spawnflags" "5888"
+"classname" "weapon_etf_rifle"
+}
+{
+"spawnflags" "5888"
+"origin" "864 -480 728"
+"classname" "weapon_chainfist"
+}
+{
+"origin" "-584 -2208 0"
+"classname" "ammo_cells"
+"spawnflags" "5888"
+}
+{
+"origin" "-528 -2208 0"
+"spawnflags" "5888"
+"classname" "ammo_cells"
+}
+{
+"origin" "-752 -1760 0"
+"spawnflags" "5888"
+"classname" "weapon_heatbeam"
+}
+{
+"model" "*9"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"angle" "180"
+"origin" "-224 -872 8"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-2048 -2312 24"
+"spawnflags" "5888"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-1952 -2296 24"
+"spawnflags" "5888"
+"classname" "weapon_rocketlauncher"
+}
+{
+"angle" "90"
+"classname" "info_player_deathmatch"
+"origin" "-1088 -2192 168"
+}
+{
+"model" "*10"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*11"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*12"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"spawnflags" "5888"
+"origin" "312 -656 896"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "392 -720 896"
+"spawnflags" "5888"
+"classname" "weapon_etf_rifle"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "45"
+"origin" "1760 -408 528"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "225"
+"origin" "2056 -8 800"
+}
+{
+"origin" "1800 272 512"
+"spawnflags" "5888"
+"classname" "weapon_chaingun"
+}
+{
+"origin" "584 -552 520"
+"spawnflags" "5888"
+"classname" "weapon_railgun"
+}
+{
+"origin" "1072 -1120 888"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "1704 -968 752"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "320 -416 736"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"noise" "world/flyby1.wav"
+"origin" "-1632 -2400 280"
+"classname" "target_speaker"
+"spawnflags" "2048"
+"targetname" "t357"
+}
+{
+"spawnflags" "2049"
+"origin" "-1760 -2376 120"
+"wait" "5"
+"classname" "func_timer"
+"random" "2"
+"target" "t358"
+}
+{
+"noise" "world/battle3.wav"
+"origin" "-1720 -2408 120"
+"classname" "target_speaker"
+"spawnflags" "2048"
+"targetname" "t358"
+}
+{
+"spawnflags" "2049"
+"random" "2"
+"classname" "func_timer"
+"wait" "5"
+"target" "t357"
+"origin" "-1672 -2368 280"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"origin" "-1600 -2384 280"
+"noise" "world/flyby1.wav"
+"targetname" "t357"
+}
+{
+"spawnflags" "2049"
+"wait" "5"
+"classname" "func_timer"
+"target" "t356"
+"random" "2"
+"origin" "-2016 -2248 120"
+}
+{
+"noise" "world/battle2.wav"
+"origin" "-1976 -2280 120"
+"classname" "target_speaker"
+"targetname" "t356"
+"spawnflags" "2052"
+}
+{
+"spawnflags" "2049"
+"classname" "func_timer"
+"target" "t355"
+"wait" "3"
+"origin" "-2080 -1976 120"
+}
+{
+"classname" "target_speaker"
+"origin" "-2040 -2008 120"
+"noise" "world/battle1.wav"
+"targetname" "t355"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "1472 -864 536"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"origin" "1440 -1496 560"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1872 -1496 408"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1920 -1240 408"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1624 -1224 856"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1336 -1168 408"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1408 -672 744"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1664 -936 784"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "2208 -384 808"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1984 -536 808"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1440 -104 552"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1944 -48 568"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "2032 576 616"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1800 192 560"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1328 192 736"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1448 656 528"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1592 768 752"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1240 760 576"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "944 496 760"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "384 120 784"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "280 -464 784"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "688 -480 784"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "640 -688 560"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "224 -672 560"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "376 -1224 888"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "1144 -112 624"
+"noise" "world/amb17.wav"
+"spawnflags" "2"
+"targetname" "t81"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-512 -664 72"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-936 -720 72"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-896 -1064 72"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-608 -1064 72"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-224 -1760 56"
+"classname" "target_speaker"
+}
+{
+"volume" ".5"
+"origin" "-1304 -1464 0"
+"classname" "target_speaker"
+"noise" "world/lava1.wav"
+"spawnflags" "2048"
+"targetname" "t15"
+}
+{
+"classname" "target_speaker"
+"origin" "-808 -1856 72"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-808 -2144 592"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-808 -2112 72"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-480 -1752 584"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-152 -1888 584"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-152 -2016 56"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-192 -2472 760"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-448 -2472 760"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-768 -2472 688"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-928 -2344 1072"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-928 -1624 1072"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"origin" "-1272 -1728 1168"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "-416 -2208 592"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "-544 -1760 64"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "-224 -800 72"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"classname" "target_speaker"
+"origin" "192 -1704 912"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "-1272 -2240 1168"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb2.wav"
+"origin" "928 -1664 712"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"origin" "-64 -1704 912"
+"noise" "world/amb2.wav"
+"spawnflags" "1"
+}
+{
+"spawnflags" "2"
+"noise" "world/amb17.wav"
+"origin" "928 -1760 840"
+"classname" "target_speaker"
+"targetname" "t81"
+}
+{
+"volume" ".5"
+"spawnflags" "2048"
+"noise" "world/lava1.wav"
+"classname" "target_speaker"
+"origin" "-1760 -2320 112"
+}
+{
+"volume" ".5"
+"origin" "-2160 -2064 112"
+"classname" "target_speaker"
+"noise" "world/lava1.wav"
+"spawnflags" "2048"
+"targetname" "t354"
+}
+{
+"spawnflags" "2049"
+"origin" "-2073 -2072 24"
+"classname" "func_timer"
+"wait" "4"
+"random" "2"
+"target" "t354"
+}
+{
+"spawnflags" "2049"
+"origin" "-2138 -2093 -8"
+"classname" "target_splash"
+"sounds" "5"
+"count" "40"
+"targetname" "t354"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "320 -544 600"
+}
+{
+"model" "*13"
+"spawnflags" "2049"
+"classname" "trigger_counter"
+"count" "2"
+"targetname" "blowgrate"
+"target" "t352"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "boom"
+"killtarget" "gibber"
+"origin" "696 -104 792"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"target" "blowgrate"
+"targetname" "gibber"
+"origin" "664 -424 792"
+}
+{
+"spawnflags" "2048"
+"killtarget" "blocko"
+"delay" ".3"
+"origin" "-504 -1848 600"
+"classname" "trigger_relay"
+"targetname" "t258"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"origin" "-472 -1880 600"
+"targetname" "blocko"
+"delay" ".3"
+"target" "blockwall"
+}
+{
+"model" "*14"
+"classname" "trigger_counter"
+"spawnflags" "1"
+"count" "2"
+"targetname" "blockwall"
+"target" "waller"
+}
+{
+"model" "*15"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t350"
+"target" "booger1"
+}
+{
+"model" "*16"
+"spawnflags" "2048"
+"target" "t349"
+"classname" "trigger_multiple"
+}
+{
+"spawnflags" "2048"
+"origin" "-2040 -2280 72"
+"target" "booger1"
+"targetname" "t5"
+"classname" "trigger_relay"
+}
+{
+"origin" "1728 -840 744"
+"classname" "item_health"
+}
+{
+"classname" "item_health_large"
+"origin" "-72 -1280 848"
+}
+{
+"classname" "monster_soldier"
+"angle" "270"
+"spawnflags" "1536"
+"target" "t169"
+"origin" "72 -1184 952"
+}
+{
+"classname" "info_player_intermission"
+"angles" "30 225 0"
+"origin" "1472 -208 872"
+}
+{
+"origin" "-688 -512 88"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-784 -512 88"
+}
+{
+"origin" "1936 784 656"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "monster_berserk"
+"angle" "90"
+"spawnflags" "769"
+"origin" "576 -1600 856"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"origin" "56 -1280 936"
+"targetname" "t169"
+}
+{
+"origin" "-544 -1024 8"
+"spawnflags" "769"
+"angle" "180"
+"classname" "monster_berserk"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "296 -760 672"
+"_color" "0.000000 0.000000 1.000000"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "240 -720 672"
+"_color" "0.000000 0.000000 1.000000"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "240 -720 568"
+"_color" "0.000000 0.000000 1.000000"
+}
+{
+"light" "120"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "440 -520 536"
+}
+{
+"origin" "-360 -1192 528"
+"spawnflags" "5888"
+"classname" "weapon_proxlauncher"
+}
+{
+"origin" "-400 -1128 528"
+"classname" "ammo_prox"
+}
+{
+"origin" "-400 -1560 528"
+"targetname" "t350"
+"classname" "item_health_small"
+"spawnflags" "1"
+}
+{
+"origin" "-400 -1512 528"
+"targetname" "t350"
+"classname" "item_health_small"
+"spawnflags" "1"
+}
+{
+"origin" "-400 -1664 528"
+"targetname" "t350"
+"classname" "item_health_small"
+"spawnflags" "1"
+}
+{
+"origin" "-400 -1608 528"
+"targetname" "t350"
+"spawnflags" "1"
+"classname" "item_health_small"
+}
+{
+"origin" "1816 -832 744"
+"targetname" "t350"
+"spawnflags" "1"
+"classname" "ammo_prox"
+}
+{
+"origin" "1192 -632 520"
+"targetname" "t350"
+"spawnflags" "1"
+"classname" "item_health"
+}
+{
+"origin" "968 480 712"
+"targetname" "t350"
+"spawnflags" "1"
+"classname" "ammo_shells"
+}
+{
+"classname" "item_health"
+"origin" "1816 200 512"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1968 764 740"
+}
+{
+"classname" "ammo_shells"
+"origin" "-952 -1832 976"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "200"
+"classname" "light"
+"origin" "-232 -800 68"
+}
+{
+"origin" "-224 -1768 56"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "200"
+"classname" "light"
+"origin" "-920 -720 68"
+}
+{
+"model" "*17"
+"spawnflags" "2048"
+"target" "t110"
+"classname" "trigger_once"
+}
+{
+"model" "*18"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "1144 -120 592"
+"classname" "target_speaker"
+"noise" "world/mach1.wav"
+"team" "mover1"
+"targetname" "t81"
+"spawnflags" "2"
+}
+{
+"origin" "848 -1516 824"
+"team" "mover2"
+"noise" "world/mach1.wav"
+"classname" "target_speaker"
+"spawnflags" "2"
+"targetname" "t81"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1976 136 560"
+}
+{
+"targetname" "t350"
+"classname" "target_help"
+"origin" "-736 -760 72"
+"spawnflags" "2048"
+"message" "Destroy all resistance!"
+}
+{
+"origin" "-416 -2440 728"
+"angle" "0"
+"spawnflags" "3"
+"targetname" "t350"
+"classname" "monster_berserk"
+}
+{
+"origin" "-280 -1824 856"
+"angle" "315"
+"spawnflags" "3"
+"targetname" "t350"
+"classname" "monster_soldier"
+}
+{
+"item" "ammo_grenades"
+"origin" "1064 -1496 584"
+"angle" "0"
+"spawnflags" "2"
+"targetname" "t350"
+"classname" "monster_gunner"
+}
+{
+"origin" "1744 -896 752"
+"item" "ammo_shells"
+"spawnflags" "3"
+"targetname" "t350"
+"angle" "0"
+"classname" "monster_soldier"
+}
+{
+"origin" "1256 -232 656"
+"angle" "270"
+"spawnflags" "771"
+"targetname" "t350"
+"classname" "monster_flyer"
+}
+{
+"origin" "1192 -224 624"
+"spawnflags" "3"
+"targetname" "t350"
+"classname" "monster_flyer"
+}
+{
+"origin" "1880 -248 528"
+"item" "ammo_bullets"
+"spawnflags" "3"
+"angle" "0"
+"targetname" "t350"
+"classname" "monster_infantry"
+}
+{
+"origin" "968 544 744" //stuck monster at "968 544 720" (it spawns after Tectonic Stabilizer objectives are completed)
+"angle" "0"
+"spawnflags" "3"
+"targetname" "t350"
+"classname" "monster_berserk"
+}
+{
+"origin" "-296 -872 200"
+"targetname" "t350"
+"classname" "monster_flyer"
+"spawnflags" "770"
+"angle" "180"
+}
+{
+"origin" "-296 -792 200"
+"angle" "180"
+"spawnflags" "3"
+"targetname" "t350"
+"classname" "monster_flyer"
+}
+{
+"origin" "-768 -1232 16"
+"targetname" "t350"
+"classname" "monster_soldier"
+"angle" "270"
+"spawnflags" "771"
+}
+{
+"origin" "-784 -1168 16"
+"item" "ammo_shells"
+"targetname" "t350"
+"classname" "monster_soldier"
+"angle" "270"
+"spawnflags" "3"
+}
+{
+"origin" "-624 -1024 16"
+"targetname" "t350"
+"classname" "monster_soldier"
+"angle" "180"
+"spawnflags" "3"
+}
+{
+"origin" "-728 -1264 16"
+"item" "ammo_shells"
+"spawnflags" "259"
+"targetname" "t350"
+"angle" "270"
+"classname" "monster_soldier"
+}
+{
+"origin" "-288 -704 200"
+"light" "175"
+"classname" "light"
+}
+{
+"origin" "2048 -24 936"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_flyer"
+"targetname" "t300"
+}
+{
+"origin" "2056 -80 896"
+"angle" "225"
+"spawnflags" "769"
+"classname" "monster_flyer"
+"targetname" "t300"
+}
+{
+"message" "Use Cargo elevator\n to enter Warehouse."
+"spawnflags" "2049"
+"targetname" "t350"
+"origin" "-704 -744 72"
+"classname" "target_help"
+}
+{
+"origin" "2000 880 584"
+"targetname" "mineend"
+"classname" "info_player_coop"
+"angle" "270"
+}
+{
+"origin" "2000 952 584"
+"targetname" "mineend"
+"classname" "info_player_coop"
+"angle" "270"
+}
+{
+"origin" "1936 952 584"
+"targetname" "mineend"
+"classname" "info_player_coop"
+"angle" "270"
+}
+{
+"origin" "1944 888 584"
+"targetname" "mineend"
+"angle" "270"
+"classname" "info_player_coop"
+}
+{
+"spawnflags" "0"
+"classname" "info_player_coop"
+"origin" "-1904 -2448 24"
+"angle" "90"
+}
+{
+"spawnflags" "0"
+"classname" "info_player_coop"
+"origin" "-2056 -2432 24"
+"angle" "90"
+}
+{
+"spawnflags" "0"
+"classname" "info_player_coop"
+"origin" "-1952 -2448 24"
+"angle" "90"
+}
+{
+"spawnflags" "0"
+"angle" "90"
+"origin" "-1856 -2448 24"
+"classname" "info_player_coop"
+}
+{
+"spawnflags" "2048"
+"origin" "-672 -672 120"
+"map" "reu1_.cin+*rware1$unitstart"
+"targetname" "t351"
+"classname" "target_changelevel"
+}
+{
+"model" "*19"
+"spawnflags" "2048"
+"target" "t351"
+"classname" "trigger_once"
+}
+{
+"spawnflags" "2048"
+"origin" "-800 -672 8"
+"targetname" "t350"
+"target" "booger2"
+"classname" "trigger_relay"
+}
+{
+"origin" "-760 -668 56"
+"target" "t350"
+"spawnflags" "2052"
+"classname" "target_crosslevel_target"
+}
+{
+"spawnflags" "2048"
+"origin" "-848 -552 -8"
+"delay" "3"
+"targetname" "t348"
+"target" "t349"
+"classname" "trigger_relay"
+}
+{
+"model" "*20"
+"targetname" "booger2"
+"target" "t347"
+"spawnflags" "2052"
+"wait" "5"
+"classname" "trigger_multiple"
+}
+{
+"model" "*21"
+"spawnflags" "2080"
+"targetname" "t348"
+"classname" "func_plat"
+"lip" "268"
+"_minlight" ".2"
+}
+{
+"model" "*22"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t346"
+"delay" "2.5"
+"message" "Security lockdown in effect."
+"origin" "-892 -676 8"
+}
+{
+"model" "*23"
+"spawnflags" "2056"
+"targetname" "booger1"
+"classname" "trigger_multiple"
+"message" "Warehouse access denied."
+"target" "t346"
+"wait" "10"
+}
+{
+"model" "*24"
+"spawnflags" "2056"
+"target" "t348"
+"targetname" "t347"
+"classname" "func_button"
+"angle" "90"
+}
+{
+"angle" "270"
+"classname" "monster_flyer"
+"origin" "-816 -736 256"
+"target" "t353"
+}
+{
+"classname" "monster_flyer"
+"angle" "90"
+"spawnflags" "768"
+"origin" "-856 -688 256"
+}
+{
+"origin" "288 -200 924"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "264 -224 924"
+"classname" "light"
+"light" "150"
+}
+{
+"classname" "item_health"
+"origin" "-528 -856 0"
+}
+{
+"classname" "monster_infantry"
+"spawnflags" "256"
+"origin" "-384 -680 8"
+"angle" "270"
+"targetname" "t353"
+}
+{
+"classname" "ammo_grenades"
+"origin" "-456 -760 64"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-256 -936 0"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-296 -936 0"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-216 -936 0"
+}
+{
+"classname" "item_health_large"
+"origin" "-936 -1064 0"
+}
+{
+"classname" "item_armor_jacket"
+"origin" "-608 -768 64"
+}
+{
+"classname" "ammo_shells"
+"origin" "-928 -672 0"
+}
+{
+"angle" "0"
+"spawnflags" "2049"
+"classname" "point_combat"
+"targetname" "t345"
+"origin" "-912 -1024 -8"
+}
+{
+"angle" "270"
+"classname" "monster_soldier_light"
+"origin" "-744 -928 8"
+"target" "t345"
+"spawnflags" "1"
+}
+{
+"classname" "monster_soldier_light"
+"angle" "180"
+"spawnflags" "1025"
+"origin" "-560 -1024 8"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2049"
+"angle" "90"
+"origin" "-768 -800 -16"
+}
+{
+"classname" "weapon_supershotgun"
+"origin" "-808 -744 0"
+}
+{
+"classname" "monster_berserk"
+"angle" "180"
+"spawnflags" "769"
+"origin" "-240 -864 8"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"targetname" "t343"
+"origin" "-368 -896 8"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t343"
+"origin" "-748 -968 8"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-764 -904 8"
+"targetname" "t335"
+}
+{
+"model" "*25"
+"mass" "100"
+"health" "1"
+"classname" "func_explosive"
+"dmg" "50"
+}
+{
+"model" "*26"
+"classname" "func_explosive"
+"health" "1"
+"mass" "100"
+"dmg" "50"
+}
+{
+"origin" "-608 -704 264"
+"classname" "light"
+"light" "175"
+}
+{
+"classname" "light"
+"light" "175"
+"origin" "-288 -832 200"
+}
+{
+"origin" "-864 -832 264"
+"classname" "light"
+"light" "175"
+}
+{
+"light" "175"
+"classname" "light"
+"origin" "-608 -832 264"
+}
+{
+"origin" "-864 -704 264"
+"classname" "light"
+"light" "175"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-752 -832 128"
+}
+{
+"origin" "480 -72 648"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "416 -72 648"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "392 -96 648"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "392 -288 648"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "392 -224 648"
+"classname" "light"
+"light" "150"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-168 -2016 56"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-544 -1768 64"
+}
+{
+"origin" "-448 -2416 756"
+"classname" "light"
+"light" "180"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-608 -960 200"
+"classname" "light"
+"light" "175"
+}
+{
+"light" "175"
+"classname" "light"
+"origin" "-864 -960 200"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-688 -512 344"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-664 -640 88"
+}
+{
+"origin" "-472 -2016 600"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"origin" "-480 -704 200"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "-784 -512 344"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-808 -640 88"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-1256 -1728 1172"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"model" "*27"
+"targetname" "t349"
+"team" "vator1"
+"angle" "180"
+"classname" "func_door"
+"spawnflags" "2056"
+}
+{
+"model" "*28"
+"targetname" "t349"
+"team" "vator1"
+"angle" "0"
+"classname" "func_door"
+"spawnflags" "2056"
+}
+{
+"model" "*29"
+"target" "getplat"
+"message" "Lift activated."
+"wait" "5"
+"lip" "3"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"origin" "-72 -1320 840"
+"classname" "item_health_large"
+"spawnflags" "1536"
+}
+{
+"angle" "270"
+"origin" "72 -1184 952"
+"spawnflags" "769"
+"classname" "monster_gunner"
+"target" "t169"
+}
+{
+"origin" "-152 -2080 544"
+"light" "75"
+"classname" "light"
+}
+{
+"model" "*30"
+"target" "t160"
+"message" "Lift activated."
+"wait" "-1"
+"angle" "0"
+"classname" "func_button"
+"sounds" "4"
+}
+{
+"classname" "monster_gunner"
+"spawnflags" "769"
+"angle" "315"
+"origin" "744 -1296 552"
+}
+{
+"classname" "item_armor_jacket"
+"origin" "-576 -1824 976"
+}
+{
+"spawnflags" "2050"
+"noise" "world/amb10.wav"
+"classname" "target_speaker"
+"team" "oremover"
+"origin" "-760 -1920 568"
+"attenuation" "1"
+"targetname" "sound"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t339"
+"target" "t340"
+"origin" "1176 720 528"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t340"
+"target" "t341"
+"origin" "1600 728 664"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t341"
+"target" "t342"
+"origin" "1600 480 712"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t342"
+"origin" "1288 488 704"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t339"
+"origin" "1848 448 528"
+}
+{
+"origin" "-368 -1392 8"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t333"
+"target" "t334"
+}
+{
+"origin" "-748 -1384 8"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t334"
+"target" "t335"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "-360 -1840 8"
+"target" "t333"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t331"
+"target" "t332"
+"origin" "-920 -1656 1024"
+}
+{
+"spawnflags" "1"
+"classname" "hint_path"
+"targetname" "t332"
+"origin" "-920 -1984 992"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"target" "t331"
+"origin" "-1168 -1656 1128"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t329"
+"target" "t330"
+"origin" "-920 -2304 1040"
+}
+{
+"spawnflags" "1"
+"classname" "hint_path"
+"targetname" "t330"
+"origin" "-912 -1984 992"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t329"
+"origin" "-1208 -2312 1128"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t327"
+"target" "t328"
+"origin" "64 -1920 848"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t328"
+"origin" "64 -1656 848"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t327"
+"origin" "-384 -1920 848"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t323"
+"target" "t324"
+"origin" "1400 -1160 392"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t324"
+"target" "t325"
+"origin" "1856 -1192 392"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t325"
+"target" "t326"
+"origin" "1880 -1464 392"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t323"
+"origin" "1392 -608 560"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t326"
+"origin" "1304 -1448 560"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t319"
+"target" "t320"
+"origin" "1648 -256 752"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t320"
+"target" "t321"
+"origin" "1816 -256 752"
+}
+{
+"classname" "hint_path"
+"targetname" "t321"
+"target" "t322"
+"origin" "1824 -512 752"
+}
+{
+"classname" "hint_path"
+"targetname" "t322"
+"spawnflags" "1"
+"origin" "1960 -504 752"
+}
+{
+"classname" "hint_path"
+"target" "t319"
+"spawnflags" "2048"
+"origin" "1648 -432 752"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t316"
+"target" "t317"
+"origin" "1632 -24 480"
+}
+{
+"classname" "hint_path"
+"targetname" "t317"
+"target" "t318"
+"origin" "1752 -24 784"
+}
+{
+"classname" "hint_path"
+"targetname" "t318"
+"spawnflags" "1"
+"origin" "2016 -40 784"
+}
+{
+"classname" "hint_path"
+"target" "t316"
+"spawnflags" "2048"
+"origin" "1416 -344 512"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "1792 -896 752"
+"target" "t314"
+}
+{
+"classname" "hint_path"
+"origin" "2136 -896 768"
+"targetname" "t314"
+"target" "t315"
+}
+{
+"classname" "hint_path"
+"spawnflags" "1"
+"origin" "2136 -368 776"
+"targetname" "t315"
+}
+{
+"classname" "item_health_large"
+"origin" "336 -328 856"
+}
+{
+"classname" "monster_soldier"
+"spawnflags" "1"
+"origin" "888 -264 904"
+"angle" "180"
+}
+{
+"classname" "item_health_small"
+"origin" "712 -424 528"
+}
+{
+"classname" "item_health_small"
+"origin" "672 -424 528"
+}
+{
+"classname" "item_health_small"
+"origin" "632 -424 528"
+}
+{
+"origin" "1592 -232 760"
+"classname" "item_health"
+}
+{
+"classname" "item_health"
+"origin" "1704 -880 744"
+"spawnflags" "1536"
+}
+{
+"classname" "item_health_large"
+"origin" "2064 -120 792"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2080"
+"angle" "180"
+"origin" "1272 648 760"
+}
+{
+"classname" "weapon_shotgun"
+"origin" "1240 592 704"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t313"
+"origin" "1264 640 720"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1264 632 752"
+}
+{
+"model" "*31"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"dmg" "50"
+"target" "t313"
+"health" "1"
+}
+{
+"origin" "2224 -288 760"
+"classname" "ammo_grenades"
+}
+{
+"spawnflags" "1536"
+"origin" "2200 -376 760"
+"classname" "weapon_grenadelauncher"
+}
+{
+"origin" "840 -1272 576"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "-184 -1632 936"
+"_color" "0.000000 0.000000 1.000000"
+"light" "125"
+}
+{
+"origin" "-160 -1512 864"
+"classname" "weapon_proxlauncher"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "160"
+"classname" "light"
+"origin" "1408 -656 748"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "195"
+"classname" "light"
+"origin" "2204 -384 804"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "160"
+"classname" "light"
+"origin" "804 -480 780"
+}
+{
+"origin" "1984 -528 804"
+"classname" "light"
+"light" "195"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "150"
+"classname" "light"
+"origin" "1144 -232 684"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "180"
+"classname" "light"
+"origin" "1020 -288 572"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "1956 -48 572"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "684 -480 780"
+}
+{
+"origin" "928 -1648 712"
+"classname" "light"
+"light" "160"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "284 -464 780"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "192 -1700 908"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-64 -1700 908"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-1256 -2240 1172"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-928 -2328 1076"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "200"
+"classname" "light"
+"origin" "-512 -680 68"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-768 -2456 692"
+}
+{
+"origin" "-928 -1640 1076"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-792 -1856 72"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-608 -1048 72"
+"classname" "light"
+"light" "200"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"origin" "-480 -1768 584"
+"classname" "light"
+"light" "175"
+"_color" "1.000000 1.000000 0.501961"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-792 -2112 72"
+}
+{
+"classname" "func_group"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-168 -1888 584"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-416 -2200 584"
+}
+{
+"classname" "func_group"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-192 -2456 764"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "-792 -2144 596"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"light" "175"
+"classname" "light"
+"origin" "384 124 780"
+}
+{
+"origin" "2004 1072 648"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "888 -1288 528"
+"angle" "135"
+"spawnflags" "2"
+"classname" "misc_deadsoldier"
+}
+{
+"classname" "light"
+"origin" "-176 -1480 944"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"spawnflags" "2048"
+"origin" "-400 -1840 848"
+"targetname" "t312"
+"target" "ammo_shells"
+"classname" "target_spawner"
+}
+{
+"spawnflags" "2048"
+"origin" "-400 -1840 864"
+"dmg" "25"
+"targetname" "t312"
+"classname" "target_explosion"
+}
+{
+"model" "*32"
+"spawnflags" "2048"
+"target" "t312"
+"classname" "func_explosive"
+"health" "10"
+}
+{
+"origin" "1224 -1360 840"
+"angle" "180"
+"classname" "monster_flyer"
+}
+{
+"origin" "1776 -832 752"
+"target" "t299"
+"targetname" "t300"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"spawnflags" "2048"
+"origin" "736 -288 528"
+"target" "ammo_grenades"
+"targetname" "t113"
+"classname" "target_spawner"
+}
+{
+"spawnflags" "2048"
+"origin" "416 -288 528"
+"targetname" "t115"
+"target" "ammo_bullets"
+"classname" "target_spawner"
+}
+{
+"origin" "800 -96 528"
+"targetname" "t114"
+"target" "ammo_shells"
+"classname" "target_spawner"
+}
+{
+"model" "*33"
+"target" "t311"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"origin" "792 -120 656"
+"targetname" "t311"
+"angle" "180"
+"classname" "monster_flyer"
+}
+{
+"targetname" "t310"
+"origin" "328 -736 736"
+"angle" "0"
+"classname" "monster_gunner"
+"spawnflags" "1"
+"item" "ammo_grenades"
+}
+{
+"target" "t310"
+"origin" "304 -328 1008"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_flyer"
+}
+{
+"origin" "312 -368 1008"
+"angle" "0"
+"spawnflags" "769"
+"classname" "monster_flyer"
+}
+{
+"origin" "1504 -608 888"
+"angle" "180"
+"classname" "monster_flyer"
+"spawnflags" "256"
+}
+{
+"origin" "1504 -560 864"
+"angle" "180"
+"spawnflags" "768"
+"classname" "monster_flyer"
+}
+{
+"origin" "1176 720 520"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_berserk"
+}
+{
+"origin" "1760 272 520"
+"spawnflags" "769"
+"angle" "0"
+"classname" "monster_berserk"
+}
+{
+"origin" "1984 688 568"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"model" "*34"
+"spawnflags" "2048"
+"targetname" "mines1"
+"classname" "trigger_multiple"
+"message" "Mine doors are locked."
+"wait" "-1"
+}
+{
+"origin" "1952 -416 760"
+"classname" "ammo_shells"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "572 -1504 965"
+}
+{
+"origin" "880 -560 896"
+"classname" "ammo_shells"
+}
+{
+"origin" "288 -704 728"
+"classname" "item_health_small"
+}
+{
+"origin" "288 -744 728"
+"classname" "item_health_small"
+}
+{
+"origin" "680 -368 728"
+"classname" "ammo_grenades"
+}
+{
+"origin" "1768 320 512"
+"spawnflags" "768"
+"classname" "ammo_bullets"
+}
+{
+"origin" "1768 224 512"
+"classname" "ammo_bullets"
+}
+{
+"origin" "1704 -376 760"
+"classname" "ammo_tesla"
+}
+{
+"origin" "2168 -704 760"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2168 -656 760"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2168 -752 760"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1936 -88 792"
+"classname" "ammo_prox"
+}
+{
+"origin" "1008 -272 520"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1016 -232 508"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1000 -312 520"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "1536"
+"origin" "1620 -1128 376"
+"classname" "ammo_grenades"
+}
+{
+"classname" "item_health_small"
+"origin" "1584 -1408 496"
+}
+{
+"origin" "1528 -1408 524"
+"classname" "item_health_small"
+}
+{
+"origin" "1632 -1416 472"
+"classname" "item_health_small"
+}
+{
+"spawnflags" "1536"
+"origin" "1280 -1296 544"
+"classname" "ammo_grenades"
+}
+{
+"origin" "824 -1664 544"
+"classname" "ammo_bullets"
+}
+{
+"origin" "1216 -1120 880"
+"classname" "ammo_shells"
+}
+{
+"origin" "24 -1160 944"
+"classname" "ammo_shells"
+}
+{
+"model" "*35"
+"spawnflags" "2048"
+"target" "t265"
+"classname" "trigger_once"
+}
+{
+"origin" "1872 -528 680"
+"spawnflags" "1025"
+"classname" "monster_flyer"
+"angle" "45"
+}
+{
+"origin" "1768 -496 680"
+"spawnflags" "769"
+"classname" "monster_flyer"
+"angle" "90"
+}
+{
+"origin" "1856 -464 680"
+"spawnflags" "1"
+"classname" "monster_flyer"
+"angle" "90"
+}
+{
+"origin" "2192 -256 680"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_flyer"
+}
+{
+"origin" "608 120 728"
+"angle" "90"
+"classname" "monster_infantry"
+}
+{
+"origin" "384 -720 504"
+"spawnflags" "2052"
+"classname" "misc_deadsoldier"
+}
+{
+"spawnflags" "2048"
+"origin" "440 -704 520"
+"classname" "item_bandolier"
+}
+{
+"spawnflags" "2048"
+"origin" "576 -632 624"
+"targetname" "t308"
+"classname" "target_secret"
+}
+{
+"model" "*36"
+"spawnflags" "2048"
+"target" "t308"
+"message" "You found a secret!"
+"classname" "trigger_once"
+}
+{
+"origin" "2096 -384 416"
+"classname" "item_adrenaline"
+"spawnflags" "2048"
+}
+{
+"model" "*37"
+"target" "t307"
+"angle" "270"
+"classname" "func_button"
+"message" "Lift activated."
+}
+{
+"model" "*38"
+"target" "t307"
+"message" "Lift activated."
+"classname" "func_button"
+"angle" "270"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "-888 -1856 1015"
+}
+{
+"model" "*39"
+"spawnflags" "2048"
+"target" "t306"
+"classname" "trigger_once"
+}
+{
+"model" "*40"
+"spawnflags" "2048"
+"target" "t305"
+"classname" "trigger_once"
+}
+{
+"origin" "1856 -1168 384"
+"targetname" "t306"
+"angle" "270"
+"classname" "monster_berserk"
+"spawnflags" "1"
+}
+{
+"targetname" "t305"
+"origin" "1880 -1432 384"
+"angle" "180"
+"classname" "monster_infantry"
+"spawnflags" "1"
+"item" "ammo_bullets"
+}
+{
+"origin" "736 -1296 552"
+"spawnflags" "1025"
+"angle" "315"
+"classname" "monster_berserk"
+}
+{
+"model" "*41"
+"spawnflags" "2048"
+"target" "t301"
+"classname" "trigger_once"
+}
+{
+"target" "t302"
+"targetname" "t301"
+"origin" "1368 -1160 888"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_berserk"
+}
+{
+"origin" "-1256 -1632 1104"
+"classname" "item_health_large"
+}
+{
+"spawnflags" "2048"
+"origin" "-544 -2008 536"
+"target" "t298"
+"targetname" "oretrain"
+"classname" "trigger_relay"
+"delay" "20"
+}
+{
+"spawnflags" "2048"
+"delay" "16.3"
+"origin" "-576 -2008 536"
+"target" "t298"
+"targetname" "oretrain"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "-576 -1976 536"
+"targetname" "oretrain"
+"delay" "10"
+"target" "t298"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "-544 -1976 536"
+"delay" "13.5"
+"targetname" "oretrain"
+"target" "t298"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2"
+"targetname" "t298"
+"origin" "-576 -2200 536"
+"team" "mony1"
+"volume" "1"
+"attenuation" "1"
+"noise" "world/mach1.wav"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "2048"
+"delay" "26"
+"origin" "-632 -1760 704"
+"targetname" "oretrain"
+"target" "sound"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "-600 -1760 704"
+"target" "sound"
+"targetname" "oretrain"
+"classname" "trigger_relay"
+}
+{
+"attenuation" "1"
+"origin" "-768 -1912 568"
+"targetname" "sound"
+"team" "oremover"
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1264 -1700 868"
+"classname" "light"
+}
+{
+"model" "*42"
+"origin" "1056 -1480 724"
+"_minlight" ".1"
+"speed" "60"
+"spawnflags" "5"
+"classname" "func_rotating"
+"dmg" "80"
+}
+{
+"model" "*43"
+"origin" "1056 -1524 724"
+"_minlight" ".05"
+"classname" "func_rotating"
+"spawnflags" "7"
+"speed" "60"
+"dmg" "80"
+}
+{
+"origin" "528 -1304 880"
+"classname" "item_health"
+}
+{
+"origin" "544 -1248 880"
+"classname" "item_health"
+}
+{
+"origin" "456 -1400 880"
+"spawnflags" "0"
+"targetname" "t297"
+"angle" "135"
+"classname" "monster_flyer"
+}
+{
+"origin" "544 -1384 880"
+"spawnflags" "1536"
+"targetname" "t297"
+"angle" "135"
+"classname" "monster_flyer"
+}
+{
+"targetname" "t302"
+"origin" "472 -1128 880"
+"spawnflags" "5"
+"angle" "270"
+"classname" "monster_soldier"
+}
+{
+"spawnflags" "2048"
+"origin" "1168 -1616 832"
+"targetname" "t296"
+"message" "You found a secret area!"
+"classname" "target_secret"
+}
+{
+"origin" "1136 -1584 800"
+"classname" "item_armor_body"
+}
+{
+"origin" "1088 -256 512"
+"classname" "dm_tag_token"
+}
+{
+"origin" "888 -304 896"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-1144 -2248 160"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-760 -2040 974"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-696 -2040 974"
+}
+{
+"origin" "-792 -2144 974"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-792 -2080 974"
+}
+{
+"origin" "-792 -1888 974"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-792 -1824 974"
+}
+{
+"origin" "-696 -1928 974"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-512 -1752 528"
+"classname" "ammo_shells"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-560 -1888 1080"
+}
+{
+"origin" "-192 -2160 912"
+"spawnflags" "1"
+"targetname" "t282"
+"angle" "270"
+"classname" "monster_flyer"
+}
+{
+"origin" "-760 -1952 704"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "-1104 -2192 152"
+"spawnflags" "2056"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "-2008 -1912 16"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "456 56 721"
+}
+{
+"origin" "456 -8 721"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "568 -8 721"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*44"
+"origin" "272 -544 712"
+"wait" "-1"
+"targetname" "t90"
+"spawnflags" "2178"
+"sounds" "2"
+"message" "This door cannot be opened from here."
+"distance" "90"
+"_minlight" ".2"
+"classname" "func_door_rotating"
+}
+{
+"origin" "912 -1208 993"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "752 -1264 965"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "912 -1264 965"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1072 -1264 965"
+}
+{
+"origin" "1072 -1248 845"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1336 432 752"
+}
+{
+"origin" "1296 744 856"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"origin" "1528 600 552"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"style" "32"
+"origin" "1264 648 760"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "t313"
+}
+{
+"origin" "1256 360 752"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "2000 352 400"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1408 592 752"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1168 536 752"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1424 368 752"
+}
+{
+"origin" "1528 648 560"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"origin" "1528 656 600"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1936 816 656"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1336 752 856"
+}
+{
+"classname" "light"
+"origin" "1736 432 768"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"classname" "light"
+"origin" "1688 448 816"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"classname" "light"
+"origin" "1688 568 816"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"classname" "light"
+"origin" "1560 208 704"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"classname" "light"
+"origin" "1552 208 624"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"origin" "720 -1576 701"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "912 -1248 845"
+}
+{
+"classname" "light"
+"origin" "1688 -1104 416"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"classname" "light"
+"origin" "1528 -1136 400"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1072 -1208 993"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "752 -1208 993"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1232 -1208 993"
+}
+{
+"origin" "928 -1304 653"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "928 -1728 853"
+"light" "75"
+"classname" "light"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1504 -1376 568"
+"light" "100"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1704 -1224 448"
+"light" "100"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1864 -1136 424"
+"light" "100"
+}
+{
+"light" "175"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "592 -1104 928"
+"classname" "light"
+}
+{
+"light" "175"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1392 -1104 928"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1528 -1136 904"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "576 -1600 701"
+}
+{
+"origin" "664 -1600 637"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1760 -1112 424"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1560 -1136 432"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "784 -1256 680"
+"light" "100"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "776 -1256 624"
+"light" "100"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1648 -1400 480"
+"light" "100"
+}
+{
+"origin" "486 -1492 853"
+"light" "75"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "752 -1248 845"
+}
+{
+"classname" "light"
+"origin" "1392 -544 544"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1920 -1184 416"
+"light" "100"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1752 -1400 496"
+"light" "100"
+}
+{
+"classname" "light"
+"origin" "1088 -1448 552"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"classname" "light"
+"origin" "1032 -1416 544"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"classname" "light"
+"origin" "1344 -1624 800"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"light" "100"
+"origin" "1744 -1232 456"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+}
+{
+"light" "100"
+"origin" "688 -1272 576"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+}
+{
+"light" "100"
+"origin" "1584 -1384 504"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+}
+{
+"light" "100"
+"origin" "1568 -1384 568"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1000 -1520 544"
+"classname" "light"
+}
+{
+"origin" "1656 -80 837"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1656 32 837"
+}
+{
+"model" "*45"
+"targetname" "t307"
+"lip" "8"
+"_minlight" ".3"
+"speed" "250"
+"classname" "func_plat2"
+}
+{
+"target" "t292"
+"targetname" "t291"
+"classname" "path_corner"
+"origin" "896 -1796 824"
+}
+{
+"target" "t291"
+"targetname" "t290"
+"classname" "path_corner"
+"origin" "896 -1608 824"
+}
+{
+"target" "t290"
+"targetname" "t289"
+"classname" "path_corner"
+"origin" "896 -1256 720"
+}
+{
+"target" "t289"
+"targetname" "t288"
+"classname" "path_corner"
+"origin" "896 -1032 720"
+}
+{
+"target" "t288"
+"targetname" "t74"
+"classname" "path_corner"
+"origin" "1112 -1032 720"
+}
+{
+"target" "t80"
+"targetname" "t292"
+"origin" "896 -1796 1156"
+"classname" "path_corner"
+}
+{
+"target" "t309"
+"message" "Mine doors opened."
+"delay" "2"
+"origin" "-1200 -1824 1128"
+"killtarget" "mines1"
+"targetname" "t259"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"targetname" "t259"
+"classname" "target_speaker"
+"noise" "world/uplink2.wav"
+"spawnflags" "4"
+"origin" "-1176 -1984 1240"
+}
+{
+"origin" "-760 -1832 1168"
+"spawnflags" "1792"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-1264 -1768 1104"
+"classname" "ammo_cells"
+"spawnflags" "5888"
+}
+{
+"origin" "-1264 -1808 1104"
+"classname" "ammo_cells"
+"spawnflags" "5888"
+}
+{
+"origin" "-1040 -1984 1104"
+"spawnflags" "5888"
+"classname" "weapon_bfg"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "-784 -2192 1176"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-1240 -2128 1303"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "-888 -2112 1011"
+}
+{
+"origin" "-704 -1856 973"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "-968 -1984 999"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-1160 -2128 1303"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-744 -2208 1172"
+}
+{
+"origin" "-1240 -1840 1303"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"origin" "-1184 -2080 1151"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"origin" "-824 -2208 1174"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-824 -1824 1175"
+}
+{
+"origin" "-744 -1824 1172"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"style" "33"
+"targetname" "t295"
+"classname" "light"
+"light" "150"
+"origin" "-1224 -1952 1652"
+"spawnflags" "1"
+}
+{
+"style" "33"
+"targetname" "t295"
+"light" "150"
+"classname" "light"
+"origin" "-1224 -2016 1652"
+"spawnflags" "1"
+}
+{
+"style" "34"
+"targetname" "t259"
+"classname" "light"
+"light" "150"
+"origin" "-1224 -2016 1400"
+"spawnflags" "1"
+}
+{
+"style" "34"
+"targetname" "t259"
+"light" "150"
+"classname" "light"
+"origin" "-1224 -1952 1400"
+"spawnflags" "1"
+}
+{
+"style" "35"
+"targetname" "t294"
+"classname" "light"
+"light" "150"
+"origin" "-1224 -2016 1528"
+"spawnflags" "1"
+}
+{
+"style" "35"
+"targetname" "t294"
+"light" "150"
+"classname" "light"
+"origin" "-1224 -1952 1528"
+"spawnflags" "1"
+}
+{
+"target" "t295"
+"targetname" "t294"
+"classname" "trigger_relay"
+"delay" "2"
+"origin" "-1208 -1992 1608"
+}
+{
+"target" "t294"
+"targetname" "t259"
+"classname" "trigger_relay"
+"delay" "2"
+"origin" "-1208 -1992 1464"
+}
+{
+"origin" "-992 -1984 1193"
+"classname" "light"
+"light" "175"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-760 -1928 974"
+}
+{
+"classname" "light"
+"_color" "0.117647 0.603922 1.000000"
+"style" "2"
+"light" "250"
+"origin" "-1112 -1984 991"
+}
+{
+"light" "175"
+"classname" "light"
+"origin" "-856 -2264 1032"
+}
+{
+"origin" "-784 -1856 1176"
+"angle" "180"
+"spawnflags" "1028"
+"classname" "monster_soldier"
+}
+{
+"origin" "-1016 -1848 1112"
+"angle" "0"
+"spawnflags" "4"
+"classname" "monster_soldier"
+}
+{
+"origin" "-1016 -2112 1112"
+"angle" "0"
+"classname" "monster_soldier"
+"spawnflags" "4"
+}
+{
+"origin" "-784 -2112 1176"
+"classname" "monster_soldier"
+"spawnflags" "1028"
+"angle" "180"
+}
+{
+"targetname" "t259"
+"spawnflags" "1"
+"origin" "-1232 -1984 1552"
+"classname" "monster_flyer"
+}
+{
+"origin" "-1168 -1984 1216"
+"classname" "point_combat"
+}
+{
+"model" "*46"
+"classname" "trigger_once"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum2.wav"
+"spawnflags" "1"
+"origin" "-1200 -1984 1208"
+}
+{
+"targetname" "t309"
+"spawnflags" "2049"
+"classname" "target_help"
+"message" "Find your way to the\nThaelite Mines."
+"origin" "-1088 -2104 1200"
+}
+{
+"classname" "ammo_grenades"
+"origin" "-760 -1952 1168"
+}
+{
+"classname" "item_health_small"
+"origin" "-760 -2336 1008"
+}
+{
+"origin" "-760 -2296 1008"
+"classname" "item_health_small"
+}
+{
+"targetname" "t309"
+"origin" "-1056 -1984 1176"
+"classname" "target_goal"
+"spawnflags" "2048"
+}
+{
+"origin" "-944 -1984 1000"
+"spawnflags" "1"
+"noise" "world/force1.wav"
+"classname" "target_speaker"
+}
+{
+"light" "175"
+"classname" "light"
+"origin" "-856 -1704 1025"
+}
+{
+"targetname" "t259"
+"attenuation" "3"
+"classname" "target_speaker"
+"noise" "world/lite_on1.wav"
+"volume" "1"
+"origin" "-1160 -1928 1192"
+}
+{
+"classname" "light"
+"spawnflags" "1"
+"light" "200"
+"origin" "-1224 -2080 1256"
+}
+{
+"light" "200"
+"spawnflags" "1"
+"classname" "light"
+"origin" "-1224 -1888 1256"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-1160 -1840 1303"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-1184 -1888 1151"
+}
+{
+"origin" "-760 -2016 1168"
+"classname" "ammo_shells"
+}
+{
+"origin" "-808 -1984 1176"
+"spawnflags" "772"
+"angle" "180"
+"classname" "monster_infantry"
+}
+{
+"targetname" "t259"
+"origin" "-1176 -2000 1240"
+"spawnflags" "4"
+"noise" "world/uplink2.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "t259"
+"classname" "target_speaker"
+"noise" "world/uplink2.wav"
+"spawnflags" "4"
+"origin" "-1176 -1968 1240"
+}
+{
+"origin" "-1232 -1984 1256"
+"light" "200"
+"spawnflags" "1"
+"classname" "light"
+}
+{
+"model" "*47"
+"spawnflags" "2048"
+"target" "t259"
+"wait" "-1"
+"classname" "func_button"
+"angle" "180"
+"message" "Communication uplink established."
+"sounds" "4"
+}
+{
+"origin" "-1176 -1888 1136"
+"classname" "light"
+"spawnflags" "0"
+"light" "125"
+}
+{
+"origin" "2136 -800 824"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "2136 -608 824"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1272 -1720 820"
+"classname" "light"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "1752 -1400 408"
+"light" "50"
+}
+{
+"classname" "light"
+"origin" "1332 -1712 820"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1344 -1696 868"
+"classname" "light"
+}
+{
+"style" "36"
+"targetname" "t287"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1240 -1632 836"
+"classname" "light"
+}
+{
+"style" "36"
+"targetname" "t287"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1288 -1592 836"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1336 -472 528"
+"classname" "light"
+}
+{
+"origin" "-752 -1144 72"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-752 -1312 72"
+"classname" "light"
+"light" "100"
+}
+{
+"model" "*48"
+"targetname" "getplat"
+"classname" "func_plat2"
+"speed" "250"
+"_minlight" ".3"
+"lip" "12"
+}
+{
+"classname" "monster_infantry"
+"angle" "180"
+"spawnflags" "1"
+"origin" "704 -112 736"
+"targetname" "t286"
+}
+{
+"classname" "ammo_prox"
+"origin" "-408 -2352 720"
+}
+{
+"model" "*49"
+"classname" "trigger_once"
+"target" "t282"
+}
+{
+"classname" "ammo_bullets"
+"origin" "-760 -1792 720"
+}
+{
+"origin" "2224 -328 744"
+"angle" "180"
+"spawnflags" "1540"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "840 -1272 544"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-232 -2464 720"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-152 -1784 528"
+"classname" "item_health"
+}
+{
+"origin" "-152 -1824 528"
+"classname" "item_health"
+}
+{
+"origin" "24 -1944 848"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-784 -1888 720"
+"classname" "weapon_machinegun"
+}
+{
+"origin" "-152 -1552 848"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "48 -1616 848"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-600 -1936 848"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-600 -1888 848"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-600 -1840 848"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-600 -1976 848"
+"classname" "item_armor_shard"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-544 -2160 840"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-608 -2160 840"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-632 -2144 840"
+}
+{
+"origin" "-408 -2360 840"
+"classname" "light"
+"light" "140"
+}
+{
+"spawnflags" "5888"
+"origin" "-152 -2464 720"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "1848 -100 838"
+"light" "125"
+"classname" "light"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1848 52 838"
+}
+{
+"origin" "1504 -40 488"
+"classname" "item_health"
+}
+{
+"classname" "ammo_tesla"
+"origin" "2032 128 512"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "864 -160 924"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1632 -288 853"
+}
+{
+"origin" "1632 -480 853"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1824 -288 853"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1656 -100 565"
+}
+{
+"origin" "1656 52 565"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "2048 -224 853"
+}
+{
+"origin" "1824 -480 853"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "568 56 721"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1544 -352 853"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1088 -576 613"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "2144 -288 709"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1824 -288 709"
+}
+{
+"origin" "1632 -448 605"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1824 -480 709"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1632 -192 605"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1544 -224 853"
+}
+{
+"spawnflags" "5888"
+"origin" "1284 -604 540"
+"classname" "ammo_rockets"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1581 -147 853"
+}
+{
+"origin" "1544 -480 853"
+"light" "150"
+"classname" "light"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1040 -480 921"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1040 -544 921"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1056 -680 920"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1184 -680 920"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1248 -680 920"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1440 -680 920"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1376 -680 920"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1120 -680 920"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "992 -680 920"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "1304 -328 574"
+}
+{
+"model" "*50"
+"classname" "trigger_once"
+"target" "t280"
+}
+{
+"classname" "ammo_tesla"
+"origin" "928 -352 528"
+}
+{
+"classname" "ammo_prox"
+"origin" "-152 -1600 864"
+}
+{
+"classname" "target_secret"
+"message" "You found a secret!"
+"targetname" "t279"
+"origin" "-148 -1936 740"
+}
+{
+"model" "*51"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t279"
+}
+{
+"origin" "-368 -1632 72"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "ammo_tesla"
+"origin" "-792 -2104 0"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"targetname" "t277"
+"target" "t23"
+"origin" "-1704 -2248 0"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"delay" "16"
+"target" "t23"
+"targetname" "t277"
+"origin" "-1664 -2248 0"
+}
+{
+"attenuation" "2"
+"spawnflags" "2048"
+"noise" "world/steam2.wav"
+"origin" "-1648 -2408 0"
+"classname" "target_speaker"
+"targetname" "t23"
+}
+{
+"attenuation" "2"
+"spawnflags" "2048"
+"targetname" "t23"
+"classname" "target_speaker"
+"origin" "-1536 -2424 0"
+"noise" "world/steam2.wav"
+}
+{
+"classname" "item_health"
+"origin" "-1920 -2232 24"
+}
+{
+"classname" "item_health_small"
+"origin" "-1040 -2224 160"
+}
+{
+"classname" "item_health_small"
+"origin" "-1040 -2176 160"
+}
+{
+"classname" "item_health_small"
+"origin" "-1040 -2272 160"
+}
+{
+"classname" "ammo_shells"
+"origin" "-1968 -1872 36"
+}
+{
+"classname" "light"
+"origin" "856 -448 584"
+"light" "100"
+}
+{
+"classname" "light"
+"origin" "584 -168 584"
+"light" "100"
+}
+{
+"light" "100"
+"origin" "448 -712 792"
+"classname" "light"
+}
+{
+"light" "125"
+"origin" "328 -728 792"
+"classname" "light"
+}
+{
+"light" "75"
+"origin" "336 -568 792"
+"classname" "light"
+}
+{
+"light" "75"
+"origin" "312 -280 792"
+"classname" "light"
+}
+{
+"light" "75"
+"origin" "544 -496 792"
+"classname" "light"
+}
+{
+"light" "75"
+"origin" "560 -296 784"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "408 -312 784"
+"light" "75"
+}
+{
+"light" "75"
+"origin" "392 -512 792"
+"classname" "light"
+}
+{
+"light" "75"
+"origin" "560 -184 768"
+"classname" "light"
+}
+{
+"light" "100"
+"origin" "512 -136 616"
+"classname" "light"
+}
+{
+"light" "100"
+"origin" "664 -344 584"
+"classname" "light"
+}
+{
+"light" "100"
+"origin" "784 -352 584"
+"classname" "light"
+}
+{
+"light" "150"
+"origin" "1176 -672 520"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "488 -592 792"
+"light" "75"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "836 -268 766"
+}
+{
+"model" "*52"
+"sounds" "4"
+"spawnflags" "2080"
+"speed" "25"
+"wait" "-1"
+"team" "door"
+"angle" "270"
+"classname" "func_door"
+"targetname" "t259"
+}
+{
+"model" "*53"
+"sounds" "4"
+"spawnflags" "2080"
+"speed" "25"
+"team" "door"
+"angle" "90"
+"classname" "func_door"
+"targetname" "t259"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "640 0 766"
+}
+{
+"origin" "924 -476 766"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "1456 -104 542"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "840 -608 731"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "120"
+}
+{
+"light" "120"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "888 -616 731"
+}
+{
+"light" "120"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "748 -88 731"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "640 -4 646"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "392 -160 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "544 -72 648"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "120"
+"classname" "light"
+"origin" "600 -32 520"
+}
+{
+"origin" "600 32 520"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "552 -776 728"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "120"
+"classname" "light"
+"origin" "600 -776 728"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "800 -72 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "864 -72 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "864 -96 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "864 -160 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "896 -232 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "912 -288 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "952 -352 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "952 -416 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "952 -480 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "952 -544 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "952 -608 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "928 -632 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "864 -632 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "800 -632 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "792 -608 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "792 -544 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "680 -440 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "744 -440 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "544 -440 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "608 -440 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "416 -440 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "480 -440 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "392 -416 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "392 -352 648"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "264 -288 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "264 -352 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "264 -416 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "264 -480 924"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "120"
+"classname" "light"
+"origin" "688 32 520"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "544 -784 924"
+}
+{
+"origin" "736 -72 648"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "264 -608 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "264 -672 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "264 -736 924"
+}
+{
+"origin" "792 -480 648"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "264 -544 924"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "352 -784 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "416 -784 924"
+}
+{
+"origin" "688 -32 520"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "608 -784 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "672 -784 924"
+}
+{
+"origin" "736 -784 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "800 -784 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "864 -784 924"
+}
+{
+"origin" "912 -736 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "912 -672 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "912 -608 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "912 -480 924"
+}
+{
+"origin" "912 -544 924"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "480 -784 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "912 -288 924"
+}
+{
+"origin" "912 -352 924"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "912 -416 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "864 -96 924"
+}
+{
+"origin" "680 -232 796"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "896 -232 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "416 -72 924"
+}
+{
+"origin" "480 -72 924"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "544 -72 924"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "840 -72 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "696 -32 924"
+}
+{
+"origin" "696 32 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "672 56 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "608 56 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "592 32 924"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "592 -32 924"
+}
+{
+"origin" "296 -776 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "352 -200 924"
+}
+{
+"origin" "392 -96 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "392 -160 924"
+}
+{
+"origin" "-352 -1800 840"
+"classname" "light"
+"light" "140"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-288 -1800 840"
+}
+{
+"light" "150"
+"origin" "1176 -480 520"
+"classname" "light"
+}
+{
+"model" "*54"
+"target" "t269"
+"health" "10"
+"classname" "func_explosive"
+"dmg" "100"
+}
+{
+"style" "37"
+"targetname" "t269"
+"origin" "-1040 -2088 216"
+"light" "175"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "248 -1240 928"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"origin" "48 -1504 1135"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "120 -1144 1120"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "168 -1112 1048"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "136 -1136 1032"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "280 -1320 1096"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"light" "75"
+"origin" "-152 -1632 888"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"origin" "20 -1580 1012"
+"targetname" "piercrys"
+"classname" "info_notnull"
+}
+{
+"target" "t297"
+"angle" "270"
+"spawnflags" "1"
+"origin" "488 -1280 872"
+"targetname" "t266"
+"classname" "monster_infantry"
+}
+{
+"spawnflags" "2048"
+"target" "t266"
+"targetname" "t265"
+"origin" "128 -1352 920"
+"killtarget" "piercrys"
+"classname" "target_anger"
+}
+{
+"targetname" "pier"
+"classname" "target_explosion"
+"origin" "8 -1648 824"
+"dmg" "75"
+}
+{
+"spawnflags" "2048"
+"targetname" "pier"
+"delay" ".3"
+"dmg" "75"
+"origin" "72 -1592 864"
+"classname" "target_explosion"
+}
+{
+"speed" "100"
+"wait" "-1"
+"targetname" "t263"
+"classname" "path_corner"
+"origin" "-8 -1664 784"
+}
+{
+"model" "*55"
+"origin" "64 -1528 824"
+"spawnflags" "66"
+"wait" "-1"
+"targetname" "pier"
+"distance" "-15"
+"classname" "func_door_rotating"
+}
+{
+"model" "*56"
+"origin" "60 -1696 824"
+"wait" "-1"
+"targetname" "pier"
+"distance" "-15"
+"spawnflags" "68"
+"classname" "func_door_rotating"
+}
+{
+"pathtarget" "pier"
+"speed" "400"
+"target" "t263"
+"origin" "-8 -1664 824"
+"targetname" "t262"
+"classname" "path_corner"
+}
+{
+"origin" "-8 -1664 1064"
+"target" "t262"
+"targetname" "t261"
+"classname" "path_corner"
+}
+{
+"model" "*57"
+"spawnflags" "2048"
+"_minlight" ".3"
+"dmg" "500"
+"targetname" "t264"
+"target" "t261"
+"classname" "func_train"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "32 -1616 1040"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "128 -1536 1080"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+"origin" "152 -1616 1127"
+}
+{
+"model" "*58"
+"origin" "-160 -1536 1128"
+"wait" "-1"
+"targetname" "t264"
+"accel" "100"
+"distance" "20"
+"speed" "300"
+"spawnflags" "2180"
+"classname" "func_door_rotating"
+}
+{
+"model" "*59"
+"mass" "400"
+"targetname" "t264"
+"classname" "func_explosive"
+}
+{
+"origin" "80 -1248 1080"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"origin" "208 -1248 1080"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"origin" "256 -1536 1080"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"origin" "-24 -1648 1040"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"origin" "0 -1536 1080"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"origin" "-128 -1536 1080"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-2032 -1688 24"
+"light" "125"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-40 -1720 991"
+}
+{
+"model" "*60"
+"killtarget" "piercrys"
+"spawnflags" "2048"
+"health" "10"
+"target" "t264"
+"dmg" "100"
+"classname" "func_explosive"
+}
+{
+"origin" "-72 -1560 984"
+"spawnflags" "1"
+"noise" "world/drip_amb.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-32 -1320 984"
+"spawnflags" "1"
+"noise" "world/drip_amb.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "1392 592 776"
+"spawnflags" "1"
+"noise" "world/drip_amb.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-512 -2176 840"
+"classname" "light"
+"light" "75"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-632 -2080 840"
+}
+{
+"classname" "point_combat"
+"origin" "-896 -1984 992"
+"targetname" "t260"
+"spawnflags" "1"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-720 -1952 728"
+}
+{
+"origin" "-720 -1824 728"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"origin" "-720 -2080 728"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"origin" "-808 -2016 736"
+"light" "75"
+"classname" "light"
+}
+{
+"model" "*61"
+"spawnflags" "2048"
+"sounds" "4"
+"classname" "func_button"
+"angle" "180"
+"wait" "-1"
+"message" "You activated the Ore Mover."
+"target" "oretrain"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"origin" "-800 -1976 648"
+}
+{
+"origin" "-800 -1848 648"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-48 -1248 1080"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"origin" "-560 -2280 1080"
+}
+{
+"origin" "-560 -2080 1080"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "-496 -2256 968"
+}
+{
+"origin" "-352 -2360 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-632 -2016 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-632 -1952 840"
+"classname" "light"
+"light" "140"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-632 -1888 840"
+}
+{
+"origin" "-632 -1824 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-400 -2336 1024"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-400 -1952 1024"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-400 -2016 1024"
+}
+{
+"origin" "-400 -1888 1024"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-400 -1824 1024"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-400 -2080 1024"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-608 -1800 840"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-544 -1800 840"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-416 -1800 840"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "912 -784 924"
+}
+{
+"origin" "744 -72 924"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-224 -1800 840"
+}
+{
+"light" "140"
+"classname" "light"
+"origin" "-160 -1800 840"
+}
+{
+"origin" "-400 -2144 1024"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-400 -2208 1024"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-136 -1824 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-136 -2080 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-136 -2144 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-136 -2208 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-136 -2272 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-136 -2336 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-160 -2360 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-224 -2360 840"
+"classname" "light"
+"light" "140"
+}
+{
+"origin" "-288 -2360 840"
+"classname" "light"
+"light" "80"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-400 -2272 1024"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*62"
+"mass" "200"
+"classname" "func_explosive"
+"spawnflags" "2048"
+"target" "t254"
+"targetname" "waller"
+}
+{
+"origin" "-664 -1768 672"
+"light" "75"
+"classname" "light"
+}
+{
+"spawnflags" "2050"
+"origin" "-784 -1920 568"
+"team" "oremover"
+"target" "blockwall"
+"targetname" "t258"
+"classname" "target_explosion"
+}
+{
+"origin" "-88 -1984 764"
+"classname" "item_ir_goggles"
+}
+{
+"origin" "-768 -2312 616"
+"classname" "monster_flyer"
+"angle" "90"
+}
+{
+"model" "*63"
+"spawnflags" "2048"
+"target" "t258"
+"team" "oremover"
+"classname" "func_explosive"
+"dmg" "100"
+"health" "5"
+}
+{
+"target" "t254"
+"targetname" "t253"
+"origin" "-484 -2024 704"
+"classname" "path_corner"
+}
+{
+"target" "t253"
+"targetname" "t252"
+"origin" "-484 -2232 704"
+"classname" "path_corner"
+}
+{
+"target" "t252"
+"targetname" "t251"
+"origin" "-612 -2232 704"
+"classname" "path_corner"
+}
+{
+"pathtarget" "blocko"
+"wait" "-1"
+"targetname" "t254"
+"classname" "path_corner"
+"origin" "-228 -2024 704"
+}
+{
+"wait" ".2"
+"pathtarget" "plat1"
+"target" "t251"
+"targetname" "t250"
+"classname" "path_corner"
+"origin" "-612 -2232 528"
+}
+{
+"model" "*64"
+"team" "mony1"
+"speed" "50"
+"targetname" "plat1"
+"target" "t242"
+"classname" "func_train"
+"spawnflags" "2"
+}
+{
+"target" "t250"
+"targetname" "t249"
+"classname" "path_corner"
+"origin" "-612 -2160 528"
+}
+{
+"target" "t249"
+"targetname" "t248"
+"classname" "path_corner"
+"origin" "-612 -2120 512"
+}
+{
+"target" "t248"
+"targetname" "t247"
+"classname" "path_corner"
+"origin" "-668 -2120 512"
+}
+{
+"target" "t246"
+"targetname" "t245"
+"origin" "-808 -1952 512"
+"classname" "path_corner"
+}
+{
+"model" "*65"
+"spawnflags" "2048"
+"speed" "50"
+"targetname" "oretrain"
+"team" "oremover"
+"target" "t245"
+"classname" "func_train"
+"noise" "world/amb10.wav"
+}
+{
+"target" "t247"
+"targetname" "t246"
+"origin" "-668 -1952 512"
+"classname" "path_corner"
+}
+{
+"origin" "-624 -2240 512"
+"wait" "-1"
+"targetname" "t244"
+"classname" "path_corner"
+}
+{
+"wait" "-1"
+"origin" "-624 -2240 512"
+"target" "t243"
+"targetname" "t242"
+"classname" "path_corner"
+}
+{
+"target" "t242"
+"wait" "3"
+"origin" "-624 -2240 688"
+"targetname" "t243"
+"classname" "path_corner"
+}
+{
+"origin" "-1808 -1640 144"
+"angle" "315"
+"classname" "monster_flyer"
+"spawnflags" "1"
+}
+{
+"origin" "-1544 -1992 144"
+"angle" "180"
+"classname" "monster_flyer"
+"spawnflags" "1"
+}
+{
+"spawnflags" "2052"
+"origin" "-1952 -2168 40"
+"targetname" "t235"
+"classname" "target_explosion"
+}
+{
+"spawnflags" "2048"
+"origin" "-1848 -1600 40"
+"targetname" "t234"
+"classname" "target_explosion"
+}
+{
+"origin" "-2048 -1888 40"
+"classname" "weapon_shotgun"
+}
+{
+"origin" "-2040 -1640 24"
+"classname" "item_double"
+}
+{
+"spawnflags" "2048"
+"origin" "-928 -1376 64"
+"targetname" "t178"
+"classname" "target_explosion"
+}
+{
+"model" "*66"
+"spawnflags" "2048"
+"targetname" "t178"
+"classname" "func_explosive"
+}
+{
+"style" "38"
+"origin" "-1960 -2168 72"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t235"
+}
+{
+"model" "*67"
+"spawnflags" "2052"
+"health" "10"
+"classname" "func_explosive"
+"dmg" "100"
+"target" "t235"
+}
+{
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-2224 -1992 288"
+}
+{
+"model" "*68"
+"spawnflags" "2048"
+"dmg" "100"
+"classname" "func_explosive"
+"target" "t234"
+"health" "10"
+}
+{
+"model" "*69"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"dmg" "100"
+"targetname" "t178"
+}
+{
+"spawnflags" "2048"
+"classname" "target_steam"
+"origin" "-1584 -2424 -16"
+"angle" "-1"
+"speed" "100"
+"count" "64"
+"sounds" "225"
+"wait" "15"
+"targetname" "t14"
+}
+{
+"spawnflags" "2048"
+"targetname" "t14"
+"wait" "17"
+"sounds" "225"
+"count" "64"
+"speed" "150"
+"angle" "-1"
+"origin" "-1640 -2432 -16"
+"classname" "target_steam"
+}
+{
+"origin" "-1600 -2464 336"
+"classname" "light"
+"light" "150"
+}
+{
+"spawnflags" "2048"
+"origin" "-1072 -1520 112"
+"delay" "1"
+"target" "t178"
+"targetname" "t176"
+"classname" "trigger_relay"
+}
+{
+"classname" "monster_berserk"
+"angle" "180"
+"spawnflags" "772"
+"origin" "688 -520 528"
+}
+{
+"model" "*70"
+"classname" "func_button"
+"angle" "270"
+"lip" "3"
+"wait" "5"
+"target" "getplat"
+"message" "Lift activated."
+}
+{
+"spawnflags" "2048"
+"target" "t176"
+"targetname" "t175"
+"origin" "-1080 -1592 80"
+"classname" "target_explosion"
+}
+{
+"spawnflags" "2048"
+"target" "t175"
+"origin" "-1104 -1672 256"
+"targetname" "t173"
+"classname" "trigger_relay"
+}
+{
+"origin" "-1072 -1848 256"
+"count" "3"
+"speed" "25"
+"targetname" "t173"
+"classname" "target_earthquake"
+}
+{
+"model" "*71"
+"target" "t173"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2052"
+"origin" "-2008 -2216 64"
+"targetname" "t277"
+"count" "2"
+"speed" "40"
+"classname" "target_earthquake"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t172"
+"origin" "96 -1664 856"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t171"
+"origin" "40 -1632 888"
+}
+{
+"origin" "-280 -2160 88"
+"target" "t160"
+"spawnflags" "1792"
+"classname" "trigger_always"
+}
+{
+"origin" "424 -544 520"
+"classname" "ammo_slugs"
+"spawnflags" "5888"
+}
+{
+"spawnflags" "5888"
+"origin" "-472 -1960 720"
+"classname" "ammo_rockets"
+}
+{
+"origin" "1656 -144 520"
+"classname" "ammo_cells"
+"spawnflags" "5888"
+}
+{
+"origin" "1392 -648 552"
+"spawnflags" "5888"
+"classname" "weapon_rocketlauncher"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "1176 -1280 552"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "72 -1272 952"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "45"
+"origin" "-792 -2200 8"
+}
+{
+"origin" "688 -688 520"
+"spawnflags" "1024"
+"classname" "ammo_shells"
+}
+{
+"origin" "688 -640 520"
+"classname" "item_health_large"
+}
+{
+"origin" "504 -776 544"
+"spawnflags" "2"
+"targetname" "t92"
+"noise" "world/l_hum1.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-1048 -1720 176"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1048 -1760 200"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1048 -1800 216"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1048 -1680 144"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "boom"
+"dmg" "50"
+"team" "boxcrane"
+"origin" "640 0 528"
+"target" "blowgrate"
+}
+{
+"model" "*72"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"health" "25"
+"mass" "200"
+"team" "boxcrane"
+"target" "boom"
+}
+{
+"model" "*73"
+"spawnflags" "2048"
+"classname" "func_train"
+"speed" "93"
+"target" "t105"
+"targetname" "t98"
+"team" "boxcrane"
+}
+{
+"classname" "info_player_start"
+"angle" "90"
+"origin" "-2008 -2448 16"
+}
+{
+"model" "*74"
+"spawnflags" "2048"
+"targetname" "t5"
+"speed" "5"
+"target" "t4"
+"classname" "func_train"
+}
+{
+"origin" "-1640 -2544 -360"
+"target" "t4"
+"targetname" "t3"
+"classname" "path_corner"
+}
+{
+"origin" "-1696 -2544 -128"
+"targetname" "t4"
+"target" "t1"
+"classname" "path_corner"
+}
+{
+"speed" "5"
+"origin" "-1696 -2544 -184"
+"wait" "2"
+"target" "t2"
+"targetname" "t1"
+"classname" "path_corner"
+}
+{
+"accel" ".1"
+"speed" "20"
+"wait" "-1"
+"origin" "-1696 -2544 -288"
+"target" "t3"
+"targetname" "t2"
+"classname" "path_corner"
+}
+{
+"model" "*75"
+"spawnflags" "2048"
+"target" "t5"
+"classname" "trigger_once"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t8"
+"origin" "-1609 -2419 112"
+"target" "t12"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"target" "t6"
+"targetname" "t5"
+"origin" "-1617 -2427 48"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"target" "t7"
+"origin" "-1585 -2411 56"
+"targetname" "t6"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t14"
+"target" "t8"
+"origin" "-1577 -2427 80"
+}
+{
+"spawnflags" "2048"
+"targetname" "t112"
+"classname" "target_explosion"
+"origin" "-1605 -2438 56"
+}
+{
+"spawnflags" "2048"
+"origin" "-1589 -2430 0"
+"classname" "target_explosion"
+"target" "t9"
+"targetname" "t11"
+}
+{
+"spawnflags" "2048"
+"origin" "-1581 -2430 24"
+"classname" "target_explosion"
+"targetname" "t9"
+"target" "t10"
+}
+{
+"spawnflags" "2048"
+"origin" "-1776 -2283 104"
+"target" "t11"
+"targetname" "t5"
+"classname" "trigger_relay"
+"delay" "12"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"delay" "2"
+"target" "t14"
+"origin" "-1541 -2256 72"
+"targetname" "t7"
+}
+{
+"origin" "-1496 -2464 130"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-1592 -2424 148"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-1680 -2472 334"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-1552 -2352 85"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-1648 -2376 69"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-1600 -2464 520"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-1680 -2464 119"
+}
+{
+"classname" "target_splash"
+"sounds" "5"
+"count" "40"
+"targetname" "t15"
+"origin" "-1306 -1493 -8"
+"spawnflags" "2049"
+}
+{
+"classname" "func_timer"
+"target" "t15"
+"wait" "5"
+"random" "2"
+"origin" "-1241 -1472 40"
+"spawnflags" "2049"
+}
+{
+"count" "40"
+"sounds" "5"
+"classname" "target_splash"
+"targetname" "t16"
+"origin" "-1714 -1709 -8"
+"spawnflags" "2049"
+}
+{
+"random" "2"
+"wait" "4"
+"classname" "func_timer"
+"target" "t16"
+"origin" "-1649 -1688 24"
+"spawnflags" "2049"
+}
+{
+"origin" "-152 -1792 0"
+"light" "175"
+"classname" "light"
+}
+{
+"origin" "-808 -1792 1"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "-736 -2216 0"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "-480 -2224 5"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "-192 -2176 605"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "175"
+"classname" "light"
+"origin" "-224 -2216 0"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-192 -2176 272"
+}
+{
+"origin" "-232 -2136 61"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-152 -2216 166"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-192 -2176 376"
+}
+{
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-744 -1984 258"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-224 -2016 723"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-416 -1960 600"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-352 -1912 264"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-352 -2056 264"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"origin" "-616 -1984 256"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-472 -1904 592"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-160 -2016 726"
+}
+{
+"origin" "-808 -2048 0"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "-152 -2048 848"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "-288 -2104 887"
+"classname" "light"
+"light" "175"
+}
+{
+"origin" "168 -1720 986"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "216 -1720 988"
+}
+{
+"origin" "-488 -1984 256"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "target_explosion"
+"targetname" "t178"
+"origin" "-885 -1396 64"
+"spawnflags" "2048"
+}
+{
+"origin" "-528 -1960 600"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-488 -1984 888"
+}
+{
+"origin" "-352 -1904 64"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-760 -2344 655"
+"classname" "light"
+"light" "175"
+}
+{
+"model" "*76"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t277"
+}
+{
+"light" "180"
+"_color" "1.000000 0.694118 0.607843"
+"classname" "light"
+"origin" "-1424 -2104 301"
+}
+{
+"style" "39"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "-1824 -1616 75"
+"targetname" "t234"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "-88 -1720 991"
+}
+{
+"origin" "56 -1688 1127"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"model" "*77"
+"origin" "2144 -440 528"
+"targetname" "t68"
+"classname" "func_rotating"
+"dmg" "100"
+"speed" "145"
+"_minlight" ".2"
+"accel" "1"
+"spawnflags" "8192"
+}
+{
+"model" "*78"
+"origin" "1952 -440 540"
+"targetname" "t68"
+"classname" "func_rotating"
+"speed" "125"
+"spawnflags" "8194"
+"dmg" "100"
+"_minlight" ".2"
+"accel" "1"
+}
+{
+"model" "*79"
+"classname" "func_explosive"
+"health" "25"
+"mass" "200"
+"targetname" "t117"
+}
+{
+"model" "*80"
+"origin" "1624 -496 750"
+"target" "t68"
+"classname" "func_door_rotating"
+"wait" "-1"
+"distance" "90"
+"spawnflags" "146"
+"speed" "50"
+"dmg" "1"
+"targetname" "t82"
+"_minlight" ".2"
+}
+{
+"light" "200"
+"classname" "light"
+"origin" "1208 -152 711"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1600 -496 761"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "1752 -32 806"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1872 -24 854"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1752 -32 525"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "1752 -32 589"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "1752 -32 653"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "1752 -32 718"
+}
+{
+"origin" "2152 -376 639"
+"classname" "light"
+"light" "125"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"origin" "2184 -336 577"
+}
+{
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2064 -336 574"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"origin" "1800 -336 574"
+}
+{
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2192 -536 572"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1984 -168 597"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"origin" "1928 -536 565"
+}
+{
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "1800 -536 566"
+}
+{
+"light" "200"
+"classname" "light"
+"origin" "1480 -424 600"
+}
+{
+"origin" "1645 -496 766"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1816 -288 543"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1920 -440 621"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2032 -440 621"
+}
+{
+"classname" "func_group"
+}
+{
+"origin" "1208 -392 679"
+"classname" "light"
+"light" "200"
+}
+{
+"origin" "1288 -536 742"
+"classname" "light"
+"light" "200"
+}
+{
+"origin" "1008 -616 919"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1040 -424 921"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1008 -344 919"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "120"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "488 -520 536"
+}
+{
+"origin" "1128 -592 742"
+"classname" "light"
+"light" "200"
+}
+{
+"origin" "840 -88 731"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "120"
+}
+{
+"_color" "0.000000 0.000000 1.000000"
+"origin" "304 -752 552"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "path_corner"
+"targetname" "t70"
+"target" "t71"
+"origin" "1112 -152 592"
+"speed" "100"
+}
+{
+"classname" "path_corner"
+"targetname" "t71"
+"target" "t72"
+"origin" "1112 -152 512"
+}
+{
+"classname" "path_corner"
+"targetname" "t72"
+"target" "t73"
+"origin" "1112 -408 516"
+}
+{
+"classname" "path_corner"
+"targetname" "t73"
+"target" "t74"
+"origin" "1112 -408 720"
+}
+{
+"classname" "path_corner"
+"targetname" "t80"
+"target" "t70"
+"origin" "1112 -152 1248"
+"speed" "500"
+}
+{
+"model" "*81"
+"team" "mover1"
+"classname" "func_train"
+"target" "t70"
+"targetname" "t81"
+"speed" "70"
+"_minlight" ".1"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t68"
+"target" "t81"
+"delay" "2"
+"origin" "1552 -480 784"
+}
+{
+"model" "*82"
+"spawnflags" "2048"
+"health" "10"
+"classname" "func_explosive"
+"target" "t115"
+}
+{
+"model" "*83"
+"spawnflags" "2048"
+"health" "10"
+"classname" "func_explosive"
+"target" "t116"
+}
+{
+"model" "*84"
+"spawnflags" "2048"
+"health" "10"
+"classname" "func_explosive"
+"target" "t113"
+}
+{
+"model" "*85"
+"health" "10"
+"classname" "func_explosive"
+"target" "t114"
+}
+{
+"model" "*86"
+"message" "Cart track active."
+"classname" "trigger_once"
+"target" "t82"
+}
+{
+"origin" "336 -768 615"
+"classname" "light"
+"light" "120"
+}
+{
+"spawnflags" "2048"
+"light" "75"
+"classname" "light"
+"origin" "408 -664 912"
+}
+{
+"origin" "1208 -128 519"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1208 -128 591"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1064 -128 517"
+}
+{
+"origin" "1064 -128 589"
+"classname" "light"
+"light" "100"
+}
+{
+"model" "*87"
+"spawnflags" "2048"
+"target" "t90"
+"classname" "trigger_once"
+}
+{
+"model" "*88"
+"target" "t104"
+"classname" "func_explosive"
+"mass" "100"
+"health" "25"
+"spawnflags" "2048"
+"targetname" "t352"
+}
+{
+"model" "*89"
+"spawnflags" "2048"
+"target" "t98"
+"sounds" "4"
+"wait" "-1"
+"angle" "90"
+"classname" "func_button"
+"message" "Crane activated."
+}
+{
+"_color" "1.000000 0.850980 0.850980"
+"origin" "1992 -280 565"
+"classname" "light"
+"light" "160"
+}
+{
+"_color" "1.000000 0.850980 0.850980"
+"origin" "520 -88 760"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "2094 -376 430"
+"light" "150"
+"_color" "1.000000 0.607843 0.607843"
+"classname" "light"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2112 -400 805"
+}
+{
+"target" "t104"
+"origin" "532 -632 800"
+"targetname" "t96"
+"classname" "path_corner"
+}
+{
+"targetname" "t105"
+"origin" "596 -8 528"
+"classname" "path_corner"
+"target" "t97"
+}
+{
+"origin" "596 -8 800"
+"targetname" "t97"
+"target" "t94"
+"classname" "path_corner"
+}
+{
+"origin" "596 -152 800"
+"target" "t95"
+"targetname" "t94"
+"classname" "path_corner"
+}
+{
+"origin" "532 -152 800"
+"target" "t96"
+"targetname" "t95"
+"classname" "path_corner"
+}
+{
+"model" "*90"
+"spawnflags" "2048"
+"speed" "93"
+"targetname" "t103"
+"target" "t102"
+"classname" "func_train"
+}
+{
+"origin" "560 -640 976"
+"wait" "-1"
+"target" "t102"
+"targetname" "t101"
+"classname" "path_corner"
+}
+{
+"origin" "624 -16 976"
+"targetname" "t102"
+"target" "t99"
+"classname" "path_corner"
+}
+{
+"origin" "624 -160 976"
+"target" "t100"
+"targetname" "t99"
+"classname" "path_corner"
+}
+{
+"origin" "560 -160 976"
+"target" "t101"
+"targetname" "t100"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"origin" "664 -344 944"
+"delay" "3"
+"target" "t103"
+"targetname" "t98"
+"classname" "trigger_relay"
+}
+{
+"target" "t164"
+"origin" "532 -632 744"
+"targetname" "t104"
+"classname" "path_corner"
+"wait" "-1"
+"pathtarget" "gibber"
+}
+{
+"model" "*91"
+"classname" "func_explosive"
+"mass" "400"
+"target" "t117"
+"health" "25"
+}
+{
+"spawnflags" "2048"
+"message" "You found a secret!"
+"classname" "target_secret"
+"targetname" "t117"
+"origin" "2064 -272 592"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"origin" "2056 -536 566"
+}
+{
+"spawnflags" "2048"
+"origin" "-1904 -1536 112"
+"message" "You found a secret area!"
+"targetname" "t110"
+"classname" "target_secret"
+}
+{
+"model" "*92"
+"classname" "func_explosive"
+"mass" "150"
+"targetname" "t117"
+}
+{
+"origin" "2112 -344 512"
+"targetname" "t117"
+"classname" "target_explosion"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-232 -2136 550"
+}
+{
+"light" "175"
+"classname" "light"
+"origin" "-608 -1912 652"
+}
+{
+"origin" "-272 -1968 759"
+"classname" "light"
+"light" "175"
+}
+{
+"light" "175"
+"classname" "light"
+"origin" "-528 -1904 734"
+}
+{
+"origin" "1824 -488 622"
+"classname" "light"
+"light" "125"
+}
+{
+"spawnflags" "2048"
+"origin" "-1536 -2384 144"
+"targetname" "t14"
+"count" "100"
+"sounds" "1"
+"classname" "target_splash"
+}
+{
+"spawnflags" "2048"
+"origin" "-1600 -2464 136"
+"targetname" "t5"
+"sounds" "1"
+"count" "100"
+"classname" "target_splash"
+}
+{
+"spawnflags" "2048"
+"origin" "-1648 -2304 56"
+"target" "t112"
+"targetname" "t10"
+"delay" "1"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "-1600 -2400 72"
+"targetname" "t112"
+"sounds" "1"
+"count" "150"
+"classname" "target_splash"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t116"
+"origin" "480 -416 560"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t113"
+"origin" "736 -288 552"
+}
+{
+"classname" "target_explosion"
+"targetname" "t114"
+"origin" "800 -96 544"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t115"
+"origin" "416 -296 552"
+}
+{
+"model" "*93"
+"classname" "func_plat2"
+"height" "520"
+"sounds" "1"
+"speed" "300"
+"spawnflags" "0"
+"targetname" "t160"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.325490 0.000000"
+"light" "150"
+"origin" "-608 -2216 24"
+}
+{
+"light" "150"
+"_color" "1.000000 0.325490 0.000000"
+"classname" "light"
+"origin" "-352 -2216 20"
+}
+{
+"origin" "-456 -2160 758"
+"classname" "light"
+"light" "175"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2080 -896 824"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "-368 -1672 48"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-368 -1472 72"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-472 -1384 72"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-648 -1384 72"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1888 -896 824"
+}
+{
+"classname" "func_timer"
+"target" "t119"
+"wait" "2"
+"random" "1"
+"spawnflags" "1"
+"targetname" "t117"
+"origin" "2152 -280 592"
+}
+{
+"classname" "target_splash"
+"count" "75"
+"sounds" "1"
+"targetname" "t119"
+"origin" "2088 -360 472"
+}
+{
+"origin" "-480 -2128 8"
+"spawnflags" "260"
+"target" "t120"
+"angle" "90"
+"classname" "monster_soldier_light"
+}
+{
+"origin" "-208 -1808 8"
+"spawnflags" "4"
+"classname" "monster_soldier_light"
+"angle" "180"
+"target" "t160"
+}
+{
+"origin" "-208 -2064 -8"
+"spawnflags" "2049"
+"targetname" "t120"
+"classname" "point_combat"
+}
+{
+"origin" "-528 -2088 8"
+"spawnflags" "4"
+"target" "t121"
+"classname" "monster_soldier_light"
+"angle" "90"
+}
+{
+"origin" "-736 -1872 -8"
+"targetname" "t121"
+"classname" "point_combat"
+"spawnflags" "1"
+}
+{
+"origin" "-192 -2176 536"
+"spawnflags" "5"
+"angle" "135"
+"classname" "monster_soldier"
+}
+{
+"spawnflags" "4"
+"origin" "-248 -1976 728"
+"target" "t123"
+"angle" "315"
+"classname" "monster_soldier_light"
+}
+{
+"spawnflags" "4"
+"origin" "-456 -1880 536"
+"classname" "monster_soldier_light"
+"angle" "315"
+}
+{
+"origin" "-440 -1992 728"
+"target" "t124"
+"targetname" "t122"
+"classname" "path_corner"
+}
+{
+"origin" "-264 -1992 728"
+"targetname" "t123"
+"target" "t122"
+"classname" "path_corner"
+}
+{
+"origin" "-448 -1952 728"
+"target" "t123"
+"targetname" "t124"
+"classname" "path_corner"
+}
+{
+"spawnflags" "4"
+"origin" "-448 -2288 728"
+"target" "t125"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "-496 -2008 728"
+"target" "t125"
+"targetname" "t127"
+"classname" "path_corner"
+}
+{
+"origin" "-464 -2272 728"
+"target" "t126"
+"targetname" "t125"
+"classname" "path_corner"
+}
+{
+"origin" "-480 -2064 728"
+"target" "t127"
+"targetname" "t126"
+"classname" "path_corner"
+}
+{
+"spawnflags" "4"
+"origin" "-472 -1952 856"
+"target" "t128"
+"angle" "0"
+"classname" "monster_soldier_light"
+}
+{
+"spawnflags" "4"
+"origin" "-384 -2160 856"
+"target" "t129"
+"classname" "monster_soldier_light"
+"angle" "0"
+}
+{
+"origin" "-320 -1920 840"
+"spawnflags" "2049"
+"targetname" "t128"
+"classname" "point_combat"
+"angle" "270"
+}
+{
+"origin" "-368 -2312 840"
+"spawnflags" "2049"
+"targetname" "t129"
+"classname" "point_combat"
+"angle" "0"
+}
+{
+"spawnflags" "4"
+"origin" "-576 -1984 984"
+"angle" "315"
+"classname" "monster_soldier_light"
+"target" "t260"
+}
+{
+"targetname" "flyercharge"
+"spawnflags" "257"
+"origin" "-88 -1632 992"
+"angle" "0"
+"classname" "monster_flyer"
+"target" "t171"
+}
+{
+"targetname" "flyercharge"
+"spawnflags" "257"
+"origin" "232 -1568 992"
+"classname" "monster_flyer"
+"angle" "180"
+"target" "t172"
+}
+{
+"target" "t300"
+"targetname" "t299"
+"origin" "1976 -376 768"
+"spawnflags" "5"
+"angle" "315"
+"classname" "monster_berserk"
+}
+{
+"origin" "1688 -440 776"
+"spawnflags" "1"
+"angle" "135"
+"classname" "monster_gunner"
+}
+{
+"classname" "monster_soldier_light"
+"angle" "270"
+"target" "t140"
+"origin" "1464 -64 496"
+"spawnflags" "4"
+}
+{
+"classname" "path_corner"
+"targetname" "t139"
+"target" "t140"
+"origin" "1504 -480 520"
+}
+{
+"classname" "path_corner"
+"target" "t137"
+"targetname" "t140"
+"origin" "1448 -80 480"
+}
+{
+"classname" "path_corner"
+"targetname" "t137"
+"target" "t138"
+"origin" "1448 -328 504"
+}
+{
+"classname" "path_corner"
+"targetname" "t138"
+"target" "t139"
+"origin" "1280 -328 504"
+}
+{
+"classname" "monster_soldier_light"
+"angle" "0"
+"spawnflags" "4"
+"origin" "1056 -352 528"
+}
+{
+"angle" "0"
+"classname" "monster_soldier_light"
+"spawnflags" "4"
+"origin" "1048 -312 528"
+"target" "t159"
+}
+{
+"target" "t169"
+"deathtarget" "flyercharge"
+"origin" "72 -1184 952"
+"spawnflags" "1281"
+"angle" "270"
+"classname" "monster_infantry"
+}
+{
+"origin" "920 -440 736"
+"spawnflags" "4"
+"angle" "0"
+"classname" "monster_soldier_light"
+"targetname" "t280"
+"target" "t286"
+}
+{
+"origin" "912 -504 736"
+"classname" "monster_soldier_light"
+"angle" "0"
+"spawnflags" "4"
+"targetname" "t280"
+}
+{
+"origin" "832 -768 904"
+"spawnflags" "769"
+"classname" "monster_soldier"
+"angle" "90"
+}
+{
+"origin" "448 -128 736"
+"angle" "0"
+"spawnflags" "4"
+"classname" "monster_soldier_light"
+}
+{
+"origin" "432 -664 528"
+"angle" "45"
+"spawnflags" "4"
+"classname" "monster_soldier"
+}
+{
+"origin" "240 -616 528"
+"spawnflags" "4"
+"angle" "0"
+"classname" "monster_soldier_light"
+}
+{
+"origin" "448 -232 536"
+"spawnflags" "4"
+"angle" "90"
+"classname" "monster_soldier_light"
+}
+{
+"origin" "552 -416 536"
+"classname" "monster_soldier_light"
+"angle" "0"
+"spawnflags" "4"
+}
+{
+"classname" "target_speaker"
+"noise" "world/drip_amb.wav"
+"spawnflags" "1"
+"origin" "184 -1560 984"
+}
+{
+"classname" "target_speaker"
+"origin" "2056 -416 624"
+"noise" "world/amb17.wav"
+"spawnflags" "2"
+"targetname" "t68"
+}
+{
+"volume" ".5"
+"spawnflags" "2048"
+"noise" "world/lava1.wav"
+"classname" "target_speaker"
+"origin" "-1712 -1688 0"
+"targetname" "t16"
+}
+{
+"volume" ".5"
+"spawnflags" "2049"
+"noise" "world/lava1.wav"
+"classname" "target_speaker"
+"origin" "-1232 -1608 112"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2064"
+"angle" "90"
+"origin" "-200 -2032 704"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t159"
+"origin" "1320 -280 536"
+}
+{
+"classname" "item_armor_shard"
+"origin" "1080 -480 688"
+}
+{
+"classname" "item_armor_shard"
+"origin" "1080 -520 688"
+}
+{
+"spawnflags" "0"
+"classname" "item_armor_shard"
+"origin" "1080 -560 688"
+}
+{
+"classname" "item_health"
+"origin" "-296 -2328 912"
+}
+{
+"classname" "item_health"
+"origin" "1584 -8 488"
+}
+{
+"classname" "item_health"
+"origin" "1592 -272 760"
+"spawnflags" "1024"
+}
+{
+"classname" "item_health"
+"origin" "936 -360 728"
+}
+{
+"classname" "item_health"
+"origin" "296 -752 896"
+}
+{
+"classname" "ammo_shells"
+"origin" "-672 -1760 64"
+}
+{
+"spawnflags" "1792"
+"classname" "ammo_shells"
+"origin" "-592 -1752 528"
+}
+{
+"classname" "ammo_grenades"
+"origin" "816 -608 528"
+}
+{
+"classname" "ammo_grenades"
+"origin" "1760 -528 520"
+}
+{
+"classname" "item_health_small"
+"origin" "2168 -240 520"
+}
+{
+"classname" "item_health_small"
+"origin" "2216 -288 520"
+}
+{
+"classname" "item_health_small"
+"origin" "2216 -240 520"
+}
+{
+"spawnflags" "2048"
+"classname" "target_splash"
+"sounds" "1"
+"count" "100"
+"origin" "-1568 -2416 88"
+"targetname" "t14"
+}
+{
+"spawnflags" "2048"
+"classname" "target_splash"
+"sounds" "1"
+"count" "100"
+"origin" "-1560 -2408 112"
+"targetname" "t14"
+}
+{
+"spawnflags" "2048"
+"classname" "target_splash"
+"sounds" "1"
+"count" "100"
+"origin" "-1552 -2400 136"
+"targetname" "t14"
+}
+{
+"spawnflags" "2048"
+"classname" "target_splash"
+"count" "150"
+"sounds" "1"
+"origin" "-1600 -2416 56"
+"targetname" "t112"
+}
+{
+"classname" "ammo_shells"
+"origin" "2056 -64 520"
+}
+{
+"classname" "ammo_shells"
+"origin" "1760 -480 520"
+}
+{
+"origin" "-184 -1952 720"
+"classname" "ammo_grenades"
+}
+{
+"origin" "808 -616 728"
+"classname" "ammo_shells"
+}
+{
+"spawnflags" "1536"
+"origin" "1640 -536 520"
+"classname" "item_health"
+}
+{
+"spawnflags" "1024"
+"origin" "1600 -536 520"
+"classname" "item_health"
+}
+{
+"origin" "-800 -1760 0"
+"classname" "item_health_large"
+}
+{
+"origin" "-432 -1832 536"
+"spawnflags" "772"
+"angle" "315"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "1752 -288 520"
+"spawnflags" "1536"
+"classname" "item_health_large"
+}
+{
+"origin" "848 -96 528"
+"spawnflags" "768"
+"classname" "item_health"
+}
+{
+"origin" "532 -632 536"
+"wait" "-1"
+"target" "t105"
+"targetname" "t164"
+"classname" "path_corner"
+}
+{
+"targetname" "t178"
+"target" "t177"
+"spawnflags" "2048"
+"origin" "-1013 -1476 64"
+"classname" "target_explosion"
+}
+{
+"classname" "light"
+"origin" "-1904 -1512 240"
+"_color" "0.000000 0.000000 1.000000"
+"light" "110"
+}
+{
+"classname" "light"
+"origin" "1352 -544 544"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1560 -1136 936"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1432 -488 528"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "240 -1248 1048"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "280 -1312 992"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "-168 -1488 1048"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "136 -1136 992"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"light" "75"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "304 -1232 1048"
+"classname" "light"
+}
+{
+"origin" "-56 -1920 920"
+"classname" "light"
+"light" "75"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "64 -1920 920"
+}
+{
+"origin" "64 -1808 920"
+"classname" "light"
+"light" "100"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1824 -328 622"
+}
+{
+"classname" "light"
+"origin" "2072 -40 872"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2080 -8 928"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "2080 -112 864"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1240 -464 512"
+"classname" "light"
+}
+{
+"origin" "1064 -1504 624"
+"spawnflags" "1"
+"noise" "world/drill1.wav"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "1"
+"classname" "point_combat"
+"targetname" "t131"
+"origin" "1064 -1504 584"
+}
+{
+"spawnflags" "0"
+"classname" "monster_flyer"
+"origin" "1064 -1504 664"
+"angle" "180"
+"target" "t131"
+"targetname" "t133"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_slugs"
+"origin" "1064 -1512 568"
+}
+{
+"model" "*94"
+"classname" "trigger_once"
+"target" "t133"
+}
+{
+"model" "*95"
+"team" "mover2"
+"targetname" "t81"
+"target" "t288"
+"classname" "func_train"
+"speed" "70"
+"_minlight" ".1"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1736 328 768"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1552 200 808"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"classname" "ammo_shells"
+"origin" "1112 424 704"
+}
+{
+"classname" "ammo_shells"
+"origin" "1112 376 704"
+"spawnflags" "0"
+}
+{
+"targetname" "t350"
+"spawnflags" "1"
+"origin" "1880 192 512"
+"classname" "item_health"
+}
+{
+"origin" "1136 288 712"
+"spawnflags" "4"
+"angle" "0"
+"classname" "monster_soldier_light"
+}
+{
+"model" "*96"
+"spawnflags" "2048"
+"sounds" "4"
+"angle" "0"
+"classname" "func_door"
+"speed" "70"
+"team" "level_exit"
+}
+{
+"model" "*97"
+"spawnflags" "2048"
+"sounds" "4"
+"angle" "180"
+"classname" "func_door"
+"speed" "70"
+"team" "level_exit"
+}
+{
+"spawnflags" "4"
+"angle" "0"
+"classname" "monster_soldier_light"
+"origin" "1168 232 712"
+}
+{
+"light" "135"
+"origin" "2032 888 698"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"light" "135"
+"origin" "1904 888 698"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"light" "135"
+"origin" "1904 992 699"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"light" "135"
+"origin" "2032 992 702"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"origin" "1968 992 640"
+"map" "rlava1$lstart1"
+"targetname" "t167"
+"classname" "target_changelevel"
+}
+{
+"model" "*98"
+"spawnflags" "2048"
+"angle" "90"
+"target" "t167"
+"classname" "trigger_multiple"
+}
+{
+"origin" "1136 344 712"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"targetname" "t350"
+"classname" "ammo_rockets"
+"spawnflags" "5889"
+"origin" "2064 664 560"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_rockets"
+"origin" "2080 712 560"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+"origin" "1504 520 504"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+"origin" "1544 520 504"
+}
+{
+"origin" "1968 1000 584"
+"angle" "270"
+"targetname" "mineend"
+"classname" "info_player_start"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "672 160 792"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "680 128 752"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "640 136 888"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "784 304 896"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "856 344 832"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "872 360 760"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"classname" "light"
+"origin" "1048 424 784"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+}
+{
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1048 456 736"
+"classname" "light"
+}
+{
+"classname" "item_health_small"
+"origin" "424 160 728"
+}
+{
+"origin" "440 200 736"
+"classname" "item_health_small"
+}
+{
+"origin" "424 120 720"
+"classname" "item_health_small"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "608 224 823"
+"_color" "1.000000 0.850980 0.850980"
+}
+{
+"light" "76"
+"classname" "light"
+"origin" "520 256 812"
+"_color" "1.000000 0.850980 0.850980"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+"origin" "480 280 744"
+}
+{
+"_color" "1.000000 0.850980 0.850980"
+"origin" "896 320 756"
+"classname" "light"
+"light" "100"
+}
+{
+"_color" "1.000000 0.850980 0.850980"
+"origin" "800 288 772"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "target_help"
+"message" "Destroy all resistance!"
+"spawnflags" "2048"
+"targetname" "t5"
+"origin" "-2168 -2288 144"
+}
+{
+"spawnflags" "2049"
+"origin" "-2128 -2320 144"
+"targetname" "t5"
+"message" "Find Comm Center to \nretrieve orders."
+"classname" "target_help"
+}
+{
+"origin" "-896 -1048 72"
+"classname" "light"
+"light" "200"
+"_color" "1.000000 1.000000 0.501961"
+}
+
diff --git a/rogue/stuff/mapfixes/rsewer1.ent b/rogue/stuff/mapfixes/rsewer1.ent
new file mode 100644
index 0000000..5dd0d11
--- /dev/null
+++ b/rogue/stuff/mapfixes/rsewer1.ent
@@ -0,0 +1,5282 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Fixed "color is not a field" warnings
+//
+// 2. Removed lava sounds when filtration system has been activated (b#2)
+//
+// 3. Fixed broken trigger_once and target_speaker (b#3)
+//
+// There are a number of trigger_once that target a voice speaker.
+// There was one pair that did not have any target/targetname so
+// I linked them together.
+//
+// 4. Added missing spawnflags to some entities (b#4)
+//
+// 5. Fixed overlapping secret sound effects (b#5)
+//
+// 6. Fixed old classnames (b#6)
+{
+"sounds" "11"
+"nextmap" "rsewer2"
+"message" "Waste Processing"
+"classname" "worldspawn"
+}
+{
+"origin" "2368 -1064 288"
+"noise" "world/valve.wav"
+"spawnflags" "2048"
+"classname" "target_speaker"
+"targetname" "t19"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2048"
+"noise" "world/valve.wav"
+"origin" "2368 -72 200"
+"targetname" "t49"
+}
+{
+"origin" "2368 352 152"
+"noise" "world/valve.wav"
+"spawnflags" "2048"
+"classname" "target_speaker"
+"targetname" "t52"
+}
+{
+"classname" "item_adrenaline"
+"angle" "90"
+"origin" "2248 -864 744"
+}
+{
+"model" "*1"
+"classname" "func_wall"
+"spawnflags" "3584"
+}
+{
+"noise" "world/amb8.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2368 -264 328"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8.wav"
+"origin" "2368 -384 328"
+}
+{
+"noise" "world/amb8.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2368 -384 560"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8.wav"
+"origin" "2368 -264 560"
+}
+{
+"noise" "world/amb14.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1184 480 208"
+}
+{
+"noise" "world/amb14.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1184 376 208"
+}
+{
+"volume" "1"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"origin" "1184 232 456"
+"noise" "world/amb16.wav"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14.wav"
+"origin" "1184 584 208"
+}
+{
+"classname" "target_help"
+"spawnflags" "2048"
+"message" "Deactivate green security\nlasers."
+"origin" "2136 -2432 64"
+"targetname" "t9"
+}
+{
+"model" "*2"
+"classname" "func_wall"
+"spawnflags" "256"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2320 -8 272"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2368 -72 272"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2656 264 176"
+}
+{
+"model" "*3"
+"classname" "trigger_hurt"
+"dmg" "10000"
+"spawnflags" "2050"
+"targetname" "t8"
+}
+{
+"model" "*4"
+"spawnflags" "2050"
+"dmg" "10000"
+"classname" "trigger_hurt"
+"targetname" "t8"
+}
+{
+"model" "*5"
+"classname" "trigger_hurt"
+"dmg" "10000"
+"spawnflags" "2050"
+"targetname" "t8"
+}
+{
+"model" "*6"
+"origin" "2368 -55 200"
+"classname" "func_door_rotating"
+"spawnflags" "10376"
+"target" "t49"
+"targetname" "t18"
+"wait" "-1"
+"distance" "90"
+}
+{
+"classname" "trigger_relay"
+"spawnflags" "2048" // b#4: added this
+"targetname" "reac"
+"delay" "15"
+"origin" "1512 -168 200"
+"message" "Containment bulkheads activated."
+"target" "t128"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048" // b#4: added this
+"targetname" "t127"
+"wait" "-1"
+"origin" "1488 0 192"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048" // b#4: added this
+"targetname" "t126"
+"target" "t127"
+"origin" "1488 0 312"
+}
+{
+"model" "*7"
+"classname" "func_train"
+"target" "t126"
+"spawnflags" "2048"
+"speed" "25"
+"targetname" "t128"
+}
+{
+"origin" "3616 -120 336"
+"spawnflags" "2048"
+"targetname" "t23"
+"classname" "trigger_relay"
+"killtarget" "brusher"
+}
+{
+"origin" "2032 -1992 -120"
+"angles" "15 45 0"
+"spawnflags" "0"
+"classname" "info_player_intermission"
+}
+{
+"origin" "1624 608 216"
+"attenuation" "-1"
+"noise" "world/voice5.wav"
+"spawnflags" "2048"
+"targetname" "t125"
+"classname" "target_speaker"
+}
+{
+"model" "*8"
+"spawnflags" "2048"
+"target" "t125"
+"classname" "trigger_once"
+}
+{
+"attenuation" "-1"
+"origin" "792 920 384"
+"spawnflags" "2048"
+"noise" "world/voice4.wav"
+"targetname" "t124"
+"classname" "target_speaker"
+}
+{
+"model" "*9"
+"target" "t124"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"model" "*10"
+"target" "t124b" // b#3: added this
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"classname" "target_speaker"
+"targetname" "t124b" // b#3: added this
+"noise" "world/voice3.wav"
+"attenuation" "-1"
+"spawnflags" "2048"
+"origin" "2632 -192 176"
+}
+{
+"model" "*11"
+"target" "t123"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"targetname" "t123"
+"spawnflags" "2048"
+"attenuation" "-1"
+"noise" "world/unit3_01.wav"
+"classname" "target_speaker"
+"origin" "2304 -2400 64"
+}
+{
+"model" "*12"
+"target" "t122"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"origin" "2280 -1784 -136"
+"targetname" "t122"
+"classname" "target_speaker"
+"noise" "world/unit3_03.wav"
+"attenuation" "-1"
+"spawnflags" "2048"
+}
+{
+"origin" "2664 -192 368"
+"spawnflags" "2048"
+"attenuation" "-1"
+"noise" "world/voice10.wav"
+"targetname" "t121"
+"classname" "target_speaker"
+}
+{
+"model" "*13"
+"spawnflags" "2048"
+"target" "t121"
+"classname" "trigger_once"
+}
+{
+"model" "*14"
+"spawnflags" "1024"
+"classname" "func_wall"
+}
+{
+"model" "*15"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*16"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*17"
+"lip" "16"
+"classname" "func_plat2"
+"spawnflags" "5889"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "2944 96 344"
+}
+{
+"origin" "928 992 216"
+"classname" "info_player_deathmatch"
+"angle" "315"
+}
+{
+"origin" "928 -32 216"
+"classname" "info_player_deathmatch"
+"angle" "45"
+}
+{
+"origin" "1824 96 152"
+"classname" "info_player_deathmatch"
+"angle" "45"
+}
+{
+"origin" "2496 -8 344"
+"classname" "info_player_deathmatch"
+"angle" "270"
+}
+{
+"origin" "3624 -352 344"
+"classname" "info_player_deathmatch"
+"angle" "180"
+}
+{
+"angle" "225"
+"classname" "info_player_deathmatch"
+"origin" "2008 -352 312"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "2304 544 344"
+}
+{
+"angle" "0"
+"classname" "info_player_deathmatch"
+"origin" "352 672 408"
+}
+{
+"angle" "0"
+"classname" "info_player_deathmatch"
+"origin" "2272 -352 120"
+}
+{
+"angle" "180"
+"classname" "info_player_deathmatch"
+"origin" "2824 -1312 24"
+}
+{
+"angle" "90"
+"classname" "info_player_deathmatch"
+"origin" "2144 -2144 24"
+}
+{
+"origin" "3264 -728 344"
+"angle" "90"
+"targetname" "t120"
+"spawnflags" "5888"
+"classname" "misc_teleporter_dest"
+}
+{
+"angle" "45"
+"origin" "2368 -1248 -232"
+"target" "t120"
+"spawnflags" "5888"
+"classname" "misc_teleporter"
+}
+{
+"origin" "2368 -1384 -240"
+"spawnflags" "5888"
+"classname" "item_double"
+}
+{
+"angle" "270"
+"origin" "3064 208 344"
+"target" "t119"
+"spawnflags" "5888"
+"classname" "misc_teleporter"
+}
+{
+"angle" "90"
+"origin" "2368 -1504 -232"
+"targetname" "t119"
+"spawnflags" "5888"
+"classname" "misc_teleporter_dest"
+}
+{
+"origin" "3040 -448 152"
+"classname" "info_player_deathmatch"
+"angle" "0"
+}
+{
+"origin" "1888 -1824 -232"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "2368 -1024 272"
+"spawnflags" "5888"
+"classname" "item_sphere_vengeance"
+}
+{
+"origin" "2016 -2720 32"
+"spawnflags" "5888"
+"classname" "item_sphere_hunter"
+}
+{
+"origin" "2048 -712 528"
+"spawnflags" "5888"
+"classname" "ammo_cells"
+}
+{
+"origin" "1920 -616 528"
+"spawnflags" "5888"
+"classname" "weapon_hyperblaster"
+}
+{
+"origin" "2880 256 336"
+"spawnflags" "5888"
+"classname" "weapon_chaingun"
+}
+{
+"origin" "3400 -16 336"
+"spawnflags" "5888"
+"classname" "weapon_rocketlauncher"
+}
+{
+"origin" "3456 -192 528"
+"spawnflags" "5888"
+"classname" "ammo_nuke"
+}
+{
+"origin" "2920 -240 144"
+"spawnflags" "5888"
+"classname" "ammo_prox"
+}
+{
+"origin" "2880 -192 144"
+"spawnflags" "5888"
+"classname" "weapon_proxlauncher"
+}
+{
+"origin" "2272 416 144"
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "2368 344 144"
+"spawnflags" "5888"
+"classname" "weapon_etf_rifle" // b#6: nailgun -> etf_rifle
+}
+{
+"origin" "672 744 400"
+"spawnflags" "5888"
+"classname" "ammo_cells"
+}
+{
+"origin" "736 480 400"
+"spawnflags" "5888"
+"classname" "weapon_plasmabeam"
+}
+{
+"model" "*18"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*19"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*20"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*21"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*22"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*23"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*24"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*25"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*26"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t118"
+"message" "Radiation levels rising,\n area quarantined."
+}
+{
+"model" "*27"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"targetname" "t117"
+}
+{
+"spawnflags" "2048"
+"classname" "target_secret"
+"message" "You found a secret!" // b#5: moved this here
+"targetname" "t117"
+"origin" "352 264 464"
+}
+{
+"model" "*28"
+"spawnflags" "2048"
+"classname" "func_button"
+"angle" "270"
+"lip" "1"
+"target" "t117"
+"wait" "-1"
+}
+{
+"classname" "item_health_mega"
+"origin" "352 256 400"
+"angle" "0"
+}
+{
+"origin" "1440 1000 208"
+"classname" "item_health_large"
+}
+{
+"model" "*29"
+"dmg" "30"
+"spawnflags" "2079"
+"classname" "trigger_hurt"
+"targetname" "t114"
+"target" "t118"
+}
+{
+"classname" "item_health_large"
+"origin" "584 512 400"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "1024"
+"origin" "584 552 400"
+}
+{
+"classname" "item_health_small"
+"origin" "1640 552 144"
+}
+{
+"classname" "item_health_small"
+"origin" "1560 512 144"
+}
+{
+"classname" "item_health_small"
+"origin" "1560 552 144"
+}
+{
+"classname" "item_health_small"
+"origin" "1640 512 144"
+}
+{
+"origin" "2272 608 192"
+"classname" "monster_turret"
+"spawnflags" "1672"
+"angle" "270"
+"targetname" "t35"
+}
+{
+"model" "*30"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"classname" "ammo_flechettes"
+"origin" "2928 -144 144"
+}
+{
+"classname" "ammo_shells"
+"origin" "2968 -152 144"
+}
+{
+"spawnflags" "5120"
+"classname" "item_health_large"
+"origin" "3400 40 336"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "1536"
+"origin" "3440 40 336"
+}
+{
+"origin" "3544 56 336"
+"classname" "ammo_rockets"
+}
+{
+"model" "*31"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t18"
+"targetname" "light1"
+}
+{
+"model" "*32"
+"classname" "func_wall"
+"targetname" "light1"
+"spawnflags" "6"
+}
+{
+"style" "32"
+"origin" "696 560 480"
+"classname" "light"
+"spawnflags" "2049"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "light2"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"classname" "light"
+"light" "125"
+"_color" "0.363636 1.000000 0.395722"
+"origin" "696 560 480"
+"targetname" "light1"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "2049"
+"classname" "light"
+"origin" "696 400 480"
+"targetname" "light2"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"_color" "0.363636 1.000000 0.395722"
+"light" "125"
+"classname" "light"
+"origin" "696 400 480"
+"targetname" "light1"
+}
+{
+"spawnflags" "2048"
+"classname" "target_help"
+"targetname" "t40"
+"origin" "608 624 440"
+"message" "Evacuate area! Radiation\n levels toxic!"
+}
+{
+"model" "*33"
+"classname" "trigger_once"
+"spawnflags" "2052"
+"targetname" "light1"
+"target" "t116"
+}
+{
+"model" "*34"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"targetname" "t19"
+"target" "t17"
+}
+{
+"model" "*35"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t17"
+"targetname" "t49"
+}
+{
+"model" "*36"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t17"
+"targetname" "t52"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"target" "signs"
+"targetname" "light1"
+"origin" "528 896 400"
+}
+{
+"attenuation" "-1"
+"classname" "target_speaker"
+"origin" "1376 64 448"
+"spawnflags" "2054"
+"noise" "world/klaxon1.wav"
+"targetname" "t35"
+}
+{
+"attenuation" "-1"
+"classname" "target_speaker"
+"origin" "1376 752 448"
+"spawnflags" "2054"
+"noise" "world/klaxon1.wav"
+"targetname" "t35"
+}
+{
+"attenuation" "-1"
+"classname" "target_speaker"
+"origin" "1040 896 448"
+"spawnflags" "2054"
+"noise" "world/klaxon1.wav"
+"targetname" "t35"
+}
+{
+"attenuation" "-1"
+"classname" "target_speaker"
+"origin" "528 232 448"
+"spawnflags" "2054"
+"noise" "world/klaxon1.wav"
+"targetname" "t35"
+}
+{
+"attenuation" "-1"
+"classname" "target_speaker"
+"origin" "528 696 448"
+"spawnflags" "2054"
+"noise" "world/klaxon1.wav"
+"targetname" "t35"
+}
+{
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"origin" "1376 520 448"
+"classname" "target_speaker"
+"attenuation" "-1"
+"targetname" "t35"
+}
+{
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"origin" "736 496 448"
+"classname" "target_speaker"
+"attenuation" "-1"
+"targetname" "t35"
+}
+{
+"spawnflags" "2048"
+"classname" "ammo_slugs"
+"origin" "3040 224 336"
+}
+{
+"classname" "ammo_rockets"
+"origin" "1696 -728 528"
+}
+{
+"classname" "ammo_rockets"
+"origin" "1568 -160 208"
+}
+{
+"model" "*37"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "signs"
+}
+{
+"model" "*38"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "signs"
+}
+{
+"model" "*39"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "signs"
+}
+{
+"model" "*40"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "signs"
+}
+{
+"model" "*41"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "signs"
+}
+{
+"model" "*42"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "signs"
+}
+{
+"model" "*43"
+"classname" "func_wall"
+"spawnflags" "6"
+"targetname" "signs"
+}
+{
+"angle" "90"
+"spawnflags" "768"
+"classname" "monster_daedalus"
+"origin" "3264 -704 376"
+}
+{
+"classname" "monster_daedalus"
+"spawnflags" "770"
+"angle" "270"
+"targetname" "t5"
+"origin" "3064 192 376"
+}
+{
+"spawnflags" "1"
+"noise" "world/comp_hum2.wav"
+"classname" "target_speaker"
+"origin" "1824 -832 576"
+}
+{
+"spawnflags" "1"
+"noise" "world/comp_hum2.wav"
+"classname" "target_speaker"
+"origin" "3456 64 376"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum2.wav"
+"spawnflags" "2049"
+"origin" "2784 -1608 80"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/pump3.wav"
+"targetname" "reac"
+"origin" "2536 208 232"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/pump3.wav"
+"targetname" "reac"
+"origin" "2368 384 232"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/pump3.wav"
+"targetname" "reac"
+"origin" "2208 200 232"
+}
+{
+"classname" "ammo_rockets"
+"origin" "2712 -1184 16"
+}
+{
+"classname" "item_health_large"
+"origin" "2056 -1160 16"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t35"
+"origin" "2080 -1184 200"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t35"
+"origin" "2656 -1184 200"
+}
+{
+"classname" "monster_floater"
+"spawnflags" "768"
+"origin" "2080 -1184 272"
+}
+{
+"classname" "monster_floater"
+"origin" "2648 -1184 272"
+}
+{
+"model" "*44"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"targetname" "t35"
+}
+{
+"model" "*45"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"targetname" "t35"
+}
+{
+"classname" "target_explosion"
+"targetname" "t35"
+"origin" "2464 568 184"
+"spawnflags" "2816"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t35"
+"origin" "2272 568 184"
+}
+{
+"model" "*46"
+"classname" "func_explosive"
+"targetname" "t35"
+"spawnflags" "2816"
+}
+{
+"model" "*47"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"targetname" "t35"
+}
+{
+"angle" "270"
+"spawnflags" "416"
+"classname" "monster_turret"
+"targetname" "t35"
+"origin" "2272 608 192"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "928"
+"angle" "270"
+"targetname" "t35"
+"origin" "2464 608 192"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "1025"
+"origin" "2880 -200 152"
+}
+{
+"targetname" "t6"
+"spawnflags" "2"
+"item" "ammo_slugs"
+"classname" "monster_gladiator"
+"angle" "0"
+"origin" "3016 -192 152"
+}
+{
+"model" "*48"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-2"
+"targetname" "t18"
+"wait" "-1"
+}
+{
+"origin" "2992 -192 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2880 -192 240"
+}
+{
+"style" "34"
+"_color" "0.000000 0.666667 1.000000"
+"classname" "light"
+"targetname" "t53"
+"origin" "2368 -1128 96"
+"spawnflags" "2049"
+"light" "125"
+}
+{
+"style" "34"
+"classname" "light"
+"_color" "0.000000 0.666667 1.000000"
+"targetname" "t53"
+"origin" "2368 -1128 352"
+"spawnflags" "2049"
+"light" "125"
+}
+{
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"origin" "648 392 448"
+"classname" "target_speaker"
+"attenuation" "-1"
+"targetname" "t35"
+}
+{
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"origin" "648 632 448"
+"classname" "target_speaker"
+"attenuation" "-1"
+"targetname" "t35"
+}
+{
+"classname" "monster_gunner"
+"spawnflags" "769"
+"angle" "0"
+"origin" "2240 -64 536"
+}
+{
+"classname" "item_health"
+"origin" "2528 -680 336"
+}
+{
+"model" "*49"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "t16"
+}
+{
+"origin" "2632 344 272"
+"classname" "ammo_slugs"
+}
+{
+"spawnflags" "2048"
+"classname" "trigger_relay"
+"delay" "19"
+"target" "t114"
+"targetname" "reac"
+"origin" "1512 896 464"
+}
+{
+"model" "*50"
+"classname" "trigger_hurt"
+"spawnflags" "2079"
+"dmg" "30"
+"targetname" "t114"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t110"
+"target" "t111"
+"origin" "2432 488 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t111"
+"target" "t112"
+"origin" "1864 312 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t112"
+"target" "t113"
+"origin" "1600 320 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t113"
+"origin" "1600 72 216"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t110"
+"origin" "2840 384 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t107"
+"target" "t108"
+"origin" "1576 56 216"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t108"
+"target" "t109"
+"origin" "1624 -200 216"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t109"
+"origin" "2200 -184 152"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t107"
+"origin" "1392 64 216"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t104"
+"target" "t105"
+"origin" "1560 768 224"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t105"
+"target" "t106"
+"origin" "2112 768 352"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t106"
+"origin" "2120 536 352"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t104"
+"origin" "1408 744 224"
+}
+{
+"origin" "2224 -512 544"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t98"
+"target" "t99"
+}
+{
+"origin" "2240 -48 544"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t99"
+"target" "t100"
+}
+{
+"origin" "2488 -48 544"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t100"
+"target" "t101"
+}
+{
+"origin" "2496 -256 544"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t101"
+"target" "t102"
+}
+{
+"origin" "2504 -560 352"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t102"
+"target" "t103"
+}
+{
+"origin" "2376 -648 352"
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t103"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "2232 -832 544"
+"target" "t98"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2248 -536 352"
+"targetname" "t93"
+"target" "t94"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2232 -64 352"
+"targetname" "t94"
+"target" "t95"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2488 -64 352"
+"targetname" "t95"
+"target" "t96"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2488 -176 352"
+"targetname" "t96"
+"target" "t97"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "2880 -208 352"
+"targetname" "t97"
+}
+{
+"origin" "1856 -104 352"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t87"
+"target" "t88"
+}
+{
+"origin" "1856 336 352"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t88"
+"target" "t89"
+}
+{
+"origin" "2120 512 352"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t89"
+"target" "t90"
+}
+{
+"origin" "2688 512 352"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t90"
+"target" "t91"
+}
+{
+"origin" "2872 368 352"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t91"
+"target" "t92"
+}
+{
+"origin" "2880 -176 352"
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t92"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "1856 -416 336"
+"target" "t87"
+}
+{
+"origin" "2384 -608 352"
+"spawnflags" "2049"
+"classname" "hint_path"
+"target" "t93"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t82"
+"target" "t83"
+"origin" "3400 -184 352"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t83"
+"target" "t84"
+"origin" "3392 -192 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t84"
+"target" "t85"
+"origin" "3560 -192 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t85"
+"target" "t86"
+"origin" "3552 -8 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t86"
+"origin" "3184 -8 144"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t82"
+"origin" "2896 -192 352"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t80"
+"target" "t81"
+"origin" "2800 -568 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t79"
+"target" "t80"
+"origin" "3136 -576 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"target" "t79"
+"origin" "3152 -240 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t78"
+"origin" "3160 -136 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t77"
+"target" "t78"
+"origin" "3104 56 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t76"
+"target" "t77"
+"origin" "2896 56 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"target" "t76"
+"origin" "2752 -160 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t81"
+"origin" "2752 -216 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"target" "t75"
+"origin" "2752 -192 144"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t75"
+"origin" "2496 -192 144"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t73"
+"target" "t74"
+"origin" "2368 -512 88"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"targetname" "t74"
+"origin" "2368 -1016 24"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"target" "t73"
+"origin" "2360 -192 144"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t69"
+"target" "t70"
+"origin" "1952 -1264 -184"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t70"
+"target" "t71"
+"origin" "1736 -1256 -184"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t71"
+"target" "t72"
+"origin" "1736 -1840 24"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t72"
+"origin" "1912 -1848 8"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t69"
+"origin" "1960 -1656 -184"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t65"
+"target" "t66"
+"origin" "2176 -1080 8"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t66"
+"target" "t67"
+"origin" "1944 -1344 8"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t67"
+"target" "t68"
+"origin" "1952 -1840 8"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t68"
+"origin" "2240 -2064 8"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t65"
+"origin" "2352 -1000 8"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t61"
+"target" "t62"
+"origin" "2496 -1040 8"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t62"
+"target" "t63"
+"origin" "2776 -1296 8"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t63"
+"target" "t64"
+"origin" "2792 -1744 8"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t64"
+"origin" "2496 -2088 8"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t61"
+"origin" "2408 -1024 8"
+}
+{
+"classname" "ammo_bullets"
+"origin" "2744 536 336"
+}
+{
+"classname" "ammo_slugs"
+"origin" "1816 352 336"
+}
+{
+"origin" "2456 552 336"
+"classname" "item_health"
+}
+{
+"origin" "1952 -152 144"
+"classname" "item_health"
+}
+{
+"classname" "monster_parasite"
+"angle" "45"
+"spawnflags" "1"
+"origin" "368 584 408"
+}
+{
+"classname" "ammo_shells"
+"origin" "1160 -16 336"
+}
+{
+"classname" "ammo_flechettes" // b#6: nails -> flechettes
+"origin" "408 216 400"
+}
+{
+"classname" "ammo_prox"
+"origin" "416 736 400"
+}
+{
+"classname" "ammo_tesla"
+"origin" "352 736 400"
+}
+{
+"origin" "1224 840 336"
+"classname" "item_health_small"
+}
+{
+"origin" "1224 888 336"
+"classname" "item_health_small"
+}
+{
+"origin" "1224 936 336"
+"classname" "item_health_small"
+}
+{
+"classname" "item_health_small"
+"origin" "1224 792 336"
+}
+{
+"classname" "ammo_bullets"
+"origin" "1432 -32 208"
+}
+{
+"classname" "weapon_chaingun"
+"origin" "1408 968 208"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2052"
+"origin" "1368 920 192"
+}
+{
+"classname" "item_health_large"
+"origin" "1376 -32 208"
+}
+{
+"classname" "item_armor_shard"
+"origin" "992 496 208"
+}
+{
+"classname" "item_armor_shard"
+"origin" "992 544 208"
+}
+{
+"classname" "item_armor_shard"
+"origin" "992 592 208"
+}
+{
+"classname" "item_armor_shard"
+"origin" "992 448 208"
+}
+{
+"spawnflags" "0"
+"classname" "ammo_cells"
+"origin" "2832 416 144"
+}
+{
+"classname" "ammo_rockets"
+"origin" "1936 440 208"
+}
+{
+"classname" "item_health"
+"origin" "2136 144 144"
+}
+{
+"classname" "item_health"
+"origin" "2088 144 144"
+}
+{
+"classname" "item_health"
+"origin" "2400 552 336"
+}
+{
+"classname" "item_health"
+"origin" "1904 -152 152"
+}
+{
+"classname" "item_armor_shard"
+"origin" "2200 -128 528"
+}
+{
+"classname" "item_armor_shard"
+"origin" "2200 -80 528"
+}
+{
+"classname" "item_armor_shard"
+"origin" "2200 -32 528"
+}
+{
+"classname" "item_armor_shard"
+"origin" "2200 -176 528"
+}
+{
+"classname" "item_health"
+"origin" "2016 -792 528"
+}
+{
+"classname" "ammo_prox"
+"origin" "2200 -736 528"
+}
+{
+"model" "*51"
+"spawnflags" "2816"
+"classname" "func_wall"
+}
+{
+"origin" "2104 -480 672"
+"angle" "180"
+"spawnflags" "776"
+"classname" "monster_turret"
+}
+{
+"classname" "ammo_cells"
+"origin" "2292 -1064 272"
+}
+{
+"classname" "ammo_cells"
+"origin" "2268 -1024 272"
+}
+{
+"classname" "ammo_shells"
+"origin" "2148 -476 528"
+}
+{
+"classname" "trigger_relay"
+"spawnflags" "7936" // b#5: added this
+"targetname" "t60"
+"origin" "2208 -832 688"
+}
+{
+"classname" "target_secret"
+"targetname" "t60"
+"message" "You found a secret!" // b#5: moved this here
+"origin" "2232 -840 688"
+}
+{
+"model" "*52"
+"health" "10"
+"classname" "func_explosive"
+"target" "t60"
+}
+{
+"classname" "target_explosion"
+"targetname" "t60"
+"origin" "2232 -856 688"
+}
+{
+"classname" "trigger_relay"
+"target" "t59"
+"targetname" "t31"
+"spawnflags" "7168"
+"origin" "1712 -728 520"
+}
+{
+"classname" "monster_parasite"
+"angle" "315"
+"spawnflags" "769"
+"targetname" "t31"
+"target" "t59"
+"origin" "1696 -672 536"
+}
+{
+"model" "*53"
+"classname" "func_wall"
+"spawnflags" "2816"
+}
+{
+"origin" "2368 -696 480"
+"angle" "90"
+"spawnflags" "288"
+"classname" "monster_turret"
+"targetname" "t115"
+}
+{
+"classname" "item_health_large"
+"origin" "1984 -208 400"
+}
+{
+"classname" "item_sphere_defender"
+"origin" "2024 32 336"
+}
+{
+"classname" "item_health"
+"origin" "2528 -624 336"
+}
+{
+"classname" "ammo_bullets"
+"origin" "2200 -224 336"
+}
+{
+"classname" "item_health"
+"origin" "2200 -32 336"
+}
+{
+"classname" "item_health"
+"origin" "2200 -88 336"
+}
+{
+"classname" "monster_infantry"
+"angle" "0"
+"spawnflags" "1"
+"origin" "2544 -192 344"
+}
+{
+"classname" "monster_parasite"
+"angle" "270"
+"spawnflags" "1"
+"origin" "2496 -8 344"
+"target" "t115"
+}
+{
+"classname" "ammo_cells"
+"origin" "2848 96 336"
+}
+{
+"classname" "ammo_rockets"
+"origin" "3488 -288 144"
+}
+{
+"classname" "ammo_tesla"
+"origin" "3488 -608 336"
+}
+{
+"origin" "3200 -608 144"
+"classname" "item_armor_shard"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3152 -608 144"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3104 -608 144"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3248 -608 144"
+}
+{
+"classname" "ammo_flechettes" // b#6: nails -> flechettes
+"origin" "2896 104 144"
+}
+{
+"classname" "weapon_etf_rifle" // b#6: nailgun -> etf_rifle
+"origin" "2432 -112 160"
+}
+{
+"classname" "item_health_small"
+"origin" "2304 -148 144"
+}
+{
+"classname" "item_health_small"
+"origin" "2264 -148 144"
+}
+{
+"classname" "item_health_small"
+"origin" "2224 -148 144"
+}
+{
+"classname" "item_health_small"
+"origin" "2344 -148 144"
+}
+{
+"classname" "ammo_shells"
+"origin" "2336 -496 80"
+}
+{
+"classname" "ammo_shells"
+"origin" "2712 -304 144"
+}
+{
+"classname" "ammo_cells"
+"origin" "2472 -344 112"
+}
+{
+"classname" "monster_parasite"
+"angle" "90"
+"spawnflags" "1"
+"origin" "2720 -448 152"
+}
+{
+"classname" "monster_floater"
+"angle" "270"
+"spawnflags" "1"
+"origin" "2728 -48 232"
+}
+{
+"classname" "item_health"
+"origin" "2088 -1112 16"
+}
+{
+"classname" "item_armor_jacket"
+"origin" "2456 -2048 16"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2050"
+"origin" "2504 -2016 0"
+}
+{
+"spawnflags" "0"
+"classname" "ammo_rockets"
+"origin" "3328 24 144"
+}
+{
+"classname" "item_armor_combat"
+"origin" "2968 -192 144"
+}
+{
+"classname" "item_health_large"
+"origin" "3096 104 144"
+}
+{
+"spawnflags" "2048"
+"classname" "ammo_flechettes" // b#6: nails -> flechettes
+"origin" "3224 -672 336"
+}
+{
+"spawnflags" "2048"
+"classname" "ammo_grenades"
+"origin" "3224 -736 336"
+}
+{
+"classname" "item_health_large"
+"origin" "3096 152 336"
+}
+{
+"spawnflags" "2048"
+"classname" "item_health_large"
+"origin" "3096 224 336"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "1"
+"origin" "3552 -544 344"
+}
+{
+"classname" "monster_parasite"
+"angle" "270"
+"spawnflags" "257"
+"target" "t26"
+"origin" "3560 -216 344"
+}
+{
+"model" "*54"
+"classname" "func_wall"
+"spawnflags" "2304"
+}
+{
+"origin" "2368 -736 232"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "2368 -736 248"
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+}
+{
+"classname" "monster_parasite"
+"spawnflags" "768"
+"origin" "2368 -960 24"
+}
+{
+"classname" "monster_parasite"
+"angle" "180"
+"targetname" "t56"
+"spawnflags" "768"
+"origin" "1952 -1248 -192"
+}
+{
+"spawnflags" "257"
+"angle" "135"
+"classname" "monster_infantry"
+"origin" "2528 -2152 24"
+"item" "ammo_bullets"
+"targetname" "t57"
+"target" "t58"
+}
+{
+"classname" "monster_infantry"
+"angle" "45"
+"spawnflags" "769"
+"origin" "2208 -2152 24"
+"target" "t57"
+"targetname" "t58"
+}
+{
+"classname" "monster_parasite"
+"angle" "225"
+"targetname" "t51"
+"origin" "2856 -1728 24"
+"spawnflags" "1"
+}
+{
+"classname" "monster_parasite"
+"origin" "1888 -1696 24"
+"angle" "315"
+"targetname" "t51"
+"spawnflags" "1"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "2368 -1600 -24"
+}
+{
+"spawnflags" "2048"
+"target" "reac"
+"targetname" "light1"
+"origin" "712 1032 448"
+"delay" "2"
+"classname" "trigger_relay"
+}
+{
+"origin" "2856 -1640 16"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2856 -1600 16"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2856 -1560 16"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2856 -1680 16"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1720 -1472 -112"
+"classname" "item_health_small"
+}
+{
+"origin" "1720 -1424 -144"
+"classname" "item_health_small"
+}
+{
+"origin" "1720 -1376 -168"
+"classname" "item_health_small"
+}
+{
+"origin" "1720 -1520 -88"
+"classname" "item_health_small"
+}
+{
+"origin" "1944 -1960 32"
+"classname" "ammo_prox"
+}
+{
+"origin" "2000 -2456 32"
+"classname" "ammo_slugs"
+}
+{
+"origin" "2592 -2152 -240"
+"classname" "ammo_cells"
+}
+{
+"origin" "2848 -1848 -240"
+"classname" "item_health"
+}
+{
+"origin" "2848 -1792 -240"
+"classname" "item_health_large"
+}
+{
+"model" "*55"
+"spawnflags" "2048"
+"target" "t29"
+"classname" "trigger_once"
+}
+{
+"origin" "1600 552 144"
+"classname" "item_armor_body"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1600 -160 304"
+}
+{
+"model" "*56"
+"_minlight" ".2"
+"spawnflags" "2048"
+"wait" "-1"
+"targetname" "t35"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"origin" "1600 488 152"
+"angle" "270"
+"classname" "monster_gladiator"
+"item" "ammo_slugs"
+}
+{
+"spawnflags" "2048"
+"origin" "3224 -544 360"
+"target" "t5"
+"delay" "2"
+"targetname" "t5"
+"classname" "trigger_relay"
+}
+{
+"origin" "2720 456 128"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"spawnflags" "2048"
+"origin" "2792 472 144"
+"classname" "weapon_plasmabeam" // b#6: heat -> plasma
+}
+{
+"model" "*57"
+"spawnflags" "2048"
+"target" "t54"
+"classname" "trigger_once"
+}
+{
+"model" "*58"
+"spawnflags" "2048"
+"target" "t54"
+"classname" "trigger_once"
+}
+{
+"model" "*59"
+"spawnflags" "2048"
+"target" "t54"
+"classname" "trigger_once"
+}
+{
+"origin" "2656 224 280"
+"targetname" "t50"
+"spawnflags" "2050"
+"noise" "world/curnt3.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "2656 160 280"
+"targetname" "t50"
+"spawnflags" "2050"
+"noise" "world/curnt3.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "968 216 296"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "968 488 296"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "968 712 296"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "1400 216 296"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "1400 488 296"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "1400 712 296"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "1184 504 456"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"targetname" "reac"
+"noise" "world/amb16.wav"
+"origin" "1184 728 456"
+"classname" "target_speaker"
+"spawnflags" "2050"
+"volume" "1"
+}
+{
+"origin" "2492 -648 76"
+"targetname" "t48"
+"classname" "target_speaker"
+"noise" "world/curnt3.wav"
+"spawnflags" "2050"
+}
+{
+"origin" "2496 -496 108"
+"targetname" "t48"
+"noise" "world/curnt3.wav"
+"classname" "target_speaker"
+"spawnflags" "2050"
+}
+{
+"origin" "2240 -496 108"
+"targetname" "t48"
+"classname" "target_speaker"
+"noise" "world/curnt3.wav"
+"spawnflags" "2050"
+}
+{
+"origin" "2236 -648 76"
+"targetname" "t48"
+"noise" "world/curnt3.wav"
+"classname" "target_speaker"
+"spawnflags" "2050"
+}
+{
+"origin" "2368 -1128 352"
+"spawnflags" "2050"
+"targetname" "t53"
+"noise" "world/curnt3.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "2368 -1128 96"
+"noise" "world/curnt3.wav"
+"targetname" "t53"
+"spawnflags" "2050"
+"classname" "target_speaker"
+}
+{
+"model" "*60"
+"targetname" "t53"
+"speed" "100"
+"spawnflags" "2049"
+"angle" "-1"
+"classname" "func_water"
+}
+{
+"spawnflags" "2048"
+"target" "t53"
+"targetname" "t19"
+"classname" "trigger_relay"
+"origin" "2416 -1048 296"
+}
+{
+"model" "*61"
+"targetname" "t53"
+"classname" "func_water"
+"angle" "-1"
+"spawnflags" "2049"
+"speed" "100"
+}
+{
+"model" "*62"
+"spawnflags" "768"
+"classname" "func_wall"
+}
+{
+"origin" "1296 -56 416"
+"angle" "90"
+"spawnflags" "264"
+"classname" "monster_turret"
+}
+{
+"style" "32"
+"targetname" "light2"
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "2049"
+"classname" "light"
+"origin" "1544 712 312"
+}
+{
+"style" "32"
+"targetname" "light2"
+"origin" "1464 712 312"
+"classname" "light"
+"spawnflags" "2049"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "32"
+"targetname" "light2"
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "2049"
+"classname" "light"
+"origin" "1464 824 312"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"targetname" "light1"
+"classname" "light"
+"light" "125"
+"_color" "0.363636 1.000000 0.395722"
+"origin" "1464 712 312"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"targetname" "light1"
+"_color" "0.363636 1.000000 0.395722"
+"light" "125"
+"classname" "light"
+"origin" "1544 712 312"
+}
+{
+"style" "32"
+"targetname" "light2"
+"origin" "1544 824 312"
+"classname" "light"
+"spawnflags" "2049"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"targetname" "light1"
+"_color" "0.363636 1.000000 0.395722"
+"light" "125"
+"classname" "light"
+"origin" "1464 824 312"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"targetname" "light1"
+"classname" "light"
+"light" "125"
+"_color" "0.363636 1.000000 0.395722"
+"origin" "1544 824 312"
+}
+{
+"model" "*63"
+"targetname" "light1"
+"spawnflags" "6"
+"classname" "func_wall"
+}
+{
+"style" "32"
+"targetname" "light2"
+"origin" "904 840 440"
+"classname" "light"
+"spawnflags" "2049"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "904 840 440"
+"targetname" "light1"
+"classname" "light"
+"light" "125"
+"_color" "0.363636 1.000000 0.395722"
+}
+{
+"style" "32"
+"targetname" "light2"
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "2049"
+"classname" "light"
+"origin" "824 856 440"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "824 840 440"
+"targetname" "light1"
+"_color" "0.363636 1.000000 0.395722"
+"light" "125"
+"classname" "light"
+}
+{
+"style" "32"
+"targetname" "light2"
+"origin" "824 952 440"
+"classname" "light"
+"spawnflags" "2049"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "824 952 440"
+"targetname" "light1"
+"classname" "light"
+"light" "125"
+"_color" "0.363636 1.000000 0.395722"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "904 952 440"
+"targetname" "light1"
+"_color" "0.363636 1.000000 0.395722"
+"light" "125"
+"classname" "light"
+}
+{
+"style" "32"
+"targetname" "light2"
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "2049"
+"classname" "light"
+"origin" "904 952 440"
+}
+{
+"model" "*64"
+"targetname" "light1"
+"classname" "func_wall"
+"spawnflags" "6"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "1464 8 312"
+"targetname" "light1"
+"classname" "light"
+"light" "125"
+"_color" "0.363636 1.000000 0.395722"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "1464 120 312"
+"targetname" "light1"
+"_color" "0.363636 1.000000 0.395722"
+"light" "125"
+"classname" "light"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "1544 120 312"
+"targetname" "light1"
+"classname" "light"
+"light" "125"
+"_color" "0.363636 1.000000 0.395722"
+}
+{
+"style" "33"
+"spawnflags" "2048"
+"origin" "1544 8 312"
+"targetname" "light1"
+"_color" "0.363636 1.000000 0.395722"
+"light" "125"
+"classname" "light"
+}
+{
+"style" "32"
+"targetname" "light2"
+"origin" "1464 8 312"
+"classname" "light"
+"spawnflags" "2049"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "32"
+"targetname" "light2"
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "2049"
+"classname" "light"
+"origin" "1464 120 312"
+}
+{
+"style" "32"
+"targetname" "light2"
+"origin" "1544 120 312"
+"classname" "light"
+"spawnflags" "2049"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "4097"
+"classname" "light"
+"origin" "1184 576 272"
+}
+{
+"model" "*65"
+"targetname" "light1"
+"spawnflags" "6"
+"classname" "func_wall"
+}
+{
+"model" "*66"
+"origin" "2368 321 156"
+"targetname" "t18"
+"target" "t52"
+"spawnflags" "10376"
+"classname" "func_door_rotating"
+"wait" "-1"
+"sounds" "1"
+"distance" "90"
+}
+{
+"target" "t55"
+"spawnflags" "1"
+"origin" "2368 -216 152"
+"angle" "270"
+"classname" "monster_gladiator"
+"item" "ammo_slugs"
+}
+{
+"origin" "1856 -192 504"
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+}
+{
+"model" "*67"
+"spawnflags" "2816"
+"classname" "func_wall"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "264"
+"angle" "0"
+"origin" "2824 -192 608"
+}
+{
+"spawnflags" "2048"
+"targetname" "t52"
+"origin" "2616 376 240"
+"delay" "1.5"
+"target" "t50"
+"classname" "trigger_relay"
+}
+{
+"style" "35"
+"origin" "2656 192 280"
+"targetname" "t50"
+"spawnflags" "2049"
+"_color" "0.000000 0.666667 1.000000"
+"classname" "light"
+}
+{
+"model" "*68"
+"targetname" "t50"
+"angle" "-1"
+"spawnflags" "2049"
+"classname" "func_water"
+"speed" "200"
+}
+{
+"spawnflags" "2048"
+"origin" "648 384 448"
+"killtarget" "pmpbrsh"
+"targetname" "light1"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "2632 -120 168"
+"target" "t42"
+"targetname" "t49"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"targetname" "t46"
+"origin" "2526 -760 24"
+"angle" "-1"
+"classname" "target_steam"
+"sounds" "3"
+"_color" "6"
+"speed" "75"
+"count" "16"
+}
+{
+"spawnflags" "2048"
+"targetname" "t46"
+"origin" "2506 -760 24"
+"angle" "-1"
+"classname" "target_steam"
+"sounds" "3"
+"_color" "6"
+"speed" "75"
+"count" "16"
+}
+{
+"spawnflags" "2048"
+"targetname" "t46"
+"angle" "-1"
+"classname" "target_steam"
+"origin" "2466 -760 24"
+"sounds" "3"
+"_color" "6"
+"speed" "75"
+"count" "16"
+}
+{
+"spawnflags" "2048"
+"targetname" "t46"
+"classname" "target_steam"
+"angle" "-1"
+"origin" "2486 -760 24"
+"sounds" "3"
+"_color" "6"
+"speed" "75"
+"count" "16"
+}
+{
+"spawnflags" "2048"
+"count" "40"
+"targetname" "t45"
+"classname" "target_steam"
+"sounds" "3"
+"origin" "2232 -456 88"
+"angle" "-1"
+"speed" "100"
+}
+{
+"spawnflags" "2048"
+"count" "40"
+"targetname" "t45"
+"origin" "2488 -456 88"
+"sounds" "3"
+"classname" "target_steam"
+"angle" "-1"
+"speed" "100"
+}
+{
+"spawnflags" "2048"
+"count" "16"
+"speed" "75"
+"_color" "6"
+"sounds" "3"
+"targetname" "t46"
+"classname" "target_steam"
+"angle" "-1"
+"origin" "2250 -760 24"
+}
+{
+"spawnflags" "2048"
+"count" "16"
+"speed" "75"
+"_color" "6"
+"sounds" "3"
+"targetname" "t46"
+"classname" "target_steam"
+"angle" "-1"
+"origin" "2270 -760 24"
+}
+{
+"spawnflags" "2048"
+"target" "t48"
+"origin" "2336 -664 160"
+"targetname" "t42"
+"classname" "trigger_relay"
+"delay" "1.75"
+}
+{
+"spawnflags" "2048"
+"origin" "2328 -448 160"
+"target" "t47"
+"delay" "1"
+"targetname" "t42"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"count" "16"
+"speed" "75"
+"_color" "6"
+"sounds" "3"
+"origin" "2230 -760 24"
+"targetname" "t46"
+"angle" "-1"
+"classname" "target_steam"
+}
+{
+"spawnflags" "2048"
+"count" "16"
+"speed" "75"
+"_color" "6"
+"sounds" "3"
+"origin" "2210 -760 24"
+"targetname" "t46"
+"classname" "target_steam"
+"angle" "-1"
+}
+{
+"spawnflags" "2048"
+"targetname" "t48"
+"origin" "2416 -680 160"
+"target" "t46"
+"classname" "func_timer"
+}
+{
+"spawnflags" "2048"
+"count" "40"
+"speed" "100"
+"angle" "-1"
+"targetname" "t45"
+"classname" "target_steam"
+"sounds" "3"
+"origin" "2504 -456 88"
+}
+{
+"spawnflags" "2048"
+"targetname" "t47"
+"origin" "2336 -496 160"
+"target" "t45"
+"classname" "func_timer"
+}
+{
+"spawnflags" "2048"
+"count" "40"
+"speed" "100"
+"angle" "-1"
+"origin" "2248 -456 88"
+"sounds" "3"
+"targetname" "t45"
+"classname" "target_steam"
+}
+{
+"style" "36"
+"spawnflags" "2048"
+"origin" "2464 -456 112"
+"targetname" "t44"
+"classname" "light"
+"light" "40"
+}
+{
+"style" "36"
+"spawnflags" "2048"
+"targetname" "t44"
+"origin" "2528 -456 112"
+"light" "40"
+"classname" "light"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"origin" "2272 -456 112"
+"targetname" "t44"
+"classname" "light"
+"light" "40"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"origin" "2208 -456 112"
+"targetname" "t44"
+"light" "40"
+"classname" "light"
+}
+{
+"model" "*69"
+"targetname" "t42"
+"classname" "func_water"
+"angle" "-2"
+"spawnflags" "2049"
+"speed" "50"
+}
+{
+"model" "*70"
+"targetname" "t42"
+"classname" "func_water"
+"angle" "-1"
+"spawnflags" "2049"
+"speed" "75"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"targetname" "t44"
+"classname" "light"
+"light" "60"
+"origin" "2240 -424 112"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"targetname" "t44"
+"light" "80"
+"classname" "light"
+"origin" "2240 -480 112"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"targetname" "t44"
+"classname" "light"
+"light" "80"
+"origin" "2240 -544 104"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"targetname" "t44"
+"classname" "light"
+"light" "80"
+"origin" "2240 -608 88"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"targetname" "t44"
+"classname" "light"
+"light" "80"
+"origin" "2240 -672 72"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"targetname" "t44"
+"light" "80"
+"classname" "light"
+"origin" "2240 -736 56"
+}
+{
+"style" "36"
+"spawnflags" "2048"
+"origin" "2496 -480 112"
+"targetname" "t44"
+"classname" "light"
+"light" "80"
+}
+{
+"spawnflags" "2048"
+"origin" "2352 -536 112"
+"message" "az"
+"targetname" "t42"
+"target" "t44"
+"speed" "1.5"
+"classname" "target_lightramp"
+}
+{
+"style" "36"
+"spawnflags" "2048"
+"origin" "2496 -672 72"
+"targetname" "t44"
+"light" "80"
+"classname" "light"
+}
+{
+"style" "36"
+"spawnflags" "2048"
+"origin" "2496 -608 88"
+"targetname" "t44"
+"light" "80"
+"classname" "light"
+}
+{
+"style" "36"
+"spawnflags" "2048"
+"origin" "2496 -544 104"
+"targetname" "t44"
+"light" "80"
+"classname" "light"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"origin" "2496 -424 112"
+"targetname" "t44"
+"light" "60"
+"classname" "light"
+}
+{
+"style" "36"
+"spawnflags" "2049"
+"origin" "2496 -736 56"
+"targetname" "t44"
+"classname" "light"
+"light" "80"
+}
+{
+"model" "*71"
+"speed" "50"
+"spawnflags" "2049"
+"angle" "-2"
+"targetname" "t42"
+"classname" "func_water"
+}
+{
+"model" "*72"
+"speed" "75"
+"targetname" "t42"
+"spawnflags" "2049"
+"angle" "-1"
+"classname" "func_water"
+}
+{
+"spawnflags" "2048"
+"origin" "2360 -1808 -224"
+"killtarget" "stmtmr"
+"targetname" "t16"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "784 480 448"
+"killtarget" "glass"
+"targetname" "t36"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"origin" "796 476 452"
+"targetname" "glass"
+"classname" "info_notnull"
+}
+{
+"spawnflags" "2048"
+"origin" "2408 -976 312"
+"targetname" "t16"
+"classname" "target_goal"
+}
+{
+"spawnflags" "2048"
+"origin" "2448 -984 328"
+"message" "Use flooded entrance\n area to exit."
+"targetname" "t16"
+"classname" "target_help"
+}
+{
+"spawnflags" "2048"
+"origin" "656 608 448"
+"targetname" "t40"
+"classname" "target_goal"
+}
+{
+"spawnflags" "2048"
+"target" "t40"
+"targetname" "light1"
+"classname" "trigger_relay"
+"delay" "2"
+"origin" "624 512 448"
+}
+{
+"spawnflags" "2048"
+"message" "Use flow control valves\n to flood entrance area."
+"origin" "1736 760 320"
+"classname" "target_help"
+"targetname" "t116"
+}
+{
+"style" "32"
+"targetname" "light2"
+"classname" "light"
+"light" "175"
+"origin" "1256 216 172"
+"spawnflags" "1"
+}
+{
+"style" "32"
+"targetname" "light2"
+"classname" "light"
+"light" "175"
+"origin" "1256 744 172"
+"spawnflags" "1"
+}
+{
+"style" "32"
+"targetname" "light2"
+"classname" "light"
+"light" "175"
+"origin" "1112 744 172"
+"spawnflags" "1"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "992 896 272"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "776"
+"angle" "0"
+"origin" "2184 -512 672"
+}
+{
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+"origin" "2880 256 504"
+}
+{
+"spawnflags" "2048"
+"origin" "672 344 448"
+"target" "t35"
+"targetname" "light1"
+"delay" "10"
+"classname" "trigger_relay"
+}
+{
+"origin" "2608 448 152"
+"spawnflags" "1"
+"angle" "45"
+"classname" "monster_infantry"
+}
+{
+"model" "*73"
+"spawnflags" "2048"
+"target" "t39"
+"classname" "trigger_once"
+}
+{
+"origin" "1856 768 304"
+"spawnflags" "1"
+"targetname" "t39"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"spawnflags" "2048"
+"targetname" "t38"
+"noise" "world/brkglas.wav"
+"origin" "792 416 456"
+"classname" "target_speaker"
+}
+{
+"spawnflags" "2048"
+"targetname" "t37"
+"classname" "target_speaker"
+"origin" "792 544 456"
+"noise" "world/brkglas.wav"
+}
+{
+"spawnflags" "2048"
+"targetname" "t36"
+"noise" "world/brkglas.wav"
+"origin" "824 480 456"
+"classname" "target_speaker"
+}
+{
+"targetname" "reac"
+"origin" "1184 648 712"
+"classname" "monster_floater"
+"spawnflags" "2"
+}
+{
+"targetname" "reac"
+"origin" "1184 384 712"
+"classname" "monster_floater"
+"spawnflags" "2"
+}
+{
+"spawnflags" "2048"
+"origin" "1184 648 616"
+"targetname" "t35"
+"classname" "target_explosion"
+}
+{
+"spawnflags" "2048"
+"origin" "1192 384 616"
+"targetname" "t35"
+"classname" "target_explosion"
+}
+{
+"model" "*74"
+"targetname" "t35"
+"classname" "func_explosive"
+}
+{
+"model" "*75"
+"targetname" "t35"
+"classname" "func_explosive"
+}
+{
+"spawnflags" "2048"
+"origin" "664 632 448"
+"target" "t35"
+"delay" "2"
+"targetname" "light1"
+"classname" "trigger_relay"
+}
+{
+"targetname" "light1"
+"classname" "target_speaker"
+"origin" "960 64 304"
+"spawnflags" "2052"
+"noise" "world/lite_on3.wav"
+}
+{
+"targetname" "light1"
+"classname" "target_speaker"
+"origin" "1408 64 304"
+"spawnflags" "2052"
+"noise" "world/lite_on3.wav"
+}
+{
+"targetname" "light1"
+"classname" "target_speaker"
+"origin" "1408 768 304"
+"spawnflags" "2052"
+"noise" "world/lite_on3.wav"
+}
+{
+"targetname" "light1"
+"classname" "target_speaker"
+"origin" "656 480 448"
+"spawnflags" "2052"
+"noise" "world/lite_on3.wav"
+}
+{
+"targetname" "t35"
+"attenuation" "-1"
+"classname" "target_speaker"
+"origin" "648 488 448"
+"spawnflags" "2054"
+"noise" "world/klaxon1.wav"
+}
+{
+"targetname" "light1"
+"noise" "world/lite_on3.wav"
+"spawnflags" "2052"
+"origin" "960 768 304"
+"classname" "target_speaker"
+}
+{
+"model" "*76"
+"dmg" "10000"
+"spawnflags" "8"
+"classname" "trigger_hurt"
+}
+{
+"model" "*77"
+"dmg" "10000"
+"spawnflags" "2057"
+"classname" "trigger_hurt"
+}
+{
+"origin" "1248 432 256"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "1256 528 256"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "1104 528 256"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1096 432 256"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "680 704 392"
+"targetname" "t34"
+"classname" "point_combat"
+"spawnflags" "2049"
+}
+{
+"origin" "688 672 408"
+"target" "t34"
+"classname" "monster_infantry"
+"angle" "0"
+"spawnflags" "1"
+}
+{
+"origin" "680 320 392"
+"targetname" "t33"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "688 288 408"
+"target" "t33"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_infantry"
+}
+{
+"targetname" "t36"
+"origin" "576 896 376"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"origin" "1184 896 216"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_infantry"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "8"
+"angle" "270"
+"origin" "1296 1016 416"
+}
+{
+"origin" "1072 -56 416"
+"angle" "90"
+"spawnflags" "8"
+"classname" "monster_turret"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "800"
+"angle" "-2"
+"origin" "1184 512 632"
+}
+{
+"origin" "1184 -192 344"
+"angle" "90"
+"spawnflags" "1"
+"classname" "monster_gladiator"
+}
+{
+"target" "t51"
+"origin" "2368 -1768 24"
+"angle" "270"
+"spawnflags" "800"
+"classname" "monster_turret"
+}
+{
+"origin" "1832 152 152"
+"spawnflags" "1"
+"angle" "45"
+"classname" "monster_gladiator"
+}
+{
+"targetname" "t54"
+"origin" "2368 360 388"
+"angle" "90"
+"spawnflags" "1"
+"classname" "monster_daedalus"
+"item" "ammo_cells"
+}
+{
+"origin" "2072 -192 152"
+"angle" "0"
+"classname" "monster_infantry"
+"spawnflags" "1"
+}
+{
+"angle" "180"
+"origin" "2048 -712 536"
+"spawnflags" "1"
+"classname" "monster_infantry"
+"targetname" "t59"
+}
+{
+"origin" "2048 -664 536"
+"spawnflags" "769"
+"angle" "180"
+"classname" "monster_infantry"
+"targetname" "t59"
+"item" "ammo_bullets"
+}
+{
+"angle" "180"
+"origin" "1992 -688 536"
+"spawnflags" "257"
+"classname" "monster_infantry"
+"targetname" "t59"
+"item" "ammo_bullets"
+}
+{
+"origin" "1856 384 344"
+"targetname" "t32"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"origin" "1952 32 344"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_infantry"
+"item" "ammo_bullets"
+}
+{
+"origin" "2008 -352 312"
+"target" "t32"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_infantry"
+}
+{
+"origin" "2240 -576 440"
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+"targetname" "t115"
+}
+{
+"spawnflags" "257"
+"origin" "1952 -832 536"
+"target" "t31"
+"angle" "0"
+"classname" "monster_gladiator"
+}
+{
+"origin" "2496 -72 536"
+"spawnflags" "1"
+"targetname" "t30"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"origin" "2320 -408 792"
+"targetname" "t31"
+"angle" "270"
+"classname" "monster_daedalus"
+"spawnflags" "1"
+}
+{
+"origin" "2240 -792 440"
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+}
+{
+"spawnflags" "2048"
+"origin" "3624 -152 344"
+"light" "75"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"origin" "2848 -1688 -224"
+"light" "75"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"origin" "2320 -1032 0"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "2912 -616 344"
+"spawnflags" "1"
+"angle" "45"
+"classname" "monster_infantry"
+"item" "ammo_bullets"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "264"
+"angle" "270"
+"origin" "1072 1016 416"
+}
+{
+"classname" "monster_turret"
+"spawnflags" "8"
+"angle" "-2"
+"origin" "2368 -1600 -8"
+"targetname" "t29"
+}
+{
+"model" "*78"
+"spawnflags" "2048"
+"target" "t29"
+"classname" "trigger_once"
+}
+{
+"spawnflags" "1"
+"targetname" "t29"
+"origin" "1872 -1248 -192"
+"angle" "180"
+"classname" "monster_infantry"
+"target" "t56"
+}
+{
+"target" "t30"
+"origin" "2480 -640 344"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_infantry"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2656 120 176"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2728 192 176"
+}
+{
+"origin" "2584 192 176"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2416 -8 272"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2240 120 384"
+}
+{
+"origin" "2240 192 456"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2240 192 312"
+}
+{
+"origin" "2240 264 384"
+"light" "100"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"origin" "2720 512 328"
+"target" "t28"
+"targetname" "t27"
+"classname" "path_corner"
+}
+{
+"spawnflags" "2048"
+"origin" "2016 512 328"
+"targetname" "t28"
+"target" "t27"
+"classname" "path_corner"
+}
+{
+"origin" "2680 512 344"
+"angle" "180"
+"target" "t27"
+"classname" "monster_gunner"
+"item" "ammo_grenades"
+}
+{
+"origin" "2496 -792 440"
+"angle" "-2"
+"spawnflags" "8"
+"classname" "monster_turret"
+}
+{
+"origin" "3096 -40 152"
+"targetname" "t25"
+"target" "t24"
+"spawnflags" "1"
+"angle" "135"
+"classname" "monster_infantry"
+}
+{
+"origin" "3040 -448 152"
+"target" "t25"
+"targetname" "t24"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_infantry"
+}
+{
+"origin" "2880 -192 152"
+"angle" "180"
+"classname" "monster_gladiator"
+"item" "ammo_slugs"
+"spawnflags" "769"
+}
+{
+"targetname" "t26"
+"spawnflags" "1"
+"origin" "3416 0 344"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"origin" "3264 -704 376"
+"targetname" "t5"
+"classname" "monster_hover"
+"angle" "90"
+"spawnflags" "1026"
+}
+{
+"origin" "3064 192 376"
+"targetname" "t5"
+"spawnflags" "1026"
+"angle" "270"
+"classname" "monster_hover"
+}
+{
+"model" "*79"
+"spawnflags" "2048"
+"message" "Lift is inactive."
+"classname" "trigger_once"
+"targetname" "brusher"
+}
+{
+"model" "*80"
+"spawnflags" "2048"
+"message" "Lift activated."
+"wait" "-1"
+"target" "t23"
+"angle" "0"
+"classname" "func_button"
+}
+{
+"targetname" "t7"
+"origin" "3432 -192 576"
+"spawnflags" "2"
+"angle" "180"
+"classname" "monster_daedalus"
+}
+{
+"origin" "1816 -192 184"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_gunner"
+"target" "t21"
+}
+{
+"spawnflags" "2048"
+"origin" "1600 -192 216"
+"target" "t22"
+"targetname" "t21"
+"classname" "point_combat"
+}
+{
+"origin" "1600 64 216"
+"angle" "90"
+"spawnflags" "2049"
+"targetname" "t22"
+"classname" "point_combat"
+}
+{
+"targetname" "t55"
+"origin" "2520 -616 216"
+"classname" "monster_floater"
+"angle" "180"
+"spawnflags" "1"
+}
+{
+"targetname" "t55"
+"origin" "2216 -616 216"
+"spawnflags" "769"
+"angle" "0"
+"classname" "monster_floater"
+}
+{
+"origin" "640 288 504"
+"classname" "light"
+"light" "150"
+}
+{
+"model" "*81"
+"spawnflags" "2048"
+"message" "Filtration sector opened."
+"target" "t20"
+"wait" "-1"
+"angle" "0"
+"classname" "func_button"
+}
+{
+"model" "*82"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-1"
+"targetname" "t16"
+"wait" "-1"
+}
+{
+"model" "*83"
+"targetname" "t8"
+"classname" "func_wall"
+"spawnflags" "7"
+}
+{
+"model" "*84"
+"targetname" "t8"
+"classname" "func_wall"
+"spawnflags" "7"
+}
+{
+"model" "*85"
+"targetname" "t8"
+"spawnflags" "7"
+"classname" "func_wall"
+}
+{
+"origin" "2464 -72 128"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"model" "*86"
+"targetname" "t16"
+"spawnflags" "2049"
+"classname" "func_wall"
+}
+{
+"model" "*87"
+"spawnflags" "2048"
+"targetname" "t17"
+"target" "t16"
+"killtarget" "lavasnd" // b#2: added this
+"count" "3"
+"classname" "trigger_counter"
+}
+{
+"angle" "270"
+"targetname" "sew1end"
+"origin" "2344 -1432 -232"
+"classname" "info_player_coop"
+}
+{
+"angle" "270"
+"targetname" "sew1end"
+"origin" "2392 -1352 -232"
+"classname" "info_player_coop"
+}
+{
+"angle" "270"
+"targetname" "sew1end"
+"origin" "2344 -1344 -232"
+"classname" "info_player_coop"
+}
+{
+"targetname" "sew1end"
+"angle" "270"
+"origin" "2392 -1440 -232"
+"classname" "info_player_coop"
+}
+{
+"targetname" "rhangar1"
+"origin" "2016 -2760 40"
+"angle" "90"
+"classname" "info_player_start"
+}
+{
+"spawnflags" "2048"
+"origin" "2368 -1256 -224"
+"targetname" "t15"
+"map" "rsewer2$sew2start"
+"classname" "target_changelevel"
+}
+{
+"model" "*88"
+"spawnflags" "2048"
+"target" "t15"
+"classname" "trigger_multiple"
+"angle" "90"
+}
+{
+"origin" "2000 -2400 32"
+"classname" "ammo_tesla"
+}
+{
+"classname" "item_health_large"
+"origin" "2168 -2368 32"
+}
+{
+"origin" "2216 -2368 32"
+"classname" "item_health_large"
+}
+{
+"origin" "2792 -1648 -192"
+"spawnflags" "1"
+"angle" "225"
+"classname" "monster_floater"
+}
+{
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "2848 -1600 -208"
+}
+{
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "2088 -704 560"
+}
+{
+"origin" "2528 -1056 24"
+"target" "t13"
+"targetname" "t12"
+"classname" "monster_gunner"
+"angle" "315"
+"spawnflags" "1"
+"item" "ammo_grenades"
+}
+{
+"origin" "2208 -1056 24"
+"targetname" "t13"
+"target" "t12"
+"spawnflags" "1"
+"angle" "225"
+"classname" "monster_gunner"
+}
+{
+"model" "*89"
+"targetname" "t16"
+"classname" "func_explosive"
+"spawnflags" "256"
+}
+{
+"targetname" "t16"
+"origin" "2208 -1696 36"
+"spawnflags" "392"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"targetname" "t16"
+"angle" "-2"
+"origin" "2528 -1696 36"
+"spawnflags" "136"
+"classname" "monster_turret"
+}
+{
+"origin" "1952 -1440 224"
+"targetname" "t11"
+"angle" "-2"
+"spawnflags" "160"
+"classname" "monster_turret"
+}
+{
+"model" "*90"
+"spawnflags" "2048"
+"target" "t11"
+"classname" "trigger_once"
+}
+{
+"model" "*91"
+"spawnflags" "2048"
+"target" "t10"
+"classname" "trigger_once"
+}
+{
+"model" "*92"
+"wait" "-1"
+"targetname" "t10"
+"classname" "func_door"
+"angle" "180"
+}
+{
+"model" "*93"
+"wait" "-1"
+"targetname" "t10"
+"classname" "func_door"
+"angle" "0"
+}
+{
+"model" "*94"
+"wait" "-1"
+"targetname" "t11"
+"angle" "0"
+"classname" "func_door"
+}
+{
+"model" "*95"
+"wait" "-1"
+"targetname" "t11"
+"angle" "180"
+"classname" "func_door"
+}
+{
+"origin" "2784 -1604 -240"
+"classname" "weapon_hyperblaster"
+}
+{
+"origin" "2840 -1596 -256"
+"angle" "180"
+"spawnflags" "2064"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "2264 -1008 16"
+"classname" "weapon_rocketlauncher"
+}
+{
+"spawnflags" "160"
+"targetname" "t10"
+"origin" "2784 -1440 224"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"angle" "90"
+"spawnflags" "257"
+"origin" "2528 -2150 -112"
+"classname" "monster_daedalus"
+}
+{
+"angle" "90"
+"spawnflags" "1"
+"origin" "2208 -2150 -112"
+"classname" "monster_daedalus"
+}
+{
+"origin" "1840 -680 576"
+"message" "Find and activate reactor\n to start filtration system."
+"spawnflags" "2048"
+"classname" "target_help"
+"targetname" "t8"
+}
+{
+"origin" "2064 -2392 64"
+"message" "Gain access to the\n Waste Disposal plant."
+"spawnflags" "2049"
+"targetname" "t9"
+"classname" "target_help"
+}
+{
+"model" "*96"
+"spawnflags" "2048"
+"target" "t9"
+"classname" "trigger_once"
+}
+{
+"targetname" "rhangar1"
+"origin" "1992 -2616 40"
+"classname" "info_player_coop"
+"angle" "90"
+}
+{
+"targetname" "rhangar1"
+"origin" "2040 -2648 40"
+"classname" "info_player_coop"
+"angle" "90"
+}
+{
+"targetname" "rhangar1"
+"origin" "1992 -2672 40"
+"classname" "info_player_coop"
+"angle" "90"
+}
+{
+"targetname" "rhangar1"
+"origin" "2040 -2584 40"
+"classname" "info_player_coop"
+"angle" "90"
+}
+{
+"targetname" "sew1end"
+"classname" "info_player_start"
+"angle" "270"
+"origin" "2368 -1392 -232"
+}
+{
+"model" "*97"
+"spawnflags" "2048"
+"targetname" "pmpbrsh"
+"message" "Pumping systems inactive."
+"classname" "trigger_multiple"
+"wait" "60"
+}
+{
+"model" "*98"
+"spawnflags" "2048"
+"targetname" "pmpbrsh"
+"message" "Pumping systems inactive."
+"classname" "trigger_multiple"
+"wait" "60"
+}
+{
+"model" "*99"
+"spawnflags" "2048"
+"targetname" "pmpbrsh"
+"classname" "trigger_multiple"
+"message" "Pumping systems inactive."
+"wait" "60"
+}
+{
+"model" "*100"
+"origin" "2368 -1079 292"
+"sounds" "1"
+"wait" "-1"
+"target" "t19"
+"targetname" "t18"
+"classname" "func_door_rotating"
+"spawnflags" "10376"
+"distance" "90"
+}
+{
+"origin" "2560 608 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*101"
+"_minlight" ".2"
+"classname" "func_plat2"
+"lip" "16"
+}
+{
+"origin" "1184 -192 432"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1184 -96 272"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1760 320 240"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1792 768 366"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1600 768 318"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1760 -192 278"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "912 896 480"
+}
+{
+"origin" "2792 -1576 -208"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+}
+{
+"model" "*102"
+"spawnflags" "2048"
+"target" "t37"
+"health" "1"
+"classname" "func_explosive"
+}
+{
+"model" "*103"
+"spawnflags" "2048"
+"target" "t36"
+"health" "1"
+"classname" "func_explosive"
+}
+{
+"origin" "912 -16 368"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "976 -16 368"
+}
+{
+"origin" "1456 -16 368"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1392 -16 368"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1456 976 368"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2528 -736 112"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2208 -736 112"
+}
+{
+"origin" "1600 64 304"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "1184 -48 480"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1184 896 272"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1184 64 272"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2560 704 304"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2560 704 432"
+}
+{
+"origin" "1184 136 248"
+"classname" "light"
+"spawnflags" "4096"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "1296 288 272"
+"classname" "light"
+"spawnflags" "4096"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "576 896 464"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "816 896 432"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1600 288 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2368 -964 160"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "384 672 504"
+}
+{
+"origin" "736 480 440"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "416 336 504"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "640 672 504"
+}
+{
+"origin" "1152 896 480"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "1184 448 568"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "2112 608 432"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"style" "32"
+"spawnflags" "1"
+"targetname" "light2"
+"origin" "1112 216 172"
+"light" "175"
+"classname" "light"
+}
+{
+"origin" "1600 512 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1952 -192 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2144 -192 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*104"
+"spawnflags" "2048"
+"target" "t38"
+"classname" "func_explosive"
+"health" "1"
+}
+{
+"model" "*105"
+"targetname" "t23"
+"classname" "func_plat2"
+"lip" "16"
+"spawnflags" "2049"
+}
+{
+"model" "*106"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*107"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2048"
+"classname" "target_lightramp"
+"message" "az"
+"speed" "10"
+"targetname" "light1"
+"target" "light2"
+"origin" "1016 416 248"
+}
+{
+"model" "*108"
+"spawnflags" "2048"
+"message" "Reactor activated."
+"classname" "func_button"
+"angle" "90"
+"target" "light1"
+"wait" "-1"
+}
+{
+"style" "32"
+"origin" "1248 576 208"
+"classname" "light"
+"spawnflags" "4097"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "light2"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "4097"
+"classname" "light"
+"origin" "1248 480 208"
+"targetname" "light2"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "4097"
+"classname" "light"
+"origin" "1248 384 208"
+"targetname" "light2"
+}
+{
+"style" "32"
+"origin" "1120 384 208"
+"classname" "light"
+"spawnflags" "4097"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "light2"
+}
+{
+"style" "32"
+"origin" "1120 480 208"
+"classname" "light"
+"spawnflags" "4097"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "light2"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "4097"
+"classname" "light"
+"origin" "1120 576 208"
+"targetname" "light2"
+}
+{
+"style" "32"
+"origin" "1184 576 272"
+"classname" "light"
+"spawnflags" "4097"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "light2"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "4097"
+"classname" "light"
+"origin" "1184 480 272"
+"targetname" "light2"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "4097"
+"classname" "light"
+"origin" "1184 384 272"
+"targetname" "light2"
+}
+{
+"style" "32"
+"origin" "1184 480 144"
+"classname" "light"
+"spawnflags" "4097"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "light2"
+}
+{
+"style" "32"
+"origin" "1184 384 144"
+"classname" "light"
+"spawnflags" "4097"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "light2"
+}
+{
+"origin" "1040 288 200"
+"classname" "light"
+"spawnflags" "4096"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "4096"
+"classname" "light"
+"origin" "1072 288 272"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "4096"
+"classname" "light"
+"origin" "1072 672 272"
+}
+{
+"classname" "light"
+"spawnflags" "4096"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1040 672 200"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "4097"
+"classname" "light"
+"origin" "1184 576 144"
+"targetname" "light2"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+"spawnflags" "4096"
+"classname" "light"
+"origin" "1184 824 248"
+}
+{
+"classname" "light"
+"spawnflags" "4096"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1328 288 200"
+}
+{
+"style" "32"
+"targetname" "light2"
+"_color" "1.000000 0.000000 0.000000"
+"light" "200"
+"spawnflags" "2049"
+"classname" "light"
+"origin" "1544 8 312"
+}
+{
+"classname" "light"
+"spawnflags" "4096"
+"light" "200"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1296 672 272"
+}
+{
+"model" "*109"
+"origin" "1184 432 208"
+"speed" "500"
+"spawnflags" "8202"
+"classname" "func_rotating"
+"targetname" "light1"
+"accel" "12"
+"decel" "12"
+"_minlight" ".2"
+}
+{
+"model" "*110"
+"origin" "1184 528 208"
+"classname" "func_rotating"
+"spawnflags" "8200"
+"speed" "500"
+"targetname" "light1"
+"accel" "12"
+"decel" "12"
+"_minlight" ".2"
+}
+{
+"origin" "3608 -256 368"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2416 -1104 432"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2368 -1072 432"
+}
+{
+"origin" "2144 -512 624"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1760 -624 432"
+}
+{
+"origin" "2256 -624 624"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2048 -624 624"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1920 -608 624"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2016 -384 384"
+}
+{
+"origin" "1720 -872 608"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1928 -872 608"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1928 -792 608"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1864 -728 608"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1784 -728 608"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1824 -864 608"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1824 -776 608"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "1792 -624 624"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum2.wav"
+"origin" "1848 -288 392"
+"targetname" "t8"
+}
+{
+"spawnflags" "2049"
+"origin" "1848 -288 344"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+"targetname" "t8"
+}
+{
+"classname" "target_speaker"
+"noise" "world/l_hum2.wav"
+"origin" "2144 -200 160"
+"spawnflags" "2049"
+"targetname" "t8"
+}
+{
+"origin" "2144 -200 208"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"targetname" "t8"
+}
+{
+"classname" "target_speaker"
+"noise" "world/l_hum2.wav"
+"origin" "2872 160 200"
+"spawnflags" "2049"
+"targetname" "t8"
+}
+{
+"origin" "2872 160 152"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"targetname" "t8"
+}
+{
+"spawnflags" "2048"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+"targetname" "t8"
+"origin" "2888 152 144"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/lasoff1.wav"
+"targetname" "t8"
+"origin" "2888 160 200"
+}
+{
+"spawnflags" "2048"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+"targetname" "t8"
+"origin" "1864 -296 352"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/lasoff1.wav"
+"targetname" "t8"
+"origin" "1864 -288 392"
+}
+{
+"spawnflags" "2048"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+"targetname" "t8"
+"origin" "2152 -184 216"
+}
+{
+"spawnflags" "2048"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+"targetname" "t8"
+"origin" "1936 -616 552"
+}
+{
+"spawnflags" "2048"
+"noise" "world/lasoff1.wav"
+"classname" "target_speaker"
+"targetname" "t8"
+"origin" "1904 -616 552"
+}
+{
+"spawnflags" "2048"
+"classname" "target_speaker"
+"noise" "world/lasoff1.wav"
+"targetname" "t8"
+"origin" "2144 -184 160"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"_color" "0.239437 1.000000 0.201878"
+"origin" "2880 160 224"
+"light" "100"
+"classname" "light"
+"targetname" "t8"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"_color" "0.239437 1.000000 0.201878"
+"origin" "2880 160 184"
+"light" "100"
+"classname" "light"
+"targetname" "t8"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"_color" "0.239437 1.000000 0.201878"
+"classname" "light"
+"light" "100"
+"origin" "2880 160 144"
+"targetname" "t8"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"_color" "0.239437 1.000000 0.201878"
+"origin" "1856 -288 416"
+"light" "100"
+"classname" "light"
+"targetname" "t8"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"_color" "0.239437 1.000000 0.201878"
+"origin" "1856 -288 376"
+"light" "100"
+"classname" "light"
+"targetname" "t8"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"_color" "0.239437 1.000000 0.201878"
+"classname" "light"
+"light" "100"
+"origin" "1856 -288 336"
+"targetname" "t8"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"classname" "light"
+"light" "100"
+"origin" "2144 -192 184"
+"_color" "0.239437 1.000000 0.201878"
+"targetname" "t8"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"classname" "light"
+"light" "100"
+"origin" "2144 -192 224"
+"_color" "0.239437 1.000000 0.201878"
+"targetname" "t8"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1856 -288 432"
+}
+{
+"origin" "2144 -832 624"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "50"
+"origin" "1720 -792 608"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2368 -736 432"
+}
+{
+"origin" "2320 -1104 432"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2624 -192 432"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*111"
+"spawnflags" "2048"
+"classname" "func_button"
+"angle" "90"
+"target" "t8"
+"message" "Security lasers deactivated."
+"wait" "-1"
+}
+{
+"model" "*112"
+"targetname" "t16"
+"classname" "func_water"
+"spawnflags" "2049"
+"angle" "-2"
+"speed" "25"
+}
+{
+"model" "*113"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*114"
+"lip" "16"
+"classname" "func_plat2"
+"_minlight" ".2"
+"spawnflags" "1"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2496 -240 432"
+}
+{
+"dmg" "10000"
+"origin" "2936 160 224"
+"classname" "target_laser"
+"spawnflags" "2053"
+"angle" "180"
+"targetname" "t8"
+}
+{
+"dmg" "10000"
+"origin" "2936 160 184"
+"classname" "target_laser"
+"spawnflags" "2053"
+"angle" "180"
+"targetname" "t8"
+}
+{
+"dmg" "10000"
+"origin" "2936 160 144"
+"angle" "180"
+"spawnflags" "2053"
+"classname" "target_laser"
+"targetname" "t8"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1984 264 592"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1912 192 592"
+}
+{
+"origin" "1984 120 592"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2056 192 592"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2728 192 464"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3320 -400 176"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3392 -472 176"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3392 -328 176"
+}
+{
+"dmg" "10000"
+"origin" "2144 -136 224"
+"classname" "target_laser"
+"spawnflags" "2053"
+"angle" "270"
+"targetname" "t8"
+}
+{
+"dmg" "10000"
+"origin" "2144 -136 184"
+"classname" "target_laser"
+"spawnflags" "2053"
+"angle" "270"
+"targetname" "t8"
+}
+{
+"dmg" "10000"
+"origin" "2144 -136 144"
+"angle" "270"
+"spawnflags" "2053"
+"classname" "target_laser"
+"targetname" "t8"
+}
+{
+"angle" "180"
+"spawnflags" "2053"
+"classname" "target_laser"
+"origin" "1912 -288 376"
+"dmg" "10000"
+"targetname" "t8"
+}
+{
+"angle" "180"
+"spawnflags" "2053"
+"classname" "target_laser"
+"origin" "1912 -288 416"
+"dmg" "10000"
+"targetname" "t8"
+}
+{
+"classname" "target_laser"
+"spawnflags" "2053"
+"angle" "180"
+"origin" "1912 -288 336"
+"dmg" "10000"
+"targetname" "t8"
+}
+{
+"model" "*115"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t7"
+}
+{
+"model" "*116"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-1"
+"targetname" "t7"
+"wait" "-1"
+"speed" "300"
+}
+{
+"style" "37"
+"spawnflags" "2048"
+"origin" "2144 -192 144"
+"light" "100"
+"classname" "light"
+"_color" "0.239437 1.000000 0.201878"
+"targetname" "t8"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3456 96 416"
+}
+{
+"origin" "3456 96 396"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3456 96 440"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3400 120 392"
+}
+{
+"_color" "1.000000 0.221277 0.221277"
+"origin" "3424 72 368"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "3456 96 460"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+"origin" "640 216 432"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "3064 208 424"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "3264 -720 432"
+}
+{
+"spawnflags" "2048"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "3392 -192 432"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2880 160 240"
+}
+{
+"origin" "3360 -192 592"
+"_color" "1.000000 0.771429 0.297959"
+"light" "175"
+"classname" "light"
+}
+{
+"origin" "2784 -192 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2624 -192 240"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3128 120 392"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3200 56 456"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "50"
+"origin" "3488 72 368"
+"_color" "1.000000 0.221277 0.221277"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3200 56 176"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3320 -400 592"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2584 192 464"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2656 264 464"
+}
+{
+"origin" "2656 120 464"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3392 -472 592"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3464 -400 592"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3328 56 456"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3392 -328 592"
+}
+{
+"origin" "3128 120 176"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "3328 56 176"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3400 120 176"
+}
+{
+"model" "*117"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t6"
+}
+{
+"model" "*118"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-2"
+"targetname" "t6"
+"wait" "-1"
+"speed" "300"
+}
+{
+"model" "*119"
+"classname" "func_door"
+"angle" "-1"
+"targetname" "t5"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"model" "*120"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t5"
+}
+{
+"spawnflags" "2048"
+"volume" ".5"
+"classname" "target_speaker"
+"noise" "world/steam1.wav"
+"targetname" "t1"
+"origin" "2448 -1888 -320"
+}
+{
+"spawnflags" "2048"
+"volume" ".5"
+"noise" "world/steam1.wav"
+"classname" "target_speaker"
+"targetname" "t1"
+"origin" "2466 -1594 -308"
+}
+{
+"spawnflags" "2048"
+"volume" ".5"
+"classname" "target_speaker"
+"noise" "world/lava1.wav"
+"targetname" "t2"
+"origin" "2576 -1728 -320"
+}
+{
+"spawnflags" "2048"
+"volume" ".5"
+"noise" "world/lava1.wav"
+"classname" "target_speaker"
+"targetname" "t4"
+"origin" "2208 -1600 -320"
+}
+{
+"spawnflags" "2048"
+"volume" ".5"
+"classname" "target_speaker"
+"targetname" "t3"
+"noise" "world/lava1.wav"
+"origin" "2248 -1856 -320"
+}
+{
+"spawnflags" "2048"
+"dmg" "25"
+"count" "16"
+"sounds" "5"
+"classname" "target_splash"
+"targetname" "t4"
+"origin" "2232 -1592 -376"
+}
+{
+"classname" "func_timer"
+"spawnflags" "2049"
+"target" "t4"
+"targetname" "lavasnd" // b#2: added this
+"wait" "10"
+"origin" "2208 -1584 -320"
+"random" "2"
+}
+{
+"spawnflags" "2048"
+"dmg" "25"
+"count" "16"
+"sounds" "5"
+"classname" "target_splash"
+"targetname" "t3"
+"origin" "2264 -1840 -376"
+}
+{
+"classname" "func_timer"
+"spawnflags" "2049"
+"target" "t3"
+"targetname" "lavasnd" // b#2: added this
+"random" "3"
+"wait" "7"
+"origin" "2240 -1832 -320"
+}
+{
+"spawnflags" "2049"
+"classname" "func_timer"
+"target" "t2"
+"targetname" "lavasnd" // b#2: added this
+"wait" "5"
+"random" "3"
+"origin" "2544 -1752 -320"
+}
+{
+"spawnflags" "2048"
+"classname" "target_splash"
+"sounds" "5"
+"count" "16"
+"dmg" "25"
+"targetname" "t2"
+"origin" "2568 -1760 -376"
+}
+{
+"spawnflags" "2048"
+"sounds" "2"
+"wait" "1"
+"angle" "270"
+"classname" "target_steam"
+"targetname" "t1"
+"origin" "2466 -1582 -308"
+}
+{
+"targetname" "stmtmr"
+"classname" "func_timer"
+"spawnflags" "2049"
+"target" "t1"
+"origin" "2488 -1880 -320"
+}
+{
+"spawnflags" "2048"
+"sounds" "2"
+"classname" "target_steam"
+"angle" "90"
+"targetname" "t1"
+"wait" "1"
+"origin" "2448 -1892 -320"
+}
+{
+"origin" "2464 -1536 -192"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2368 -1528 -96"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2312 -864 24"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1984 768 414"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1392 976 368"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2424 -864 24"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2728 -1440 24"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2840 -1440 24"
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "1960 -1224 24"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+}
+{
+"origin" "2152 -1032 24"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2584 -1032 24"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2600 -1208 24"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2776 -1224 24"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "2272 -1536 -192"
+}
+{
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+"origin" "2136 -1208 24"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "3392 -192 224"
+}
+{
+"_color" "1.000000 0.771429 0.297959"
+"light" "100"
+"classname" "light"
+"origin" "2008 -1440 24"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.771429 0.297959"
+"origin" "1896 -1440 24"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2016 -2400 112"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2208 -2400 112"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2368 -2400 112"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2368 -2208 112"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1824 -1840 112"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "1952 -1312 -120"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "1824 -1248 -120"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "1736 -1376 -96"
+}
+{
+"origin" "1736 -1568 8"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "1736 -1760 112"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "2016 -2584 112"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1952 -1504 -120"
+"light" "75"
+"classname" "light"
+}
+{
+"model" "*121"
+"message" "Filtration sector access denied!"
+"spawnflags" "2048"
+"_minlight" ".2"
+"wait" "-1"
+"targetname" "t20"
+"angle" "-2"
+"classname" "func_door"
+} \ No newline at end of file
diff --git a/rogue/stuff/mapfixes/rsewer2.ent b/rogue/stuff/mapfixes/rsewer2.ent
new file mode 100644
index 0000000..f3bf6e6
--- /dev/null
+++ b/rogue/stuff/mapfixes/rsewer2.ent
@@ -0,0 +1,6805 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Fixed broken path chain for monster_soldier_light (4712)
+// (fix suggested by NightFright2k19 on github)
+//
+// The last path_corner (3431) has target t215 but no entity
+// has that targetname. Fixed by clearing the target field.
+//
+// 2. Fixed tagetname typos
+{
+"sky" "rogue1"
+"sounds" "7"
+"message" "Waste Disposal"
+"classname" "worldspawn"
+"nextmap" "rhangar2"
+}
+{
+"model" "*1"
+"classname" "func_door"
+"angle" "-1"
+"targetname" "t122"
+"wait" "-1"
+"speed" "150"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t276"
+"target" "t320"
+"origin" "2600 936 328"
+}
+{
+"classname" "ammo_prox"
+"origin" "-168 728 144"
+}
+{
+"classname" "ammo_rockets"
+"origin" "488 1832 80"
+}
+{
+"classname" "item_pack"
+"origin" "792 1632 -80"
+}
+{
+"classname" "ammo_rockets"
+"origin" "1120 1568 -112"
+}
+{
+"classname" "ammo_grenades"
+"origin" "944 -936 -240"
+}
+{
+"classname" "ammo_slugs"
+"origin" "800 -344 -112"
+}
+{
+"origin" "-2088 1192 -264"
+"noise" "world/comp_hum2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"model" "*2"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*3"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum2.wav"
+"origin" "1480 -40 -24"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum3.wav"
+"origin" "2800 -448 -400"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"origin" "-2152 864 -264"
+}
+{
+"origin" "-2088 608 -240"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+}
+{
+"origin" "1472 -152 -24"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1128 680 -112"
+"spawnflags" "2048"
+"classname" "target_splash"
+}
+{
+"origin" "1184 728 -112"
+"targetname" "t326"
+"classname" "target_splash"
+"angle" "135"
+}
+{
+"origin" "1072 712 -112"
+"random" "1.5"
+"wait" "6"
+"spawnflags" "2049"
+"target" "t326"
+"classname" "func_timer"
+}
+{
+"origin" "1192 680 -88"
+"targetname" "t326"
+"angle" "0"
+"classname" "target_splash"
+}
+{
+"model" "*4"
+"targetname" "t106"
+"classname" "func_explosive"
+}
+{
+"model" "*5"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*6"
+"classname" "func_wall"
+"spawnflags" "7"
+"targetname" "t263"
+}
+{
+"origin" "16 -960 -264"
+"angles" "-15 45 0"
+"classname" "info_player_intermission"
+}
+{
+"classname" "func_timer"
+"target" "t325"
+"spawnflags" "1"
+"wait" "2"
+"random" ".5"
+"targetname" "t123"
+"origin" "3000 -616 -384"
+}
+{
+"classname" "target_splash"
+"sounds" "1"
+"angle" "180"
+"targetname" "t325"
+"origin" "3032 -576 -384"
+}
+{
+"origin" "448 1384 -8"
+"noise" "world/fan1.wav"
+"spawnflags" "2050"
+"classname" "target_speaker"
+"targetname" "t269"
+}
+{
+"model" "*7"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t324"
+}
+{
+"classname" "target_speaker"
+"origin" "1192 -984 -192"
+"spawnflags" "2048"
+"noise" "world/voice7.wav"
+"targetname" "t324"
+"attenuation" "-1"
+}
+{
+"model" "*8"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t323"
+}
+{
+"noise" "world/v_bas6.wav"
+"spawnflags" "2048"
+"origin" "640 -168 -64"
+"classname" "target_speaker"
+"targetname" "t323"
+"attenuation" "-1"
+}
+{
+"model" "*9"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t322"
+}
+{
+"noise" "world/unit1_02.wav"
+"spawnflags" "2048"
+"origin" "-768 880 -64"
+"classname" "target_speaker"
+"targetname" "t322"
+"attenuation" "-1"
+}
+{
+"classname" "target_speaker"
+"targetname" "t321"
+"origin" "136 1928 -256"
+"spawnflags" "2048"
+"noise" "world/unit3_04.wav"
+"attenuation" "-1"
+}
+{
+"model" "*10"
+"classname" "trigger_once"
+"target" "t321"
+"spawnflags" "2048"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "5888"
+"origin" "-340 1244 -492"
+}
+{
+"classname" "weapon_heatbeam"
+"spawnflags" "5888"
+"origin" "-320 1184 -492"
+}
+{
+"model" "*11"
+"classname" "func_water"
+"spawnflags" "5888"
+}
+{
+"origin" "288 1312 -512"
+"killtarget" "fantrig"
+"targetname" "t269"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"model" "*12"
+"message" "Ventilation fan inactive."
+"targetname" "fantrig"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"model" "*13"
+"target" "t319"
+"spawnflags" "5888"
+"classname" "trigger_once"
+}
+{
+"model" "*14"
+"target" "t275"
+"targetname" "t319"
+"spawnflags" "5888"
+"classname" "trigger_once"
+}
+{
+"model" "*15"
+"target" "t319"
+"spawnflags" "5888"
+"classname" "trigger_once"
+}
+{
+"model" "*16"
+"angle" "-1"
+"targetname" "t269"
+"spawnflags" "2"
+"classname" "trigger_push"
+}
+{
+"model" "*17"
+"target" "t318"
+"classname" "func_button"
+"angle" "0"
+}
+{
+"model" "*18"
+"target" "t318"
+"angle" "0"
+"classname" "func_button"
+}
+{
+"model" "*19"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"classname" "weapon_hyperblaster"
+"spawnflags" "5888"
+"origin" "1216 1984 -496"
+}
+{
+"origin" "-1944 1168 -272"
+"classname" "ammo_tesla"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "5888"
+"origin" "-2080 608 -272"
+}
+{
+"classname" "weapon_hyperblaster"
+"spawnflags" "5888"
+"origin" "-1960 608 -272"
+}
+{
+"classname" "item_sphere_hunter"
+"spawnflags" "5888"
+"origin" "-2048 896 -272"
+}
+{
+"classname" "ammo_flechettes"
+"origin" "-96 1184 -48"
+}
+{
+"classname" "item_sphere_vengeance"
+"spawnflags" "5888"
+"origin" "448 1408 -40"
+}
+{
+"classname" "ammo_bullets"
+"spawnflags" "5888"
+"origin" "64 960 144"
+}
+{
+"classname" "weapon_chaingun"
+"spawnflags" "5888"
+"origin" "32 1024 144"
+}
+{
+"classname" "weapon_proxlauncher"
+"spawnflags" "5888"
+"origin" "-784 1624 -96"
+}
+{
+"classname" "weapon_rocketlauncher"
+"spawnflags" "5888"
+"origin" "-360 1184 -304"
+}
+{
+"classname" "item_doppleganger"
+"spawnflags" "5888"
+"origin" "-632 888 -496"
+}
+{
+"classname" "item_sphere_defender"
+"spawnflags" "5888"
+"origin" "472 1496 -304"
+}
+{
+"classname" "ammo_bullets"
+"spawnflags" "5888"
+"origin" "864 1696 -304"
+}
+{
+"classname" "weapon_machinegun"
+"spawnflags" "5888"
+"origin" "800 1760 -304"
+}
+{
+"classname" "item_armor_body"
+"spawnflags" "5888"
+"origin" "576 2128 -480"
+}
+{
+"classname" "weapon_railgun"
+"spawnflags" "5888"
+"origin" "2424 1408 272"
+}
+{
+"classname" "weapon_grenadelauncher"
+"spawnflags" "5888"
+"origin" "1248 544 80"
+}
+{
+"classname" "ammo_shells"
+"spawnflags" "5888"
+"origin" "800 1296 -112"
+}
+{
+"classname" "weapon_supershotgun"
+"spawnflags" "5888"
+"origin" "800 1216 -112"
+}
+{
+"classname" "weapon_rocketlauncher"
+"origin" "232 -96 -112"
+"spawnflags" "5888"
+}
+{
+"classname" "weapon_chaingun"
+"spawnflags" "5888"
+"origin" "1504 32 -48"
+}
+{
+"classname" "weapon_plasmabeam"
+"spawnflags" "5888"
+"origin" "2496 -400 -400"
+}
+{
+"spawnflags" "5888"
+"classname" "item_adrenaline"
+"origin" "2528 1072 64"
+}
+{
+"classname" "item_power_shield"
+"spawnflags" "5888"
+"origin" "2528 1232 64"
+}
+{
+"model" "*20"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "928 1568 -104"
+}
+{
+"angle" "0"
+"classname" "info_player_deathmatch"
+"origin" "832 896 24"
+}
+{
+"origin" "1760 -112 -40"
+"classname" "info_player_deathmatch"
+"angle" "180"
+}
+{
+"angle" "90"
+"classname" "info_player_deathmatch"
+"origin" "2560 -640 -424"
+}
+{
+"angle" "180"
+"classname" "info_player_deathmatch"
+"origin" "1376 -1344 -232"
+}
+{
+"angle" "315"
+"classname" "info_player_deathmatch"
+"origin" "1464 -272 -232"
+}
+{
+"angle" "180"
+"classname" "info_player_deathmatch"
+"origin" "800 -480 -104"
+}
+{
+"angle" "0"
+"classname" "info_player_deathmatch"
+"origin" "-1312 544 -424"
+}
+{
+"angle" "0"
+"classname" "info_player_deathmatch"
+"origin" "-2120 1120 -264"
+}
+{
+"angle" "90"
+"classname" "info_player_deathmatch"
+"origin" "-416 800 -88"
+}
+{
+"origin" "288 -672 -296"
+"classname" "info_player_deathmatch"
+"angle" "270"
+}
+{
+"origin" "1312 864 88"
+"classname" "info_player_deathmatch"
+"angle" "180"
+}
+{
+"origin" "64 1408 -4"
+"classname" "info_player_deathmatch"
+"angle" "270"
+}
+{
+"origin" "480 1712 -296"
+"angle" "90"
+"classname" "info_player_deathmatch"
+}
+{
+"angle" "225"
+"classname" "info_player_deathmatch"
+"origin" "544 1560 -296"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "0"
+"origin" "1824 1440 216"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "352 1760 -432"
+}
+{
+"model" "*21"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"model" "*22"
+"classname" "func_plat2"
+"spawnflags" "5888"
+"lip" "16"
+}
+{
+"model" "*23"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*24"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*25"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*26"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"model" "*27"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"model" "*28"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"model" "*29"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"model" "*30"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*31"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*32"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"model" "*33"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t33"
+"delay" "2"
+"message" "Incinerator access granted."
+"origin" "-304 1264 -144"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"targetname" "t317"
+"dmg" "75"
+"origin" "448 -880 -320"
+}
+{
+"classname" "target_explosion"
+"targetname" "t317"
+"dmg" "75"
+"origin" "528 -856 -320"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t214"
+"target" "t316"
+"origin" "1800 -88 -216"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"target" "btnbrus"
+"targetname" "t316"
+"origin" "1792 -120 0"
+"spawnflags" "2048"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"classname" "ammo_rockets"
+"origin" "1640 1224 88"
+}
+{
+"classname" "ammo_cells"
+"origin" "2272 1768 80"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"classname" "ammo_cells"
+"origin" "1568 1088 88"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"classname" "ammo_slugs"
+"origin" "1888 992 80"
+}
+{
+"classname" "ammo_rockets"
+"origin" "2280 536 80"
+}
+{
+"classname" "ammo_rockets"
+"origin" "-424 1448 -304"
+}
+{
+"classname" "item_health_large"
+"origin" "-312 984 -304"
+}
+{
+"origin" "1416 -400 -224"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "monster_infantry"
+"spawnflags" "768"
+"target" "t119"
+"origin" "1504 -24 -40"
+}
+{
+"angle" "90"
+"classname" "info_player_start"
+"targetname" "sew2start"
+"origin" "960 -1920 -232"
+}
+{
+"classname" "trigger_relay"
+"origin" "3024 -520 -384"
+"killtarget" "btnbrus"
+"spawnflags" "2048"
+}
+{
+"model" "*34"
+"wait" "5"
+"message" "Access denied!"
+"classname" "trigger_multiple"
+"spawnflags" "2060"
+}
+{
+"origin" "240 1064 -296"
+"delay" "5"
+"target" "alarm"
+"targetname" "t269"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"origin" "272 1120 -296"
+"targetname" "t269"
+"target" "alarm"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"origin" "-40 -432 -176"
+"spawnflags" "769"
+"angle" "315"
+"classname" "monster_parasite"
+}
+{
+"spawnflags" "1"
+"origin" "216 -680 -296"
+"classname" "monster_infantry"
+"angle" "0"
+}
+{
+"origin" "472 -616 -288"
+"spawnflags" "769"
+"angle" "0"
+"classname" "monster_infantry"
+}
+{
+"origin" "2504 -64 -232"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_medic"
+}
+{
+"targetname" "t275"
+"origin" "1952 928 128"
+"classname" "monster_soldier"
+"angle" "135"
+"spawnflags" "3"
+}
+{
+"targetname" "t275"
+"origin" "1944 1312 128"
+"spawnflags" "3"
+"angle" "225"
+"classname" "monster_soldier"
+}
+{
+"classname" "ammo_bullets"
+"origin" "352 1120 -48"
+}
+{
+"classname" "ammo_cells"
+"origin" "536 1576 -48"
+}
+{
+"classname" "ammo_nails"
+"origin" "-288 864 144"
+}
+{
+"classname" "item_health_small"
+"origin" "0 1128 144"
+}
+{
+"classname" "item_health_small"
+"origin" "-48 1128 144"
+}
+{
+"classname" "item_health_small"
+"origin" "-96 1128 144"
+}
+{
+"classname" "item_health_small"
+"origin" "48 1128 144"
+}
+{
+"classname" "ammo_shells"
+"origin" "-296 1376 144"
+}
+{
+"classname" "ammo_prox"
+"origin" "-216 1576 -48"
+}
+{
+"classname" "ammo_tesla"
+"origin" "-168 1704 80"
+}
+{
+"classname" "item_health"
+"origin" "336 1832 80"
+}
+{
+"classname" "item_health_large"
+"origin" "288 1832 80"
+}
+{
+"classname" "monster_stalker"
+"spawnflags" "777"
+"angle" "90"
+"origin" "36 996 226"
+}
+{
+"classname" "monster_soldier"
+"spawnflags" "1537"
+"angle" "90"
+"origin" "192 880 -40"
+}
+{
+"classname" "monster_gunner"
+"angle" "90"
+"spawnflags" "257"
+"origin" "192 896 -40"
+}
+{
+"classname" "monster_turret"
+"angle" "180"
+"spawnflags" "800"
+"origin" "376 1024 192"
+}
+{
+"classname" "monster_turret"
+"angle" "270"
+"spawnflags" "800"
+"origin" "-608 1656 32"
+}
+{
+"angle" "225"
+"classname" "monster_soldier"
+"spawnflags" "1"
+"origin" "152 1640 88"
+}
+{
+"classname" "monster_soldier"
+"angle" "270"
+"spawnflags" "769"
+"origin" "0 1824 88"
+}
+{
+"spawnflags" "2049"
+"noise" "world/comp_hum3.wav"
+"classname" "target_speaker"
+"origin" "-256 856 176"
+}
+{
+"spawnflags" "1"
+"noise" "world/comp_hum1.wav"
+"classname" "target_speaker"
+"origin" "-264 992 176"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum3.wav"
+"spawnflags" "1"
+"origin" "-168 768 176"
+}
+{
+"spawnflags" "1"
+"noise" "world/comp_hum2.wav"
+"classname" "target_speaker"
+"origin" "128 1808 104"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum1.wav"
+"spawnflags" "1"
+"origin" "528 1808 104"
+}
+{
+"origin" "532 1812 104"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "40 1832 120"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "216 1840 120"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "128 1816 120"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "600 1304 -464"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"classname" "monster_parasite"
+"angle" "180"
+"spawnflags" "1"
+"origin" "544 1536 -40"
+}
+{
+"classname" "item_armor_body"
+"origin" "544 1648 80"
+}
+{
+"classname" "path_corner"
+"target" "t314"
+"targetname" "t315"
+"origin" "-296 1696 144"
+"spawnflags" "2048"
+}
+{
+"classname" "path_corner"
+"targetname" "t314"
+"target" "t315"
+"origin" "-216 912 144"
+"spawnflags" "2048"
+}
+{
+"classname" "monster_gladiator"
+"angle" "270"
+"target" "t315"
+"spawnflags" "1"
+"origin" "-256 1664 152"
+"item" "ammo_slugs"
+}
+{
+"classname" "monster_parasite"
+"angle" "270"
+"spawnflags" "1"
+"origin" "-288 1568 -40"
+}
+{
+"classname" "monster_stalker"
+"spawnflags" "9"
+"angle" "90"
+"origin" "-276 1196 34"
+}
+{
+"classname" "ammo_cells"
+"origin" "-288 800 -496"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2049"
+"origin" "600 1176 -512"
+}
+{
+"classname" "weapon_heatbeam"
+"origin" "600 1128 -496"
+"spawnflags" "2048"
+}
+{
+"classname" "ammo_grenades"
+"origin" "-416 1576 -496"
+}
+{
+"classname" "item_pack"
+"origin" "-600 1584 -496"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+"origin" "632 1120 -464"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+"origin" "600 1064 -464"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "532 1812 148"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "608 1184 -432"
+}
+{
+"classname" "item_health"
+"origin" "352 1576 -304"
+}
+{
+"classname" "item_health"
+"origin" "304 1576 -304"
+}
+{
+"classname" "point_combat"
+"targetname" "t313"
+"spawnflags" "2049"
+"origin" "224 856 -504"
+}
+{
+"classname" "monster_gladiator"
+"target" "t313"
+"angle" "90"
+"spawnflags" "1"
+"origin" "256 816 -488"
+"item" "ammo_slugs"
+}
+{
+"model" "*35"
+"classname" "func_wall"
+"targetname" "t39"
+"spawnflags" "3"
+}
+{
+"spawnflags" "257"
+"angle" "0"
+"classname" "monster_flyer"
+"origin" "-296 928 -416"
+}
+{
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_flyer"
+"origin" "-224 928 -296"
+}
+{
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_flyer"
+"origin" "-224 1440 -296"
+}
+{
+"classname" "monster_flyer"
+"angle" "0"
+"spawnflags" "769"
+"origin" "-296 1440 -416"
+}
+{
+"classname" "monster_soldier"
+"angle" "90"
+"origin" "352 1688 -488"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "1"
+"origin" "1248 1968 -488"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "576 2080 -448"
+}
+{
+"classname" "monster_gunner"
+"angle" "270"
+"spawnflags" "1"
+"origin" "288 1440 -296"
+}
+{
+"classname" "hint_path"
+"targetname" "t308"
+"target" "t309"
+"origin" "784 1936 -480"
+"spawnflags" "2048"
+}
+{
+"classname" "hint_path"
+"targetname" "t309"
+"target" "t310"
+"origin" "368 1936 -480"
+"spawnflags" "2048"
+}
+{
+"classname" "hint_path"
+"targetname" "t310"
+"target" "t311"
+"origin" "352 2144 -480"
+"spawnflags" "2048"
+}
+{
+"classname" "hint_path"
+"targetname" "t311"
+"target" "t312"
+"origin" "344 2136 -280"
+"spawnflags" "2048"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"targetname" "t312"
+"origin" "328 1928 -280"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t308"
+"origin" "1224 1960 -480"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t302"
+"target" "t303"
+"origin" "-608 1688 -280"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t303"
+"target" "t304"
+"origin" "-368 1944 -280"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t304"
+"target" "t305"
+"origin" "576 1952 -280"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t305"
+"target" "t306"
+"origin" "1088 1952 -280"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t306"
+"origin" "1264 1768 -280"
+"target" "t307"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "1256 1760 104"
+"targetname" "t307"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t302"
+"origin" "-608 1480 -280"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t300"
+"target" "t301"
+"origin" "1856 1496 224"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t299"
+"target" "t300"
+"origin" "1856 1728 224"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t298"
+"target" "t299"
+"origin" "2200 1744 104"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"targetname" "t301"
+"origin" "2312 1496 288"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t296"
+"origin" "2216 1112 104"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t293"
+"target" "t294"
+"origin" "1864 832 224"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t294"
+"target" "t295"
+"origin" "1864 600 224"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t295"
+"target" "t296"
+"origin" "2208 584 104"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t298"
+"origin" "2232 1192 104"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"target" "t293"
+"origin" "2320 832 288"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"targetname" "t292"
+"origin" "1216 1152 104"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t292"
+"origin" "2264 1160 104"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t288"
+"target" "t289"
+"origin" "1256 544 104"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t289"
+"target" "t290"
+"origin" "1248 1152 104"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t290"
+"target" "t291"
+"origin" "1240 1760 104"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t291"
+"origin" "856 1760 104"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t288"
+"origin" "792 536 104"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2050"
+"noise" "world/fan1.wav"
+"targetname" "t269"
+"origin" "448 1432 -8"
+}
+{
+"classname" "trigger_relay"
+"killtarget" "t283"
+"targetname" "t275"
+"origin" "1376 704 72"
+"spawnflags" "2048"
+}
+{
+"classname" "item_health"
+"origin" "-2136 728 -272"
+}
+{
+"classname" "item_health"
+"origin" "-2136 768 -272"
+}
+{
+"classname" "ammo_tesla"
+"origin" "-808 1184 -304"
+"spawnflags" "5888"
+}
+{
+"origin" "-1768 472 -432"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1816 472 -432"
+"classname" "item_armor_shard"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-1720 472 -432"
+}
+{
+"classname" "weapon_supershotgun"
+"origin" "-2032 1096 -432"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2056"
+"origin" "-2080 1056 -448"
+}
+{
+"classname" "ammo_shells"
+"origin" "-2016 544 -272"
+}
+{
+"classname" "ammo_nails"
+"origin" "-1248 608 -432"
+}
+{
+"classname" "ammo_bullets"
+"origin" "-1248 1184 -432"
+}
+{
+"classname" "item_health_large"
+"origin" "-1632 1280 -432"
+}
+{
+"classname" "item_health_large"
+"origin" "-1584 1280 -432"
+}
+{
+"classname" "item_health_small"
+"origin" "-1128 936 -560"
+}
+{
+"classname" "item_health_small"
+"origin" "-1088 936 -560"
+}
+{
+"classname" "item_health_small"
+"origin" "-1048 936 -560"
+}
+{
+"classname" "item_health_small"
+"origin" "-1176 936 -544"
+}
+{
+"model" "*36"
+"message" "Lift activated."
+"classname" "func_button"
+"angle" "90"
+"target" "t255"
+}
+{
+"classname" "ammo_rockets"
+"origin" "216 -144 -112"
+}
+{
+"classname" "item_armor_shard"
+"origin" "248 -48 -112"
+}
+{
+"classname" "item_armor_shard"
+"origin" "208 -48 -112"
+}
+{
+"classname" "item_armor_shard"
+"origin" "288 -48 -112"
+}
+{
+"model" "*37"
+"classname" "trigger_once"
+"target" "t287"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "416 -96 16"
+}
+{
+"model" "*38"
+"classname" "func_door"
+"angle" "-1"
+"targetname" "t287"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"origin" "-352 1264 -288"
+"targetname" "t286"
+"classname" "target_goal"
+"spawnflags" "2048"
+}
+{
+"message" "Destroy containment glass\nto activate ventilation."
+"origin" "-352 1296 -272"
+"targetname" "t286"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"model" "*39"
+"target" "t286"
+"targetname" "t33"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"volume" ".5"
+"origin" "-192 1176 -496"
+"targetname" "t39"
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+}
+{
+"volume" ".5"
+"origin" "-64 1184 -496"
+"targetname" "t39"
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+}
+{
+"volume" ".5"
+"origin" "64 1184 -496"
+"targetname" "t39"
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+}
+{
+"volume" ".5"
+"origin" "192 1184 -496"
+"targetname" "t39"
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+}
+{
+"volume" ".5"
+"origin" "336 1184 -496"
+"targetname" "t39"
+"classname" "target_speaker"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+}
+{
+"volume" ".5"
+"origin" "-320 1184 -496"
+"targetname" "t39"
+"noise" "world/amb10.wav"
+"classname" "target_speaker"
+"spawnflags" "2050"
+}
+{
+"model" "*40"
+"targetname" "speak"
+"dmg" "10000"
+"spawnflags" "2059"
+"classname" "trigger_hurt"
+}
+{
+"targetname" "t282"
+"target" "speak"
+"origin" "1384 864 72"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"target" "speak"
+"targetname" "goal"
+"classname" "trigger_relay"
+"delay" "2"
+"origin" "1384 920 72"
+"spawnflags" "2048"
+}
+{
+"targetname" "speak"
+"spawnflags" "2050"
+"classname" "target_speaker"
+"origin" "1280 1152 136"
+"noise" "world/amb10.wav"
+}
+{
+"targetname" "speak"
+"spawnflags" "2050"
+"classname" "target_speaker"
+"origin" "1248 1280 136"
+"noise" "world/amb10.wav"
+}
+{
+"targetname" "speak"
+"spawnflags" "2050"
+"classname" "target_speaker"
+"origin" "1248 1472 136"
+"noise" "world/amb10.wav"
+}
+{
+"targetname" "speak"
+"spawnflags" "2050"
+"classname" "target_speaker"
+"origin" "1248 1600 136"
+"noise" "world/amb10.wav"
+}
+{
+"targetname" "speak"
+"spawnflags" "2050"
+"noise" "world/amb10.wav"
+"origin" "1408 1152 136"
+"classname" "target_speaker"
+}
+{
+"classname" "light"
+"light" "60"
+"origin" "1304 488 208"
+}
+{
+"classname" "light"
+"light" "60"
+"origin" "1304 488 176"
+}
+{
+"classname" "light"
+"light" "60"
+"origin" "1304 488 144"
+}
+{
+"classname" "light"
+"light" "60"
+"origin" "1248 472 96"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1184 488 120"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "1304 488 240"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "1376 600 80"
+"target" "t285"
+"targetname" "t284"
+"delay" "2"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"origin" "1336 1000 80"
+"target" "t284"
+"targetname" "goal"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+"message" "Maintenance Hangar doors sealed."
+}
+{
+"origin" "1384 744 72"
+"target" "t283"
+"delay" "2"
+"targetname" "t282"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"model" "*41"
+"message" "Waste conveyor activated."
+"target" "t282"
+"wait" "-1"
+"angle" "270"
+"classname" "func_button"
+}
+{
+"origin" "1152 1056 72"
+"target" "t280"
+"targetname" "t279"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "1152 1600 72"
+"target" "t279"
+"targetname" "t278"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "1152 1664 64"
+"targetname" "t281"
+"target" "t278"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"pathtarget" "goal"
+"origin" "1248 1056 72"
+"targetname" "t280"
+"wait" "-1"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1296 496 104"
+"light" "100"
+"classname" "light"
+}
+{
+"speed" "2"
+"target" "veylit"
+"targetname" "t282"
+"origin" "1376 800 72"
+"classname" "target_lightramp"
+"message" "az"
+"spawnflags" "2048"
+}
+{
+"speed" "2"
+"targetname" "t284"
+"target" "veylit"
+"origin" "1376 1696 72"
+"message" "za"
+"classname" "target_lightramp"
+"spawnflags" "2048"
+}
+{
+"model" "*42"
+"targetname" "t283"
+"target" "t281"
+"classname" "func_train"
+"spawnflags" "2048"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1376 1216 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"origin" "1376 1088 80"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"light" "125"
+}
+{
+"style" "32"
+"targetname" "veylit"
+"spawnflags" "2048"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1440 1088 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1312 1088 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1248 1088 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1184 1120 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1184 1184 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1184 1248 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1184 1312 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1312 1312 80"
+}
+{
+"style" "32"
+"spawnflags" "1"
+"targetname" "veylit"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1312 1504 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "1184 1504 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+"origin" "1312 1568 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"light" "125"
+"origin" "1184 1568 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+"origin" "1312 1632 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"light" "125"
+"origin" "1184 1632 80"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"origin" "1312 1376 80"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"origin" "1184 1376 80"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"origin" "1312 1440 80"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"origin" "1184 1440 80"
+"light" "125"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"style" "32"
+"targetname" "veylit"
+"spawnflags" "2049"
+"origin" "1440 1216 80"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"light" "125"
+}
+{
+"style" "32"
+"spawnflags" "2048"
+"targetname" "veylit"
+"origin" "1312 1248 80"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+"classname" "light"
+}
+{
+"targetname" "t285"
+"classname" "target_help"
+"spawnflags" "2048"
+"origin" "1288 1064 96"
+"message" "Locate and activate\nincinerator."
+}
+{
+"targetname" "t285"
+"message" "Find secondary route\n into Maintenance Hangars."
+"origin" "1224 1040 96"
+"spawnflags" "2049"
+"classname" "target_help"
+}
+{
+"target" "t277"
+"targetname" "t263"
+"classname" "trigger_relay"
+"origin" "-2032 1056 -280"
+"delay" "3"
+"spawnflags" "2048"
+}
+{
+"message" "Activate incinerator and\n break containment glass."
+"origin" "-2000 944 -264"
+"targetname" "t277"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"origin" "1160 1312 168"
+"message" "Use Cargo lift to re-enter\nMaintenance Hangars."
+"targetname" "t275"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"origin" "2272 1000 88"
+"targetname" "rhangar2b"
+"classname" "info_player_coop"
+"angle" "180"
+}
+{
+"origin" "2356 1076 96"
+"targetname" "rhangar2b"
+"classname" "info_player_coop"
+"angle" "180"
+}
+{
+"origin" "2360 1216 96"
+"targetname" "rhangar2b"
+"classname" "info_player_coop"
+"angle" "180"
+}
+{
+"origin" "2236 1208 96"
+"targetname" "rhangar2b"
+"angle" "180"
+"classname" "info_player_coop"
+}
+{
+"origin" "2360 1152 92"
+"targetname" "rhangar2b"
+"angle" "180"
+"classname" "info_player_start"
+}
+{
+"origin" "1216 1328 152"
+"targetname" "t275"
+"killtarget" "doorun"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+"message" "Maintenance Hangar access denied!"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"origin" "1824 864 208"
+"classname" "ammo_slugs"
+}
+{
+"origin" "2112 1504 352"
+"classname" "light"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+}
+{
+"origin" "2400 1472 352"
+"classname" "light"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+}
+{
+"origin" "1240 1760 184"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "2464 1432 288"
+"targetname" "t151"
+"classname" "target_speaker"
+"noise" "world/lasoff1.wav"
+"spawnflags" "2048"
+}
+{
+"origin" "2464 1392 288"
+"noise" "world/lasoff1.wav"
+"targetname" "t151"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"origin" "2380 1152 176"
+"targetname" "t151"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/l_hum2.wav"
+}
+{
+"origin" "2384 1152 104"
+"targetname" "t151"
+"spawnflags" "2049"
+"noise" "world/l_hum2.wav"
+"classname" "target_speaker"
+}
+{
+"style" "33"
+"targetname" "t151"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"light" "100"
+"origin" "2416 1152 144"
+"spawnflags" "2048"
+}
+{
+"style" "33"
+"targetname" "t151"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"light" "100"
+"origin" "2416 1152 112"
+"spawnflags" "2048"
+}
+{
+"style" "33"
+"targetname" "t151"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"light" "100"
+"origin" "2416 1152 80"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "2600 1152 104"
+}
+{
+"origin" "2600 1152 232"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "2600 1152 360"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "2600 1152 488"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "2600 1152 616"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "2600 1152 744"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "2600 1152 872"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "2600 1152 1000"
+"classname" "light"
+"light" "150"
+}
+{
+"style" "33"
+"targetname" "t151"
+"_color" "1.000000 1.000000 0.000000"
+"origin" "2416 1152 176"
+"light" "100"
+"classname" "light"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"origin" "2320 1152 352"
+}
+{
+"model" "*43"
+"message" "Hangar lift door opened."
+"sounds" "4"
+"target" "t276"
+"wait" "-1"
+"angle" "0"
+"classname" "func_button"
+"spawnflags" "2048"
+}
+{
+"model" "*44"
+"origin" "2400 1152 240"
+"wait" "-1"
+"sounds" "4"
+"targetname" "t276"
+"distance" "140"
+"spawnflags" "2176"
+"classname" "func_door_rotating"
+}
+{
+"origin" "2416 1248 112"
+"targetname" "t151"
+"classname" "target_laser"
+"angle" "270"
+"spawnflags" "2065"
+}
+{
+"origin" "2416 1248 80"
+"targetname" "t151"
+"spawnflags" "2065"
+"angle" "270"
+"classname" "target_laser"
+}
+{
+"origin" "2416 1248 144"
+"targetname" "t151"
+"classname" "target_laser"
+"angle" "270"
+"spawnflags" "2065"
+}
+{
+"origin" "2416 1248 176"
+"targetname" "t151"
+"spawnflags" "2065"
+"angle" "270"
+"classname" "target_laser"
+}
+{
+"model" "*45"
+"sounds" "4"
+"targetname" "t275"
+"wait" "-1"
+"lip" "96"
+"angle" "270"
+"classname" "func_door"
+"spawnflags" "2048"
+"message" "Maintenance Hangar access denied!"
+"team" "hangdor1"
+}
+{
+"model" "*46"
+"sounds" "4"
+"targetname" "t275"
+"wait" "-1"
+"lip" "96"
+"angle" "90"
+"classname" "func_door"
+"spawnflags" "2048"
+"team" "hangdor1"
+"message" "Maintenance Hangar access denied!"
+}
+{
+"model" "*47"
+"spawnflags" "3"
+"targetname" "t275"
+"classname" "func_wall"
+}
+{
+"origin" "1376 1568 152"
+"target" "t275"
+"spawnflags" "2049"
+"classname" "target_crosslevel_target"
+}
+{
+"model" "*48"
+"targetname" "t275"
+"spawnflags" "2055"
+"classname" "func_wall"
+}
+{
+"origin" "1440 1152 192"
+"classname" "light"
+"light" "150"
+}
+{
+"model" "*49"
+"_minlight" ".2"
+"lip" "168"
+"spawnflags" "2048"
+"classname" "func_plat2"
+"sounds" "4"
+"targetname" "t320"
+}
+{
+"model" "*50"
+"target" "t274"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+}
+{
+"map" "rhangar2$rsewer2b"
+"targetname" "t274"
+"origin" "2576 1160 632"
+"classname" "target_changelevel"
+"spawnflags" "2048"
+}
+{
+"model" "*51"
+"angle" "270"
+"classname" "func_button"
+"spawnflags" "2048"
+"target" "t320"
+"message" "Hangar lift activated."
+}
+{
+"origin" "-32 1416 -40"
+"targetname" "rhangar2a"
+"classname" "info_player_coop"
+"angle" "180"
+"spawnflags" "2048"
+}
+{
+"origin" "192 1392 -40"
+"targetname" "rhangar2a"
+"classname" "info_player_coop"
+"angle" "180"
+"spawnflags" "2048"
+}
+{
+"origin" "160 1504 -40"
+"targetname" "rhangar2a"
+"classname" "info_player_coop"
+"angle" "180"
+"spawnflags" "2048"
+}
+{
+"origin" "56 1320 -40"
+"angle" "180"
+"targetname" "rhangar2a"
+"classname" "info_player_coop"
+"spawnflags" "2048"
+}
+{
+"origin" "64 1408 248"
+"angle" "180"
+"targetname" "rhangar2a"
+"classname" "info_player_start"
+}
+{
+"map" "rhangar2$rsewer2a"
+"origin" "456 1400 620"
+"targetname" "t273"
+"classname" "target_changelevel"
+"spawnflags" "2048"
+}
+{
+"model" "*52"
+"targetname" "t269"
+"target" "t273"
+"classname" "trigger_multiple"
+"spawnflags" "2056"
+}
+{
+"origin" "424 1496 -48"
+"targetname" "t269"
+"target" "t272"
+"classname" "func_timer"
+"spawnflags" "2048"
+}
+{
+"targetname" "t272"
+"sounds" "208"
+"wait" "1"
+"count" "96"
+"speed" "150"
+"classname" "target_steam"
+"angle" "-1"
+"origin" "448 1408 -56"
+"spawnflags" "2048"
+}
+{
+"volume" ".5"
+"spawnflags" "2050"
+"origin" "-88 912 -440"
+"targetname" "alarm"
+"classname" "target_speaker"
+"noise" "world/klaxon1.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "2050"
+"origin" "-72 1192 -440"
+"targetname" "alarm"
+"classname" "target_speaker"
+"noise" "world/klaxon1.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "2050"
+"origin" "-64 1424 -440"
+"targetname" "alarm"
+"classname" "target_speaker"
+"noise" "world/klaxon1.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "2050"
+"origin" "280 928 -440"
+"targetname" "alarm"
+"classname" "target_speaker"
+"noise" "world/klaxon1.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "2050"
+"origin" "296 1192 -440"
+"targetname" "alarm"
+"classname" "target_speaker"
+"noise" "world/klaxon1.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "2050"
+"origin" "352 1536 -440"
+"targetname" "alarm"
+"classname" "target_speaker"
+"noise" "world/klaxon1.wav"
+}
+{
+"volume" ".5"
+"spawnflags" "2050"
+"origin" "-336 1192 -272"
+"targetname" "alarm"
+"noise" "world/klaxon1.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-80 1400 -392"
+"targetname" "t254"
+"target" "t270"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"origin" "272 968 -392"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"origin" "272 968 -392"
+"target" "t270"
+"targetname" "t253"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"message" "Use ventilation fan shaft\n to access Hangar."
+"origin" "160 1208 -280"
+"targetname" "t269"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"origin" "216 1248 -296"
+"targetname" "t269"
+"classname" "target_goal"
+"spawnflags" "2048"
+}
+{
+"model" "*53"
+"killtarget" "fantrig"
+"targetname" "t270"
+"target" "t269"
+"count" "2"
+"classname" "trigger_counter"
+"spawnflags" "2048"
+}
+{
+"model" "*54"
+"height" "376"
+"targetname" "t256"
+"spawnflags" "2"
+"classname" "func_plat2"
+"dmg" "100"
+"speed" "100"
+}
+{
+"model" "*55"
+"target" "t256"
+"classname" "func_button"
+"angle" "90"
+"message" "Cargo lift activated."
+"spawnflags" "0"
+}
+{
+"origin" "-764 1036 -308"
+"targetname" "t263"
+"noise" "world/force3.wav"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"origin" "356 1676 -456"
+"targetname" "t263"
+"noise" "world/force3.wav"
+"classname" "target_speaker"
+"spawnflags" "2049"
+}
+{
+"model" "*56"
+"targetname" "t263"
+"spawnflags" "2054"
+"classname" "func_wall"
+}
+{
+"origin" "-2136 984 -232"
+"noise" "world/fuseout.wav"
+"targetname" "t263"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"origin" "-2144 768 -232"
+"noise" "world/fuseout.wav"
+"targetname" "t263"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"origin" "-2144 840 -240"
+"targetname" "t263"
+"target" "t267"
+"message" "az"
+"speed" "2"
+"classname" "target_lightramp"
+"spawnflags" "2048"
+}
+{
+"model" "*57"
+"spawnflags" "1536"
+"classname" "func_wall"
+}
+{
+"model" "*58"
+"spawnflags" "1024"
+"classname" "func_wall"
+}
+{
+"origin" "-2192 984 -232"
+"light" "100"
+"classname" "light"
+"spawnflags" "2048"
+}
+{
+"delay" "3"
+"target" "t265"
+"origin" "-2056 1088 -280"
+"targetname" "t264"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"model" "*59"
+"origin" "-2180 967 -232"
+"speed" "40"
+"_minlight" ".1"
+"distance" "170"
+"wait" "-1"
+"targetname" "t264"
+"spawnflags" "2050"
+"classname" "func_door_rotating"
+}
+{
+"origin" "-2240 972 -288"
+"targetname" "t262"
+"target" "t261"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "-2208 972 -288"
+"wait" "-1"
+"targetname" "t261"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"model" "*60"
+"speed" "5"
+"_minlight" ".2"
+"targetname" "t265"
+"team" "train"
+"target" "t262"
+"classname" "func_train"
+"spawnflags" "2048"
+}
+{
+"model" "*61"
+"target" "t263"
+"team" "train"
+"health" "1"
+"classname" "func_explosive"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2049"
+"targetname" "t263"
+"team" "train"
+"origin" "-2192 976 -232"
+"classname" "target_speaker"
+"noise" "world/fan1.wav"
+}
+{
+"targetname" "t263"
+"spawnflags" "2049"
+"team" "train"
+"origin" "-2192 992 -232"
+"noise" "world/fan1.wav"
+"classname" "target_speaker"
+}
+{
+"origin" "-1120 1024 -192"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-2208 768 -192"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-2208 1024 -192"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "-1120 768 -192"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-1664 896 -396"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-1472 896 -432"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-1280 896 -456"
+"classname" "light"
+"light" "125"
+}
+{
+"targetname" "t51"
+"angle" "0"
+"spawnflags" "1"
+"origin" "-2136 896 -264"
+"classname" "monster_medic_commander"
+}
+{
+"targetname" "t260"
+"classname" "target_speaker"
+"noise" "world/brkglas.wav"
+"origin" "1616 -232 -16"
+"spawnflags" "2048"
+}
+{
+"model" "*62"
+"target" "t260"
+"classname" "func_explosive"
+"mass" "300"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-752 1432 80"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-680 1504 80"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-752 1576 80"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "-752 1504 -168"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-680 1504 -272"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-752 1432 -272"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-824 1504 -272"
+}
+{
+"style" "34"
+"targetname" "t267"
+"_color" "0.257282 1.000000 0.237864"
+"origin" "-2168 864 -200"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*63"
+"targetname" "t56"
+"mass" "200"
+"classname" "func_explosive"
+}
+{
+"model" "*64"
+"message" "Power conduit accessible."
+"wait" "-1"
+"target" "t264"
+"angle" "180"
+"classname" "func_button"
+"spawnflags" "2048"
+}
+{
+"model" "*65"
+"target" "t255"
+"angle" "90"
+"classname" "func_button"
+"message" "Lift activated."
+}
+{
+"model" "*66"
+"message" "Cargo lift activated."
+"target" "t257"
+"angle" "180"
+"classname" "func_button"
+}
+{
+"model" "*67"
+"message" "Cargo lift activated."
+"target" "t256"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"origin" "-960 896 -104"
+"spawnflags" "257"
+"angle" "0"
+"classname" "monster_parasite"
+}
+{
+"classname" "ammo_nails"
+"origin" "-408 1640 -96"
+}
+{
+"origin" "-464 1640 -96"
+"classname" "ammo_nails"
+}
+{
+"origin" "-560 888 -112"
+"classname" "weapon_nailgun"
+}
+{
+"origin" "-472 856 -112"
+"classname" "misc_deadsoldier"
+"spawnflags" "2048"
+}
+{
+"origin" "-536 1280 -256"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-536 1328 -280"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-536 1376 -304"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-536 1232 -224"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-312 1024 -304"
+"classname" "item_health_large"
+}
+{
+"origin" "-608 1672 -144"
+"spawnflags" "32"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"origin" "-384 1392 -296"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"origin" "-64 1480 -336"
+"targetname" "t254"
+"noise" "world/brkglas.wav"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"origin" "-64 880 -336"
+"targetname" "t253"
+"noise" "world/brkglas.wav"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"origin" "-544 936 -304"
+"classname" "ammo_slugs"
+}
+{
+"origin" "-808 1384 -304"
+"classname" "item_health"
+}
+{
+"origin" "-808 1336 -304"
+"classname" "item_health"
+}
+{
+"origin" "-640 888 -104"
+"spawnflags" "1537"
+"angle" "90"
+"classname" "monster_soldier_light"
+}
+{
+"origin" "-456 1608 -104"
+"target" "t252"
+"targetname" "t251"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "-432 880 -104"
+"targetname" "t252"
+"target" "t251"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "-448 1520 -88"
+"angle" "225"
+"spawnflags" "1"
+"target" "t251"
+"classname" "monster_medic"
+}
+{
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "32"
+"origin" "-392 1184 -136"
+}
+{
+"origin" "-560 896 -296"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_parasite"
+}
+{
+"origin" "-644 892 -110"
+"spawnflags" "257"
+"angle" "90"
+"classname" "monster_stalker"
+}
+{
+"spawnflags" "1"
+"angle" "0"
+"origin" "-792 1624 -88"
+"classname" "monster_gunner"
+}
+{
+"origin" "-576 1624 -304"
+"classname" "ammo_prox"
+}
+{
+"origin" "-80 1984 -304"
+"classname" "ammo_tesla"
+}
+{
+"origin" "0 1952 -144"
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "32"
+}
+{
+"origin" "1320 1504 88"
+"classname" "ammo_bullets"
+}
+{
+"origin" "1320 1552 88"
+"classname" "ammo_bullets"
+}
+{
+"origin" "664 672 -112"
+"classname" "ammo_shells"
+}
+{
+"origin" "1120 1760 -304"
+"classname" "item_health"
+}
+{
+"origin" "1104 1824 -304"
+"classname" "item_health"
+}
+{
+"origin" "288 1688 -304"
+"classname" "ammo_rockets"
+}
+{
+"origin" "728 1728 -304"
+"classname" "item_health_small"
+}
+{
+"origin" "728 1776 -304"
+"classname" "item_health_small"
+}
+{
+"origin" "728 1824 -304"
+"classname" "item_health_small"
+}
+{
+"origin" "728 1680 -304"
+"classname" "item_health_small"
+}
+{
+"origin" "1256 2216 -304"
+"classname" "ammo_slugs"
+}
+{
+"origin" "816 1696 -488"
+"angle" "90"
+"classname" "monster_parasite"
+}
+{
+"origin" "576 2136 -480"
+"classname" "item_adrenaline"
+"spawnflags" "2048"
+}
+{
+"origin" "1008 1696 -512"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "992 1752 -496"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "336 1712 -296"
+"spawnflags" "256"
+"targetname" "t250"
+"angle" "90"
+"classname" "monster_medic"
+"item" "ammo_cells"
+}
+{
+"target" "t250"
+"origin" "704 2192 -296"
+"spawnflags" "257"
+"angle" "270"
+"classname" "monster_gladiator"
+}
+{
+"origin" "1212 2180 -150"
+"angle" "270"
+"spawnflags" "777"
+"classname" "monster_stalker"
+}
+{
+"origin" "872 1696 -296"
+"classname" "monster_soldier"
+"angle" "90"
+"spawnflags" "1"
+"item" "ammo_shells"
+}
+{
+"origin" "1200 2160 -296"
+"spawnflags" "1025"
+"angle" "270"
+"classname" "monster_soldier"
+}
+{
+"origin" "736 512 64"
+"angle" "0"
+"classname" "misc_deadsoldier"
+"spawnflags" "2048"
+}
+{
+"origin" "680 480 144"
+"classname" "item_armor_combat"
+}
+{
+"origin" "1128 1176 -112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1128 1056 -112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1128 1096 -112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1128 1136 -112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1048 1832 80"
+"classname" "item_health"
+}
+{
+"origin" "1104 1832 80"
+"classname" "item_health"
+}
+{
+"classname" "misc_explobox"
+"origin" "672 576 64"
+"spawnflags" "2048"
+}
+{
+"classname" "misc_explobox"
+"origin" "744 792 -128"
+}
+{
+"origin" "1048 1736 88"
+"angle" "270"
+"classname" "monster_gunner"
+"spawnflags" "1"
+"item" "ammo_grenades"
+}
+{
+"origin" "816 960 16"
+"classname" "ammo_rockets"
+}
+{
+"origin" "960 112 -112"
+"classname" "ammo_cells"
+}
+{
+"origin" "960 176 -112"
+"classname" "ammo_cells"
+}
+{
+"origin" "472 -136 -112"
+"classname" "item_health_large"
+}
+{
+"targetname" "t219"
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "288"
+"origin" "928 256 48"
+}
+{
+"origin" "768 576 88"
+"spawnflags" "0"
+"targetname" "t31"
+"classname" "monster_medic_commander"
+"angle" "90"
+}
+{
+"target" "t249"
+"targetname" "t122"
+"classname" "trigger_relay"
+"origin" "2632 -176 -408"
+"spawnflags" "2048"
+}
+{
+"target" "t249"
+"origin" "2520 160 -40"
+"classname" "trigger_always"
+"spawnflags" "2048"
+}
+{
+"target" "t248"
+"origin" "2680 -200 -408"
+"targetname" "t122"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"killtarget" "t248"
+"targetname" "t123"
+"origin" "3008 -520 -384"
+"classname" "trigger_relay"
+"target" "t316"
+"spawnflags" "2048"
+}
+{
+"model" "*68"
+"targetname" "btnbrus"
+"spawnflags" "2060"
+"classname" "trigger_multiple"
+"message" "Access denied!"
+"wait" "5"
+}
+{
+"target" "t242"
+"targetname" "t241"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "944 808 -96"
+}
+{
+"target" "t243"
+"targetname" "t242"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "928 -96 -96"
+}
+{
+"target" "t244"
+"targetname" "t243"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "680 -104 -92"
+}
+{
+"targetname" "t244"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "680 -368 -100"
+}
+{
+"target" "t241"
+"origin" "944 1344 -96"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t238"
+"targetname" "t237"
+"origin" "32 -400 -164"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t239"
+"targetname" "t238"
+"origin" "-48 -480 -172"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t240"
+"targetname" "t239"
+"origin" "0 -952 -316"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"targetname" "t240"
+"origin" "516 -708 -288"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t237"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "688 -392 -92"
+}
+{
+"target" "t234"
+"targetname" "t233"
+"origin" "2816 -168 -408"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t235"
+"targetname" "t234"
+"origin" "2824 -64 -216"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t236"
+"targetname" "t235"
+"origin" "2248 -72 -224"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"targetname" "t236"
+"origin" "2200 -440 -216"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t233"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "2680 -440 -416"
+}
+{
+"target" "t227"
+"targetname" "t226"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1632 40 -32"
+}
+{
+"target" "t228"
+"targetname" "t227"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2280 48 -32"
+}
+{
+"target" "t229"
+"targetname" "t228"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2824 48 -104"
+}
+{
+"target" "t230"
+"targetname" "t229"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2832 -736 -224"
+}
+{
+"target" "t231"
+"targetname" "t230"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2232 -696 -216"
+}
+{
+"target" "t232"
+"targetname" "t231"
+"origin" "2224 -424 -208"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"targetname" "t232"
+"origin" "1632 -400 -224"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t226"
+"origin" "1616 -200 -32"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "952 -400 -208"
+"target" "t225"
+"targetname" "t224"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1632 -376 -224"
+"targetname" "t225"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "928 -792 -216"
+"target" "t224"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "1624 -848 -224"
+"target" "t222"
+"targetname" "t221"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1168 -856 -224"
+"target" "t223"
+"targetname" "t222"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1168 -1160 -216"
+"targetname" "t223"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1616 -416 -208"
+"target" "t221"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "608 -928 -304"
+"classname" "item_armor_shard"
+}
+{
+"origin" "656 -928 -292"
+"classname" "item_armor_shard"
+}
+{
+"origin" "560 -928 -304"
+"classname" "item_armor_shard"
+}
+{
+"origin" "704 -928 -276"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2272 -352 -240"
+"classname" "ammo_tesla"
+}
+{
+"origin" "1904 104 -48"
+"classname" "item_health"
+}
+{
+"origin" "1960 104 -48"
+"classname" "item_health"
+}
+{
+"origin" "992 -1472 -416"
+"targetname" "t220"
+"classname" "target_secret"
+}
+{
+"model" "*69"
+"message" "You've found a secret."
+"target" "t220"
+"classname" "trigger_once"
+}
+{
+"origin" "944 -1496 -408"
+"classname" "item_health_mega"
+}
+{
+"origin" "1432 -824 -248"
+"delay" "1"
+"targetname" "flyer"
+"target" "t106"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"model" "*70"
+"targetname" "t249"
+"spawnflags" "2060"
+"target" "t124"
+"wait" "5"
+"message" "DNA not on file..."
+"classname" "trigger_multiple"
+}
+{
+"origin" "776 1216 160"
+"spawnflags" "288"
+"angle" "0"
+"classname" "monster_turret"
+}
+{
+"origin" "1616 -320 56"
+"spawnflags" "288"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"origin" "216 -344 -152"
+"classname" "item_health"
+}
+{
+"spawnflags" "1536"
+"origin" "272 -344 -136"
+"classname" "item_health"
+}
+{
+"origin" "128 -608 -318"
+"classname" "ammo_shells"
+}
+{
+"origin" "680 -416 -248"
+"classname" "ammo_prox"
+}
+{
+"origin" "248 -832 -312"
+"classname" "weapon_proxlauncher"
+}
+{
+"origin" "288 -864 -324"
+"classname" "misc_deadsoldier"
+"spawnflags" "2048"
+}
+{
+"origin" "784 -368 -296"
+"classname" "item_health_large"
+}
+{
+"origin" "784 -416 -296"
+"classname" "item_health_large"
+}
+{
+"classname" "monster_turret"
+"angle" "270"
+"spawnflags" "816"
+"origin" "944 -616 -40"
+}
+{
+"origin" "1504 104 -48"
+"classname" "ammo_bullets"
+}
+{
+"origin" "1560 104 -48"
+"classname" "ammo_bullets"
+}
+{
+"origin" "2752 88 -112"
+"classname" "weapon_railgun"
+}
+{
+"origin" "2792 56 -128"
+"spawnflags" "2050"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "2856 -136 -112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2856 -88 -112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2856 -40 -112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2928 -536 -432"
+"classname" "item_ir_goggles"
+}
+{
+"origin" "1512 -128 -48"
+"classname" "item_armor_combat"
+}
+{
+"origin" "1552 -104 -64"
+"spawnflags" "2056"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "2408 -608 -432"
+"classname" "ammo_nails"
+}
+{
+"origin" "2400 -192 -432"
+"classname" "ammo_cells"
+"spawnflags" "0"
+}
+{
+"origin" "2208 -24 -240"
+"classname" "item_health_large"
+}
+{
+"origin" "2272 -24 -240"
+"classname" "item_health_large"
+}
+{
+"targetname" "t219"
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "800"
+"origin" "480 -96 48"
+}
+{
+"origin" "320 -328 -40"
+"spawnflags" "264"
+"angle" "270"
+"classname" "monster_turret"
+}
+{
+"origin" "2528 -224 -72"
+"spawnflags" "8"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "288"
+"origin" "2272 -608 -72"
+}
+{
+"model" "*71"
+"classname" "func_wall"
+"spawnflags" "2304"
+}
+{
+"origin" "2424 -776 -240"
+"classname" "item_health_small"
+}
+{
+"origin" "2376 -776 -240"
+"classname" "item_health_small"
+}
+{
+"origin" "2328 -776 -240"
+"classname" "item_health_small"
+}
+{
+"origin" "2472 -776 -240"
+"classname" "item_health_small"
+}
+{
+"origin" "2280 -216 -240"
+"classname" "ammo_rockets"
+}
+{
+"origin" "1792 -280 -240"
+"classname" "item_health"
+}
+{
+"origin" "1536 -824 -240"
+"classname" "ammo_prox"
+}
+{
+"spawnflags" "1"
+"item" "ammo_grenades"
+"origin" "1352 -848 -232"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"origin" "1376 -1120 -240"
+"classname" "ammo_nails"
+}
+{
+"origin" "656 -1216 -240"
+"classname" "weapon_nailgun"
+}
+{
+"origin" "720 -1224 -256"
+"angle" "90"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "536 -1304 -240"
+"classname" "item_armor_shard"
+}
+{
+"origin" "544 -1256 -240"
+"classname" "item_armor_shard"
+}
+{
+"origin" "544 -1352 -240"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1312 -1568 -240"
+"classname" "ammo_slugs"
+}
+{
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "1032"
+"origin" "960 -1216 -16"
+}
+{
+"model" "*72"
+"spawnflags" "2304"
+"classname" "func_wall"
+}
+{
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "8"
+"origin" "2528 -576 -72"
+}
+{
+"origin" "584 -1120 -248"
+"target" "t216"
+"targetname" "t139"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "1336 -1160 -248"
+"targetname" "t216"
+//"target" "t215" // t215 does not exist in the level
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"item" "ammo_cells"
+"origin" "1464 -560 -232"
+"classname" "monster_soldier_light"
+"angle" "0"
+"spawnflags" "1"
+}
+{
+"origin" "680 -1592 -160"
+"classname" "monster_turret"
+"angle" "90"
+"spawnflags" "800"
+}
+{
+"targetname" "t217"
+"origin" "1632 64 56"
+"classname" "monster_turret"
+"angle" "-2"
+"spawnflags" "8"
+}
+{
+"origin" "2272 -168 -72"
+"spawnflags" "288"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"origin" "1240 -1592 -160"
+"spawnflags" "800"
+"angle" "90"
+"classname" "monster_turret"
+}
+{
+"message" "Find cargo lift to\n Maintenance Hangars."
+"origin" "936 -1544 -184"
+"spawnflags" "2048"
+"targetname" "t214"
+"classname" "target_help"
+}
+{
+"spawnflags" "2049"
+"message" "Gain access to Maintenance\n Hangars."
+"origin" "1000 -1528 -200"
+"targetname" "t214"
+"classname" "target_help"
+}
+{
+"model" "*73"
+"target" "t214"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"origin" "984 -1824 -232"
+"targetname" "sew2start"
+"classname" "info_player_coop"
+"angle" "90"
+}
+{
+"origin" "984 -1728 -232"
+"targetname" "sew2start"
+"classname" "info_player_coop"
+"angle" "90"
+}
+{
+"origin" "936 -1864 -232"
+"targetname" "sew2start"
+"classname" "info_player_coop"
+"angle" "90"
+}
+{
+"origin" "936 -1776 -232"
+"angle" "90"
+"targetname" "sew2start"
+"classname" "info_player_coop"
+}
+{
+"target" "t219"
+"classname" "monster_stalker"
+"angle" "180"
+"origin" "868 -92 -110"
+"spawnflags" "1"
+}
+{
+"spawnflags" "3"
+"origin" "252 -100 -110"
+"angle" "0"
+"classname" "monster_stalker"
+"targetname" "t287"
+}
+{
+"origin" "816 1816 96"
+"spawnflags" "1"
+"angle" "315"
+"classname" "monster_parasite"
+}
+{
+"model" "*74"
+"health" "25"
+"mass" "100"
+"dmg" "96"
+"classname" "func_explosive"
+}
+{
+"model" "*75"
+"health" "25"
+"mass" "100"
+"dmg" "96"
+"classname" "func_explosive"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"origin" "1984 1384 80"
+"classname" "item_armor_shard"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"origin" "1936 1384 80"
+"classname" "item_armor_shard"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"origin" "1888 1384 80"
+"classname" "item_armor_shard"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"origin" "2032 1384 80"
+"classname" "item_armor_shard"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"origin" "2272 1376 80"
+"classname" "item_health_large"
+}
+{
+"targetname" "t275"
+"spawnflags" "1"
+"origin" "2272 928 80"
+"classname" "item_health_large"
+}
+{
+"target" "t153"
+"origin" "2368 1344 280"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_gladiator"
+}
+{
+"item" "ammo_cells"
+"origin" "2068 1728 366"
+"angle" "180"
+"spawnflags" "9"
+"classname" "monster_stalker"
+}
+{
+"item" "ammo_cells"
+"origin" "2068 576 366"
+"angle" "180"
+"spawnflags" "9"
+"classname" "monster_stalker"
+}
+{
+"model" "*76"
+"sounds" "4"
+"message" "Yellow lasers disabled."
+"wait" "-1"
+"target" "t151"
+"angle" "0"
+"classname" "func_button"
+"spawnflags" "2048"
+}
+{
+"origin" "2368 824 280"
+"angle" "180"
+"classname" "monster_medic_commander"
+"targetname" "t153"
+}
+{
+"item" "ammo_cells"
+"origin" "944 -540 -150"
+"target" "t150"
+"angle" "90"
+"spawnflags" "9"
+"classname" "monster_stalker"
+}
+{
+"classname" "monster_stalker"
+"origin" "2244 -732 -82"
+"angle" "90"
+"spawnflags" "9"
+}
+{
+"targetname" "t143"
+"origin" "656 -424 -104"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_medic_commander"
+}
+{
+"target" "t149"
+"targetname" "t111"
+"delay" "14"
+"origin" "2568 -248 -416"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"target" "t149"
+"delay" "9"
+"origin" "2536 -248 -416"
+"targetname" "t111"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"model" "*77"
+"wait" "-1"
+"message" "Humanoid Lifeform dectected!"
+"targetname" "t149"
+"target" "t122"
+"spawnflags" "2056"
+"classname" "trigger_multiple"
+}
+{
+"origin" "2216 -72 -144"
+"angle" "270"
+"classname" "monster_daedalus"
+}
+{
+"target" "t217"
+"spawnflags" "9"
+"angle" "270"
+"origin" "2812 60 46"
+"classname" "monster_stalker"
+}
+{
+"origin" "-1600 1296 -424"
+"spawnflags" "2049"
+"noise" "world/steamy.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "t148"
+"classname" "target_speaker"
+"noise" "world/steamy.wav"
+"spawnflags" "2050"
+"origin" "-1600 656 -432"
+}
+{
+"targetname" "t148"
+"classname" "target_speaker"
+"noise" "world/steamy.wav"
+"spawnflags" "2050"
+"origin" "-1600 736 -432"
+}
+{
+"targetname" "t148"
+"origin" "-1608 696 -408"
+"spawnflags" "2050"
+"noise" "world/steamy.wav"
+"classname" "target_speaker"
+}
+{
+"targetname" "t147"
+"classname" "target_speaker"
+"noise" "world/steamy.wav"
+"spawnflags" "2050"
+"origin" "-1600 1264 -424"
+}
+{
+"classname" "target_speaker"
+"noise" "world/steamy.wav"
+"spawnflags" "2049"
+"origin" "-1600 696 -472"
+}
+{
+"targetname" "t147"
+"origin" "-1600 1232 -424"
+"spawnflags" "2050"
+"noise" "world/steamy.wav"
+"classname" "target_speaker"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-960 896 -456"
+}
+{
+"origin" "-960 896 -72"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-960 896 -264"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-352 1408 16"
+"classname" "light"
+"light" "125"
+}
+{
+"model" "*78"
+"targetname" "t255"
+"spawnflags" "0"
+"_minlight" ".3"
+"lip" "16"
+"classname" "func_plat2"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "96 1952 -184"
+}
+{
+"origin" "-256 1184 212"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-256 1440 212"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-64 768 212"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "192 768 212"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-328 1368 -168"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "-168 768 196"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-256 1696 212"
+}
+{
+"origin" "448 1024 84"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-256 928 212"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "448 768 212"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "512 880 100"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "280 896 40"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "136 1056 32"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "224 1592 160"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-256 1216 16"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-256 1536 16"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-512 896 -240"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "472 1592 160"
+"light" "100"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "40 1056 184"
+"light" "100"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "704 1760 -416"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-96 1952 -184"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "632 1216 -464"
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "-608 1184 -88"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "200"
+"origin" "-608 1280 120"
+}
+{
+"origin" "-608 1152 120"
+"light" "200"
+"classname" "light"
+}
+{
+"origin" "-368 1056 -200"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "200"
+"origin" "2048 1152 376"
+}
+{
+"origin" "2176 1152 376"
+"light" "200"
+"classname" "light"
+}
+{
+"origin" "352 1952 -408"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "-328 992 -192"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-344 1440 -272"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"origin" "-328 1368 -272"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"origin" "1760 -104 16"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"origin" "1752 -104 48"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "-368 1312 -200"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-736 896 -240"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-544 1760 -184"
+}
+{
+"origin" "448 1824 -200"
+"light" "180"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "-456 928 56"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-456 1120 56"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-456 1312 56"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "-456 1504 56"
+"light" "125"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "-760 1312 56"
+"light" "125"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "-760 1120 56"
+"light" "125"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.778226 0.028226"
+"origin" "704 2016 -416"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-256 856 196"
+"classname" "light"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"origin" "-416 1888 -184"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-288 1952 -184"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "928 352 16"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "928 160 16"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "928 -32 16"
+"classname" "light"
+"light" "125"
+}
+{
+"style" "35"
+"spawnflags" "2048"
+"light" "75"
+"origin" "312 1672 -440"
+"classname" "light"
+"targetname" "t263"
+}
+{
+"style" "35"
+"light" "75"
+"origin" "392 1672 -440"
+"classname" "light"
+"targetname" "t263"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"light" "75"
+"origin" "432 1680 -488"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"spawnflags" "2048"
+}
+{
+"origin" "1240 1728 -432"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "832 2072 -432"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "832 1824 -432"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "504 2200 -432"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1184 1952 -408"
+}
+{
+"origin" "352 1632 -432"
+"light" "125"
+"classname" "light"
+"spawnflags" "5888"
+}
+{
+"origin" "1088 2080 -184"
+"light" "180"
+"classname" "light"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "960 1760 -416"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-32 1592 160"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "960 1872 -416"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "704 1872 -416"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "960 2016 -416"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-760 928 56"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "704 2128 -416"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "960 2128 -416"
+"_color" "1.000000 0.778226 0.028226"
+}
+{
+"classname" "light"
+"light" "180"
+"origin" "768 2080 -176"
+}
+{
+"classname" "light"
+"light" "180"
+"origin" "448 2080 -176"
+}
+{
+"style" "35"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"origin" "432 1680 -456"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"origin" "432 1680 -424"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "t263"
+"origin" "272 1680 -456"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "t263"
+"origin" "272 1680 -424"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "t263"
+"origin" "432 1680 -392"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"origin" "304 1680 -384"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"origin" "336 1680 -384"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"origin" "368 1680 -384"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"origin" "400 1680 -384"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "t263"
+"origin" "272 1680 -488"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"style" "35"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"targetname" "t263"
+"origin" "272 1680 -392"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"origin" "1088 1824 -200"
+"light" "180"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "180"
+"origin" "768 1824 -200"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "352 1504 -424"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1240 1152 184"
+}
+{
+"origin" "2208 608 200"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "2208 800 136"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "1240 1760 -64"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "360 2144 -440"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "2016 1728 312"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "1824 1248 248"
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"classname" "light"
+}
+{
+"origin" "1728 1152 192"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "1240 1760 -248"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-776 1120 -256"
+}
+{
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"classname" "light"
+"origin" "2400 832 352"
+}
+{
+"light" "100"
+"_color" "1.000000 0.501961 0.000000"
+"classname" "light"
+"origin" "2112 800 352"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.501961 0.000000"
+"light" "100"
+"origin" "1824 1056 248"
+}
+{
+"origin" "2016 576 312"
+"light" "150"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "2208 1696 200"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1568 1152 192"
+}
+{
+"model" "*79"
+"health" "25"
+"classname" "func_explosive"
+"dmg" "96"
+"mass" "100"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"origin" "1280 1152 100"
+}
+{
+"origin" "432 -360 16"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "2816 -192 36"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "2816 -448 36"
+}
+{
+"origin" "2816 -704 36"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "1364 -1172 -104"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"origin" "1324 -1132 -104"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"origin" "256 -96 -40"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "608 -96 16"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-1088 896 -456"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-1856 896 -360"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-608 1632 -184"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "800 -96 16"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "672 -224 16"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "928 544 16"
+}
+{
+"classname" "info_notnull"
+"targetname" "steamer1"
+"origin" "476 -844 -316"
+}
+{
+"targetname" "t142"
+"classname" "target_anger"
+"target" "t143"
+"origin" "872 -736 -312"
+"killtarget" "steamer1"
+"spawnflags" "2048"
+}
+{
+"model" "*80"
+"classname" "trigger_once"
+"target" "t142"
+"spawnflags" "2048"
+}
+{
+"targetname" "t150"
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "1"
+"origin" "1024 -672 -232"
+}
+{
+"spawnflags" "1"
+"angle" "270"
+"origin" "776 -392 -208"
+"classname" "monster_flyer"
+}
+{
+"classname" "monster_flyer"
+"origin" "664 -440 -208"
+"angle" "270"
+"spawnflags" "1"
+}
+{
+"origin" "544 -936 -240"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "45"
+}
+{
+"origin" "520 -936 -168"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "584 -920 -128"
+}
+{
+"light" "45"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "536 -912 -112"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "984 -944 -216"
+}
+{
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+"origin" "176 -360 16"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "64 -800 40"
+}
+{
+"origin" "80 -856 8"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "128 -864 40"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "432 -520 -208"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "296 -624 -208"
+}
+{
+"light" "105"
+"classname" "light"
+"origin" "144 -600 -208"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "72 -720 -152"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "112 -856 -200"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "-72 -912 -208"
+}
+{
+"classname" "light"
+"light" "95"
+"origin" "-40 -576 -144"
+}
+{
+"origin" "0 -976 -224"
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "736 -480 -172"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "608 -480 -172"
+}
+{
+"light" "125"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "800 -368 -232"
+}
+{
+"origin" "-88 -928 -64"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"origin" "368 -488 40"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"origin" "792 -456 16"
+"light" "100"
+"_color" "1.000000 1.000000 0.000000"
+"classname" "light"
+}
+{
+"origin" "1008 -888 -120"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"light" "75"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "368 -552 -312"
+}
+{
+"classname" "light"
+"_color" "1.000000 1.000000 0.000000"
+"light" "100"
+"origin" "944 -656 16"
+}
+{
+"light" "75"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "336 -552 -312"
+}
+{
+"light" "75"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "248 -544 -320"
+}
+{
+"light" "75"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "224 -552 -320"
+}
+{
+"light" "65"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "-112 -424 -48"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "416 -496 8"
+}
+{
+"light" "100"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "-104 -720 -32"
+}
+{
+"light" "65"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "-96 -424 -160"
+}
+{
+"light" "65"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "-104 -384 -40"
+}
+{
+"origin" "432 -552 40"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "100"
+}
+{
+"origin" "-104 -376 -104"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "65"
+}
+{
+"origin" "-120 -448 -96"
+"classname" "light"
+"_color" "0.000000 0.000000 1.000000"
+"light" "65"
+}
+{
+"light" "65"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "-104 -376 -160"
+}
+{
+"light" "50"
+"_color" "0.000000 0.000000 1.000000"
+"classname" "light"
+"origin" "960 -912 -104"
+}
+{
+"classname" "target_speaker"
+"noise" "world/steamy.wav"
+"spawnflags" "1"
+"targetname" "steamer1"
+"origin" "496 -848 -304"
+}
+{
+"origin" "1768 -560 -232"
+"classname" "monster_soldier_light"
+"angle" "180"
+"spawnflags" "257"
+}
+{
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_soldier_light"
+"origin" "544 -1120 -232"
+"target" "t139"
+}
+{
+"classname" "monster_gunner"
+"angle" "0"
+"spawnflags" "1"
+"origin" "2032 64 -40"
+}
+{
+"item" "ammo_slugs"
+"classname" "monster_gladiator"
+"angle" "270"
+"spawnflags" "1"
+"origin" "2248 64 -40"
+}
+{
+"spawnflags" "2049"
+"noise" "world/force1.wav"
+"origin" "-768 1060 -244"
+"classname" "target_speaker"
+"targetname" "t263"
+}
+{
+"spawnflags" "2049"
+"noise" "world/force1.wav"
+"origin" "-768 1036 -308"
+"classname" "target_speaker"
+"targetname" "t263"
+}
+{
+"wait" "5"
+"random" "2.5"
+"origin" "2212 156 -40"
+"classname" "func_timer"
+"target" "t136"
+"targetname" "t123"
+"spawnflags" "2048"
+}
+{
+"classname" "target_splash"
+"angle" "315"
+"sounds" "1"
+"targetname" "t136"
+"origin" "2224 116 -40"
+"spawnflags" "2048"
+}
+{
+"origin" "2544 -616 -416"
+"classname" "trigger_relay"
+"delay" "7"
+"targetname" "t114"
+"target" "t135"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"origin" "2576 -608 -416"
+"targetname" "t114"
+"target" "t135"
+"spawnflags" "2048"
+}
+{
+"classname" "target_speaker"
+"noise" "world/scan1.wav"
+"origin" "2504 -400 -376"
+"targetname" "t135"
+"spawnflags" "2050"
+}
+{
+"classname" "target_speaker"
+"noise" "world/scan1.wav"
+"origin" "2464 -400 -376"
+"targetname" "t135"
+"spawnflags" "2050"
+}
+{
+"classname" "target_speaker"
+"noise" "world/scan1.wav"
+"origin" "2544 -400 -376"
+"targetname" "t135"
+"spawnflags" "2050"
+}
+{
+"model" "*81"
+"spawnflags" "2048"
+"classname" "trigger_multiple"
+"wait" "5"
+"message" "Scanner inactive."
+"targetname" "scan2"
+}
+{
+"model" "*82"
+"classname" "trigger_once"
+"targetname" "t124"
+"killtarget" "scan2"
+"spawnflags" "2048"
+}
+{
+"model" "*83"
+"classname" "trigger_once"
+"targetname" "t124"
+"target" "t134"
+"spawnflags" "2048"
+}
+{
+"classname" "target_speaker"
+"origin" "2136 64 -24"
+"noise" "world/l_hum1.wav"
+"spawnflags" "2049"
+"targetname" "t126"
+}
+{
+"classname" "target_speaker"
+"origin" "2136 96 -24"
+"noise" "world/l_hum1.wav"
+"spawnflags" "2049"
+"targetname" "t126"
+}
+{
+"classname" "target_speaker"
+"origin" "2136 32 -24"
+"noise" "world/l_hum1.wav"
+"spawnflags" "2049"
+"targetname" "t126"
+}
+{
+"model" "*84"
+"classname" "trigger_multiple"
+"spawnflags" "2052"
+"target" "t133"
+"targetname" "t134"
+}
+{
+"classname" "target_speaker"
+"origin" "320 1672 -456"
+"noise" "world/force1.wav"
+"spawnflags" "2049"
+"targetname" "t263"
+}
+{
+"origin" "384 1668 -460"
+"classname" "target_speaker"
+"noise" "world/force1.wav"
+"spawnflags" "2049"
+"targetname" "t263"
+}
+{
+"noise" "world/fan1.wav"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"origin" "3016 -584 -384"
+"targetname" "t123"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2049"
+"noise" "world/fan1.wav"
+"origin" "3016 -560 -384"
+"targetname" "t123"
+}
+{
+"classname" "target_speaker"
+"origin" "1104 -1520 -320"
+"noise" "world/steam2.wav"
+"targetname" "t130"
+}
+{
+"classname" "target_speaker"
+"origin" "1064 -1360 -312"
+"noise" "world/steam2.wav"
+"targetname" "t129"
+}
+{
+"noise" "world/steam2.wav"
+"origin" "792 -1544 -304"
+"classname" "target_speaker"
+"targetname" "t131"
+}
+{
+"classname" "target_speaker"
+"origin" "824 -1360 -304"
+"noise" "world/steam2.wav"
+"targetname" "t132"
+}
+{
+"random" "2"
+"classname" "func_timer"
+"target" "t132"
+"origin" "800 -1352 -320"
+"spawnflags" "1"
+"wait" "10"
+}
+{
+"classname" "func_timer"
+"random" "2"
+"wait" "11"
+"target" "t131"
+"origin" "832 -1536 -320"
+"spawnflags" "1"
+}
+{
+"classname" "func_timer"
+"wait" "9"
+"random" "2.5"
+"target" "t130"
+"origin" "1128 -1512 -320"
+"spawnflags" "1"
+}
+{
+"classname" "func_timer"
+"spawnflags" "1"
+"wait" "7"
+"target" "t129"
+"origin" "1128 -1344 -320"
+"random" ".4"
+}
+{
+"classname" "target_steam"
+"angle" "-1"
+"speed" "75"
+"count" "48"
+"targetname" "t129"
+"origin" "1064 -1352 -328"
+"wait" "1.5"
+}
+{
+"count" "48"
+"speed" "125"
+"angle" "-1"
+"classname" "target_steam"
+"targetname" "t130"
+"origin" "1104 -1528 -328"
+"wait" "1.5"
+}
+{
+"count" "64"
+"speed" "150"
+"angle" "-1"
+"classname" "target_steam"
+"targetname" "t132"
+"origin" "824 -1368 -328"
+"wait" "1.5"
+}
+{
+"classname" "target_steam"
+"angle" "-1"
+"speed" "175"
+"count" "50"
+"targetname" "t131"
+"origin" "792 -1544 -328"
+"wait" "1.5"
+}
+{
+"targetname" "t263"
+"attenuation" "-1"
+"origin" "-1972 972 -160"
+"noise" "world/redforce.wav"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"model" "*85"
+"targetname" "t122"
+"spawnflags" "2052"
+"classname" "trigger_once"
+"target" "t128"
+}
+{
+"classname" "func_timer"
+"origin" "2108 156 -40"
+"random" ".5"
+"wait" "2"
+"targetname" "t123"
+"target" "t126"
+"spawnflags" "2048"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1800 -168 -32"
+}
+{
+"attenuation" "-1"
+"origin" "2808 -120 -376"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"classname" "target_speaker"
+"targetname" "t125"
+}
+{
+"attenuation" "-1"
+"origin" "2712 -264 -376"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"classname" "target_speaker"
+"targetname" "t125"
+}
+{
+"attenuation" "-1"
+"origin" "2488 -392 -376"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"classname" "target_speaker"
+"targetname" "t125"
+}
+{
+"attenuation" "-1"
+"origin" "2696 -584 -376"
+"noise" "world/klaxon1.wav"
+"spawnflags" "2054"
+"classname" "target_speaker"
+"targetname" "t125"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t122"
+"target" "t125"
+"origin" "2632 -560 -376"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t122"
+"delay" "9"
+"target" "t125"
+"origin" "2672 -544 -376"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t124"
+"message" "Use Med Scanner to encode system access"
+"origin" "2296 96 -40"
+"delay" "2"
+"spawnflags" "2048"
+}
+{
+"origin" "2976 -576 -352"
+"classname" "light"
+"light" "75"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2054"
+"noise" "world/klaxon1.wav"
+"origin" "2472 -600 -376"
+"attenuation" "-1"
+"targetname" "t125"
+}
+{
+"style" "36"
+"origin" "2136 112 -48"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+"targetname" "t126"
+"spawnflags" "2048"
+}
+{
+"style" "36"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2136 112 -8"
+"targetname" "t126"
+"spawnflags" "2048"
+}
+{
+"style" "36"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2136 112 32"
+"targetname" "t126"
+"spawnflags" "2048"
+}
+{
+"style" "36"
+"origin" "2136 16 -8"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+"targetname" "t126"
+"spawnflags" "2048"
+}
+{
+"style" "36"
+"origin" "2136 16 32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+"targetname" "t126"
+"spawnflags" "2048"
+}
+{
+"origin" "1456 -40 16"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t123"
+"killtarget" "lasbrush"
+"origin" "3040 -520 -384"
+"message" "Laser power interrupted."
+"spawnflags" "2048"
+}
+{
+"model" "*86"
+"spawnflags" "2055"
+"classname" "func_wall"
+"targetname" "t126"
+}
+{
+"classname" "target_explosion"
+"dmg" "64"
+"targetname" "t123"
+"origin" "3024 -576 -384"
+"spawnflags" "2048"
+}
+{
+"model" "*87"
+"classname" "func_explosive"
+"health" "10"
+"target" "t123"
+"spawnflags" "2048"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"origin" "2992 -544 -424"
+"targetname" "t122"
+"spawnflags" "1"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"origin" "2952 -608 -424"
+"targetname" "t122"
+"spawnflags" "1"
+}
+{
+"angle" "270"
+"spawnflags" "2051"
+"classname" "target_laser"
+"origin" "2136 120 -8"
+"dmg" "1000"
+"targetname" "t126"
+}
+{
+"angle" "270"
+"spawnflags" "2051"
+"classname" "target_laser"
+"origin" "2136 120 32"
+"dmg" "1000"
+"targetname" "t126"
+}
+{
+"classname" "target_laser"
+"spawnflags" "2051"
+"angle" "270"
+"origin" "2136 120 -48"
+"dmg" "1000"
+"targetname" "t126"
+}
+{
+"origin" "2816 -64 -192"
+"classname" "light"
+"light" "75"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1416 -400 -168"
+}
+{
+"model" "*88"
+"classname" "trigger_once"
+"target" "t121"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t120"
+"origin" "1736 -180 -40"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t119"
+"origin" "1500 -192 -40"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+"origin" "-264 992 176"
+}
+{
+"style" "36"
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2136 16 -48"
+"targetname" "t126"
+"spawnflags" "2048"
+}
+{
+"light" "75"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "1456 -40 -16"
+}
+{
+"light" "50"
+"classname" "light"
+"origin" "1480 -56 48"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1616 -96 32"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "75"
+"origin" "1456 -152 -8"
+}
+{
+"classname" "monster_gunner"
+"angle" "270"
+"spawnflags" "1"
+"origin" "1664 -112 -24"
+"target" "t120"
+"targetname" "t121"
+}
+{
+"model" "*89"
+"message" "Waste storage and processing opened."
+"target" "t110"
+"classname" "func_button"
+"angle" "0"
+"wait" "-1"
+"targetname" "t128"
+"sounds" "4"
+"spawnflags" "2048"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1792 96 36"
+}
+{
+"origin" "1480 -24 48"
+"classname" "light"
+"light" "50"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "2096 -400 -160"
+"_color" "1.000000 0.811475 0.045082"
+}
+{
+"origin" "2208 112 -32"
+"classname" "light"
+"light" "75"
+"spawnflags" "2048"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "2816 64 36"
+}
+{
+"origin" "2560 64 36"
+"classname" "light"
+"light" "75"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "2304 64 36"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3024 -576 -384"
+"_color" "1.000000 0.000000 0.000000"
+"style" "37"
+"targetname" "t123"
+"spawnflags" "2048"
+}
+{
+"origin" "2816 -480 -216"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "2208 -400 -96"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "2728 -344 -416"
+"classname" "trigger_relay"
+"delay" "11"
+"targetname" "t111"
+"target" "t116"
+"spawnflags" "2048"
+}
+{
+"speed" "3"
+"message" "az"
+"classname" "target_lightramp"
+"targetname" "t114"
+"spawnflags" "2048"
+"target" "t115"
+"origin" "2704 -424 -416"
+}
+{
+"classname" "target_lightramp"
+"message" "za"
+"speed" "3"
+"origin" "2776 -400 -416"
+"targetname" "t116"
+"target" "t115"
+"spawnflags" "2048"
+}
+{
+"style" "38"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"spawnflags" "2049"
+"targetname" "t115"
+"origin" "2488 -400 -376"
+}
+{
+"origin" "2728 -544 -416"
+"classname" "trigger_relay"
+"delay" "5"
+"target" "t114"
+"targetname" "t111"
+"spawnflags" "2048"
+}
+{
+"model" "*90"
+"classname" "func_door"
+"spawnflags" "2049"
+"angle" "180"
+"lip" "-128"
+"targetname" "t114"
+"speed" "75"
+}
+{
+"origin" "2608 -248 -416"
+"classname" "trigger_relay"
+"delay" "14"
+"target" "t113"
+"targetname" "t111"
+"spawnflags" "2048"
+}
+{
+"delay" "4"
+"classname" "trigger_relay"
+"target" "t113"
+"targetname" "t111"
+"origin" "2696 -296 -416"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t111"
+"target" "t112"
+"delay" "2"
+"origin" "2432 -200 -416"
+"spawnflags" "2048"
+}
+{
+"model" "*91"
+"classname" "func_door"
+"spawnflags" "2053"
+"angle" "-2"
+"targetname" "t112"
+"speed" "200"
+"wait" "11"
+"_minlight" ".3"
+"dmg" "1000"
+"sounds" "1"
+}
+{
+"model" "*92"
+"classname" "func_wall"
+"spawnflags" "2051"
+"targetname" "t113"
+}
+{
+"model" "*93"
+"origin" "2508 -308 -444"
+"classname" "func_door_rotating"
+"spawnflags" "70"
+"distance" "90"
+"targetname" "t111"
+"wait" "15"
+"_minlight" ".3"
+"dmg" "1000"
+"sounds" "3"
+}
+{
+"model" "*94"
+"origin" "2588 -400 -444"
+"classname" "func_door_rotating"
+"spawnflags" "132"
+"distance" "90"
+"targetname" "t111"
+"wait" "15"
+"_minlight" ".3"
+"dmg" "1000"
+"sounds" "3"
+}
+{
+"model" "*95"
+"origin" "2508 -492 -444"
+"classname" "func_door_rotating"
+"spawnflags" "68"
+"distance" "90"
+"targetname" "t111"
+"wait" "15"
+"_minlight" ".3"
+"dmg" "1000"
+"sounds" "3"
+}
+{
+"model" "*96"
+"angle" "180"
+"classname" "func_button"
+"target" "t111"
+"wait" "18"
+"message" "Scan initiated."
+"targetname" "t133"
+"spawnflags" "2048"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "960 -1728 -144"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "2048 96 36"
+}
+{
+"_color" "1.000000 0.811475 0.045082"
+"origin" "1968 -400 -160"
+"classname" "light"
+"light" "75"
+}
+{
+"_color" "1.000000 0.811475 0.045082"
+"origin" "1056 -1456 -272"
+"classname" "light"
+"light" "75"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1088 -1200 -40"
+}
+{
+"model" "*97"
+"classname" "func_door"
+"angle" "-2"
+"team" "forcedoor"
+"speed" "15"
+"targetname" "t110"
+"wait" "-1"
+"sounds" "4"
+"message" "This door is locked."
+"spawnflags" "2048"
+}
+{
+"model" "*98"
+"classname" "func_door"
+"angle" "-1"
+"team" "forcedoor"
+"speed" "15"
+"targetname" "t110"
+"wait" "-1"
+"sounds" "4"
+"spawnflags" "2048"
+}
+{
+"classname" "item_health"
+"origin" "880 -1048 -240"
+}
+{
+"classname" "item_health"
+"origin" "928 -1096 -240"
+}
+{
+"classname" "item_double"
+"origin" "672 -672 -416"
+}
+{
+"classname" "target_secret"
+"targetname" "t107"
+"origin" "832 -1144 -240"
+}
+{
+"model" "*99"
+"classname" "trigger_once"
+"target" "t107"
+"message" "You found a secret!"
+}
+{
+"spawnflags" "258"
+"classname" "monster_flyer"
+"targetname" "flyer"
+"origin" "1168 -832 -40"
+}
+{
+"spawnflags" "2"
+"classname" "monster_flyer"
+"targetname" "flyer"
+"origin" "1632 -864 -40"
+}
+{
+"classname" "monster_flyer"
+"targetname" "flyer"
+"origin" "1600 -824 -40"
+"spawnflags" "770"
+}
+{
+"classname" "monster_flyer"
+"spawnflags" "2"
+"targetname" "flyer"
+"origin" "1184 -872 -40"
+}
+{
+"dmg" "32"
+"delay" ".1"
+"classname" "target_explosion"
+"targetname" "t106"
+"origin" "1608 -856 -128"
+"spawnflags" "2048"
+}
+{
+"delay" ".2"
+"classname" "target_explosion"
+"targetname" "t106"
+"origin" "1592 -824 -120"
+"spawnflags" "2048"
+}
+{
+"delay" ".1"
+"classname" "target_explosion"
+"targetname" "t106"
+"origin" "1176 -872 -136"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"targetname" "t106"
+"origin" "1144 -824 -120"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"delay" ""
+"targetname" "t106"
+"origin" "1648 -880 -128"
+"spawnflags" "2048"
+}
+{
+"model" "*100"
+"target" "flyer"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"origin" "864 -1456 -272"
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.811475 0.045082"
+}
+{
+"origin" "1088 -1488 -40"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "768 -1200 -40"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "736 -912 -400"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "736 -1120 -304"
+"classname" "light"
+"light" "75"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "720 -832 -400"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "752 -1040 -368"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "744 -1056 -336"
+}
+{
+"classname" "light"
+"light" "75"
+"origin" "704 -768 -400"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "768 -1488 -40"
+}
+{
+"light" "75"
+"classname" "light"
+"origin" "1824 -400 -160"
+"_color" "1.000000 0.811475 0.045082"
+}
+{
+"targetname" "t103"
+"random" "1"
+"classname" "func_timer"
+"target" "t105"
+"origin" "680 -1124 -248"
+"wait" "2"
+}
+{
+"sounds" "1"
+"classname" "target_splash"
+"targetname" "t105"
+"origin" "720 -1108 -272"
+"angle" "270"
+}
+{
+"classname" "target_splash"
+"sounds" "1"
+"targetname" "t104"
+"origin" "720 -1208 -272"
+"angle" "90"
+}
+{
+"classname" "func_timer"
+"random" "2"
+"target" "t104"
+"targetname" "t103"
+"origin" "740 -1200 -248"
+"wait" "3"
+}
+{
+"origin" "1364 -1516 -104"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"origin" "596 -1068 -104"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"origin" "556 -1108 -104"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"origin" "556 -1516 -104"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"origin" "596 -1556 -104"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"origin" "864 -1608 -200"
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+}
+{
+"classname" "target_explosion"
+"dmg" "32"
+"targetname" "t103"
+"origin" "688 -1144 -248"
+"delay" ".2"
+}
+{
+"classname" "target_explosion"
+"targetname" "t103"
+"dmg" "32"
+"origin" "696 -1192 -264"
+}
+{
+"classname" "target_explosion"
+"dmg" "32"
+"targetname" "t103"
+"origin" "736 -1160 -256"
+"delay" ".1"
+}
+{
+"classname" "target_explosion"
+"dmg" "32"
+"targetname" "t103"
+"origin" "712 -1120 -272"
+}
+{
+"map" "rsewer1$sew1end"
+"origin" "960 -1912 -248"
+"targetname" "t102"
+"classname" "target_changelevel"
+"spawnflags" "2048"
+}
+{
+"model" "*101"
+"target" "t102"
+"classname" "trigger_multiple"
+"angle" "270"
+"spawnflags" "2048"
+}
+{
+"origin" "960 -1632 -180"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "960 -1888 -144"
+"light" "75"
+"classname" "light"
+}
+{
+"targetname" "t26"
+"origin" "1160 656 -80"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*102"
+"spawnflags" "4"
+"targetname" "t318"
+"classname" "func_plat2"
+"lip" "16"
+"_minlight" ".3"
+}
+{
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+"origin" "1048 -1608 -200"
+}
+{
+"light" "100"
+"_color" "1.000000 0.714876 0.053719"
+"classname" "light"
+"origin" "960 -1608 -96"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.714876 0.053719"
+"light" "100"
+"origin" "1324 -1556 -104"
+}
+{
+"model" "*103"
+"health" "10"
+"target" "t253"
+"classname" "func_explosive"
+"mass" "200"
+"spawnflags" "2048"
+}
+{
+"model" "*104"
+"health" "10"
+"target" "t254"
+"classname" "func_explosive"
+"mass" "200"
+"spawnflags" "2048"
+}
+{
+"origin" "-1952 896 -440"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"spawnflags" "257"
+"origin" "-1320 1248 -424"
+"classname" "monster_gunner"
+"angle" "225"
+}
+{
+"spawnflags" "2305"
+"origin" "-2016 544 -200"
+"classname" "monster_flyer"
+"angle" "45"
+}
+{
+"spawnflags" "2305"
+"origin" "-2016 1248 -200"
+"classname" "monster_flyer"
+"angle" "315"
+}
+{
+"model" "*105"
+"targetname" "t56"
+"classname" "func_explosive"
+"mass" "200"
+}
+{
+"model" "*106"
+"spawnflags" "768"
+"targetname" "t56"
+"classname" "func_explosive"
+"mass" "200"
+}
+{
+"model" "*107"
+"spawnflags" "256"
+"mass" "200"
+"targetname" "t56"
+"classname" "func_explosive"
+}
+{
+"targetname" "t266"
+"target" "t56"
+"origin" "-1256 896 -360"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_gladiator"
+}
+{
+"targetname" "t56"
+"classname" "monster_turret"
+"spawnflags" "928"
+"angle" "0"
+"origin" "-2208 768 -192"
+}
+{
+"targetname" "t56"
+"origin" "-2208 1024 -192"
+"angle" "0"
+"spawnflags" "160"
+"classname" "monster_turret"
+}
+{
+"targetname" "t56"
+"classname" "monster_turret"
+"spawnflags" "160"
+"angle" "180"
+"origin" "-1120 768 -192"
+}
+{
+"targetname" "t56"
+"origin" "-1120 1024 -192"
+"angle" "180"
+"spawnflags" "416"
+"classname" "monster_turret"
+}
+{
+"model" "*108"
+"targetname" "t52"
+"dmg" "500"
+"classname" "trigger_hurt"
+"spawnflags" "2048"
+}
+{
+"origin" "-1600 688 -472"
+"spawnflags" "2049"
+"targetname" "floorpipe"
+"target" "t55"
+"classname" "func_timer"
+}
+{
+"origin" "-1604 692 -480"
+"sounds" "6"
+"count" "16"
+"speed" "50"
+"angle" "-1"
+"targetname" "t55"
+"classname" "target_steam"
+"spawnflags" "2048"
+}
+{
+"origin" "-1600 1304 -416"
+"targetname" "steampipe"
+"spawnflags" "2049"
+"target" "t54"
+"classname" "func_timer"
+}
+{
+"origin" "-1600 1320 -424"
+"targetname" "t54"
+"sounds" "6"
+"count" "16"
+"speed" "20"
+"angle" "270"
+"classname" "target_steam"
+"spawnflags" "2048"
+}
+{
+"origin" "-1528 768 -472"
+"target" "t53"
+"targetname" "t52"
+"classname" "func_timer"
+"spawnflags" "2048"
+}
+{
+"model" "*109"
+"dmg" "15"
+"targetname" "t52"
+"spawnflags" "2051"
+"classname" "trigger_hurt"
+}
+{
+"origin" "-1624 688 -480"
+"delay" ".2"
+"targetname" "t52"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"origin" "-1584 704 -488"
+"delay" ".3"
+"targetname" "t52"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"origin" "-1600 664 -496"
+"targetname" "t52"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"target" "t148"
+"origin" "-1608 720 -480"
+"delay" ".2"
+"dmg" "128"
+"targetname" "t52"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*110"
+"spawnflags" "2048"
+"target" "t51"
+"classname" "trigger_once"
+}
+{
+"origin" "-1520 720 -472"
+"killtarget" "floorpipe"
+"target" "t47"
+"targetname" "t51"
+"classname" "target_anger"
+"spawnflags" "2048"
+}
+{
+"targetname" "t53"
+"origin" "-1600 688 -528"
+"sounds" "6"
+"count" "96"
+"speed" "200"
+"angle" "-1"
+"classname" "target_steam"
+"spawnflags" "2048"
+}
+{
+"origin" "-1604 684 -468"
+"targetname" "floorpipe"
+"classname" "info_notnull"
+"spawnflags" "2048"
+}
+{
+"model" "*111"
+"health" "5"
+"spawnflags" "2048"
+"target" "t52"
+"killtarget" "floorpipe"
+"mass" "800"
+"classname" "func_explosive"
+}
+{
+"origin" "520 -832 -280"
+"targetname" "steamer1"
+"target" "t50"
+"spawnflags" "1"
+"classname" "func_timer"
+}
+{
+"model" "*112"
+"classname" "trigger_hurt"
+"dmg" "15"
+"targetname" "t48"
+"spawnflags" "2051"
+}
+{
+"classname" "func_timer"
+"targetname" "t48"
+"target" "t49"
+"origin" "-1512 1336 -416"
+"spawnflags" "2048"
+}
+{
+"target" "t147"
+"dmg" "128"
+"classname" "target_explosion"
+"origin" "-1608 1320 -440"
+"targetname" "t48"
+"spawnflags" "2048"
+}
+{
+"delay" ".1"
+"dmg" "128"
+"classname" "target_explosion"
+"targetname" "t48"
+"origin" "-1584 1320 -424"
+"spawnflags" "2048"
+}
+{
+"delay" ".2"
+"dmg" "128"
+"classname" "target_explosion"
+"origin" "-1616 1320 -408"
+"targetname" "t48"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"dmg" "128"
+"origin" "-1592 1320 -400"
+"targetname" "t48"
+"spawnflags" "2048"
+}
+{
+"target" "t266"
+"classname" "target_anger"
+"targetname" "t46"
+"origin" "-1528 1296 -440"
+"killtarget" "steampipe"
+"spawnflags" "2048"
+}
+{
+"model" "*113"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t46"
+}
+{
+"classname" "info_notnull"
+"origin" "-1600 1308 -416"
+"targetname" "steampipe"
+"spawnflags" "2048"
+}
+{
+"count" "75"
+"speed" "150"
+"classname" "target_steam"
+"angle" "270"
+"origin" "-1600 1320 -416"
+"targetname" "t49"
+"sounds" "6"
+"spawnflags" "2048"
+}
+{
+"model" "*114"
+"health" "20"
+"mass" "10"
+"classname" "func_explosive"
+"target" "t103"
+}
+{
+"targetname" "t50"
+"classname" "target_steam"
+"count" "32"
+"sounds" "208"
+"origin" "496 -848 -320"
+"angle" "-1"
+}
+{
+"model" "*115"
+"origin" "448 1408 -54"
+"accel" "1"
+"targetname" "t269"
+"classname" "func_rotating"
+"spawnflags" "10240"
+"speed" "500"
+}
+{
+"model" "*116"
+"origin" "-448 1064 -432"
+"targetname" "doors"
+"wait" "2.5"
+"distance" "90"
+"spawnflags" "6"
+"classname" "func_door_rotating"
+}
+{
+"model" "*117"
+"targetname" "t257"
+"height" "184"
+"classname" "func_plat2"
+"_minlight" ".2"
+"spawnflags" "2"
+"speed" "100"
+}
+{
+"model" "*118"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"targetname" "t45"
+"classname" "target_explosion"
+"dmg" "256"
+"origin" "408 1152 -1040"
+"delay" ".3"
+"spawnflags" "2048"
+}
+{
+"target" "t45"
+"targetname" "t44"
+"delay" ".3"
+"origin" "424 1200 -1104"
+"dmg" "256"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"origin" "-64 800 -388"
+"targetname" "t44"
+"angle" "-1"
+"classname" "target_steam"
+"speed" "150"
+"count" "96"
+"wait" "8"
+"sounds" "208"
+"spawnflags" "2048"
+}
+{
+"origin" "-64 1560 -388"
+"targetname" "t43"
+"angle" "-1"
+"sounds" "208"
+"wait" "8"
+"count" "96"
+"speed" "150"
+"classname" "target_steam"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"dmg" "256"
+"origin" "392 1216 -1232"
+"targetname" "t42"
+"delay" ".3"
+"target" "t43"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"dmg" "256"
+"origin" "448 1168 -1296"
+"targetname" "t41"
+"delay" ".3"
+"target" "t42"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"dmg" "256"
+"origin" "376 1184 -1360"
+"targetname" "t40"
+"delay" ".3"
+"target" "t41"
+"spawnflags" "2048"
+}
+{
+"classname" "target_explosion"
+"dmg" "256"
+"origin" "416 1184 -1424"
+"targetname" "explo"
+"target" "t40"
+"spawnflags" "2048"
+}
+{
+"target" "t44"
+"classname" "target_explosion"
+"dmg" "256"
+"origin" "456 1208 -1168"
+"targetname" "t43"
+"delay" ".3"
+"spawnflags" "2048"
+}
+{
+"style" "39"
+"origin" "288 1120 -488"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"origin" "288 1248 -488"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"spawnflags" "2049"
+}
+{
+"origin" "-160 1024 -496"
+"classname" "trigger_relay"
+"target" "t39"
+"targetname" "doors"
+"delay" "2"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"origin" "32 1376 -496"
+"target" "t39"
+"targetname" "light2"
+"delay" "1"
+"spawnflags" "2048"
+}
+{
+"model" "*119"
+"classname" "trigger_hurt"
+"dmg" "500"
+"targetname" "t39"
+"spawnflags" "2059"
+"message" "Player was electrocuted."
+}
+{
+"origin" "-192 896 -488"
+"message" "az"
+"spawnflags" "2048"
+"speed" "1"
+"classname" "target_lightramp"
+"target" "uplight"
+"targetname" "doors"
+}
+{
+"classname" "target_lightramp"
+"speed" "3"
+"spawnflags" "2048"
+"message" "za"
+"origin" "-224 1536 -488"
+"targetname" "door2"
+"target" "uplight"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-560 1240 -544"
+"spawnflags" "1"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-560 1128 -544"
+"spawnflags" "1"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-544 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-544 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-480 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-480 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-416 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-416 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-352 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-352 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-288 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-288 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-224 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-224 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-160 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-160 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-96 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-96 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-32 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-32 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "32 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "32 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "96 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "96 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "160 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "160 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "224 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "224 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "352 1248 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "352 1120 -488"
+"spawnflags" "2049"
+}
+{
+"style" "39"
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"targetname" "uplight"
+"origin" "-656 1128 -544"
+"spawnflags" "1"
+}
+{
+"style" "39"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"targetname" "uplight"
+"origin" "-664 1240 -544"
+"spawnflags" "1"
+}
+{
+"origin" "320 1088 -512"
+"classname" "path_corner"
+"target" "t36"
+"targetname" "t38"
+"pathtarget" "light2"
+"spawnflags" "2048"
+}
+{
+"classname" "path_corner"
+"targetname" "t36"
+"target" "t37"
+"origin" "320 1088 -1576"
+"pathtarget" "explo"
+"speed" "400"
+"spawnflags" "2048"
+}
+{
+"classname" "path_corner"
+"targetname" "t35"
+"origin" "152 1088 -512"
+"pathtarget" "door2"
+"target" "t38"
+"spawnflags" "2048"
+}
+{
+"classname" "path_corner"
+"targetname" "t34"
+"target" "t35"
+"origin" "-704 1088 -512"
+"pathtarget" "doors"
+"spawnflags" "2048"
+}
+{
+"classname" "path_corner"
+"targetname" "t37"
+"target" "t34"
+"origin" "-704 1088 -1584"
+"speed" "200"
+"spawnflags" "2048"
+}
+{
+"classname" "info_notnull"
+"targetname" "gladbox"
+"origin" "1132 804 -108"
+"spawnflags" "2048"
+}
+{
+"classname" "monster_gladiator"
+"targetname" "t30"
+"origin" "808 1296 -104"
+"spawnflags" "1"
+"item" "ammo_slugs"
+}
+{
+"classname" "target_anger"
+"target" "t30"
+"targetname" "t31"
+"killtarget" "gladbox"
+"origin" "1080 816 -104"
+"spawnflags" "2048"
+}
+{
+"model" "*120"
+"classname" "trigger_once"
+"target" "t31"
+"spawnflags" "2048"
+}
+{
+"origin" "1216 704 96"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_parasite"
+}
+{
+"model" "*121"
+"message" "Incenerator Activated."
+"wait" "-1"
+"angle" "0"
+"classname" "func_button"
+"target" "t33"
+"spawnflags" "2048"
+}
+{
+"model" "*122"
+"classname" "func_train"
+"targetname" "t33"
+"dmg" "1000"
+"_minlight" ".2"
+"target" "t37"
+"speed" "200"
+"spawnflags" "2048"
+}
+{
+"model" "*123"
+"origin" "-448 1304 -432"
+"classname" "func_door_rotating"
+"spawnflags" "4"
+"distance" "90"
+"targetname" "doors"
+"wait" "2.5"
+}
+{
+"model" "*124"
+"spawnflags" "2054"
+"classname" "func_wall"
+"targetname" "t263"
+}
+{
+"model" "*125"
+"origin" "260 1664 -372"
+"wait" "-1"
+"distance" "-90"
+"spawnflags" "2212"
+"classname" "func_door_rotating"
+"targetname" "t263"
+}
+{
+"model" "*126"
+"origin" "444 1664 -508"
+"distance" "90"
+"spawnflags" "2214"
+"classname" "func_door_rotating"
+"targetname" "t263"
+}
+{
+"model" "*127"
+"targetname" "t33"
+"wait" "-1"
+"classname" "func_door"
+"angle" "-2"
+"speed" "5"
+"spawnflags" "2080"
+"team" "bigdoor"
+}
+{
+"model" "*128"
+"targetname" "t33"
+"wait" "-1"
+"classname" "func_door"
+"angle" "-1"
+"speed" "5"
+"spawnflags" "2080"
+"team" "bigdoor"
+}
+{
+"model" "*129"
+"classname" "func_door"
+"angle" "-1"
+"sounds" "2"
+}
+{
+"model" "*130"
+"mass" "100"
+"dmg" "96"
+"classname" "func_explosive"
+}
+{
+"targetname" "t26"
+"classname" "target_explosion"
+"origin" "1184 688 -104"
+"spawnflags" "2048"
+}
+{
+"origin" "1184 720 -96"
+"targetname" "t26"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"dmg" "128"
+"origin" "1144 704 -104"
+"targetname" "t26"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*131"
+"origin" "1184 704 -120"
+"dmg" "10000"
+"distance" "82.5"
+"targetname" "t26"
+"speed" "175"
+"spawnflags" "2222"
+"classname" "func_door_rotating"
+"_minlight" ".2"
+}
+{
+"model" "*132"
+"target" "t26"
+"classname" "func_explosive"
+"killtarget" "gladbox"
+"spawnflags" "2048"
+"health" "10"
+}
+{
+"model" "*133"
+"health" "5"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"mass" "300"
+"target" "t48"
+"killtarget" "steampipe"
+}
+{
+"model" "*134"
+"health" "25"
+"killtarget" "steamer1"
+"mass" "800"
+"classname" "func_explosive"
+"target" "t317"
+}
+{
+"origin" "-608 1424 -104"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "2208 1504 136"
+"light" "150"
+"classname" "light"
+} \ No newline at end of file
diff --git a/rogue/stuff/mapfixes/rware2.ent b/rogue/stuff/mapfixes/rware2.ent
new file mode 100644
index 0000000..c3cf837
--- /dev/null
+++ b/rogue/stuff/mapfixes/rware2.ent
@@ -0,0 +1,8317 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Fixed unreachable monster_soldier (2690)
+//
+// This guy was set to a triggered spawn but has no targetname.
+// Changed his spawnflags from 2051 to 2049.
+//
+// 2. Fixed console warning for monster_stalker (5942)
+//
+// Changed his spawnflags from 2057 to 2825 (Hard only).
+// And spawned another monster_stalker (8313) (same guy) with
+// spawnflags 3081 (Easy + Medium).
+//
+// 3. Raised item_pack vertically (89)
+//
+// This Ammo Pack had a tendency to be in solid, so I raised it up
+// a few units on vertical axis.
+//
+// 4. Moved item_armor_jacket (207) out of a wall
+//
+// This Jacket Armor was spawning partially inside a wall
+// so I lowered its X-positioning a bit (1664 to 1624).
+//
+// 5. Silenced warning for monster_stalker (5078)
+//
+// He spawns on all difficulties but his targets (t119) are
+// not present on easy or medium. Fixed by giving him a
+// dummy target (3000) just so he has "something" to target.
+{
+"classname" "worldspawn"
+"message" "Waterfront Storage"
+"nextmap" "rbase1"
+"sky" "rogue1"
+"sounds" "3"
+}
+{
+"model" "*1"
+"targetname" "t163"
+"target" "t164"
+"spawnflags" "2816"
+"classname" "func_explosive"
+}
+{
+"model" "*2"
+"spawnflags" "3328"
+"target" "t155"
+"classname" "func_explosive"
+}
+{
+"origin" "2848 -2464 920"
+"targetname" "t392"
+"wait" "2.2"
+"spawnflags" "2048"
+"target" "t369"
+"classname" "func_timer"
+}
+{
+"origin" "3296 -392 928"
+"angle" "0"
+"spawnflags" "7681"
+"classname" "monster_soldier"
+}
+{
+"origin" "1904 -872 864"
+"targetname" "t137"
+"angle" "0"
+"spawnflags" "7681"
+"classname" "monster_gunner"
+}
+{
+"origin" "1056 136 1248"
+"spawnflags" "6145"
+"angle" "270"
+"classname" "monster_berserk"
+}
+{
+"origin" "2456 -96 1224"
+"targetname" "t391"
+"spawnflags" "2048"
+"target" "t160"
+"classname" "trigger_relay"
+}
+{
+"origin" "3520 -128 912"
+"targetname" "t390"
+"spawnflags" "2048"
+"classname" "point_combat"
+}
+{
+"origin" "1615 -2165 1120"
+"spawnflags" "6144"
+"classname" "item_pack"
+}
+{
+"origin" "2272 -800 1144"
+"angle" "-2"
+"spawnflags" "3873"
+"classname" "monster_turret"
+}
+{
+"classname" "item_sphere_vengeance"
+"spawnflags" "5888"
+"origin" "2816 -1472 1168"
+}
+{
+"classname" "item_sphere_hunter"
+"spawnflags" "5888"
+"origin" "1480 -160 1312"
+}
+{
+"classname" "ammo_nuke"
+"spawnflags" "5888"
+"origin" "2400 -2464 848"
+}
+{
+"classname" "ammo_tesla"
+"spawnflags" "5888"
+"origin" "1536 -392 984"
+}
+{
+"classname" "misc_teleporter_dest"
+"angle" "270"
+"spawnflags" "5888"
+"targetname" "t389"
+"origin" "416 -2016 984"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "5888"
+"target" "t389"
+"origin" "1984 -192 992"
+}
+{
+"classname" "ammo_flechettes"
+"spawnflags" "2048"
+"origin" "2016 -216 984"
+}
+{
+"classname" "ammo_bullets"
+"origin" "3104 -1112 1168"
+}
+{
+"classname" "ammo_bullets"
+"spawnflags" "5888"
+"origin" "2720 -1128 1168"
+}
+{
+"classname" "weapon_chaingun"
+"origin" "2728 -1056 1168"
+"spawnflags" "5888"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_cells"
+"origin" "2664 40 1040"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "5888"
+"origin" "2784 416 1104"
+}
+{
+"classname" "weapon_hyperblaster"
+"spawnflags" "5888"
+"origin" "2752 256 1040"
+}
+{
+"classname" "weapon_rocketlauncher"
+"spawnflags" "5888"
+"origin" "1824 -184 1232"
+}
+{
+"classname" "ammo_rockets"
+"spawnflags" "5888"
+"origin" "1824 -32 1232"
+}
+{
+"classname" "weapon_grenadelauncher"
+"spawnflags" "5888"
+"origin" "1568 -1848 1128"
+}
+{
+"classname" "ammo_grenades"
+"spawnflags" "5888"
+"origin" "1528 -1968 1120"
+}
+{
+"classname" "ammo_grenades"
+"spawnflags" "5888"
+"origin" "1552 -2224 1120"
+}
+{
+"classname" "item_armor_combat"
+"spawnflags" "5888"
+"origin" "-480 -2840 912"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "5888"
+"origin" "1416 -2960 1104"
+}
+{
+"classname" "ammo_tesla"
+"spawnflags" "5888"
+"origin" "1952 -2344 1104"
+}
+{
+"classname" "item_armor_jacket"
+"spawnflags" "1792"
+"origin" "1624 -2192 1128"
+}
+{
+"classname" "item_armor_body"
+"spawnflags" "5888"
+"origin" "864 -1504 1168"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_cells"
+"origin" "872 -1112 1232"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "5888"
+"origin" "608 -1192 1232"
+}
+{
+"classname" "weapon_plasmabeam"
+"spawnflags" "5888"
+"origin" "760 -1136 1232"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+"origin" "256 -224 1360"
+}
+{
+"spawnflags" "5888"
+"classname" "ammo_flechettes"
+"origin" "224 -488 1360"
+}
+{
+"spawnflags" "5888"
+"classname" "weapon_etf_rifle"
+"origin" "280 -384 1360"
+}
+{
+"classname" "ammo_rockets"
+"origin" "344 256 1232"
+}
+{
+"classname" "ammo_shells"
+"spawnflags" "5888"
+"origin" "480 296 1304"
+}
+{
+"classname" "weapon_supershotgun"
+"spawnflags" "5888"
+"origin" "512 208 1232"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "225"
+"origin" "1832 -856 1048"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "135"
+"origin" "2848 -160 1176"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "315"
+"origin" "992 -1120 1048"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "2536 -2336 1112"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "2848 -872 1176"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "2848 -256 920"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "315"
+"origin" "1880 -856 856"
+}
+{
+"angle" "45"
+"classname" "info_player_deathmatch"
+"origin" "1056 -168 1240"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "1856 -672 984"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "315"
+"origin" "1184 -160 984"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "180"
+"origin" "664 -192 1240"
+}
+{
+"angle" "180"
+"classname" "info_player_deathmatch"
+"origin" "-160 -2368 792"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "-128 -2688 984"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "3360 -1120 856"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "2808 -1944 1176"
+}
+{
+"angle" "270"
+"classname" "info_player_deathmatch"
+"origin" "3736 24 920"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "90"
+"origin" "3328 -608 1176"
+}
+{
+"classname" "item_doppleganger"
+"spawnflags" "5888"
+"origin" "3328 -128 920"
+}
+{
+"classname" "item_sphere_defender"
+"spawnflags" "5888"
+"origin" "488 -464 1360"
+}
+{
+"model" "*3"
+"classname" "func_wall"
+"spawnflags" "2048"
+}
+{
+"origin" "2976 -1816 1304"
+"angles" "20 45 0"
+"classname" "info_player_intermission"
+}
+{
+"classname" "item_health_small"
+"origin" "928 -288 1240"
+}
+{
+"classname" "item_health_small"
+"origin" "936 -160 1240"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1896 408 1232"
+"target" "t387"
+"targetname" "t388"
+}
+{
+"classname" "point_combat"
+"targetname" "t384"
+"target" "t385"
+"spawnflags" "2048"
+"origin" "1976 120 1232"
+}
+{
+"model" "*4"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t383"
+}
+{
+"origin" "512 120 1372"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "2912 -2208 1240"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "2976 -2752 1112"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "1824 224 1304"
+"classname" "item_health_large"
+}
+{
+"origin" "1416 -2880 1120"
+"targetname" "t254"
+"angle" "90"
+"spawnflags" "2051"
+"classname" "monster_gunner"
+}
+{
+"origin" "1392 -2976 1120"
+"angle" "90"
+"targetname" "t254"
+"spawnflags" "2051"
+"classname" "monster_gunner"
+}
+{
+"origin" "1448 -2976 1120"
+"targetname" "t254"
+"angle" "90"
+"spawnflags" "2053"
+"classname" "monster_gunner"
+}
+{
+"model" "*5"
+"targetname" "t254"
+"spawnflags" "2048"
+"classname" "func_explosive"
+}
+{
+"origin" "-176 -128 1504"
+"_color" "1.000000 0.000000 0.000000"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "2592 -2628 904"
+"targetname" "t380"
+"spawnflags" "2048"
+"angle" "90"
+"classname" "target_splash"
+"sounds" "1"
+}
+{
+"origin" "2560 -2616 904"
+"wait" "2.5"
+"random" ".75"
+"spawnflags" "2049"
+"targetname" "t27"
+"target" "t380"
+"classname" "func_timer"
+}
+{
+"origin" "2616 -2304 904"
+"random" "1.5"
+"wait" "3"
+"target" "t379"
+"targetname" "t30"
+"spawnflags" "2049"
+"classname" "func_timer"
+}
+{
+"targetname" "t379"
+"sounds" "1"
+"spawnflags" "2048"
+"angle" "270"
+"origin" "2592 -2300 904"
+"classname" "target_splash"
+}
+{
+"model" "*6"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*7"
+"target" "t88"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"model" "*8"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+"target" "t143"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2049"
+"noise" "world/scan1.wav"
+"origin" "1720 -2368 1152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "2049"
+"noise" "world/scan1.wav"
+"origin" "1720 -2496 1152"
+}
+{
+"model" "*9"
+"classname" "func_door"
+"angle" "90"
+"wait" "-1"
+"_minlight" ".3"
+"team" "done3"
+"targetname" "t250"
+"spawnflags" "2048"
+}
+{
+"model" "*10"
+"classname" "func_door"
+"angle" "270"
+"_minlight" ".3"
+"targetname" "t250"
+"team" "done3"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"classname" "ammo_shells"
+"origin" "1632 32 1048"
+}
+{
+"classname" "target_lightramp"
+"targetname" "t376"
+"target" "t377"
+"speed" "5"
+"spawnflags" "2048"
+"origin" "2688 128 1216"
+"message" "az"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t129"
+"origin" "2752 160 1216"
+"target" "t376"
+}
+{
+"classname" "trigger_relay"
+"spawnflags" "2048"
+"targetname" "t129"
+"delay" "3"
+"origin" "2752 192 1216"
+"target" "t378"
+}
+{
+"model" "*11"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+"targetname" "comeout"
+"wait" "3"
+"message" "Shuttle inactive."
+}
+{
+"classname" "trigger_relay"
+"targetname" "t129"
+"target" "t136"
+"spawnflags" "2048"
+"origin" "2768 -344 1320"
+}
+{
+"model" "*12"
+"classname" "trigger_once"
+"spawnflags" "2052"
+"target" "t375"
+"targetname" "t129"
+}
+{
+"origin" "432 176 1304"
+"message" "Canyon entrance to\n Logistics is now\naccessible."
+"spawnflags" "2048"
+"targetname" "t7"
+"classname" "target_help"
+}
+{
+"origin" "2024 -296 1304"
+"message" "Acquire blue key card\n to proceed to Logistics."
+"spawnflags" "2048"
+"targetname" "t223"
+"classname" "target_help"
+}
+{
+"origin" "2880 -584 1184"
+"targetname" "t246"
+"target" "t247"
+"angle" "270"
+"item" "ammo_cells"
+"spawnflags" "3841"
+"classname" "monster_medic_commander"
+}
+{
+"targetname" "t247"
+"origin" "2800 -824 1184"
+"angle" "45"
+"item" "ammo_grenades"
+"spawnflags" "3841"
+"classname" "monster_gunner"
+}
+{
+"classname" "monster_stalker"
+"spawnflags" "6921"
+"angle" "180"
+"origin" "2380 -1916 1258"
+}
+{
+"origin" "2912 -2208 1272"
+"spawnflags" "2057"
+"classname" "monster_turret"
+"angle" "-2"
+}
+{
+"origin" "2848 -2944 1184"
+"angle" "270"
+"spawnflags" "3841"
+"classname" "monster_gunner"
+}
+{
+"origin" "2096 -2720 1168"
+"spawnflags" "3841"
+"target" "t179"
+"angle" "180"
+"classname" "monster_daedalus"
+}
+{
+"origin" "464 -2424 1160"
+"spawnflags" "6401"
+"classname" "monster_daedalus"
+"angle" "90"
+}
+{
+"target" "t96"
+"origin" "408 -2304 992"
+"item" "ammo_slugs"
+"spawnflags" "3841"
+"classname" "monster_gladiator"
+}
+{
+"origin" "-120 -2536 992"
+"spawnflags" "3841"
+"target" "t72"
+"item" "ammo_slugs"
+"classname" "monster_gladiator"
+}
+{
+"origin" "32 -2368 1024"
+"angle" "0"
+"spawnflags" "6913"
+"classname" "monster_hover"
+}
+{
+"origin" "480 356 1376"
+"angle" "270"
+"targetname" "t166"
+"spawnflags" "4001"
+"classname" "monster_turret"
+}
+{
+"origin" "1188 284 1378"
+"angle" "270"
+"targetname" "t363"
+"spawnflags" "3849"
+"classname" "monster_stalker"
+}
+{
+"classname" "monster_soldier_ss"
+"item" "ammo_bullets"
+"spawnflags" "6401"
+"angle" "180"
+"origin" "1248 104 1248"
+}
+{
+"origin" "864 -672 1248"
+"targetname" "t198"
+"item" "ammo_bullets"
+"spawnflags" "3843"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"targetname" "t140"
+"origin" "912 224 1264"
+"angle" "45"
+"spawnflags" "6915"
+"classname" "monster_daedalus"
+"item" "ammo_cells"
+}
+{
+"angle" "315"
+"origin" "1792 416 1248"
+"spawnflags" "3843"
+"targetname" "uknow"
+"target" "t227"
+"classname" "monster_gunner"
+}
+{
+"origin" "1912 -152 1240"
+"spawnflags" "6913"
+"angle" "315"
+"classname" "monster_gunner"
+"item" "ammo_bullets"
+}
+{
+"origin" "1880 -664 992"
+"targetname" "t383"
+"target" "t218"
+"item" "ammo_grenades"
+"spawnflags" "3843"
+"angle" "135"
+"classname" "monster_gunner"
+}
+{
+"origin" "944 -56 1088"
+"angle" "270"
+"spawnflags" "3841"
+"item" "ammo_cells"
+"classname" "monster_daedalus"
+}
+{
+"origin" "1248 -152 992"
+"targetname" "t101"
+"item" "ammo_cells"
+"angle" "270"
+"spawnflags" "3843"
+"classname" "monster_medic"
+}
+{
+"origin" "1056 -416 1000"
+"targetname" "t101"
+"target" "t100"
+"item" "ammo_bullets"
+"angle" "0"
+"spawnflags" "3841"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "1392 -548 992"
+"targetname" "t101"
+"target" "t97"
+"spawnflags" "3841"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"origin" "776 -1440 864"
+"spawnflags" "6400"
+"classname" "monster_gunner"
+"item" "ammo_bullets"
+}
+{
+"origin" "1568 -1256 1224"
+"angle" "180"
+"spawnflags" "3841"
+"classname" "monster_daedalus"
+"item" "ammo_cells"
+}
+{
+"origin" "1496 -1808 1144"
+"angle" "90"
+"spawnflags" "6401"
+"classname" "monster_daedalus"
+"item" "ammo_cells"
+}
+{
+"origin" "1632 -1488 1048"
+"spawnflags" "5120"
+"classname" "ammo_slugs"
+}
+{
+"item" "ammo_slugs"
+"origin" "1072 -1200 1056"
+"target" "t93"
+"spawnflags" "3841"
+"classname" "monster_gladiator"
+}
+{
+"origin" "1768 -1064 1056"
+"targetname" "t88"
+"target" "t87"
+"item" "ammo_grenades"
+"spawnflags" "3841"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"angle" "90"
+"classname" "monster_gunner"
+"spawnflags" "6912"
+"item" "ammo_bullets"
+"origin" "2488 -992 1056"
+}
+{
+"angle" "90"
+"origin" "2504 -992 1056"
+"item" "ammo_bullets"
+"spawnflags" "3841"
+"classname" "monster_gunner"
+}
+{
+"origin" "2504 -1096 1048"
+"targetname" "t88"
+"target" "t86"
+"item" "ammo_cells"
+"angle" "180"
+"spawnflags" "3841"
+"classname" "monster_medic"
+}
+{
+"targetname" "t160"
+"classname" "monster_turret"
+"spawnflags" "2977"
+"angle" "-2"
+"origin" "2528 -160 1316"
+}
+{
+"targetname" "t160"
+"classname" "monster_turret"
+"spawnflags" "6553"
+"angle" "-2"
+"origin" "2400 352 1316"
+}
+{
+"target" "t391"
+"origin" "2400 -104 1176"
+"item" "ammo_cells"
+"angle" "45"
+"spawnflags" "3841"
+"classname" "monster_medic"
+}
+{
+"origin" "2268 -556 1066"
+"angle" "180"
+"targetname" "t80"
+"target" "t81"
+"spawnflags" "3849"
+"classname" "monster_stalker"
+}
+{
+"origin" "2280 -680 1048"
+"angle" "135"
+"spawnflags" "2049"
+"classname" "monster_flyer"
+}
+{
+"origin" "2168 -600 864"
+"target" "t79"
+"item" "ammo_grenades"
+"spawnflags" "3841"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"origin" "3444 64 1178"
+"spawnflags" "6913"
+"angle" "180"
+"classname" "monster_stalker"
+}
+{
+"origin" "3512 -496 1264"
+"spawnflags" "6913"
+"angle" "90"
+"classname" "monster_daedalus"
+}
+{
+"origin" "3308 -404 1010"
+"target" "t163"
+"spawnflags" "3849"
+"angle" "0"
+"classname" "monster_stalker"
+}
+{
+"classname" "monster_gunner"
+"spawnflags" "6913"
+"angle" "225"
+"origin" "3608 -544 928"
+"item" "ammo_bullets"
+}
+{
+"classname" "monster_soldier_ss"
+"spawnflags" "6913"
+"angle" "0"
+"origin" "2912 -936 928"
+"item" "ammo_bullets"
+}
+{
+"targetname" "t123"
+"classname" "monster_turret"
+"spawnflags" "4001"
+"angle" "-2"
+"origin" "3104 -928 1060"
+}
+{
+"classname" "monster_daedalus"
+"angle" "45"
+"spawnflags" "6913"
+"origin" "3096 -1848 1208"
+"item" "ammo_cells"
+}
+{
+"targetname" "t31"
+"origin" "3280 -1528 1208"
+"spawnflags" "3843"
+"angle" "225"
+"classname" "monster_daedalus"
+}
+{
+"angle" "180"
+"origin" "3288 -1440 1184"
+"spawnflags" "6913"
+"classname" "monster_hover"
+}
+{
+"origin" "3496 -1048 928"
+"targetname" "t120"
+"target" "t121"
+"item" "ammo_bullets"
+"spawnflags" "6913"
+"classname" "monster_gunner"
+}
+{
+"target" "t371"
+"targetname" "t370"
+"spawnflags" "2048"
+"classname" "hint_path"
+"origin" "512 208 1232"
+}
+{
+"targetname" "t371"
+"spawnflags" "2049"
+"classname" "hint_path"
+"origin" "744 208 1232"
+}
+{
+"target" "t392"
+"classname" "trigger_relay"
+"targetname" "t28"
+"spawnflags" "2048"
+"origin" "2840 -2440 920"
+}
+{
+"target" "t392"
+"classname" "trigger_relay"
+"targetname" "t28"
+"spawnflags" "2048"
+"delay" "12"
+"origin" "2840 -2488 920"
+}
+{
+"origin" "2872 -600 1184"
+"item" "ammo_grenades"
+"targetname" "t246"
+"target" "t247"
+"angle" "270"
+"spawnflags" "7681"
+"classname" "monster_gunner"
+}
+{
+"origin" "2888 -600 1184"
+"targetname" "t246"
+"target" "t247"
+"angle" "270"
+"spawnflags" "7425"
+"item" "ammo_cells"
+"classname" "monster_medic"
+}
+{
+"origin" "2920 -2200 1176"
+"classname" "ammo_prox"
+}
+{
+"origin" "2656 -2072 1240"
+"classname" "ammo_tesla"
+}
+{
+"origin" "2200 -1880 1176"
+"classname" "item_health"
+}
+{
+"origin" "2536 -1752 1176"
+"classname" "item_health_large"
+}
+{
+"origin" "2704 -328 1296"
+"targetname" "t136"
+"classname" "target_goal"
+"spawnflags" "2048"
+}
+{
+"origin" "2456 -2400 976"
+"targetname" "t31"
+"classname" "target_goal"
+"spawnflags" "2048"
+}
+{
+"origin" "1664 -2408 1184"
+"targetname" "t250"
+"classname" "target_goal"
+"spawnflags" "2048"
+}
+{
+"origin" "1496 -216 1384"
+"spawnflags" "2048"
+"targetname" "t364"
+"classname" "target_goal"
+}
+{
+"classname" "ammo_bullets"
+"origin" "2264 -2576 1048"
+}
+{
+"classname" "ammo_grenades"
+"origin" "2344 -2576 1048"
+}
+{
+"classname" "item_health_large"
+"origin" "1880 -2792 1048"
+}
+{
+"classname" "item_doppleganger"
+"spawnflags" "5888"
+"origin" "1888 -2848 1048"
+}
+{
+"classname" "item_quad"
+"spawnflags" "2048"
+"origin" "1888 -2848 1048"
+}
+{
+"classname" "item_health_small"
+"origin" "2016 -2544 1112"
+}
+{
+"classname" "item_health_small"
+"origin" "2016 -2600 1112"
+}
+{
+"classname" "item_health_small"
+"origin" "2016 -2656 1112"
+}
+{
+"classname" "item_health_small"
+"origin" "2016 -2496 1112"
+}
+{
+"classname" "item_health_large"
+"origin" "1496 -2392 1128"
+}
+{
+"classname" "item_health"
+"origin" "280 -2336 984"
+}
+{
+"classname" "item_health"
+"origin" "280 -2016 984"
+}
+{
+"origin" "656 -2024 984"
+"classname" "ammo_shells"
+}
+{
+"classname" "ammo_shells"
+"origin" "592 -2016 984"
+}
+{
+"classname" "ammo_bullets"
+"origin" "168 -1880 920"
+}
+{
+"classname" "item_health_small"
+"origin" "-144 -2040 984"
+}
+{
+"classname" "item_health_small"
+"origin" "-96 -2040 984"
+}
+{
+"classname" "item_health_small"
+"origin" "-192 -2056 984"
+}
+{
+"classname" "ammo_prox"
+"origin" "-192 -2224 984"
+}
+{
+"classname" "weapon_proxlauncher"
+"origin" "-152 -2384 984"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2050"
+"origin" "-168 -2344 960"
+}
+{
+"classname" "item_health"
+"origin" "168 -2856 920"
+}
+{
+"classname" "ammo_slugs"
+"origin" "-488 -2584 784"
+}
+{
+"classname" "ammo_prox"
+"origin" "-552 -1976 792"
+}
+{
+"classname" "item_health_small"
+"origin" "-24 -2008 784"
+}
+{
+"classname" "item_sphere_hunter"
+"spawnflags" "5888"
+"origin" "-32 -2720 792"
+}
+{
+"classname" "ammo_tesla"
+"origin" "-80 -2720 792"
+}
+{
+"classname" "item_health_large"
+"origin" "-544 -2816 856"
+}
+{
+"classname" "item_armor_combat"
+"origin" "-480 -2848 920"
+"spawnflags" "2048"
+}
+{
+"origin" "2380 -1916 1218"
+"angle" "180"
+"spawnflags" "3849"
+"classname" "monster_stalker"
+}
+{
+"origin" "2840 -2584 1176"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2840 -2400 1176"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2840 -2344 1176"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2840 -2528 1176"
+"classname" "item_armor_shard"
+}
+{
+"origin" "3152 -2976 1240"
+"classname" "ammo_prox"
+}
+{
+"origin" "3240 -2920 1168"
+"classname" "item_health"
+}
+{
+"origin" "416 112 1376"
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "96 -128 1400"
+}
+{
+"model" "*13"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"light" "50"
+"classname" "light"
+"origin" "2032 -544 992"
+}
+{
+"classname" "light"
+"light" "50"
+"origin" "1936 -352 1248"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t367"
+"target" "t368"
+"delay" "1"
+"origin" "1984 -448 1248"
+}
+{
+"model" "*14"
+"message" "Lift activated."
+"wait" "2"
+"angle" "0"
+"classname" "func_button"
+"target" "t367"
+}
+{
+"model" "*15"
+"classname" "func_button"
+"angle" "180"
+"wait" "2"
+"message" "Lift activated."
+"target" "t367"
+}
+{
+"delay" "1"
+"origin" "3400 64 936"
+"target" "t366"
+"targetname" "t360"
+"classname" "trigger_relay"
+}
+{
+"origin" "2416 -936 864"
+"target" "t365"
+"delay" "1"
+"targetname" "t359"
+"classname" "trigger_relay"
+}
+{
+"origin" "1376 -96 984"
+"classname" "ammo_flechettes"
+}
+{
+"model" "*16"
+"target" "t362"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "1248 -432 1000"
+"angle" "315"
+"spawnflags" "2049"
+"targetname" "t101"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "1304 -296 1304"
+"classname" "item_health_large"
+}
+{
+"origin" "1176 296 1240"
+"classname" "ammo_cells"
+}
+{
+"origin" "840 112 1232"
+"targetname" "t361"
+"spawnflags" "2048"
+"classname" "point_combat"
+}
+{
+"origin" "800 -608 1304"
+"classname" "item_armor_combat"
+}
+{
+"model" "*17"
+"message" "Lift activated."
+"target" "t360"
+"classname" "func_button"
+"angle" "0"
+"_minlight" ".3"
+"wait" "2"
+}
+{
+"origin" "2416 -992 864"
+"classname" "light"
+"light" "50"
+}
+{
+"origin" "2448 -992 1056"
+"light" "50"
+"classname" "light"
+}
+{
+"model" "*18"
+"message" "Lift activated."
+"target" "t359"
+"classname" "func_button"
+"angle" "180"
+"_minlight" ".3"
+"wait" "2"
+}
+{
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_soldier"
+"origin" "2520 -896 1056"
+}
+{
+"model" "*19"
+"message" "Lift activated."
+"target" "t359"
+"classname" "func_button"
+"angle" "0"
+"_minlight" ".3"
+"wait" "2"
+}
+{
+"model" "*20"
+"message" "Lift activated."
+"target" "t360"
+"wait" "2"
+"_minlight" ".3"
+"angle" "270"
+"classname" "func_button"
+}
+{
+"origin" "1880 -856 864"
+"spawnflags" "7680"
+"classname" "ammo_rockets"
+}
+{
+"origin" "2072 -792 856"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "1560 -1440 600"
+"classname" "ammo_tesla"
+}
+{
+"classname" "item_health"
+"origin" "2296 -1472 616"
+}
+{
+"origin" "2248 -1160 600"
+"classname" "ammo_cells"
+}
+{
+"origin" "2048 -1168 600"
+"classname" "ammo_prox"
+}
+{
+"origin" "2080 -984 600"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1848 -1472 616"
+"classname" "item_health"
+}
+{
+"origin" "2016 -1088 600"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2208 -984 600"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2280 -1088 600"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2144 -1472 616"
+"classname" "item_armor_combat"
+}
+{
+"origin" "2584 -552 1176"
+"classname" "item_health"
+}
+{
+"origin" "2672 -512 1168"
+"targetname" "t358"
+"angle" "0"
+"classname" "point_combat"
+"spawnflags" "2049"
+}
+{
+"origin" "2944 -528 1168"
+"target" "t358"
+"wait" "60"
+"targetname" "t357"
+"classname" "point_combat"
+"spawnflags" "2048"
+}
+{
+"origin" "2776 -360 1176"
+"classname" "ammo_slugs"
+}
+{
+"origin" "2752 -320 1280"
+"light" "115"
+"classname" "light"
+}
+{
+"origin" "2792 -280 1168"
+"classname" "item_health"
+}
+{
+"origin" "2412 -324 1258"
+"angle" "0"
+"spawnflags" "2057"
+"item" "ammo_cells"
+"classname" "monster_stalker"
+}
+{
+"origin" "3104 -616 1176"
+"classname" "item_armor_jacket"
+}
+{
+"origin" "3104 -992 1176"
+"classname" "ammo_prox"
+}
+{
+"origin" "2784 -864 1184"
+"angle" "45"
+"targetname" "t247"
+"item" "ammo_shells"
+"spawnflags" "7169"
+"classname" "monster_soldier"
+}
+{
+"origin" "1984 -16 1232"
+"target" "t356"
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t386"
+}
+{
+"origin" "2008 224 1232"
+"spawnflags" "2048"
+"classname" "hint_path"
+"target" "t386"
+"targetname" "t387"
+}
+{
+"origin" "1664 96 1232"
+"target" "t354"
+"targetname" "t353"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1672 208 1232"
+"target" "t353"
+"targetname" "t352"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1584 216 1232"
+"target" "t352"
+"targetname" "t351"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1584 376 1232"
+"target" "t351"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1984 -344 1232"
+"targetname" "t356"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1504 416 1232"
+"targetname" "t350"
+"classname" "hint_path"
+"spawnflags" "2048"
+"target" "t388"
+}
+{
+"origin" "1048 416 1232"
+"target" "t350"
+"targetname" "t349"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "864 256 1232"
+"target" "t349"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t370"
+"origin" "472 -280 1232"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1928 96 1232"
+"targetname" "t354"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "800 -192 1232"
+"target" "t348"
+"targetname" "t347"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "816 -536 1232"
+"target" "t347"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "800 208 1232"
+"targetname" "t348"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "896 -536 1232"
+"targetname" "t343"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "1024 -480 1232"
+"target" "t343"
+"targetname" "t342"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1120 -344 1232"
+"target" "t342"
+"targetname" "t341"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1088 -96 1232"
+"target" "t341"
+"targetname" "t340"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1040 96 1232"
+"target" "t340"
+"targetname" "t339"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1216 96 1232"
+"target" "t339"
+"targetname" "t338"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1216 216 1232"
+"target" "t338"
+"targetname" "t337"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1384 216 1232"
+"target" "t337"
+"targetname" "t336"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1408 -200 1296"
+"target" "t336"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"targetname" "t335"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "1504 112 976"
+}
+{
+"targetname" "t334"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "1504 160 976"
+}
+{
+"target" "t334"
+"targetname" "t333"
+"origin" "1696 160 976"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t333"
+"origin" "1784 288 976"
+"targetname" "t332"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1984 160 976"
+"target" "t332"
+"targetname" "t331"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1856 0 976"
+"target" "t331"
+"targetname" "t330"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1664 -256 976"
+"target" "t330"
+"targetname" "t329"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1720 -544 976"
+"target" "t329"
+"targetname" "t328"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1984 -544 976"
+"target" "t328"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t335"
+"origin" "1496 -72 976"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "1152 -320 976"
+"target" "t327"
+"targetname" "t326"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1328 -480 976"
+"target" "t326"
+"targetname" "t325"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1536 -568 976"
+"target" "t325"
+"targetname" "t324"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1536 -816 1024"
+"target" "t324"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "984 -192 976"
+"targetname" "t327"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t320"
+"target" "t321"
+"origin" "-360 -1920 784"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t321"
+"target" "t322"
+"origin" "128 -1928 912"
+}
+{
+"spawnflags" "2048"
+"classname" "hint_path"
+"targetname" "t322"
+"target" "t323"
+"origin" "128 -2160 976"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t315"
+"target" "t316"
+"origin" "-352 -2816 784"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t316"
+"target" "t317"
+"origin" "128 -2816 912"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t317"
+"target" "t318"
+"origin" "128 -2368 976"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2048"
+"targetname" "t318"
+"target" "t319"
+"origin" "128 -2192 976"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"targetname" "t323"
+"origin" "312 -2152 976"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"target" "t315"
+"origin" "-352 -2464 784"
+}
+{
+"spawnflags" "2049"
+"classname" "hint_path"
+"targetname" "t319"
+"origin" "312 -2200 976"
+}
+{
+"classname" "hint_path"
+"spawnflags" "2049"
+"target" "t320"
+"origin" "-456 -2136 784"
+}
+{
+"origin" "504 -2304 960"
+"target" "t312"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "888 -2520 1072"
+"target" "t314"
+"targetname" "t313"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1216 -2480 1112"
+"targetname" "t314"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "712 -2544 1032"
+"target" "t313"
+"targetname" "t312"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "1568 -2176 1112"
+"target" "t310"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "1280 -2176 1112"
+"target" "t311"
+"spawnflags" "2048"
+"targetname" "t310"
+"classname" "hint_path"
+}
+{
+"origin" "1280 -2448 1112"
+"targetname" "t311"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t308"
+"targetname" "t307"
+"origin" "1280 -2752 1112"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t307"
+"targetname" "t306"
+"origin" "1536 -2752 1112"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t306"
+"targetname" "t305"
+"origin" "1536 -2432 1112"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"targetname" "t308"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "1280 -2528 1112"
+}
+{
+"target" "t304"
+"targetname" "t303"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1920 -2752 1040"
+}
+{
+"target" "t303"
+"targetname" "t302"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2304 -2752 1040"
+}
+{
+"target" "t302"
+"origin" "2624 -2752 1040"
+"targetname" "t301"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "3104 -2752 1040"
+"target" "t301"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t305"
+"targetname" "t304"
+"origin" "1944 -2472 1104"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"origin" "2944 -2944 1168"
+"target" "t299"
+"targetname" "t298"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "2880 -2736 1168"
+"target" "t300"
+"targetname" "t299"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "2880 -2240 1168"
+"targetname" "t300"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "3200 -2832 1168"
+"target" "t298"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t297"
+"targetname" "t296"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2240 -2104 1168"
+}
+{
+"target" "t296"
+"targetname" "t295"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2240 -1912 1168"
+}
+{
+"target" "t295"
+"targetname" "t294"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2520 -1856 1168"
+}
+{
+"target" "t294"
+"targetname" "t293"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2816 -1800 1168"
+}
+{
+"target" "t293"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "2816 -1472 1168"
+}
+{
+"target" "t292"
+"targetname" "t291"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1024 -1832 880"
+}
+{
+"target" "t291"
+"targetname" "t290"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1320 -1896 944"
+}
+{
+"target" "t290"
+"targetname" "t289"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1600 -1648 1040"
+}
+{
+"target" "t289"
+"targetname" "t288"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1584 -1256 1040"
+}
+{
+"target" "t288"
+"targetname" "t287"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "1120 -1224 1040"
+}
+{
+"target" "t287"
+"origin" "1120 -904 1040"
+"targetname" "t286"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1536 -856 1040"
+"target" "t286"
+"targetname" "t285"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1792 -904 1040"
+"target" "t285"
+"targetname" "t284"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "1792 -1096 1040"
+"target" "t284"
+"targetname" "t283"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "2152 -1048 1040"
+"target" "t283"
+"targetname" "t282"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "2496 -1088 1040"
+"target" "t282"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"targetname" "t292"
+"origin" "1184 -1616 848"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "3584 -1344 848"
+"targetname" "t281"
+"spawnflags" "1"
+"classname" "hint_path"
+}
+{
+"targetname" "t280"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "2816 -1408 1168"
+}
+{
+"target" "t280"
+"targetname" "t279"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "2816 -1136 1168"
+}
+{
+"target" "t279"
+"targetname" "t278"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "3008 -936 1168"
+}
+{
+"target" "t278"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "2880 -640 1168"
+}
+{
+"origin" "2400 -896 848"
+"target" "t277"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "2208 -768 848"
+"targetname" "t277"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "2272 -800 1128"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "2176 -640 848"
+"targetname" "t276"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"origin" "2592 -640 912"
+"target" "t276"
+"targetname" "t275"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"origin" "2880 -640 912"
+"target" "t275"
+"classname" "hint_path"
+"spawnflags" "2049"
+}
+{
+"target" "t274"
+"targetname" "t273"
+"origin" "2208 -256 864"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t273"
+"targetname" "t272"
+"origin" "2752 -256 912"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t272"
+"targetname" "t271"
+"origin" "2680 488 1040"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t271"
+"targetname" "t270"
+"origin" "2888 496 1040"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t270"
+"targetname" "t269"
+"origin" "2816 -64 1168"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t269"
+"targetname" "t268"
+"origin" "2432 -64 1168"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t268"
+"targetname" "t267"
+"origin" "2432 -320 1168"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t267"
+"targetname" "t266"
+"origin" "2624 -320 1168"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t266"
+"targetname" "t265"
+"origin" "2624 -512 1168"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t265"
+"targetname" "t264"
+"origin" "2880 -512 1168"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"targetname" "t274"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "2176 -576 864"
+}
+{
+"target" "t264"
+"targetname" "t263"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "3328 -512 1168"
+}
+{
+"targetname" "t297"
+"origin" "2656 -2248 1168"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t263"
+"origin" "3328 -40 1168"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"target" "t261"
+"targetname" "t260"
+"origin" "3560 -272 912"
+"spawnflags" "2048"
+"classname" "hint_path"
+}
+{
+"target" "t260"
+"targetname" "t259"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "3584 64 912"
+}
+{
+"target" "t262"
+"targetname" "t261"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "3448 -632 912"
+}
+{
+"targetname" "t262"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "3200 -640 912"
+}
+{
+"origin" "3176 -608 1184"
+"light" "50"
+"classname" "light"
+}
+{
+"target" "t256"
+"classname" "hint_path"
+"spawnflags" "2049"
+"origin" "3008 -640 912"
+}
+{
+"target" "t257"
+"targetname" "t256"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "3008 -896 912"
+}
+{
+"target" "t258"
+"targetname" "t257"
+"classname" "hint_path"
+"spawnflags" "2048"
+"origin" "3256 -896 912"
+}
+{
+"target" "t281"
+"targetname" "t258"
+"origin" "3520 -896 912"
+"classname" "hint_path"
+"spawnflags" "2048"
+}
+{
+"target" "t259"
+"origin" "3416 64 912"
+"spawnflags" "2049"
+"classname" "hint_path"
+}
+{
+"origin" "1048 -856 1048"
+"classname" "ammo_flechettes"
+}
+{
+"origin" "1608 -1232 1048"
+"classname" "item_health"
+}
+{
+"origin" "1456 -1120 1048"
+"classname" "item_health_large"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "552 -288 1416"
+}
+{
+"origin" "3624 -800 920"
+"classname" "ammo_grenades"
+}
+{
+"_color" "1.000000 1.000000 0.000000"
+"origin" "1408 128 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1120 -1040 1184"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "3400 -896 1056"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-840 -2400 800"
+"classname" "info_player_coop"
+"angle" "0"
+"targetname" "rw2b"
+"spawnflags" "2048"
+}
+{
+"origin" "-840 -2336 800"
+"angle" "0"
+"classname" "info_player_coop"
+"targetname" "rw2b"
+"spawnflags" "2048"
+}
+{
+"origin" "-768 -2336 800"
+"classname" "info_player_coop"
+"angle" "0"
+"targetname" "rw2b"
+"spawnflags" "2048"
+}
+{
+"origin" "-768 -2400 800"
+"angle" "0"
+"classname" "info_player_coop"
+"targetname" "rw2b"
+"spawnflags" "2048"
+}
+{
+"origin" "-704 -2368 840"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-864 -2368 840"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-80 -88 1376"
+"classname" "info_player_coop"
+"angle" "0"
+"targetname" "rw2a"
+"spawnflags" "2048"
+}
+{
+"origin" "-136 -72 1376"
+"classname" "info_player_coop"
+"angle" "0"
+"targetname" "rw2a"
+"spawnflags" "2048"
+}
+{
+"origin" "-128 -184 1376"
+"classname" "info_player_coop"
+"angle" "0"
+"targetname" "rw2a"
+"spawnflags" "2048"
+}
+{
+"origin" "-72 -200 1376"
+"angle" "0"
+"classname" "info_player_coop"
+"targetname" "rw2a"
+"spawnflags" "2048"
+}
+{
+"angle" "90"
+"classname" "info_player_coop"
+"origin" "3524 -1952 888"
+"targetname" "rw2"
+"spawnflags" "2048"
+}
+{
+"classname" "info_player_coop"
+"angle" "90"
+"origin" "3452 -1904 880"
+"targetname" "rw2"
+"spawnflags" "2048"
+}
+{
+"angle" "90"
+"classname" "info_player_coop"
+"origin" "3524 -2048 896"
+"targetname" "rw2"
+"spawnflags" "2048"
+}
+{
+"classname" "info_player_coop"
+"angle" "90"
+"origin" "3452 -2000 896"
+"targetname" "rw2"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "3488 -1936 952"
+}
+{
+"origin" "3488 -2080 952"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1320 -2624 1128"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1320 -2576 1128"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1320 -2672 1128"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1248 -2136 1120"
+"classname" "ammo_rockets"
+}
+{
+"origin" "1520 -2480 1152"
+"killtarget" "t254"
+"spawnflags" "2048"
+"targetname" "t255"
+"classname" "trigger_relay"
+}
+{
+"origin" "1512 -2424 1152"
+"target" "t255"
+"killtarget" "nogood"
+"spawnflags" "2052"
+"classname" "target_crosslevel_target"
+}
+{
+"angle" "90"
+"targetname" "t254"
+"origin" "1448 -2928 1120"
+"spawnflags" "2051"
+"classname" "monster_gunner"
+}
+{
+"angle" "90"
+"targetname" "t254"
+"origin" "1392 -2928 1120"
+"spawnflags" "2051"
+"classname" "monster_gunner"
+}
+{
+"model" "*21"
+"message" "Humanoid life form detected."
+"targetname" "nogood"
+"target" "t254"
+"classname" "trigger_once"
+}
+{
+"origin" "1664 -2440 1184"
+"item" "key_commander_head"
+"target" "t250"
+"spawnflags" "2048"
+"targetname" "t249"
+"classname" "trigger_key"
+}
+{
+"model" "*22"
+"target" "t249"
+"spawnflags" "2048"
+"classname" "trigger_multiple"
+}
+{
+"targetname" "t364"
+"classname" "target_help"
+"spawnflags" "2048"
+"origin" "1448 -256 1320"
+"message" "Use key card to\n open cargo doors."
+}
+{
+"target" "t364"
+"classname" "key_blue_key"
+"origin" "1488 -160 1316"
+"spawnflags" "2048"
+}
+{
+"model" "*23"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "t248"
+}
+{
+"classname" "light"
+"light" "60"
+"origin" "2496 552 1072"
+}
+{
+"origin" "480 -288 1456"
+"light" "50"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "384 -288 1416"
+}
+{
+"origin" "552 -96 1416"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "384 -96 1416"
+}
+{
+"origin" "384 -480 1416"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "448 -192 1464"
+"light" "120"
+"classname" "light"
+}
+{
+"origin" "1304 424 1232"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1376 424 1232"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1240 424 1232"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1504 344 1232"
+"classname" "item_health"
+}
+{
+"origin" "1624 88 1240"
+"classname" "item_health_large"
+}
+{
+"origin" "3032 -472 1168"
+"classname" "item_health_small"
+}
+{
+"origin" "2984 -472 1168"
+"classname" "item_health_small"
+}
+{
+"origin" "2936 -472 1168"
+"classname" "item_health_small"
+}
+{
+"origin" "3072 -472 1168"
+"classname" "item_health_small"
+}
+{
+"origin" "3160 -608 1176"
+"spawnflags" "2049"
+"angle" "135"
+"classname" "monster_soldier"
+}
+{
+"origin" "3032 -608 1184"
+"angle" "90"
+"spawnflags" "2049"
+"classname" "monster_berserk"
+}
+{
+"origin" "3136 -512 1208"
+"classname" "light"
+"light" "115"
+}
+{
+"origin" "2856 -872 1176"
+"angle" "90"
+"spawnflags" "2305"
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "2712 -1128 1168"
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+}
+{
+"origin" "2712 -984 1168"
+"classname" "item_health_large"
+}
+{
+"origin" "3168 -784 1168"
+"classname" "item_armor_shard"
+}
+{
+"origin" "3168 -728 1168"
+"classname" "item_armor_shard"
+}
+{
+"origin" "3112 -728 1168"
+"classname" "item_armor_shard"
+}
+{
+"origin" "3168 -864 1168"
+"classname" "item_health_large"
+}
+{
+"item" "ammo_grenades"
+"targetname" "t247"
+"angle" "45"
+"origin" "2784 -824 1184"
+"spawnflags" "6913"
+"classname" "monster_gunner"
+}
+{
+"target" "t247"
+"item" "ammo_cells"
+"targetname" "t246"
+"origin" "2880 -608 1184"
+"spawnflags" "6913"
+"angle" "270"
+"classname" "monster_medic_commander"
+}
+{
+"origin" "2936 -840 1280"
+"classname" "light"
+"light" "70"
+}
+{
+"origin" "2936 -760 1280"
+"light" "70"
+"classname" "light"
+}
+{
+"origin" "3144 -760 1280"
+"classname" "light"
+"light" "70"
+}
+{
+"origin" "3040 -800 1280"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "3144 -840 1280"
+"light" "70"
+"classname" "light"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"classname" "light"
+"origin" "2768 -832 1208"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "2816 -832 1208"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "2816 -704 1208"
+}
+{
+"origin" "3184 -800 1232"
+"classname" "light"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+}
+{
+"target" "t245"
+"targetname" "t244"
+"origin" "2880 472 1040"
+"classname" "point_combat"
+"spawnflags" "2048"
+}
+{
+"target" "t244"
+"targetname" "t243"
+"origin" "2904 176 1168"
+"classname" "point_combat"
+"spawnflags" "2048"
+}
+{
+"targetname" "t245"
+"origin" "2760 472 1040"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"target" "t391"
+"origin" "2400 -88 1176"
+"angle" "45"
+"spawnflags" "7169"
+"item" "ammo_shells"
+"classname" "monster_soldier"
+}
+{
+"target" "t391"
+"item" "ammo_cells"
+"angle" "45"
+"origin" "2400 -112 1176"
+"spawnflags" "6913"
+"classname" "monster_medic"
+}
+{
+"target" "t243"
+"origin" "2896 16 1184"
+"spawnflags" "2049"
+"angle" "135"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "2792 360 1040"
+"classname" "ammo_cells"
+}
+{
+"target" "t72"
+"spawnflags" "6913"
+"origin" "-120 -2512 992"
+"classname" "monster_gladiator"
+"item" "ammo_slugs"
+}
+{
+"origin" "32 -2824 928"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_soldier"
+}
+{
+"origin" "2008 -800 1048"
+"classname" "ammo_grenades"
+}
+{
+"origin" "1952 -800 1048"
+"classname" "ammo_rockets"
+}
+{
+"origin" "1000 -600 1056"
+"classname" "monster_soldier"
+"angle" "180"
+"spawnflags" "2049"
+}
+{
+"targetname" "t362"
+"origin" "1184 96 1056"
+"spawnflags" "2051"
+"angle" "90"
+"classname" "monster_soldier"
+}
+{
+"origin" "872 88 1248"
+"spawnflags" "2049"
+"angle" "90"
+"classname" "monster_berserk"
+}
+{
+"classname" "item_health_large"
+"origin" "992 160 1304"
+}
+{
+"classname" "item_armor_shard"
+"origin" "992 52 1240"
+}
+{
+"classname" "item_armor_shard"
+"origin" "992 96 1240"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "0"
+"spawnflags" "2051"
+"targetname" "t198"
+"origin" "1048 -168 1248"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"target" "t240"
+"targetname" "t242"
+"origin" "1568 224 1240"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t241"
+"target" "t242"
+"origin" "1568 224 1392"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2048"
+"targetname" "t240"
+"origin" "1568 400 1240"
+}
+{
+"targetname" "t363"
+"classname" "monster_stalker"
+"spawnflags" "6921"
+"angle" "270"
+"origin" "1188 284 1378"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t237"
+"target" "t238"
+"origin" "1048 112 1384"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t236"
+"target" "t237"
+"origin" "1152 -152 1384"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t235"
+"target" "t236"
+"origin" "1144 -408 1384"
+}
+{
+"spawnflags" "2048"
+"classname" "point_combat"
+"targetname" "t234"
+"target" "t235"
+"origin" "1056 -408 1384"
+}
+{
+"target" "t361"
+"classname" "point_combat"
+"spawnflags" "2048"
+"targetname" "t238"
+"origin" "840 112 1384"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_stalker"
+"spawnflags" "2057"
+"angle" "135"
+"target" "t234"
+"targetname" "t198"
+"origin" "1052 -668 1386"
+}
+{
+"targetname" "t140"
+"item" "ammo_cells"
+"classname" "monster_daedalus"
+"spawnflags" "3843"
+"angle" "45"
+"origin" "916 228 1264"
+}
+{
+"targetname" "t140"
+"item" "ammo_cells"
+"classname" "monster_hover"
+"spawnflags" "7171"
+"angle" "45"
+"origin" "908 224 1264"
+}
+{
+"classname" "point_combat"
+"wait" "10"
+"targetname" "t232"
+"origin" "1000 304 1224"
+"spawnflags" "2048"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+"angle" "315"
+"spawnflags" "2049"
+"origin" "920 424 1240"
+"target" "t232"
+}
+{
+"classname" "point_combat"
+"origin" "1560 352 1224"
+"spawnflags" "2049"
+"angle" "315"
+"targetname" "t229"
+}
+{
+"classname" "point_combat"
+"targetname" "t228"
+"origin" "1600 224 1224"
+"target" "t229"
+"spawnflags" "2048"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t227"
+"origin" "1920 288 1224"
+}
+{
+"angle" "315"
+"classname" "monster_soldier_ss"
+"targetname" "uknow"
+"spawnflags" "7171"
+"target" "t227"
+"origin" "1736 416 1240"
+}
+{
+"angle" "315"
+"classname" "monster_gunner"
+"targetname" "uknow"
+"spawnflags" "6913"
+"target" "t227"
+"origin" "1784 416 1248"
+}
+{
+"classname" "point_combat"
+"origin" "1992 296 1224"
+"targetname" "t225"
+"wait" "2.5"
+"angle" "270"
+"spawnflags" "2048"
+}
+{
+"classname" "monster_soldier"
+"angle" "315"
+"spawnflags" "2051"
+"origin" "1896 288 1248"
+"target" "t225"
+"targetname" "t223"
+}
+{
+"model" "*24"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t223"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_gunner"
+"angle" "315"
+"spawnflags" "3841"
+"origin" "1920 -152 1240"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2048"
+"angle" "0"
+"origin" "1720 96 1224"
+"target" "t228"
+"wait" "5"
+"targetname" "t385"
+}
+{
+"deathtarget" "uknow"
+"spawnflags" "2049"
+"item" "ammo_shells"
+"classname" "monster_soldier"
+"angle" "270"
+"origin" "2016 -64 1248"
+"targetname" "t213"
+"target" "t384"
+}
+{
+"classname" "monster_soldier"
+"angle" "225"
+"spawnflags" "2049"
+"item" "ammo_shells"
+"origin" "1704 232 1248"
+}
+{
+"item" "ammo_shells"
+"classname" "monster_soldier"
+"spawnflags" "7169"
+"target" "t122"
+"angle" "0"
+"origin" "2912 -912 928"
+}
+{
+"classname" "monster_soldier"
+"item" "ammo_shells"
+"spawnflags" "7683"
+"target" "t218"
+"targetname" "t383"
+"origin" "1880 -680 992"
+}
+{
+"classname" "ammo_prox"
+"origin" "1760 -672 1048"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t218"
+"origin" "1776 -592 968"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"target" "t218"
+"spawnflags" "6403"
+"angle" "135"
+"targetname" "t383"
+"origin" "1880 -672 992"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t217"
+"origin" "2016 -576 976"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+"spawnflags" "2051"
+"angle" "45"
+"target" "t217"
+"origin" "1624 -488 984"
+"targetname" "t383"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2056"
+"angle" "90"
+"origin" "1688 -96 960"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2054"
+"angle" "315"
+"origin" "1632 -312 960"
+}
+{
+"classname" "ammo_rockets"
+"origin" "1696 8 992"
+"targetname" "t119" // added to silence warning
+}
+{
+"classname" "item_health"
+"origin" "1376 -224 992"
+}
+{
+"classname" "item_health"
+"origin" "1504 -360 992"
+}
+{
+"model" "*25"
+"classname" "func_wall"
+"spawnflags" "3584"
+}
+{
+"model" "*26"
+"classname" "func_wall"
+"spawnflags" "3072"
+}
+{
+"targetname" "t101"
+"classname" "monster_medic"
+"angle" "225"
+"spawnflags" "6915"
+"item" "ammo_cells"
+"origin" "1304 -160 992"
+}
+{
+"targetname" "t101"
+"classname" "monster_soldier"
+"angle" "270"
+"spawnflags" "7683"
+"origin" "1248 -96 992"
+}
+{
+"classname" "point_combat"
+"targetname" "t214"
+"spawnflags" "2049"
+"origin" "1312 -480 976"
+}
+{
+"angle" "0"
+"classname" "monster_soldier_ss"
+"spawnflags" "6913"
+"item" "ammo_bullets"
+"target" "t100"
+"targetname" "t101"
+"origin" "1056 -416 1000"
+}
+{
+"classname" "monster_soldier"
+"targetname" "t101"
+"target" "t98"
+"spawnflags" "3073"
+"origin" "1432 -616 992"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "315"
+"origin" "1904 -152 1240"
+"spawnflags" "7168"
+}
+{
+"item" "ammo_cells"
+"origin" "1824 -864 1048"
+"spawnflags" "2049"
+"angle" "225"
+"classname" "monster_medic"
+}
+{
+"origin" "2496 -1088 1048"
+"item" "ammo_cells"
+"targetname" "t88"
+"target" "t86"
+"angle" "180"
+"spawnflags" "6401"
+"classname" "monster_medic"
+}
+{
+"origin" "2496 -1104 1048"
+"targetname" "t88"
+"target" "t86"
+"spawnflags" "7681"
+"angle" "180"
+"classname" "monster_soldier"
+}
+{
+"origin" "1904 -872 864"
+"targetname" "t137"
+"angle" "0"
+"spawnflags" "3841"
+"classname" "monster_medic"
+}
+{
+"origin" "2184 -648 864"
+"spawnflags" "7169"
+"angle" "0"
+"target" "t79"
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "2176 -256 848"
+"targetname" "t212"
+"classname" "point_combat"
+"spawnflags" "2048"
+}
+{
+"origin" "2416 -264 928"
+"target" "t212"
+"targetname" "t211"
+"angle" "180"
+"spawnflags" "2049"
+"item" "ammo_shells"
+"classname" "monster_soldier"
+}
+{
+"item" "ammo_bullets"
+"origin" "1800 -1120 1056"
+"targetname" "t88"
+"target" "t87"
+"angle" "0"
+"spawnflags" "7681"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "2272 -800 1144"
+"spawnflags" "6409"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"model" "*27"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"origin" "480 -288 1476"
+"targetname" "t208"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*28"
+"targetname" "t154"
+"target" "t208"
+"spawnflags" "2048"
+"mass" "75"
+"classname" "func_explosive"
+}
+{
+"targetname" "t154"
+"origin" "480 -288 1508"
+"angle" "-2"
+"spawnflags" "2208"
+"classname" "monster_turret"
+}
+{
+"targetname" "t163"
+"target" "t390"
+"origin" "3344 -128 936"
+"spawnflags" "2049"
+"angle" "0"
+"classname" "monster_medic"
+}
+{
+"angle" "45"
+"origin" "3216 -1840 1184"
+"spawnflags" "7169"
+"classname" "monster_hover"
+}
+{
+"origin" "1528 -2592 1136"
+"angle" "270"
+"spawnflags" "2049"
+"classname" "monster_gunner"
+}
+{
+"origin" "1256 -2768 1112"
+"targetname" "t207"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "1288 -2512 1144"
+"target" "t207"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "880 -2496 1240"
+"spawnflags" "7169"
+"classname" "monster_hover"
+}
+{
+"origin" "896 -2536 1240"
+"spawnflags" "2817"
+"classname" "monster_daedalus"
+}
+{
+"origin" "1208 -2472 1128"
+"angle" "180"
+"spawnflags" "2049"
+"classname" "monster_gunner"
+}
+{
+"origin" "544 -2056 1112"
+"classname" "item_armor_combat"
+}
+{
+"origin" "-360 -1984 800"
+"item" "ammo_shells"
+"spawnflags" "2049"
+"angle" "225"
+"classname" "monster_soldier"
+}
+{
+"origin" "-384 -2816 784"
+"targetname" "t203"
+"classname" "point_combat"
+"spawnflags" "2048"
+}
+{
+"target" "t96"
+"item" "ammo_slugs"
+"origin" "408 -2312 992"
+"angle" "180"
+"spawnflags" "6913"
+"classname" "monster_gladiator"
+}
+{
+"targetname" "t204"
+"origin" "24 -1904 928"
+"target" "t202"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_medic_commander"
+"item" "ammo_cells"
+}
+{
+"origin" "-392 -1920 784"
+"targetname" "t202"
+"spawnflags" "2048"
+"classname" "point_combat"
+}
+{
+"origin" "1376 296 1240"
+"classname" "ammo_prox"
+}
+{
+"origin" "1440 224 1240"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1312 296 1240"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1440 168 1240"
+"classname" "item_armor_shard"
+}
+{
+"origin" "1248 104 1248"
+"angle" "180"
+"spawnflags" "3841"
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "1248 96 1248"
+"spawnflags" "7681"
+"angle" "180"
+"classname" "monster_soldier"
+}
+{
+"origin" "1372 220 1378"
+"angle" "270"
+"spawnflags" "2057"
+"classname" "monster_stalker"
+"targetname" "t239"
+"target" "t241"
+}
+{
+"origin" "1120 -24 1240"
+"spawnflags" "2048"
+"classname" "ammo_nails"
+}
+{
+"target" "t363"
+"origin" "1056 136 1248"
+"spawnflags" "3841"
+"angle" "270"
+"classname" "monster_berserk"
+}
+{
+"classname" "item_health_large"
+"origin" "928 -224 1304"
+}
+{
+"origin" "792 -288 1224"
+"spawnflags" "2048"
+"targetname" "t200"
+"classname" "point_combat"
+}
+{
+"item" "ammo_grenades"
+"origin" "920 -344 1248"
+"targetname" "getem'"
+"target" "t200"
+"spawnflags" "2051"
+"angle" "135"
+"classname" "monster_gunner"
+}
+{
+"origin" "968 -344 1248"
+"item" "ammo_shells"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_soldier"
+}
+{
+"origin" "992 -288 1304"
+"classname" "item_health_small"
+}
+{
+"origin" "928 96 1304"
+"classname" "ammo_prox"
+}
+{
+"origin" "792 -496 1232"
+"classname" "item_health_large"
+}
+{
+"origin" "880 -472 1224"
+"spawnflags" "2049"
+"targetname" "t199"
+"classname" "point_combat"
+}
+{
+"targetname" "getem'"
+"origin" "1000 -552 1240"
+"target" "t199"
+"spawnflags" "2051"
+"angle" "135"
+"classname" "monster_gunner"
+}
+{
+"model" "*29"
+"target" "t198"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"pathtarget" "getem'"
+"origin" "880 -80 1224"
+"angle" "180"
+"spawnflags" "2049"
+"targetname" "t197"
+"classname" "point_combat"
+}
+{
+"origin" "800 -96 1240"
+"target" "t197"
+"spawnflags" "2049"
+"angle" "90"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "664 -728 1232"
+"classname" "item_health_small"
+}
+{
+"origin" "600 -728 1232"
+"classname" "item_health_small"
+}
+{
+"origin" "720 -728 1232"
+"classname" "item_health_small"
+}
+{
+"item" "ammo_shells"
+"targetname" "t198"
+"origin" "856 -672 1248"
+"angle" "90"
+"spawnflags" "7171"
+"classname" "monster_soldier"
+}
+{
+"item" "ammo_bullets"
+"targetname" "t198"
+"origin" "872 -672 1248"
+"spawnflags" "6915"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"origin" "936 -1000 1304"
+"classname" "ammo_slugs"
+}
+{
+"classname" "item_health"
+"origin" "600 -1056 1240"
+}
+{
+"origin" "856 -1112 1224"
+"targetname" "t194"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"origin" "688 -1176 1248"
+"target" "t194"
+"spawnflags" "2049"
+"angle" "90"
+"classname" "monster_soldier_ss"
+}
+{
+"model" "*30"
+"message" "Cargo lift activated."
+"target" "t193"
+"spawnflags" "2048"
+"_minlight" ".3"
+"wait" "-1"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"model" "*31"
+"target" "t381"
+"spawnflags" "2048"
+"targetname" "t193"
+"classname" "func_train"
+}
+{
+"target" "t382"
+"targetname" "t381"
+"origin" "784 -1344 1184"
+"spawnflags" "0"
+"classname" "path_corner"
+}
+{
+"target" "ammo_grenades"
+"origin" "608 -1184 1240"
+"targetname" "t144"
+"spawnflags" "2048"
+"classname" "target_spawner"
+}
+{
+"target" "ammo_rockets"
+"origin" "752 -928 1240"
+"targetname" "t66"
+"item" "ammo_bullets"
+"spawnflags" "2048"
+"classname" "target_spawner"
+}
+{
+"target" "ammo_tesla"
+"origin" "-416 -2336 848"
+"spawnflags" "2048"
+"targetname" "t59"
+"classname" "target_spawner"
+}
+{
+"origin" "224 -744 1440"
+"light" "80"
+"classname" "light"
+}
+{
+"origin" "1888 -2848 1048"
+"light" "80"
+"classname" "light"
+}
+{
+"origin" "2784 -2648 1112"
+"classname" "ammo_rockets"
+}
+{
+"origin" "2696 -2328 1112"
+"classname" "item_health_large"
+}
+{
+"origin" "2488 -2592 1112"
+"classname" "item_health_large"
+}
+{
+"spawnflags" "2048"
+"item" "ammo_grenades"
+"origin" "2536 -1752 1176"
+"targetname" "t190"
+"classname" "monster_gunner"
+}
+{
+"origin" "2456 -1816 1160"
+"targetname" "t189"
+"classname" "point_combat"
+"spawnflags" "2048"
+}
+{
+"origin" "2544 -1952 1176"
+"target" "t190"
+"spawnflags" "2049"
+"angle" "180"
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+}
+{
+"target" "t189"
+"spawnflags" "2049"
+"angle" "180"
+"item" "ammo_shells"
+"origin" "2592 -1888 1184"
+"classname" "monster_soldier"
+}
+{
+"origin" "2856 592 1048"
+"classname" "item_health_large"
+}
+{
+"origin" "2904 672 1144"
+"classname" "ammo_prox"
+}
+{
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+"light" "125"
+"origin" "2496 344 1096"
+}
+{
+"map" "rbase2$rb2"
+"origin" "2488 304 1048"
+"targetname" "t188"
+"classname" "target_changelevel"
+"spawnflags" "2048"
+}
+{
+"origin" "2456 304 1048"
+"target" "t188"
+"delay" "5"
+"targetname" "t134"
+"classname" "trigger_relay"
+}
+{
+"item" "ammo_cells"
+"origin" "3060 -1108 1178"
+"target" "t246"
+"angle" "180"
+"spawnflags" "2049"
+"classname" "monster_stalker"
+}
+{
+"classname" "monster_hover"
+"spawnflags" "3841"
+"origin" "3288 -1440 1184"
+"angle" "180"
+}
+{
+"angle" "270"
+"origin" "2912 -1248 1208"
+"targetname" "t31"
+"spawnflags" "2051"
+"classname" "monster_daedalus"
+}
+{
+"origin" "2848 -2728 1168"
+"targetname" "t185"
+"spawnflags" "2048"
+"wait" "15"
+"classname" "point_combat"
+}
+{
+"classname" "monster_gunner"
+"spawnflags" "6913"
+"angle" "45"
+"origin" "2848 -2952 1184"
+}
+{
+"origin" "2528 -2128 1168"
+"target" "t182"
+"targetname" "t181"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "2528 -2112 1168"
+"target" "t184"
+"targetname" "t183"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "2248 -2120 1168"
+"targetname" "t184"
+"target" "t181"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "2688 -2256 1168"
+"target" "t183"
+"targetname" "t182"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"item" "ammo_slugs"
+"origin" "2240 -2112 1184"
+"target" "t184"
+"spawnflags" "2049"
+"classname" "monster_gladiator"
+}
+{
+"origin" "1952 -2364 1112"
+"spawnflags" "2048"
+"targetname" "t180"
+"classname" "target_explosion"
+}
+{
+"origin" "1952 -2364 1184"
+"delay" ".2"
+"spawnflags" "2048"
+"targetname" "t180"
+"classname" "target_explosion"
+}
+{
+"model" "*32"
+"targetname" "t179"
+"spawnflags" "2048"
+"target" "t180"
+"mass" "150"
+"classname" "func_explosive"
+}
+{
+"origin" "1952 -2416 1104"
+"targetname" "t178"
+"spawnflags" "2048"
+"classname" "point_combat"
+}
+{
+"origin" "1952 -2336 1120"
+"targetname" "t179"
+"target" "t178"
+"spawnflags" "2049"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"origin" "2096 -2720 1168"
+"target" "t179"
+"classname" "monster_daedalus"
+"spawnflags" "6401"
+"angle" "180"
+}
+{
+"angle" "180"
+"origin" "2088 -2720 1168"
+"target" "t179"
+"spawnflags" "7681"
+"classname" "monster_hover"
+}
+{
+"origin" "1920 -2800 1072"
+"targetname" "t177"
+"classname" "target_secret"
+"spawnflags" "2048"
+}
+{
+"origin" "1888 -2820 1056"
+"targetname" "t177"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*33"
+"message" "You found a secret."
+"health" "5"
+"target" "t177"
+"mass" "100"
+"spawnflags" "2048"
+"classname" "func_explosive"
+}
+{
+"attenuation" "-1"
+"origin" "2880 -2464 920"
+"noise" "world/fish.wav"
+"classname" "target_speaker"
+"spawnflags" "2048"
+"targetname" "t369"
+}
+{
+"origin" "2944 -2728 1008"
+"spawnflags" "2048"
+"message" "Security doors offline.\n Proceed to shuttle."
+"targetname" "t170"
+"classname" "target_help"
+}
+{
+"model" "*34"
+"targetname" "t28"
+"spawnflags" "2052"
+"target" "t170"
+"classname" "trigger_once"
+}
+{
+"origin" "352 -632 1520"
+"targetname" "t169"
+"classname" "target_secret"
+"spawnflags" "2048"
+}
+{
+"origin" "224 -748 1432"
+"spawnflags" "5888"
+"classname" "item_quad"
+}
+{
+"origin" "224 -744 1432"
+"spawnflags" "2048"
+"classname" "item_sphere_defender"
+}
+{
+"origin" "224 -712 1440"
+"spawnflags" "2048"
+"targetname" "t168"
+"classname" "target_explosion"
+}
+{
+"model" "*35"
+"targetname" "t169"
+"target" "t168"
+"spawnflags" "2048"
+"mass" "100"
+"classname" "func_explosive"
+}
+{
+"model" "*36"
+"spawnflags" "7936"
+"classname" "func_wall"
+}
+{
+"model" "*37"
+"target" "t169"
+"message" "A secret is revealed."
+"classname" "func_button"
+"angle" "270"
+"health" "1"
+"wait" "-1"
+"lip" "18"
+"spawnflags" "2048"
+}
+{
+"origin" "272 -536 1376"
+"spawnflags" "2049"
+"angle" "0"
+"classname" "monster_soldier"
+}
+{
+"item" "ammo_shells"
+"target" "t166"
+"origin" "424 304 1248"
+"spawnflags" "2049"
+"angle" "315"
+"classname" "monster_soldier"
+}
+{
+"origin" "448 224 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "480 356 1376"
+"targetname" "t166"
+"classname" "monster_turret"
+"angle" "270"
+"spawnflags" "7073"
+}
+{
+"origin" "480 356 1376"
+"targetname" "t166"
+"spawnflags" "7305"
+"angle" "270"
+"classname" "monster_turret"
+}
+{
+"origin" "480 324 1376"
+"spawnflags" "2048"
+"targetname" "t165"
+"classname" "target_explosion"
+}
+{
+"model" "*38"
+"targetname" "t166"
+"spawnflags" "2048"
+"target" "t165"
+"mass" "75"
+"classname" "func_explosive"
+}
+{
+"model" "*39"
+"classname" "func_wall"
+"spawnflags" "5888"
+}
+{
+"origin" "1976 -184 968"
+"classname" "misc_deadsoldier"
+"spawnflags" "2052"
+}
+{
+"origin" "1408 -272 968"
+"angle" "270"
+"spawnflags" "2818"
+"classname" "misc_deadsoldier"
+}
+{
+"item" "ammo_slugs"
+"origin" "1408 -128 1312"
+"spawnflags" "2049"
+"angle" "90"
+"classname" "monster_gladiator"
+"target" "t248"
+}
+{
+"origin" "1512 -104 984"
+"classname" "item_health_large"
+}
+{
+"origin" "1176 304 1128"
+"target" "oresound"
+"targetname" "oredeath"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"origin" "1224 304 1128"
+"delay" "23"
+"target" "oresound"
+"targetname" "oredeath"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"targetname" "oresound"
+"origin" "1112 288 1016"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+"team" "fiuck"
+"classname" "target_speaker"
+}
+{
+"targetname" "oresound"
+"origin" "1128 288 1016"
+"noise" "world/amb10.wav"
+"spawnflags" "2050"
+"team" "fiuck"
+"classname" "target_speaker"
+}
+{
+"origin" "3504 -616 1184"
+"spawnflags" "2049"
+"angle" "90"
+"item" "ammo_shells"
+"classname" "monster_soldier"
+}
+{
+"origin" "3552 -352 1348"
+"spawnflags" "2816"
+"targetname" "t164"
+"classname" "target_explosion"
+}
+{
+"model" "*40"
+"spawnflags" "1024"
+"classname" "func_wall"
+}
+{
+"targetname" "t163"
+"angle" "-2"
+"spawnflags" "2977"
+"origin" "3552 -352 1380"
+"classname" "monster_turret"
+}
+{
+"origin" "3584 -60 1216"
+"targetname" "t162"
+"spawnflags" "2304"
+"classname" "target_explosion"
+}
+{
+"model" "*41"
+"targetname" "t163"
+"target" "t162"
+"spawnflags" "2304"
+"mass" "75"
+"classname" "func_explosive"
+}
+{
+"model" "*42"
+"classname" "func_wall"
+"spawnflags" "1536"
+}
+{
+"targetname" "t163"
+"origin" "3584 -28 1216"
+"spawnflags" "2441"
+"angle" "270"
+"classname" "monster_turret"
+}
+{
+"item" "ammo_grenades"
+"targetname" "t186"
+"target" "t185"
+"origin" "2840 -2856 1184"
+"angle" "45"
+"spawnflags" "2049"
+"classname" "monster_gunner"
+}
+{
+"origin" "2848 -2968 1184"
+"spawnflags" "7425"
+"angle" "45"
+"classname" "monster_berserk"
+}
+{
+"item" "ammo_shells"
+"origin" "2848 -2984 1184"
+"spawnflags" "7681"
+"angle" "45"
+"classname" "monster_soldier"
+}
+{
+"origin" "3488 -1032 928"
+"targetname" "t120"
+"target" "t121"
+"item" "ammo_bullets"
+"spawnflags" "3841"
+"classname" "monster_gunner"
+}
+{
+"origin" "3488 -1040 928"
+"target" "t121"
+"targetname" "t120"
+"spawnflags" "7169"
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+}
+{
+"spawnflags" "2816"
+"origin" "2528 -160 1284"
+"targetname" "t161"
+"classname" "target_explosion"
+}
+{
+"model" "*43"
+"targetname" "t160"
+"target" "t161"
+"spawnflags" "2816"
+"mass" "75"
+"classname" "func_explosive"
+}
+{
+"model" "*44"
+"spawnflags" "1024"
+"classname" "func_wall"
+}
+{
+"spawnflags" "2304"
+"origin" "2400 352 1284"
+"targetname" "t159"
+"classname" "target_explosion"
+}
+{
+"model" "*45"
+"spawnflags" "2304"
+"targetname" "t160"
+"target" "t159"
+"mass" "75"
+"classname" "func_explosive"
+}
+{
+"model" "*46"
+"spawnflags" "1536"
+"classname" "func_wall"
+}
+{
+"spawnflags" "2048"
+"origin" "2752 672 1284"
+"targetname" "t157"
+"classname" "target_explosion"
+}
+{
+"model" "*47"
+"mass" "75"
+"targetname" "t126"
+"target" "t157"
+"spawnflags" "2048"
+"classname" "func_explosive"
+}
+{
+"model" "*48"
+"spawnflags" "5888"
+"classname" "func_wall"
+}
+{
+"origin" "2752 672 1316"
+"targetname" "t126"
+"spawnflags" "2185"
+"angle" "-2"
+"classname" "monster_turret"
+}
+{
+"origin" "3168 -816 920"
+"classname" "item_health_small"
+}
+{
+"origin" "3232 -816 920"
+"classname" "item_health_small"
+}
+{
+"model" "*49"
+"spawnflags" "5120"
+"classname" "func_wall"
+}
+{
+"origin" "3104 -928 1028"
+"targetname" "t155"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*50"
+"targetname" "t123"
+"target" "t155"
+"spawnflags" "6912"
+"mass" "75"
+"classname" "func_explosive"
+}
+{
+"origin" "3104 -928 1060"
+"targetname" "t123"
+"angle" "-2"
+"spawnflags" "7049"
+"classname" "monster_turret"
+}
+{
+"origin" "2752 -64 1016"
+"light" "125"
+"classname" "light"
+}
+{
+"target" "t357"
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"angle" "0"
+"spawnflags" "2049"
+"origin" "2624 -512 1176"
+}
+{
+"model" "*51"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "90"
+"team" "goinexit"
+"_minlight" ".3"
+"spawnflags" "2048"
+"wait" "-1"
+}
+{
+"model" "*52"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "270"
+"team" "goinexit"
+"_minlight" ".3"
+"spawnflags" "2048"
+"wait" "-1"
+}
+{
+"angle" "-2"
+"classname" "monster_turret"
+"spawnflags" "6153"
+"targetname" "t105"
+"origin" "2976 -2752 1144"
+}
+{
+"angle" "-2"
+"classname" "monster_turret"
+"spawnflags" "3873"
+"targetname" "t105"
+"origin" "2976 -2752 1144"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-128 -2304 1024"
+}
+{
+"classname" "item_health"
+"origin" "640 -600 1368"
+}
+{
+"classname" "item_health"
+"origin" "640 -544 1368"
+}
+{
+"classname" "point_combat"
+"targetname" "t152"
+"target" "t153"
+"origin" "200 -224 1376"
+"spawnflags" "2048"
+}
+{
+"classname" "point_combat"
+"wait" "6"
+"targetname" "t153"
+"origin" "288 -224 1376"
+"spawnflags" "2048"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"angle" "315"
+"spawnflags" "2049"
+"target" "t152"
+"targetname" "t154"
+"origin" "152 -120 1376"
+}
+{
+"item" "ammo_shells"
+"classname" "monster_soldier"
+"angle" "0"
+"spawnflags" "2049"
+"origin" "344 -88 1248"
+}
+{
+"item" "ammo_slugs"
+"classname" "monster_gladiator"
+"angle" "90"
+"spawnflags" "2049"
+"target" "t154"
+"origin" "560 -392 1248"
+}
+{
+"model" "*53"
+"classname" "trigger_hurt"
+"dmg" "10000"
+"spawnflags" "3"
+"targetname" "t129"
+}
+{
+"classname" "func_timer"
+"target" "t151"
+"random" "2"
+"wait" "4"
+"spawnflags" "0"
+"origin" "2496 896 920"
+"targetname" "t378"
+}
+{
+"classname" "func_timer"
+"target" "t149"
+"random" "2"
+"wait" "4"
+"spawnflags" "0"
+"origin" "2496 448 920"
+"targetname" "t378"
+}
+{
+"classname" "func_timer"
+"target" "t148"
+"random" "2"
+"wait" "4"
+"spawnflags" "0"
+"origin" "2496 320 920"
+"targetname" "t378"
+}
+{
+"classname" "func_timer"
+"target" "t147"
+"random" "2"
+"wait" "4"
+"spawnflags" "0"
+"origin" "2496 192 920"
+"targetname" "t378"
+}
+{
+"classname" "func_timer"
+"target" "t146"
+"random" "2"
+"wait" "4"
+"spawnflags" "0"
+"origin" "2496 64 920"
+"targetname" "t378"
+}
+{
+"classname" "func_timer"
+"target" "t150"
+"random" "2"
+"wait" "4"
+"spawnflags" "0"
+"origin" "2496 576 920"
+"targetname" "t378"
+}
+{
+"sounds" "1"
+"count" "50"
+"origin" "2424 896 920"
+"classname" "target_splash"
+"targetname" "t151"
+"spawnflags" "2048"
+}
+{
+"sounds" "1"
+"count" "50"
+"origin" "2568 896 920"
+"classname" "target_splash"
+"targetname" "t151"
+"spawnflags" "2048"
+}
+{
+"count" "50"
+"sounds" "1"
+"origin" "2424 64 920"
+"classname" "target_splash"
+"targetname" "t146"
+"spawnflags" "2048"
+}
+{
+"count" "50"
+"sounds" "1"
+"origin" "2568 64 920"
+"classname" "target_splash"
+"targetname" "t146"
+}
+{
+"sounds" "1"
+"count" "50"
+"origin" "2424 192 920"
+"classname" "target_splash"
+"targetname" "t147"
+}
+{
+"sounds" "1"
+"count" "50"
+"origin" "2568 192 920"
+"classname" "target_splash"
+"targetname" "t147"
+"spawnflags" "2048"
+}
+{
+"sounds" "1"
+"count" "50"
+"origin" "2424 320 920"
+"classname" "target_splash"
+"targetname" "t148"
+}
+{
+"sounds" "1"
+"count" "50"
+"origin" "2568 320 920"
+"classname" "target_splash"
+"targetname" "t148"
+"spawnflags" "2048"
+}
+{
+"sounds" "1"
+"count" "50"
+"classname" "target_splash"
+"origin" "2424 448 920"
+"targetname" "t149"
+"spawnflags" "2048"
+}
+{
+"sounds" "1"
+"count" "50"
+"classname" "target_splash"
+"origin" "2568 448 920"
+"targetname" "t149"
+}
+{
+"sounds" "1"
+"count" "50"
+"classname" "target_splash"
+"origin" "2424 576 920"
+"targetname" "t150"
+}
+{
+"sounds" "1"
+"count" "50"
+"classname" "target_splash"
+"origin" "2568 576 920"
+"targetname" "t150"
+"spawnflags" "2048"
+}
+{
+"style" "32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2432 640 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2560 640 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2432 832 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2560 832 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2432 512 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2560 512 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2432 384 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2560 384 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2432 256 944"
+"light" "150"
+"targetname" "t377"
+"spawnflags" "1"
+}
+{
+"style" "32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2560 256 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2432 128 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2560 128 944"
+"light" "150"
+"targetname" "t377"
+"spawnflags" "1"
+}
+{
+"style" "32"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2560 960 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"style" "32"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2432 960 944"
+"light" "150"
+"spawnflags" "1"
+"targetname" "t377"
+}
+{
+"classname" "item_health_small"
+"origin" "2664 624 1048"
+}
+{
+"classname" "item_health_small"
+"origin" "2664 576 1048"
+}
+{
+"classname" "item_health_small"
+"origin" "2664 528 1048"
+}
+{
+"classname" "item_health_small"
+"origin" "2664 672 1048"
+}
+{
+"angle" "-2"
+"classname" "monster_turret"
+"origin" "2752 -160 1080"
+"spawnflags" "2057"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+"origin" "396 100 1300"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"classname" "light"
+"origin" "608 112 1376"
+}
+{
+"classname" "item_health"
+"origin" "1824 -352 1240"
+}
+{
+"classname" "item_armor_shard"
+"origin" "736 -1424 1112"
+}
+{
+"classname" "monster_stalker"
+"spawnflags" "2057"
+"angle" "180"
+"origin" "916 -948 1322"
+}
+{
+"classname" "target_explosion"
+"targetname" "t144"
+"spawnflags" "2048"
+"origin" "608 -1184 1256"
+}
+{
+"model" "*54"
+"classname" "func_explosive"
+"health" "25"
+"mass" "250"
+"target" "t144"
+"spawnflags" "2048"
+}
+{
+"origin" "608 120 1368"
+"classname" "light"
+"light" "75"
+}
+{
+"origin" "416 120 1368"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "448 128 1336"
+"item" "key_blue_key"
+"spawnflags" "2048"
+"targetname" "t143"
+"target" "t7"
+"classname" "trigger_key"
+}
+{
+"light" "120"
+"origin" "1344 -112 1144"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1344 -256 1144"
+"light" "120"
+}
+{
+"light" "120"
+"origin" "1472 -256 1144"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1472 -112 1144"
+"light" "120"
+}
+{
+"origin" "2016 -536 1048"
+"spawnflags" "2051"
+"angle" "135"
+"classname" "monster_daedalus"
+"targetname" "t383"
+}
+{
+"origin" "1224 -688 1024"
+"targetname" "t142"
+"classname" "target_secret"
+"spawnflags" "2048"
+}
+{
+"model" "*55"
+"target" "t142"
+"message" "You found a secret."
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"targetname" "t362"
+"origin" "920 152 1056"
+"item" "ammo_bullets"
+"spawnflags" "2051"
+"angle" "0"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "1000 232 1048"
+"classname" "weapon_machinegun"
+}
+{
+"origin" "928 224 1024"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "1824 -32 1240"
+"classname" "item_armor_body"
+"spawnflags" "2048"
+}
+{
+"origin" "1920 424 1232"
+"classname" "item_health_small"
+}
+{
+"origin" "1968 424 1232"
+"classname" "item_health_small"
+}
+{
+"origin" "2016 424 1232"
+"classname" "item_health_small"
+}
+{
+"origin" "2024 376 1232"
+"classname" "item_health_small"
+}
+{
+"model" "*56"
+"target" "t140"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "1120 344 1240"
+"targetname" "t140"
+"angle" "45"
+"spawnflags" "2051"
+"classname" "monster_berserk"
+}
+{
+"spawnflags" "2051"
+"origin" "1896 96 1240"
+"angle" "315"
+"classname" "monster_berserk"
+"targetname" "t223"
+}
+{
+"item" "ammo_slugs"
+"origin" "1664 400 1240"
+"spawnflags" "2051"
+"angle" "0"
+"classname" "monster_gladiator"
+"target" "t239"
+"targetname" "uknow"
+}
+{
+"origin" "1848 200 1136"
+"targetname" "t138"
+"classname" "target_secret"
+"spawnflags" "2048"
+}
+{
+"model" "*57"
+"message" "You found a secret"
+"target" "t138"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"model" "*58"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "928 -288 1048"
+"classname" "item_health"
+}
+{
+"origin" "920 -232 976"
+"classname" "item_health"
+}
+{
+"origin" "1024 -296 992"
+"classname" "item_armor_shard"
+}
+{
+"origin" "984 -296 992"
+"classname" "item_armor_shard"
+}
+{
+"origin" "920 -184 992"
+"classname" "item_armor_shard"
+}
+{
+"item" "ammo_cells"
+"origin" "952 -40 1088"
+"spawnflags" "7681"
+"classname" "monster_hover"
+}
+{
+"item" "ammo_cells"
+"origin" "968 -56 1088"
+"angle" "270"
+"spawnflags" "6401"
+"classname" "monster_daedalus"
+}
+{
+"targetname" "t101"
+"origin" "1184 -168 992"
+"angle" "315"
+"spawnflags" "7427"
+"classname" "monster_gunner"
+"item" "ammo_grenades"
+}
+{
+"targetname" "t137"
+"origin" "1904 -872 864"
+"spawnflags" "6401"
+"angle" "0"
+"classname" "monster_gladiator"
+}
+{
+"origin" "2520 -992 1056"
+"classname" "monster_soldier"
+"angle" "90"
+"spawnflags" "7681"
+}
+{
+"target" "t137"
+"origin" "2344 -792 864"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "monster_soldier"
+}
+{
+"origin" "2504 -1008 1056"
+"spawnflags" "7425"
+"angle" "90"
+"classname" "monster_berserk"
+}
+{
+"classname" "misc_deadsoldier"
+"spawnflags" "2056"
+"origin" "984 -1696 832"
+}
+{
+"classname" "ammo_slugs"
+"origin" "992 -1624 848"
+}
+{
+"classname" "weapon_railgun"
+"origin" "992 -1744 856"
+}
+{
+"target" "t211"
+"classname" "monster_berserk"
+"angle" "270"
+"spawnflags" "2049"
+"origin" "2176 -232 864"
+}
+{
+"classname" "monster_stalker"
+"spawnflags" "2057"
+"angle" "180"
+"origin" "2820 -252 994"
+}
+{
+"spawnflags" "2048"
+"classname" "target_help"
+"targetname" "t136"
+"origin" "2712 -360 1312"
+"message" "Transport accesssible.\nProceed to Logistics."
+}
+{
+"classname" "trigger_relay"
+"targetname" "t134"
+"target" "t135"
+"origin" "2640 384 1144"
+}
+{
+"model" "*59"
+"classname" "func_door"
+"angle" "180"
+"_minlight" ".3"
+"team" "exitrain"
+"targetname" "t135"
+"wait" "-1"
+}
+{
+"model" "*60"
+"spawnflags" "0"
+"classname" "func_door"
+"angle" "0"
+"_minlight" ".3"
+"team" "exitrain"
+"targetname" "t135"
+"wait" "-1"
+}
+{
+"model" "*61"
+"origin" "2496 396 1028"
+"classname" "func_door_rotating"
+"distance" "70"
+"_minlight" ".3"
+"wait" "-1"
+"sounds" "2"
+"spawnflags" "2114"
+"target" "t134"
+"targetname" "t375"
+}
+{
+"classname" "path_corner"
+"wait" "-1"
+"origin" "2576 214 1030"
+"targetname" "t373"
+"spawnflags" "2048"
+}
+{
+"classname" "path_corner"
+"origin" "2592 214 1030"
+"wait" "1.5"
+"targetname" "t372"
+"target" "t373"
+"spawnflags" "2048"
+}
+{
+"model" "*62"
+"message" "Shuttle power online."
+"spawnflags" "2048"
+"classname" "func_button"
+"angle" "0"
+"_minlight" ".3"
+"wait" "-1"
+"target" "t129"
+"killtarget" "comeout"
+}
+{
+"classname" "path_corner"
+"origin" "2592 302 1030"
+"target" "t372"
+"targetname" "t374"
+"spawnflags" "2048"
+}
+{
+"model" "*63"
+"classname" "func_train"
+"spawnflags" "2050"
+"speed" "50"
+"target" "t374"
+"targetname" "t135"
+}
+{
+"model" "*64"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t126"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t125"
+"origin" "2752 -16 1168"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"spawnflags" "2049"
+"angle" "90"
+"target" "t125"
+"targetname" "t126"
+"origin" "2752 -64 1184"
+}
+{
+"classname" "ammo_cells"
+"origin" "1176 -608 984"
+}
+{
+"classname" "weapon_bfg"
+"origin" "1184 -672 984"
+}
+{
+"origin" "3368 -608 1152"
+"angle" "180"
+"spawnflags" "2064"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "3416 -608 1240"
+"classname" "weapon_shotgun"
+}
+{
+"origin" "3552 -480 1168"
+"classname" "ammo_shells"
+}
+{
+"origin" "3288 -616 1176"
+"classname" "item_health_large"
+}
+{
+"origin" "3544 -496 1264"
+"spawnflags" "7425"
+"angle" "90"
+"classname" "monster_hover"
+}
+{
+"target" "t156"
+"origin" "3296 -416 928"
+"spawnflags" "6913"
+"angle" "0"
+"classname" "monster_soldier"
+}
+{
+"origin" "3296 -128 928"
+"classname" "ammo_grenades"
+"spawnflags" "2048"
+}
+{
+"target" "t83"
+"targetname" "t82"
+"origin" "3352 -600 912"
+"spawnflags" "2048"
+"classname" "point_combat"
+}
+{
+"origin" "3632 -640 904"
+"targetname" "t124"
+"spawnflags" "2049"
+"classname" "point_combat"
+}
+{
+"item" "ammo_bullets"
+"origin" "3624 -616 928"
+"target" "t124"
+"angle" "225"
+"spawnflags" "3841"
+"classname" "monster_gunner"
+}
+{
+"item" "ammo_bullets"
+"origin" "3592 -528 928"
+"angle" "225"
+"spawnflags" "7681"
+"classname" "monster_soldier_ss"
+}
+{
+"origin" "3600 -536 928"
+"spawnflags" "7425"
+"angle" "225"
+"classname" "monster_berserk"
+}
+{
+"origin" "3688 -352 984"
+"classname" "ammo_bullets"
+}
+{
+"origin" "3752 -136 912"
+"classname" "item_health"
+}
+{
+"origin" "3480 -80 912"
+"classname" "item_health_large"
+}
+{
+"origin" "3496 64 1184"
+"spawnflags" "7169"
+"angle" "180"
+"classname" "monster_soldier"
+}
+{
+"origin" "3316 68 1178"
+"spawnflags" "2049"
+"angle" "0"
+"classname" "monster_stalker"
+}
+{
+"origin" "3496 24 1176"
+"classname" "ammo_slugs"
+}
+{
+"origin" "3496 104 1176"
+"classname" "ammo_cells"
+}
+{
+"origin" "2904 -1000 920"
+"classname" "ammo_cells"
+}
+{
+"origin" "3096 -1000 920"
+"classname" "ammo_shells"
+}
+{
+"origin" "3232 -856 912"
+"targetname" "t122"
+"spawnflags" "2048"
+"classname" "point_combat"
+}
+{
+"origin" "2920 -1760 832"
+"spawnflags" "2049"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "2848 -1688 856"
+"classname" "ammo_prox"
+}
+{
+"origin" "2944 -1808 856"
+"spawnflags" "2048"
+"classname" "weapon_proxlauncher"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3616 -1328 856"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3616 -1384 856"
+}
+{
+"origin" "3616 -1432 856"
+"classname" "item_armor_shard"
+}
+{
+"origin" "3616 -1272 856"
+"classname" "item_armor_shard"
+}
+{
+"origin" "3584 -1040 912"
+"spawnflags" "2048"
+"targetname" "t121"
+"classname" "point_combat"
+}
+{
+"model" "*65"
+"target" "t120"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"item" "ammo_shells"
+"origin" "3000 -1240 928"
+"spawnflags" "2049"
+"angle" "90"
+"classname" "monster_soldier"
+}
+{
+"item" "ammo_cells"
+"origin" "3216 -1832 1208"
+"spawnflags" "3841"
+"angle" "45"
+"classname" "monster_daedalus"
+}
+{
+"targetname" "t31"
+"angle" "180"
+"origin" "3288 -1664 1208"
+"spawnflags" "7427"
+"classname" "monster_hover"
+}
+{
+"origin" "2944 -512 1208"
+"classname" "light"
+"light" "115"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"classname" "light"
+"origin" "2768 -704 1208"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2496 -256 968"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "2688 -256 968"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "2816 -256 968"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2304 -256 968"
+}
+{
+"origin" "1472 -256 1024"
+"targetname" "t118"
+"classname" "target_explosion"
+"spawnflags" "2816"
+}
+{
+"model" "*66"
+"targetname" "t119"
+"target" "t118"
+"mass" "150"
+"classname" "func_explosive"
+"spawnflags" "2816"
+}
+{
+"origin" "1404 -252 1058"
+"targetname" "t119"
+"spawnflags" "2819"
+"angle" "0"
+"classname" "monster_stalker"
+}
+{
+"origin" "1728 -60 1024"
+"targetname" "t117"
+"classname" "target_explosion"
+"spawnflags" "2304"
+}
+{
+"model" "*67"
+"targetname" "t116"
+"target" "t117"
+"mass" "150"
+"classname" "func_explosive"
+"spawnflags" "2304"
+}
+{
+"origin" "1532 -388 1050"
+"target" "t119"
+"targetname" "t116"
+"angle" "0"
+"spawnflags" "2051"
+"classname" "monster_stalker"
+}
+{
+"origin" "1596 -384 1024"
+"targetname" "t115"
+"classname" "target_explosion"
+"spawnflags" "2048"
+}
+{
+"model" "*68"
+"targetname" "t116"
+"target" "t115"
+"spawnflags" "2048"
+"mass" "150"
+"classname" "func_explosive"
+}
+{
+"model" "*69"
+"target" "t114"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "1924 -192 1024"
+"spawnflags" "2048"
+"targetname" "t113"
+"classname" "target_explosion"
+}
+{
+"model" "*70"
+"targetname" "t114"
+"spawnflags" "2048"
+"target" "t113"
+"mass" "150"
+"classname" "func_explosive"
+}
+{
+"target" "t116"
+"targetname" "t114"
+"origin" "1996 -180 1058"
+"classname" "monster_stalker"
+"spawnflags" "2051"
+"angle" "180"
+}
+{
+"targetname" "t116"
+"origin" "1724 12 1050"
+"angle" "270"
+"spawnflags" "2307"
+"classname" "monster_stalker"
+}
+{
+"origin" "1504 -32 984"
+"target" "t111"
+"spawnflags" "2050"
+"angle" "90"
+"classname" "monster_soldier_light"
+"targetname" "t362"
+}
+{
+"origin" "1416 -96 992"
+"targetname" "t111"
+"spawnflags" "2051"
+"angle" "0"
+"classname" "monster_gunner"
+"item" "ammo_grenades"
+}
+{
+"angle" "0"
+"origin" "1376 160 992"
+"spawnflags" "2051"
+"classname" "monster_soldier"
+"targetname" "t362"
+}
+{
+"model" "*71"
+"target" "oredeath"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "1728 256 968"
+"spawnflags" "2048"
+"target" "t110"
+"targetname" "t109"
+"classname" "path_corner"
+}
+{
+"origin" "1728 128 968"
+"spawnflags" "2048"
+"target" "t109"
+"targetname" "t108"
+"classname" "path_corner"
+}
+{
+"origin" "1472 128 968"
+"spawnflags" "2048"
+"target" "t108"
+"targetname" "t107"
+"classname" "path_corner"
+}
+{
+"origin" "1472 256 968"
+"spawnflags" "2048"
+"target" "t107"
+"targetname" "t106"
+"classname" "path_corner"
+}
+{
+"origin" "1088 256 968"
+"spawnflags" "2048"
+"targetname" "deathtram"
+"target" "t106"
+"classname" "path_corner"
+}
+{
+"origin" "1984 256 968"
+"spawnflags" "2048"
+"targetname" "t110"
+"classname" "path_corner"
+}
+{
+"model" "*72"
+"speed" "50"
+"team" "fiuck"
+"targetname" "oredeath"
+"spawnflags" "2048"
+"target" "deathtram"
+"classname" "func_train"
+"_minlight" ".1"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1216 224 1080"
+}
+{
+"origin" "1120 288 1016"
+"classname" "light"
+"light" "80"
+}
+{
+"target" "ammo_cells"
+"origin" "1208 -544 1040"
+"targetname" "t64"
+"spawnflags" "2048"
+"classname" "target_spawner"
+}
+{
+"origin" "1824 224 1104"
+"classname" "item_health_mega"
+"spawnflags" "2048"
+"angle" "135"
+}
+{
+"model" "*73"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"classname" "light"
+"origin" "1952 -160 1104"
+"light" "100"
+}
+{
+"model" "*74"
+"spawnflags" "2048"
+"target" "t105"
+"classname" "trigger_once"
+}
+{
+"classname" "info_player_start"
+"angle" "90"
+"targetname" "rw2"
+"origin" "3488 -2080 896"
+}
+{
+"classname" "info_player_start"
+"angle" "0"
+"targetname" "rw2b"
+"origin" "-880 -2368 808"
+}
+{
+"classname" "info_player_start"
+"angle" "0"
+"targetname" "rw2a"
+"origin" "-136 -128 1376"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "2624 -320 1280"
+}
+{
+"model" "*75"
+"spawnflags" "2048"
+"wait" "-1"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "180"
+"_minlight" ",3"
+"team" "thasit"
+}
+{
+"model" "*76"
+"spawnflags" "2048"
+"wait" "-1"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "0"
+"_minlight" ",3"
+"team" "thasit"
+}
+{
+"classname" "target_changelevel"
+"targetname" "t104"
+"map" "rbase1$rb1"
+"origin" "-168 -80 1464"
+"spawnflags" "2048"
+}
+{
+"model" "*77"
+"angle" "180"
+"classname" "trigger_multiple"
+"target" "t104"
+"spawnflags" "2048"
+}
+{
+"target" "t186"
+"classname" "monster_gunner"
+"angle" "315"
+"spawnflags" "2049"
+"origin" "2304 -2584 1056"
+}
+{
+"classname" "item_armor_shard"
+"origin" "2208 -2792 1048"
+}
+{
+"classname" "item_armor_shard"
+"origin" "2152 -2792 1048"
+}
+{
+"classname" "item_armor_shard"
+"origin" "2264 -2792 1048"
+}
+{
+"item" "ammo_bullets"
+"target" "t122"
+"origin" "2912 -928 928"
+"angle" "0"
+"spawnflags" "3841"
+"classname" "monster_soldier_ss"
+}
+{
+"model" "*78"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "90"
+"_minlight" ".3"
+"team" "mickid"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"model" "*79"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "270"
+"_minlight" ".3"
+"team" "mickid"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "2784 -512 1208"
+}
+{
+"origin" "2624 -512 1208"
+"classname" "light"
+"light" "115"
+}
+{
+"model" "*80"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "180"
+"_minlight" ".3"
+"team" "lasdior"
+"spawnflags" "2048"
+"wait" "-1"
+}
+{
+"model" "*81"
+"message" "Doors sealed."
+"targetname" "t31"
+"classname" "func_door"
+"angle" "0"
+"_minlight" ".3"
+"team" "lasdior"
+"spawnflags" "2048"
+"wait" "-1"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "2816 -992 1232"
+}
+{
+"origin" "2816 -1120 1232"
+"classname" "light"
+"light" "115"
+}
+{
+"origin" "3008 -992 1232"
+"classname" "light"
+"light" "115"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "3008 -1120 1232"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "3200 -864 1016"
+}
+{
+"origin" "3200 -992 1016"
+"classname" "light"
+"light" "115"
+}
+{
+"model" "*82"
+"spawnflags" "2"
+"targetname" "t366"
+"classname" "func_plat2"
+"lip" "8"
+"angle" "-2"
+"_minlight" ".3"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3112 -536 920"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3000 -544 984"
+}
+{
+"classname" "item_armor_shard"
+"origin" "3056 -552 952"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_hover"
+"spawnflags" "6657"
+"angle" "135"
+"origin" "1568 -1760 1144"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-96 -240 1440"
+}
+{
+"origin" "-96 -128 1504"
+"light" "80"
+"classname" "light"
+}
+{
+"origin" "-32 -208 1440"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "80"
+"origin" "-96 -16 1440"
+}
+{
+"origin" "-160 -208 1440"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*83"
+"team" "bizzitch"
+"_minlight" ".3"
+"angle" "90"
+"classname" "func_door"
+}
+{
+"model" "*84"
+"team" "bizzitch"
+"_minlight" ".3"
+"angle" "270"
+"classname" "func_door"
+}
+{
+"origin" "-32 -48 1440"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "96 -80 1388"
+"_color" "1.000000 0.000000 0.000000"
+"light" "50"
+"classname" "light"
+}
+{
+"origin" "256 -192 1464"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "448 -384 1464"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "552 -480 1416"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "-160 -48 1440"
+}
+{
+"origin" "2968 -1112 912"
+"classname" "item_breather"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"targetname" "t100"
+"origin" "1280 -352 976"
+}
+{
+"classname" "monster_soldier"
+"spawnflags" "7169"
+"target" "t100"
+"origin" "1056 -464 992"
+"targetname" "t101"
+"item" "ammo_shells"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "45"
+"origin" "920 -680 1048"
+}
+{
+"classname" "item_health_large"
+"origin" "920 -632 1048"
+}
+{
+"targetname" "t362"
+"classname" "monster_stalker"
+"angle" "90"
+"spawnflags" "2051"
+"origin" "928 -452 1130"
+}
+{
+"model" "*85"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t101"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2048"
+"targetname" "t97"
+"origin" "1568 -560 968"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2048"
+"targetname" "t98"
+"origin" "1512 -640 968"
+}
+{
+"classname" "monster_gunner"
+"spawnflags" "6913"
+"target" "t97"
+"targetname" "t101"
+"origin" "1432 -616 992"
+}
+{
+"classname" "ammo_prox"
+"origin" "1112 -584 1040"
+}
+{
+"classname" "item_armor_jacket"
+"origin" "1440 -344 1048"
+}
+{
+"angle" "90"
+"classname" "monster_daedalus"
+"spawnflags" "3841"
+"origin" "472 -2424 1160"
+}
+{
+"angle" "225"
+"classname" "monster_flyer"
+"spawnflags" "2049"
+"origin" "632 -2032 1072"
+}
+{
+"spawnflags" "7169"
+"classname" "monster_flyer"
+"angle" "135"
+"origin" "704 -2552 1120"
+}
+{
+"classname" "monster_flyer"
+"spawnflags" "2049"
+"angle" "225"
+"origin" "560 -1976 1000"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"origin" "376 -2368 1000"
+"spawnflags" "7425"
+"target" "t96"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_soldier_ss"
+"spawnflags" "7681"
+"target" "t96"
+"origin" "432 -2400 1000"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t94"
+"target" "t95"
+"origin" "384 -2000 984"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"target" "t94"
+"targetname" "t96"
+"origin" "376 -2408 984"
+}
+{
+"classname" "path_corner"
+"spawnflags" "2048"
+"targetname" "t95"
+"target" "t96"
+"origin" "512 -2136 984"
+}
+{
+"classname" "monster_daedalus"
+"angle" "0"
+"spawnflags" "3841"
+"origin" "32 -2368 1024"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-544 -1880 920"
+}
+{
+"classname" "item_armor_shard"
+"origin" "-544 -1928 920"
+}
+{
+"classname" "item_health_small"
+"origin" "1232 -1808 880"
+}
+{
+"classname" "item_health_small"
+"origin" "1168 -1856 872"
+}
+{
+"classname" "item_health_large"
+"origin" "1304 -1792 872"
+}
+{
+"item" "ammo_cells"
+"classname" "monster_daedalus"
+"spawnflags" "3841"
+"angle" "45"
+"origin" "1000 -1848 1224"
+}
+{
+"item" "ammo_shells"
+"classname" "monster_soldier"
+"spawnflags" "7681"
+"target" "t93"
+"origin" "1000 -1240 1056"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"spawnflags" "7425"
+"target" "t93"
+"origin" "992 -1160 1056"
+}
+{
+"classname" "path_corner"
+"targetname" "t92"
+"target" "t93"
+"spawnflags" "2048"
+"origin" "1024 -1216 1040"
+}
+{
+"classname" "path_corner"
+"target" "t92"
+"targetname" "t93"
+"spawnflags" "2048"
+"origin" "1488 -1216 1040"
+}
+{
+"item" "ammo_slugs"
+"classname" "monster_gladiator"
+"spawnflags" "6913"
+"target" "t93"
+"origin" "1048 -1160 1056"
+}
+{
+"item" "ammo_bullets"
+"classname" "monster_gunner"
+"spawnflags" "3840"
+"origin" "752 -1440 864"
+}
+{
+"item" "ammo_shells"
+"classname" "monster_soldier_ss"
+"angle" "0"
+"spawnflags" "7681"
+"origin" "736 -1440 864"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum1.wav"
+"origin" "2144 -192 904"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum1.wav"
+"origin" "2216 -192 904"
+}
+{
+"targetname" "t156"
+"spawnflags" "6913"
+"angle" "180"
+"classname" "monster_flyer"
+"origin" "3888 -256 1136"
+}
+{
+"targetname" "t156"
+"classname" "monster_flyer"
+"angle" "180"
+"spawnflags" "6913"
+"origin" "3880 -336 1120"
+}
+{
+"model" "*86"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t88"
+}
+{
+"classname" "point_combat"
+"targetname" "t87"
+"spawnflags" "2049"
+"origin" "2096 -1056 1040"
+}
+{
+"classname" "point_combat"
+"targetname" "t86"
+"spawnflags" "2048"
+"origin" "2208 -1056 1040"
+}
+{
+"classname" "monster_soldier_ss"
+"angle" "315"
+"spawnflags" "2049"
+"origin" "2784 -560 920"
+}
+{
+"classname" "item_armor_combat"
+"origin" "3304 -128 1048"
+}
+{
+"target" "t163"
+"classname" "monster_stalker"
+"angle" "0"
+"spawnflags" "6409"
+"origin" "3308 -404 1066"
+}
+{
+"origin" "3416 -128 952"
+"classname" "light"
+"light" "100"
+}
+{
+"classname" "monster_stalker"
+"angle" "180"
+"spawnflags" "3841"
+"origin" "3444 64 1178"
+}
+{
+"classname" "monster_daedalus"
+"angle" "90"
+"spawnflags" "3841"
+"origin" "3528 -488 1264"
+}
+{
+"classname" "item_health"
+"origin" "1888 -1104 848"
+}
+{
+"classname" "item_health"
+"origin" "1888 -1024 848"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"spawnflags" "6913"
+"angle" "0"
+"origin" "1768 -1072 1056"
+"target" "t87"
+"targetname" "t88"
+}
+{
+"classname" "ammo_nails"
+"origin" "1768 -1112 1048"
+}
+{
+"classname" "ammo_rockets"
+"origin" "2528 -1120 1048"
+}
+{
+"classname" "item_health_small"
+"origin" "2104 -1240 984"
+}
+{
+"classname" "item_health_small"
+"origin" "2056 -1240 984"
+}
+{
+"classname" "item_health_small"
+"origin" "2184 -1240 984"
+}
+{
+"classname" "item_health_small"
+"origin" "2232 -1240 984"
+}
+{
+"origin" "2496 -896 1088"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "2464 -896 896"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1792 -896 1088"
+}
+{
+"origin" "2048 -1088 1120"
+"classname" "light"
+"light" "125"
+}
+{
+"model" "*87"
+"spawnflags" "2"
+"targetname" "t365"
+"angle" "-1"
+"classname" "func_plat2"
+"_minlight" ".3"
+"lip" "8"
+}
+{
+"classname" "item_health_small"
+"origin" "2488 -536 992"
+}
+{
+"classname" "item_health_large"
+"origin" "2656 -544 984"
+}
+{
+"classname" "ammo_tesla"
+"origin" "2944 -552 984"
+}
+{
+"classname" "point_combat"
+"targetname" "t83"
+"spawnflags" "2049"
+"origin" "3360 -536 912"
+}
+{
+"item" "ammo_shells"
+"classname" "monster_soldier"
+"angle" "270"
+"spawnflags" "2049"
+"target" "t82"
+"origin" "3112 -616 928"
+}
+{
+"classname" "point_combat"
+"targetname" "t81"
+"origin" "2240 -624 856"
+"spawnflags" "2048"
+}
+{
+"classname" "monster_stalker"
+"spawnflags" "6409"
+"angle" "180"
+"targetname" "t80"
+"target" "t81"
+"origin" "2268 -556 1066"
+}
+{
+"model" "*88"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t80"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"spawnflags" "6913"
+"origin" "2184 -608 864"
+"target" "t79"
+"angle" "0"
+}
+{
+"spawnflags" "2049"
+"classname" "point_combat"
+"targetname" "t78"
+"origin" "2616 -584 912"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"target" "t78"
+"origin" "2440 -640 912"
+"targetname" "t79"
+}
+{
+"target" "t123"
+"classname" "monster_stalker"
+"angle" "0"
+"spawnflags" "2825"
+"origin" "2988 -924 1066"
+}
+{
+"targetname" "t31"
+"classname" "monster_daedalus"
+"angle" "225"
+"spawnflags" "6915"
+"origin" "3280 -1528 1208"
+}
+{
+"classname" "ammo_rockets"
+"origin" "2776 -1656 856"
+"spawnflags" "0"
+}
+{
+"classname" "weapon_rocketlauncher"
+"origin" "2960 -1824 856"
+"spawnflags" "5888"
+}
+{
+"classname" "point_combat"
+"spawnflags" "2049"
+"origin" "2864 -1560 1168"
+"targetname" "t77"
+}
+{
+"item" "ammo_grenades"
+"classname" "monster_gunner"
+"angle" "90"
+"spawnflags" "2049"
+"origin" "2848 -1944 1176"
+"target" "t77"
+}
+{
+"classname" "ammo_nails"
+"origin" "3616 -1528 856"
+}
+{
+"classname" "item_health_large"
+"origin" "3200 -1112 856"
+}
+{
+"classname" "item_health_large"
+"origin" "3144 -1112 856"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3200 -1152 1024"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3392 -1152 1024"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3584 -1152 1024"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3584 -1344 1024"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3584 -1536 1024"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3392 -1664 1024"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "3584 -1664 1024"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "3008 -1152 1024"
+}
+{
+"origin" "-80 -2008 784"
+"classname" "item_health_small"
+}
+{
+"origin" "-24 -2080 784"
+"classname" "item_health_small"
+}
+{
+"origin" "-232 -2008 856"
+"classname" "ammo_shells"
+}
+{
+"origin" "108 -2820 994"
+"target" "t76"
+"spawnflags" "2057"
+"classname" "monster_stalker"
+"item" "ammo_cells"
+}
+{
+"origin" "-32 -2720 792"
+"classname" "item_adrenaline"
+"spawnflags" "2048"
+}
+{
+"target" "t204"
+"origin" "-552 -2656 792"
+"angle" "0"
+"spawnflags" "2049"
+"classname" "monster_soldier_ss"
+"item" "ammo_bullets"
+}
+{
+"origin" "-152 -2608 992"
+"target" "t72"
+"spawnflags" "7169"
+"classname" "monster_gunner"
+"item" "ammo_grenades"
+}
+{
+"origin" "-128 -2688 976"
+"targetname" "t72"
+"target" "t71"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "-128 -2096 976"
+"target" "t72"
+"targetname" "t71"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1536 -896 1136"
+}
+{
+"origin" "2496 -1088 1088"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1664 -896 1088"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1536 -768 1088"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2144 -984 1160"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2048 -896 1120"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2240 -896 1120"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2240 -1088 1120"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "2144 -1248 1024"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "2016 -1248 1024"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "1984 -1088 896"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "2144 -1088 896"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "2304 -1088 896"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "2272 -1248 1024"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "2208 -608 1016"
+}
+{
+"origin" "2176 -448 960"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "2432 -640 1016"
+"classname" "light"
+"light" "120"
+}
+{
+"model" "*89"
+"spawnflags" "2"
+"lip" "8"
+"_minlight" ".3"
+"angle" "-2"
+"classname" "func_plat2"
+"targetname" "t368"
+}
+{
+"origin" "2560 -640 1016"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "2688 -640 1016"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "2816 -640 1016"
+"classname" "light"
+"light" "120"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "3232 -640 960"
+}
+{
+"origin" "3328 -128 1040"
+"classname" "light"
+"light" "120"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "3328 64 1272"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "3328 -576 1272"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "3520 -576 1272"
+}
+{
+"origin" "3328 64 1056"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "2944 -640 1016"
+"classname" "light"
+"light" "120"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "3456 64 1272"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "2176 -256 960"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "3072 -640 1016"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "3584 64 960"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "3456 64 960"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "3328 -256 1024"
+}
+{
+"origin" "3680 -160 1272"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "3712 64 960"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "3328 -384 1024"
+"classname" "light"
+"light" "120"
+}
+{
+"origin" "3424 -352 1272"
+"classname" "light"
+"light" "125"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "3680 -352 1272"
+}
+{
+"light" "90"
+"classname" "light"
+"origin" "3336 -128 952"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "3424 -160 1272"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "3616 -608 1024"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "3712 -256 1152"
+"angle" "180"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "3424 -608 1024"
+}
+{
+"classname" "func_group"
+}
+{
+"target" "t76"
+"targetname" "t73"
+"origin" "128 -1896 1072"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"targetname" "t76"
+"target" "t73"
+"origin" "128 -2808 1072"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "-424 -2592 792"
+"spawnflags" "2049"
+"classname" "monster_berserk"
+"angle" "90"
+}
+{
+"origin" "3008 -864 1016"
+"classname" "light"
+"light" "115"
+}
+{
+"model" "*90"
+"team" "towthis"
+"_minlight" ".3"
+"angle" "270"
+"classname" "func_door"
+}
+{
+"model" "*91"
+"team" "towthis"
+"_minlight" ".3"
+"angle" "90"
+"classname" "func_door"
+}
+{
+"origin" "3008 -992 1016"
+"classname" "light"
+"light" "115"
+}
+{
+"origin" "2720 -1248 1216"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "2816 -1888 1216"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2912 -1248 1216"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2240 -1920 1216"
+}
+{
+"origin" "2528 -1784 1272"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "2528 -1928 1272"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2592 -1952 1240"
+"classname" "item_armor_jacket"
+}
+{
+"origin" "2816 -1696 1216"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "2240 -2112 1216"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "2432 -2112 1216"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "80"
+"classname" "light"
+"origin" "3008 -800 992"
+}
+{
+"classname" "target_explosion"
+"targetname" "t66"
+"origin" "752 -928 1256"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2048"
+"classname" "target_explosion"
+"targetname" "t64"
+"origin" "1224 -560 1056"
+}
+{
+"model" "*92"
+"spawnflags" "2048"
+"classname" "func_explosive"
+"mass" "250"
+"health" "25"
+"target" "t64"
+}
+{
+"model" "*93"
+"classname" "trigger_once"
+"target" "t60"
+}
+{
+"noise" "world/comp_hum1.wav"
+"classname" "target_speaker"
+"origin" "-48 -2072 1000"
+}
+{
+"noise" "world/comp_hum3.wav"
+"classname" "target_speaker"
+"origin" "-48 -2176 1000"
+}
+{
+"noise" "world/comp_hum1.wav"
+"classname" "target_speaker"
+"origin" "-48 -2272 1000"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum1.wav"
+"origin" "-56 -2664 1000"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum1.wav"
+"origin" "-48 -2464 1000"
+}
+{
+"classname" "target_speaker"
+"noise" "world/comp_hum2.wav"
+"origin" "-48 -2560 992"
+}
+{
+"classname" "target_explosion"
+"targetname" "t59"
+"origin" "-416 -2336 864"
+"spawnflags" "2048"
+}
+{
+"origin" "-56 -2720 800"
+"light" "80"
+"classname" "light"
+}
+{
+"targetname" "t57"
+"classname" "target_speaker"
+"origin" "-240 -2560 1016"
+"noise" "world/brkglas.wav"
+"spawnflags" "2048"
+}
+{
+"targetname" "t58"
+"noise" "world/brkglas.wav"
+"origin" "-240 -2176 1016"
+"classname" "target_speaker"
+"spawnflags" "2048"
+}
+{
+"origin" "-912 -2328 824"
+"map" "rbase1$rb1a"
+"targetname" "t55"
+"classname" "target_changelevel"
+"spawnflags" "2048"
+}
+{
+"model" "*94"
+"angle" "180"
+"target" "t55"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+}
+{
+"origin" "-920 -2336 880"
+"spawnflags" "2049"
+"targetname" "t54"
+"classname" "target_help"
+"message" "Use transport shuttle to \n enter Tactical Center."
+}
+{
+"message" "Disable power to \n transport shuttle security."
+"origin" "-920 -2400 880"
+"targetname" "t54"
+"classname" "target_help"
+"spawnflags" "2048"
+}
+{
+"model" "*95"
+"target" "t54"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"model" "*96"
+"_minlight" ".3"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"origin" "3448 -2120 984"
+"map" "rware1$rw1a"
+"targetname" "t52"
+"classname" "target_changelevel"
+"spawnflags" "2048"
+}
+{
+"model" "*97"
+"target" "t52"
+"classname" "trigger_multiple"
+"angle" "270"
+"spawnflags" "2048"
+}
+{
+"spawnflags" "2048"
+"origin" "3448 -2128 904"
+"targetname" "t51"
+"classname" "target_help"
+"message" "Locate canyon entrance \n to Logistics complex."
+}
+{
+"origin" "3528 -2128 904"
+"message" "Gain access to \n Logistics Complex."
+"spawnflags" "2049"
+"targetname" "t51"
+"classname" "target_help"
+}
+{
+"model" "*98"
+"target" "t51"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"model" "*99"
+"classname" "trigger_once"
+"target" "t43"
+"spawnflags" "2048"
+}
+{
+"model" "*100"
+"classname" "func_button"
+"angle" "0"
+"lip" "20"
+"_minlight" ".3"
+"wait" "-1"
+"message" "Power conduit accessible."
+"target" "powerdoor"
+"targetname" "t43"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t32"
+"delay" "12"
+"target" "t42"
+"origin" "2744 -2480 1000"
+"spawnflags" "2048"
+}
+{
+"model" "*101"
+"classname" "trigger_hurt"
+"spawnflags" "2075"
+"dmg" "20"
+"targetname" "t42"
+}
+{
+"classname" "func_group"
+"spawnflags" "2048"
+}
+{
+"classname" "func_group"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t32"
+"target" "powerdoor"
+"delay" "8"
+"origin" "2744 -2504 1000"
+"spawnflags" "2048"
+}
+{
+"model" "*102"
+"origin" "2932 -2684 1028"
+"classname" "func_door_rotating"
+"_minlight" ".3"
+"distance" "135"
+"sounds" "2"
+"speed" "15"
+"spawnflags" "2084"
+"team" "conduit"
+"targetname" "powerdoor"
+"message" "Conduit locked."
+"target" "portal1"
+}
+{
+"model" "*103"
+"origin" "2828 -2684 1028"
+"classname" "func_door_rotating"
+"distance" "135"
+"speed" "15"
+"_minlight" ".3"
+"dmg" "50"
+"sounds" "2"
+"spawnflags" "2086"
+"team" "conduit"
+"targetname" "powerdoor"
+"message" "Conduit locked."
+"target" "portal1"
+}
+{
+"model" "*104"
+"classname" "trigger_once"
+"targetname" "t40"
+"message" "Plasma emissions detected."
+"spawnflags" "2048"
+}
+{
+"model" "*105"
+"classname" "trigger_once"
+"targetname" "t41"
+"message" "Conduit lockdown in 10 seconds."
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t32"
+"delay" "4"
+"target" "t41"
+"origin" "2488 -2544 992"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"origin" "2488 -2520 992"
+"delay" "2"
+"targetname" "t32"
+"target" "t40"
+"spawnflags" "2048"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2880 -2752 1024"
+}
+{
+"model" "*106"
+"classname" "trigger_once"
+"targetname" "t32"
+"message" "Power disrupted,security network offline."
+"spawnflags" "2048"
+}
+{
+"classname" "target_lightramp"
+"origin" "2592 -2464 904"
+"message" "az"
+"speed" "3"
+"target" "t38"
+"targetname" "t39"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2472 -2464 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2488 -2568 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2592 -2584 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2696 -2568 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2712 -2464 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2696 -2360 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2592 -2344 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2488 -2360 776"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"spawnflags" "2049"
+"targetname" "t38"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2472 -2464 952"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2592 -2584 952"
+"classname" "light"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2472 -2584 952"
+"classname" "light"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2472 -2344 952"
+"classname" "light"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2592 -2344 952"
+"classname" "light"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2712 -2344 952"
+"classname" "light"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"light" "150"
+"_color" "1.000000 0.000000 0.000000"
+"origin" "2712 -2464 952"
+"classname" "light"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"style" "33"
+"classname" "light"
+"origin" "2712 -2584 952"
+"_color" "1.000000 0.000000 0.000000"
+"light" "150"
+"targetname" "t38"
+"spawnflags" "2049"
+}
+{
+"classname" "func_timer"
+"target" "t37"
+"spawnflags" "1"
+"wait" "3.5"
+"random" "1.5"
+"origin" "2576 -2448 712"
+}
+{
+"classname" "func_timer"
+"origin" "2608 -2480 712"
+"spawnflags" "1"
+"target" "t36"
+"random" "2.5"
+"wait" "4"
+}
+{
+"classname" "func_timer"
+"target" "t35"
+"random" "2"
+"wait" "3.5"
+"spawnflags" "1"
+"origin" "2536 -2584 712"
+}
+{
+"classname" "func_timer"
+"target" "t34"
+"random" "1.75"
+"wait" "3"
+"spawnflags" "1"
+"origin" "2472 -2344 712"
+}
+{
+"classname" "target_splash"
+"angle" "-1"
+"sounds" "1"
+"count" "75"
+"targetname" "t34"
+"origin" "2728 -2464 712"
+"spawnflags" "2048"
+}
+{
+"classname" "target_splash"
+"angle" "-1"
+"sounds" "1"
+"count" "75"
+"targetname" "t35"
+"origin" "2592 -2320 712"
+"spawnflags" "2048"
+}
+{
+"classname" "target_splash"
+"angle" "-1"
+"sounds" "1"
+"count" "75"
+"targetname" "t34"
+"origin" "2456 -2464 712"
+"spawnflags" "2048"
+}
+{
+"classname" "target_splash"
+"angle" "-1"
+"sounds" "1"
+"count" "75"
+"origin" "2504 -2552 712"
+"targetname" "t36"
+}
+{
+"count" "75"
+"sounds" "1"
+"angle" "-1"
+"classname" "target_splash"
+"origin" "2680 -2368 712"
+"targetname" "t36"
+"random" "2.5"
+"wait" "4"
+"spawnflags" "0"
+}
+{
+"classname" "target_splash"
+"angle" "-1"
+"sounds" "1"
+"count" "75"
+"origin" "2504 -2376 712"
+"targetname" "t37"
+}
+{
+"count" "75"
+"sounds" "1"
+"angle" "-1"
+"classname" "target_splash"
+"targetname" "t35"
+"origin" "2592 -2600 712"
+"spawnflags" "2048"
+}
+{
+"classname" "target_splash"
+"angle" "-1"
+"sounds" "1"
+"count" "75"
+"origin" "2680 -2552 712"
+"targetname" "t37"
+}
+{
+"model" "*107"
+"classname" "trigger_hurt"
+"dmg" "1000"
+"spawnflags" "8"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t28"
+"origin" "2456 -2528 960"
+"target" "t39"
+"delay" "3"
+"spawnflags" "2048"
+}
+{
+"classname" "target_lightramp"
+"targetname" "t32"
+"target" "t33"
+"message" "za"
+"speed" "5"
+"origin" "2592 -2464 960"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t28"
+"origin" "2456 -2432 960"
+"target" "t31"
+"delay" "4"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t28"
+"origin" "2456 -2496 960"
+"target" "t32"
+"spawnflags" "2048"
+}
+{
+"light" "100"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "2592 -2288 904"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "100"
+"origin" "2592 -2640 904"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2880 -2752 1208"
+}
+{
+"origin" "2720 -2752 1208"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2592 -2752 1208"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2432 -2752 1208"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2880 -2592 1208"
+}
+{
+"origin" "2880 -2304 1208"
+"light" "115"
+"classname" "light"
+}
+{
+"origin" "2880 -2464 1208"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2432 -2592 1208"
+}
+{
+"origin" "2432 -2304 1208"
+"light" "115"
+"classname" "light"
+}
+{
+"origin" "2432 -2464 1208"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2720 -2304 1208"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2752 -2464 1208"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2688 -2560 792"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "115"
+"origin" "2496 -2560 792"
+"angle" "-1"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "115"
+"origin" "2688 -2368 792"
+"angle" "-1"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2496 -2368 792"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2688 -2368 960"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "115"
+"origin" "2496 -2368 960"
+"angle" "-1"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2496 -2560 960"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2496 -2464 792"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2616 -2120 1216"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "3136 -2752 1088"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2592 -2608 1008"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"angle" "-1"
+"origin" "2656 -2608 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2592 -2608 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2528 -2608 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2736 -2400 1008"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2736 -2464 1008"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "125"
+"origin" "2736 -2528 1008"
+"angle" "-1"
+"_color" "0.000000 0.000000 1.000000"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2736 -2400 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2736 -2464 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "125"
+"origin" "2736 -2528 720"
+"angle" "-1"
+"_color" "0.000000 0.000000 1.000000"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2592 -2368 792"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "115"
+"origin" "2592 -2560 792"
+"angle" "-1"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2688 -2464 792"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "115"
+"origin" "2688 -2560 960"
+"angle" "-1"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2496 -2464 920"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "115"
+"origin" "2688 -2464 920"
+"angle" "-1"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"angle" "-1"
+"origin" "2592 -2560 920"
+"light" "115"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"angle" "-1"
+"origin" "2448 -2528 1008"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "3072 -2944 1216"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "3136 -2752 1216"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2880 -2944 1216"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "125"
+"origin" "2592 -2320 1008"
+"_color" "0.000000 0.000000 1.000000"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2528 -2320 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2592 -2320 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"angle" "-1"
+"origin" "2656 -2320 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "125"
+"origin" "2448 -2400 1008"
+"_color" "0.000000 0.000000 1.000000"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "125"
+"origin" "2448 -2464 1008"
+"_color" "0.000000 0.000000 1.000000"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"classname" "light"
+"light" "115"
+"origin" "2592 -2368 920"
+"angle" "-1"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"model" "*108"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "2752 -2592 1208"
+"classname" "light"
+"light" "115"
+}
+{
+"origin" "2592 -2304 1208"
+"light" "115"
+"classname" "light"
+}
+{
+"origin" "2560 -2464 1208"
+"classname" "light"
+"light" "115"
+}
+{
+"origin" "2560 -2592 1208"
+"light" "115"
+"classname" "light"
+}
+{
+"origin" "2304 -2752 1144"
+"classname" "light"
+"light" "125"
+}
+{
+"model" "*109"
+"targetname" "t29"
+"target" "t28"
+"classname" "trigger_counter"
+"spawnflags" "2048"
+}
+{
+"target" "t29"
+"targetname" "t30"
+"origin" "2592 -2288 904"
+"classname" "target_explosion"
+}
+{
+"target" "t29"
+"origin" "2592 -2640 904"
+"targetname" "t27"
+"classname" "target_explosion"
+}
+{
+"model" "*110"
+"target" "t30"
+"mass" "140"
+"classname" "func_explosive"
+"health" "5"
+"_minlight" ".3"
+"spawnflags" "2048"
+}
+{
+"model" "*111"
+"target" "t27"
+"mass" "140"
+"classname" "func_explosive"
+"health" "5"
+"_minlight" ".3"
+"spawnflags" "2048"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2112 -2752 1144"
+}
+{
+"light" "130"
+"classname" "light"
+"origin" "1952 -2688 1216"
+}
+{
+"classname" "light"
+"origin" "2880 -2464 960"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+}
+{
+"classname" "light"
+"origin" "2880 -2560 1016"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "100"
+"origin" "2800 -2464 896"
+"classname" "light"
+}
+{
+"origin" "2304 -2624 1144"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1952 -2496 1216"
+"classname" "light"
+"light" "130"
+}
+{
+"origin" "1952 -2592 1216"
+"light" "130"
+"classname" "light"
+}
+{
+"model" "*112"
+"spawnflags" "1"
+"_minlight" ".3"
+"angle" "-2"
+"classname" "func_plat2"
+"lip" "8"
+}
+{
+"model" "*113"
+"team" "sealant"
+"spawnflags" "2049"
+"_minlight" ".3"
+"angle" "0"
+"classname" "func_door"
+"targetname" "t31"
+"speed" "20"
+"wait" "-1"
+}
+{
+"model" "*114"
+"team" "sealant"
+"_minlight" ".3"
+"spawnflags" "2049"
+"angle" "180"
+"classname" "func_door"
+"targetname" "t31"
+"wait" "-1"
+"speed" "20"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"angle" "-1"
+"origin" "2448 -2528 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2448 -2464 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"style" "34"
+"_color" "0.000000 0.000000 1.000000"
+"origin" "2448 -2400 720"
+"light" "125"
+"classname" "light"
+"targetname" "t33"
+"spawnflags" "2048"
+}
+{
+"origin" "2560 -2112 1224"
+"classname" "light"
+"light" "115"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "1720 -2432 1144"
+}
+{
+"target" "t24"
+"targetname" "t26"
+"origin" "1676 -2544 1186"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"model" "*115"
+"target" "t24"
+"spawnflags" "2049"
+"_minlight" ".3"
+"classname" "func_train"
+}
+{
+"target" "t25"
+"targetname" "t24"
+"classname" "path_corner"
+"origin" "1676 -2544 1186"
+"spawnflags" "2048"
+}
+{
+"target" "t26"
+"targetname" "t25"
+"origin" "1676 -2544 1082"
+"classname" "path_corner"
+"spawnflags" "2048"
+}
+{
+"origin" "1720 -2328 1144"
+"classname" "light"
+"light" "100"
+"_color" "0.000000 0.501961 1.000000"
+}
+{
+"origin" "1784 -2432 1144"
+"classname" "light"
+"light" "100"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"origin" "1720 -2536 1144"
+"light" "100"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "128 -2112 1024"
+}
+{
+"model" "*116"
+"wait" "-1"
+"lip" "24"
+"target" "t23"
+"_minlight" ".3"
+"angle" "0"
+"classname" "func_button"
+"targetname" "t60"
+"message" "Door access granted."
+"spawnflags" "2048"
+}
+{
+"origin" "-64 -2688 832"
+"classname" "light"
+"light" "100"
+}
+{
+"model" "*117"
+"wait" "-1"
+"message" "Access denied."
+"targetname" "t23"
+"classname" "func_door"
+"angle" "90"
+"_minlight" ".3"
+"team" "finale"
+"spawnflags" "2048"
+}
+{
+"model" "*118"
+"wait" "-1"
+"message" "Access denied."
+"targetname" "t23"
+"classname" "func_door"
+"angle" "270"
+"_minlight" ".3"
+"team" "finale"
+"spawnflags" "2048"
+}
+{
+"origin" "-192 -2624 824"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-448 -1920 1024"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-256 -1920 1024"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-64 -1920 1024"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "128 -1920 1024"
+}
+{
+"origin" "-256 -2816 1024"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-64 -2816 1024"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "128 -2816 1024"
+"light" "125"
+"classname" "light"
+}
+{
+"_color" "1.000000 1.000000 0.501961"
+"origin" "128 -2368 1024"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-180 -2368 1020"
+"light" "100"
+"classname" "light"
+"_color" "0.000000 0.501961 1.000000"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-128 -2432 1024"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-128 -2624 1024"
+}
+{
+"origin" "-128 -2112 1024"
+"light" "125"
+"classname" "light"
+}
+{
+"model" "*119"
+"health" "5"
+"mass" "250"
+"classname" "func_explosive"
+"target" "t59"
+"spawnflags" "2048"
+}
+{
+"origin" "-416 -2528 1016"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-416 -2688 952"
+"classname" "light"
+"light" "125"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-448 -2816 1024"
+}
+{
+"origin" "128 -2624 1024"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-192 -2368 824"
+}
+{
+"light" "100"
+"classname" "light"
+"origin" "-64 -2048 824"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-416 -2208 1016"
+}
+{
+"origin" "-416 -2368 952"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-192 -2112 824"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "-416 -2048 952"
+"light" "125"
+"classname" "light"
+}
+{
+"model" "*120"
+"target" "t57"
+"mass" "300"
+"health" "5"
+"classname" "func_explosive"
+"spawnflags" "2048"
+}
+{
+"model" "*121"
+"target" "t58"
+"health" "5"
+"mass" "300"
+"classname" "func_explosive"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "576 224 1336"
+}
+{
+"origin" "448 -576 1464"
+"light" "120"
+"classname" "light"
+}
+{
+"model" "*122"
+"classname" "func_door"
+"angle" "-2"
+"_minlight" ".3"
+}
+{
+"model" "*123"
+"message" "Cargo doors locked"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-2"
+"sounds" "4"
+"_minlight" ".3"
+"team" "cargo2"
+"targetname" "t7"
+"wait" "-1"
+"speed" "25"
+}
+{
+"model" "*124"
+"message" "Cargo doors locked"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-1"
+"sounds" "4"
+"_minlight" ".3"
+"team" "cargo2"
+"targetname" "t7"
+"wait" "-1"
+"speed" "25"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "256 -384 1464"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "256 -576 1464"
+}
+{
+"model" "*125"
+"health" "25"
+"mass" "250"
+"classname" "func_explosive"
+"target" "t66"
+"spawnflags" "2048"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "640 -960 1272"
+}
+{
+"origin" "832 -960 1272"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "640 -1152 1272"
+}
+{
+"origin" "832 -1152 1272"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "896 -768 1272"
+}
+{
+"classname" "light"
+"light" "125"
+"_color" "1.000000 1.000000 0.000000"
+"origin" "832 -1344 1256"
+}
+{
+"spawnflags" "2048"
+"origin" "864 -1496 1176"
+"classname" "item_adrenaline"
+}
+{
+"origin" "736 -1384 1112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "736 -1464 1112"
+"classname" "item_armor_shard"
+}
+{
+"origin" "800 -1472 1144"
+"classname" "light"
+"light" "100"
+}
+{
+"origin" "640 -768 1272"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1216 128 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "800 -512 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "800 -320 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "800 -128 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "800 64 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "800 256 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "960 384 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1152 416 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1312 416 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1472 416 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1664 384 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1664 128 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1984 128 1272"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "50"
+"origin" "1408 72 1376"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1984 -64 1272"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1984 -256 1272"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1984 -448 1272"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1952 -608 1264"
+}
+{
+"origin" "1952 32 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1952 224 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1728 224 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "1472 224 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "992 224 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "992 32 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "992 -160 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "992 -608 1080"
+"classname" "light"
+"light" "125"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1696 -544 1336"
+}
+{
+"light" "100"
+"origin" "1472 -112 1344"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1344 -112 1344"
+"light" "100"
+}
+{
+"light" "100"
+"origin" "1344 -256 1344"
+"classname" "light"
+}
+{
+"classname" "light"
+"origin" "1472 -256 1344"
+"light" "100"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1792 -1088 1088"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1312 -896 1136"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1088 -896 1136"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1696 -288 1336"
+}
+{
+"origin" "1696 -32 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1184 -288 1336"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "1984 320 1272"
+"light" "125"
+"classname" "light"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1440 -544 1336"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "1184 -544 1336"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "1184 -32 1336"
+}
+{
+"origin" "832 -1352 1024"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"model" "*126"
+"target" "t382"
+"speed" "55"
+"spawnflags" "5889"
+"classname" "func_train"
+}
+{
+"targetname" "t382"
+"target" "t381"
+"origin" "784 -1344 820"
+"classname" "path_corner"
+"spawnflags" "0"
+}
+{
+"origin" "832 -1352 896"
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "125"
+}
+{
+"origin" "832 -1352 1152"
+"light" "125"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "120"
+"origin" "3488 -1720 952"
+}
+{
+"model" "*127"
+"wait" "5"
+"classname" "trigger_multiple"
+"message" "Cargo doors sealed."
+}
+{
+"origin" "3424 -1248 944"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "3328 -1248 944"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "3328 -1344 944"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "3424 -1344 944"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "3520 -896 1016"
+}
+{
+"origin" "2496 160 1096"
+"light" "125"
+"_color" "0.000000 0.501961 1.000000"
+"classname" "light"
+}
+{
+"origin" "2496 252 1096"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "2848 672 1208"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "2656 672 1208"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "2464 672 1208"
+"classname" "light"
+"light" "125"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2464 352 1208"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2656 352 1208"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2848 352 1208"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2464 32 1208"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2656 32 1208"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2848 32 1208"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "2464 -160 1208"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2656 -160 1208"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "2848 -160 1208"
+}
+{
+"origin" "2432 -320 1216"
+"classname" "light"
+"light" "115"
+}
+{
+"_color" "0.000000 0.501961 1.000000"
+"light" "80"
+"classname" "light"
+"origin" "2800 -320 1212"
+}
+{
+"origin" "2656 576 1288"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2656 448 1288"
+}
+{
+"origin" "2816 448 1288"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2816 576 1288"
+}
+{
+"origin" "2496 448 1288"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2496 576 1288"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2496 128 1280"
+}
+{
+"origin" "2496 256 1280"
+"light" "115"
+"classname" "light"
+}
+{
+"origin" "2816 256 1280"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2816 128 1280"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "2656 256 1280"
+}
+{
+"origin" "2656 128 1280"
+"light" "115"
+"classname" "light"
+}
+{
+"classname" "monster_stalker"
+"spawnflags" "3081"
+"origin" "2988 -924 1066"
+} \ No newline at end of file
diff --git a/xatrix/.gitignore b/xatrix/.gitignore
new file mode 100644
index 0000000..cadaacd
--- /dev/null
+++ b/xatrix/.gitignore
@@ -0,0 +1,2 @@
+/build/
+/release/ \ No newline at end of file
diff --git a/xatrix/CHANGELOG b/xatrix/CHANGELOG
new file mode 100644
index 0000000..b2d09da
--- /dev/null
+++ b/xatrix/CHANGELOG
@@ -0,0 +1,129 @@
+The Reckoning 2.11 to 2.12:
+- Relicense under GPL2.
+- Implement `g_swap_speed`. This allows to skip frames of "putting down
+ weapon" and "raising weapon" animations, speeding them up. (by Jaime
+ Moreira)
+- Several fixes to makron (by BjossiAlfreds)
+- Fixed stand-ground gladiators not attacking at certain range. (by
+ BjossiAlfreds)
+- Fixed monsters seeing players during intermissions. (by BjossiAlfreds)
+
+The Reckoning 2.10 to 2.11:
+- Implement faster weapon switching with the new 'cycleweap' command.
+ (by protocultor).
+- Fixes pusher delta yaw manipulation. This fixes the infamous bug were
+ a player standing on a blocked elevator gets turned around (by
+ skuller).
+- Fix several coop related bugs with the powercubes. (by BjossiAlfreds)
+- A way better fix for dead bodies obstructing elevators or falling
+ through the worldmodel. (by BjossiAlfreds)
+- Fix items already in water playing a splash sound at level start. (by
+ BjossiAlfreds)
+- Phalanx explosions should be muffled under water. (by protocultor)
+
+The Reckoning 2.09 to 2.10:
+- Refine the 'g_footstep' cvar to match Quake II itself.
+- Implement 'g_machinegun_norecoil'. The cvar is cheat protected. (by
+ De-Seppe)
+- Fix soldiers never showing their pain skins as long as they're alive.
+ (by BjossiAlfreds)
+- Fix laser guard trying to fire two shots when dying. (Found by
+ drakonorodny and fix suggested by BjossiAlfreds)
+
+The Reckoning 2.08 to 2.09:
+- Fix wrong sound for some items when activated. (by BjossiAlfreds)
+- Port the 'aimfix' cvar. (by Mitchell Richters)
+- Port the 'coop_pickup_weapons' and 'coop_elevator_delay' cvars.
+- Fix a long standing crash occuring when exploding projectiles like
+ grenates or rockets generate sound targets and a least one monster
+ starts moving to one of that targets.
+- Add a cvar `g_footsteps` to control the generation of footstep sound.
+- Move several hard coded map fixes to entity files. Add newly
+ discovered mapfixes to the entity files. (by BjossiAlfreds)
+- Fix several subtile gameplay and entity handling bug. (by
+ BjossiAlfreds)
+
+The Reckoning 2.07 to 2.08:
+- Several fixes for subtile bugs (by BjossiAlfreds)
+
+The Reckoning 2.06 to 2.07:
+- New commands: 'listentities' allows listing of entities. 'teleport'
+ teleports the player to the given coordinates.
+- A lot of fixes for subtle, long standing AI and game play bugs. (by
+ BjossiAlfreds)
+- Fix problem found by PVS studio. (analysis by demoth and fixes by
+ BjossiAlfreds)
+
+The Reckoning 2.05 to 2.06:
+- Small bugfixes and better support for the current version of the
+ Windows build environment.
+
+The Reckoning 2.04 to 2.05:
+- Fix a lot of potential crashes. (reported by Maarakate)
+- Fix monsters running in place.
+- Fix monsters not recognizing the player under some
+ circumstances.
+- Fix a rare progress stopper in xsewer1 in coop. (by Maarakate)
+- Make Brains work again.
+
+The Reckoning 2.03 to 2.04:
+- Make gibs and debris SOLID_BBOX so they move on entities.
+- Switch from an arch whitelist to an "all archs are supported"
+ approach.
+
+The Reckoning 2.02 to 2.03:
+- Add CMake as an optional build system.
+- Fix bug with high velocities in vents in 32bit builds.
+
+The Reckoning 2.01 to 2.02:
+- Coop bugfixes
+
+The Reckoning 2.00 to 2.01:
+- Fix a progress stopper in xware.bsp
+
+The Reckoning 2.00RC to 2.00:
+- Updates to the opened inventory screen
+ were fixed. (By svdijk, merged from
+ baseq2)
+
+The Reckoning 1.09 to 2.00RC:
+- Cleanup of the whole source, nearly every line
+ was audited and touched.
+- Add sanity checks to all function.
+- Fix all known bugs.
+- Merge all missing changes from baseq2.
+
+The Reckoning 1.08 to 1.09
+- Port 'The Reckoning' to Mac OS X.
+
+The Reckoning 1.07 to 1.08
+- Port 'The Reckoning' to Windows.
+- Use randk() instead of rand(), a better PNRG.
+- Fix some potential problems found by scan-build.
+
+The Reckoning 1.06 to 1.07
+- Port the new savegame system from baseq2
+- Reorder the files to reflect the new structure of baseq2
+
+The Reckoning 1.05 to 1.06:
+- Reformat the console output
+
+The Reckoning 1.04 to 1.05:
+- Fix "weapprev" cmd, weapon change via mousewheel should now
+ work without running into loops
+- Fix animation of strogg infantry
+
+The Reckoning 1.04RC2 to 1.04:
+- Just version number change
+
+The Reckoning 1.04RC to 1.04RC2:
+- Saner CFLAGS
+- Do not show the gun symbol when fov is bigger than 91 and
+ cl_gun is set to 2
+
+The Reckoning 1.03 to 1.04RC:
+- Slightly better performance (~10 FPS)
+
+The Reckoning 1.02 to 1.03:
+- Added License
+- Added Readme
diff --git a/xatrix/CMakeLists.txt b/xatrix/CMakeLists.txt
new file mode 100644
index 0000000..e0c98d8
--- /dev/null
+++ b/xatrix/CMakeLists.txt
@@ -0,0 +1,139 @@
+cmake_minimum_required(VERSION 3.0)
+
+# Print a message that using the Makefiles is recommended.
+message(NOTICE: " The CMakeLists.txt is unmaintained. Use the Makefile if possible.")
+
+# Enforce "Debug" as standard build type
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+endif(NOT CMAKE_BUILD_TYPE)
+
+# CMake project configuration
+project(yquake2-xatrix)
+
+# Enforce compiler flags (GCC / Clang compatible, yquake2
+# won't build with another compiler anyways)
+# -Wall -> More warnings
+# -fno-strict-aliasing -> Quake 2 is far away from strict aliasing
+# -fwrapv -> Make signed integer overflows defined
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fno-strict-aliasing -fwrapv")
+
+# Use -O2 as maximum optimization level. -O3 has it's problems with yquake2.
+string(REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
+
+# Operating system
+add_definitions(-DYQ2OSTYPE="${CMAKE_SYSTEM_NAME}")
+
+# Architecture string
+string(REGEX REPLACE "amd64" "x86_64" YQ2_ARCH "${CMAKE_SYSTEM_PROCESSOR}")
+string(REGEX REPLACE "i.86" "i386" YQ2_ARCH "${YQ2_ARCH}")
+string(REGEX REPLACE "^arm.*" "arm" YQ2_ARCH "${YQ2_ARCH}")
+add_definitions(-DYQ2ARCH="${YQ2_ARCH}")
+
+# Linker Flags
+if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
+ list(APPEND XatrixLinkerFlags "-lm")
+else()
+ list(APPEND XatrixLinkerFlags "-lm -rdynamic")
+endif()
+
+set(Xatrix-Source
+ src/monster/berserker/berserker.c
+ src/monster/boss2/boss2.c
+ src/monster/boss3/boss3.c
+ src/monster/boss3/boss31.c
+ src/monster/boss3/boss32.c
+ src/monster/boss5/boss5.c
+ src/monster/brain/brain.c
+ src/monster/chick/chick.c
+ src/monster/fixbot/fixbot.c
+ src/monster/flipper/flipper.c
+ src/monster/float/float.c
+ src/monster/flyer/flyer.c
+ src/monster/gekk/gekk.c
+ src/monster/gladiator/gladb.c
+ src/monster/gladiator/gladiator.c
+ src/monster/gunner/gunner.c
+ src/monster/hover/hover.c
+ src/monster/infantry/infantry.c
+ src/monster/insane/insane.c
+ src/monster/medic/medic.c
+ src/monster/misc/move.c
+ src/monster/mutant/mutant.c
+ src/monster/parasite/parasite.c
+ src/monster/soldier/soldier.c
+ src/monster/supertank/supertank.c
+ src/monster/tank/tank.c
+ src/player/client.c
+ src/player/hud.c
+ src/player/trail.c
+ src/player/view.c
+ src/player/weapon.c
+ src/savegame/savegame.c
+ src/shared/flash.c
+ src/shared/rand.c
+ src/shared/shared.c
+ src/g_ai.c
+ src/g_chase.c
+ src/g_cmds.c
+ src/g_combat.c
+ src/g_func.c
+ src/g_items.c
+ src/g_main.c
+ src/g_misc.c
+ src/g_monster.c
+ src/g_phys.c
+ src/g_spawn.c
+ src/g_svcmds.c
+ src/g_target.c
+ src/g_trigger.c
+ src/g_turret.c
+ src/g_utils.c
+ src/g_weapon.c
+ )
+
+set(Xatrix-Header
+ src/header/game.h
+ src/header/local.h
+ src/header/shared.h
+ src/monster/berserker/berserker.h
+ src/monster/boss2/boss2.h
+ src/monster/boss3/boss31.h
+ src/monster/boss3/boss32.h
+ src/monster/brain/brain.h
+ src/monster/chick/chick.h
+ src/monster/fixbot/fixbot.h
+ src/monster/flipper/flipper.h
+ src/monster/float/float.h
+ src/monster/flyer/flyer.h
+ src/monster/gekk/gekk.h
+ src/monster/gladiator/gladiator.h
+ src/monster/gunner/gunner.h
+ src/monster/hover/hover.h
+ src/monster/infantry/infantry.h
+ src/monster/insane/insane.h
+ src/monster/medic/medic.h
+ src/monster/misc/player.h
+ src/monster/mutant/mutant.h
+ src/monster/parasite/parasite.h
+ src/monster/soldier/soldier.h
+ src/monster/soldier/soldierh.h
+ src/monster/supertank/supertank.h
+ src/monster/tank/tank.h
+ src/savegame/tables/clientfields.h
+ src/savegame/tables/fields.h
+ src/savegame/tables/gamefunc_decs.h
+ src/savegame/tables/gamefunc_list.h
+ src/savegame/tables/gamemmove_decs.h
+ src/savegame/tables/gamemmove_list.h
+ src/savegame/tables/levelfields.h
+ )
+
+# Build the xatrix dynamic library
+add_library(game SHARED ${Xatrix-Source} ${Xatrix-Header})
+set_target_properties(game PROPERTIES
+ PREFIX ""
+ LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/Debug
+ LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/Release
+)
+target_link_libraries(game ${XatrixLinkerFlags})
diff --git a/xatrix/LICENSE b/xatrix/LICENSE
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/xatrix/LICENSE
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/xatrix/Makefile b/xatrix/Makefile
new file mode 100644
index 0000000..c38cc7a
--- /dev/null
+++ b/xatrix/Makefile
@@ -0,0 +1,308 @@
+# ----------------------------------------------------- #
+# Makefile for the xatrix game module for Quake II #
+# #
+# Just type "make" to compile the #
+# - The Reckoning Game (game.so / game.dll) #
+# #
+# Dependencies: #
+# - None, but you need a Quake II to play. #
+# While in theory every one should work #
+# Yamagi Quake II is recommended. #
+# #
+# Platforms: #
+# - FreeBSD #
+# - Linux #
+# - Mac OS X #
+# - OpenBSD #
+# - Windows #
+# ----------------------------------------------------- #
+
+# Detect the OS
+ifdef SystemRoot
+YQ2_OSTYPE ?= Windows
+else
+YQ2_OSTYPE ?= $(shell uname -s)
+endif
+
+# Special case for MinGW
+ifneq (,$(findstring MINGW,$(YQ2_OSTYPE)))
+YQ2_OSTYPE := Windows
+endif
+
+# Detect the architecture
+ifeq ($(YQ2_OSTYPE), Windows)
+ifdef MINGW_CHOST
+ifeq ($(MINGW_CHOST), x86_64-w64-mingw32)
+YQ2_ARCH ?= x86_64
+else # i686-w64-mingw32
+YQ2_ARCH ?= i386
+endif
+else # windows, but MINGW_CHOST not defined
+ifdef PROCESSOR_ARCHITEW6432
+# 64 bit Windows
+YQ2_ARCH ?= $(PROCESSOR_ARCHITEW6432)
+else
+# 32 bit Windows
+YQ2_ARCH ?= $(PROCESSOR_ARCHITECTURE)
+endif
+endif # windows but MINGW_CHOST not defined
+else
+ifneq ($(YQ2_OSTYPE), Darwin)
+# Normalize some abiguous YQ2_ARCH strings
+YQ2_ARCH ?= $(shell uname -m | sed -e 's/i.86/i386/' -e 's/amd64/x86_64/' -e 's/^arm.*/arm/')
+else
+YQ2_ARCH ?= $(shell uname -m)
+endif
+endif
+
+# On Windows / MinGW $(CC) is undefined by default.
+ifeq ($(YQ2_OSTYPE),Windows)
+CC ?= gcc
+endif
+
+# Detect the compiler
+ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1)
+COMPILER := clang
+COMPILERVER := $(shell $(CC) -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/')
+else ifeq ($(shell $(CC) -v 2>&1 | grep -c -E "(gcc version|gcc-Version)"), 1)
+COMPILER := gcc
+COMPILERVER := $(shell $(CC) -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/')
+else
+COMPILER := unknown
+endif
+
+# ----------
+
+# Base CFLAGS. These may be overridden by the environment.
+# Highest supported optimizations are -O2, higher levels
+# will likely break this crappy code.
+ifdef DEBUG
+CFLAGS ?= -O0 -g -Wall -pipe
+else
+CFLAGS ?= -O2 -Wall -pipe -fomit-frame-pointer
+endif
+
+# Always needed are:
+# -fno-strict-aliasing since the source doesn't comply
+# with strict aliasing rules and it's next to impossible
+# to get it there...
+# -fwrapv for defined integer wrapping. MSVC6 did this
+# and the game code requires it.
+override CFLAGS += -std=gnu99 -fno-strict-aliasing -fwrapv
+
+# -MMD to generate header dependencies. Unsupported by
+# the Clang shipped with OS X.
+ifneq ($(YQ2_OSTYPE), Darwin)
+override CFLAGS += -MMD
+endif
+
+# OS X architecture.
+ifeq ($(YQ2_OSTYPE), Darwin)
+override CFLAGS += -arch $(YQ2_ARCH)
+endif
+
+# ----------
+
+# Switch of some annoying warnings.
+ifeq ($(COMPILER), clang)
+ # -Wno-missing-braces because otherwise clang complains
+ # about totally valid 'vec3_t bla = {0}' constructs.
+ CFLAGS += -Wno-missing-braces
+else ifeq ($(COMPILER), gcc)
+ # GCC 8.0 or higher.
+ ifeq ($(shell test $(COMPILERVER) -ge 80000; echo $$?),0)
+ # -Wno-format-truncation and -Wno-format-overflow
+ # because GCC spams about 50 false positives.
+ CFLAGS += -Wno-format-truncation -Wno-format-overflow
+ endif
+endif
+
+# ----------
+
+# Defines the operating system and architecture
+override CFLAGS += -DYQ2OSTYPE=\"$(YQ2_OSTYPE)\" -DYQ2ARCH=\"$(YQ2_ARCH)\"
+
+# ----------
+
+# For reproduceable builds, look here for details:
+# https://reproducible-builds.org/specs/source-date-epoch/
+ifdef SOURCE_DATE_EPOCH
+CFLAGS += -DBUILD_DATE=\"$(shell date --utc --date="@${SOURCE_DATE_EPOCH}" +"%b %_d %Y" | sed -e 's/ /\\ /g')\"
+endif
+
+# ----------
+
+# Using the default x87 float math on 32bit x86 causes rounding trouble
+# -ffloat-store could work around that, but the better solution is to
+# just enforce SSE - every x86 CPU since Pentium3 supports that
+# and this should even improve the performance on old CPUs
+ifeq ($(YQ2_ARCH), i386)
+override CFLAGS += -msse -mfpmath=sse
+endif
+
+# Force SSE math on x86_64. All sane compilers should do this
+# anyway, just to protect us from broken Linux distros.
+ifeq ($(YQ2_ARCH), x86_64)
+override CFLAGS += -mfpmath=sse
+endif
+
+# ----------
+
+# Base LDFLAGS.
+LDFLAGS ?=
+
+# It's a shared library.
+override LDFLAGS += -shared
+
+# Required libaries
+ifeq ($(YQ2_OSTYPE), Darwin)
+override LDFLAGS += -arch $(YQ2_ARCH)
+else ifeq ($(YQ2_OSTYPE), Windows)
+override LDFLAGS += -static-libgcc
+else
+override LDFLAGS += -lm
+endif
+
+# ----------
+
+# Builds everything
+all: xatrix
+
+# ----------
+
+# When make is invoked by "make VERBOSE=1" print
+# the compiler and linker commands.
+
+ifdef VERBOSE
+Q :=
+else
+Q := @
+endif
+
+# ----------
+
+# Phony targets
+.PHONY : all clean xatrix
+
+# ----------
+
+# Cleanup
+clean:
+ @echo "===> CLEAN"
+ ${Q}rm -Rf build release
+
+# ----------
+
+# The xatrix game
+ifeq ($(YQ2_OSTYPE), Windows)
+xatrix:
+ @echo "===> Building game.dll"
+ ${Q}mkdir -p release
+ ${MAKE} release/game.dll
+else ifeq ($(YQ2_OSTYPE), Darwin)
+xatrix:
+ @echo "===> Building game.dylib"
+ ${Q}mkdir -p release
+ $(MAKE) release/game.dylib
+else
+xatrix:
+ @echo "===> Building game.so"
+ ${Q}mkdir -p release
+ $(MAKE) release/game.so
+
+release/game.so : CFLAGS += -fPIC
+endif
+
+build/%.o: %.c
+ @echo "===> CC $<"
+ ${Q}mkdir -p $(@D)
+ ${Q}$(CC) -c $(CFLAGS) -o $@ $<
+
+# ----------
+
+XATRIX_OBJS_ = \
+ src/g_ai.o \
+ src/g_chase.o \
+ src/g_cmds.o \
+ src/g_combat.o \
+ src/g_func.o \
+ src/g_items.o \
+ src/g_main.o \
+ src/g_misc.o \
+ src/g_monster.o \
+ src/g_phys.o \
+ src/g_spawn.o \
+ src/g_svcmds.o \
+ src/g_target.o \
+ src/g_trigger.o \
+ src/g_turret.o \
+ src/g_utils.o \
+ src/g_weapon.o \
+ src/monster/berserker/berserker.o \
+ src/monster/boss2/boss2.o \
+ src/monster/boss3/boss3.o \
+ src/monster/boss3/boss31.o \
+ src/monster/boss3/boss32.o \
+ src/monster/boss5/boss5.o \
+ src/monster/brain/brain.o \
+ src/monster/chick/chick.o \
+ src/monster/fixbot/fixbot.o \
+ src/monster/flipper/flipper.o \
+ src/monster/float/float.o \
+ src/monster/flyer/flyer.o \
+ src/monster/gekk/gekk.o \
+ src/monster/gladiator/gladb.o \
+ src/monster/gladiator/gladiator.o \
+ src/monster/gunner/gunner.o \
+ src/monster/hover/hover.o \
+ src/monster/infantry/infantry.o \
+ src/monster/insane/insane.o \
+ src/monster/medic/medic.o \
+ src/monster/misc/move.o \
+ src/monster/mutant/mutant.o \
+ src/monster/parasite/parasite.o \
+ src/monster/soldier/soldier.o \
+ src/monster/supertank/supertank.o \
+ src/monster/tank/tank.o \
+ src/player/client.o \
+ src/player/hud.o \
+ src/player/trail.o \
+ src/player/view.o \
+ src/player/weapon.o \
+ src/savegame/savegame.o \
+ src/shared/flash.o \
+ src/shared/rand.o \
+ src/shared/shared.o
+
+# ----------
+
+# Rewrite paths to our object directory
+XATRIX_OBJS = $(patsubst %,build/%,$(XATRIX_OBJS_))
+
+# ----------
+
+# Generate header dependencies
+XATRIX_DEPS= $(XATRIX_OBJS:.o=.d)
+
+# ----------
+
+# Suck header dependencies in
+-include $(XATRIX_DEPS)
+
+# ----------
+
+ifeq ($(YQ2_OSTYPE), Windows)
+release/game.dll : $(XATRIX_OBJS)
+ @echo "===> LD $@"
+ ${Q}$(CC) -o $@ $(XATRIX_OBJS) $(LDFLAGS)
+else ifeq ($(YQ2_OSTYPE), Darwin)
+release/game.dylib : $(XATRIX_OBJS)
+ @echo "===> LD $@"
+ ${Q}$(CC) -o $@ $(XATRIX_OBJS) $(LDFLAGS)
+else
+release/game.so : $(XATRIX_OBJS)
+ @echo "===> LD $@"
+ ${Q}$(CC) -o $@ $(XATRIX_OBJS) $(LDFLAGS)
+endif
+
+# ----------
diff --git a/xatrix/README b/xatrix/README
new file mode 100644
index 0000000..a65304e
--- /dev/null
+++ b/xatrix/README
@@ -0,0 +1,50 @@
+This is a bugfixed version of id Software's Quake II missionpack
+"The Reckoning", developed by Xatrix Software. Hundred bugs were
+fixed, this version should run much more stable than the the old
+SDK version. It should be used with the "Yamagi Quake II Client",
+but may work with other clients too. For more information visit
+http://www.yamagi.org/quake2.
+
+Installation for FreeBSD, Linux and OpenBSD:
+--------------------------------------------
+1. Type "make" or "gmake" to compile the game.so.
+2. Create a subdirectory xatrix/ in your quake2 directory.
+3. Copy pak0.pak and videos/ from the the Reckoning CD to
+ the newly created directory xatrix/.
+4. Copy release/game.so to xatrix/.
+5. Start the game with "./quake2 +set game xatrix"
+
+Installation for OS X:
+----------------------
+1. Create a subdirectory xatrix/ in your quake2 directory.
+2. Copy pak0.pak and videos/ from the the Reckoning CD to
+ the newly created directory xatrix/.
+3. Copy game.so from the zip-archive to xatrix/.
+4. Start the game with "quake2 +set game xatrix"
+
+If you want to compile 'xatrix' for OS X from source, please take a
+look at the "Installation" section of the README of the Yamagi Quake II
+client. In the same file the integration into an app-bundle is
+explained.
+
+Installation for Windows:
+-------------------------
+1. Create a subdirectory xatrix\ in your quake2 directory.
+2. Copy pak0.pak and videos\ from the the Reckoning CD to
+ the newly created directory xatrix\.
+3. Copy game.dll from the zip-archive to xatrix/.
+4. Start the game with "quake2.exe +set game xatrix"
+
+If you want to compile 'xatrix' for Windows from source, please take a
+look at the "Installation" section of the README of the Yamagi Quake II
+client. There's descripted how to setup the build environment.
+
+=======================================================================
+
+FAQ
+---
+
+The sounds after starting "xship" aka "Stroggos Freightener" are
+distorted and hurting my ears.
+ - Yeah, the quality of the sound samples is very low. Not much
+ we can do about it...
diff --git a/xatrix/src/g_ai.c b/xatrix/src/g_ai.c
new file mode 100644
index 0000000..ae69e1b
--- /dev/null
+++ b/xatrix/src/g_ai.c
@@ -0,0 +1,1286 @@
+/* =======================================================================
+ *
+ * The basic AI functions like enemy detection, attacking and so on.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+qboolean FindTarget(edict_t *self);
+extern cvar_t *maxclients;
+qboolean ai_checkattack(edict_t *self, float dist);
+qboolean enemy_vis;
+qboolean enemy_infront;
+int enemy_range;
+float enemy_yaw;
+
+/* ============================================================================ */
+
+/*
+ * Called once each frame to set level.sight_client
+ * to the player to be checked for in findtarget.
+ * If all clients are either dead or in notarget,
+ * sight_client will be null. In coop games,
+ * sight_client will cycle between the clients.
+ */
+void
+AI_SetSightClient(void)
+{
+ edict_t *ent;
+ int start, check;
+
+ if (level.sight_client == NULL)
+ {
+ start = 1;
+ }
+ else
+ {
+ start = level.sight_client - g_edicts;
+ }
+
+ check = start;
+
+ while (1)
+ {
+ check++;
+
+ if (check > game.maxclients)
+ {
+ check = 1;
+ }
+
+ ent = &g_edicts[check];
+
+ if (ent->inuse &&
+ (ent->health > 0) &&
+ !(ent->flags & FL_NOTARGET))
+ {
+ level.sight_client = ent;
+ return; /* got one */
+ }
+
+ if (check == start)
+ {
+ level.sight_client = NULL;
+ return; /* nobody to see */
+ }
+ }
+}
+
+/* ============================================================================ */
+
+/*
+ * Move the specified distance at current
+ * facing. This replaces the QC functions:
+ * ai_forward, ai_back, ai_pain, and ai_painforward
+ */
+void
+ai_move(edict_t *self, float dist)
+{
+ M_walkmove(self, self->s.angles[YAW], dist);
+}
+
+/*
+ * Used for standing around and looking for
+ * players. Distance is for slight position
+ * adjustments needed by the animations
+ */
+void
+ai_stand(edict_t *self, float dist)
+{
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (dist)
+ {
+ M_walkmove(self, self->s.angles[YAW], dist);
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ if (self->enemy)
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, v);
+ self->ideal_yaw = vectoyaw(v);
+
+ if ((self->s.angles[YAW] != self->ideal_yaw) &&
+ self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+ {
+ self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+ self->monsterinfo.run(self);
+ }
+
+ M_ChangeYaw(self);
+ ai_checkattack(self, 0);
+ }
+ else
+ {
+ FindTarget(self);
+ }
+
+ return;
+ }
+
+ if (FindTarget(self))
+ {
+ return;
+ }
+
+ if (level.time > self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.walk(self);
+ return;
+ }
+
+ if (!(self->spawnflags & 1) && (self->monsterinfo.idle) &&
+ (level.time > self->monsterinfo.idle_time))
+ {
+ if (self->monsterinfo.idle_time)
+ {
+ self->monsterinfo.idle(self);
+ self->monsterinfo.idle_time = level.time + 15 + random() * 15;
+ }
+ else
+ {
+ self->monsterinfo.idle_time = level.time + random() * 15;
+ }
+ }
+}
+
+/*
+ * The monster is walking it's beat
+ */
+void
+ai_walk(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ M_MoveToGoal(self, dist);
+
+ /* check for noticing a player */
+ if (FindTarget(self))
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
+ {
+ if (self->monsterinfo.idle_time)
+ {
+ self->monsterinfo.search(self);
+ self->monsterinfo.idle_time = level.time + 15 + random() * 15;
+ }
+ else
+ {
+ self->monsterinfo.idle_time = level.time + random() * 15;
+ }
+ }
+}
+
+/*
+ * Turns towards target and advances.
+ * Use this call with a distnace of 0
+ * to replace ai_face
+ */
+void
+ai_charge(edict_t *self, float dist)
+{
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if(self->enemy)
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, v);
+ }
+
+ self->ideal_yaw = vectoyaw(v);
+ M_ChangeYaw(self);
+
+ if (dist)
+ {
+ M_walkmove(self, self->s.angles[YAW], dist);
+ }
+}
+
+/*
+ * Don't move, but turn towards ideal_yaw
+ * Distance is for slight position adjustments
+ * needed by the animations
+ */
+void
+ai_turn(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (dist)
+ {
+ M_walkmove(self, self->s.angles[YAW], dist);
+ }
+
+ if (FindTarget(self))
+ {
+ return;
+ }
+
+ M_ChangeYaw(self);
+}
+
+/*
+ * .enemy
+ * Will be world if not currently angry at anyone.
+ *
+ * .movetarget
+ * The next path spot to walk toward. If .enemy, ignore .movetarget.
+ * When an enemy is killed, the monster will try to return to it's path.
+ *
+ * .hunt_time
+ * Set to time + something when the player is in sight, but movement straight for
+ * him is blocked. This causes the monster to use wall following code for
+ * movement direction instead of sighting on the player.
+ *
+ * .ideal_yaw
+ * A yaw angle of the intended direction, which will be turned towards at up
+ * to 45 deg / state. If the enemy is in view and hunt_time is not active,
+ * this will be the exact line towards the enemy.
+ *
+ * .pausetime
+ * A monster will leave it's stand state and head towards it's .movetarget when
+ * time > .pausetime.
+ *
+ * walkmove(angle, speed) primitive is all or nothing
+ */
+
+/*
+ * returns the range catagorization of an entity reletive to self
+ * 0 melee range, will become hostile even if back is turned
+ * 1 visibility and infront, or visibility and show hostile
+ * 2 infront and show hostile
+ * 3 only triggered by damage
+ */
+int
+range(edict_t *self, edict_t *other)
+{
+ vec3_t v;
+ float len;
+
+ if (!self || !other)
+ {
+ return 0;
+ }
+
+ VectorSubtract(self->s.origin, other->s.origin, v);
+ len = VectorLength(v);
+
+ if (len < MELEE_DISTANCE)
+ {
+ return RANGE_MELEE;
+ }
+
+ if (len < 500)
+ {
+ return RANGE_NEAR;
+ }
+
+ if (len < 1000)
+ {
+ return RANGE_MID;
+ }
+
+ return RANGE_FAR;
+}
+
+/*
+ * returns 1 if the entity is visible
+ * to self, even if not infront()
+ */
+qboolean
+visible(edict_t *self, edict_t *other)
+{
+ vec3_t spot1;
+ vec3_t spot2;
+ trace_t trace;
+
+ if (!self || !other)
+ {
+ return false;
+ }
+
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(other->s.origin, spot2);
+ spot2[2] += other->viewheight;
+ trace = gi.trace(spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * returns 1 if the entity is in
+ * front (in sight) of self
+ */
+qboolean
+infront(edict_t *self, edict_t *other)
+{
+ vec3_t vec;
+ float dot;
+ vec3_t forward;
+
+ if (!self || !other)
+ {
+ return false;
+ }
+
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ VectorSubtract(other->s.origin, self->s.origin, vec);
+ VectorNormalize(vec);
+ dot = DotProduct(vec, forward);
+
+ if (dot > 0.3)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/* ============================================================================ */
+
+void
+HuntTarget(edict_t *self)
+{
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->goalentity = self->enemy;
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.stand(self);
+ }
+ else
+ {
+ self->monsterinfo.run(self);
+ }
+
+ if(visible(self, self->enemy))
+ {
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ }
+
+ self->ideal_yaw = vectoyaw(vec);
+
+ /* wait a while before first attack */
+ if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+ {
+ AttackFinished(self, 1);
+ }
+}
+
+void
+FoundTarget(edict_t *self)
+{
+ if (!self|| !self->enemy || !self->enemy->inuse)
+ {
+ return;
+ }
+
+ /* let other monsters see this monster for a while */
+ if (self->enemy->client)
+ {
+ level.sight_entity = self;
+ level.sight_entity_framenum = level.framenum;
+ level.sight_entity->light_level = 128;
+ }
+
+ self->show_hostile = level.time + 1; /* wake up other monsters */
+
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.trail_time = level.time;
+
+ if (!self->combattarget)
+ {
+ HuntTarget(self);
+ return;
+ }
+
+ self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
+
+ if (!self->movetarget)
+ {
+ self->goalentity = self->movetarget = self->enemy;
+ HuntTarget(self);
+ gi.dprintf("%s at %s, combattarget %s not found\n",
+ self->classname, vtos(self->s.origin),
+ self->combattarget);
+ return;
+ }
+
+ /* clear out our combattarget, these are a one shot deal */
+ self->combattarget = NULL;
+ self->monsterinfo.aiflags |= AI_COMBAT_POINT;
+
+ /* clear the targetname, that point is ours! */
+ self->movetarget->targetname = NULL;
+ self->monsterinfo.pausetime = 0;
+
+ /* run for it */
+ self->monsterinfo.run(self);
+}
+
+/*
+ * Self is currently not attacking anything, so try
+ * to find a target. Returns TRUE if an enemy was sighted.
+ * When a player fires a missile, the point of impact
+ * becomes a fakeplayer so that monsters that see the
+ * impact will respond as if they had seen the player.
+ *
+ * To avoid spending too much time, only a single client
+ * (or fakeclient) is checked each frame. This means multi
+ * player games will have slightly slower noticing monsters.
+ */
+qboolean
+FindTarget(edict_t *self)
+{
+ edict_t *client;
+ qboolean heardit;
+ int r;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_GOOD_GUY)
+ {
+ return false;
+ }
+
+ /* if we're going to a combat point, just proceed */
+ if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+ {
+ return false;
+ }
+
+ /* if the first spawnflag bit is set, the
+ monster will only wake up on really seeing
+ the player, not another monster getting angry
+ or hearing something */
+ heardit = false;
+
+ if ((level.sight_entity_framenum >= (level.framenum - 1)) &&
+ !(self->spawnflags & 1))
+ {
+ client = level.sight_entity;
+
+ if (client->enemy == self->enemy)
+ {
+ return false;
+ }
+ }
+ else if (level.sound_entity_framenum >= (level.framenum - 1))
+ {
+ client = level.sound_entity;
+ heardit = true;
+ }
+ else if (!(self->enemy) &&
+ (level.sound2_entity_framenum >= (level.framenum - 1)) &&
+ !(self->spawnflags & 1))
+ {
+ client = level.sound2_entity;
+ heardit = true;
+ }
+ else
+ {
+ client = level.sight_client;
+ }
+
+ /* if the entity went away, forget it */
+ if (!client || !client->inuse ||
+ (client->client && level.intermissiontime))
+ {
+ return false;
+ }
+
+ if (client == self->enemy)
+ {
+ return true;
+ }
+
+ if (client->client)
+ {
+ if (client->flags & FL_NOTARGET)
+ {
+ return false;
+ }
+ }
+ else if (client->svflags & SVF_MONSTER)
+ {
+ if (!client->enemy)
+ {
+ return false;
+ }
+
+ if (client->enemy->flags & FL_NOTARGET)
+ {
+ return false;
+ }
+ }
+ else if (heardit)
+ {
+ if (client->owner->flags & FL_NOTARGET)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ if (!heardit)
+ {
+ r = range(self, client);
+
+ if (r == RANGE_FAR)
+ {
+ return false;
+ }
+
+ /* is client in an spot too dark to be seen? */
+ if (client->light_level <= 5)
+ {
+ return false;
+ }
+
+ if (!visible(self, client))
+ {
+ return false;
+ }
+
+ if (r == RANGE_NEAR)
+ {
+ if ((client->show_hostile < level.time) && !infront(self, client))
+ {
+ return false;
+ }
+ }
+ else if (r == RANGE_MID)
+ {
+ if (!infront(self, client))
+ {
+ return false;
+ }
+ }
+
+ self->enemy = client;
+
+ if (strcmp(self->enemy->classname, "player_noise") != 0)
+ {
+ self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+ if (!self->enemy->client)
+ {
+ self->enemy = self->enemy->enemy;
+
+ if (!self->enemy->client)
+ {
+ self->enemy = NULL;
+ return false;
+ }
+ }
+ }
+ }
+ else /* heardit */
+ {
+ vec3_t temp;
+
+ if (self->spawnflags & 1)
+ {
+ if (!visible(self, client))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!gi.inPHS(self->s.origin, client->s.origin))
+ {
+ return false;
+ }
+ }
+
+ VectorSubtract(client->s.origin, self->s.origin, temp);
+
+ if (VectorLength(temp) > 1000) /* too far to hear */
+ {
+ return false;
+ }
+
+ /* check area portals - if they are different
+ and not connected then we can't hear it */
+ if (client->areanum != self->areanum)
+ {
+ if (!gi.AreasConnected(self->areanum, client->areanum))
+ {
+ return false;
+ }
+ }
+
+ self->ideal_yaw = vectoyaw(temp);
+ M_ChangeYaw(self);
+
+ /* hunt the sound for a bit; hopefully find the real player */
+ self->monsterinfo.aiflags |= AI_SOUND_TARGET;
+ self->enemy = client;
+ }
+
+ /* got one */
+ FoundTarget(self);
+
+ if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) &&
+ (self->monsterinfo.sight))
+ {
+ self->monsterinfo.sight(self, self->enemy);
+ }
+
+ return true;
+}
+
+/* ============================================================================= */
+
+qboolean
+FacingIdeal(edict_t *self)
+{
+ float delta;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
+
+ if ((delta > 45) && (delta < 315))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+/* ============================================================================= */
+
+qboolean
+M_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ float chance;
+ trace_t tr;
+
+ if (!self || !self->enemy || !self->enemy->inuse)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA | CONTENTS_WINDOW);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ return false;
+ }
+ }
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ /* don't always melee in easy mode */
+ if ((skill->value == SKILL_EASY) && (rand() & 3))
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.1;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.02;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ chance *= 0.5;
+ }
+ else if (skill->value >= SKILL_HARD)
+ {
+ chance *= 2;
+ }
+
+ if (random() < chance)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.3)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Turn and close until within
+ * an angle to launch a melee attack
+ */
+void
+ai_run_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->ideal_yaw = enemy_yaw;
+ M_ChangeYaw(self);
+
+ if (FacingIdeal(self))
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.melee(self);
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+}
+
+/*
+ * Turn in place until within
+ * an angle to launch a missile attack
+ */
+void
+ai_run_missile(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->ideal_yaw = enemy_yaw;
+ M_ChangeYaw(self);
+
+ if (FacingIdeal(self))
+ {
+ if (self->monsterinfo.attack) {
+ self->monsterinfo.attack(self);
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+}
+
+/*
+ * Strafe sideways, but stay
+ * at aproximately the same range
+ */
+void
+ai_run_slide(edict_t *self, float distance)
+{
+ float ofs;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->ideal_yaw = enemy_yaw;
+ M_ChangeYaw(self);
+
+ if (self->monsterinfo.lefty)
+ {
+ ofs = 90;
+ }
+ else
+ {
+ ofs = -90;
+ }
+
+ if (M_walkmove(self, self->ideal_yaw + ofs, distance))
+ {
+ return;
+ }
+
+ self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+ M_walkmove(self, self->ideal_yaw - ofs, distance);
+}
+
+/*
+ * Decides if we're going to
+ * attack or do something else
+ */
+static qboolean
+hesDeadJim(const edict_t *self)
+{
+ const edict_t *enemy = self->enemy;
+
+ if (!enemy || !enemy->inuse)
+ {
+ return true;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ return (enemy->health > 0);
+ }
+
+ if (enemy->client && level.intermissiontime)
+ {
+ return true;
+ }
+
+ if (self->monsterinfo.aiflags & AI_BRUTAL)
+ {
+ return (enemy->health <= -80);
+ }
+
+ return (enemy->health <= 0);
+}
+
+qboolean
+ai_checkattack(edict_t *self, float dist)
+{
+ vec3_t temp;
+
+ if (!self)
+ {
+ enemy_vis = false;
+
+ return false;
+ }
+
+ /* this causes monsters to run blindly
+ to the combat point w/o firing */
+ if (self->goalentity)
+ {
+ if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+ {
+ return false;
+ }
+
+ if ((self->monsterinfo.aiflags & AI_SOUND_TARGET) && !visible(self, self->goalentity))
+ {
+ if ((level.time - self->enemy->last_sound_time) > 5.0)
+ {
+ if (self->goalentity == self->enemy)
+ {
+ if (self->movetarget)
+ {
+ self->goalentity = self->movetarget;
+ }
+ else
+ {
+ self->goalentity = NULL;
+ }
+ }
+
+ self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+ if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+ {
+ self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+ }
+ }
+ else
+ {
+ self->show_hostile = level.time + 1;
+ return false;
+ }
+ }
+ }
+
+ enemy_vis = false;
+
+ /* see if the enemy is dead */
+ if (hesDeadJim(self))
+ {
+ self->enemy = NULL;
+ self->monsterinfo.aiflags &= ~AI_MEDIC;
+
+ if (self->oldenemy && (self->oldenemy->health > 0))
+ {
+ self->enemy = self->oldenemy;
+ self->oldenemy = NULL;
+ HuntTarget(self);
+ }
+ else
+ {
+ if (self->movetarget)
+ {
+ self->goalentity = self->movetarget;
+ self->monsterinfo.walk(self);
+ }
+ else
+ {
+ /* we need the pausetime otherwise the stand code
+ will just revert to walking with no target and
+ the monsters will wonder around aimlessly trying
+ to hunt the world entity */
+ self->monsterinfo.pausetime = level.time + 100000000;
+ self->monsterinfo.stand(self);
+ }
+
+ return true;
+ }
+ }
+
+ self->show_hostile = level.time + 1; /* wake up other monsters */
+
+ /* check knowledge of enemy */
+ enemy_vis = visible(self, self->enemy);
+
+ if (enemy_vis)
+ {
+ self->monsterinfo.search_time = level.time + 5;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ }
+
+ if (coop && coop->value && (self->monsterinfo.search_time < level.time))
+ {
+ if (FindTarget(self))
+ {
+ return true;
+ }
+ }
+
+ if (self->enemy)
+ {
+ enemy_infront = infront(self, self->enemy);
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+ }
+
+ if (self->monsterinfo.attack_state == AS_MISSILE)
+ {
+ ai_run_missile(self);
+ return true;
+ }
+
+ if (self->monsterinfo.attack_state == AS_MELEE)
+ {
+ ai_run_melee(self);
+ return true;
+ }
+
+ /* if enemy is not currently
+ visible, we will never attack */
+ if (!enemy_vis)
+ {
+ return false;
+ }
+
+ return self->monsterinfo.checkattack(self);
+}
+
+/*
+ * The monster has an enemy it is trying to kill
+ */
+void
+ai_run(edict_t *self, float dist)
+{
+ vec3_t v;
+ edict_t *tempgoal;
+ edict_t *save;
+ qboolean new;
+ edict_t *marker;
+ float d1, d2;
+ trace_t tr;
+ vec3_t v_forward, v_right;
+ float left, center, right;
+ vec3_t left_target, right_target;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we're going to a combat point, just proceed */
+ if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+ {
+ M_MoveToGoal(self, dist);
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+ {
+ /* Special case: Some projectiles like grenades or rockets are
+ classified as an enemy. When they explode they generate a
+ sound entity, triggering this code path. Since they're gone
+ after the explosion their entity pointer is NULL. Therefor
+ self->enemy is also NULL and we're crashing. Work around
+ this by predending that the enemy is still there, and move
+ to it. */
+ if (self->enemy)
+ {
+ VectorSubtract(self->s.origin, self->enemy->s.origin, v);
+
+ if (VectorLength(v) < 64)
+ {
+ self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+ self->monsterinfo.stand(self);
+ return;
+ }
+ }
+
+ M_MoveToGoal(self, dist);
+
+ if (!FindTarget(self))
+ {
+ return;
+ }
+ }
+
+ if (ai_checkattack(self, dist))
+ {
+ return;
+ }
+
+ if (self->monsterinfo.attack_state == AS_SLIDING)
+ {
+ ai_run_slide(self, dist);
+ return;
+ }
+
+ if (enemy_vis)
+ {
+ M_MoveToGoal(self, dist);
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.trail_time = level.time;
+ return;
+ }
+
+ if ((self->monsterinfo.search_time) &&
+ (level.time > (self->monsterinfo.search_time + 20)))
+ {
+ M_MoveToGoal(self, dist);
+ self->monsterinfo.search_time = 0;
+ return;
+ }
+
+ tempgoal = G_SpawnOptional();
+
+ if (!tempgoal)
+ {
+ M_MoveToGoal(self, dist);
+ return;
+ }
+
+ save = self->goalentity;
+ self->goalentity = tempgoal;
+
+ new = false;
+
+ if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
+ {
+ /* just lost sight of the player, decide where to go first */
+ self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
+ self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
+ new = true;
+ }
+
+ if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
+ {
+ self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
+
+ /* give ourself more time since we got this far */
+ self->monsterinfo.search_time = level.time + 5;
+
+ if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
+ {
+ self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
+ marker = NULL;
+ VectorCopy(self->monsterinfo.saved_goal,
+ self->monsterinfo.last_sighting);
+ new = true;
+ }
+ else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
+ {
+ self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
+ marker = PlayerTrail_PickFirst(self);
+ }
+ else
+ {
+ marker = PlayerTrail_PickNext(self);
+ }
+
+ if (marker)
+ {
+ VectorCopy(marker->s.origin, self->monsterinfo.last_sighting);
+ self->monsterinfo.trail_time = marker->timestamp;
+ self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
+ new = true;
+ }
+ }
+
+ VectorSubtract(self->s.origin, self->monsterinfo.last_sighting, v);
+ d1 = VectorLength(v);
+
+ if (d1 <= dist)
+ {
+ self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
+ dist = d1;
+ }
+
+ VectorCopy(self->monsterinfo.last_sighting, self->goalentity->s.origin);
+
+ if (new)
+ {
+ tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting,
+ self, MASK_PLAYERSOLID);
+
+ if (tr.fraction < 1)
+ {
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ d1 = VectorLength(v);
+ center = tr.fraction;
+ d2 = d1 * ((center + 1) / 2);
+ self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+ AngleVectors(self->s.angles, v_forward, v_right, NULL);
+
+ VectorSet(v, d2, -16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward, v_right, left_target);
+ tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target,
+ self, MASK_PLAYERSOLID);
+ left = tr.fraction;
+
+ VectorSet(v, d2, 16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward, v_right, right_target);
+ tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target,
+ self, MASK_PLAYERSOLID);
+ right = tr.fraction;
+
+ center = (d1 * center) / d2;
+
+ if ((left >= center) && (left > right))
+ {
+ if (left < 1)
+ {
+ VectorSet(v, d2 * left * 0.5, -16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward, v_right,
+ left_target);
+ }
+
+ VectorCopy(self->monsterinfo.last_sighting,
+ self->monsterinfo.saved_goal);
+ self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+ VectorCopy(left_target, self->goalentity->s.origin);
+ VectorCopy(left_target, self->monsterinfo.last_sighting);
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+ }
+ else if ((right >= center) && (right > left))
+ {
+ if (right < 1)
+ {
+ VectorSet(v, d2 * right * 0.5, 16, 0);
+ G_ProjectSource(self->s.origin, v, v_forward,
+ v_right, right_target);
+ }
+
+ VectorCopy(self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+ self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+ VectorCopy(right_target, self->goalentity->s.origin);
+ VectorCopy(right_target, self->monsterinfo.last_sighting);
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+ }
+ }
+ }
+
+ M_MoveToGoal(self, dist);
+
+ G_FreeEdict(tempgoal);
+
+ self->goalentity = save;
+}
diff --git a/xatrix/src/g_chase.c b/xatrix/src/g_chase.c
new file mode 100644
index 0000000..a4a5457
--- /dev/null
+++ b/xatrix/src/g_chase.c
@@ -0,0 +1,246 @@
+/*
+ * =======================================================================
+ *
+ * Chase cam. Only used in multiplayer mode.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+void
+UpdateChaseCam(edict_t *ent)
+{
+ vec3_t o, ownerv, goal;
+ edict_t *targ;
+ vec3_t forward, right;
+ trace_t trace;
+ int i;
+ vec3_t angles;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* is our chase target gone? */
+ if (!ent->client->chase_target->inuse ||
+ ent->client->chase_target->client->resp.spectator)
+ {
+ edict_t *old = ent->client->chase_target;
+ ChaseNext(ent);
+
+ if (ent->client->chase_target == old)
+ {
+ ent->client->chase_target = NULL;
+ ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+ return;
+ }
+ }
+
+ targ = ent->client->chase_target;
+
+ VectorCopy(targ->s.origin, ownerv);
+
+ ownerv[2] += targ->viewheight;
+
+ VectorCopy(targ->client->v_angle, angles);
+
+ if (angles[PITCH] > 56)
+ {
+ angles[PITCH] = 56;
+ }
+
+ AngleVectors(angles, forward, right, NULL);
+ VectorNormalize(forward);
+ VectorMA(ownerv, -30, forward, o);
+
+ if (o[2] < targ->s.origin[2] + 20)
+ {
+ o[2] = targ->s.origin[2] + 20;
+ }
+
+ /* jump animation lifts */
+ if (!targ->groundentity)
+ {
+ o[2] += 16;
+ }
+
+ trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+ VectorCopy(trace.endpos, goal);
+
+ VectorMA(goal, 2, forward, goal);
+
+ /* pad for floors and ceilings */
+ VectorCopy(goal, o);
+ o[2] += 6;
+ trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+ if (trace.fraction < 1)
+ {
+ VectorCopy(trace.endpos, goal);
+ goal[2] -= 6;
+ }
+
+ VectorCopy(goal, o);
+ o[2] -= 6;
+ trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+ if (trace.fraction < 1)
+ {
+ VectorCopy(trace.endpos, goal);
+ goal[2] += 6;
+ }
+
+ if (targ->deadflag)
+ {
+ ent->client->ps.pmove.pm_type = PM_DEAD;
+ }
+ else
+ {
+ ent->client->ps.pmove.pm_type = PM_FREEZE;
+ }
+
+ VectorCopy(goal, ent->s.origin);
+
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
+ targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
+ }
+
+ if (targ->deadflag)
+ {
+ ent->client->ps.viewangles[ROLL] = 40;
+ ent->client->ps.viewangles[PITCH] = -15;
+ ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
+ }
+ else
+ {
+ VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
+ VectorCopy(targ->client->v_angle, ent->client->v_angle);
+ }
+
+ ent->viewheight = 0;
+ ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
+ gi.linkentity(ent);
+}
+
+void
+ChaseNext(edict_t *ent)
+{
+ int i;
+ edict_t *e;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client->chase_target)
+ {
+ return;
+ }
+
+ i = ent->client->chase_target - g_edicts;
+
+ do
+ {
+ i++;
+
+ if (i > maxclients->value)
+ {
+ i = 1;
+ }
+
+ e = g_edicts + i;
+
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->client->resp.spectator)
+ {
+ break;
+ }
+ }
+ while (e != ent->client->chase_target);
+
+ ent->client->chase_target = e;
+ ent->client->update_chase = true;
+}
+
+void
+ChasePrev(edict_t *ent)
+{
+ int i;
+ edict_t *e;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client->chase_target)
+ {
+ return;
+ }
+
+ i = ent->client->chase_target - g_edicts;
+
+ do
+ {
+ i--;
+
+ if (i < 1)
+ {
+ i = maxclients->value;
+ }
+
+ e = g_edicts + i;
+
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->client->resp.spectator)
+ {
+ break;
+ }
+ }
+ while (e != ent->client->chase_target);
+
+ ent->client->chase_target = e;
+ ent->client->update_chase = true;
+}
+
+void
+GetChaseTarget(edict_t *ent)
+{
+ int i;
+ edict_t *other;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ for (i = 1; i <= maxclients->value; i++)
+ {
+ other = g_edicts + i;
+
+ if (other->inuse && !other->client->resp.spectator)
+ {
+ ent->client->chase_target = other;
+ ent->client->update_chase = true;
+ UpdateChaseCam(ent);
+ return;
+ }
+ }
+
+ gi.centerprintf(ent, "No other players to chase.");
+}
+
diff --git a/xatrix/src/g_cmds.c b/xatrix/src/g_cmds.c
new file mode 100644
index 0000000..bc2cfea
--- /dev/null
+++ b/xatrix/src/g_cmds.c
@@ -0,0 +1,1908 @@
+/* =======================================================================
+ *
+ * Game command processing.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+#include "monster/misc/player.h"
+
+static char *
+ClientTeam(edict_t *ent, char* value)
+{
+ char *p;
+
+ value[0] = 0;
+
+ if (!ent)
+ {
+ return value;
+ }
+
+ if (!ent->client)
+ {
+ return value;
+ }
+
+ strcpy(value, Info_ValueForKey(ent->client->pers.userinfo, "skin"));
+ p = strchr(value, '/');
+
+ if (!p)
+ {
+ return value;
+ }
+
+ if ((int)(dmflags->value) & DF_MODELTEAMS)
+ {
+ *p = 0;
+ return value;
+ }
+
+ return ++p;
+}
+
+qboolean
+OnSameTeam(edict_t *ent1, edict_t *ent2)
+{
+ char ent1Team[512];
+ char ent2Team[512];
+
+ if (!ent1 || !ent2)
+ {
+ return false;
+ }
+
+ if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+ {
+ return false;
+ }
+
+ ClientTeam(ent1, ent1Team);
+ ClientTeam(ent2, ent2Team);
+
+ if (ent1Team[0] != '\0' && strcmp(ent1Team, ent2Team) == 0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+SelectNextItem(edict_t *ent, int itflags)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (cl->chase_target)
+ {
+ ChaseNext(ent);
+ return;
+ }
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ index = (cl->pers.selected_item + i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & itflags))
+ {
+ continue;
+ }
+
+ cl->pers.selected_item = index;
+ return;
+ }
+
+ cl->pers.selected_item = -1;
+}
+
+void
+SelectPrevItem(edict_t *ent, int itflags)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (cl->chase_target)
+ {
+ ChasePrev(ent);
+ return;
+ }
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ index = (cl->pers.selected_item + MAX_ITEMS - i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & itflags))
+ {
+ continue;
+ }
+
+ cl->pers.selected_item = index;
+ return;
+ }
+
+ cl->pers.selected_item = -1;
+}
+
+void
+ValidateSelectedItem(edict_t *ent)
+{
+ gclient_t *cl;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (cl->pers.inventory[cl->pers.selected_item])
+ {
+ return; /* valid */
+ }
+
+ SelectNextItem(ent, -1);
+}
+
+/* ================================================================================= */
+
+/*
+ * Give items to a client
+ */
+void
+Cmd_Give_f(edict_t *ent)
+{
+ char *name;
+ gitem_t *it;
+ int index;
+ int i;
+ qboolean give_all;
+ edict_t *it_ent;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ name = gi.args();
+
+ if (Q_stricmp(name, "all") == 0)
+ {
+ give_all = true;
+ }
+ else
+ {
+ give_all = false;
+ }
+
+ if (give_all || (Q_stricmp(gi.argv(1), "health") == 0))
+ {
+ if (gi.argc() == 3)
+ {
+ ent->health = atoi(gi.argv(2));
+ ent->health = ent->health < 1 ? 1 : ent->health;
+ }
+ else
+ {
+ ent->health = ent->max_health;
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "weapons") == 0))
+ {
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = itemlist + i;
+
+ if (!it->pickup)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ continue;
+ }
+
+ ent->client->pers.inventory[i] += 1;
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "ammo") == 0))
+ {
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = itemlist + i;
+
+ if (!it->pickup)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_AMMO))
+ {
+ continue;
+ }
+
+ Add_Ammo(ent, it, 1000);
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "armor") == 0))
+ {
+ gitem_armor_t *info;
+
+ it = FindItem("Jacket Armor");
+ ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+ it = FindItem("Combat Armor");
+ ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+ it = FindItem("Body Armor");
+ info = (gitem_armor_t *)it->info;
+ ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count;
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all || (Q_stricmp(name, "Power Shield") == 0))
+ {
+ it = FindItem("Power Shield");
+ it_ent = G_Spawn();
+ it_ent->classname = it->classname;
+ SpawnItem(it_ent, it);
+ Touch_Item(it_ent, ent, NULL, NULL);
+
+ if (it_ent->inuse)
+ {
+ G_FreeEdict(it_ent);
+ }
+
+ if (!give_all)
+ {
+ return;
+ }
+ }
+
+ if (give_all)
+ {
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = itemlist + i;
+
+ if (!it->pickup)
+ {
+ continue;
+ }
+
+ if (it->flags & (IT_ARMOR | IT_WEAPON | IT_AMMO))
+ {
+ continue;
+ }
+
+ ent->client->pers.inventory[i] = 1;
+ }
+
+ return;
+ }
+
+ it = FindItem(name);
+
+ if (!it)
+ {
+ name = gi.argv(1);
+ it = FindItem(name);
+
+ if (!it)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "unknown item\n");
+ return;
+ }
+ }
+
+ if (!it->pickup)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "non-pickup item\n");
+ return;
+ }
+
+ index = ITEM_INDEX(it);
+
+ if (it->flags & IT_AMMO)
+ {
+ if (gi.argc() == 3)
+ {
+ ent->client->pers.inventory[index] = atoi(gi.argv(2));
+ }
+ else
+ {
+ ent->client->pers.inventory[index] += it->quantity;
+ }
+ }
+ else
+ {
+ it_ent = G_Spawn();
+ it_ent->classname = it->classname;
+ SpawnItem(it_ent, it);
+ Touch_Item(it_ent, ent, NULL, NULL);
+
+ if (it_ent->inuse)
+ {
+ G_FreeEdict(it_ent);
+ }
+ }
+}
+
+/*
+ * Sets client to godmode
+ */
+void
+Cmd_God_f(edict_t *ent)
+{
+ char *msg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ ent->flags ^= FL_GODMODE;
+
+ if (!(ent->flags & FL_GODMODE))
+ {
+ msg = "godmode OFF\n";
+ }
+ else
+ {
+ msg = "godmode ON\n";
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, msg);
+}
+
+/*
+ * Sets client to notarget
+ */
+void
+Cmd_Notarget_f(edict_t *ent)
+{
+ char *msg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ ent->flags ^= FL_NOTARGET;
+
+ if (!(ent->flags & FL_NOTARGET))
+ {
+ msg = "notarget OFF\n";
+ }
+ else
+ {
+ msg = "notarget ON\n";
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, msg);
+}
+
+void
+Cmd_Noclip_f(edict_t *ent)
+{
+ char *msg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH,
+ "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ if (ent->movetype == MOVETYPE_NOCLIP)
+ {
+ ent->movetype = MOVETYPE_WALK;
+ msg = "noclip OFF\n";
+ }
+ else
+ {
+ ent->movetype = MOVETYPE_NOCLIP;
+ msg = "noclip ON\n";
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, msg);
+}
+
+/*
+ * Use an inventory item
+ */
+void
+Cmd_Use_f(edict_t *ent)
+{
+ int index;
+ gitem_t *it;
+ char *s;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ s = gi.args();
+ it = FindItem(s);
+
+ if (!it)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "unknown item: %s\n", s);
+ return;
+ }
+
+ if (!it->use)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not usable.\n");
+ return;
+ }
+
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ if (strcmp(it->pickup_name, "HyperBlaster") == 0)
+ {
+ it = FindItem("Ionripper");
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+ }
+ else if (strcmp(it->pickup_name, "Railgun") == 0)
+ {
+ it = FindItem("Phalanx");
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+ }
+ else
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+ }
+
+ it->use(ent, it);
+}
+
+/*
+ * Drop an inventory item
+ */
+void
+Cmd_Drop_f(edict_t *ent)
+{
+ int index;
+ gitem_t *it;
+ char *s;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ s = gi.args();
+ it = FindItem(s);
+
+ if (!it)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "unknown item: %s\n", s);
+ return;
+ }
+
+ if (!it->drop)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not dropable.\n");
+ return;
+ }
+
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ if (strcmp(it->pickup_name, "HyperBlaster") == 0)
+ {
+ it = FindItem("Ionripper");
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+ }
+ else if (strcmp(it->pickup_name, "Railgun") == 0)
+ {
+ it = FindItem("Phalanx");
+ index = ITEM_INDEX(it);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+ }
+ else
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", s);
+ return;
+ }
+ }
+
+ it->drop(ent, it);
+}
+
+void
+Cmd_Score_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->showinventory = false;
+ ent->client->showhelp = false;
+
+ if (!deathmatch->value && !coop->value)
+ {
+ return;
+ }
+
+ if (ent->client->showscores)
+ {
+ ent->client->showscores = false;
+ return;
+ }
+
+ ent->client->showscores = true;
+ DeathmatchScoreboardMessage(ent, ent->enemy);
+ gi.unicast(ent, true);
+}
+
+ void
+Cmd_Help_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* this is for backwards compatability */
+ if (deathmatch->value)
+ {
+ Cmd_Score_f(ent);
+ return;
+ }
+
+ ent->client->showinventory = false;
+ ent->client->showscores = false;
+
+ if (ent->client->showhelp)
+ {
+ ent->client->showhelp = false;
+ return;
+ }
+
+ ent->client->showhelp = true;
+ ent->client->pers.helpchanged = 0;
+ HelpComputerMessage(ent);
+ gi.unicast(ent, true);
+}
+
+void
+Cmd_Inven_f(edict_t *ent)
+{
+ gclient_t *cl;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ cl->showscores = false;
+ cl->showhelp = false;
+
+ if (cl->showinventory)
+ {
+ cl->showinventory = false;
+ return;
+ }
+
+ cl->showinventory = true;
+ InventoryMessage(ent);
+ gi.unicast(ent, true);
+}
+
+void
+Cmd_InvUse_f(edict_t *ent)
+{
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ValidateSelectedItem(ent);
+
+ if (ent->client->pers.selected_item == -1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No item to use.\n");
+ return;
+ }
+
+ it = &itemlist[ent->client->pers.selected_item];
+
+ if (!it->use)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not usable.\n");
+ return;
+ }
+
+ it->use(ent, it);
+}
+
+void
+Cmd_WeapPrev_f(edict_t *ent)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+ int selected_weapon;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (!cl->pers.weapon)
+ {
+ return;
+ }
+
+ selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ index = (selected_weapon + MAX_ITEMS - i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ continue;
+ }
+
+ it->use(ent, it);
+
+ if (cl->newweapon == it)
+ {
+ return;
+ }
+ }
+}
+
+void
+Cmd_WeapNext_f(edict_t *ent)
+{
+ gclient_t *cl;
+ int i, index;
+ gitem_t *it;
+ int selected_weapon;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (!cl->pers.weapon)
+ {
+ return;
+ }
+
+ selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+ /* scan for the next valid one */
+ for (i = 1; i <= MAX_ITEMS; i++)
+ {
+ index = (selected_weapon + i) % MAX_ITEMS;
+
+ if (!cl->pers.inventory[index])
+ {
+ continue;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ continue;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ continue;
+ }
+
+ it->use(ent, it);
+
+ if (cl->newweapon == it)
+ {
+ return;
+ }
+ }
+}
+
+void
+Cmd_WeapLast_f(edict_t *ent)
+{
+ gclient_t *cl;
+ int index;
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ cl = ent->client;
+
+ if (!cl->pers.weapon || !cl->pers.lastweapon)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(cl->pers.lastweapon);
+
+ if (!cl->pers.inventory[index])
+ {
+ return;
+ }
+
+ it = &itemlist[index];
+
+ if (!it->use)
+ {
+ return;
+ }
+
+ if (!(it->flags & IT_WEAPON))
+ {
+ return;
+ }
+
+ it->use(ent, it);
+}
+
+void
+Cmd_InvDrop_f(edict_t *ent)
+{
+ gitem_t *it;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ValidateSelectedItem(ent);
+
+ if (ent->client->pers.selected_item == -1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No item to drop.\n");
+ return;
+ }
+
+ it = &itemlist[ent->client->pers.selected_item];
+
+ if (!it->drop)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Item is not dropable.\n");
+ return;
+ }
+
+ it->drop(ent, it);
+}
+
+void
+Cmd_Kill_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (((level.time - ent->client->respawn_time) < 5) ||
+ (ent->client->resp.spectator))
+ {
+ return;
+ }
+
+ ent->flags &= ~FL_GODMODE;
+ ent->health = 0;
+ meansOfDeath = MOD_SUICIDE;
+ player_die(ent, ent, ent, 100000, vec3_origin);
+}
+
+void
+Cmd_PutAway_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->showscores = false;
+ ent->client->showhelp = false;
+ ent->client->showinventory = false;
+}
+
+int
+PlayerSort(void const *a, void const *b)
+{
+ int anum, bnum;
+
+ if (!a || !b)
+ {
+ return 0;
+ }
+
+ anum = *(int *)a;
+ bnum = *(int *)b;
+
+ anum = game.clients[anum].ps.stats[STAT_FRAGS];
+ bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
+
+ if (anum < bnum)
+ {
+ return -1;
+ }
+
+ if (anum > bnum)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+Cmd_Players_f(edict_t *ent)
+{
+ int i;
+ int count;
+ char small[64];
+ char large[1280];
+ int index[256];
+
+ if (!ent)
+ {
+ return;
+ }
+
+ count = 0;
+
+ for (i = 0; i < maxclients->value; i++)
+ {
+ if (game.clients[i].pers.connected)
+ {
+ index[count] = i;
+ count++;
+ }
+ }
+
+ /* sort by frags */
+ qsort(index, count, sizeof(index[0]), PlayerSort);
+
+ /* print information */
+ large[0] = 0;
+
+ for (i = 0; i < count; i++)
+ {
+ Com_sprintf(small, sizeof(small), "%3i %s\n",
+ game.clients[index[i]].ps.stats[STAT_FRAGS],
+ game.clients[index[i]].pers.netname);
+
+ if (strlen(small) + strlen(large) > sizeof(large) - 100)
+ {
+ /* can't print all of them in one packet */
+ strcat(large, "...\n");
+ break;
+ }
+
+ strcat(large, small);
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, "%s\n%i players\n", large, count);
+}
+
+void
+Cmd_Wave_f(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ i = atoi(gi.argv(1));
+
+ /* can't wave when ducked */
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ return;
+ }
+
+ if (ent->client->anim_priority > ANIM_WAVE)
+ {
+ return;
+ }
+
+ ent->client->anim_priority = ANIM_WAVE;
+
+ switch (i)
+ {
+ case 0:
+ gi.cprintf(ent, PRINT_HIGH, "flipoff\n");
+ ent->s.frame = FRAME_flip01 - 1;
+ ent->client->anim_end = FRAME_flip12;
+ break;
+ case 1:
+ gi.cprintf(ent, PRINT_HIGH, "salute\n");
+ ent->s.frame = FRAME_salute01 - 1;
+ ent->client->anim_end = FRAME_salute11;
+ break;
+ case 2:
+ gi.cprintf(ent, PRINT_HIGH, "taunt\n");
+ ent->s.frame = FRAME_taunt01 - 1;
+ ent->client->anim_end = FRAME_taunt17;
+ break;
+ case 3:
+ gi.cprintf(ent, PRINT_HIGH, "wave\n");
+ ent->s.frame = FRAME_wave01 - 1;
+ ent->client->anim_end = FRAME_wave11;
+ break;
+ case 4:
+ default:
+ gi.cprintf(ent, PRINT_HIGH, "point\n");
+ ent->s.frame = FRAME_point01 - 1;
+ ent->client->anim_end = FRAME_point12;
+ break;
+ }
+}
+
+static qboolean
+flooded(edict_t *ent)
+{
+ gclient_t *cl;
+ int i;
+ int num_msgs;
+ int mx;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!deathmatch->value && !coop->value)
+ {
+ return false;
+ }
+
+ num_msgs = flood_msgs->value;
+ if (num_msgs <= 0)
+ {
+ return false;
+ }
+
+ cl = ent->client;
+ mx = sizeof(cl->flood_when) / sizeof(cl->flood_when[0]);
+
+ if (num_msgs > mx)
+ {
+ gi.dprintf("flood_msgs lowered to max: 10\n");
+
+ num_msgs = mx;
+ gi.cvar_forceset("flood_msgs", "10");
+ }
+
+ if (level.time < cl->flood_locktill)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n",
+ (int)(cl->flood_locktill - level.time));
+
+ return true;
+ }
+
+ i = (cl->flood_whenhead - num_msgs) + 1;
+
+ if (i < 0)
+ {
+ i += mx;
+ }
+
+ if (cl->flood_when[i] &&
+ (level.time - cl->flood_when[i]) < flood_persecond->value)
+ {
+ cl->flood_locktill = level.time + flood_waitdelay->value;
+
+ gi.cprintf(ent, PRINT_CHAT,
+ "Flood protection: You can't talk for %d seconds.\n",
+ (int)flood_waitdelay->value);
+
+ return true;
+ }
+
+ cl->flood_whenhead = (cl->flood_whenhead + 1) % mx;
+ cl->flood_when[cl->flood_whenhead] = level.time;
+
+ return false;
+}
+
+void
+Cmd_Say_f(edict_t *ent, qboolean team, qboolean arg0)
+{
+ int j;
+ edict_t *other;
+ char *p;
+ char text[2048];
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((gi.argc() < 2) && !arg0)
+ {
+ return;
+ }
+
+ if (flooded(ent))
+ {
+ return;
+ }
+
+ if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+ {
+ team = false;
+ }
+
+ if (team)
+ {
+ Com_sprintf(text, sizeof(text), "(%s): ", ent->client->pers.netname);
+ }
+ else
+ {
+ Com_sprintf(text, sizeof(text), "%s: ", ent->client->pers.netname);
+ }
+
+ if (arg0)
+ {
+ strcat(text, gi.argv(0));
+ strcat(text, " ");
+ strcat(text, gi.args());
+ }
+ else
+ {
+ p = gi.args();
+
+ if (*p == '"')
+ {
+ p++;
+ p[strlen(p) - 1] = 0;
+ }
+
+ strcat(text, p);
+ }
+
+ /* don't let text be too long for malicious reasons */
+ if (strlen(text) > 150)
+ {
+ text[150] = 0;
+ }
+
+ strcat(text, "\n");
+
+ if (dedicated->value)
+ {
+ gi.cprintf(NULL, PRINT_CHAT, "%s", text);
+ }
+
+ for (j = 1; j <= game.maxclients; j++)
+ {
+ other = &g_edicts[j];
+
+ if (!other->inuse)
+ {
+ continue;
+ }
+
+ if (!other->client)
+ {
+ continue;
+ }
+
+ if (team)
+ {
+ if (!OnSameTeam(ent, other))
+ {
+ continue;
+ }
+ }
+
+ gi.cprintf(other, PRINT_CHAT, "%s", text);
+ }
+}
+
+void
+Cmd_PlayerList_f(edict_t *ent)
+{
+ int i, text_len;
+ char st[80];
+ char text[1400];
+ edict_t *e2;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* connect time, ping, score, name */
+ *text = '\0';
+
+ for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++)
+ {
+ if (!e2->inuse)
+ {
+ continue;
+ }
+
+ Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n",
+ (level.framenum - e2->client->resp.enterframe) / 600,
+ ((level.framenum - e2->client->resp.enterframe) % 600) / 10,
+ e2->client->ping, e2->client->resp.score,
+ e2->client->pers.netname,
+ e2->client->resp.spectator ? " (spectator)" : "");
+
+ text_len = strlen(text);
+
+ if ((text_len + strlen(st)) > (sizeof(text) - 50))
+ {
+ snprintf(text + text_len, sizeof(text) - text_len, "And more...\n");
+ gi.cprintf(ent, PRINT_HIGH, "%s", text);
+ return;
+ }
+
+ strcat(text, st);
+ }
+
+ gi.cprintf(ent, PRINT_HIGH, "%s", text);
+}
+
+void
+Cmd_Teleport_f(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ if (gi.argc() != 4)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: teleport x y z\n");
+ return;
+ }
+
+ /* Unlink it to prevent unwanted interactions with
+ other entities. This works because linkentity()
+ uses the first available slot and the player is
+ always at postion 0. */
+ gi.unlinkentity(ent);
+
+ /* Set new position */
+ ent->s.origin[0] = atof(gi.argv(1));
+ ent->s.origin[1] = atof(gi.argv(2));
+ ent->s.origin[2] = atof(gi.argv(3)) + 10.0;
+
+ /* Remove velocity and keep the entity briefly in place
+ to give the server and clients time to catch up. */
+ VectorClear(ent->velocity);
+ ent->client->ps.pmove.pm_time = 20;
+ ent->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+ /* Remove viewangles. They'll be recalculated
+ by the client at the next frame. */
+ VectorClear(ent->s.angles);
+ VectorClear(ent->client->ps.viewangles);
+ VectorClear(ent->client->v_angle);
+
+ /* Telefrag everything that's in the target location. */
+ KillBox(ent);
+
+ /* And link it back in. */
+ gi.linkentity(ent);
+}
+
+void
+Cmd_ListEntities_f(edict_t *ent)
+{
+ if ((deathmatch->value || coop->value) && !sv_cheats->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+ return;
+ }
+
+ if (gi.argc() < 2)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: listentities <all|ammo|items|keys|monsters|weapons>\n");
+ return;
+ }
+
+ /* What to print? */
+ qboolean all = false;
+ qboolean ammo = false;
+ qboolean items = false;
+ qboolean keys = false;
+ qboolean monsters = false;
+ qboolean weapons = false;
+
+ for (int i = 1; i < gi.argc(); i++)
+ {
+ const char *arg = gi.argv(i);
+
+ if (Q_stricmp(arg, "all") == 0)
+ {
+ all = true;
+ }
+ else if (Q_stricmp(arg, "ammo") == 0)
+ {
+ ammo = true;
+ }
+ else if (Q_stricmp(arg, "items") == 0)
+ {
+ items = true;
+ }
+ else if (Q_stricmp(arg, "keys") == 0)
+ {
+ keys = true;
+ }
+ else if (Q_stricmp(arg, "monsters") == 0)
+ {
+ monsters = true;
+ }
+ else if (Q_stricmp(arg, "weapons") == 0)
+ {
+ weapons = true;
+ }
+ else
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: listentities <all|ammo|items|keys|monsters|weapons>\n");
+ }
+ }
+
+ /* Print what's requested. */
+ for (int i = 0; i < globals.num_edicts; i++)
+ {
+ edict_t *cur = &g_edicts[i];
+ qboolean print = false;
+
+ /* Ensure that the entity is valid. */
+ if (!cur->classname)
+ {
+ continue;
+ }
+
+ if (all)
+ {
+ print = true;
+ }
+ else
+ {
+ if (ammo)
+ {
+ if (strncmp(cur->classname, "ammo_", 5) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (items)
+ {
+ if (strncmp(cur->classname, "item_", 5) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (keys)
+ {
+ if (strncmp(cur->classname, "key_", 4) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (monsters)
+ {
+ if (strncmp(cur->classname, "monster_", 8) == 0)
+ {
+ print = true;
+ }
+ }
+
+ if (weapons)
+ {
+ if (strncmp(cur->classname, "weapon_", 7) == 0)
+ {
+ print = true;
+ }
+ }
+ }
+
+ if (print)
+ {
+ /* We use dprintf() because cprintf() may flood the server... */
+ gi.dprintf("%s: %f %f %f\n", cur->classname, cur->s.origin[0], cur->s.origin[1], cur->s.origin[2]);
+ }
+ }
+}
+
+static int
+get_ammo_usage(gitem_t *weap)
+{
+ if (!weap)
+ {
+ return 0;
+ }
+
+ /* handles grenades and tesla which only use 1 ammo per shot */
+ /* have to check this because they don't store their ammo usage in weap->quantity */
+ if (weap->flags & IT_AMMO)
+ {
+ return 1;
+ }
+
+ /* weapons store their ammo usage in the quantity field */
+ return weap->quantity;
+}
+
+static gitem_t *
+cycle_weapon(edict_t *ent)
+{
+ gclient_t *cl;
+ gitem_t *noammo_fallback;
+ gitem_t *noweap_fallback;
+ gitem_t *weap;
+ gitem_t *ammo;
+ int i;
+ int start;
+ int num_weaps;
+ const char *weapname = NULL;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ cl = ent->client;
+
+ if (!cl)
+ {
+ return NULL;
+ }
+
+ num_weaps = gi.argc();
+
+ /* find where we want to start the search for the next eligible weapon */
+ if (cl->newweapon)
+ {
+ weapname = cl->newweapon->classname;
+ }
+ else if (cl->pers.weapon)
+ {
+ weapname = cl->pers.weapon->classname;
+ }
+
+ if (weapname)
+ {
+ for (i = 1; i < num_weaps; i++)
+ {
+ if (Q_stricmp(weapname, gi.argv(i)) == 0)
+ {
+ break;
+ }
+ }
+
+ i++;
+
+ if (i >= num_weaps)
+ {
+ i = 1;
+ }
+ }
+ else
+ {
+ i = 1;
+ }
+
+ start = i;
+ noammo_fallback = NULL;
+ noweap_fallback = NULL;
+
+ /* find the first eligible weapon in the list we can switch to */
+ do
+ {
+ weap = FindItemByClassname(gi.argv(i));
+
+ if (weap && weap != cl->pers.weapon && (weap->flags & IT_WEAPON) && weap->use)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(weap)] > 0)
+ {
+ if (weap->ammo)
+ {
+ ammo = FindItem(weap->ammo);
+ if (ammo)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(ammo)] >= get_ammo_usage(weap))
+ {
+ return weap;
+ }
+
+ if (!noammo_fallback)
+ {
+ noammo_fallback = weap;
+ }
+ }
+ }
+ else
+ {
+ return weap;
+ }
+ }
+ else if (!noweap_fallback)
+ {
+ noweap_fallback = weap;
+ }
+ }
+
+ i++;
+
+ if (i >= num_weaps)
+ {
+ i = 1;
+ }
+ } while (i != start);
+
+ /* if no weapon was found, the fallbacks will be used for
+ printing the appropriate error message to the console
+ */
+
+ if (noammo_fallback)
+ {
+ return noammo_fallback;
+ }
+
+ return noweap_fallback;
+}
+
+void
+Cmd_CycleWeap_f(edict_t *ent)
+{
+ gitem_t *weap;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (gi.argc() <= 1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: cycleweap classname1 classname2 .. classnameN\n");
+ return;
+ }
+
+ weap = cycle_weapon(ent);
+ if (weap)
+ {
+ if (ent->client->pers.inventory[ITEM_INDEX(weap)] <= 0)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", weap->pickup_name);
+ }
+ else
+ {
+ weap->use(ent, weap);
+ }
+ }
+}
+
+static gitem_t *
+preferred_weapon(edict_t *ent)
+{
+ gclient_t *cl;
+ gitem_t *noammo_fallback;
+ gitem_t *noweap_fallback;
+ gitem_t *weap;
+ gitem_t *ammo;
+ int i;
+ int num_weaps;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ cl = ent->client;
+
+ if (!cl)
+ {
+ return NULL;
+ }
+
+ num_weaps = gi.argc();
+ noammo_fallback = NULL;
+ noweap_fallback = NULL;
+
+ /* find the first eligible weapon in the list we can switch to */
+ for (i = 1; i < num_weaps; i++)
+ {
+ weap = FindItemByClassname(gi.argv(i));
+
+ if (weap && (weap->flags & IT_WEAPON) && weap->use)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(weap)] > 0)
+ {
+ if (weap->ammo)
+ {
+ ammo = FindItem(weap->ammo);
+ if (ammo)
+ {
+ if (cl->pers.inventory[ITEM_INDEX(ammo)] >= get_ammo_usage(weap))
+ {
+ return weap;
+ }
+
+ if (!noammo_fallback)
+ {
+ noammo_fallback = weap;
+ }
+ }
+ }
+ else
+ {
+ return weap;
+ }
+ }
+ else if (!noweap_fallback)
+ {
+ noweap_fallback = weap;
+ }
+ }
+ }
+
+ /* if no weapon was found, the fallbacks will be used for
+ printing the appropriate error message to the console
+ */
+
+ if (noammo_fallback)
+ {
+ return noammo_fallback;
+ }
+
+ return noweap_fallback;
+}
+
+void
+Cmd_PrefWeap_f(edict_t *ent)
+{
+ gitem_t *weap;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (gi.argc() <= 1)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Usage: prefweap classname1 classname2 .. classnameN\n");
+ return;
+ }
+
+ weap = preferred_weapon(ent);
+ if (weap)
+ {
+ if (ent->client->pers.inventory[ITEM_INDEX(weap)] <= 0)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Out of item: %s\n", weap->pickup_name);
+ }
+ else
+ {
+ weap->use(ent, weap);
+ }
+ }
+}
+
+void
+ClientCommand(edict_t *ent)
+{
+ char *cmd;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client)
+ {
+ return; /* not fully in game yet */
+ }
+
+ cmd = gi.argv(0);
+
+ if (Q_stricmp(cmd, "players") == 0)
+ {
+ Cmd_Players_f(ent);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "say") == 0)
+ {
+ Cmd_Say_f(ent, false, false);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "say_team") == 0)
+ {
+ Cmd_Say_f(ent, true, false);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "score") == 0)
+ {
+ Cmd_Score_f(ent);
+ return;
+ }
+
+ if (Q_stricmp(cmd, "help") == 0)
+ {
+ Cmd_Help_f(ent);
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return;
+ }
+
+ if (Q_stricmp(cmd, "use") == 0)
+ {
+ Cmd_Use_f(ent);
+ }
+ else if (Q_stricmp(cmd, "drop") == 0)
+ {
+ Cmd_Drop_f(ent);
+ }
+ else if (Q_stricmp(cmd, "give") == 0)
+ {
+ Cmd_Give_f(ent);
+ }
+ else if (Q_stricmp(cmd, "god") == 0)
+ {
+ Cmd_God_f(ent);
+ }
+ else if (Q_stricmp(cmd, "notarget") == 0)
+ {
+ Cmd_Notarget_f(ent);
+ }
+ else if (Q_stricmp(cmd, "noclip") == 0)
+ {
+ Cmd_Noclip_f(ent);
+ }
+ else if (Q_stricmp(cmd, "inven") == 0)
+ {
+ Cmd_Inven_f(ent);
+ }
+ else if (Q_stricmp(cmd, "invnext") == 0)
+ {
+ SelectNextItem(ent, -1);
+ }
+ else if (Q_stricmp(cmd, "invprev") == 0)
+ {
+ SelectPrevItem(ent, -1);
+ }
+ else if (Q_stricmp(cmd, "invnextw") == 0)
+ {
+ SelectNextItem(ent, IT_WEAPON);
+ }
+ else if (Q_stricmp(cmd, "invprevw") == 0)
+ {
+ SelectPrevItem(ent, IT_WEAPON);
+ }
+ else if (Q_stricmp(cmd, "invnextp") == 0)
+ {
+ SelectNextItem(ent, IT_POWERUP);
+ }
+ else if (Q_stricmp(cmd, "invprevp") == 0)
+ {
+ SelectPrevItem(ent, IT_POWERUP);
+ }
+ else if (Q_stricmp(cmd, "invuse") == 0)
+ {
+ Cmd_InvUse_f(ent);
+ }
+ else if (Q_stricmp(cmd, "invdrop") == 0)
+ {
+ Cmd_InvDrop_f(ent);
+ }
+ else if (Q_stricmp(cmd, "weapprev") == 0)
+ {
+ Cmd_WeapPrev_f(ent);
+ }
+ else if (Q_stricmp(cmd, "weapnext") == 0)
+ {
+ Cmd_WeapNext_f(ent);
+ }
+ else if (Q_stricmp(cmd, "weaplast") == 0)
+ {
+ Cmd_WeapLast_f(ent);
+ }
+ else if (Q_stricmp(cmd, "kill") == 0)
+ {
+ Cmd_Kill_f(ent);
+ }
+ else if (Q_stricmp(cmd, "putaway") == 0)
+ {
+ Cmd_PutAway_f(ent);
+ }
+ else if (Q_stricmp(cmd, "wave") == 0)
+ {
+ Cmd_Wave_f(ent);
+ }
+ else if (Q_stricmp(cmd, "playerlist") == 0)
+ {
+ Cmd_PlayerList_f(ent);
+ }
+ else if (Q_stricmp(cmd, "teleport") == 0)
+ {
+ Cmd_Teleport_f(ent);
+ }
+ else if (Q_stricmp(cmd, "listentities") == 0)
+ {
+ Cmd_ListEntities_f(ent);
+ }
+ else if (Q_stricmp(cmd, "cycleweap") == 0)
+ {
+ Cmd_CycleWeap_f(ent);
+ }
+ else if (Q_stricmp(cmd, "prefweap") == 0)
+ {
+ Cmd_PrefWeap_f(ent);
+ }
+ else /* anything that doesn't match a command will be a chat */
+ {
+ Cmd_Say_f(ent, false, true);
+ }
+}
+
diff --git a/xatrix/src/g_combat.c b/xatrix/src/g_combat.c
new file mode 100644
index 0000000..5f84ad1
--- /dev/null
+++ b/xatrix/src/g_combat.c
@@ -0,0 +1,750 @@
+/*
+ * =======================================================================
+ *
+ * Combat code like damage, death and so on.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+/*
+ * Returns true if the inflictor can directly damage the
+ * target. Used for explosions and melee attacks.
+ */
+qboolean
+CanDamage(edict_t *targ, edict_t *inflictor)
+{
+ vec3_t dest;
+ trace_t trace;
+
+ if (!targ || !inflictor)
+ {
+ return false;
+ }
+
+ /* bmodels need special checking because their origin is 0,0,0 */
+ if (targ->movetype == MOVETYPE_PUSH)
+ {
+ VectorAdd(targ->absmin, targ->absmax, dest);
+ VectorScale(dest, 0.5, dest);
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ if (trace.ent == targ)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ targ->s.origin, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] += 15.0;
+ dest[1] += 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] += 15.0;
+ dest[1] -= 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] -= 15.0;
+ dest[1] += 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ VectorCopy(targ->s.origin, dest);
+ dest[0] -= 15.0;
+ dest[1] -= 15.0;
+ trace = gi.trace(inflictor->s.origin, vec3_origin, vec3_origin,
+ dest, inflictor, MASK_SOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point)
+{
+ if (!targ || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ targ->enemy = attacker;
+
+ if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+ {
+ if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ level.killed_monsters++;
+
+ if (coop->value && attacker->client)
+ {
+ attacker->client->resp.score++;
+ }
+
+ /* medics won't heal monsters that they kill themselves */
+ if (attacker->classname && strcmp(attacker->classname, "monster_medic") == 0)
+ {
+ targ->owner = attacker;
+ }
+ }
+ }
+
+ if ((targ->movetype == MOVETYPE_PUSH) ||
+ (targ->movetype == MOVETYPE_STOP) ||
+ (targ->movetype == MOVETYPE_NONE))
+ {
+ /* doors, triggers, etc */
+ targ->die(targ, inflictor, attacker, damage, point);
+ return;
+ }
+
+ if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+ {
+ targ->touch = NULL;
+ monster_death_use(targ);
+ }
+
+ targ->die(targ, inflictor, attacker, damage, point);
+}
+
+void
+SpawnDamage(int type, vec3_t origin, vec3_t normal, int damage)
+{
+ gi.WriteByte (svc_temp_entity);
+ gi.WriteByte (type);
+ gi.WritePosition (origin);
+ gi.WriteDir (normal);
+ gi.multicast (origin, MULTICAST_PVS);
+}
+
+/*
+ * targ entity that is being damaged
+ * inflictor entity that is causing the damage
+ * attacker entity that caused the inflictor to damage targ
+ * example: targ=monster, inflictor=rocket, attacker=player
+ *
+ * dir direction of the attack
+ * point point at which the damage is being inflicted
+ * normal normal vector from that point
+ * damage amount of damage being inflicted
+ * knockback force to be applied against targ as a result of the damage
+ *
+ * dflags these flags are used to control how T_Damage works
+ * DAMAGE_RADIUS damage was indirect (from a nearby explosion)
+ * DAMAGE_NO_ARMOR armor does not protect from this damage
+ * DAMAGE_ENERGY damage is from an energy based weapon
+ * DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
+ * DAMAGE_BULLET damage is from a bullet (used for ricochets)
+ * DAMAGE_NO_PROTECTION kills godmode, armor, everything
+ * ============
+ */
+int
+CheckPowerArmor(edict_t *ent, vec3_t point, vec3_t normal,
+ int damage, int dflags)
+{
+ gclient_t *client;
+ int save;
+ int power_armor_type;
+ int index = 0;
+ int damagePerCell;
+ int pa_te_type;
+ int power = 0;
+ int power_used;
+
+ if (!ent)
+ {
+ return 0;
+ }
+
+ if (!damage)
+ {
+ return 0;
+ }
+
+ index = 0;
+
+ client = ent->client;
+
+ if (dflags & DAMAGE_NO_ARMOR)
+ {
+ return 0;
+ }
+
+ if (client)
+ {
+ power_armor_type = PowerArmorType(ent);
+
+ if (power_armor_type != POWER_ARMOR_NONE)
+ {
+ index = ITEM_INDEX(FindItem("Cells"));
+ power = client->pers.inventory[index];
+ }
+ }
+ else if (ent->svflags & SVF_MONSTER)
+ {
+ power_armor_type = ent->monsterinfo.power_armor_type;
+ power = ent->monsterinfo.power_armor_power;
+ index = 0;
+ }
+ else
+ {
+ return 0;
+ }
+
+ if (power_armor_type == POWER_ARMOR_NONE)
+ {
+ return 0;
+ }
+
+ if (!power)
+ {
+ return 0;
+ }
+
+ if (power_armor_type == POWER_ARMOR_SCREEN)
+ {
+ vec3_t vec;
+ float dot;
+ vec3_t forward;
+
+ /* only works if damage point is in front */
+ AngleVectors(ent->s.angles, forward, NULL, NULL);
+ VectorSubtract(point, ent->s.origin, vec);
+ VectorNormalize(vec);
+ dot = DotProduct(vec, forward);
+
+ if (dot <= 0.3)
+ {
+ return 0;
+ }
+
+ damagePerCell = 1;
+ pa_te_type = TE_SCREEN_SPARKS;
+ damage = damage / 3;
+ }
+ else
+ {
+ damagePerCell = 2;
+ pa_te_type = TE_SHIELD_SPARKS;
+ damage = (2 * damage) / 3;
+ }
+
+ save = power * damagePerCell;
+
+ if (!save)
+ {
+ return 0;
+ }
+
+ if (save > damage)
+ {
+ save = damage;
+ }
+
+ SpawnDamage(pa_te_type, point, normal, save);
+ ent->powerarmor_time = level.time + 0.2;
+
+ power_used = save / damagePerCell;
+
+ if (client)
+ {
+ client->pers.inventory[index] -= power_used;
+ }
+ else
+ {
+ ent->monsterinfo.power_armor_power -= power_used;
+ }
+
+ return save;
+}
+
+int
+CheckArmor(edict_t *ent, vec3_t point, vec3_t normal,
+ int damage, int te_sparks, int dflags)
+{
+ gclient_t *client;
+ int save;
+ int index;
+ gitem_t *armor;
+
+ if (!ent)
+ {
+ return 0;
+ }
+
+ if (!damage)
+ {
+ return 0;
+ }
+
+ client = ent->client;
+
+ if (!client)
+ {
+ return 0;
+ }
+
+ if (dflags & DAMAGE_NO_ARMOR)
+ {
+ return 0;
+ }
+
+ index = ArmorIndex(ent);
+
+ if (!index)
+ {
+ return 0;
+ }
+
+ armor = GetItemByIndex(index);
+
+ if (dflags & DAMAGE_ENERGY)
+ {
+ save = ceil(((gitem_armor_t *)armor->info)->energy_protection * damage);
+ }
+ else
+ {
+ save = ceil(((gitem_armor_t *)armor->info)->normal_protection * damage);
+ }
+
+ if (save >= client->pers.inventory[index])
+ {
+ save = client->pers.inventory[index];
+ }
+
+ if (!save)
+ {
+ return 0;
+ }
+
+ client->pers.inventory[index] -= save;
+ SpawnDamage(te_sparks, point, normal, save);
+
+ return save;
+}
+
+void
+M_ReactToDamage(edict_t *targ, edict_t *attacker)
+{
+ if (!targ || !attacker)
+ {
+ return;
+ }
+
+ if (targ->health <= 0)
+ {
+ return;
+ }
+
+ if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
+ {
+ return;
+ }
+
+ if ((attacker == targ) || (attacker == targ->enemy))
+ {
+ return;
+ }
+
+ /* if we are a good guy monster and our attacker is a
+ player or another good guy, do not get mad at them */
+ if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
+ {
+ if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ return;
+ }
+ }
+
+ /* if attacker is a client, get mad at
+ them because he's good and we're not */
+ if (attacker->client)
+ {
+ targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+ /* this can only happen in coop (both new and old
+ enemies are clients) only switch if can't see the
+ current enemy */
+ if (targ->enemy && targ->enemy->client)
+ {
+ if (visible(targ, targ->enemy))
+ {
+ targ->oldenemy = attacker;
+ return;
+ }
+
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+
+ return;
+ }
+
+ /* it's the same base (walk/swim/fly) type and
+ a different classname and it's not a tank
+ (they spray too much), get mad at them */
+ if (((targ->flags & (FL_FLY | FL_SWIM)) ==
+ (attacker->flags & (FL_FLY | FL_SWIM))) &&
+ (strcmp(targ->classname, attacker->classname) != 0) &&
+ (strcmp(attacker->classname, "monster_tank") != 0) &&
+ (strcmp(attacker->classname, "monster_supertank") != 0) &&
+ (strcmp(attacker->classname, "monster_makron") != 0) &&
+ (strcmp(attacker->classname, "monster_jorg") != 0))
+ {
+ if (targ->enemy && targ->enemy->client)
+ {
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+ }
+ /* if they *meant* to shoot us, then shoot back */
+ else if (attacker->enemy == targ)
+ {
+ if (targ->enemy && targ->enemy->client)
+ {
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+ }
+ /* otherwise get mad at whoever they are mad at (help our buddy) unless it is us! */
+ else if (attacker->enemy)
+ {
+ if (targ->enemy && targ->enemy->client)
+ {
+ targ->oldenemy = targ->enemy;
+ }
+
+ targ->enemy = attacker->enemy;
+
+ if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+ {
+ FoundTarget(targ);
+ }
+ }
+}
+
+static void
+apply_knockback(edict_t *targ, vec3_t dir, float knockback, float scale)
+{
+ vec3_t kvel;
+ float mass;
+
+ if (!knockback)
+ {
+ return;
+ }
+
+ mass = (targ->mass < 50) ? 50.0f : (float)targ->mass;
+
+ VectorNormalize2(dir, kvel);
+ VectorScale(kvel, scale * (knockback / mass), kvel);
+ VectorAdd(targ->velocity, kvel, targ->velocity);
+}
+
+void
+T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir,
+ vec3_t point, vec3_t normal, int damage, int knockback, int dflags,
+ int mod)
+{
+ gclient_t *client;
+ int take;
+ int save;
+ int asave;
+ int psave;
+ int te_sparks;
+
+ if (!targ || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ if (!targ->takedamage)
+ {
+ return;
+ }
+
+ /* friendly fire avoidance. If enabled you can't
+ hurt teammates (but you can hurt yourself)
+ knockback still occurs */
+ if ((targ != attacker) && ((deathmatch->value &&
+ ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) ||
+ coop->value))
+ {
+ if (OnSameTeam(targ, attacker))
+ {
+ if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
+ {
+ damage = 0;
+ }
+ else
+ {
+ mod |= MOD_FRIENDLY_FIRE;
+ }
+ }
+ }
+
+ meansOfDeath = mod;
+
+ /* easy mode takes half damage */
+ if ((skill->value == SKILL_EASY) && (deathmatch->value == 0) && targ->client)
+ {
+ damage *= 0.5;
+
+ if (!damage)
+ {
+ damage = 1;
+ }
+ }
+
+ client = targ->client;
+
+ if (dflags & DAMAGE_BULLET)
+ {
+ te_sparks = TE_BULLET_SPARKS;
+ }
+ else
+ {
+ te_sparks = TE_SPARKS;
+ }
+
+ /* bonus damage for suprising a monster */
+ if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) &&
+ (attacker->client) && (!targ->enemy) && (targ->health > 0))
+ {
+ damage *= 2;
+ }
+
+ if (targ->flags & FL_NO_KNOCKBACK)
+ {
+ knockback = 0;
+ }
+
+ /* figure momentum add */
+ if (!(dflags & DAMAGE_NO_KNOCKBACK) &&
+ (targ->movetype != MOVETYPE_NONE) &&
+ (targ->movetype != MOVETYPE_BOUNCE) &&
+ (targ->movetype != MOVETYPE_PUSH) &&
+ (targ->movetype != MOVETYPE_STOP))
+ {
+ apply_knockback (targ, dir, knockback,
+ ((client && attacker == targ) ? 1600.0f : 500.0f));
+ }
+
+ take = damage;
+ save = 0;
+
+ /* check for godmode */
+ if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION))
+ {
+ take = 0;
+ save = damage;
+ SpawnDamage(te_sparks, point, normal, save);
+ }
+
+ /* check for invincibility */
+ if ((client && (client->invincible_framenum > level.framenum)) &&
+ !(dflags & DAMAGE_NO_PROTECTION) && (mod != MOD_TRAP))
+ {
+ if (targ->pain_debounce_time < level.time)
+ {
+ gi.sound(targ, CHAN_ITEM, gi.soundindex(
+ "items/protect4.wav"), 1, ATTN_NORM, 0);
+ targ->pain_debounce_time = level.time + 2;
+ }
+
+ take = 0;
+ save = damage;
+ }
+
+ psave = CheckPowerArmor(targ, point, normal, take, dflags);
+ take -= psave;
+
+ asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
+ take -= asave;
+
+ /* treat cheat/powerup savings the same as armor */
+ asave += save;
+
+ /* do the damage */
+ if (take)
+ {
+ if ((targ->svflags & SVF_MONSTER) || (client))
+ {
+ if (strcmp(targ->classname, "monster_gekk") == 0)
+ {
+ SpawnDamage(TE_GREENBLOOD, point, normal, take);
+ }
+ else
+ {
+ SpawnDamage(TE_BLOOD, point, normal, take);
+ }
+ }
+ else
+ {
+ SpawnDamage(te_sparks, point, normal, take);
+ }
+
+ targ->health = targ->health - take;
+
+ if (targ->health <= 0)
+ {
+ if ((targ->svflags & SVF_MONSTER) || (client))
+ {
+ targ->flags |= FL_NO_KNOCKBACK;
+ }
+
+ Killed(targ, inflictor, attacker, take, point);
+ return;
+ }
+ }
+
+ if (targ->svflags & SVF_MONSTER)
+ {
+ M_ReactToDamage(targ, attacker);
+
+ if (!(targ->monsterinfo.aiflags & (AI_DUCKED|AI_IGNORE_PAIN)) && (take))
+ {
+ targ->pain(targ, attacker, knockback, take);
+
+ /* nightmare mode monsters don't go into pain frames often */
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ targ->pain_debounce_time = level.time + 5;
+ }
+ }
+ }
+ else if (client)
+ {
+ if (!(targ->flags & FL_GODMODE) && (take))
+ {
+ targ->pain(targ, attacker, knockback, take);
+ }
+ }
+ else if (take)
+ {
+ if (targ->pain)
+ {
+ targ->pain(targ, attacker, knockback, take);
+ }
+ }
+
+ /* add to the damage inflicted on a player this frame
+ the total will be turned into screen blends and view angle kicks
+ at the end of the frame */
+ if (client)
+ {
+ client->damage_parmor += psave;
+ client->damage_armor += asave;
+ client->damage_blood += take;
+ client->damage_knockback += knockback;
+ VectorCopy(point, client->damage_from);
+ }
+}
+
+void
+T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage,
+ edict_t *ignore, float radius, int mod)
+{
+ float points;
+ edict_t *ent = NULL;
+ vec3_t v;
+ vec3_t dir;
+
+ if (!inflictor || !attacker)
+ {
+ return;
+ }
+
+ while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
+ {
+ if (ent == ignore)
+ {
+ continue;
+ }
+
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ VectorAdd(ent->mins, ent->maxs, v);
+ VectorMA(ent->s.origin, 0.5, v, v);
+ VectorSubtract(inflictor->s.origin, v, v);
+ points = damage - 0.5 * VectorLength(v);
+
+ if (ent == attacker)
+ {
+ points = points * 0.5;
+ }
+
+ if (points > 0)
+ {
+ if (CanDamage(ent, inflictor))
+ {
+ VectorSubtract(ent->s.origin, inflictor->s.origin, dir);
+ T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin,
+ vec3_origin, (int)points, (int)points, DAMAGE_RADIUS,
+ mod);
+ }
+ }
+ }
+}
+
diff --git a/xatrix/src/g_func.c b/xatrix/src/g_func.c
new file mode 100644
index 0000000..7c3cbe8
--- /dev/null
+++ b/xatrix/src/g_func.c
@@ -0,0 +1,3214 @@
+/*
+ * =======================================================================
+ *
+ * Level functions. Platforms, buttons, dooors and so on.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+/*
+ * PLATS
+ *
+ * movement options:
+ *
+ * linear
+ * smooth start, hard stop
+ * smooth start, smooth stop
+ *
+ * start
+ * end
+ * acceleration
+ * speed
+ * deceleration
+ * begin sound
+ * end sound
+ * target fired when reaching end
+ * wait at end
+ *
+ * object characteristics that use move segments
+ * ---------------------------------------------
+ * movetype_push, or movetype_stop
+ * action when touched
+ * action when blocked
+ * action when used
+ * disabled?
+ * auto trigger spawning
+ */
+
+#define PLAT_LOW_TRIGGER 1
+#define STATE_TOP 0
+#define STATE_BOTTOM 1
+#define STATE_UP 2
+#define STATE_DOWN 3
+#define DOOR_START_OPEN 1
+#define DOOR_REVERSE 2
+#define DOOR_CRUSHER 4
+#define DOOR_NOMONSTER 8
+#define DOOR_TOGGLE 32
+#define DOOR_X_AXIS 64
+#define DOOR_Y_AXIS 128
+
+/* Support routines for movement (changes in origin using velocity) */
+
+void
+Move_Done(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->velocity);
+ ent->moveinfo.endfunc(ent);
+}
+
+void
+Move_Final(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->moveinfo.remaining_distance == 0)
+ {
+ Move_Done(ent);
+ return;
+ }
+
+ VectorScale(ent->moveinfo.dir,
+ ent->moveinfo.remaining_distance / FRAMETIME,
+ ent->velocity);
+
+ ent->think = Move_Done;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+Move_Begin(edict_t *ent)
+{
+ float frames;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
+ {
+ Move_Final(ent);
+ return;
+ }
+
+ VectorScale(ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
+ frames = floor((ent->moveinfo.remaining_distance /
+ ent->moveinfo.speed) / FRAMETIME);
+ ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
+ ent->nextthink = level.time + (frames * FRAMETIME);
+ ent->think = Move_Final;
+}
+
+void Think_AccelMove(edict_t *ent);
+
+void
+Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *))
+{
+ if (!ent || !func)
+ {
+ return;
+ }
+
+ VectorClear(ent->velocity);
+ VectorSubtract(dest, ent->s.origin, ent->moveinfo.dir);
+ ent->moveinfo.remaining_distance = VectorNormalize(ent->moveinfo.dir);
+ ent->moveinfo.endfunc = func;
+
+ if ((ent->moveinfo.speed == ent->moveinfo.accel) &&
+ (ent->moveinfo.speed == ent->moveinfo.decel))
+ {
+ if (level.current_entity ==
+ ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+ {
+ Move_Begin(ent);
+ }
+ else
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = Move_Begin;
+ }
+ }
+ else
+ {
+ /* accelerative */
+ ent->moveinfo.current_speed = 0;
+ ent->think = Think_AccelMove;
+ ent->nextthink = level.time + FRAMETIME;
+ }
+}
+
+/*
+ * Support routines for angular movement
+ * (changes in angle using avelocity)
+ */
+void
+AngleMove_Done(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->avelocity);
+ ent->moveinfo.endfunc(ent);
+}
+
+void
+AngleMove_Final(edict_t *ent)
+{
+ vec3_t move;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->moveinfo.state == STATE_UP)
+ {
+ VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, move);
+ }
+ else
+ {
+ VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, move);
+ }
+
+ if (VectorCompare(move, vec3_origin))
+ {
+ AngleMove_Done(ent);
+ return;
+ }
+
+ VectorScale(move, 1.0 / FRAMETIME, ent->avelocity);
+
+ ent->think = AngleMove_Done;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+AngleMove_Begin(edict_t *ent)
+{
+ vec3_t destdelta;
+ float len;
+ float traveltime;
+ float frames;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* set destdelta to the vector needed to move */
+ if (ent->moveinfo.state == STATE_UP)
+ {
+ VectorSubtract(ent->moveinfo.end_angles, ent->s.angles, destdelta);
+ }
+ else
+ {
+ VectorSubtract(ent->moveinfo.start_angles, ent->s.angles, destdelta);
+ }
+
+ /* calculate length of vector */
+ len = VectorLength(destdelta);
+
+ /* divide by speed to get time to reach dest */
+ traveltime = len / ent->moveinfo.speed;
+
+ if (traveltime < FRAMETIME)
+ {
+ AngleMove_Final(ent);
+ return;
+ }
+
+ frames = floor(traveltime / FRAMETIME);
+
+ /* scale the destdelta vector by the time spent traveling to get velocity */
+ VectorScale(destdelta, 1.0 / traveltime, ent->avelocity);
+
+ /* set nextthink to trigger a think when dest is reached */
+ ent->nextthink = level.time + frames * FRAMETIME;
+ ent->think = AngleMove_Final;
+}
+
+void
+AngleMove_Calc(edict_t *ent, void (*func)(edict_t *))
+{
+ if (!ent || !func)
+ {
+ return;
+ }
+
+ VectorClear(ent->avelocity);
+ ent->moveinfo.endfunc = func;
+
+ if (level.current_entity ==
+ ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+ {
+ AngleMove_Begin(ent);
+ }
+ else
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = AngleMove_Begin;
+ }
+}
+
+/*
+ * The team has completed a frame of movement, so
+ * change the speed for the next frame
+ */
+
+#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2)
+
+void
+plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
+{
+ float accel_dist;
+ float decel_dist;
+
+ if (!moveinfo)
+ {
+ return;
+ }
+
+ moveinfo->move_speed = moveinfo->speed;
+
+ if (moveinfo->remaining_distance < moveinfo->accel)
+ {
+ moveinfo->current_speed = moveinfo->remaining_distance;
+ return;
+ }
+
+ accel_dist = AccelerationDistance(moveinfo->speed, moveinfo->accel);
+ decel_dist = AccelerationDistance(moveinfo->speed, moveinfo->decel);
+
+ if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
+ {
+ float f;
+
+ f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
+ moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
+ decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel);
+ }
+
+ moveinfo->decel_distance = decel_dist;
+}
+
+void
+plat_Accelerate(moveinfo_t *moveinfo)
+{
+ if (!moveinfo)
+ {
+ return;
+ }
+
+ /* are we decelerating? */
+ if (moveinfo->remaining_distance <= moveinfo->decel_distance)
+ {
+ if (moveinfo->remaining_distance < moveinfo->decel_distance)
+ {
+ if (moveinfo->next_speed)
+ {
+ moveinfo->current_speed = moveinfo->next_speed;
+ moveinfo->next_speed = 0;
+ return;
+ }
+
+ if (moveinfo->current_speed > moveinfo->decel)
+ {
+ moveinfo->current_speed -= moveinfo->decel;
+ }
+ }
+
+ return;
+ }
+
+ /* are we at full speed and need to start decelerating during this move? */
+ if (moveinfo->current_speed == moveinfo->move_speed)
+ {
+ if ((moveinfo->remaining_distance - moveinfo->current_speed) <
+ moveinfo->decel_distance)
+ {
+ float p1_distance;
+ float p2_distance;
+ float distance;
+
+ p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+ p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
+ distance = p1_distance + p2_distance;
+ moveinfo->current_speed = moveinfo->move_speed;
+ moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
+ return;
+ }
+ }
+
+ /* are we accelerating? */
+ if (moveinfo->current_speed < moveinfo->speed)
+ {
+ float old_speed;
+ float p1_distance;
+ float p1_speed;
+ float p2_distance;
+ float distance;
+
+ old_speed = moveinfo->current_speed;
+
+ /* figure simple acceleration up to move_speed */
+ moveinfo->current_speed += moveinfo->accel;
+
+ if (moveinfo->current_speed > moveinfo->speed)
+ {
+ moveinfo->current_speed = moveinfo->speed;
+ }
+
+ /* are we accelerating throughout this entire move? */
+ if ((moveinfo->remaining_distance - moveinfo->current_speed) >=
+ moveinfo->decel_distance)
+ {
+ return;
+ }
+
+ /* during this move we will accelrate from current_speed to move_speed
+ and cross over the decel_distance; figure the average speed for the
+ entire move */
+ p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+ p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
+ p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
+ distance = p1_distance + p2_distance;
+ moveinfo->current_speed = (p1_speed * (p1_distance /
+ distance)) + (moveinfo->move_speed * (p2_distance / distance));
+ moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel *
+ (p2_distance / distance);
+ return;
+ }
+
+ /* we are at constant velocity (move_speed) */
+ return;
+}
+
+/*
+ * The team has completed a frame of movement,
+ * so change the speed for the next frame
+ */
+void
+Think_AccelMove(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
+
+ if (ent->moveinfo.current_speed == 0) /* starting or blocked */
+ {
+ plat_CalcAcceleratedMove(&ent->moveinfo);
+ }
+
+ plat_Accelerate(&ent->moveinfo);
+
+ /* will the entire move complete on next frame? */
+ if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
+ {
+ Move_Final(ent);
+ return;
+ }
+
+ VectorScale(ent->moveinfo.dir, ent->moveinfo.current_speed * 10,
+ ent->velocity);
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = Think_AccelMove;
+}
+
+void plat_go_down(edict_t *ent);
+
+void
+plat_hit_top(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_end)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = 0;
+ }
+
+ ent->moveinfo.state = STATE_TOP;
+
+ ent->think = plat_go_down;
+ ent->nextthink = level.time + 3;
+}
+
+void
+plat_hit_bottom(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_end)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end,
+ 1, ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = 0;
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+}
+
+void
+plat_go_down(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_start)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_start,
+ 1, ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = ent->moveinfo.sound_middle;
+ }
+
+ ent->moveinfo.state = STATE_DOWN;
+ Move_Calc(ent, ent->moveinfo.end_origin, plat_hit_bottom);
+}
+
+void
+plat_go_up(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->flags & FL_TEAMSLAVE))
+ {
+ if (ent->moveinfo.sound_start)
+ {
+ gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+ }
+
+ ent->s.sound = ent->moveinfo.sound_middle;
+ }
+
+ ent->moveinfo.state = STATE_UP;
+ Move_Calc(ent, ent->moveinfo.start_origin, plat_hit_top);
+}
+
+void
+plat_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entity without it's origin near the model */
+ VectorMA (other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+ if (self->moveinfo.state == STATE_UP)
+ {
+ plat_go_down(self);
+ }
+ else if (self->moveinfo.state == STATE_DOWN)
+ {
+ plat_go_up(self);
+ }
+}
+
+void
+Use_Plat(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->think)
+ {
+ return; /* already down */
+ }
+
+ plat_go_down(ent);
+}
+
+void
+wait_and_change_think(edict_t* ent)
+{
+ void (*afterwaitfunc)(edict_t *) = ent->moveinfo.endfunc;
+ ent->moveinfo.endfunc = NULL;
+ afterwaitfunc(ent);
+}
+
+/*
+ * In coop mode, this waits for coop_elevator_delay seconds
+ * before calling afterwaitfunc(ent); otherwise it just calls
+ * afterwaitfunc(ent);
+ */
+static void
+wait_and_change(edict_t* ent, void (*afterwaitfunc)(edict_t *))
+{
+ float waittime = coop_elevator_delay->value;
+ if (coop->value && waittime > 0.0f)
+ {
+ if(ent->nextthink == 0)
+ {
+ ent->moveinfo.endfunc = afterwaitfunc;
+ ent->think = wait_and_change_think;
+ ent->nextthink = level.time + waittime;
+ }
+ }
+ else
+ {
+ afterwaitfunc(ent);
+
+ }
+}
+
+void
+Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ ent = ent->enemy; /* now point at the plat, not the trigger */
+
+ if (ent->moveinfo.state == STATE_BOTTOM)
+ {
+ wait_and_change(ent, plat_go_up);
+ }
+ else if (ent->moveinfo.state == STATE_TOP)
+ {
+ ent->nextthink = level.time + 1; /* the player is still on the plat, so delay going down */
+ }
+}
+
+void
+plat_spawn_inside_trigger(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ edict_t *trigger;
+ vec3_t tmin, tmax;
+
+ /* middle trigger */
+ trigger = G_Spawn();
+ trigger->touch = Touch_Plat_Center;
+ trigger->movetype = MOVETYPE_NONE;
+ trigger->solid = SOLID_TRIGGER;
+ trigger->enemy = ent;
+
+ tmin[0] = ent->mins[0] + 25;
+ tmin[1] = ent->mins[1] + 25;
+
+ tmax[0] = ent->maxs[0] - 25;
+ tmax[1] = ent->maxs[1] - 25;
+ tmax[2] = ent->maxs[2] + 8;
+
+ tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
+
+ if (ent->spawnflags & PLAT_LOW_TRIGGER)
+ {
+ tmax[2] = tmin[2] + 8;
+ }
+
+ if (tmax[0] - tmin[0] <= 0)
+ {
+ tmin[0] = (ent->mins[0] + ent->maxs[0]) * 0.5;
+ tmax[0] = tmin[0] + 1;
+ }
+
+ if (tmax[1] - tmin[1] <= 0)
+ {
+ tmin[1] = (ent->mins[1] + ent->maxs[1]) * 0.5;
+ tmax[1] = tmin[1] + 1;
+ }
+
+ VectorCopy(tmin, trigger->mins);
+ VectorCopy(tmax, trigger->maxs);
+
+ gi.linkentity(trigger);
+}
+
+/*
+ * QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
+ *
+ * speed -> default 150
+ *
+ * Plats are always drawn in the extended position,
+ * so they will light correctly.
+ *
+ * If the plat is the target of another trigger or button,
+ * it will start out disabled in the extended position until
+ * it is trigger, when it will lower and become a normal plat.
+ *
+ * "speed" overrides default 200.
+ * "accel" overrides default 500
+ * "lip" overrides default 8 pixel lip
+ *
+ * If the "height" key is set, that will determine the amount
+ * the plat moves, instead of being implicitly determoveinfoned
+ * by the model's height.
+ *
+ * Set "sounds" to one of the following:
+ * 1) base fast
+ * 2) chain slow
+ */
+void
+SP_func_plat(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->s.angles);
+ ent->solid = SOLID_BSP;
+ ent->movetype = MOVETYPE_PUSH;
+
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = plat_blocked;
+
+ if (!ent->speed)
+ {
+ ent->speed = 20;
+ }
+ else
+ {
+ ent->speed *= 0.1;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = 5;
+ }
+ else
+ {
+ ent->accel *= 0.1;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = 5;
+ }
+ else
+ {
+ ent->decel *= 0.1;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ if (!st.lip)
+ {
+ st.lip = 8;
+ }
+
+ /* pos1 is the top position, pos2 is the bottom */
+ VectorCopy(ent->s.origin, ent->pos1);
+ VectorCopy(ent->s.origin, ent->pos2);
+
+ if (st.height)
+ {
+ ent->pos2[2] -= st.height;
+ }
+ else
+ {
+ ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
+ }
+
+ ent->use = Use_Plat;
+
+ plat_spawn_inside_trigger(ent); /* the "start moving" trigger */
+
+ if (ent->targetname)
+ {
+ ent->moveinfo.state = STATE_UP;
+ }
+ else
+ {
+ VectorCopy(ent->pos2, ent->s.origin);
+ gi.linkentity(ent);
+ ent->moveinfo.state = STATE_BOTTOM;
+ }
+
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->pos1, ent->moveinfo.start_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
+ VectorCopy(ent->pos2, ent->moveinfo.end_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
+
+ ent->moveinfo.sound_start = gi.soundindex("plats/pt1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("plats/pt1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("plats/pt1_end.wav");
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS
+ * TOUCH_PAIN STOP ANIMATED ANIMATED_FAST
+ *
+ * You need to have an origin brush as part of this entity.
+ * The center of that brush will be the point around which it
+ * is rotated. It will rotate around the Z axis by default.
+ * You can check either the X_AXIS or Y_AXIS box to change that.
+ *
+ * "speed" determines how fast it moves; default value is 100.
+ * "dmg" damage to inflict when blocked (2 default)
+ *
+ * REVERSE will cause the it to rotate in the opposite direction.
+ * STOP mean it will stop moving instead of pushing entities
+ */
+void
+rotating_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+rotating_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
+ {
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+ }
+}
+
+void
+rotating_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!VectorCompare(self->avelocity, vec3_origin))
+ {
+ self->s.sound = 0;
+ VectorClear(self->avelocity);
+ self->touch = NULL;
+ }
+ else
+ {
+ self->s.sound = self->moveinfo.sound_middle;
+ VectorScale(self->movedir, self->speed, self->avelocity);
+
+ if (self->spawnflags & 16)
+ {
+ self->touch = rotating_touch;
+ }
+ }
+}
+
+void
+SP_func_rotating(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->solid = SOLID_BSP;
+
+ if (ent->spawnflags & 32)
+ {
+ ent->movetype = MOVETYPE_STOP;
+ }
+ else
+ {
+ ent->movetype = MOVETYPE_PUSH;
+ }
+
+ /* set the axis of rotation */
+ VectorClear(ent->movedir);
+
+ if (ent->spawnflags & 4)
+ {
+ ent->movedir[2] = 1.0;
+ }
+ else if (ent->spawnflags & 8)
+ {
+ ent->movedir[0] = 1.0;
+ }
+ else
+ {
+ ent->movedir[1] = 1.0;
+ }
+
+ /* check for reverse rotation */
+ if (ent->spawnflags & 2)
+ {
+ VectorNegate(ent->movedir, ent->movedir);
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 100;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ ent->use = rotating_use;
+
+ ent->blocked = rotating_blocked;
+
+ if (ent->spawnflags & 1)
+ {
+ ent->use(ent, NULL, NULL);
+ }
+
+ if (ent->spawnflags & 64)
+ {
+ ent->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (ent->spawnflags & 128)
+ {
+ ent->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ gi.setmodel(ent, ent->model);
+ gi.linkentity(ent);
+}
+
+/* ==================================================================== */
+
+/* BUTTONS */
+
+/*
+ * QUAKED func_button (0 .5 .8) ?
+ *
+ * When a button is touched, it moves some distance
+ * in the direction of it's angle, triggers all of it's
+ * targets, waits some time, then returns to it's original
+ * position where it can be triggered again.
+ *
+ * "angle" determines the opening direction
+ * "target" all entities with a matching targetname will be used
+ * "speed" override the default 40 speed
+ * "wait" override the default 1 second wait (-1 = never return)
+ * "lip" override the default 4 pixel lip remaining at end of move
+ * "health" if set, the button must be killed instead of touched
+ * "sounds"
+ * 1) silent
+ * 2) steam metal
+ * 3) wooden clunk
+ * 4) metallic click
+ * 5) in-out
+ */
+void
+button_done(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_BOTTOM;
+ self->s.effects &= ~EF_ANIM23;
+ self->s.effects |= EF_ANIM01;
+}
+
+void
+button_return(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_DOWN;
+
+ Move_Calc(self, self->moveinfo.start_origin, button_done);
+
+ self->s.frame = 0;
+
+ if (self->health)
+ {
+ self->takedamage = DAMAGE_YES;
+ }
+}
+
+void
+button_wait(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_TOP;
+ self->s.effects &= ~EF_ANIM01;
+ self->s.effects |= EF_ANIM23;
+
+ G_UseTargets(self, self->activator);
+ self->s.frame = 1;
+
+ if (self->moveinfo.wait >= 0)
+ {
+ self->nextthink = level.time + self->moveinfo.wait;
+ self->think = button_return;
+ }
+}
+
+void
+button_fire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->moveinfo.state == STATE_UP) ||
+ (self->moveinfo.state == STATE_TOP))
+ {
+ return;
+ }
+
+ self->moveinfo.state = STATE_UP;
+
+ if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_start,
+ 1, ATTN_STATIC, 0);
+ }
+
+ Move_Calc(self, self->moveinfo.end_origin, button_wait);
+}
+
+void
+button_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self ||!activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+ button_fire(self);
+}
+
+void
+button_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ self->activator = other;
+ button_fire(self);
+}
+
+void
+button_killed(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unsued */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->activator = attacker;
+ self->health = self->max_health;
+ self->takedamage = DAMAGE_NO;
+ button_fire(self);
+}
+
+void
+SP_func_button(edict_t *ent)
+{
+ vec3_t abs_movedir;
+ float dist;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ G_SetMovedir(ent->s.angles, ent->movedir);
+ ent->movetype = MOVETYPE_STOP;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ if (ent->sounds != 1)
+ {
+ ent->moveinfo.sound_start = gi.soundindex("switches/butn2.wav");
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 40;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = ent->speed;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = ent->speed;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 3;
+ }
+
+ if (!st.lip)
+ {
+ st.lip = 4;
+ }
+
+ VectorCopy(ent->s.origin, ent->pos1);
+ abs_movedir[0] = fabs(ent->movedir[0]);
+ abs_movedir[1] = fabs(ent->movedir[1]);
+ abs_movedir[2] = fabs(ent->movedir[2]);
+ dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] +
+ abs_movedir[2] * ent->size[2] - st.lip;
+ VectorMA(ent->pos1, dist, ent->movedir, ent->pos2);
+
+ ent->use = button_use;
+ ent->s.effects |= EF_ANIM01;
+
+ if (ent->health)
+ {
+ ent->max_health = ent->health;
+ ent->die = button_killed;
+ ent->takedamage = DAMAGE_YES;
+ }
+ else if (!ent->targetname)
+ {
+ ent->touch = button_touch;
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->pos1, ent->moveinfo.start_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
+ VectorCopy(ent->pos2, ent->moveinfo.end_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
+
+ gi.linkentity(ent);
+}
+
+/* ==================================================================== */
+
+/*
+ * DOORS
+ *
+ * spawn a trigger surrounding the entire team
+ * unless it is already targeted by another
+ */
+
+void door_go_down(edict_t *self);
+
+/*
+ * QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
+ *
+ * TOGGLE wait in both the start and end states for a trigger event.
+ * START_OPEN the door to moves to its destination when spawned, and operate in reverse.
+ * It is used to temporarily or permanently close off an area when triggered
+ * (not useful for touch or takedamage doors).
+ * NOMONSTER monsters will not trigger this door
+ *
+ * "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+ * "angle" determines the opening direction
+ * "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+ * "health" if set, door must be shot open
+ * "speed" movement speed (100 default)
+ * "wait" wait before returning (3 default, -1 = never return)
+ * "lip" lip remaining at end of move (8 default)
+ * "dmg" damage to inflict when blocked (2 default)
+ * "sounds"
+ * 1) silent
+ * 2) light
+ * 3) medium
+ * 4) heavy
+ */
+
+void
+door_use_areaportals(edict_t *self, qboolean open)
+{
+ edict_t *t = NULL;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ return;
+ }
+
+ while ((t = G_Find(t, FOFS(targetname), self->target)))
+ {
+ if (Q_stricmp(t->classname, "func_areaportal") == 0)
+ {
+ gi.SetAreaPortalState(t->style, open);
+ }
+ }
+}
+
+void
+door_hit_top(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_end)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_end, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = 0;
+ }
+
+ self->moveinfo.state = STATE_TOP;
+
+ if (self->spawnflags & DOOR_TOGGLE)
+ {
+ return;
+ }
+
+ if (self->moveinfo.wait >= 0)
+ {
+ self->think = door_go_down;
+ self->nextthink = level.time + self->moveinfo.wait;
+ }
+}
+
+void
+door_hit_bottom(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_end)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_end, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = 0;
+ }
+
+ self->moveinfo.state = STATE_BOTTOM;
+ door_use_areaportals(self, false);
+}
+
+void
+door_go_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_start)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = self->moveinfo.sound_middle;
+ }
+
+ if (self->max_health)
+ {
+ self->takedamage = DAMAGE_YES;
+ self->health = self->max_health;
+ }
+
+ self->moveinfo.state = STATE_DOWN;
+
+ if (strcmp(self->classname, "func_door") == 0)
+ {
+ Move_Calc(self, self->moveinfo.start_origin, door_hit_bottom);
+ }
+ else if (strcmp(self->classname, "func_door_rotating") == 0)
+ {
+ AngleMove_Calc(self, door_hit_bottom);
+ }
+}
+
+void
+door_go_up(edict_t *self, edict_t *activator)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->moveinfo.state == STATE_UP)
+ {
+ return; /* already going up */
+ }
+
+ if (self->moveinfo.state == STATE_TOP)
+ {
+ /* reset top wait time */
+ if (self->moveinfo.wait >= 0)
+ {
+ self->nextthink = level.time + self->moveinfo.wait;
+ }
+
+ return;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_start)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = self->moveinfo.sound_middle;
+ }
+
+ self->moveinfo.state = STATE_UP;
+
+ if (strcmp(self->classname, "func_door") == 0)
+ {
+ Move_Calc(self, self->moveinfo.end_origin, door_hit_top);
+ }
+ else if (strcmp(self->classname, "func_door_rotating") == 0)
+ {
+ AngleMove_Calc(self, door_hit_top);
+ }
+
+ G_UseTargets(self, activator);
+ door_use_areaportals(self, true);
+}
+
+void
+door_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ edict_t *ent;
+
+ if (self->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ if (self->spawnflags & DOOR_TOGGLE)
+ {
+ if ((self->moveinfo.state == STATE_UP) ||
+ (self->moveinfo.state == STATE_TOP))
+ {
+ /* trigger all paired doors */
+ for (ent = self; ent; ent = ent->teamchain)
+ {
+ ent->message = NULL;
+ ent->touch = NULL;
+ door_go_down(ent);
+ }
+
+ return;
+ }
+ }
+
+ /* trigger all paired doors */
+ for (ent = self; ent; ent = ent->teamchain)
+ {
+ ent->message = NULL;
+ ent->touch = NULL;
+ door_go_up(ent, activator);
+ }
+}
+
+void
+Touch_DoorTrigger(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->health <= 0)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ return;
+ }
+
+ if ((self->owner->spawnflags & DOOR_NOMONSTER) &&
+ (other->svflags & SVF_MONSTER))
+ {
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 1.0;
+
+ door_use(self->owner, other, other);
+}
+
+void
+Think_CalcMoveSpeed(edict_t *self)
+{
+ edict_t *ent;
+ float min;
+ float time;
+ float newspeed;
+ float ratio;
+ float dist;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->flags & FL_TEAMSLAVE)
+ {
+ return; /* only the team master does this */
+ }
+
+ /* find the smallest distance any member of the team will be moving */
+ min = fabs(self->moveinfo.distance);
+
+ for (ent = self->teamchain; ent; ent = ent->teamchain)
+ {
+ dist = fabs(ent->moveinfo.distance);
+
+ if (dist < min)
+ {
+ min = dist;
+ }
+ }
+
+ time = min / self->moveinfo.speed;
+
+ /* adjust speeds so they will all complete at the same time */
+ for (ent = self; ent; ent = ent->teamchain)
+ {
+ newspeed = fabs(ent->moveinfo.distance) / time;
+ ratio = newspeed / ent->moveinfo.speed;
+
+ if (ent->moveinfo.accel == ent->moveinfo.speed)
+ {
+ ent->moveinfo.accel = newspeed;
+ }
+ else
+ {
+ ent->moveinfo.accel *= ratio;
+ }
+
+ if (ent->moveinfo.decel == ent->moveinfo.speed)
+ {
+ ent->moveinfo.decel = newspeed;
+ }
+ else
+ {
+ ent->moveinfo.decel *= ratio;
+ }
+
+ ent->moveinfo.speed = newspeed;
+ }
+}
+
+void
+Think_SpawnDoorTrigger(edict_t *ent)
+{
+ edict_t *other;
+ vec3_t mins, maxs;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->flags & FL_TEAMSLAVE)
+ {
+ return; /* only the team leader spawns a trigger */
+ }
+
+ VectorCopy(ent->absmin, mins);
+ VectorCopy(ent->absmax, maxs);
+
+ for (other = ent->teamchain; other; other = other->teamchain)
+ {
+ AddPointToBounds(other->absmin, mins, maxs);
+ AddPointToBounds(other->absmax, mins, maxs);
+ }
+
+ /* expand */
+ mins[0] -= 60;
+ mins[1] -= 60;
+ maxs[0] += 60;
+ maxs[1] += 60;
+
+ other = G_Spawn();
+ VectorCopy(mins, other->mins);
+ VectorCopy(maxs, other->maxs);
+ other->owner = ent;
+ other->solid = SOLID_TRIGGER;
+ other->movetype = MOVETYPE_NONE;
+ other->touch = Touch_DoorTrigger;
+ gi.linkentity(other);
+
+ if (ent->spawnflags & DOOR_START_OPEN)
+ {
+ door_use_areaportals(ent, true);
+ }
+
+ Think_CalcMoveSpeed(ent);
+}
+
+void
+door_blocked(edict_t *self, edict_t *other)
+{
+ edict_t *ent;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entitiy without their origin near the model */
+ VectorMA (other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin,
+ self->dmg, 1, 0, MOD_CRUSH);
+
+ if (self->spawnflags & DOOR_CRUSHER)
+ {
+ return;
+ }
+
+ /* if a door has a negative wait, it would never
+ come back if blocked, so let it just squash the
+ object to death real fast */
+ if (self->moveinfo.wait >= 0)
+ {
+ if (self->moveinfo.state == STATE_DOWN)
+ {
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ door_go_up(ent, ent->activator);
+ }
+ }
+ else
+ {
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ door_go_down(ent);
+ }
+ }
+ }
+}
+
+void
+door_killed(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ edict_t *ent;
+
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ ent->health = ent->max_health;
+ ent->takedamage = DAMAGE_NO;
+ }
+
+ door_use(self->teammaster, attacker, attacker);
+}
+
+void
+door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 5.0;
+
+ gi.centerprintf(other, "%s", self->message);
+ gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+SP_func_door(edict_t *ent)
+{
+ vec3_t abs_movedir;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->sounds != 1)
+ {
+ ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
+ }
+
+ G_SetMovedir(ent->s.angles, ent->movedir);
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = door_blocked;
+ ent->use = door_use;
+
+ if (!ent->speed)
+ {
+ ent->speed = 100;
+ }
+
+ if (deathmatch->value)
+ {
+ ent->speed *= 2;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = ent->speed;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = ent->speed;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 3;
+ }
+
+ if (!st.lip)
+ {
+ st.lip = 8;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ /* calculate second position */
+ VectorCopy(ent->s.origin, ent->pos1);
+ abs_movedir[0] = fabs(ent->movedir[0]);
+ abs_movedir[1] = fabs(ent->movedir[1]);
+ abs_movedir[2] = fabs(ent->movedir[2]);
+ ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] *
+ ent->size[1] + abs_movedir[2] * ent->size[2] -
+ st.lip;
+ VectorMA(ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
+
+ /* if it starts open, switch the positions */
+ if (ent->spawnflags & DOOR_START_OPEN)
+ {
+ VectorCopy(ent->pos2, ent->s.origin);
+ VectorCopy(ent->pos1, ent->pos2);
+ VectorCopy(ent->s.origin, ent->pos1);
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+
+ if (ent->health)
+ {
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_killed;
+ ent->max_health = ent->health;
+ }
+ else if (ent->targetname && ent->message)
+ {
+ gi.soundindex("misc/talk.wav");
+ ent->touch = door_touch;
+ }
+
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->pos1, ent->moveinfo.start_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.start_angles);
+ VectorCopy(ent->pos2, ent->moveinfo.end_origin);
+ VectorCopy(ent->s.angles, ent->moveinfo.end_angles);
+
+ if (ent->spawnflags & 16)
+ {
+ ent->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (ent->spawnflags & 64)
+ {
+ ent->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ /* to simplify logic elsewhere, make non-teamed doors into a team of one */
+ if (!ent->team)
+ {
+ ent->teammaster = ent;
+ }
+
+ gi.linkentity(ent);
+
+ ent->nextthink = level.time + FRAMETIME;
+
+ if (ent->health || ent->targetname)
+ {
+ ent->think = Think_CalcMoveSpeed;
+ }
+ else
+ {
+ ent->think = Think_SpawnDoorTrigger;
+ }
+}
+
+/*
+ * QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS
+ *
+ * TOGGLE causes the door to wait in both the start and end states for a trigger event.
+ * START_OPEN the door to moves to its destination when spawned, and operate in reverse.
+ * It is used to temporarily or permanently close off an area when triggered
+ * (not useful for touch or takedamage doors).
+ * NOMONSTER monsters will not trigger this door
+ *
+ * You need to have an origin brush as part of this entity. The center of that brush will be
+ * the point around which it is rotated. It will rotate around the Z axis by default. You can
+ * check either the X_AXIS or Y_AXIS box to change that.
+ *
+ * "distance" is how many degrees the door will be rotated.
+ * "speed" determines how fast the door moves; default value is 100.
+ *
+ * REVERSE will cause the door to rotate in the opposite direction.
+ *
+ * "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+ * "angle" determines the opening direction
+ * "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+ * "health" if set, door must be shot open
+ * "speed" movement speed (100 default)
+ * "wait" wait before returning (3 default, -1 = never return)
+ * "dmg" damage to inflict when blocked (2 default)
+ * "sounds"
+ * 1) silent
+ * 2) light
+ * 3) medium
+ * 4) heavy
+ */
+
+void
+SP_func_door_rotating(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorClear(ent->s.angles);
+
+ /* set the axis of rotation */
+ VectorClear(ent->movedir);
+
+ if (ent->spawnflags & DOOR_X_AXIS)
+ {
+ ent->movedir[2] = 1.0;
+ }
+ else if (ent->spawnflags & DOOR_Y_AXIS)
+ {
+ ent->movedir[0] = 1.0;
+ }
+ else /* Z_AXIS */
+ {
+ ent->movedir[1] = 1.0;
+ }
+
+ /* check for reverse rotation */
+ if (ent->spawnflags & DOOR_REVERSE)
+ {
+ VectorNegate(ent->movedir, ent->movedir);
+ }
+
+ if (!st.distance)
+ {
+ gi.dprintf("%s at %s with no distance set\n", ent->classname,
+ vtos(ent->s.origin));
+ st.distance = 90;
+ }
+
+ VectorCopy(ent->s.angles, ent->pos1);
+ VectorMA(ent->s.angles, st.distance, ent->movedir, ent->pos2);
+ ent->moveinfo.distance = st.distance;
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = door_blocked;
+ ent->use = door_use;
+
+ if (!ent->speed)
+ {
+ ent->speed = 100;
+ }
+
+ if (!ent->accel)
+ {
+ ent->accel = ent->speed;
+ }
+
+ if (!ent->decel)
+ {
+ ent->decel = ent->speed;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 3;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ if (ent->sounds != 1)
+ {
+ ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
+ }
+
+ /* if it starts open, switch the positions */
+ if (ent->spawnflags & DOOR_START_OPEN)
+ {
+ VectorCopy(ent->pos2, ent->s.angles);
+ VectorCopy(ent->pos1, ent->pos2);
+ VectorCopy(ent->s.angles, ent->pos1);
+ VectorNegate(ent->movedir, ent->movedir);
+ }
+
+ if (ent->health)
+ {
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_killed;
+ ent->max_health = ent->health;
+ }
+
+ if (ent->targetname && ent->message)
+ {
+ gi.soundindex("misc/talk.wav");
+ ent->touch = door_touch;
+ }
+
+ ent->moveinfo.state = STATE_BOTTOM;
+ ent->moveinfo.speed = ent->speed;
+ ent->moveinfo.accel = ent->accel;
+ ent->moveinfo.decel = ent->decel;
+ ent->moveinfo.wait = ent->wait;
+ VectorCopy(ent->s.origin, ent->moveinfo.start_origin);
+ VectorCopy(ent->pos1, ent->moveinfo.start_angles);
+ VectorCopy(ent->s.origin, ent->moveinfo.end_origin);
+ VectorCopy(ent->pos2, ent->moveinfo.end_angles);
+
+ if (ent->spawnflags & 16)
+ {
+ ent->s.effects |= EF_ANIM_ALL;
+ }
+
+ /* to simplify logic elsewhere, make non-teamed doors into a team of one */
+ if (!ent->team)
+ {
+ ent->teammaster = ent;
+ }
+
+ gi.linkentity(ent);
+
+ ent->nextthink = level.time + FRAMETIME;
+
+ if (ent->health || ent->targetname)
+ {
+ ent->think = Think_CalcMoveSpeed;
+ }
+ else
+ {
+ ent->think = Think_SpawnDoorTrigger;
+ }
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_water (0 .5 .8) ? START_OPEN
+ *
+ * func_water is a moveable water brush. It must be targeted to operate.
+ * Use a non-water texture at your own risk.
+ *
+ * START_OPEN causes the water to move to its destination when spawned
+ * and operate in reverse.
+ *
+ * "angle" determines the opening direction (up or down only)
+ * "speed" movement speed (25 default)
+ * "wait" wait before returning (-1 default, -1 = TOGGLE)
+ * "lip" lip remaining at end of move (0 default)
+ * "sounds" (yes, these need to be changed)
+ * 0) no sound
+ * 1) water
+ * 2) lava
+ */
+void
+SP_func_water(edict_t *self)
+{
+ vec3_t abs_movedir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ G_SetMovedir(self->s.angles, self->movedir);
+ self->movetype = MOVETYPE_PUSH;
+ self->solid = SOLID_BSP;
+ gi.setmodel(self, self->model);
+
+ switch (self->sounds)
+ {
+ default:
+ break;
+
+ case 1: /* water */
+ case 2: /* lava */
+ self->moveinfo.sound_start = gi.soundindex("world/mov_watr.wav");
+ self->moveinfo.sound_end = gi.soundindex("world/stp_watr.wav");
+ break;
+ }
+
+ /* calculate second position */
+ VectorCopy(self->s.origin, self->pos1);
+ abs_movedir[0] = fabs(self->movedir[0]);
+ abs_movedir[1] = fabs(self->movedir[1]);
+ abs_movedir[2] = fabs(self->movedir[2]);
+ self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] *
+ self->size[1] + abs_movedir[2] * self->size[2] -
+ st.lip;
+ VectorMA(self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
+
+ /* if it starts open, switch the positions */
+ if (self->spawnflags & DOOR_START_OPEN)
+ {
+ VectorCopy(self->pos2, self->s.origin);
+ VectorCopy(self->pos1, self->pos2);
+ VectorCopy(self->s.origin, self->pos1);
+ }
+
+ VectorCopy(self->pos1, self->moveinfo.start_origin);
+ VectorCopy(self->s.angles, self->moveinfo.start_angles);
+ VectorCopy(self->pos2, self->moveinfo.end_origin);
+ VectorCopy(self->s.angles, self->moveinfo.end_angles);
+
+ self->moveinfo.state = STATE_BOTTOM;
+
+ if (!self->speed)
+ {
+ self->speed = 25;
+ }
+
+ self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
+
+ if (!self->wait)
+ {
+ self->wait = -1;
+ }
+
+ self->moveinfo.wait = self->wait;
+
+ self->use = door_use;
+
+ if (self->wait == -1)
+ {
+ self->spawnflags |= DOOR_TOGGLE;
+ }
+
+ self->classname = "func_door";
+
+ gi.linkentity(self);
+}
+
+/* ==================================================================== */
+
+#define TRAIN_START_ON 1
+#define TRAIN_TOGGLE 2
+#define TRAIN_BLOCK_STOPS 4
+
+void train_next(edict_t *self);
+
+/*
+ * QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
+ *
+ * Trains are moving platforms that players can ride.
+ * The targets origin specifies the min point of the train
+ * at each corner. The train spawns at the first target it
+ * is pointing at. If the train is the target of a button
+ * or trigger, it will not begin moving until activated.
+ *
+ * speed default 100
+ * dmg default 2
+ * noise looping sound to play when the train is in motion
+ *
+ */
+void train_next(edict_t *self);
+
+void
+train_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entity without an origin near the model */
+ VectorMA (other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ if (!self->dmg)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 0.5;
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+train_wait(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->target_ent->pathtarget)
+ {
+ char *savetarget;
+ edict_t *ent;
+
+ ent = self->target_ent;
+ savetarget = ent->target;
+ ent->target = ent->pathtarget;
+ G_UseTargets(ent, self->activator);
+ ent->target = savetarget;
+
+ /* make sure we didn't get killed by a killtarget */
+ if (!self->inuse)
+ {
+ return;
+ }
+ }
+
+ if (self->moveinfo.wait)
+ {
+ if (self->moveinfo.wait > 0)
+ {
+ self->nextthink = level.time + self->moveinfo.wait;
+ self->think = train_next;
+ }
+ else if (self->spawnflags & TRAIN_TOGGLE)
+ {
+ train_next(self);
+ self->spawnflags &= ~TRAIN_START_ON;
+ VectorClear(self->velocity);
+ self->nextthink = 0;
+ }
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_end)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_end, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = 0;
+ }
+ }
+ else
+ {
+ train_next(self);
+ }
+}
+
+void
+train_next(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ edict_t *ent;
+ vec3_t dest;
+ qboolean first;
+
+ first = true;
+
+again:
+
+ if (!self->target)
+ {
+ return;
+ }
+
+ ent = G_PickTarget(self->target);
+
+ if (!ent)
+ {
+ gi.dprintf("train_next: bad target %s\n", self->target);
+ return;
+ }
+
+ self->target = ent->target;
+
+ /* check for a teleport path_corner */
+ if (ent->spawnflags & 1)
+ {
+ if (!first)
+ {
+ gi.dprintf("connected teleport path_corners, see %s at %s\n",
+ ent->classname, vtos(ent->s.origin));
+ return;
+ }
+
+ first = false;
+ VectorSubtract(ent->s.origin, self->mins, self->s.origin);
+ VectorCopy(self->s.origin, self->s.old_origin);
+ self->s.event = EV_OTHER_TELEPORT;
+ gi.linkentity(self);
+ goto again;
+ }
+
+ self->moveinfo.wait = ent->wait;
+ self->target_ent = ent;
+
+ if (!(self->flags & FL_TEAMSLAVE))
+ {
+ if (self->moveinfo.sound_start)
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ }
+
+ self->s.sound = self->moveinfo.sound_middle;
+ }
+
+ VectorSubtract(ent->s.origin, self->mins, dest);
+ self->moveinfo.state = STATE_TOP;
+ VectorCopy(self->s.origin, self->moveinfo.start_origin);
+ VectorCopy(dest, self->moveinfo.end_origin);
+ Move_Calc(self, dest, train_wait);
+ self->spawnflags |= TRAIN_START_ON;
+}
+
+void
+train_resume(edict_t *self)
+{
+ edict_t *ent;
+ vec3_t dest;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = self->target_ent;
+
+ VectorSubtract(ent->s.origin, self->mins, dest);
+ self->moveinfo.state = STATE_TOP;
+ VectorCopy(self->s.origin, self->moveinfo.start_origin);
+ VectorCopy(dest, self->moveinfo.end_origin);
+ Move_Calc(self, dest, train_wait);
+ self->spawnflags |= TRAIN_START_ON;
+}
+
+void
+func_train_find(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("train_find: no target\n");
+ return;
+ }
+
+ ent = G_PickTarget(self->target);
+
+ if (!ent)
+ {
+ gi.dprintf("train_find: target %s not found\n", self->target);
+ return;
+ }
+
+ self->target = ent->target;
+
+ VectorSubtract(ent->s.origin, self->mins, self->s.origin);
+ gi.linkentity(self);
+
+ /* if not triggered, start immediately */
+ if (!self->targetname)
+ {
+ self->spawnflags |= TRAIN_START_ON;
+ }
+
+ if (self->spawnflags & TRAIN_START_ON)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ self->think = train_next;
+ self->activator = self;
+ }
+}
+
+void
+train_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ if (self->spawnflags & TRAIN_START_ON)
+ {
+ if (!(self->spawnflags & TRAIN_TOGGLE))
+ {
+ return;
+ }
+
+ self->spawnflags &= ~TRAIN_START_ON;
+ VectorClear(self->velocity);
+ self->nextthink = 0;
+ }
+ else
+ {
+ if (self->target_ent)
+ {
+ train_resume(self);
+ }
+ else
+ {
+ train_next(self);
+ }
+ }
+}
+
+void
+SP_func_train(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+
+ VectorClear(self->s.angles);
+ self->blocked = train_blocked;
+
+ if (self->spawnflags & TRAIN_BLOCK_STOPS)
+ {
+ self->dmg = 0;
+ }
+ else
+ {
+ if (!self->dmg)
+ {
+ self->dmg = 100;
+ }
+ }
+
+ self->solid = SOLID_BSP;
+ gi.setmodel(self, self->model);
+
+ if (st.noise)
+ {
+ self->moveinfo.sound_middle = gi.soundindex(st.noise);
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 100;
+ }
+
+ self->moveinfo.speed = self->speed;
+ self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
+
+ self->use = train_use;
+
+ gi.linkentity(self);
+
+ if (self->target)
+ {
+ /* start trains on the second frame, to make
+ * sure their targets have had a chance to spawn */
+ self->nextthink = level.time + FRAMETIME;
+ self->think = func_train_find;
+ }
+ else
+ {
+ gi.dprintf("func_train without a target at %s\n", vtos(self->absmin));
+ }
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
+ */
+void
+trigger_elevator_use(edict_t *self, edict_t *other, edict_t *activator /* unused */)
+{
+ edict_t *target;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (self->movetarget->nextthink)
+ {
+ return;
+ }
+
+ if (!other->pathtarget)
+ {
+ gi.dprintf("elevator used with no pathtarget\n");
+ return;
+ }
+
+ target = G_PickTarget(other->pathtarget);
+
+ if (!target)
+ {
+ gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
+ return;
+ }
+
+ self->movetarget->target_ent = target;
+ train_resume(self->movetarget);
+}
+
+void
+trigger_elevator_init(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("trigger_elevator has no target\n");
+ return;
+ }
+
+ self->movetarget = G_PickTarget(self->target);
+
+ if (!self->movetarget)
+ {
+ gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
+ return;
+ }
+
+ if (strcmp(self->movetarget->classname, "func_train") != 0)
+ {
+ gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
+ return;
+ }
+
+ self->use = trigger_elevator_use;
+ self->svflags = SVF_NOCLIENT;
+}
+
+void
+SP_trigger_elevator(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = trigger_elevator_init;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
+ *
+ * "wait" base time between triggering all targets, default is 1
+ * "random" wait variance, default is 0
+ *
+ * so, the basic time between firing is a random time
+ * between (wait - random) and (wait + random)
+ *
+ * "delay" delay before first firing when turned on, default is 0
+ * "pausetime" additional delay used only the very first time
+ * and only if spawned with START_ON
+ *
+ * These can used but not touched.
+ */
+void
+func_timer_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_UseTargets(self, self->activator);
+ self->nextthink = level.time + self->wait + crandom() * self->random;
+}
+
+void
+func_timer_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ /* if on, turn it off */
+ if (self->nextthink)
+ {
+ self->nextthink = 0;
+ return;
+ }
+
+ /* turn it on */
+ if (self->delay)
+ {
+ self->nextthink = level.time + self->delay;
+ }
+ else
+ {
+ func_timer_think(self);
+ }
+}
+
+void
+SP_func_timer(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->wait)
+ {
+ self->wait = 1.0;
+ }
+
+ self->use = func_timer_use;
+ self->think = func_timer_think;
+
+ if (self->random >= self->wait)
+ {
+ self->random = self->wait - FRAMETIME;
+ gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
+ }
+
+ if (self->spawnflags & 1)
+ {
+ self->nextthink = level.time + 1.0 + st.pausetime + self->delay +
+ self->wait + crandom() * self->random;
+ self->activator = self;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
+ *
+ * Conveyors are stationary brushes that move what's on them.
+ * The brush should be have a surface with at least one current
+ * content enabled.
+ *
+ * speed default 100
+ */
+void
+func_conveyor_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 1)
+ {
+ self->speed = 0;
+ self->spawnflags &= ~1;
+ }
+ else
+ {
+ self->speed = self->count;
+ self->spawnflags |= 1;
+ }
+
+ if (!(self->spawnflags & 2))
+ {
+ self->count = 0;
+ }
+}
+
+void
+SP_func_conveyor(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 100;
+ }
+
+ if (!(self->spawnflags & 1))
+ {
+ self->count = self->speed;
+ self->speed = 0;
+ }
+
+ self->use = func_conveyor_use;
+
+ gi.setmodel(self, self->model);
+ self->solid = SOLID_BSP;
+ gi.linkentity(self);
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
+ * A secret door. Slide back and then to the side.
+ *
+ * open_once doors never closes
+ * 1st_left 1st move is left of arrow
+ * 1st_down 1st move is down from arrow
+ * always_shoot door is shootebale even if targeted
+ *
+ * "angle" determines the direction
+ * "dmg" damage to inflic when blocked (default 2)
+ * "wait" how long to hold in the open position (default 5, -1 means hold)
+ */
+
+#define SECRET_ALWAYS_SHOOT 1
+#define SECRET_1ST_LEFT 2
+#define SECRET_1ST_DOWN 4
+
+void door_secret_move1(edict_t *self);
+void door_secret_move2(edict_t *self);
+void door_secret_move3(edict_t *self);
+void door_secret_move4(edict_t *self);
+void door_secret_move5(edict_t *self);
+void door_secret_move6(edict_t *self);
+void door_secret_done(edict_t *self);
+
+void
+door_secret_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* make sure we're not already moving */
+ if (!VectorCompare(self->s.origin, vec3_origin))
+ {
+ return;
+ }
+
+ Move_Calc(self, self->pos1, door_secret_move1);
+ door_use_areaportals(self, true);
+}
+
+void
+door_secret_move1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + 1.0;
+ self->think = door_secret_move2;
+}
+
+void
+door_secret_move2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, self->pos2, door_secret_move3);
+}
+
+void
+door_secret_move3(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->wait == -1)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + self->wait;
+ self->think = door_secret_move4;
+}
+
+void
+door_secret_move4(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, self->pos1, door_secret_move5);
+}
+
+void
+door_secret_move5(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + 1.0;
+ self->think = door_secret_move6;
+}
+
+void
+door_secret_move6(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ Move_Calc(self, vec3_origin, door_secret_done);
+}
+
+void
+door_secret_done(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
+ {
+ self->health = 0;
+ self->takedamage = DAMAGE_YES;
+ }
+
+ door_use_areaportals(self, false);
+}
+
+void
+door_secret_blocked(edict_t *self, edict_t *other)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER) && (!other->client))
+ {
+ /* give it a chance to go away on it's own terms (like gibs) */
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_CRUSH);
+
+ /* if it's still there, nuke it */
+ if (other->inuse)
+ {
+ /* Hack for entities without their origin near the model */
+ VectorMA (other->absmin, 0.5, other->size, other->s.origin);
+ BecomeExplosion1(other);
+ }
+
+ return;
+ }
+
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 0.5;
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+door_secret_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+ door_secret_use(self, attacker, attacker);
+}
+
+void
+SP_func_door_secret(edict_t *ent)
+{
+ vec3_t forward, right, up;
+ float side;
+ float width;
+ float length;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav");
+ ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav");
+ ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav");
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ gi.setmodel(ent, ent->model);
+
+ ent->blocked = door_secret_blocked;
+ ent->use = door_secret_use;
+
+ if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
+ {
+ ent->health = 0;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_secret_die;
+ }
+
+ if (!ent->dmg)
+ {
+ ent->dmg = 2;
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 5;
+ }
+
+ ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = 50;
+
+ /* calculate positions */
+ AngleVectors(ent->s.angles, forward, right, up);
+ VectorClear(ent->s.angles);
+ side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
+
+ if (ent->spawnflags & SECRET_1ST_DOWN)
+ {
+ width = fabs(DotProduct(up, ent->size));
+ }
+ else
+ {
+ width = fabs(DotProduct(right, ent->size));
+ }
+
+ length = fabs(DotProduct(forward, ent->size));
+
+ if (ent->spawnflags & SECRET_1ST_DOWN)
+ {
+ VectorMA(ent->s.origin, -1 * width, up, ent->pos1);
+ }
+ else
+ {
+ VectorMA(ent->s.origin, side * width, right, ent->pos1);
+ }
+
+ VectorMA(ent->pos1, length, forward, ent->pos2);
+
+ if (ent->health)
+ {
+ ent->takedamage = DAMAGE_YES;
+ ent->die = door_killed;
+ ent->max_health = ent->health;
+ }
+ else if (ent->targetname && ent->message)
+ {
+ gi.soundindex("misc/talk.wav");
+ ent->touch = door_touch;
+ }
+
+ ent->classname = "func_door";
+
+ gi.linkentity(ent);
+}
+
+/* ==================================================================== */
+
+/*
+ * QUAKED func_killbox (1 0 0) ?
+ *
+ * Kills everything inside when fired,
+ * irrespective of protection.
+ */
+void
+use_killbox(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ KillBox(self);
+
+ /* Hack to make sure that really everything is killed */
+ self->count--;
+
+ if (!self->count)
+ {
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 1;
+ }
+}
+
+void
+SP_func_killbox(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, ent->model);
+ ent->use = use_killbox;
+ ent->svflags = SVF_NOCLIENT;
+}
+
+/*
+ * QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM
+ * "health" if set, the light may be killed.
+ */
+
+#define START_OFF 1
+
+void
+rotating_light_alarm(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & START_OFF)
+ {
+ self->think = NULL;
+ self->nextthink = 0;
+ }
+ else
+ {
+ gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE,
+ self->moveinfo.sound_start, 1,
+ ATTN_STATIC, 0);
+ self->nextthink = level.time + 1;
+ }
+}
+
+void
+rotating_light_killed(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage /* unused */,
+ vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WELDING_SPARKS);
+ gi.WriteByte(30);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(vec3_origin);
+ gi.WriteByte(0xe0 + (rand() & 7));
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ self->s.effects &= ~EF_SPINNINGLIGHTS;
+ self->use = NULL;
+
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 0.1;
+}
+
+void
+rotating_light_use(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & START_OFF)
+ {
+ self->spawnflags &= ~START_OFF;
+ self->s.effects |= EF_SPINNINGLIGHTS;
+
+ if (self->spawnflags & 2)
+ {
+ self->think = rotating_light_alarm;
+ self->nextthink = level.time + 0.1;
+ }
+ }
+ else
+ {
+ self->spawnflags |= START_OFF;
+ self->s.effects &= ~EF_SPINNINGLIGHTS;
+ }
+}
+
+void
+SP_rotating_light(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_STOP;
+ self->solid = SOLID_BBOX;
+
+ self->s.modelindex = gi.modelindex("models/objects/light/tris.md2");
+
+ self->s.frame = 0;
+
+ self->use = rotating_light_use;
+
+ if (self->spawnflags & START_OFF)
+ {
+ self->s.effects &= ~EF_SPINNINGLIGHTS;
+ }
+ else
+ {
+ self->s.effects |= EF_SPINNINGLIGHTS;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 32;
+ }
+
+ if (!self->health)
+ {
+ self->health = 10;
+ self->max_health = self->health;
+ self->die = rotating_light_killed;
+ self->takedamage = DAMAGE_YES;
+ }
+ else
+ {
+ self->max_health = self->health;
+ self->die = rotating_light_killed;
+ self->takedamage = DAMAGE_YES;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->moveinfo.sound_start = gi.soundindex("misc/alarm.wav");
+ }
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED func_object_repair (1 .5 0) (-8 -8 -8) (8 8 8)
+ * object to be repaired.
+ * The default delay is 1 second
+ * "delay" the delay in seconds for spark to occur
+ */
+void
+object_repair_fx(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->nextthink = level.time + ent->delay;
+
+ if (ent->health <= 100)
+ {
+ ent->health++;
+ }
+ else
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WELDING_SPARKS);
+ gi.WriteByte(10);
+ gi.WritePosition(ent->s.origin);
+ gi.WriteDir(vec3_origin);
+ gi.WriteByte(0xe0 + (rand() & 7));
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+ }
+}
+
+void
+object_repair_dead(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ G_UseTargets(ent, ent);
+ ent->nextthink = level.time + 0.1;
+ ent->think = object_repair_fx;
+}
+
+void
+object_repair_sparks(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->health < 0)
+ {
+ ent->nextthink = level.time + 0.1;
+ ent->think = object_repair_dead;
+ return;
+ }
+
+ ent->nextthink = level.time + ent->delay;
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WELDING_SPARKS);
+ gi.WriteByte(10);
+ gi.WritePosition(ent->s.origin);
+ gi.WriteDir(vec3_origin);
+ gi.WriteByte(0xe0 + (rand() & 7));
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+}
+
+void
+SP_object_repair(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->classname = "object_repair";
+ ent->think = object_repair_sparks;
+ ent->nextthink = level.time + 1.0;
+ ent->health = 100;
+
+ if (!ent->delay)
+ {
+ ent->delay = 1.0;
+ }
+
+ gi.linkentity(ent);
+}
+
diff --git a/xatrix/src/g_items.c b/xatrix/src/g_items.c
new file mode 100644
index 0000000..1333373
--- /dev/null
+++ b/xatrix/src/g_items.c
@@ -0,0 +1,3045 @@
+/*
+ * =======================================================================
+ *
+ * Item handling and item definitions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define HEALTH_IGNORE_MAX 1
+#define HEALTH_TIMED 2
+
+qboolean Pickup_Weapon(edict_t *ent, edict_t *other);
+void Use_Weapon(edict_t *ent, gitem_t *inv);
+void Use_Weapon2(edict_t *ent, gitem_t *inv);
+void Drop_Weapon(edict_t *ent, gitem_t *inv);
+
+void Weapon_Blaster(edict_t *ent);
+void Weapon_Shotgun(edict_t *ent);
+void Weapon_SuperShotgun(edict_t *ent);
+void Weapon_Machinegun(edict_t *ent);
+void Weapon_Chaingun(edict_t *ent);
+void Weapon_HyperBlaster(edict_t *ent);
+void Weapon_RocketLauncher(edict_t *ent);
+void Weapon_Grenade(edict_t *ent);
+void Weapon_GrenadeLauncher(edict_t *ent);
+void Weapon_Railgun(edict_t *ent);
+void Weapon_BFG(edict_t *ent);
+
+void Weapon_Ionripper(edict_t *ent);
+void Weapon_Phalanx(edict_t *ent);
+void Weapon_Trap(edict_t *ent);
+
+gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET};
+gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT};
+gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY};
+
+int jacket_armor_index;
+int combat_armor_index;
+int body_armor_index;
+static int power_screen_index;
+static int power_shield_index;
+
+void Use_Quad(edict_t *ent, gitem_t *item);
+void Use_QuadFire(edict_t *ent, gitem_t *item);
+
+static int quad_drop_timeout_hack;
+static int quad_fire_drop_timeout_hack;
+
+/* ====================================================================== */
+
+gitem_t *
+GetItemByIndex(int index)
+{
+ if ((index == 0) || (index >= game.num_items))
+ {
+ return NULL;
+ }
+
+ return &itemlist[index];
+}
+
+gitem_t *
+FindItemByClassname(char *classname)
+{
+ int i;
+ gitem_t *it;
+
+ if (!classname)
+ {
+ return NULL;
+ }
+
+ it = itemlist;
+
+ for (i = 0; i < game.num_items; i++, it++)
+ {
+ if (!it->classname)
+ {
+ continue;
+ }
+
+ if (!Q_stricmp(it->classname, classname))
+ {
+ return it;
+ }
+ }
+
+ return NULL;
+}
+
+gitem_t *
+FindItem(char *pickup_name)
+{
+ int i;
+ gitem_t *it;
+
+ if (!pickup_name)
+ {
+ return NULL;
+ }
+
+ it = itemlist;
+
+ for (i = 0; i < game.num_items; i++, it++)
+ {
+ if (!it->pickup_name)
+ {
+ continue;
+ }
+
+ if (!Q_stricmp(it->pickup_name, pickup_name))
+ {
+ return it;
+ }
+ }
+
+ return NULL;
+}
+
+/* ====================================================================== */
+
+void
+DoRespawn(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->team)
+ {
+ edict_t *master;
+ int count;
+ int choice;
+
+ master = ent->teammaster;
+
+ for (count = 0, ent = master; ent; ent = ent->chain, count++)
+ {
+ }
+
+ choice = count ? randk() % count : 0;
+
+ for (count = 0, ent = master; count < choice; ent = ent->chain, count++)
+ {
+ }
+ }
+
+ ent->svflags &= ~SVF_NOCLIENT;
+ ent->solid = SOLID_TRIGGER;
+ gi.linkentity(ent);
+
+ /* send an effect */
+ ent->s.event = EV_ITEM_RESPAWN;
+}
+
+void
+SetRespawn(edict_t *ent, float delay)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->flags |= FL_RESPAWN;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+ ent->nextthink = level.time + delay;
+ ent->think = DoRespawn;
+ gi.linkentity(ent);
+}
+
+/* ====================================================================== */
+
+qboolean
+Pickup_Powerup(edict_t *ent, edict_t *other)
+{
+ int quantity;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ if (((skill->value == SKILL_MEDIUM) &&
+ (quantity >= 2)) || ((skill->value >= SKILL_HARD) && (quantity >= 1)))
+ {
+ return false;
+ }
+
+ if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ if (deathmatch->value)
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+ }
+
+ return true;
+}
+
+void
+Drop_General(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ Drop_Item(ent, item);
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+}
+
+/* ====================================================================== */
+
+qboolean
+Pickup_Adrenaline(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (!deathmatch->value)
+ {
+ other->max_health += 1;
+ }
+
+ if (other->health < other->max_health)
+ {
+ other->health = other->max_health;
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_AncientHead(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ other->max_health += 2;
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Bandolier(edict_t *ent, edict_t *other)
+{
+ gitem_t *item;
+ int index;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (other->client->pers.max_bullets < 250)
+ {
+ other->client->pers.max_bullets = 250;
+ }
+
+ if (other->client->pers.max_shells < 150)
+ {
+ other->client->pers.max_shells = 150;
+ }
+
+ if (other->client->pers.max_cells < 250)
+ {
+ other->client->pers.max_cells = 250;
+ }
+
+ if (other->client->pers.max_slugs < 75)
+ {
+ other->client->pers.max_slugs = 75;
+ }
+
+ if (other->client->pers.max_magslug < 75)
+ {
+ other->client->pers.max_magslug = 75;
+ }
+
+ item = FindItem("Bullets");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_bullets)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_bullets;
+ }
+ }
+
+ item = FindItem("Shells");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_shells)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_shells;
+ }
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Pack(edict_t *ent, edict_t *other)
+{
+ gitem_t *item;
+ int index;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (other->client->pers.max_bullets < 300)
+ {
+ other->client->pers.max_bullets = 300;
+ }
+
+ if (other->client->pers.max_shells < 200)
+ {
+ other->client->pers.max_shells = 200;
+ }
+
+ if (other->client->pers.max_rockets < 100)
+ {
+ other->client->pers.max_rockets = 100;
+ }
+
+ if (other->client->pers.max_grenades < 100)
+ {
+ other->client->pers.max_grenades = 100;
+ }
+
+ if (other->client->pers.max_cells < 300)
+ {
+ other->client->pers.max_cells = 300;
+ }
+
+ if (other->client->pers.max_slugs < 100)
+ {
+ other->client->pers.max_slugs = 100;
+ }
+
+ if (other->client->pers.max_magslug < 100)
+ {
+ other->client->pers.max_magslug = 100;
+ }
+
+ item = FindItem("Bullets");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_bullets)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_bullets;
+ }
+ }
+
+ item = FindItem("Shells");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_shells)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_shells;
+ }
+ }
+
+ item = FindItem("Cells");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_cells)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_cells;
+ }
+ }
+
+ item = FindItem("Grenades");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_grenades)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_grenades;
+ }
+ }
+
+ item = FindItem("Rockets");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_rockets)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_rockets;
+ }
+ }
+
+ item = FindItem("Slugs");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_slugs)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_slugs;
+ }
+ }
+
+ item = FindItem("Mag Slug");
+
+ if (item)
+ {
+ index = ITEM_INDEX(item);
+ other->client->pers.inventory[index] += item->quantity;
+
+ if (other->client->pers.inventory[index] >
+ other->client->pers.max_magslug)
+ {
+ other->client->pers.inventory[index] =
+ other->client->pers.max_magslug;
+ }
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ return true;
+}
+
+/* ====================================================================== */
+
+void
+Use_Quad(edict_t *ent, gitem_t *item)
+{
+ int timeout;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (quad_drop_timeout_hack)
+ {
+ timeout = quad_drop_timeout_hack;
+ quad_drop_timeout_hack = 0;
+ }
+ else
+ {
+ timeout = 300;
+ }
+
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ ent->client->quad_framenum += timeout;
+ }
+ else
+ {
+ ent->client->quad_framenum = level.framenum + timeout;
+ }
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+/* ===================================================================== */
+
+void
+Use_QuadFire(edict_t *ent, gitem_t *item)
+{
+ int timeout;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (quad_fire_drop_timeout_hack)
+ {
+ timeout = quad_fire_drop_timeout_hack;
+ quad_fire_drop_timeout_hack = 0;
+ }
+ else
+ {
+ timeout = 300;
+ }
+
+ if (ent->client->quadfire_framenum > level.framenum)
+ {
+ ent->client->quadfire_framenum += timeout;
+ }
+ else
+ {
+ ent->client->quadfire_framenum = level.framenum + timeout;
+ }
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire1.wav"), 1, ATTN_NORM, 0);
+}
+
+/* ====================================================================== */
+
+void
+Use_Breather(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->breather_framenum > level.framenum)
+ {
+ ent->client->breather_framenum += 300;
+ }
+ else
+ {
+ ent->client->breather_framenum = level.framenum + 300;
+ }
+}
+
+/* ====================================================================== */
+
+void
+Use_Envirosuit(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->enviro_framenum > level.framenum)
+ {
+ ent->client->enviro_framenum += 300;
+ }
+ else
+ {
+ ent->client->enviro_framenum = level.framenum + 300;
+ }
+}
+
+/* ====================================================================== */
+
+void
+Use_Invulnerability(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+
+ if (ent->client->invincible_framenum > level.framenum)
+ {
+ ent->client->invincible_framenum += 300;
+ }
+ else
+ {
+ ent->client->invincible_framenum = level.framenum + 300;
+ }
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0);
+}
+
+/* ====================================================================== */
+
+void
+Use_Silencer(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ ent->client->pers.inventory[ITEM_INDEX(item)]--;
+ ValidateSelectedItem(ent);
+ ent->client->silencer_shots += 30;
+}
+
+/* ====================================================================== */
+
+qboolean
+Pickup_Key(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (coop->value)
+ {
+ if (strcmp(ent->classname, "key_power_cube") == 0)
+ {
+ if (other->client->pers.power_cubes &
+ ((ent->spawnflags & 0x0000ff00) >> 8))
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+ other->client->pers.power_cubes |=
+ ((ent->spawnflags & 0x0000ff00) >> 8);
+ }
+ else
+ {
+ if (other->client->pers.inventory[ITEM_INDEX(ent->item)])
+ {
+ return false;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1;
+ }
+
+ return true;
+ }
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+ return true;
+}
+
+/* ====================================================================== */
+
+qboolean
+Add_Ammo(edict_t *ent, gitem_t *item, int count)
+{
+ int index;
+ int max;
+
+ if (!ent || !item)
+ {
+ return false;
+ }
+
+ if (!ent->client)
+ {
+ return false;
+ }
+
+ if (item->tag == AMMO_BULLETS)
+ {
+ max = ent->client->pers.max_bullets;
+ }
+ else if (item->tag == AMMO_SHELLS)
+ {
+ max = ent->client->pers.max_shells;
+ }
+ else if (item->tag == AMMO_ROCKETS)
+ {
+ max = ent->client->pers.max_rockets;
+ }
+ else if (item->tag == AMMO_GRENADES)
+ {
+ max = ent->client->pers.max_grenades;
+ }
+ else if (item->tag == AMMO_CELLS)
+ {
+ max = ent->client->pers.max_cells;
+ }
+ else if (item->tag == AMMO_SLUGS)
+ {
+ max = ent->client->pers.max_slugs;
+ }
+ else if (item->tag == AMMO_MAGSLUG)
+ {
+ max = ent->client->pers.max_magslug;
+ }
+ else if (item->tag == AMMO_TRAP)
+ {
+ max = ent->client->pers.max_trap;
+ }
+ else
+ {
+ return false;
+ }
+
+ index = ITEM_INDEX(item);
+
+ if (ent->client->pers.inventory[index] == max)
+ {
+ return false;
+ }
+
+ ent->client->pers.inventory[index] += count;
+
+ if (ent->client->pers.inventory[index] > max)
+ {
+ ent->client->pers.inventory[index] = max;
+ }
+
+ return true;
+}
+
+qboolean
+Pickup_Ammo(edict_t *ent, edict_t *other)
+{
+ int oldcount;
+ int count;
+ qboolean weapon;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ weapon = (ent->item->flags & IT_WEAPON);
+
+ if ((weapon) && ((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ count = 1000;
+ }
+ else if (ent->count)
+ {
+ count = ent->count;
+ }
+ else
+ {
+ count = ent->item->quantity;
+ }
+
+ oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ if (!Add_Ammo(other, ent->item, count))
+ {
+ return false;
+ }
+
+ if (weapon && !oldcount)
+ {
+ if ((other->client->pers.weapon != ent->item) &&
+ (!deathmatch->value ||
+ (other->client->pers.weapon == FindItem("blaster"))))
+ {
+ other->client->newweapon = ent->item;
+ }
+ }
+
+ if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) &&
+ (deathmatch->value))
+ {
+ SetRespawn(ent, 30);
+ }
+
+ return true;
+}
+
+void
+Drop_Ammo(edict_t *ent, gitem_t *item)
+{
+ edict_t *dropped;
+ int index;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(item);
+ dropped = Drop_Item(ent, item);
+
+ if (ent->client->pers.inventory[index] >= item->quantity)
+ {
+ dropped->count = item->quantity;
+ }
+ else
+ {
+ dropped->count = ent->client->pers.inventory[index];
+ }
+
+ if (ent->client->pers.weapon &&
+ (ent->client->pers.weapon->tag == AMMO_GRENADES) &&
+ (item->tag == AMMO_GRENADES) &&
+ (ent->client->pers.inventory[index] - dropped->count <= 0))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Can't drop current weapon\n");
+ G_FreeEdict(dropped);
+ return;
+ }
+
+ ent->client->pers.inventory[index] -= dropped->count;
+ ValidateSelectedItem(ent);
+}
+
+/* ====================================================================== */
+
+void
+MegaHealth_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->owner->health > self->owner->max_health)
+ {
+ self->nextthink = level.time + 1;
+ self->owner->health -= 1;
+ return;
+ }
+
+ if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(self, 20);
+ }
+ else
+ {
+ G_FreeEdict(self);
+ }
+}
+
+qboolean
+Pickup_Health(edict_t *ent, edict_t *other)
+{
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ if (!(ent->style & HEALTH_IGNORE_MAX))
+ {
+ if (other->health >= other->max_health)
+ {
+ return false;
+ }
+ }
+
+ other->health += ent->count;
+
+ if (!(ent->style & HEALTH_IGNORE_MAX))
+ {
+ if (other->health > other->max_health)
+ {
+ other->health = other->max_health;
+ }
+ }
+
+ if (ent->style & HEALTH_TIMED)
+ {
+ ent->think = MegaHealth_think;
+ ent->nextthink = level.time + 5;
+ ent->owner = other;
+ ent->flags |= FL_RESPAWN;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+ }
+ else
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, 30);
+ }
+ }
+
+ return true;
+}
+
+/* ====================================================================== */
+
+int
+ArmorIndex(edict_t *ent)
+{
+ if (!ent)
+ {
+ return 0;
+ }
+
+ if (!ent->client)
+ {
+ return 0;
+ }
+
+ if (ent->client->pers.inventory[jacket_armor_index] > 0)
+ {
+ return jacket_armor_index;
+ }
+
+ if (ent->client->pers.inventory[combat_armor_index] > 0)
+ {
+ return combat_armor_index;
+ }
+
+ if (ent->client->pers.inventory[body_armor_index] > 0)
+ {
+ return body_armor_index;
+ }
+
+ return 0;
+}
+
+qboolean
+Pickup_Armor(edict_t *ent, edict_t *other)
+{
+ int old_armor_index;
+ gitem_armor_t *oldinfo;
+ gitem_armor_t *newinfo;
+ int newcount;
+ float salvage;
+ int salvagecount;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ /* get info on new armor */
+ newinfo = (gitem_armor_t *)ent->item->info;
+
+ old_armor_index = ArmorIndex(other);
+
+ /* handle armor shards specially */
+ if (ent->item->tag == ARMOR_SHARD)
+ {
+ if (!old_armor_index)
+ {
+ other->client->pers.inventory[jacket_armor_index] = 2;
+ }
+ else
+ {
+ other->client->pers.inventory[old_armor_index] += 2;
+ }
+ }
+
+ /* if player has no armor, just use it */
+ else if (!old_armor_index)
+ {
+ other->client->pers.inventory[ITEM_INDEX(ent->item)] =
+ newinfo->base_count;
+ }
+
+ /* use the better armor */
+ else
+ {
+ /* get info on old armor */
+ if (old_armor_index == jacket_armor_index)
+ {
+ oldinfo = &jacketarmor_info;
+ }
+ else if (old_armor_index == combat_armor_index)
+ {
+ oldinfo = &combatarmor_info;
+ }
+ else /* (old_armor_index == body_armor_index) */
+ {
+ oldinfo = &bodyarmor_info;
+ }
+
+ if (newinfo->normal_protection > oldinfo->normal_protection)
+ {
+ /* calc new armor values */
+ salvage = oldinfo->normal_protection / newinfo->normal_protection;
+ salvagecount = salvage * other->client->pers.inventory[old_armor_index];
+ newcount = newinfo->base_count + salvagecount;
+
+ if (newcount > newinfo->max_count)
+ {
+ newcount = newinfo->max_count;
+ }
+
+ /* zero count of old armor so it goes away */
+ other->client->pers.inventory[old_armor_index] = 0;
+
+ /* change armor to new item with computed value */
+ other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount;
+ }
+ else
+ {
+ /* calc new armor values */
+ salvage = newinfo->normal_protection / oldinfo->normal_protection;
+ salvagecount = salvage * newinfo->base_count;
+ newcount = other->client->pers.inventory[old_armor_index] +
+ salvagecount;
+
+ if (newcount > oldinfo->max_count)
+ {
+ newcount = oldinfo->max_count;
+ }
+
+ /* if we're already maxed out then we don't need the new armor */
+ if (other->client->pers.inventory[old_armor_index] >= newcount)
+ {
+ return false;
+ }
+
+ /* update current armor value */
+ other->client->pers.inventory[old_armor_index] = newcount;
+ }
+ }
+
+ if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+ {
+ SetRespawn(ent, 20);
+ }
+
+ return true;
+}
+
+/* ====================================================================== */
+
+int
+PowerArmorType(edict_t *ent)
+{
+ if (!ent)
+ {
+ return POWER_ARMOR_NONE;
+ }
+
+ if (!ent->client)
+ {
+ return POWER_ARMOR_NONE;
+ }
+
+ if (!(ent->flags & FL_POWER_ARMOR))
+ {
+ return POWER_ARMOR_NONE;
+ }
+
+ if (ent->client->pers.inventory[power_shield_index] > 0)
+ {
+ return POWER_ARMOR_SHIELD;
+ }
+
+ if (ent->client->pers.inventory[power_screen_index] > 0)
+ {
+ return POWER_ARMOR_SCREEN;
+ }
+
+ return POWER_ARMOR_NONE;
+}
+
+void
+Use_PowerArmor(edict_t *ent, gitem_t *item)
+{
+ int index;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if (ent->flags & FL_POWER_ARMOR)
+ {
+ ent->flags &= ~FL_POWER_ARMOR;
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ index = ITEM_INDEX(FindItem("cells"));
+
+ if (!ent->client->pers.inventory[index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No cells for power armor.\n");
+ return;
+ }
+
+ ent->flags |= FL_POWER_ARMOR;
+ gi.sound(ent, CHAN_AUTO, gi.soundindex(
+ "misc/power1.wav"), 1, ATTN_NORM, 0);
+ }
+}
+
+qboolean
+Pickup_PowerArmor(edict_t *ent, edict_t *other)
+{
+ int quantity;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+ other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+ if (deathmatch->value)
+ {
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ SetRespawn(ent, ent->item->quantity);
+ }
+
+ /* auto-use for DM only if we didn't already have one */
+ if (!quantity)
+ {
+ ent->item->use(other, ent->item);
+ }
+ }
+
+ return true;
+}
+
+void
+Drop_PowerArmor(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if ((ent->flags & FL_POWER_ARMOR) &&
+ (ent->client->pers.inventory[ITEM_INDEX(item)] == 1))
+ {
+ Use_PowerArmor(ent, item);
+ }
+
+ Drop_General(ent, item);
+}
+
+/* ====================================================================== */
+
+void
+Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ qboolean taken;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ if (other->health < 1)
+ {
+ return; /* dead people can't pickup */
+ }
+
+ if (!ent->item->pickup)
+ {
+ return; /* not a grabbable item? */
+ }
+
+ taken = ent->item->pickup(ent, other);
+
+ if (taken)
+ {
+ /* flash the screen */
+ other->client->bonus_alpha = 0.25;
+
+ /* show icon and name on status bar */
+ other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon);
+ other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS + ITEM_INDEX(ent->item);
+ other->client->pickup_msg_time = level.time + 3.0;
+
+ /* change selected item */
+ if (ent->item->use)
+ {
+ other->client->pers.selected_item =
+ other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(
+ ent->item);
+ }
+
+ if (ent->item->pickup == Pickup_Health)
+ {
+ if (ent->count == 2)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex(
+ "items/s_health.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->count == 10)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex(
+ "items/n_health.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (ent->count == 25)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex(
+ "items/l_health.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex(
+ "items/m_health.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+ else if (ent->item->pickup_sound)
+ {
+ gi.sound(other, CHAN_ITEM, gi.soundindex(
+ ent->item->pickup_sound), 1, ATTN_NORM, 0);
+ }
+
+ /* activate item instantly if appropriate */
+ /* moved down here so activation sounds override the pickup sound */
+ if (deathmatch->value)
+ {
+ if ((((int)dmflags->value & DF_INSTANT_ITEMS) &&
+ (ent->item->flags & IT_INSTANT_USE)) ||
+ (((ent->item->use == Use_Quad) || (ent->item->use == Use_QuadFire)) &&
+ (ent->spawnflags & DROPPED_PLAYER_ITEM)))
+ {
+ if (ent->spawnflags & DROPPED_PLAYER_ITEM)
+ {
+ if (ent->item->use == Use_Quad)
+ {
+ quad_drop_timeout_hack =
+ (ent->nextthink - level.time) / FRAMETIME;
+ }
+ else if (ent->item->use == Use_QuadFire)
+ {
+ quad_fire_drop_timeout_hack =
+ (ent->nextthink - level.time) / FRAMETIME;
+ }
+ }
+
+ if (ent->item->use)
+ {
+ ent->item->use(other, ent->item);
+ }
+ }
+ }
+ }
+
+ if (!(ent->spawnflags & ITEM_TARGETS_USED))
+ {
+ G_UseTargets(ent, other);
+ ent->spawnflags |= ITEM_TARGETS_USED;
+ }
+
+ if (!taken)
+ {
+ return;
+ }
+
+ if (!((coop->value) &&
+ (ent->item->flags & IT_STAY_COOP)) ||
+ (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
+ {
+ if (ent->flags & FL_RESPAWN)
+ {
+ ent->flags &= ~FL_RESPAWN;
+ }
+ else
+ {
+ G_FreeEdict(ent);
+ }
+ }
+}
+
+/* ====================================================================== */
+
+void
+drop_temp_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (other == ent->owner)
+ {
+ return;
+ }
+
+ /* plane and surf are unused in Touch_Item
+ but since the function is part of the
+ game <-> client interface dropping
+ them is too much pain. */
+ Touch_Item(ent, other, plane, surf);
+}
+
+void
+drop_make_touchable(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->touch = Touch_Item;
+
+ if (deathmatch->value)
+ {
+ ent->nextthink = level.time + 29;
+ ent->think = G_FreeEdict;
+ }
+}
+
+edict_t *
+Drop_Item(edict_t *ent, gitem_t *item)
+{
+ edict_t *dropped;
+ vec3_t forward, right;
+ vec3_t offset;
+
+ if (!ent || !item)
+ {
+ return NULL;
+ }
+
+ dropped = G_Spawn();
+
+ dropped->classname = item->classname;
+ dropped->item = item;
+ dropped->spawnflags = DROPPED_ITEM;
+ dropped->s.effects = item->world_model_flags;
+ dropped->s.renderfx = RF_GLOW;
+
+ if (random() > 0.5)
+ {
+ dropped->s.angles[1] += random()*45;
+ }
+ else
+ {
+ dropped->s.angles[1] -= random()*45;
+ }
+
+ VectorSet (dropped->mins, -16, -16, -16);
+ VectorSet (dropped->maxs, 16, 16, 16);
+ gi.setmodel(dropped, dropped->item->world_model);
+ dropped->solid = SOLID_TRIGGER;
+ dropped->movetype = MOVETYPE_TOSS;
+ dropped->touch = drop_temp_touch;
+ dropped->owner = ent;
+
+ if (ent->client)
+ {
+ trace_t trace;
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ VectorSet(offset, 24, 0, -16);
+ G_ProjectSource(ent->s.origin, offset, forward, right,
+ dropped->s.origin);
+ trace = gi.trace(ent->s.origin, dropped->mins, dropped->maxs,
+ dropped->s.origin, ent, CONTENTS_SOLID);
+ VectorCopy(trace.endpos, dropped->s.origin);
+ }
+ else
+ {
+ AngleVectors(ent->s.angles, forward, right, NULL);
+ VectorCopy(ent->s.origin, dropped->s.origin);
+ }
+
+ VectorScale(forward, 100, dropped->velocity);
+ dropped->velocity[2] = 300;
+
+ dropped->think = drop_make_touchable;
+ dropped->nextthink = level.time + 1;
+
+ gi.linkentity(dropped);
+
+ return dropped;
+}
+
+void
+Use_Item(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->svflags &= ~SVF_NOCLIENT;
+ ent->use = NULL;
+
+ if (ent->spawnflags & ITEM_NO_TOUCH)
+ {
+ ent->solid = SOLID_BBOX;
+ ent->touch = NULL;
+ }
+ else
+ {
+ ent->solid = SOLID_TRIGGER;
+ ent->touch = Touch_Item;
+ }
+
+ gi.linkentity(ent);
+}
+
+/* ====================================================================== */
+
+void
+droptofloor(edict_t *ent)
+{
+ trace_t tr;
+ vec3_t dest;
+ float *v;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ v = tv(-15, -15, -15);
+ VectorCopy(v, ent->mins);
+ v = tv(15, 15, 15);
+ VectorCopy(v, ent->maxs);
+
+ if (ent->model)
+ {
+ gi.setmodel(ent, ent->model);
+ }
+ else
+ {
+ gi.setmodel(ent, ent->item->world_model);
+ }
+
+ ent->solid = SOLID_TRIGGER;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->touch = Touch_Item;
+
+ v = tv(0, 0, -128);
+ VectorAdd(ent->s.origin, v, dest);
+
+ tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
+
+ if (tr.startsolid)
+ {
+ if (strcmp(ent->classname, "foodcube") == 0)
+ {
+ VectorCopy(ent->s.origin, tr.endpos);
+ ent->velocity[2] = 0;
+ }
+ else
+ {
+ gi.dprintf("droptofloor: %s startsolid at %s\n",
+ ent->classname, vtos(ent->s.origin));
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ VectorCopy(tr.endpos, ent->s.origin);
+
+ if (ent->team)
+ {
+ ent->flags &= ~FL_TEAMSLAVE;
+ ent->chain = ent->teamchain;
+ ent->teamchain = NULL;
+
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+
+ if (ent == ent->teammaster)
+ {
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = DoRespawn;
+ }
+ }
+
+ if (ent->spawnflags & ITEM_NO_TOUCH)
+ {
+ ent->solid = SOLID_BBOX;
+ ent->touch = NULL;
+ ent->s.effects &= ~EF_ROTATE;
+ ent->s.renderfx &= ~RF_GLOW;
+ }
+
+ if (ent->spawnflags & ITEM_TRIGGER_SPAWN)
+ {
+ ent->svflags |= SVF_NOCLIENT;
+ ent->solid = SOLID_NOT;
+ ent->use = Use_Item;
+ }
+
+ gi.linkentity(ent);
+}
+
+/*
+ * Precaches all data needed for a given item.
+ * This will be called for each item spawned in a level,
+ * and for each item in each client's inventory.
+ */
+void
+PrecacheItem(gitem_t *it)
+{
+ char *s, *start;
+ char data[MAX_QPATH];
+ int len;
+ gitem_t *ammo;
+
+ if (!it)
+ {
+ return;
+ }
+
+ if (it->pickup_sound)
+ {
+ gi.soundindex(it->pickup_sound);
+ }
+
+ if (it->world_model)
+ {
+ gi.modelindex(it->world_model);
+ }
+
+ if (it->view_model)
+ {
+ gi.modelindex(it->view_model);
+ }
+
+ if (it->icon)
+ {
+ gi.imageindex(it->icon);
+ }
+
+ /* parse everything for its ammo */
+ if (it->ammo && it->ammo[0])
+ {
+ ammo = FindItem(it->ammo);
+
+ if (ammo != it)
+ {
+ PrecacheItem(ammo);
+ }
+ }
+
+ /* parse the space seperated precache string for other items */
+ s = it->precaches;
+
+ if (!s || !s[0])
+ {
+ return;
+ }
+
+ while (*s)
+ {
+ start = s;
+
+ while (*s && *s != ' ')
+ {
+ s++;
+ }
+
+ len = s - start;
+
+ if ((len >= MAX_QPATH) || (len < 5))
+ {
+ gi.error("PrecacheItem: %s has bad precache string", it->classname);
+ }
+
+ memcpy(data, start, len);
+ data[len] = 0;
+
+ if (*s)
+ {
+ s++;
+ }
+
+ /* determine type based on extension */
+ if (!strcmp(data + len - 3, "md2"))
+ {
+ gi.modelindex(data);
+ }
+ else if (!strcmp(data + len - 3, "sp2"))
+ {
+ gi.modelindex(data);
+ }
+ else if (!strcmp(data + len - 3, "wav"))
+ {
+ gi.soundindex(data);
+ }
+
+ if (!strcmp(data + len - 3, "pcx"))
+ {
+ gi.imageindex(data);
+ }
+ }
+}
+
+/*
+ * Sets the clipping size and plants the object on the floor.
+ * Items can't be immediately dropped to floor, because they might
+ * be on an entity that hasn't spawned yet.
+ */
+void
+SpawnItem(edict_t *ent, gitem_t *item)
+{
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ PrecacheItem(item);
+
+ if (ent->spawnflags)
+ {
+ if (strcmp(ent->classname, "key_power_cube") != 0)
+ {
+ ent->spawnflags = 0;
+ gi.dprintf("%s at %s has invalid spawnflags set\n",
+ ent->classname, vtos(ent->s.origin));
+ }
+ }
+
+ /* some items will be prevented in deathmatch */
+ if (deathmatch->value)
+ {
+ if ((int)dmflags->value & DF_NO_ARMOR)
+ {
+ if ((item->pickup == Pickup_Armor) ||
+ (item->pickup == Pickup_PowerArmor))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_NO_ITEMS)
+ {
+ if (item->pickup == Pickup_Powerup)
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_NO_HEALTH)
+ {
+ if ((item->pickup == Pickup_Health) ||
+ (item->pickup == Pickup_Adrenaline) ||
+ (item->pickup == Pickup_AncientHead))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+
+ if ((int)dmflags->value & DF_INFINITE_AMMO)
+ {
+ if ((item->flags == IT_AMMO) ||
+ (strcmp(ent->classname, "weapon_bfg") == 0))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+ }
+ }
+
+ if (coop->value && !(ent->spawnflags & ITEM_NO_TOUCH) && (strcmp(ent->classname, "key_power_cube") == 0))
+ {
+ ent->spawnflags |= (1 << (8 + level.power_cubes));
+ level.power_cubes++;
+ }
+
+ /* don't let them drop items that stay in a coop game */
+ if ((coop->value) && (item->flags & IT_STAY_COOP))
+ {
+ item->drop = NULL;
+ }
+
+ ent->item = item;
+ ent->nextthink = level.time + 2 * FRAMETIME; /* items start after other solids */
+ ent->think = droptofloor;
+ ent->s.effects = item->world_model_flags;
+ ent->s.renderfx = RF_GLOW;
+
+ if (ent->model)
+ {
+ gi.modelindex(ent->model);
+ }
+}
+
+/* ====================================================================== */
+
+gitem_t itemlist[] = {
+ {
+ NULL
+ },
+
+
+ /*
+ * QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_armor_body",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar1_pkup.wav",
+ "models/items/armor/body/tris.md2", EF_ROTATE,
+ NULL,
+ "i_bodyarmor",
+ "Body Armor",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ &bodyarmor_info,
+ ARMOR_BODY,
+ ""
+ },
+
+ /*
+ * QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_armor_combat",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar1_pkup.wav",
+ "models/items/armor/combat/tris.md2", EF_ROTATE,
+ NULL,
+ "i_combatarmor",
+ "Combat Armor",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ &combatarmor_info,
+ ARMOR_COMBAT,
+ ""
+ },
+
+ /*
+ * QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_armor_jacket",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar1_pkup.wav",
+ "models/items/armor/jacket/tris.md2", EF_ROTATE,
+ NULL,
+ "i_jacketarmor",
+ "Jacket Armor",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ &jacketarmor_info,
+ ARMOR_JACKET,
+ ""
+ },
+
+ /*
+ * QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_armor_shard",
+ Pickup_Armor,
+ NULL,
+ NULL,
+ NULL,
+ "misc/ar2_pkup.wav",
+ "models/items/armor/shard/tris.md2", EF_ROTATE,
+ NULL,
+ "i_jacketarmor",
+ "Armor Shard",
+ 3,
+ 0,
+ NULL,
+ IT_ARMOR,
+ 0,
+ NULL,
+ ARMOR_SHARD,
+ ""
+ },
+
+ /*
+ * QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_power_screen",
+ Pickup_PowerArmor,
+ Use_PowerArmor,
+ Drop_PowerArmor,
+ NULL,
+ "misc/ar3_pkup.wav",
+ "models/items/armor/screen/tris.md2", EF_ROTATE,
+ NULL,
+ "i_powerscreen",
+ "Power Screen",
+ 0,
+ 60,
+ NULL,
+ IT_ARMOR,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_power_shield",
+ Pickup_PowerArmor,
+ Use_PowerArmor,
+ Drop_PowerArmor,
+ NULL,
+ "misc/ar3_pkup.wav",
+ "models/items/armor/shield/tris.md2", EF_ROTATE,
+ NULL,
+ "i_powershield",
+ "Power Shield",
+ 0,
+ 60,
+ NULL,
+ IT_ARMOR,
+ 0,
+ NULL,
+ 0,
+ "misc/power2.wav misc/power1.wav"
+ },
+
+ /*
+ * weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+ * always owned, never in the world
+ */
+ {
+ "weapon_blaster",
+ NULL,
+ Use_Weapon,
+ NULL,
+ Weapon_Blaster,
+ "misc/w_pkup.wav",
+ NULL, 0,
+ "models/weapons/v_blast/tris.md2",
+ "w_blaster",
+ "Blaster",
+ 0,
+ 0,
+ NULL,
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_BLASTER,
+ NULL,
+ 0,
+ "weapons/blastf1a.wav misc/lasfly.wav"
+ },
+
+ /*
+ * QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_shotgun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Shotgun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_shotg/tris.md2", EF_ROTATE,
+ "models/weapons/v_shotg/tris.md2",
+ "w_shotgun",
+ "Shotgun",
+ 0,
+ 1,
+ "Shells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_SHOTGUN,
+ NULL,
+ 0,
+ "weapons/shotgf1b.wav weapons/shotgr1b.wav"
+ },
+
+ /*
+ * QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_supershotgun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_SuperShotgun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_shotg2/tris.md2", EF_ROTATE,
+ "models/weapons/v_shotg2/tris.md2",
+ "w_sshotgun",
+ "Super Shotgun",
+ 0,
+ 2,
+ "Shells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_SUPERSHOTGUN,
+ NULL,
+ 0,
+ "weapons/sshotf1b.wav"
+ },
+
+ /*
+ * QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_machinegun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Machinegun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_machn/tris.md2", EF_ROTATE,
+ "models/weapons/v_machn/tris.md2",
+ "w_machinegun",
+ "Machinegun",
+ 0,
+ 1,
+ "Bullets",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_MACHINEGUN,
+ NULL,
+ 0,
+
+ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"
+ },
+
+ /*
+ * QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_chaingun",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Chaingun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_chain/tris.md2", EF_ROTATE,
+ "models/weapons/v_chain/tris.md2",
+ "w_chaingun",
+ "Chaingun",
+ 0,
+ 1,
+ "Bullets",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_CHAINGUN,
+ NULL,
+ 0,
+
+ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"
+ },
+
+ /*
+ * QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_grenades",
+ Pickup_Ammo,
+ Use_Weapon,
+ Drop_Ammo,
+ Weapon_Grenade,
+ "misc/am_pkup.wav",
+ "models/items/ammo/grenades/medium/tris.md2", 0,
+ "models/weapons/v_handgr/tris.md2",
+ "a_grenades",
+ "Grenades",
+ 3,
+ 5,
+ "grenades",
+ IT_AMMO | IT_WEAPON,
+ WEAP_GRENADES,
+ NULL,
+ AMMO_GRENADES,
+
+ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "
+ },
+
+ /*
+ * QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_trap",
+ Pickup_Ammo,
+ Use_Weapon,
+ Drop_Ammo,
+ Weapon_Trap,
+ "misc/am_pkup.wav",
+ "models/weapons/g_trap/tris.md2", EF_ROTATE,
+ "models/weapons/v_trap/tris.md2",
+ "a_trap",
+ "Trap",
+ 3,
+ 1,
+ "trap",
+ IT_AMMO | IT_WEAPON,
+ 0,
+ NULL,
+ AMMO_TRAP,
+
+ "weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav"
+ },
+
+ /*
+ * QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_grenadelauncher",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_GrenadeLauncher,
+ "misc/w_pkup.wav",
+ "models/weapons/g_launch/tris.md2", EF_ROTATE,
+ "models/weapons/v_launch/tris.md2",
+ "w_glauncher",
+ "Grenade Launcher",
+ 0,
+ 1,
+ "Grenades",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_GRENADELAUNCHER,
+ NULL,
+ 0,
+
+ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"
+ },
+
+ /*
+ * QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_rocketlauncher",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_RocketLauncher,
+ "misc/w_pkup.wav",
+ "models/weapons/g_rocket/tris.md2", EF_ROTATE,
+ "models/weapons/v_rocket/tris.md2",
+ "w_rlauncher",
+ "Rocket Launcher",
+ 0,
+ 1,
+ "Rockets",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_ROCKETLAUNCHER,
+ NULL,
+ 0,
+
+ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2"
+ },
+
+ /*
+ * QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_hyperblaster",
+ Pickup_Weapon,
+ Use_Weapon2,
+ Drop_Weapon,
+ Weapon_HyperBlaster,
+ "misc/w_pkup.wav",
+ "models/weapons/g_hyperb/tris.md2", EF_ROTATE,
+ "models/weapons/v_hyperb/tris.md2",
+ "w_hyperblaster",
+ "HyperBlaster",
+ 0,
+ 1,
+ "Cells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_HYPERBLASTER,
+ NULL,
+ 0,
+
+ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"
+ },
+
+ /*
+ * QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_boomer",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Ionripper,
+ "misc/w_pkup.wav",
+ "models/weapons/g_boom/tris.md2", EF_ROTATE,
+ "models/weapons/v_boomer/tris.md2",
+ "w_ripper",
+ "Ionripper",
+ 0,
+ 2,
+ "Cells",
+ IT_WEAPON,
+ WEAP_BOOMER,
+ NULL,
+ 0,
+ "weapons/rg_hum.wav weapons/rippfire.wav"
+ },
+
+ /*
+ * QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_railgun",
+ Pickup_Weapon,
+ Use_Weapon2,
+ Drop_Weapon,
+ Weapon_Railgun,
+ "misc/w_pkup.wav",
+ "models/weapons/g_rail/tris.md2", EF_ROTATE,
+ "models/weapons/v_rail/tris.md2",
+ "w_railgun",
+ "Railgun",
+ 0,
+ 1,
+ "Slugs",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_RAILGUN,
+ NULL,
+ 0,
+ "weapons/rg_hum.wav"
+ },
+
+ /*
+ * QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+
+ {
+ "weapon_phalanx",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_Phalanx,
+ "misc/w_pkup.wav",
+ "models/weapons/g_shotx/tris.md2", EF_ROTATE,
+ "models/weapons/v_shotx/tris.md2",
+ "w_phallanx",
+ "Phalanx",
+ 0,
+ 1,
+ "Mag Slug",
+ IT_WEAPON,
+ WEAP_PHALANX,
+ NULL,
+ 0,
+ "weapons/plasshot.wav"
+ },
+
+ /*
+ * QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "weapon_bfg",
+ Pickup_Weapon,
+ Use_Weapon,
+ Drop_Weapon,
+ Weapon_BFG,
+ "misc/w_pkup.wav",
+ "models/weapons/g_bfg/tris.md2", EF_ROTATE,
+ "models/weapons/v_bfg/tris.md2",
+ "w_bfg",
+ "BFG10K",
+ 0,
+ 50,
+ "Cells",
+ IT_WEAPON | IT_STAY_COOP,
+ WEAP_BFG,
+ NULL,
+ 0,
+
+ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav"
+ },
+
+ /*
+ * QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_shells",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/shells/medium/tris.md2", 0,
+ NULL,
+ "a_shells",
+ "Shells",
+ 3,
+ 10,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_SHELLS,
+ ""
+ },
+
+ /*
+ * QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_bullets",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/bullets/medium/tris.md2", 0,
+ NULL,
+ "a_bullets",
+ "Bullets",
+ 3,
+ 50,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_BULLETS,
+ ""
+ },
+
+ /*
+ * QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_cells",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/cells/medium/tris.md2", 0,
+ NULL,
+ "a_cells",
+ "Cells",
+ 3,
+ 50,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_CELLS,
+ ""
+ },
+
+ /*
+ * QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_rockets",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/rockets/medium/tris.md2", 0,
+ NULL,
+ "a_rockets",
+ "Rockets",
+ 3,
+ 5,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_ROCKETS,
+ ""
+ },
+
+ /*
+ * QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_slugs",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/items/ammo/slugs/medium/tris.md2", 0,
+ NULL,
+ "a_slugs",
+ "Slugs",
+ 3,
+ 10,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_SLUGS,
+ ""
+ },
+
+ /*
+ * QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "ammo_magslug",
+ Pickup_Ammo,
+ NULL,
+ Drop_Ammo,
+ NULL,
+ "misc/am_pkup.wav",
+ "models/objects/ammo/tris.md2", 0,
+ NULL,
+ "a_mslugs",
+ "Mag Slug",
+ 3,
+ 10,
+ NULL,
+ IT_AMMO,
+ 0,
+ NULL,
+ AMMO_MAGSLUG,
+ ""
+ },
+
+ /*
+ * QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_quad",
+ Pickup_Powerup,
+ Use_Quad,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/quaddama/tris.md2", EF_ROTATE,
+ NULL,
+ "p_quad",
+ "Quad Damage",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/damage.wav items/damage2.wav items/damage3.wav"
+ },
+
+ /*
+ * QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_quadfire",
+ Pickup_Powerup,
+ Use_QuadFire,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/quadfire/tris.md2", EF_ROTATE,
+ NULL,
+ "p_quadfire",
+
+ "DualFire Damage",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav"
+ },
+
+ /*
+ * QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_invulnerability",
+ Pickup_Powerup,
+ Use_Invulnerability,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/invulner/tris.md2", EF_ROTATE,
+ NULL,
+ "p_invulnerability",
+ "Invulnerability",
+ 2,
+ 300,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/protect.wav items/protect2.wav items/protect4.wav"
+ },
+
+ /*
+ * QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_silencer",
+ Pickup_Powerup,
+ Use_Silencer,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/silencer/tris.md2", EF_ROTATE,
+ NULL,
+ "p_silencer",
+ "Silencer",
+ 2,
+ 60,
+ NULL,
+ IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_breather",
+ Pickup_Powerup,
+ Use_Breather,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/breather/tris.md2", EF_ROTATE,
+ NULL,
+ "p_rebreather",
+ "Rebreather",
+ 2,
+ 60,
+ NULL,
+ IT_STAY_COOP | IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/airout.wav"
+ },
+
+ /*
+ * QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_enviro",
+ Pickup_Powerup,
+ Use_Envirosuit,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/enviro/tris.md2", EF_ROTATE,
+ NULL,
+ "p_envirosuit",
+ "Environment Suit",
+ 2,
+ 60,
+ NULL,
+ IT_STAY_COOP | IT_POWERUP | IT_INSTANT_USE,
+ 0,
+ NULL,
+ 0,
+ "items/airout.wav"
+ },
+
+ /*
+ * QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16)
+ * Special item that gives +2 to maximum health
+ */
+ {
+ "item_ancient_head",
+ Pickup_AncientHead,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/c_head/tris.md2", EF_ROTATE,
+ NULL,
+ "i_fixme",
+ "Ancient Head",
+ 2,
+ 60,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16)
+ * gives +1 to maximum health
+ */
+ {
+ "item_adrenaline",
+ Pickup_Adrenaline,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/adrenal/tris.md2", EF_ROTATE,
+ NULL,
+ "p_adrenaline",
+ "Adrenaline",
+ 2,
+ 60,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_bandolier",
+ Pickup_Bandolier,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/band/tris.md2", EF_ROTATE,
+ NULL,
+ "p_bandolier",
+ "Bandolier",
+ 2,
+ 60,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+ {
+ "item_pack",
+ Pickup_Pack,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ "models/items/pack/tris.md2", EF_ROTATE,
+ NULL,
+ "i_pack",
+ "Ammo Pack",
+ 2,
+ 180,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * key for computer centers
+ */
+ {
+ "key_data_cd",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/data_cd/tris.md2", EF_ROTATE,
+ NULL,
+ "k_datacd",
+ "Data CD",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH
+ * warehouse circuits
+ */
+ {
+ "key_power_cube",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/power/tris.md2", EF_ROTATE,
+ NULL,
+ "k_powercube",
+ "Power Cube",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * key for the entrance of jail3
+ */
+ {
+ "key_pyramid",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/pyramid/tris.md2", EF_ROTATE,
+ NULL,
+ "k_pyramid",
+ "Pyramid Key",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * key for the city computer
+ */
+ {
+ "key_data_spinner",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/spinner/tris.md2", EF_ROTATE,
+ NULL,
+ "k_dataspin",
+ "Data Spinner",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * security pass for the security level
+ */
+ {
+ "key_pass",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/pass/tris.md2", EF_ROTATE,
+ NULL,
+ "k_security",
+ "Security Pass",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * normal door key - blue
+ */
+ {
+ "key_blue_key",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/key/tris.md2", EF_ROTATE,
+ NULL,
+ "k_bluekey",
+ "Blue Key",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * normal door key - red
+ */
+ {
+ "key_red_key",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/red_key/tris.md2", EF_ROTATE,
+ NULL,
+ "k_redkey",
+ "Red Key",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+
+ /*
+ * QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * normal door key - blue
+ */
+ {
+ "key_green_key",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/green_key/tris.md2", EF_ROTATE,
+ NULL,
+ "k_green",
+ "Green Key",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * tank commander's head
+ */
+ {
+ "key_commander_head",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/monsters/commandr/head/tris.md2", EF_GIB,
+ NULL,
+ "k_comhead",
+ "Commander's Head",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ /*
+ * QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16)
+ * tank commander's head
+ */
+ {
+ "key_airstrike_target",
+ Pickup_Key,
+ NULL,
+ Drop_General,
+ NULL,
+ "items/pkup.wav",
+ "models/items/keys/target/tris.md2", EF_ROTATE,
+ NULL,
+ "i_airstrike",
+ "Airstrike Marker",
+ 2,
+ 0,
+ NULL,
+ IT_STAY_COOP | IT_KEY,
+ 0,
+ NULL,
+ 0,
+ ""
+ },
+
+ {
+ NULL,
+ Pickup_Health,
+ NULL,
+ NULL,
+ NULL,
+ "items/pkup.wav",
+ NULL, 0,
+ NULL,
+ "i_health",
+ "Health",
+ 3,
+ 0,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ 0,
+
+ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"
+ },
+
+ /* end of list marker */
+ {NULL}
+};
+
+/*
+ * QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+void
+SP_item_health(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/healing/medium/tris.md2";
+ self->count = 10;
+ SpawnItem(self, FindItem("Health"));
+ gi.soundindex("items/n_health.wav");
+}
+
+/*
+ * QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+void
+SP_item_health_small(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/healing/stimpack/tris.md2";
+ self->count = 2;
+ SpawnItem(self, FindItem("Health"));
+ self->style = HEALTH_IGNORE_MAX;
+ gi.soundindex("items/s_health.wav");
+}
+
+/*
+ * QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+void
+SP_item_health_large(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/healing/large/tris.md2";
+ self->count = 25;
+ SpawnItem(self, FindItem("Health"));
+ gi.soundindex("items/l_health.wav");
+}
+
+/*
+ * QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16)
+ */
+void
+SP_item_health_mega(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/items/mega_h/tris.md2";
+ self->count = 100;
+ SpawnItem(self, FindItem("Health"));
+ gi.soundindex("items/m_health.wav");
+ self->style = HEALTH_IGNORE_MAX | HEALTH_TIMED;
+}
+
+void
+SP_item_foodcube(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->model = "models/objects/trapfx/tris.md2";
+ SpawnItem(self, FindItem("Health"));
+ self->spawnflags |= DROPPED_ITEM;
+ self->style = HEALTH_IGNORE_MAX;
+ gi.soundindex("items/s_health.wav");
+ self->classname = "foodcube";
+}
+
+void
+InitItems(void)
+{
+ game.num_items = sizeof(itemlist) / sizeof(itemlist[0]) - 1;
+}
+
+/*
+ * Called by worldspawn
+ */
+void
+SetItemNames(void)
+{
+ int i;
+ gitem_t *it;
+
+ for (i = 0; i < game.num_items; i++)
+ {
+ it = &itemlist[i];
+ gi.configstring(CS_ITEMS + i, it->pickup_name);
+ }
+
+ jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
+ combat_armor_index = ITEM_INDEX(FindItem("Combat Armor"));
+ body_armor_index = ITEM_INDEX(FindItem("Body Armor"));
+ power_screen_index = ITEM_INDEX(FindItem("Power Screen"));
+ power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
+}
+
diff --git a/xatrix/src/g_main.c b/xatrix/src/g_main.c
new file mode 100644
index 0000000..3eaba6d
--- /dev/null
+++ b/xatrix/src/g_main.c
@@ -0,0 +1,467 @@
+/*
+ * =======================================================================
+ *
+ * Jump in into the game.so and support functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+game_locals_t game;
+level_locals_t level;
+game_import_t gi;
+game_export_t globals;
+spawn_temp_t st;
+
+int sm_meat_index;
+int snd_fry;
+int meansOfDeath;
+
+edict_t *g_edicts;
+
+cvar_t *deathmatch;
+cvar_t *coop;
+cvar_t *coop_elevator_delay;
+cvar_t *coop_pickup_weapons;
+cvar_t *dmflags;
+cvar_t *skill;
+cvar_t *fraglimit;
+cvar_t *timelimit;
+cvar_t *password;
+cvar_t *spectator_password;
+cvar_t *needpass;
+cvar_t *maxclients;
+cvar_t *maxspectators;
+cvar_t *maxentities;
+cvar_t *g_select_empty;
+cvar_t *dedicated;
+cvar_t *g_footsteps;
+cvar_t *g_fix_triggered;
+
+cvar_t *filterban;
+
+cvar_t *sv_maxvelocity;
+cvar_t *sv_gravity;
+
+cvar_t *sv_rollspeed;
+cvar_t *sv_rollangle;
+cvar_t *gun_x;
+cvar_t *gun_y;
+cvar_t *gun_z;
+
+cvar_t *run_pitch;
+cvar_t *run_roll;
+cvar_t *bob_up;
+cvar_t *bob_pitch;
+cvar_t *bob_roll;
+
+cvar_t *sv_cheats;
+
+cvar_t *flood_msgs;
+cvar_t *flood_persecond;
+cvar_t *flood_waitdelay;
+
+cvar_t *sv_maplist;
+
+cvar_t *gib_on;
+
+cvar_t *aimfix;
+cvar_t *g_machinegun_norecoil;
+cvar_t *g_swap_speed;
+
+void SpawnEntities(char *mapname, char *entities, char *spawnpoint);
+void ClientThink(edict_t *ent, usercmd_t *cmd);
+qboolean ClientConnect(edict_t *ent, char *userinfo);
+void ClientUserinfoChanged(edict_t *ent, char *userinfo);
+void ClientDisconnect(edict_t *ent);
+void ClientBegin(edict_t *ent);
+void ClientCommand(edict_t *ent);
+void RunEntity(edict_t *ent);
+void WriteGame(char *filename, qboolean autosave);
+void ReadGame(char *filename);
+void WriteLevel(char *filename);
+void ReadLevel(char *filename);
+void InitGame(void);
+void G_RunFrame(void);
+
+/* =================================================================== */
+
+void
+ShutdownGame(void)
+{
+ gi.dprintf("==== ShutdownGame ====\n");
+
+ gi.FreeTags(TAG_LEVEL);
+ gi.FreeTags(TAG_GAME);
+}
+
+/*
+ * Returns a pointer to the structure with
+ * all entry points and global variables
+ */
+game_export_t *
+GetGameAPI(game_import_t *import)
+{
+ gi = *import;
+
+ globals.apiversion = GAME_API_VERSION;
+ globals.Init = InitGame;
+ globals.Shutdown = ShutdownGame;
+ globals.SpawnEntities = SpawnEntities;
+
+ globals.WriteGame = WriteGame;
+ globals.ReadGame = ReadGame;
+ globals.WriteLevel = WriteLevel;
+ globals.ReadLevel = ReadLevel;
+
+ globals.ClientThink = ClientThink;
+ globals.ClientConnect = ClientConnect;
+ globals.ClientUserinfoChanged = ClientUserinfoChanged;
+ globals.ClientDisconnect = ClientDisconnect;
+ globals.ClientBegin = ClientBegin;
+ globals.ClientCommand = ClientCommand;
+
+ globals.RunFrame = G_RunFrame;
+
+ globals.ServerCommand = ServerCommand;
+
+ globals.edict_size = sizeof(edict_t);
+
+ /* Initalize the PRNG */
+ randk_seed();
+
+ return &globals;
+}
+
+void
+Sys_Error(char *error, ...)
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start(argptr, error);
+ vsprintf(text, error, argptr);
+ va_end(argptr);
+
+ gi.error("%s", text);
+}
+
+void
+Com_Printf(char *msg, ...)
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start(argptr, msg);
+ vsprintf(text, msg, argptr);
+ va_end(argptr);
+
+ gi.dprintf("%s", text);
+}
+
+/* ====================================================================== */
+
+void
+ClientEndServerFrames(void)
+{
+ int i;
+ edict_t *ent;
+
+ /* calc the player views now that all
+ pushing and damage has been added */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ ent = g_edicts + 1 + i;
+
+ if (!ent->inuse || !ent->client)
+ {
+ continue;
+ }
+
+ ClientEndServerFrame(ent);
+ }
+}
+
+/*
+ * Returns the created target changelevel
+ */
+edict_t *
+CreateTargetChangeLevel(char *map)
+{
+ edict_t *ent;
+
+ if (!map)
+ {
+ return NULL;
+ }
+
+ ent = G_Spawn();
+ ent->classname = "target_changelevel";
+ Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map);
+ ent->map = level.nextmap;
+ return ent;
+}
+
+/*
+ * The timelimit or fraglimit has been exceeded
+ */
+void
+EndDMLevel(void)
+{
+ edict_t *ent;
+ char *s, *t, *f;
+ static const char *seps = " ,\n\r";
+
+ /* stay on same level flag */
+ if ((int)dmflags->value & DF_SAME_LEVEL)
+ {
+ BeginIntermission(CreateTargetChangeLevel(level.mapname));
+ return;
+ }
+
+ /* see if it's in the map list */
+ if (*sv_maplist->string)
+ {
+ s = strdup(sv_maplist->string);
+ f = NULL;
+ t = strtok(s, seps);
+
+ while (t != NULL)
+ {
+ if (Q_stricmp(t, level.mapname) == 0)
+ {
+ /* it's in the list, go to the next one */
+ t = strtok(NULL, seps);
+
+ if (t == NULL) /* end of list, go to first one */
+ {
+ if (f == NULL) /* there isn't a first one, same level */
+ {
+ BeginIntermission(CreateTargetChangeLevel(level.mapname));
+ }
+ else
+ {
+ BeginIntermission(CreateTargetChangeLevel(f));
+ }
+ }
+ else
+ {
+ BeginIntermission(CreateTargetChangeLevel(t));
+ }
+
+ free(s);
+ return;
+ }
+
+ if (!f)
+ {
+ f = t;
+ }
+
+ t = strtok(NULL, seps);
+ }
+
+ free(s);
+ }
+
+ if (level.nextmap[0]) /* go to a specific map */
+ {
+ BeginIntermission(CreateTargetChangeLevel(level.nextmap));
+ }
+ else /* search for a changelevel */
+ {
+ ent = G_Find(NULL, FOFS(classname), "target_changelevel");
+
+ if (!ent)
+ { /* the map designer didn't include a changelevel,
+ so create a fake ent that goes back to the same level */
+ BeginIntermission(CreateTargetChangeLevel(level.mapname));
+ return;
+ }
+
+ BeginIntermission(ent);
+ }
+}
+
+void
+CheckNeedPass(void)
+{
+ int need;
+
+ /* if password or spectator_password has changed,
+ update needpass as needed */
+ if (password->modified || spectator_password->modified)
+ {
+ password->modified = spectator_password->modified = false;
+
+ need = 0;
+
+ if (*password->string && Q_stricmp(password->string, "none"))
+ {
+ need |= 1;
+ }
+
+ if (*spectator_password->string &&
+ Q_stricmp(spectator_password->string, "none"))
+ {
+ need |= 2;
+ }
+
+ gi.cvar_set("needpass", va("%d", need));
+ }
+}
+
+void
+CheckDMRules(void)
+{
+ int i;
+ gclient_t *cl;
+
+ if (level.intermissiontime)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ return;
+ }
+
+ if (timelimit->value)
+ {
+ if (level.time >= timelimit->value * 60)
+ {
+ gi.bprintf(PRINT_HIGH, "Timelimit hit.\n");
+ EndDMLevel();
+ return;
+ }
+ }
+
+ if (fraglimit->value)
+ {
+ for (i = 0; i < maxclients->value; i++)
+ {
+ cl = game.clients + i;
+
+ if (!g_edicts[i + 1].inuse)
+ {
+ continue;
+ }
+
+ if (cl->resp.score >= fraglimit->value)
+ {
+ gi.bprintf(PRINT_HIGH, "Fraglimit hit.\n");
+ EndDMLevel();
+ return;
+ }
+ }
+ }
+}
+
+void
+ExitLevel(void)
+{
+ int i;
+ edict_t *ent;
+ char command[256];
+
+ Com_sprintf(command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
+ gi.AddCommandString(command);
+ level.changemap = NULL;
+ level.exitintermission = 0;
+ level.intermissiontime = 0;
+ ClientEndServerFrames();
+
+ /* clear some things before going to next level */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ ent = g_edicts + 1 + i;
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (ent->health > ent->max_health)
+ {
+ ent->health = ent->max_health;
+ }
+ }
+
+ debristhisframe = 0;
+ gibsthisframe = 0;
+}
+
+/*
+ * Advances the world by 0.1 seconds
+ */
+void
+G_RunFrame(void)
+{
+ int i;
+ edict_t *ent;
+
+ level.framenum++;
+ level.time = level.framenum * FRAMETIME;
+
+ debristhisframe = 0;
+ gibsthisframe = 0;
+
+ /* choose a client for monsters to target this frame */
+ AI_SetSightClient();
+
+ /* exit intermissions */
+ if (level.exitintermission)
+ {
+ ExitLevel();
+ return;
+ }
+
+ /* treat each object in turn even
+ the world gets a chance to think */
+ ent = &g_edicts[0];
+
+ for (i = 0; i < globals.num_edicts; i++, ent++)
+ {
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ level.current_entity = ent;
+
+ VectorCopy(ent->s.origin, ent->s.old_origin);
+
+ /* if the ground entity moved, make sure we are still on it */
+ if ((ent->groundentity) &&
+ (ent->groundentity->linkcount != ent->groundentity_linkcount))
+ {
+ ent->groundentity = NULL;
+
+ if (!(ent->flags & (FL_SWIM | FL_FLY)) &&
+ (ent->svflags & SVF_MONSTER))
+ {
+ M_CheckGround(ent);
+ }
+ }
+
+ if ((i > 0) && (i <= maxclients->value))
+ {
+ ClientBeginServerFrame(ent);
+ continue;
+ }
+
+ G_RunEntity(ent);
+ }
+
+ /* see if it is time to end a deathmatch */
+ CheckDMRules();
+
+ /* see if needpass needs updated */
+ CheckNeedPass();
+
+ /* build the playerstate_t structures for all players */
+ ClientEndServerFrames();
+}
+
diff --git a/xatrix/src/g_misc.c b/xatrix/src/g_misc.c
new file mode 100644
index 0000000..eaf1c0b
--- /dev/null
+++ b/xatrix/src/g_misc.c
@@ -0,0 +1,2982 @@
+/* =======================================================================
+ *
+ * Miscellaneos entities, functs and functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+int debristhisframe;
+int gibsthisframe;
+
+/*
+ * QUAKED func_group (0 0 0) ?
+ * Used to group brushes together just for editor convenience.
+ */
+
+/* ===================================================== */
+
+void
+Use_Areaportal(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->count ^= 1; /* toggle state */
+ gi.SetAreaPortalState(ent->style, ent->count);
+}
+
+/*
+ * QUAKED func_areaportal (0 0 0) ?
+ *
+ * This is a non-visible object that divides the world into
+ * areas that are seperated when this portal is not activated.
+ * Usually enclosed in the middle of a door.
+ */
+void
+SP_func_areaportal(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->use = Use_Areaportal;
+ ent->count = 0; /* always start closed; */
+}
+
+/* ===================================================== */
+
+void
+VelocityForDamage(int damage, vec3_t v)
+{
+ v[0] = 100.0 * crandom();
+ v[1] = 100.0 * crandom();
+ v[2] = 200.0 + 100.0 * random();
+
+ if (damage < 50)
+ {
+ VectorScale(v, 0.7, v);
+ }
+ else
+ {
+ VectorScale(v, 1.2, v);
+ }
+}
+
+void
+ClipGibVelocity(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->velocity[0] < -300)
+ {
+ ent->velocity[0] = -300;
+ }
+ else if (ent->velocity[0] > 300)
+ {
+ ent->velocity[0] = 300;
+ }
+
+ if (ent->velocity[1] < -300)
+ {
+ ent->velocity[1] = -300;
+ }
+ else if (ent->velocity[1] > 300)
+ {
+ ent->velocity[1] = 300;
+ }
+
+ if (ent->velocity[2] < 200)
+ {
+ ent->velocity[2] = 200; /* always some upwards */
+ }
+ else if (ent->velocity[2] > 500)
+ {
+ ent->velocity[2] = 500;
+ }
+}
+
+void
+gib_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.frame++;
+ self->nextthink = level.time + FRAMETIME;
+
+ if (self->s.frame == 10)
+ {
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 8 + random() * 10;
+ }
+}
+
+void
+gib_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane, csurface_t *surf /* unused */)
+{
+ vec3_t normal_angles, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->groundentity)
+ {
+ return;
+ }
+
+ self->touch = NULL;
+
+ if (plane)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0);
+
+ vectoangles(plane->normal, normal_angles);
+ AngleVectors(normal_angles, NULL, right, NULL);
+ vectoangles(right, self->s.angles);
+
+ if (self->s.modelindex == sm_meat_index)
+ {
+ self->s.frame++;
+ self->think = gib_think;
+ self->nextthink = level.time + FRAMETIME;
+ }
+ }
+}
+
+void
+gib_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+ThrowGib(edict_t *self, char *gibname, int damage, int type)
+{
+ edict_t *gib;
+ vec3_t vd;
+ vec3_t origin;
+ vec3_t size;
+ float vscale;
+
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ if (gibsthisframe > MAX_GIBS)
+ {
+ return;
+ }
+
+ gib = G_SpawnOptional();
+
+ if (!gib)
+ {
+ return;
+ }
+
+ gibsthisframe++;
+
+ VectorScale(self->size, 0.5, size);
+ VectorAdd(self->absmin, size, origin);
+ gib->s.origin[0] = origin[0] + crandom() * size[0];
+ gib->s.origin[1] = origin[1] + crandom() * size[1];
+ gib->s.origin[2] = origin[2] + crandom() * size[2];
+
+ gi.setmodel(gib, gibname);
+ gib->solid = SOLID_BBOX;
+ gib->svflags = SVF_DEADMONSTER;
+ gib->s.effects |= EF_GIB;
+ gib->flags |= FL_NO_KNOCKBACK;
+ gib->takedamage = DAMAGE_YES;
+ gib->die = gib_die;
+ gib->health = 250;
+
+ if (type == GIB_ORGANIC)
+ {
+ gib->movetype = MOVETYPE_TOSS;
+ gib->touch = gib_touch;
+ vscale = 0.5;
+ }
+ else
+ {
+ gib->movetype = MOVETYPE_BOUNCE;
+ vscale = 1.0;
+ }
+
+ VelocityForDamage(damage, vd);
+ VectorMA(self->velocity, vscale, vd, gib->velocity);
+ ClipGibVelocity(gib);
+ gib->avelocity[0] = random() * 600;
+ gib->avelocity[1] = random() * 600;
+ gib->avelocity[2] = random() * 600;
+
+ gib->think = G_FreeEdict;
+ gib->nextthink = level.time + 10 + random() * 10;
+
+ gi.linkentity(gib);
+}
+
+void
+ThrowHead(edict_t *self, char *gibname, int damage, int type)
+{
+ vec3_t vd;
+ float vscale;
+
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ self->s.skinnum = 0;
+ self->s.frame = 0;
+ VectorClear(self->mins);
+ VectorClear(self->maxs);
+
+ self->s.modelindex2 = 0;
+ gi.setmodel(self, gibname);
+ self->solid = SOLID_BBOX;
+ self->s.effects |= EF_GIB;
+ self->s.effects &= ~EF_FLIES;
+ self->s.sound = 0;
+ self->flags |= FL_NO_KNOCKBACK;
+ self->svflags &= ~SVF_MONSTER;
+ self->takedamage = DAMAGE_YES;
+ self->die = gib_die;
+
+ // The entity still has the monsters clipmaks.
+ // Reset it to MASK_SHOT to be on the save side.
+ self->clipmask = MASK_SHOT;
+
+ if (type == GIB_ORGANIC)
+ {
+ self->movetype = MOVETYPE_TOSS;
+ self->touch = gib_touch;
+ vscale = 0.5;
+ }
+ else
+ {
+ self->movetype = MOVETYPE_BOUNCE;
+ vscale = 1.0;
+ }
+
+ VelocityForDamage(damage, vd);
+ VectorMA(self->velocity, vscale, vd, self->velocity);
+ ClipGibVelocity(self);
+
+ self->avelocity[YAW] = crandom() * 600;
+
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 10 + random() * 10;
+
+ gi.linkentity(self);
+}
+
+void
+ThrowGibACID(edict_t *self, char *gibname, int damage, int type)
+{
+ edict_t *gib;
+ vec3_t vd;
+ vec3_t origin;
+ vec3_t size;
+ float vscale;
+
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ gibsthisframe++;
+
+ if (gibsthisframe > MAX_GIBS)
+ {
+ return;
+ }
+
+ gib = G_Spawn();
+
+ VectorScale(self->size, 0.5, size);
+ VectorAdd(self->absmin, size, origin);
+ gib->s.origin[0] = origin[0] + crandom() * size[0];
+ gib->s.origin[1] = origin[1] + crandom() * size[1];
+ gib->s.origin[2] = origin[2] + crandom() * size[2];
+
+ /* gi.setmodel (gib, gibname); */
+ gib->s.modelindex = gi.modelindex(gibname);
+
+ gib->clipmask = MASK_SHOT;
+ gib->solid = SOLID_BBOX;
+
+ gib->s.effects |= EF_GREENGIB;
+ /* note to self check this */
+ gib->s.renderfx |= RF_FULLBRIGHT;
+ gib->flags |= FL_NO_KNOCKBACK;
+ gib->takedamage = DAMAGE_YES;
+ gib->die = gib_die;
+ gib->dmg = 2;
+ gib->health = 250;
+
+ if (type == GIB_ORGANIC)
+ {
+ gib->movetype = MOVETYPE_TOSS;
+ vscale = 3.0;
+ }
+ else
+ {
+ gib->movetype = MOVETYPE_BOUNCE;
+ vscale = 1.0;
+ }
+
+ VelocityForDamage(damage, vd);
+ VectorMA(self->velocity, vscale, vd, gib->velocity);
+ ClipGibVelocity(gib);
+ gib->avelocity[0] = random() * 600;
+ gib->avelocity[1] = random() * 600;
+ gib->avelocity[2] = random() * 600;
+
+ gib->think = G_FreeEdict;
+ gib->nextthink = level.time + 10 + random() * 10;
+
+ gi.linkentity(gib);
+}
+
+void
+ThrowHeadACID(edict_t *self, char *gibname, int damage, int type)
+{
+ vec3_t vd;
+ float vscale;
+
+ if (!self || !gibname)
+ {
+ return;
+ }
+
+ self->s.skinnum = 0;
+ self->s.frame = 0;
+ VectorClear(self->mins);
+ VectorClear(self->maxs);
+
+ self->s.modelindex2 = 0;
+ gi.setmodel(self, gibname);
+
+ self->clipmask = MASK_SHOT;
+ self->solid = SOLID_BBOX;
+
+ self->s.effects |= EF_GREENGIB;
+ self->s.effects &= ~EF_FLIES;
+ self->s.effects |= RF_FULLBRIGHT;
+ self->s.sound = 0;
+ self->flags |= FL_NO_KNOCKBACK;
+ self->svflags &= ~SVF_MONSTER;
+ self->takedamage = DAMAGE_YES;
+ self->die = gib_die;
+ self->dmg = 2;
+
+ if (type == GIB_ORGANIC)
+ {
+ self->movetype = MOVETYPE_TOSS;
+ vscale = 0.5;
+ }
+ else
+ {
+ self->movetype = MOVETYPE_BOUNCE;
+ vscale = 1.0;
+ }
+
+ VelocityForDamage(damage, vd);
+ VectorMA(self->velocity, vscale, vd, self->velocity);
+ ClipGibVelocity(self);
+
+ self->avelocity[YAW] = crandom() * 600;
+
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 10 + random() * 10;
+
+ gi.linkentity(self);
+}
+
+void
+ThrowClientHead(edict_t *self, int damage)
+{
+ vec3_t vd;
+ char *gibname;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (rand() & 1)
+ {
+ gibname = "models/objects/gibs/head2/tris.md2";
+ self->s.skinnum = 1; /* second skin is player */
+ }
+ else
+ {
+ gibname = "models/objects/gibs/skull/tris.md2";
+ self->s.skinnum = 0;
+ }
+
+ self->s.origin[2] += 32;
+ self->s.frame = 0;
+ gi.setmodel(self, gibname);
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 16);
+
+ self->takedamage = DAMAGE_NO;
+ self->solid = SOLID_BBOX;
+ self->s.effects = EF_GIB;
+ self->s.sound = 0;
+ self->flags |= FL_NO_KNOCKBACK;
+
+ // The entity still has the monsters clipmaks.
+ // Reset it to MASK_SHOT to be on the save side.
+ self->clipmask = MASK_SHOT;
+
+ self->movetype = MOVETYPE_BOUNCE;
+ VelocityForDamage(damage, vd);
+ VectorAdd(self->velocity, vd, self->velocity);
+
+ if (self->client) /* bodies in the queue don't have a client anymore */
+ {
+ self->client->anim_priority = ANIM_DEATH;
+ self->client->anim_end = self->s.frame;
+ }
+ else
+ {
+ self->think = NULL;
+ self->nextthink = 0;
+ }
+
+ gi.linkentity(self);
+}
+
+void
+debris_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin)
+{
+ edict_t *chunk;
+ vec3_t v;
+
+ if (!self || !modelname)
+ {
+ return;
+ }
+
+ if (debristhisframe > MAX_DEBRIS)
+ {
+ return;
+ }
+
+ chunk = G_SpawnOptional();
+
+ if (!chunk)
+ {
+ return;
+ }
+
+ debristhisframe++;
+
+ VectorCopy(origin, chunk->s.origin);
+ gi.setmodel(chunk, modelname);
+ v[0] = 100 * crandom();
+ v[1] = 100 * crandom();
+ v[2] = 100 + 100 * crandom();
+ VectorMA(self->velocity, speed, v, chunk->velocity);
+ chunk->movetype = MOVETYPE_BOUNCE;
+ chunk->solid = SOLID_NOT;
+ chunk->avelocity[0] = random() * 600;
+ chunk->avelocity[1] = random() * 600;
+ chunk->avelocity[2] = random() * 600;
+ chunk->think = G_FreeEdict;
+ chunk->nextthink = level.time + 5 + random() * 5;
+ chunk->s.frame = 0;
+ chunk->flags = 0;
+ chunk->classname = "debris";
+ chunk->takedamage = DAMAGE_YES;
+ chunk->die = debris_die;
+ chunk->health = 250;
+ gi.linkentity(chunk);
+}
+
+void
+BecomeExplosion1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(self);
+}
+
+void
+BecomeExplosion2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION2);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(self);
+}
+/* ===================================================== */
+
+/*
+ * QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
+ * Target: next path corner
+ * Pathtarget: gets used when an entity that has
+ * this path_corner targeted touches it
+ */
+void
+path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ vec3_t v;
+ edict_t *next;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->movetarget != self)
+ {
+ return;
+ }
+
+ if (other->enemy)
+ {
+ return;
+ }
+
+ if (self->pathtarget)
+ {
+ char *savetarget;
+
+ savetarget = self->target;
+ self->target = self->pathtarget;
+ G_UseTargets(self, other);
+ self->target = savetarget;
+ }
+
+ if (self->target)
+ {
+ next = G_PickTarget(self->target);
+ }
+ else
+ {
+ next = NULL;
+ }
+
+ if ((next) && (next->spawnflags & 1))
+ {
+ VectorCopy(next->s.origin, v);
+ v[2] += next->mins[2];
+ v[2] -= other->mins[2];
+ VectorCopy(v, other->s.origin);
+ next = G_PickTarget(next->target);
+ other->s.event = EV_OTHER_TELEPORT;
+ }
+
+ other->goalentity = other->movetarget = next;
+
+ if (self->wait)
+ {
+ other->monsterinfo.pausetime = level.time + self->wait;
+ other->monsterinfo.stand(other);
+ return;
+ }
+
+ if (!other->movetarget)
+ {
+ other->monsterinfo.pausetime = level.time + 100000000;
+ other->monsterinfo.stand(other);
+ }
+ else
+ {
+ VectorSubtract(other->goalentity->s.origin, other->s.origin, v);
+ other->ideal_yaw = vectoyaw(v);
+ }
+}
+
+void
+SP_path_corner(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->targetname)
+ {
+ gi.dprintf("path_corner with no targetname at %s\n",
+ vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->touch = path_corner_touch;
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+ self->svflags |= SVF_NOCLIENT;
+ gi.linkentity(self);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
+ *
+ * Makes this the target of a monster and it will head here
+ * when first activated before going after the activator. If
+ * hold is selected, it will stay here.
+ */
+void
+point_combat_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ edict_t *activator;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->movetarget != self)
+ {
+ return;
+ }
+
+ if (self->target)
+ {
+ other->target = self->target;
+ other->goalentity = other->movetarget = G_PickTarget(other->target);
+
+ if (!other->goalentity)
+ {
+ gi.dprintf("%s at %s target %s does not exist\n",
+ self->classname, vtos(self->s.origin),
+ self->target);
+ other->movetarget = self;
+ }
+
+ self->target = NULL;
+ }
+ else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM | FL_FLY)))
+ {
+ other->monsterinfo.pausetime = level.time + 100000000;
+ other->monsterinfo.aiflags |= AI_STAND_GROUND;
+ other->monsterinfo.stand(other);
+ }
+
+ if (other->movetarget == self)
+ {
+ other->target = NULL;
+ other->movetarget = NULL;
+ other->goalentity = other->enemy;
+ other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
+ }
+
+ if (self->pathtarget)
+ {
+ char *savetarget;
+
+ savetarget = self->target;
+ self->target = self->pathtarget;
+
+ if (other->enemy && other->enemy->client)
+ {
+ activator = other->enemy;
+ }
+ else if (other->oldenemy && other->oldenemy->client)
+ {
+ activator = other->oldenemy;
+ }
+ else if (other->activator && other->activator->client)
+ {
+ activator = other->activator;
+ }
+ else
+ {
+ activator = other;
+ }
+
+ G_UseTargets(self, activator);
+ self->target = savetarget;
+ }
+}
+
+void
+SP_point_combat(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->touch = point_combat_touch;
+ VectorSet(self->mins, -8, -8, -16);
+ VectorSet(self->maxs, 8, 8, 16);
+ self->svflags = SVF_NOCLIENT;
+ gi.linkentity(self);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8)
+ * Just for the debugging level. Don't use
+ */
+void
+TH_viewthing(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.frame = (ent->s.frame + 1) % 7;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+SP_viewthing(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.dprintf("viewthing spawned\n");
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.renderfx = RF_FRAMELERP;
+ VectorSet(ent->mins, -16, -16, -24);
+ VectorSet(ent->maxs, 16, 16, 32);
+ ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
+ gi.linkentity(ent);
+ ent->nextthink = level.time + 0.5;
+ ent->think = TH_viewthing;
+ return;
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
+ * Used as a positional target for spotlights, etc.
+ */
+void
+SP_info_null(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_FreeEdict(self);
+}
+
+/*
+ * QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
+ * Used as a positional target for lighting.
+ */
+void
+SP_info_notnull(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->s.origin, self->absmin);
+ VectorCopy(self->s.origin, self->absmax);
+}
+
+/*
+ * QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
+ * Non-displayed light.
+ * Default light value is 300.
+ * Default style is 0.
+ * If targeted, will toggle between on and off.
+ * Default _cone value is 10 (used to set size of light for spotlights)
+ */
+
+#define START_OFF 1
+
+void
+light_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & START_OFF)
+ {
+ gi.configstring(CS_LIGHTS + self->style, "m");
+ self->spawnflags &= ~START_OFF;
+ }
+ else
+ {
+ gi.configstring(CS_LIGHTS + self->style, "a");
+ self->spawnflags |= START_OFF;
+ }
+}
+
+void
+SP_light(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* no targeted lights in deathmatch, because they cause global messages */
+ if (!self->targetname || deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->style >= 32)
+ {
+ self->use = light_use;
+
+ if (self->spawnflags & START_OFF)
+ {
+ gi.configstring(CS_LIGHTS + self->style, "a");
+ }
+ else
+ {
+ gi.configstring(CS_LIGHTS + self->style, "m");
+ }
+ }
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
+ * This is just a solid wall if not inhibited
+ *
+ * TRIGGER_SPAWN the wall will not be present until triggered
+ * it will then blink in to existance; it will
+ * kill anything that was in it's way
+ *
+ * TOGGLE only valid for TRIGGER_SPAWN walls
+ * this allows the wall to be turned on and off
+ *
+ * START_ON only valid for TRIGGER_SPAWN walls
+ * the wall will initially be present
+ */
+void
+func_wall_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->solid == SOLID_NOT)
+ {
+ self->solid = SOLID_BSP;
+ self->svflags &= ~SVF_NOCLIENT;
+ KillBox(self);
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ self->svflags |= SVF_NOCLIENT;
+ }
+
+ gi.linkentity(self);
+
+ if (!(self->spawnflags & 2))
+ {
+ self->use = NULL;
+ }
+}
+
+void
+SP_func_wall(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+
+ if (self->spawnflags & 8)
+ {
+ self->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (self->spawnflags & 16)
+ {
+ self->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ /* just a wall */
+ if ((self->spawnflags & 7) == 0)
+ {
+ self->solid = SOLID_BSP;
+ gi.linkentity(self);
+ return;
+ }
+
+ /* it must be TRIGGER_SPAWN */
+ if (!(self->spawnflags & 1))
+ {
+ self->spawnflags |= 1;
+ }
+
+ /* yell if the spawnflags are odd */
+ if (self->spawnflags & 4)
+ {
+ if (!(self->spawnflags & 2))
+ {
+ gi.dprintf("func_wall START_ON without TOGGLE\n");
+ self->spawnflags |= 2;
+ }
+ }
+
+ self->use = func_wall_use;
+
+ if (self->spawnflags & 4)
+ {
+ self->solid = SOLID_BSP;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ self->svflags |= SVF_NOCLIENT;
+ }
+
+ gi.linkentity(self);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
+ * This is solid bmodel that will fall if it's support it removed.
+ */
+void
+func_object_touch(edict_t *self, edict_t *other, cplane_t *plane,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other || !plane)
+ {
+ return;
+ }
+
+ if (plane->normal[2] < 1.0)
+ {
+ return;
+ }
+
+ if (other->takedamage == DAMAGE_NO)
+ {
+ return;
+ }
+
+ T_Damage(other, self, self, vec3_origin, self->s.origin,
+ vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void
+func_object_release(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_TOSS;
+ self->touch = func_object_touch;
+}
+
+void
+func_object_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BSP;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = NULL;
+ KillBox(self);
+ func_object_release(self);
+}
+
+void
+SP_func_object(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.setmodel(self, self->model);
+
+ self->mins[0] += 1;
+ self->mins[1] += 1;
+ self->mins[2] += 1;
+ self->maxs[0] -= 1;
+ self->maxs[1] -= 1;
+ self->maxs[2] -= 1;
+
+ if (!self->dmg)
+ {
+ self->dmg = 100;
+ }
+
+ if (self->spawnflags == 0)
+ {
+ self->solid = SOLID_BSP;
+ self->movetype = MOVETYPE_PUSH;
+ self->think = func_object_release;
+ self->nextthink = level.time + 2 * FRAMETIME;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ self->movetype = MOVETYPE_PUSH;
+ self->use = func_object_use;
+ self->svflags |= SVF_NOCLIENT;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (self->spawnflags & 4)
+ {
+ self->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ self->clipmask = MASK_MONSTERSOLID;
+
+ gi.linkentity(self);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST
+ * Any brush that you want to explode or break apart. If you want an
+ * explosion, set dmg and it will do a radius explosion of that amount
+ * at the center of the bursh.
+ *
+ * If targeted it will not be shootable.
+ *
+ * health defaults to 100.
+ *
+ * mass defaults to 75. This determines how much debris is emitted when
+ * it explodes. You get one large chunk per 100 of mass (up to 8) and
+ * one small chunk per 25 of mass (up to 16). So 800 gives the most.
+ */
+void
+func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ vec3_t origin;
+ vec3_t chunkorigin;
+ vec3_t size;
+ int count;
+ int mass;
+
+ if (!self || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ /* bmodel origins are (0 0 0), we need to adjust that here */
+ VectorScale(self->size, 0.5, size);
+ VectorAdd(self->absmin, size, origin);
+ VectorCopy(origin, self->s.origin);
+
+ self->takedamage = DAMAGE_NO;
+
+ if (self->dmg)
+ {
+ T_RadiusDamage(self, attacker, self->dmg, NULL,
+ self->dmg + 40, MOD_EXPLOSIVE);
+ }
+
+ VectorSubtract(self->s.origin, inflictor->s.origin, self->velocity);
+ VectorNormalize(self->velocity);
+ VectorScale(self->velocity, 150, self->velocity);
+
+ /* start chunks towards the center */
+ VectorScale(size, 0.5, size);
+
+ mass = self->mass;
+
+ if (!mass)
+ {
+ mass = 75;
+ }
+
+ /* big chunks */
+ if (mass >= 100)
+ {
+ count = mass / 100;
+
+ if (count > 8)
+ {
+ count = 8;
+ }
+
+ while (count--)
+ {
+ chunkorigin[0] = origin[0] + crandom() * size[0];
+ chunkorigin[1] = origin[1] + crandom() * size[1];
+ chunkorigin[2] = origin[2] + crandom() * size[2];
+ ThrowDebris(self, "models/objects/debris1/tris.md2", 1, chunkorigin);
+ }
+ }
+
+ /* small chunks */
+ count = mass / 25;
+
+ if (count > 16)
+ {
+ count = 16;
+ }
+
+ while (count--)
+ {
+ chunkorigin[0] = origin[0] + crandom() * size[0];
+ chunkorigin[1] = origin[1] + crandom() * size[1];
+ chunkorigin[2] = origin[2] + crandom() * size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", 2, chunkorigin);
+ }
+
+ G_UseTargets(self, attacker);
+
+ if (self->dmg)
+ {
+ BecomeExplosion1(self);
+ }
+ else
+ {
+ G_FreeEdict(self);
+ }
+}
+
+void
+func_explosive_use(edict_t *self, edict_t *other, edict_t *activator)
+{
+ func_explosive_explode(self, self, other, self->health, vec3_origin);
+}
+
+void
+func_explosive_spawn(edict_t *self, edict_t *other, edict_t *activator)
+{
+ self->solid = SOLID_BSP;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = NULL;
+ KillBox(self);
+ gi.linkentity(self);
+}
+
+void
+SP_func_explosive(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+
+ gi.modelindex("models/objects/debris1/tris.md2");
+ gi.modelindex("models/objects/debris2/tris.md2");
+
+ gi.setmodel(self, self->model);
+
+ if (self->spawnflags & 1)
+ {
+ self->svflags |= SVF_NOCLIENT;
+ self->solid = SOLID_NOT;
+ self->use = func_explosive_spawn;
+ }
+ else
+ {
+ self->solid = SOLID_BSP;
+
+ if (self->targetname)
+ {
+ self->use = func_explosive_use;
+ }
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->s.effects |= EF_ANIM_ALL;
+ }
+
+ if (self->spawnflags & 4)
+ {
+ self->s.effects |= EF_ANIM_ALLFAST;
+ }
+
+ if (self->use != func_explosive_use)
+ {
+ if (!self->health)
+ {
+ self->health = 100;
+ }
+
+ self->die = func_explosive_explode;
+ self->takedamage = DAMAGE_YES;
+ }
+
+ gi.linkentity(self);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40)
+ * Large exploding box. You can override its mass (100),
+ * health (80), and dmg (150).
+ */
+
+void
+barrel_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /*unused */)
+{
+ float ratio;
+ vec3_t v;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if ((!other->groundentity) || (other->groundentity == self))
+ {
+ return;
+ }
+
+ ratio = (float)other->mass / (float)self->mass;
+ VectorSubtract(self->s.origin, other->s.origin, v);
+ M_walkmove(self, vectoyaw(v), 20 * ratio * FRAMETIME);
+}
+
+void
+barrel_explode(edict_t *self)
+{
+ vec3_t org;
+ float spd;
+ vec3_t save;
+
+ if (!self)
+ {
+ return;
+ }
+
+ T_RadiusDamage(self, self->activator, self->dmg, NULL,
+ self->dmg + 40, MOD_BARREL);
+ VectorCopy(self->s.origin, save);
+ VectorMA(self->absmin, 0.5, self->size, self->s.origin);
+
+ /* a few big chunks */
+ spd = 1.5 * (float)self->dmg / 200.0;
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org);
+
+ /* bottom corners */
+ spd = 1.75 * (float)self->dmg / 200.0;
+ VectorCopy(self->absmin, org);
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+ VectorCopy(self->absmin, org);
+ org[0] += self->size[0];
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+ VectorCopy(self->absmin, org);
+ org[1] += self->size[1];
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+ VectorCopy(self->absmin, org);
+ org[0] += self->size[0];
+ org[1] += self->size[1];
+ ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org);
+
+ /* a bunch of little chunks */
+ spd = (float)(2 * self->dmg / 200);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+ org[0] = self->s.origin[0] + crandom() * self->size[0];
+ org[1] = self->s.origin[1] + crandom() * self->size[1];
+ org[2] = self->s.origin[2] + crandom() * self->size[2];
+ ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org);
+
+ VectorCopy(save, self->s.origin);
+
+ if (self->groundentity)
+ {
+ BecomeExplosion2(self);
+ }
+ else
+ {
+ BecomeExplosion1(self);
+ }
+}
+
+void
+barrel_delay(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ self->takedamage = DAMAGE_NO;
+ self->nextthink = level.time + 2 * FRAMETIME;
+ self->think = barrel_explode;
+ self->activator = attacker;
+}
+
+void
+SP_misc_explobox(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(self);
+ return;
+ }
+
+ gi.modelindex("models/objects/debris1/tris.md2");
+ gi.modelindex("models/objects/debris2/tris.md2");
+ gi.modelindex("models/objects/debris3/tris.md2");
+
+ self->solid = SOLID_BBOX;
+ self->movetype = MOVETYPE_STEP;
+
+ self->model = "models/objects/barrels/tris.md2";
+ self->s.modelindex = gi.modelindex(self->model);
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 40);
+
+ if (!self->mass)
+ {
+ self->mass = 400;
+ }
+
+ if (!self->health)
+ {
+ self->health = 10;
+ }
+
+ if (!self->dmg)
+ {
+ self->dmg = 150;
+ }
+
+ self->die = barrel_delay;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.aiflags = AI_NOSTEP;
+
+ self->touch = barrel_touch;
+
+ self->think = M_droptofloor;
+ self->nextthink = level.time + 2 * FRAMETIME;
+
+ gi.linkentity(self);
+}
+
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8)
+ */
+void
+misc_blackhole_use(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ G_FreeEdict(ent);
+}
+
+void
+misc_blackhole_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 19)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 0;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void misc_blackhole_transparent (edict_t *ent)
+{
+ ent->s.renderfx = RF_TRANSLUCENT|RF_NOSHADOW;
+ ent->prethink = NULL;
+ gi.linkentity(ent);
+}
+
+void
+SP_misc_blackhole(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_NOT;
+ VectorSet(ent->mins, -64, -64, 0);
+ VectorSet(ent->maxs, 64, 64, 8);
+ ent->s.modelindex = gi.modelindex("models/objects/black/tris.md2");
+ ent->s.renderfx = RF_TRANSLUCENT;
+ ent->use = misc_blackhole_use;
+ ent->think = misc_blackhole_think;
+ ent->prethink = misc_blackhole_transparent;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
+ */
+void
+misc_eastertank_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 293)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 254;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+SP_misc_eastertank(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, -16);
+ VectorSet(ent->maxs, 32, 32, 32);
+ ent->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
+ ent->s.frame = 254;
+ ent->think = misc_eastertank_think;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
+ */
+void
+misc_easterchick_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 247)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 208;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+SP_misc_easterchick(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, 0);
+ VectorSet(ent->maxs, 32, 32, 32);
+ ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
+ ent->s.frame = 208;
+ ent->think = misc_easterchick_think;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
+ */
+void
+misc_easterchick2_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 287)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->s.frame = 248;
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+SP_misc_easterchick2(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, 0);
+ VectorSet(ent->maxs, 32, 32, 32);
+ ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
+ ent->s.frame = 248;
+ ent->think = misc_easterchick2_think;
+ ent->nextthink = level.time + 2 * FRAMETIME;
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48)
+ * Not really a monster, this is the Tank Commander's decapitated body.
+ * There should be a item_commander_head that has this as it's target.
+ */
+void
+commander_body_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (++self->s.frame < 24)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else
+ {
+ self->nextthink = 0;
+ }
+
+ if (self->s.frame == 22)
+ {
+ gi.sound(self, CHAN_BODY, gi.soundindex(
+ "tank/thud.wav"), 1, ATTN_NORM, 0);
+ }
+}
+
+void
+commander_body_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = commander_body_think;
+ self->nextthink = level.time + FRAMETIME;
+ gi.sound(self, CHAN_BODY, gi.soundindex("tank/pain.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+commander_body_drop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_TOSS;
+ self->s.origin[2] += 2;
+}
+
+void
+SP_monster_commander_body(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_BBOX;
+ self->model = "models/monsters/commandr/tris.md2";
+ self->s.modelindex = gi.modelindex(self->model);
+ VectorSet(self->mins, -32, -32, 0);
+ VectorSet(self->maxs, 32, 32, 48);
+ self->use = commander_body_use;
+ self->takedamage = DAMAGE_YES;
+ self->flags = FL_GODMODE;
+ self->s.renderfx |= RF_FRAMELERP;
+ gi.linkentity(self);
+
+ gi.soundindex("tank/thud.wav");
+ gi.soundindex("tank/pain.wav");
+
+ self->think = commander_body_drop;
+ self->nextthink = level.time + 5 * FRAMETIME;
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4)
+ * The origin is the bottom of the banner.
+ * The banner is 128 tall.
+ */
+void
+misc_banner_think(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.frame = (ent->s.frame + 1) % 16;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+void
+SP_misc_banner(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
+ ent->s.frame = rand() % 16;
+ gi.linkentity(ent);
+
+ ent->think = misc_banner_think;
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED
+ * This is the dead player model. Comes in 6 exciting different poses!
+ */
+void
+misc_deadsoldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health > -80)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self,
+ "models/objects/gibs/sm_meat/tris.md2",
+ damage,
+ GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+}
+
+void
+SP_misc_deadsoldier(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.modelindex = gi.modelindex("models/deadbods/dude/tris.md2");
+
+ /* Defaults to frame 0 */
+ if (ent->spawnflags & 2)
+ {
+ ent->s.frame = 1;
+ }
+ else if (ent->spawnflags & 4)
+ {
+ ent->s.frame = 2;
+ }
+ else if (ent->spawnflags & 8)
+ {
+ ent->s.frame = 3;
+ }
+ else if (ent->spawnflags & 16)
+ {
+ ent->s.frame = 4;
+ }
+ else if (ent->spawnflags & 32)
+ {
+ ent->s.frame = 5;
+ }
+ else
+ {
+ ent->s.frame = 0;
+ }
+
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 16);
+ ent->deadflag = DEAD_DEAD;
+ ent->takedamage = DAMAGE_YES;
+ ent->svflags |= SVF_MONSTER | SVF_DEADMONSTER;
+ ent->die = misc_deadsoldier_die;
+ ent->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32)
+ * This is the Viper for the flyby bombing.
+ * It is trigger_spawned, so you must have something use it for it to show up.
+ * There must be a path for it to follow once it is activated.
+ *
+ * "speed" How fast the Viper should fly
+ */
+
+extern void train_use(edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find(edict_t *self);
+
+void
+misc_viper_use(edict_t *self, edict_t *other, edict_t *activator)
+{
+ if (!self || !other || !activator)
+ {
+ return;
+ }
+
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = train_use;
+ train_use(self, other, activator);
+}
+
+void
+SP_misc_viper(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->target)
+ {
+ gi.dprintf("misc_viper without a target at %s\n", vtos(ent->absmin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 300;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/ships/viper/tris.md2");
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 32);
+
+ ent->think = func_train_find;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->use = misc_viper_use;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed =
+ ent->speed;
+
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_crashviper (1 .5 0) (-176 -120 -24) (176 120 72)
+ * This is a large viper about to crash
+ */
+void
+SP_misc_crashviper(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->target)
+ {
+ gi.dprintf("misc_viper without a target at %s\n", vtos(ent->absmin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 300;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2");
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 32);
+
+ ent->think = func_train_find;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->use = misc_viper_use;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72)
+ * This is a large stationary viper as seen in Paul's intro
+ */
+void
+SP_misc_bigviper(edict_t *ent)
+{
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -176, -120, -24);
+ VectorSet(ent->maxs, 176, 120, 72);
+ ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2");
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8)
+ * "dmg" how much boom should the bomb make?
+ */
+void
+misc_viper_bomb_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_UseTargets(self, self->activator);
+
+ self->s.origin[2] = self->absmin[2] + 1;
+ T_RadiusDamage(self, self, self->dmg, NULL, self->dmg + 40, MOD_BOMB);
+ BecomeExplosion2(self);
+}
+
+void
+misc_viper_bomb_prethink(edict_t *self)
+{
+ vec3_t v;
+ float diff;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->groundentity = NULL;
+
+ diff = self->timestamp - level.time;
+
+ if (diff < -1.0)
+ {
+ diff = -1.0;
+ }
+
+ VectorScale(self->moveinfo.dir, 1.0 + diff, v);
+ v[2] = diff;
+
+ diff = self->s.angles[2];
+ vectoangles(v, self->s.angles);
+ self->s.angles[2] = diff + 10;
+}
+
+void
+misc_viper_bomb_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ edict_t *viper;
+
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BBOX;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->s.effects |= EF_ROCKET;
+ self->use = NULL;
+ self->movetype = MOVETYPE_TOSS;
+ self->prethink = misc_viper_bomb_prethink;
+ self->touch = misc_viper_bomb_touch;
+ self->activator = activator;
+
+ viper = G_Find(NULL, FOFS(classname), "misc_viper");
+ VectorScale(viper->moveinfo.dir, viper->moveinfo.speed, self->velocity);
+
+ self->timestamp = level.time;
+ VectorCopy(viper->moveinfo.dir, self->moveinfo.dir);
+}
+
+void
+SP_misc_viper_bomb(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_NOT;
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+
+ self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2");
+
+ if (!self->dmg)
+ {
+ self->dmg = 1000;
+ }
+
+ self->use = misc_viper_bomb_use;
+ self->svflags |= SVF_NOCLIENT;
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED misc_viper_missile (1 0 0) (-8 -8 -8) (8 8 8)
+ * "dmg" how much boom should the bomb make? the default value is 250
+ */
+
+void
+misc_viper_missile_use(edict_t *self, edict_t *other, edict_t *activator)
+{
+ vec3_t forward, right, up;
+ vec3_t start, dir;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, up);
+
+ self->enemy = G_Find(NULL, FOFS(targetname), self->target);
+
+ VectorCopy(self->enemy->s.origin, vec);
+
+ VectorCopy(self->s.origin, start);
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_rocket(self, start, dir, self->dmg, 500, MZ2_CHICK_ROCKET_1);
+
+ self->nextthink = level.time + 0.1;
+ self->think = G_FreeEdict;
+}
+
+void
+SP_misc_viper_missile(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_NOT;
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+
+ if (!self->dmg)
+ {
+ self->dmg = 250;
+ }
+
+ self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2");
+
+ self->use = misc_viper_missile_use;
+ self->svflags |= SVF_NOCLIENT;
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32)
+ * This is a Storgg ship for the flybys.
+ * It is trigger_spawned, so you must have something use it for it to show up.
+ * There must be a path for it to follow once it is activated.
+ *
+ * "speed" How fast it should fly
+ */
+
+extern void train_use(edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find(edict_t *self);
+
+void
+misc_strogg_ship_use(edict_t *self, edict_t *other /* other */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->svflags &= ~SVF_NOCLIENT;
+ self->use = train_use;
+ train_use(self, other, activator);
+}
+
+void
+SP_misc_strogg_ship(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->target)
+ {
+ gi.dprintf("%s without a target at %s\n", ent->classname,
+ vtos(ent->absmin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 300;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2");
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 32);
+
+ ent->think = func_train_find;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->use = misc_strogg_ship_use;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed =
+ ent->speed;
+
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_transport (1 0 0) (-8 -8 -8) (8 8 8) TRIGGER_SPAWN
+ * Maxx's transport at end of game
+ */
+void
+SP_misc_transport(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->target)
+ {
+ gi.dprintf("%s without a target at %s\n", ent->classname,
+ vtos(ent->absmin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->speed)
+ {
+ ent->speed = 300;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_NOT;
+ ent->s.modelindex = gi.modelindex("models/objects/ship/tris.md2");
+
+ VectorSet(ent->mins, -16, -16, 0);
+ VectorSet(ent->maxs, 16, 16, 32);
+
+ ent->think = func_train_find;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->use = misc_strogg_ship_use;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+ if (!(ent->spawnflags & 1))
+ {
+ ent->spawnflags |= 1;
+ }
+
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
+ */
+void
+misc_satellite_dish_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.frame++;
+
+ if (self->s.frame < 38)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+misc_satellite_dish_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.frame = 0;
+ self->think = misc_satellite_dish_think;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+SP_misc_satellite_dish(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -64, -64, 0);
+ VectorSet(ent->maxs, 64, 64, 128);
+ ent->s.modelindex = gi.modelindex("models/objects/satellite/tris.md2");
+ ent->use = misc_satellite_dish_use;
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
+ */
+void
+SP_light_mine1(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.modelindex = gi.modelindex("models/objects/minelite/light1/tris.md2");
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
+ */
+void
+SP_light_mine2(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_NONE;
+ ent->solid = SOLID_BBOX;
+ ent->s.modelindex = gi.modelindex("models/objects/minelite/light2/tris.md2");
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8)
+ * Intended for use with the target_spawner
+ */
+void
+SP_misc_gib_arm(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/gibs/arm/tris.md2");
+ ent->solid = SOLID_BBOX;
+ ent->s.effects |= EF_GIB;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = gib_die;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->svflags |= SVF_MONSTER;
+ ent->deadflag = DEAD_DEAD;
+ ent->avelocity[0] = random() * 200;
+ ent->avelocity[1] = random() * 200;
+ ent->avelocity[2] = random() * 200;
+ ent->think = G_FreeEdict;
+ ent->nextthink = level.time + 30;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8)
+ * Intended for use with the target_spawner
+ */
+void
+SP_misc_gib_leg(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/gibs/leg/tris.md2");
+ ent->solid = SOLID_BBOX;
+ ent->s.effects |= EF_GIB;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = gib_die;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->svflags |= SVF_MONSTER;
+ ent->deadflag = DEAD_DEAD;
+ ent->avelocity[0] = random() * 200;
+ ent->avelocity[1] = random() * 200;
+ ent->avelocity[2] = random() * 200;
+ ent->think = G_FreeEdict;
+ ent->nextthink = level.time + 30;
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8)
+ * Intended for use with the target_spawner
+ */
+void
+SP_misc_gib_head(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/gibs/head/tris.md2");
+ ent->solid = SOLID_BBOX;
+ ent->s.effects |= EF_GIB;
+ ent->takedamage = DAMAGE_YES;
+ ent->die = gib_die;
+ ent->movetype = MOVETYPE_TOSS;
+ ent->svflags |= SVF_MONSTER;
+ ent->deadflag = DEAD_DEAD;
+ ent->avelocity[0] = random() * 200;
+ ent->avelocity[1] = random() * 200;
+ ent->avelocity[2] = random() * 200;
+ ent->think = G_FreeEdict;
+ ent->nextthink = level.time + 30;
+ gi.linkentity(ent);
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED target_character (0 0 1) ?
+ * used with target_string (must be on same "team")
+ * "count" is position in the string (starts at 1)
+ */
+void
+SP_target_character(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+ self->solid = SOLID_BSP;
+ self->s.frame = 12;
+ gi.linkentity(self);
+ return;
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
+ */
+void
+target_string_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ edict_t *e;
+ int n, l;
+ char c;
+
+ if (!self)
+ {
+ return;
+ }
+
+ l = strlen(self->message);
+
+ for (e = self->teammaster; e; e = e->teamchain)
+ {
+ if (!e->count)
+ {
+ continue;
+ }
+
+ n = e->count - 1;
+
+ if (n > l)
+ {
+ e->s.frame = 12;
+ continue;
+ }
+
+ c = self->message[n];
+
+ if ((c >= '0') && (c <= '9'))
+ {
+ e->s.frame = c - '0';
+ }
+ else if (c == '-')
+ {
+ e->s.frame = 10;
+ }
+ else if (c == ':')
+ {
+ e->s.frame = 11;
+ }
+ else
+ {
+ e->s.frame = 12;
+ }
+ }
+}
+
+void
+SP_target_string(edict_t *self)
+{
+ if (!self->message)
+ {
+ self->message = "";
+ }
+
+ self->use = target_string_use;
+}
+
+/* ===================================================== */
+
+/*
+ * QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE
+ * target a target_string with this
+ *
+ * The default is to be a time of day clock
+ *
+ * TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget"
+ * If START_OFF, this entity must be used before it starts
+ *
+ * "style" 0 "xx"
+ * 1 "xx:xx"
+ * 2 "xx:xx:xx"
+ */
+
+#define CLOCK_MESSAGE_SIZE 16
+
+/* don't let field width of any clock messages change, or it
+ could cause an overwrite after a game load */
+
+void
+func_clock_reset(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->activator = NULL;
+
+ if (self->spawnflags & 1)
+ {
+ self->health = 0;
+ self->wait = self->count;
+ }
+ else if (self->spawnflags & 2)
+ {
+ self->health = self->count;
+ self->wait = 0;
+ }
+}
+
+void
+func_clock_format_countdown(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->style == 0)
+ {
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health);
+ return;
+ }
+
+ if (self->style == 1)
+ {
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE,
+ "%2i:%2i", self->health / 60, self->health % 60);
+
+ if (self->message[3] == ' ')
+ {
+ self->message[3] = '0';
+ }
+
+ return;
+ }
+
+ if (self->style == 2)
+ {
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE,
+ "%2i:%2i:%2i", self->health / 3600,
+ (self->health - (self->health / 3600) * 3600) / 60,
+ self->health % 60);
+
+ if (self->message[3] == ' ')
+ {
+ self->message[3] = '0';
+ }
+
+ if (self->message[6] == ' ')
+ {
+ self->message[6] = '0';
+ }
+
+ return;
+ }
+}
+
+void
+func_clock_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!self->enemy)
+ {
+ return;
+ }
+ }
+
+ if (self->spawnflags & 1)
+ {
+ func_clock_format_countdown(self);
+ self->health++;
+ }
+ else if (self->spawnflags & 2)
+ {
+ func_clock_format_countdown(self);
+ self->health--;
+ }
+ else
+ {
+ struct tm *ltime;
+ time_t gmtime;
+
+ time(&gmtime);
+ ltime = localtime(&gmtime);
+ Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i",
+ ltime->tm_hour, ltime->tm_min, ltime->tm_sec);
+
+ if (self->message[3] == ' ')
+ {
+ self->message[3] = '0';
+ }
+
+ if (self->message[6] == ' ')
+ {
+ self->message[6] = '0';
+ }
+ }
+
+ self->enemy->message = self->message;
+ self->enemy->use(self->enemy, self, self);
+
+ if (((self->spawnflags & 1) && (self->health > self->wait)) ||
+ ((self->spawnflags & 2) && (self->health < self->wait)))
+ {
+ if (self->pathtarget)
+ {
+ char *savetarget;
+ char *savemessage;
+
+ savetarget = self->target;
+ savemessage = self->message;
+ self->target = self->pathtarget;
+ self->message = NULL;
+ G_UseTargets(self, self->activator);
+ self->target = savetarget;
+ self->message = savemessage;
+ }
+
+ if (!(self->spawnflags & 8))
+ {
+ self->think = G_FreeEdict;
+ self->nextthink = level.time + 1;
+ return;
+ }
+
+ func_clock_reset(self);
+
+ if (self->spawnflags & 4)
+ {
+ return;
+ }
+ }
+
+ self->nextthink = level.time + 1;
+}
+
+void
+func_clock_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (!(self->spawnflags & 8))
+ {
+ self->use = NULL;
+ }
+
+ if (self->activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+ self->think(self);
+}
+
+void
+SP_func_clock(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("%s with no target at %s\n", self->classname,
+ vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ if ((self->spawnflags & 2) && (!self->count))
+ {
+ gi.dprintf("%s with no count at %s\n", self->classname,
+ vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ if ((self->spawnflags & 1) && (!self->count))
+ {
+ self->count = 60 * 60;
+ }
+
+ func_clock_reset(self);
+
+ self->message = gi.TagMalloc(CLOCK_MESSAGE_SIZE, TAG_LEVEL);
+
+ self->think = func_clock_think;
+
+ if (self->spawnflags & 4)
+ {
+ self->use = func_clock_use;
+ }
+ else
+ {
+ self->nextthink = level.time + 1;
+ }
+}
+
+/* ================================================================================= */
+
+void
+teleporter_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ edict_t *dest;
+ int i;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->client)
+ {
+ return;
+ }
+
+ dest = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!dest)
+ {
+ gi.dprintf("Couldn't find destination\n");
+ return;
+ }
+
+ /* unlink to make sure it can't possibly interfere with KillBox */
+ gi.unlinkentity(other);
+
+ VectorCopy(dest->s.origin, other->s.origin);
+ VectorCopy(dest->s.origin, other->s.old_origin);
+ other->s.origin[2] += 10;
+
+ /* clear the velocity and hold them in place briefly */
+ VectorClear(other->velocity);
+ other->client->ps.pmove.pm_time = 160 >> 3; /* hold time */
+ other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+ /* draw the teleport splash at source and on the player */
+ self->owner->s.event = EV_PLAYER_TELEPORT;
+ other->s.event = EV_PLAYER_TELEPORT;
+
+ /* set angles */
+ for (i = 0; i < 3; i++)
+ {
+ other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
+ dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+ }
+
+ VectorClear(other->s.angles);
+ VectorClear(other->client->ps.viewangles);
+ VectorClear(other->client->v_angle);
+
+ /* kill anything at the destination */
+ KillBox(other);
+
+ gi.linkentity(other);
+}
+
+/*
+ * QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
+ * Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
+ */
+void
+SP_misc_teleporter(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ edict_t *trig;
+
+ if (!ent->target)
+ {
+ gi.dprintf("teleporter without a target.\n");
+ G_FreeEdict(ent);
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/dmspot/tris.md2");
+ ent->s.skinnum = 1;
+ ent->s.effects = EF_TELEPORTER;
+ ent->s.sound = gi.soundindex("world/amb10.wav");
+ ent->solid = SOLID_BBOX;
+
+ VectorSet(ent->mins, -32, -32, -24);
+ VectorSet(ent->maxs, 32, 32, -16);
+ gi.linkentity(ent);
+
+ trig = G_Spawn();
+ trig->touch = teleporter_touch;
+ trig->solid = SOLID_TRIGGER;
+ trig->target = ent->target;
+ trig->owner = ent;
+ VectorCopy(ent->s.origin, trig->s.origin);
+ VectorSet(trig->mins, -8, -8, 8);
+ VectorSet(trig->maxs, 8, 8, 24);
+ gi.linkentity(trig);
+}
+
+/*
+ * QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
+ * Point teleporters at these.
+ */
+void
+SP_misc_teleporter_dest(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.setmodel(ent, "models/objects/dmspot/tris.md2");
+ ent->s.skinnum = 0;
+ ent->solid = SOLID_BBOX;
+ VectorSet(ent->mins, -32, -32, -24);
+ VectorSet(ent->maxs, 32, 32, -16);
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_amb4 (1 0 0) (-16 -16 -16) (16 16 16)
+ * Mal's amb4 loop entity
+ */
+static int amb4sound;
+
+void
+amb4_think(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->nextthink = level.time + 2.0;
+ gi.sound(ent, CHAN_VOICE, amb4sound, 1, ATTN_NONE, 0);
+}
+
+void
+SP_misc_amb4(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->think = amb4_think;
+ ent->nextthink = level.time + 1;
+ amb4sound = gi.soundindex("world/amb4.wav");
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED misc_nuke (1 0 0) (-16 -16 -16) (16 16 16)
+ */
+void
+use_nuke(edict_t *self, edict_t *other, edict_t *activator)
+{
+ edict_t *from = g_edicts;
+
+ if (!self)
+ {
+ return;
+ }
+
+ for ( ; from < &g_edicts[globals.num_edicts]; from++)
+ {
+ if (from == self)
+ {
+ continue;
+ }
+
+ if (from->client)
+ {
+ T_Damage(from, self, self, vec3_origin, from->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_TRAP);
+ }
+ else if (from->svflags & SVF_MONSTER)
+ {
+ G_FreeEdict(from);
+ }
+ }
+
+ self->use = NULL;
+}
+
+void
+SP_misc_nuke(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->use = use_nuke;
+}
+
diff --git a/xatrix/src/g_monster.c b/xatrix/src/g_monster.c
new file mode 100644
index 0000000..d1441bf
--- /dev/null
+++ b/xatrix/src/g_monster.c
@@ -0,0 +1,1260 @@
+/* =======================================================================
+ *
+ * Monster utility functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+void monster_start_go(edict_t *self);
+
+/* Monster weapons */
+
+void
+monster_fire_bullet(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int kick, int hspread, int vspread, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int count, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_shotgun(self, start, aimdir, damage, kick, hspread, vspread,
+ count, MOD_UNKNOWN);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_blaster(self, start, dir, damage, speed, effect, false);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_blueblaster(self, start, dir, damage, speed, effect);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(MZ_BLUEHYPERBLASTER);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_ionripper(self, start, dir, damage, speed, effect);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_heat(self, start, dir, damage, speed, damage, damage);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+dabeam_hit(edict_t *self)
+{
+ edict_t *ignore;
+ vec3_t start;
+ vec3_t end;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ignore = self;
+ VectorCopy(self->s.origin, start);
+ VectorMA(start, 2048, self->movedir, end);
+
+ while (1)
+ {
+ tr = gi.trace(start, NULL, NULL, end, ignore,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
+
+ if (!tr.ent)
+ {
+ break;
+ }
+
+ /* hurt it if we can */
+ if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) &&
+ (tr.ent != self->owner))
+ {
+ T_Damage(tr.ent, self, self->owner, self->movedir, tr.endpos,
+ vec3_origin, self->dmg, skill->value, DAMAGE_ENERGY,
+ MOD_TARGET_LASER);
+ }
+
+ if (self->dmg < 0) /* healer ray */
+ {
+ /* when player is at 100 health
+ just undo health fix */
+ if (tr.ent->client && (tr.ent->health > 100))
+ {
+ tr.ent->health += self->dmg;
+ }
+ }
+
+ /* if we hit something that's not a monster or
+ player or is immune to lasers, we're done */
+ if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+ {
+ if (self->spawnflags & 0x80000000)
+ {
+ self->spawnflags &= ~0x80000000;
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_LASER_SPARKS);
+ gi.WriteByte(10);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(self->s.skinnum);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+
+ break;
+ }
+
+ ignore = tr.ent;
+ VectorCopy(tr.endpos, start);
+ }
+
+ VectorCopy(tr.endpos, self->s.old_origin);
+ self->nextthink = level.time + 0.1;
+ self->think = G_FreeEdict;
+}
+
+void
+monster_dabeam(edict_t *self)
+{
+ vec3_t last_movedir;
+ vec3_t point;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_NOT;
+ self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT;
+ self->s.modelindex = 1;
+
+ self->s.frame = 2;
+
+ if (self->owner->monsterinfo.aiflags & AI_MEDIC)
+ {
+ self->s.skinnum = 0xf3f3f1f1;
+ }
+ else
+ {
+ self->s.skinnum = 0xf2f2f0f0;
+ }
+
+ if (self->enemy)
+ {
+ VectorCopy(self->movedir, last_movedir);
+ VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point);
+
+ if (self->owner->monsterinfo.aiflags & AI_MEDIC)
+ {
+ point[0] += sin(level.time) * 8;
+ }
+
+ VectorSubtract(point, self->s.origin, self->movedir);
+ VectorNormalize(self->movedir);
+
+ if (!VectorCompare(self->movedir, last_movedir))
+ {
+ self->spawnflags |= 0x80000000;
+ }
+ }
+ else
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+
+ self->think = dabeam_hit;
+ self->nextthink = level.time + 0.1;
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+ gi.linkentity(self);
+
+ self->spawnflags |= 0x80000001;
+ self->svflags &= ~SVF_NOCLIENT;
+}
+
+void
+monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir,
+ int damage, int speed, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_grenade(self, start, aimdir, damage, speed, 2.5, damage + 40);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir,
+ int damage, int speed, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_rocket(self, start, dir, damage, speed, damage + 20, damage);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir,
+ int damage, int kick, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_rail(self, start, aimdir, damage, kick);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+void
+monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int kick, float damage_radius, int flashtype)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_bfg(self, start, aimdir, damage, speed, damage_radius);
+
+ gi.WriteByte(svc_muzzleflash2);
+ gi.WriteShort(self - g_edicts);
+ gi.WriteByte(flashtype);
+ gi.multicast(start, MULTICAST_PVS);
+}
+
+/* Monster utility functions */
+
+void
+M_FliesOff(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.effects &= ~EF_FLIES;
+ self->s.sound = 0;
+}
+
+void
+M_FliesOn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ return;
+ }
+
+ self->s.effects |= EF_FLIES;
+ self->s.sound = gi.soundindex("infantry/inflies1.wav");
+ self->think = M_FliesOff;
+ self->nextthink = level.time + 60;
+}
+
+void
+M_FlyCheck(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ return;
+ }
+
+ if (random() > 0.5)
+ {
+ return;
+ }
+
+ self->think = M_FliesOn;
+ self->nextthink = level.time + 5 + 10 * random();
+}
+
+void
+AttackFinished(edict_t *self, float time)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.attack_finished = level.time + time;
+}
+
+void
+M_CheckGround(edict_t *ent)
+{
+ vec3_t point;
+ trace_t trace;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->flags & (FL_SWIM | FL_FLY))
+ {
+ return;
+ }
+
+ if (ent->velocity[2] > 100)
+ {
+ ent->groundentity = NULL;
+ return;
+ }
+
+ /* if the hull point one-quarter unit down
+ is solid the entity is on ground */
+ point[0] = ent->s.origin[0];
+ point[1] = ent->s.origin[1];
+ point[2] = ent->s.origin[2] - 0.25;
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ point, ent, MASK_MONSTERSOLID);
+
+ /* check steepness */
+ if ((trace.plane.normal[2] < 0.7) && !trace.startsolid)
+ {
+ ent->groundentity = NULL;
+ return;
+ }
+
+ if (!trace.startsolid && !trace.allsolid)
+ {
+ VectorCopy(trace.endpos, ent->s.origin);
+ ent->groundentity = trace.ent;
+ ent->groundentity_linkcount = trace.ent->linkcount;
+ ent->velocity[2] = 0;
+ }
+}
+
+void
+M_CatagorizePosition(edict_t *ent)
+{
+ vec3_t point;
+ int cont;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* get waterlevel */
+ point[0] = (ent->absmax[0] + ent->absmin[0])/2;
+ point[1] = (ent->absmax[1] + ent->absmin[1])/2;
+ point[2] = ent->absmin[2] + 2;
+ cont = gi.pointcontents(point);
+
+ if (!(cont & MASK_WATER))
+ {
+ ent->waterlevel = 0;
+ ent->watertype = 0;
+ return;
+ }
+
+ ent->watertype = cont;
+ ent->waterlevel = 1;
+ point[2] += 26;
+ cont = gi.pointcontents(point);
+
+ if (!(cont & MASK_WATER))
+ {
+ return;
+ }
+
+ ent->waterlevel = 2;
+ point[2] += 22;
+ cont = gi.pointcontents(point);
+
+ if (cont & MASK_WATER)
+ {
+ ent->waterlevel = 3;
+ }
+}
+
+void
+M_WorldEffects(edict_t *ent)
+{
+ int dmg;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->health > 0)
+ {
+ if (!(ent->flags & FL_SWIM))
+ {
+ if (ent->waterlevel < 3)
+ {
+ ent->air_finished = level.time + 12;
+ }
+ else if (ent->air_finished < level.time)
+ {
+ /* drown! */
+ if (ent->pain_debounce_time < level.time)
+ {
+ dmg = 2 + 2 * floor(level.time - ent->air_finished);
+
+ if (dmg > 15)
+ {
+ dmg = 15;
+ }
+
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin,
+ vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+ ent->pain_debounce_time = level.time + 1;
+ }
+ }
+ }
+ else
+ {
+ if (ent->waterlevel > 0)
+ {
+ ent->air_finished = level.time + 9;
+ }
+ else if (ent->air_finished < level.time)
+ {
+ /* suffocate! */
+ if (ent->pain_debounce_time < level.time)
+ {
+ dmg = 2 + 2 * floor(level.time - ent->air_finished);
+
+ if (dmg > 15)
+ {
+ dmg = 15;
+ }
+
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin,
+ vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+ ent->pain_debounce_time = level.time + 1;
+ }
+ }
+ }
+ }
+
+ if (ent->waterlevel == 0)
+ {
+ if (ent->flags & FL_INWATER)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex( "player/watr_out.wav"),
+ 1, ATTN_NORM, 0);
+ ent->flags &= ~FL_INWATER;
+ }
+
+ return;
+ }
+
+ if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
+ {
+ if (ent->damage_debounce_time < level.time)
+ {
+ ent->damage_debounce_time = level.time + 0.2;
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin,
+ vec3_origin, 10 * ent->waterlevel, 0, 0, MOD_LAVA);
+ }
+ }
+
+ if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
+ {
+ if (ent->damage_debounce_time < level.time)
+ {
+ ent->damage_debounce_time = level.time + 1;
+ T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin,
+ 4 * ent->waterlevel, 0, 0, MOD_SLIME);
+ }
+ }
+
+ if (!(ent->flags & FL_INWATER))
+ {
+ if (!(ent->svflags & SVF_DEADMONSTER))
+ {
+ if (ent->watertype & CONTENTS_LAVA)
+ {
+ if (random() <= 0.5)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex( "player/lava1.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava2.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ }
+ else if (ent->watertype & CONTENTS_SLIME)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ else if (ent->watertype & CONTENTS_WATER)
+ {
+ gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ }
+
+ ent->flags |= FL_INWATER;
+ ent->damage_debounce_time = 0;
+ }
+}
+
+void
+M_droptofloor(edict_t *ent)
+{
+ vec3_t end;
+ trace_t trace;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.origin[2] += 1;
+ VectorCopy(ent->s.origin, end);
+ end[2] -= 256;
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end,
+ ent, MASK_MONSTERSOLID);
+
+ if ((trace.fraction == 1) || trace.allsolid)
+ {
+ return;
+ }
+
+ VectorCopy(trace.endpos, ent->s.origin);
+
+ gi.linkentity(ent);
+ M_CheckGround(ent);
+ M_CatagorizePosition(ent);
+}
+
+void
+M_SetEffects(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN);
+ ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE);
+
+ if (ent->monsterinfo.aiflags & AI_RESURRECTING)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= RF_SHELL_RED;
+ }
+
+ if (ent->health <= 0)
+ {
+ return;
+ }
+
+ if (ent->powerarmor_time > level.time)
+ {
+ if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
+ {
+ ent->s.effects |= EF_POWERSCREEN;
+ }
+ else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= RF_SHELL_GREEN;
+ }
+ }
+}
+
+void
+M_MoveFrame(edict_t *self)
+{
+ mmove_t *move;
+ int index;
+
+ if (!self)
+ {
+ return;
+ }
+
+ move = self->monsterinfo.currentmove;
+ self->nextthink = level.time + FRAMETIME;
+
+ if ((self->monsterinfo.nextframe) &&
+ (self->monsterinfo.nextframe >= move->firstframe) &&
+ (self->monsterinfo.nextframe <= move->lastframe))
+ {
+ if (self->s.frame != self->monsterinfo.nextframe)
+ {
+ self->s.frame = self->monsterinfo.nextframe;
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+
+ self->monsterinfo.nextframe = 0;
+ }
+ else
+ {
+ /* prevent nextframe from leaking into a future move */
+ self->monsterinfo.nextframe = 0;
+
+ if (self->s.frame == move->lastframe)
+ {
+ if (move->endfunc)
+ {
+ move->endfunc(self);
+
+ /* regrab move, endfunc is very likely to change it */
+ move = self->monsterinfo.currentmove;
+
+ /* check for death */
+ if (self->svflags & SVF_DEADMONSTER)
+ {
+ return;
+ }
+ }
+ }
+
+ if ((self->s.frame < move->firstframe) ||
+ (self->s.frame > move->lastframe))
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ self->s.frame = move->firstframe;
+ }
+ else
+ {
+ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+ {
+ self->s.frame++;
+
+ if (self->s.frame > move->lastframe)
+ {
+ self->s.frame = move->firstframe;
+ }
+ }
+ }
+ }
+
+ index = self->s.frame - move->firstframe;
+
+ if (move->frame[index].aifunc)
+ {
+ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+ {
+ move->frame[index].aifunc(self,
+ move->frame[index].dist * self->monsterinfo.scale);
+ }
+ else
+ {
+ move->frame[index].aifunc(self, 0);
+ }
+ }
+
+ if (move->frame[index].thinkfunc)
+ {
+ move->frame[index].thinkfunc(self);
+ }
+}
+
+void
+monster_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ M_MoveFrame(self);
+
+ if (self->linkcount != self->monsterinfo.linkcount)
+ {
+ self->monsterinfo.linkcount = self->linkcount;
+ M_CheckGround(self);
+ }
+
+ M_CatagorizePosition(self);
+ M_WorldEffects(self);
+ M_SetEffects(self);
+}
+
+/*
+ * Using a monster makes it angry
+ * at the current activator
+ */
+void
+monster_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ if (self->health <= 0)
+ {
+ return;
+ }
+
+ if (activator->flags & FL_NOTARGET)
+ {
+ return;
+ }
+
+ if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ return;
+ }
+
+ /* delay reaction so if the monster is
+ teleported, its sound is still heard */
+ self->enemy = activator;
+ FoundTarget(self);
+}
+
+void
+monster_triggered_spawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.origin[2] += 1;
+ KillBox(self);
+
+ self->solid = SOLID_BBOX;
+ self->movetype = MOVETYPE_STEP;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->air_finished = level.time + 12;
+ gi.linkentity(self);
+
+ monster_start_go(self);
+
+ if (strcmp(self->classname, "monster_fixbot") == 0)
+ {
+ if (self->spawnflags & 16 || self->spawnflags & 8 || self->spawnflags &
+ 4)
+ {
+ self->enemy = NULL;
+ return;
+ }
+ }
+
+ if (self->enemy && !(self->spawnflags & 1) &&
+ !(self->enemy->flags & FL_NOTARGET))
+ {
+ FoundTarget(self);
+ }
+ else
+ {
+ self->enemy = NULL;
+ }
+}
+
+void
+monster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ /* we have a one frame delay here so we
+ don't telefrag the guy who activated us */
+ self->think = monster_triggered_spawn;
+ self->nextthink = level.time + FRAMETIME;
+
+ if (activator->client)
+ {
+ self->enemy = activator;
+ }
+
+ self->use = monster_use;
+}
+
+void
+monster_triggered_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_NOT;
+ self->movetype = MOVETYPE_NONE;
+ self->svflags |= SVF_NOCLIENT;
+ self->nextthink = 0;
+ self->use = monster_triggered_spawn_use;
+}
+
+/*
+ * When a monster dies, it fires all of its
+ * targets with the current enemy as activator.
+ */
+void
+monster_death_use(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags &= ~(FL_FLY | FL_SWIM);
+ self->monsterinfo.aiflags &= AI_GOOD_GUY;
+
+ if (self->item)
+ {
+ Drop_Item(self, self->item);
+ self->item = NULL;
+ }
+
+ if (self->deathtarget)
+ {
+ self->target = self->deathtarget;
+ }
+
+ if (!self->target)
+ {
+ return;
+ }
+
+ G_UseTargets(self, self->enemy);
+}
+
+qboolean
+monster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return false;
+ }
+
+ if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ self->spawnflags &= ~4;
+ self->spawnflags |= 1;
+ }
+
+ if ((self->spawnflags & 2) && !self->targetname)
+ {
+ if (g_fix_triggered->value)
+ {
+ self->spawnflags &= ~2;
+ }
+
+ gi.dprintf ("triggered %s at %s has no targetname\n", self->classname, vtos (self->s.origin));
+ }
+
+ if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
+ {
+ level.total_monsters++;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+ self->svflags |= SVF_MONSTER;
+ self->s.renderfx |= RF_FRAMELERP;
+ self->takedamage = DAMAGE_AIM;
+ self->air_finished = level.time + 12;
+ self->use = monster_use;
+
+ if(!self->max_health)
+ {
+ self->max_health = self->health;
+ }
+
+ self->clipmask = MASK_MONSTERSOLID;
+
+ self->s.skinnum = 0;
+ self->deadflag = DEAD_NO;
+ self->svflags &= ~SVF_DEADMONSTER;
+
+ if (!self->monsterinfo.checkattack)
+ {
+ self->monsterinfo.checkattack = M_CheckAttack;
+ }
+
+ VectorCopy(self->s.origin, self->s.old_origin);
+
+ if (st.item)
+ {
+ self->item = FindItemByClassname(st.item);
+
+ if (!self->item)
+ {
+ gi.dprintf("%s at %s has bad item: %s\n", self->classname,
+ vtos(self->s.origin), st.item);
+ }
+ }
+
+ /* randomize what frame they start on */
+ if (self->monsterinfo.currentmove)
+ {
+ self->s.frame = self->monsterinfo.currentmove->firstframe +
+ (rand() %
+ (self->monsterinfo.currentmove->lastframe -
+ self->monsterinfo.currentmove->firstframe + 1));
+ }
+
+ return true;
+}
+
+void
+monster_start_go(edict_t *self)
+{
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= 0)
+ {
+ return;
+ }
+
+ /* check for target to combat_point
+ and change to combattarget */
+ if (self->target)
+ {
+ qboolean notcombat;
+ qboolean fixup;
+ edict_t *target;
+
+ target = NULL;
+ notcombat = false;
+ fixup = false;
+
+ while ((target = G_Find(target, FOFS(targetname), self->target)) != NULL)
+ {
+ if (strcmp(target->classname, "point_combat") == 0)
+ {
+ self->combattarget = self->target;
+ fixup = true;
+ }
+ else
+ {
+ notcombat = true;
+ }
+ }
+
+ if (notcombat && self->combattarget)
+ {
+ gi.dprintf("%s at %s has target with mixed types\n",
+ self->classname, vtos(self->s.origin));
+ }
+
+ if (fixup)
+ {
+ self->target = NULL;
+ }
+ }
+
+ /* validate combattarget */
+ if (self->combattarget)
+ {
+ edict_t *target;
+
+ target = NULL;
+
+ while ((target = G_Find(target, FOFS(targetname),
+ self->combattarget)) != NULL)
+ {
+ if (strcmp(target->classname, "point_combat") != 0)
+ {
+ gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
+ self->classname, (int)self->s.origin[0], (int)self->s.origin[1],
+ (int)self->s.origin[2], self->combattarget, target->classname,
+ (int)target->s.origin[0], (int)target->s.origin[1],
+ (int)target->s.origin[2]);
+ }
+ }
+ }
+
+ if (self->target)
+ {
+ self->goalentity = self->movetarget = G_PickTarget(self->target);
+
+ if (!self->movetarget)
+ {
+ gi.dprintf("%s can't find target %s at %s\n", self->classname,
+ self->target, vtos(self->s.origin));
+ self->target = NULL;
+ self->monsterinfo.pausetime = 100000000;
+ self->monsterinfo.stand(self);
+ }
+ else if (strcmp(self->movetarget->classname, "path_corner") == 0)
+ {
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
+ self->monsterinfo.walk(self);
+ self->target = NULL;
+ }
+ else
+ {
+ self->goalentity = self->movetarget = NULL;
+ self->monsterinfo.pausetime = 100000000;
+ self->monsterinfo.stand(self);
+ }
+ }
+ else
+ {
+ self->monsterinfo.pausetime = 100000000;
+ self->monsterinfo.stand(self);
+ }
+
+ self->think = monster_think;
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+walkmonster_start_go(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->spawnflags & 2) && (level.time < 1))
+ {
+ M_droptofloor(self);
+
+ if (self->groundentity)
+ {
+ if (!M_walkmove(self, 0, 0))
+ {
+ gi.dprintf("%s in solid at %s\n", self->classname,
+ vtos(self->s.origin));
+ }
+ }
+ }
+
+ if (!self->yaw_speed)
+ {
+ self->yaw_speed = 20;
+ }
+
+ if (!self->viewheight)
+ {
+ self->viewheight = 25;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ monster_triggered_start(self);
+ }
+ else
+ {
+ monster_start_go(self);
+ }
+}
+
+void
+walkmonster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = walkmonster_start_go;
+ monster_start(self);
+}
+
+void
+flymonster_start_go(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!M_walkmove(self, 0, 0))
+ {
+ gi.dprintf("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+ }
+
+ if (!self->yaw_speed)
+ {
+ self->yaw_speed = 10;
+ }
+
+ if (!self->viewheight)
+ {
+ self->viewheight = 25;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ monster_triggered_start(self);
+ }
+ else
+ {
+ monster_start_go(self);
+ }
+}
+
+void
+flymonster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags |= FL_FLY;
+ self->think = flymonster_start_go;
+ monster_start(self);
+}
+
+void
+swimmonster_start_go(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->yaw_speed)
+ {
+ self->yaw_speed = 10;
+ }
+
+ if (!self->viewheight)
+ {
+ self->viewheight = 10;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ monster_triggered_start(self);
+ }
+ else
+ {
+ monster_start_go(self);
+ }
+}
+
+void
+swimmonster_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags |= FL_SWIM;
+ self->think = swimmonster_start_go;
+ monster_start(self);
+}
+
diff --git a/xatrix/src/g_phys.c b/xatrix/src/g_phys.c
new file mode 100644
index 0000000..b2c35f2
--- /dev/null
+++ b/xatrix/src/g_phys.c
@@ -0,0 +1,1300 @@
+/* =======================================================================
+ *
+ * Quake IIs legendary physic engine.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define STOP_EPSILON 0.1
+#define MAX_CLIP_PLANES 5
+
+#define sv_stopspeed 100
+#define sv_friction 6
+#define sv_waterfriction 1
+
+/*
+ * pushmove objects do not obey gravity, and do not interact
+ * with each other or trigger fields, but block normal movement
+ * and push normal objects when they move.
+ *
+ * onground is set for toss objects when they come to a complete
+ * rest. It is set for steping or walking objects.
+ *
+ * doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
+ * bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
+ * corpses are SOLID_NOT and MOVETYPE_TOSS
+ * crates are SOLID_BBOX and MOVETYPE_TOSS
+ * walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
+ * flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
+ *
+ * solid_edge items only clip against bsp models.
+ */
+
+edict_t *
+SV_TestEntityPosition(edict_t *ent)
+{
+ trace_t trace;
+ int mask;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ /* dead bodies are supposed to not be solid so lets
+ ensure they only collide with BSP during pushmoves
+ */
+ if (ent->clipmask && !(ent->svflags & SVF_MONSTER))
+ {
+ mask = ent->clipmask;
+ }
+ else
+ {
+ mask = MASK_SOLID;
+ }
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ ent->s.origin, ent, mask);
+
+ if (trace.startsolid)
+ {
+ return g_edicts;
+ }
+
+ return NULL;
+}
+
+void
+SV_CheckVelocity(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (VectorLength(ent->velocity) > sv_maxvelocity->value)
+ {
+ VectorNormalize(ent->velocity);
+ VectorScale(ent->velocity, sv_maxvelocity->value, ent->velocity);
+ }
+}
+
+/*
+ * Runs thinking code for
+ * this frame if necessary
+ */
+qboolean
+SV_RunThink(edict_t *ent)
+{
+ float thinktime;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ thinktime = ent->nextthink;
+
+ if (thinktime <= 0)
+ {
+ return true;
+ }
+
+ if (thinktime > level.time + 0.001)
+ {
+ return true;
+ }
+
+ ent->nextthink = 0;
+
+ if (!ent->think)
+ {
+ gi.error("NULL ent->think");
+ }
+
+ ent->think(ent);
+
+ return false;
+}
+
+/*
+ * Two entities have touched, so
+ * run their touch functions
+ */
+void
+SV_Impact(edict_t *e1, trace_t *trace)
+{
+ edict_t *e2;
+
+ if (!e1 || !trace)
+ {
+ return;
+ }
+
+ e2 = trace->ent;
+
+ if (e1->touch && (e1->solid != SOLID_NOT))
+ {
+ e1->touch(e1, e2, &trace->plane, trace->surface);
+ }
+
+ if (e2->touch && (e2->solid != SOLID_NOT))
+ {
+ e2->touch(e2, e1, NULL, NULL);
+ }
+}
+
+/*
+ * Slide off of the impacting object
+ * returns the blocked flags:
+ * 1 = floor
+ * 2 = step / wall
+ */
+int
+ClipVelocity(vec3_t in, vec3_t normal, vec3_t out, float overbounce)
+{
+ float backoff;
+ float change;
+ int i, blocked;
+
+ blocked = 0;
+
+ if (normal[2] > 0)
+ {
+ blocked |= 1; /* floor */
+ }
+
+ if (!normal[2])
+ {
+ blocked |= 2; /* step */
+ }
+
+ backoff = DotProduct(in, normal) * overbounce;
+
+ for (i = 0; i < 3; i++)
+ {
+ change = normal[i] * backoff;
+ out[i] = in[i] - change;
+
+ if ((out[i] > -STOP_EPSILON) && (out[i] < STOP_EPSILON))
+ {
+ out[i] = 0;
+ }
+ }
+
+ return blocked;
+}
+
+/*
+ * The basic solid body movement clip that slides
+ * along multiple planes. Returns the clipflags if
+ * the velocity was modified (hit something solid):
+ * 1 = floor
+ * 2 = wall / step
+ * 4 = dead stop
+ */
+int
+SV_FlyMove(edict_t *ent, float time, int mask)
+{
+ edict_t *hit;
+ int bumpcount, numbumps;
+ vec3_t dir;
+ float d;
+ int numplanes;
+ vec3_t planes[MAX_CLIP_PLANES];
+ vec3_t primal_velocity, original_velocity, new_velocity;
+ int i, j;
+ trace_t trace;
+ vec3_t end;
+ float time_left;
+ int blocked;
+
+ if (!ent)
+ {
+ return 0;
+ }
+
+ numbumps = 4;
+
+ blocked = 0;
+ VectorCopy(ent->velocity, original_velocity);
+ VectorCopy(ent->velocity, primal_velocity);
+ numplanes = 0;
+
+ time_left = time;
+
+ ent->groundentity = NULL;
+
+ for (bumpcount = 0; bumpcount < numbumps; bumpcount++)
+ {
+ for (i = 0; i < 3; i++)
+ {
+ end[i] = ent->s.origin[i] + time_left * ent->velocity[i];
+ }
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
+
+ if (trace.allsolid)
+ {
+ /* entity is trapped in another solid */
+ VectorCopy(vec3_origin, ent->velocity);
+ return 3;
+ }
+
+ if (trace.fraction > 0)
+ {
+ /* actually covered some distance */
+ VectorCopy(trace.endpos, ent->s.origin);
+ VectorCopy(ent->velocity, original_velocity);
+ numplanes = 0;
+ }
+
+ if (trace.fraction == 1)
+ {
+ break; /* moved the entire distance */
+ }
+
+ hit = trace.ent;
+
+ if (trace.plane.normal[2] > 0.7)
+ {
+ blocked |= 1; /* floor */
+
+ if (hit->solid == SOLID_BSP)
+ {
+ ent->groundentity = hit;
+ ent->groundentity_linkcount = hit->linkcount;
+ }
+ }
+
+ if (!trace.plane.normal[2])
+ {
+ blocked |= 2; /* step */
+ }
+
+ /* run the impact function */
+ SV_Impact(ent, &trace);
+
+ if (!ent->inuse)
+ {
+ break; /* removed by the impact function */
+ }
+
+ time_left -= time_left * trace.fraction;
+
+ /* cliped to another plane */
+ if (numplanes >= MAX_CLIP_PLANES)
+ {
+ /* this shouldn't really happen */
+ VectorCopy(vec3_origin, ent->velocity);
+ return 3;
+ }
+
+ VectorCopy(trace.plane.normal, planes[numplanes]);
+ numplanes++;
+
+ /* modify original_velocity so it
+ parallels all of the clip planes */
+ for (i = 0; i < numplanes; i++)
+ {
+ ClipVelocity(original_velocity, planes[i], new_velocity, 1);
+
+ for (j = 0; j < numplanes; j++)
+ {
+ if ((j != i) && !VectorCompare(planes[i], planes[j]))
+ {
+ if (DotProduct(new_velocity, planes[j]) < 0)
+ {
+ break; /* not ok */
+ }
+ }
+ }
+
+ if (j == numplanes)
+ {
+ break;
+ }
+ }
+
+ if (i != numplanes)
+ {
+ /* go along this plane */
+ VectorCopy(new_velocity, ent->velocity);
+ }
+ else
+ {
+ /* go along the crease */
+ if (numplanes != 2)
+ {
+ VectorCopy(vec3_origin, ent->velocity);
+ return 7;
+ }
+
+ CrossProduct(planes[0], planes[1], dir);
+ d = DotProduct(dir, ent->velocity);
+ VectorScale(dir, d, ent->velocity);
+ }
+
+ /* If original velocity is against the original
+ velocity, stop dead to avoid tiny occilations
+ in sloping corners */
+ if (DotProduct(ent->velocity, primal_velocity) <= 0)
+ {
+ VectorCopy(vec3_origin, ent->velocity);
+ return blocked;
+ }
+ }
+
+ return blocked;
+}
+
+void
+SV_AddGravity(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
+}
+
+/*
+ * Returns the actual bounding box of a bmodel.
+ * This is a big improvement over what q2 normally
+ * does with rotating bmodels - q2 sets absmin,
+ * absmax to a cube that will completely contain
+ * the bmodel at *any* rotation on *any* axis, whether
+ * the bmodel can actually rotate to that angle or not.
+ * This leads to a lot of false block tests in SV_Push
+ * if another bmodel is in the vicinity.
+ */
+void
+RealBoundingBox(edict_t *ent, vec3_t mins, vec3_t maxs)
+{
+ vec3_t forward, left, up, f1, l1, u1;
+ vec3_t p[8];
+ int i, j, k, j2, k4;
+
+ for (k = 0; k < 2; k++)
+ {
+ k4 = k * 4;
+
+ if (k)
+ {
+ p[k4][2] = ent->maxs[2];
+ }
+ else
+ {
+ p[k4][2] = ent->mins[2];
+ }
+
+ p[k4 + 1][2] = p[k4][2];
+ p[k4 + 2][2] = p[k4][2];
+ p[k4 + 3][2] = p[k4][2];
+
+ for (j = 0; j < 2; j++)
+ {
+ j2 = j * 2;
+
+ if (j)
+ {
+ p[j2 + k4][1] = ent->maxs[1];
+ }
+ else
+ {
+ p[j2 + k4][1] = ent->mins[1];
+ }
+
+ p[j2 + k4 + 1][1] = p[j2 + k4][1];
+
+ for (i = 0; i < 2; i++)
+ {
+ if (i)
+ {
+ p[i + j2 + k4][0] = ent->maxs[0];
+ }
+ else
+ {
+ p[i + j2 + k4][0] = ent->mins[0];
+ }
+ }
+ }
+ }
+
+ AngleVectors(ent->s.angles, forward, left, up);
+
+ for (i = 0; i < 8; i++)
+ {
+ VectorScale(forward, p[i][0], f1);
+ VectorScale(left, -p[i][1], l1);
+ VectorScale(up, p[i][2], u1);
+ VectorAdd(ent->s.origin, f1, p[i]);
+ VectorAdd(p[i], l1, p[i]);
+ VectorAdd(p[i], u1, p[i]);
+ }
+
+ VectorCopy(p[0], mins);
+ VectorCopy(p[0], maxs);
+
+ for (i = 1; i < 8; i++)
+ {
+ if (mins[0] > p[i][0])
+ {
+ mins[0] = p[i][0];
+ }
+
+ if (mins[1] > p[i][1])
+ {
+ mins[1] = p[i][1];
+ }
+
+ if (mins[2] > p[i][2])
+ {
+ mins[2] = p[i][2];
+ }
+
+ if (maxs[0] < p[i][0])
+ {
+ maxs[0] = p[i][0];
+ }
+
+ if (maxs[1] < p[i][1])
+ {
+ maxs[1] = p[i][1];
+ }
+
+ if (maxs[2] < p[i][2])
+ {
+ maxs[2] = p[i][2];
+ }
+ }
+}
+
+/* =============================================================================== */
+
+/* PUSHMOVE */
+
+/*
+ * Does not change the entities velocity at all
+ */
+trace_t
+SV_PushEntity(edict_t *ent, vec3_t push)
+{
+ trace_t trace;
+ vec3_t start;
+ vec3_t end;
+ int mask;
+
+ VectorCopy(ent->s.origin, start);
+ VectorAdd(start, push, end);
+
+retry:
+
+ if (ent->clipmask)
+ {
+ mask = ent->clipmask;
+ }
+ else
+ {
+ mask = MASK_SOLID;
+ }
+
+ trace = gi.trace(start, ent->mins, ent->maxs, end, ent, mask);
+
+ /* startsolid treats different-content volumes
+ as continuous, like the bbox of a monster/player
+ and the floor of an elevator. So do another trace
+ that only collides with BSP so that we make a best
+ effort to keep these entities inside non-solid space
+ */
+ if (trace.startsolid && (mask & ~MASK_SOLID))
+ {
+ trace = gi.trace (start, ent->mins, ent->maxs, end, ent, MASK_SOLID);
+ }
+
+ VectorCopy(trace.endpos, ent->s.origin);
+ gi.linkentity(ent);
+
+ /* Push slightly away from non-horizontal surfaces,
+ prevent origin stuck in the plane which causes
+ the entity to be rendered in full black. */
+ if (trace.plane.type != 2)
+ {
+ /* Limit the fix to gibs, debris and dead monsters.
+ Everything else may break existing maps. Items
+ may slide to unreachable locations, monsters may
+ get stuck, etc. */
+ if (((strncmp(ent->classname, "monster_", 8) == 0) && ent->health < 1) ||
+ (strcmp(ent->classname, "debris") == 0) || (ent->s.effects & EF_GIB))
+ {
+ VectorAdd(ent->s.origin, trace.plane.normal, ent->s.origin);
+ }
+ }
+
+ if (trace.fraction != 1.0)
+ {
+ SV_Impact(ent, &trace);
+
+ /* if the pushed entity went away and the pusher is still there */
+ if (!trace.ent->inuse && ent->inuse)
+ {
+ /* move the pusher back and try again */
+ VectorCopy(start, ent->s.origin);
+ gi.linkentity(ent);
+ goto retry;
+ }
+ }
+
+ if (ent->inuse)
+ {
+ G_TouchTriggers(ent);
+ }
+
+ return trace;
+}
+
+typedef struct
+{
+ edict_t *ent;
+ vec3_t origin;
+ vec3_t angles;
+} pushed_t;
+
+pushed_t pushed[MAX_EDICTS], *pushed_p;
+edict_t *obstacle;
+
+/*
+ * Objects need to be moved back on a failed push,
+ * otherwise riders would continue to slide.
+ */
+qboolean
+SV_Push(edict_t *pusher, vec3_t move, vec3_t amove)
+{
+ int i, e;
+ edict_t *check, *block;
+ pushed_t *p;
+ vec3_t org, org2, move2, forward, right, up;
+ vec3_t realmins, realmaxs;
+
+ if (!pusher)
+ {
+ return false;
+ }
+
+ /* clamp the move to 1/8 units, so the position will
+ be accurate for client side prediction */
+ for (i = 0; i < 3; i++)
+ {
+ float temp;
+ temp = move[i] * 8.0;
+
+ if (temp > 0.0)
+ {
+ temp += 0.5;
+ }
+ else
+ {
+ temp -= 0.5;
+ }
+
+ move[i] = 0.125 * (int)temp;
+ }
+
+ /* we need this for pushing things later */
+ VectorSubtract(vec3_origin, amove, org);
+ AngleVectors(org, forward, right, up);
+
+ /* save the pusher's original position */
+ pushed_p->ent = pusher;
+ VectorCopy(pusher->s.origin, pushed_p->origin);
+ VectorCopy(pusher->s.angles, pushed_p->angles);
+ pushed_p++;
+
+ /* move the pusher to it's final position */
+ VectorAdd(pusher->s.origin, move, pusher->s.origin);
+ VectorAdd(pusher->s.angles, amove, pusher->s.angles);
+ gi.linkentity(pusher);
+
+ /* Create a real bounding box for
+ rotating brush models. */
+ RealBoundingBox(pusher, realmins, realmaxs);
+
+ /* see if any solid entities are inside the final position */
+ check = g_edicts + 1;
+
+ for (e = 1; e < globals.num_edicts; e++, check++)
+ {
+ if (!check->inuse)
+ {
+ continue;
+ }
+
+ if ((check->movetype == MOVETYPE_PUSH) ||
+ (check->movetype == MOVETYPE_STOP) ||
+ (check->movetype == MOVETYPE_NONE) ||
+ (check->movetype == MOVETYPE_NOCLIP))
+ {
+ continue;
+ }
+
+ if (!check->area.prev)
+ {
+ continue; /* not linked in anywhere */
+ }
+
+ /* if the entity is standing on the pusher, it will definitely be moved */
+ if (check->groundentity != pusher)
+ {
+ /* see if the ent needs to be tested */
+ if ((check->absmin[0] >= realmaxs[0]) ||
+ (check->absmin[1] >= realmaxs[1]) ||
+ (check->absmin[2] >= realmaxs[2]) ||
+ (check->absmax[0] <= realmins[0]) ||
+ (check->absmax[1] <= realmins[1]) ||
+ (check->absmax[2] <= realmins[2]))
+ {
+ continue;
+ }
+
+ /* see if the ent's bbox is inside the pusher's final position */
+ if (!SV_TestEntityPosition(check))
+ {
+ continue;
+ }
+ }
+
+ if ((pusher->movetype == MOVETYPE_PUSH) ||
+ (check->groundentity == pusher))
+ {
+ /* move this entity */
+ pushed_p->ent = check;
+ VectorCopy(check->s.origin, pushed_p->origin);
+ VectorCopy(check->s.angles, pushed_p->angles);
+ pushed_p++;
+
+ /* try moving the contacted entity */
+ VectorAdd(check->s.origin, move, check->s.origin);
+
+ /* figure movement due to the pusher's amove */
+ VectorSubtract(check->s.origin, pusher->s.origin, org);
+ org2[0] = DotProduct(org, forward);
+ org2[1] = -DotProduct(org, right);
+
+ /* Quirk for blocking Elevators when
+ running under amd64. This is most
+ likey caused by a too high float
+ precision. -_- */
+ if (((pusher->s.number == 285) &&
+ (Q_strcasecmp(level.mapname, "xcompnd2") == 0)) ||
+ ((pusher->s.number == 520) &&
+ (Q_strcasecmp(level.mapname, "xsewer2") == 0)))
+ {
+ org2[2] = DotProduct(org, up) + 2;
+ }
+ else
+ {
+ org2[2] = DotProduct(org, up);
+ }
+
+ VectorSubtract(org2, org, move2);
+ VectorAdd(check->s.origin, move2, check->s.origin);
+
+ /* may have pushed them off an edge */
+ if (check->groundentity != pusher)
+ {
+ check->groundentity = NULL;
+ }
+
+ block = SV_TestEntityPosition(check);
+
+ if (!block)
+ {
+ /* pushed ok */
+ gi.linkentity(check);
+
+ /* impact? */
+ continue;
+ }
+
+ /* if it is ok to leave in the old position, do it this
+ is only relevent for riding entities, not pushed */
+ VectorSubtract(check->s.origin, move, check->s.origin);
+ block = SV_TestEntityPosition(check);
+
+ if (!block)
+ {
+ pushed_p--;
+ continue;
+ }
+ }
+
+ /* save off the obstacle so we can call the block function */
+ obstacle = check;
+
+ /* move back any entities we already moved
+ go backwards, so if the same entity was pushed
+ twice, it goes back to the original position */
+ for (p = pushed_p - 1; p >= pushed; p--)
+ {
+ VectorCopy(p->origin, p->ent->s.origin);
+ VectorCopy(p->angles, p->ent->s.angles);
+
+ gi.linkentity(p->ent);
+ }
+
+ return false;
+ }
+
+ /* see if anything we moved has touched a trigger */
+ for (p = pushed_p - 1; p >= pushed; p--)
+ {
+ G_TouchTriggers(p->ent);
+ }
+
+ return true;
+}
+
+/*
+ * Bmodel objects don't interact with each
+ * other, but push all box objects
+ */
+void
+SV_Physics_Pusher(edict_t *ent)
+{
+ vec3_t move, amove;
+ edict_t *part, *mv;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* if not a team captain, so movement
+ will be handled elsewhere */
+ if (ent->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ /* make sure all team slaves can move before commiting
+ any moves or calling any think functions. if the move
+ is blocked, all moved objects will be backed out */
+ pushed_p = pushed;
+
+ for (part = ent; part; part = part->teamchain)
+ {
+ if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
+ part->avelocity[0] || part->avelocity[1] || part->avelocity[2])
+ {
+ /* object is moving */
+ VectorScale(part->velocity, FRAMETIME, move);
+ VectorScale(part->avelocity, FRAMETIME, amove);
+
+ if (!SV_Push(part, move, amove))
+ {
+ break; /* move was blocked */
+ }
+ }
+ }
+
+ if (pushed_p > &pushed[MAX_EDICTS-1])
+ {
+ gi.error("pushed_p > &pushed[MAX_EDICTS-1], memory corrupted");
+ }
+
+ if (part)
+ {
+ /* the move failed, bump all nextthink times and back out moves */
+ for (mv = ent; mv; mv = mv->teamchain)
+ {
+ if (mv->nextthink > 0)
+ {
+ mv->nextthink += FRAMETIME;
+ }
+ }
+
+ /* if the pusher has a "blocked" function, call it
+ otherwise, just stay in place until the obstacle
+ is gone */
+ if (part->blocked)
+ {
+ part->blocked(part, obstacle);
+ }
+ }
+ else
+ {
+ /* the move succeeded, so call all think functions */
+ for (part = ent; part; part = part->teamchain)
+ {
+ SV_RunThink(part);
+ }
+ }
+}
+
+/* ================================================================== */
+
+/*
+ * Non moving objects can only think
+ */
+void
+SV_Physics_None(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* regular thinking */
+ SV_RunThink(ent);
+}
+
+/*
+ * A moving object that doesn't obey physics
+ */
+void
+SV_Physics_Noclip(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* regular thinking */
+ if (!SV_RunThink(ent))
+ {
+ return;
+ }
+
+ VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+ VectorMA(ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin);
+
+ gi.linkentity(ent);
+}
+
+/* ============================================================================== */
+
+/* TOSS / BOUNCE */
+
+/*
+ * Toss, bounce, and fly movement. When onground, do nothing.
+ */
+void
+SV_Physics_Toss(edict_t *ent)
+{
+ trace_t trace;
+ vec3_t move;
+ float backoff;
+ edict_t *slave;
+ qboolean wasinwater;
+ qboolean isinwater;
+ vec3_t old_origin;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* regular thinking */
+ SV_RunThink(ent);
+
+ /* entities are very often freed during thinking */
+ if (!ent->inuse)
+ {
+ return;
+ }
+
+ /* if not a team captain, so movement will be handled elsewhere */
+ if (ent->flags & FL_TEAMSLAVE)
+ {
+ return;
+ }
+
+ if (ent->velocity[2] > 0)
+ {
+ ent->groundentity = NULL;
+ }
+
+ /* check for the groundentity going away */
+ if (ent->groundentity)
+ {
+ if (!ent->groundentity->inuse)
+ {
+ ent->groundentity = NULL;
+ }
+ }
+
+ /* if onground, return without moving */
+ if (ent->groundentity)
+ {
+ return;
+ }
+
+ VectorCopy(ent->s.origin, old_origin);
+
+ SV_CheckVelocity(ent);
+
+ /* add gravity */
+ if ((ent->movetype != MOVETYPE_FLY) &&
+ (ent->movetype != MOVETYPE_FLYMISSILE)
+ && (ent->movetype != MOVETYPE_WALLBOUNCE))
+ {
+ SV_AddGravity(ent);
+ }
+
+ /* move angles */
+ VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+
+ /* move origin */
+ VectorScale(ent->velocity, FRAMETIME, move);
+ trace = SV_PushEntity(ent, move);
+
+ if (!ent->inuse)
+ {
+ return;
+ }
+
+ if (trace.fraction < 1)
+ {
+ if (ent->movetype == MOVETYPE_WALLBOUNCE)
+ {
+ backoff = 2.0;
+ }
+ else if (ent->movetype == MOVETYPE_BOUNCE)
+ {
+ backoff = 1.5;
+ }
+ else
+ {
+ backoff = 1;
+ }
+
+ ClipVelocity(ent->velocity, trace.plane.normal, ent->velocity, backoff);
+
+ if (ent->movetype == MOVETYPE_WALLBOUNCE)
+ {
+ vectoangles(ent->velocity, ent->s.angles);
+ }
+
+ /* stop if on ground */
+ if ((trace.plane.normal[2] > 0.7) &&
+ (ent->movetype != MOVETYPE_WALLBOUNCE))
+ {
+ if ((ent->velocity[2] < 60) || (ent->movetype != MOVETYPE_BOUNCE))
+ {
+ ent->groundentity = trace.ent;
+ ent->groundentity_linkcount = trace.ent->linkcount;
+ VectorCopy(vec3_origin, ent->velocity);
+ VectorCopy(vec3_origin, ent->avelocity);
+ }
+ }
+ }
+
+ /* check for water transition */
+ wasinwater = (ent->watertype & MASK_WATER);
+ ent->watertype = gi.pointcontents(ent->s.origin);
+ isinwater = ent->watertype & MASK_WATER;
+
+ if (isinwater)
+ {
+ ent->waterlevel = 1;
+ }
+ else
+ {
+ ent->waterlevel = 0;
+ }
+
+ if (!wasinwater && isinwater)
+ {
+ /* don't play splash sound for entities already in water on level start */
+ if (level.framenum > 3)
+ {
+ gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO,
+ gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+ }
+ }
+ else if (wasinwater && !isinwater)
+ {
+ gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO,
+ gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+ }
+
+ /* move teamslaves */
+ for (slave = ent->teamchain; slave; slave = slave->teamchain)
+ {
+ VectorCopy(ent->s.origin, slave->s.origin);
+ gi.linkentity(slave);
+ }
+}
+
+/* =============================================================================== */
+
+/* STEPPING MOVEMENT */
+
+/*
+ * Monsters freefall when they don't have a ground entity, otherwise
+ * all movement is done with discrete steps.
+ *
+ * This is also used for objects that have become still on the ground, but
+ * will fall if the floor is pulled out from under them.
+ */
+
+void
+SV_AddRotationalFriction(edict_t *ent)
+{
+ int n;
+ float adjustment;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+ adjustment = FRAMETIME * sv_stopspeed * sv_friction;
+
+ for (n = 0; n < 3; n++)
+ {
+ if (ent->avelocity[n] > 0)
+ {
+ ent->avelocity[n] -= adjustment;
+
+ if (ent->avelocity[n] < 0)
+ {
+ ent->avelocity[n] = 0;
+ }
+ }
+ else
+ {
+ ent->avelocity[n] += adjustment;
+
+ if (ent->avelocity[n] > 0)
+ {
+ ent->avelocity[n] = 0;
+ }
+ }
+ }
+}
+
+void
+SV_Physics_Step(edict_t *ent)
+{
+ qboolean wasonground;
+ qboolean hitsound = false;
+ float *vel;
+ float speed, newspeed, control;
+ float friction;
+ edict_t *groundentity;
+ int mask;
+ vec3_t oldorig;
+ trace_t tr;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* airborn monsters should always check for ground */
+ if (!ent->groundentity)
+ {
+ M_CheckGround(ent);
+ }
+
+ groundentity = ent->groundentity;
+
+ SV_CheckVelocity(ent);
+
+ if (groundentity)
+ {
+ wasonground = true;
+ }
+ else
+ {
+ wasonground = false;
+ }
+
+ if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
+ {
+ SV_AddRotationalFriction(ent);
+ }
+
+ /* add gravity except:
+ - flying monsters
+ - swimming monsters who are in the water */
+ if (!wasonground)
+ {
+ if (!(ent->flags & FL_FLY))
+ {
+ if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2)))
+ {
+ if (ent->velocity[2] < sv_gravity->value * -0.1)
+ {
+ hitsound = true;
+ }
+
+ if (ent->waterlevel == 0)
+ {
+ SV_AddGravity(ent);
+ }
+ }
+ }
+ }
+
+ /* friction for flying monsters that have been given vertical velocity */
+ if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
+ {
+ speed = fabs(ent->velocity[2]);
+ control = speed < sv_stopspeed ? sv_stopspeed : speed;
+ friction = sv_friction / 3;
+ newspeed = speed - (FRAMETIME * control * friction);
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+ ent->velocity[2] *= newspeed;
+ }
+
+ /* friction for flying monsters that have been given vertical velocity */
+ if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
+ {
+ speed = fabs(ent->velocity[2]);
+ control = speed < sv_stopspeed ? sv_stopspeed : speed;
+ newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+ ent->velocity[2] *= newspeed;
+ }
+
+ if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])
+ {
+ /* let dead monsters who aren't completely onground slide */
+ if ((wasonground) || (ent->flags & (FL_SWIM | FL_FLY)))
+ {
+ if (!((ent->health <= 0.0) && !M_CheckBottom(ent)))
+ {
+ vel = ent->velocity;
+ speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]);
+
+ if (speed)
+ {
+ friction = sv_friction;
+
+ control = speed < sv_stopspeed ? sv_stopspeed : speed;
+ newspeed = speed - FRAMETIME * control * friction;
+
+ if (newspeed < 0)
+ {
+ newspeed = 0;
+ }
+
+ newspeed /= speed;
+
+ vel[0] *= newspeed;
+ vel[1] *= newspeed;
+ }
+ }
+ }
+
+ if (ent->svflags & SVF_MONSTER)
+ {
+ mask = MASK_MONSTERSOLID;
+ }
+ else
+ {
+ mask = MASK_SOLID;
+ }
+
+ VectorCopy(ent->s.origin, oldorig);
+ SV_FlyMove(ent, FRAMETIME, mask);
+
+ /* Evil hack to work around dead parasites (and maybe other monster)
+ falling through the worldmodel into the void. We copy the current
+ origin (see above) and after the SV_FlyMove() was performend we
+ checl if we're stuck in the world model. If yes we're undoing the
+ move. */
+ if (!VectorCompare(ent->s.origin, oldorig))
+ {
+ tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);
+
+ if (tr.startsolid)
+ {
+ VectorCopy(oldorig, ent->s.origin);
+ }
+ }
+
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+
+ if (!ent->inuse)
+ {
+ return;
+ }
+
+ if (ent->groundentity)
+ {
+ if (!wasonground)
+ {
+ if (hitsound)
+ {
+ gi.sound(ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
+ }
+ }
+ }
+ }
+
+ /* regular thinking */
+ SV_RunThink(ent);
+}
+
+/* ============================================================================ */
+
+void
+G_RunEntity(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->prethink)
+ {
+ ent->prethink(ent);
+ }
+
+ switch ((int)ent->movetype)
+ {
+ case MOVETYPE_PUSH:
+ case MOVETYPE_STOP:
+ SV_Physics_Pusher(ent);
+ break;
+ case MOVETYPE_NONE:
+ SV_Physics_None(ent);
+ break;
+ case MOVETYPE_NOCLIP:
+ SV_Physics_Noclip(ent);
+ break;
+ case MOVETYPE_STEP:
+ SV_Physics_Step(ent);
+ break;
+ case MOVETYPE_TOSS:
+ case MOVETYPE_BOUNCE:
+ case MOVETYPE_FLY:
+ case MOVETYPE_FLYMISSILE:
+ case MOVETYPE_WALLBOUNCE:
+ SV_Physics_Toss(ent);
+ break;
+ default:
+ gi.error("SV_Physics: bad movetype %i", (int)ent->movetype);
+ }
+}
+
diff --git a/xatrix/src/g_spawn.c b/xatrix/src/g_spawn.c
new file mode 100644
index 0000000..fd7f4ff
--- /dev/null
+++ b/xatrix/src/g_spawn.c
@@ -0,0 +1,1113 @@
+/* =======================================================================
+ *
+ * Item spawning.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+typedef struct
+{
+ char *name;
+ void (*spawn)(edict_t *ent);
+} spawn_t;
+
+void SP_item_health(edict_t *self);
+void SP_item_health_small(edict_t *self);
+void SP_item_health_large(edict_t *self);
+void SP_item_health_mega(edict_t *self);
+
+void SP_info_player_start(edict_t *ent);
+void SP_info_player_deathmatch(edict_t *ent);
+void SP_info_player_coop(edict_t *ent);
+void SP_info_player_intermission(edict_t *ent);
+
+void SP_func_plat(edict_t *ent);
+void SP_func_rotating(edict_t *ent);
+void SP_func_button(edict_t *ent);
+void SP_func_door(edict_t *ent);
+void SP_func_door_secret(edict_t *ent);
+void SP_func_door_rotating(edict_t *ent);
+void SP_func_water(edict_t *ent);
+void SP_func_train(edict_t *ent);
+void SP_func_conveyor(edict_t *self);
+void SP_func_wall(edict_t *self);
+void SP_func_object(edict_t *self);
+void SP_func_explosive(edict_t *self);
+void SP_func_timer(edict_t *self);
+void SP_func_areaportal(edict_t *ent);
+void SP_func_clock(edict_t *ent);
+void SP_func_killbox(edict_t *ent);
+
+void SP_trigger_always(edict_t *ent);
+void SP_trigger_once(edict_t *ent);
+void SP_trigger_multiple(edict_t *ent);
+void SP_trigger_relay(edict_t *ent);
+void SP_trigger_push(edict_t *ent);
+void SP_trigger_hurt(edict_t *ent);
+void SP_trigger_key(edict_t *ent);
+void SP_trigger_counter(edict_t *ent);
+void SP_trigger_elevator(edict_t *ent);
+void SP_trigger_gravity(edict_t *ent);
+void SP_trigger_monsterjump(edict_t *ent);
+
+void SP_target_temp_entity(edict_t *ent);
+void SP_target_speaker(edict_t *ent);
+void SP_target_explosion(edict_t *ent);
+void SP_target_changelevel(edict_t *ent);
+void SP_target_secret(edict_t *ent);
+void SP_target_goal(edict_t *ent);
+void SP_target_splash(edict_t *ent);
+void SP_target_spawner(edict_t *ent);
+void SP_target_blaster(edict_t *ent);
+void SP_target_crosslevel_trigger(edict_t *ent);
+void SP_target_crosslevel_target(edict_t *ent);
+void SP_target_laser(edict_t *self);
+void SP_target_help(edict_t *ent);
+void SP_target_lightramp(edict_t *self);
+void SP_target_earthquake(edict_t *ent);
+void SP_target_character(edict_t *ent);
+void SP_target_string(edict_t *ent);
+
+void SP_worldspawn(edict_t *ent);
+void SP_viewthing(edict_t *ent);
+
+void SP_light(edict_t *self);
+void SP_light_mine1(edict_t *ent);
+void SP_light_mine2(edict_t *ent);
+void SP_info_null(edict_t *self);
+void SP_info_notnull(edict_t *self);
+void SP_path_corner(edict_t *self);
+void SP_point_combat(edict_t *self);
+
+void SP_misc_explobox(edict_t *self);
+void SP_misc_banner(edict_t *self);
+void SP_misc_satellite_dish(edict_t *self);
+void SP_misc_gib_arm(edict_t *self);
+void SP_misc_gib_leg(edict_t *self);
+void SP_misc_gib_head(edict_t *self);
+void SP_misc_insane(edict_t *self);
+void SP_misc_deadsoldier(edict_t *self);
+void SP_misc_viper(edict_t *self);
+void SP_misc_viper_bomb(edict_t *self);
+void SP_misc_bigviper(edict_t *self);
+void SP_misc_strogg_ship(edict_t *self);
+void SP_misc_teleporter(edict_t *self);
+void SP_misc_teleporter_dest(edict_t *self);
+void SP_misc_blackhole(edict_t *self);
+void SP_misc_eastertank(edict_t *self);
+void SP_misc_easterchick(edict_t *self);
+void SP_misc_easterchick2(edict_t *self);
+
+void SP_monster_berserk(edict_t *self);
+void SP_monster_gladiator(edict_t *self);
+void SP_monster_gunner(edict_t *self);
+void SP_monster_infantry(edict_t *self);
+void SP_monster_soldier_light(edict_t *self);
+void SP_monster_soldier(edict_t *self);
+void SP_monster_soldier_ss(edict_t *self);
+void SP_monster_tank(edict_t *self);
+void SP_monster_medic(edict_t *self);
+void SP_monster_flipper(edict_t *self);
+void SP_monster_chick(edict_t *self);
+void SP_monster_parasite(edict_t *self);
+void SP_monster_flyer(edict_t *self);
+void SP_monster_brain(edict_t *self);
+void SP_monster_floater(edict_t *self);
+void SP_monster_hover(edict_t *self);
+void SP_monster_mutant(edict_t *self);
+void SP_monster_supertank(edict_t *self);
+void SP_monster_boss2(edict_t *self);
+void SP_monster_jorg(edict_t *self);
+void SP_monster_makron(edict_t *self);
+void SP_monster_boss3_stand(edict_t *self);
+
+void SP_monster_commander_body(edict_t *self);
+
+void SP_turret_breach(edict_t *self);
+void SP_turret_base(edict_t *self);
+void SP_turret_driver(edict_t *self);
+
+void SP_monster_soldier_hypergun(edict_t *self);
+void SP_monster_soldier_lasergun(edict_t *self);
+void SP_monster_soldier_ripper(edict_t *self);
+void SP_monster_fixbot(edict_t *self);
+void SP_monster_gekk(edict_t *self);
+void SP_monster_chick_heat(edict_t *self);
+void SP_monster_gladb(edict_t *self);
+void SP_monster_boss5(edict_t *self);
+void SP_rotating_light(edict_t *self);
+void SP_object_repair(edict_t *self);
+void SP_misc_crashviper(edict_t *ent);
+void SP_misc_viper_missile(edict_t *self);
+void SP_misc_amb4(edict_t *ent);
+void SP_target_mal_laser(edict_t *ent);
+void SP_misc_transport(edict_t *ent);
+
+void SP_misc_nuke(edict_t *ent);
+
+spawn_t spawns[] = {
+ {"item_health", SP_item_health},
+ {"item_health_small", SP_item_health_small},
+ {"item_health_large", SP_item_health_large},
+ {"item_health_mega", SP_item_health_mega},
+
+ {"info_player_start", SP_info_player_start},
+ {"info_player_deathmatch", SP_info_player_deathmatch},
+ {"info_player_coop", SP_info_player_coop},
+ {"info_player_intermission", SP_info_player_intermission},
+
+ {"func_plat", SP_func_plat},
+ {"func_button", SP_func_button},
+ {"func_door", SP_func_door},
+ {"func_door_secret", SP_func_door_secret},
+ {"func_door_rotating", SP_func_door_rotating},
+ {"func_rotating", SP_func_rotating},
+ {"func_train", SP_func_train},
+ {"func_water", SP_func_water},
+ {"func_conveyor", SP_func_conveyor},
+ {"func_areaportal", SP_func_areaportal},
+ {"func_clock", SP_func_clock},
+ {"func_wall", SP_func_wall},
+ {"func_object", SP_func_object},
+ {"func_timer", SP_func_timer},
+ {"func_explosive", SP_func_explosive},
+ {"func_killbox", SP_func_killbox},
+
+ {"func_object_repair", SP_object_repair},
+ {"rotating_light", SP_rotating_light},
+
+ {"trigger_always", SP_trigger_always},
+ {"trigger_once", SP_trigger_once},
+ {"trigger_multiple", SP_trigger_multiple},
+ {"trigger_relay", SP_trigger_relay},
+ {"trigger_push", SP_trigger_push},
+ {"trigger_hurt", SP_trigger_hurt},
+ {"trigger_key", SP_trigger_key},
+ {"trigger_counter", SP_trigger_counter},
+ {"trigger_elevator", SP_trigger_elevator},
+ {"trigger_gravity", SP_trigger_gravity},
+ {"trigger_monsterjump", SP_trigger_monsterjump},
+
+ {"target_temp_entity", SP_target_temp_entity},
+ {"target_speaker", SP_target_speaker},
+ {"target_explosion", SP_target_explosion},
+ {"target_changelevel", SP_target_changelevel},
+ {"target_secret", SP_target_secret},
+ {"target_goal", SP_target_goal},
+ {"target_splash", SP_target_splash},
+ {"target_spawner", SP_target_spawner},
+ {"target_blaster", SP_target_blaster},
+ {"target_crosslevel_trigger", SP_target_crosslevel_trigger},
+ {"target_crosslevel_target", SP_target_crosslevel_target},
+ {"target_laser", SP_target_laser},
+ {"target_help", SP_target_help},
+ {"target_lightramp", SP_target_lightramp},
+ {"target_earthquake", SP_target_earthquake},
+ {"target_character", SP_target_character},
+ {"target_string", SP_target_string},
+ {"target_mal_laser", SP_target_mal_laser},
+
+ {"worldspawn", SP_worldspawn},
+ {"viewthing", SP_viewthing},
+
+ {"light", SP_light},
+ {"light_mine1", SP_light_mine1},
+ {"light_mine2", SP_light_mine2},
+ {"info_null", SP_info_null},
+ {"func_group", SP_info_null},
+ {"info_notnull", SP_info_notnull},
+ {"path_corner", SP_path_corner},
+ {"point_combat", SP_point_combat},
+
+ {"misc_explobox", SP_misc_explobox},
+ {"misc_banner", SP_misc_banner},
+ {"misc_satellite_dish", SP_misc_satellite_dish},
+ {"misc_gib_arm", SP_misc_gib_arm},
+ {"misc_gib_leg", SP_misc_gib_leg},
+ {"misc_gib_head", SP_misc_gib_head},
+ {"misc_insane", SP_misc_insane},
+ {"misc_deadsoldier", SP_misc_deadsoldier},
+ {"misc_viper", SP_misc_viper},
+ {"misc_viper_bomb", SP_misc_viper_bomb},
+ {"misc_bigviper", SP_misc_bigviper},
+ {"misc_strogg_ship", SP_misc_strogg_ship},
+ {"misc_teleporter", SP_misc_teleporter},
+ {"misc_teleporter_dest", SP_misc_teleporter_dest},
+ {"misc_blackhole", SP_misc_blackhole},
+ {"misc_eastertank", SP_misc_eastertank},
+ {"misc_easterchick", SP_misc_easterchick},
+ {"misc_easterchick2", SP_misc_easterchick2},
+ {"misc_crashviper", SP_misc_crashviper},
+ {"misc_viper_missile", SP_misc_viper_missile},
+ {"misc_amb4", SP_misc_amb4},
+ {"misc_transport", SP_misc_transport},
+ {"misc_nuke", SP_misc_nuke},
+
+ {"monster_berserk", SP_monster_berserk},
+ {"monster_gladiator", SP_monster_gladiator},
+ {"monster_gunner", SP_monster_gunner},
+ {"monster_infantry", SP_monster_infantry},
+ {"monster_soldier_light", SP_monster_soldier_light},
+ {"monster_soldier", SP_monster_soldier},
+ {"monster_soldier_ss", SP_monster_soldier_ss},
+ {"monster_tank", SP_monster_tank},
+ {"monster_tank_commander", SP_monster_tank},
+ {"monster_medic", SP_monster_medic},
+ {"monster_flipper", SP_monster_flipper},
+ {"monster_chick", SP_monster_chick},
+ {"monster_parasite", SP_monster_parasite},
+ {"monster_flyer", SP_monster_flyer},
+ {"monster_brain", SP_monster_brain},
+ {"monster_floater", SP_monster_floater},
+ {"monster_hover", SP_monster_hover},
+ {"monster_mutant", SP_monster_mutant},
+ {"monster_supertank", SP_monster_supertank},
+ {"monster_boss2", SP_monster_boss2},
+ {"monster_boss3_stand", SP_monster_boss3_stand},
+ {"monster_makron", SP_monster_makron},
+ {"monster_jorg", SP_monster_jorg},
+ {"monster_commander_body", SP_monster_commander_body},
+ {"monster_soldier_hypergun", SP_monster_soldier_hypergun},
+ {"monster_soldier_lasergun", SP_monster_soldier_lasergun},
+ {"monster_soldier_ripper", SP_monster_soldier_ripper},
+ {"monster_fixbot", SP_monster_fixbot},
+ {"monster_gekk", SP_monster_gekk},
+ {"monster_chick_heat", SP_monster_chick_heat},
+ {"monster_gladb", SP_monster_gladb},
+ {"monster_boss5", SP_monster_boss5},
+
+ {"turret_breach", SP_turret_breach},
+ {"turret_base", SP_turret_base},
+ {"turret_driver", SP_turret_driver},
+
+ {NULL, NULL}
+};
+
+qboolean Spawn_CheckCoop_MapHacks (edict_t *ent)
+{
+ if(!coop->value || !ent)
+ {
+ return false;
+ }
+
+ if(!Q_stricmp(level.mapname, "xsewer1"))
+ {
+ if(ent->classname && !Q_stricmp(ent->classname, "trigger_relay") && ent->target && !Q_stricmp(ent->target, "t3") && ent->targetname && !Q_stricmp(ent->targetname, "t2"))
+ {
+ return true;
+ }
+ if(ent->classname && !Q_stricmp(ent->classname, "func_button") && ent->target && !Q_stricmp(ent->target, "t16") && ent->model && !Q_stricmp(ent->model, "*71"))
+ {
+ ent->message = "Overflow valve maintenance\nhatch A opened.";
+ return false;
+ }
+
+ if(ent->classname && !Q_stricmp(ent->classname, "trigger_once") && ent->model && !Q_stricmp(ent->model, "*3"))
+ {
+ ent->message = "Overflow valve maintenance\nhatch B opened.";
+ return false;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Finds the spawn function for
+ * the entity and calls it
+ */
+void
+ED_CallSpawn(edict_t *ent)
+{
+ spawn_t *s;
+ gitem_t *item;
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->classname)
+ {
+ gi.dprintf("ED_CallSpawn: NULL classname\n");
+ return;
+ }
+
+ /* check item spawn functions */
+ for (i = 0, item = itemlist; i < game.num_items; i++, item++)
+ {
+ if (!item->classname)
+ {
+ continue;
+ }
+
+ if (!strcmp(item->classname, ent->classname))
+ {
+ /* found it */
+ SpawnItem(ent, item);
+ return;
+ }
+ }
+
+ /* check normal spawn functions */
+ for (s = spawns; s->name; s++)
+ {
+ if (!strcmp(s->name, ent->classname))
+ {
+ /* found it */
+ s->spawn(ent);
+ return;
+ }
+ }
+
+ gi.dprintf("%s doesn't have a spawn function\n", ent->classname);
+}
+
+char *
+ED_NewString(const char *string)
+{
+ char *newb, *new_p;
+ int i, l;
+
+ if (!string)
+ {
+ return NULL;
+ }
+
+ l = strlen(string) + 1;
+
+ newb = gi.TagMalloc(l, TAG_LEVEL);
+
+ new_p = newb;
+
+ for (i = 0; i < l; i++)
+ {
+ if ((string[i] == '\\') && (i < l - 1))
+ {
+ i++;
+
+ if (string[i] == 'n')
+ {
+ *new_p++ = '\n';
+ }
+ else
+ {
+ *new_p++ = '\\';
+ }
+ }
+ else
+ {
+ *new_p++ = string[i];
+ }
+ }
+
+ return newb;
+}
+
+/*
+ * Takes a key/value pair and sets
+ * the binary values in an edict
+ */
+void
+ED_ParseField(const char *key, const char *value, edict_t *ent)
+{
+ field_t *f;
+ byte *b;
+ float v;
+ vec3_t vec;
+
+ if (!key || !value)
+ {
+ return;
+ }
+
+ for (f = fields; f->name; f++)
+ {
+ if (!(f->flags & FFL_NOSPAWN) && !Q_strcasecmp(f->name, (char *)key))
+ {
+ /* found it */
+ if (f->flags & FFL_SPAWNTEMP)
+ {
+ b = (byte *)&st;
+ }
+ else
+ {
+ b = (byte *)ent;
+ }
+
+ switch (f->type)
+ {
+ case F_LSTRING:
+ *(char **)(b + f->ofs) = ED_NewString(value);
+ break;
+ case F_VECTOR:
+ sscanf(value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
+ ((float *)(b + f->ofs))[0] = vec[0];
+ ((float *)(b + f->ofs))[1] = vec[1];
+ ((float *)(b + f->ofs))[2] = vec[2];
+ break;
+ case F_INT:
+ *(int *)(b + f->ofs) = (int)strtol(value, (char **)NULL, 10);
+ break;
+ case F_FLOAT:
+ *(float *)(b + f->ofs) = (float)strtod(value, (char **)NULL);
+ break;
+ case F_ANGLEHACK:
+ v = (float)strtod(value, (char **)NULL);
+ ((float *)(b + f->ofs))[0] = 0;
+ ((float *)(b + f->ofs))[1] = v;
+ ((float *)(b + f->ofs))[2] = 0;
+ break;
+ case F_IGNORE:
+ break;
+ default:
+ break;
+ }
+
+ return;
+ }
+ }
+
+ gi.dprintf("%s is not a field\n", key);
+}
+
+/*
+ * Parses an edict out of the given string,
+ * returning the new position. ed should be
+ * a properly initialized empty edict.
+ */
+char *
+ED_ParseEdict(char *data, edict_t *ent)
+{
+ qboolean init;
+ char keyname[256];
+ const char *com_token;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ init = false;
+ memset(&st, 0, sizeof(st));
+
+ /* go through all the dictionary pairs */
+ while (1)
+ {
+ /* parse key */
+ com_token = COM_Parse(&data);
+
+ if (com_token[0] == '}')
+ {
+ break;
+ }
+
+ if (!data)
+ {
+ gi.error("ED_ParseEntity: EOF without closing brace");
+ }
+
+ strncpy(keyname, com_token, sizeof(keyname) - 1);
+
+ /* parse value */
+ com_token = COM_Parse(&data);
+
+ if (!data)
+ {
+ gi.error("ED_ParseEntity: EOF without closing brace");
+ }
+
+ if (com_token[0] == '}')
+ {
+ gi.error("ED_ParseEntity: closing brace without data");
+ }
+
+ init = true;
+
+ /* keynames with a leading underscore are
+ used for utility comments, and are
+ immediately discarded by quake */
+ if (keyname[0] == '_')
+ {
+ continue;
+ }
+
+ ED_ParseField(keyname, com_token, ent);
+ }
+
+ if (!init)
+ {
+ memset(ent, 0, sizeof(*ent));
+ }
+
+ return data;
+}
+
+/*
+ * Chain together all entities with a matching team field.
+ *
+ * All but the first will have the FL_TEAMSLAVE flag set.
+ * All but the last will have the teamchain field set to the next one
+ */
+void
+G_FindTeams(void)
+{
+ edict_t *e, *e2, *chain;
+ int i, j;
+ int c, c2;
+
+ c = 0;
+ c2 = 0;
+
+ for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
+ {
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->team)
+ {
+ continue;
+ }
+
+ if (e->flags & FL_TEAMSLAVE)
+ {
+ continue;
+ }
+
+ chain = e;
+ e->teammaster = e;
+ c++;
+ c2++;
+
+ for (j = i + 1, e2 = e + 1; j < globals.num_edicts; j++, e2++)
+ {
+ if (!e2->inuse)
+ {
+ continue;
+ }
+
+ if (!e2->team)
+ {
+ continue;
+ }
+
+ if (e2->flags & FL_TEAMSLAVE)
+ {
+ continue;
+ }
+
+ if (!strcmp(e->team, e2->team))
+ {
+ c2++;
+ chain->teamchain = e2;
+ e2->teammaster = e;
+ chain = e2;
+ e2->flags |= FL_TEAMSLAVE;
+ }
+ }
+ }
+
+ gi.dprintf("%i teams with %i entities.\n", c, c2);
+}
+
+/*
+ * Creates a server's entity / program execution context by
+ * parsing textual entity definitions out of an ent file.
+ */
+void
+SpawnEntities(const char *mapname, char *entities, const char *spawnpoint)
+{
+ edict_t *ent;
+ int inhibit;
+ const char *com_token;
+ int i;
+ float skill_level;
+
+ if (!mapname || !entities || !spawnpoint)
+ {
+ return;
+ }
+
+ skill_level = floor(skill->value);
+
+ if (skill_level < 0)
+ {
+ skill_level = 0;
+ }
+
+ if (skill_level > 3)
+ {
+ skill_level = 3;
+ }
+
+ if (skill->value != skill_level)
+ {
+ gi.cvar_forceset("skill", va("%f", skill_level));
+ }
+
+ SaveClientData();
+
+ gi.FreeTags(TAG_LEVEL);
+
+ memset(&level, 0, sizeof(level));
+ memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0]));
+
+ strncpy(level.mapname, mapname, sizeof(level.mapname) - 1);
+ strncpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint) - 1);
+
+ /* set client fields on player ents */
+ for (i = 0; i < game.maxclients; i++)
+ {
+ g_edicts[i + 1].client = game.clients + i;
+ }
+
+ ent = NULL;
+ inhibit = 0;
+
+ /* parse ents */
+ while (1)
+ {
+ /* parse the opening brace */
+ com_token = COM_Parse(&entities);
+
+ if (!entities)
+ {
+ break;
+ }
+
+ if (com_token[0] != '{')
+ {
+ gi.error("ED_LoadFromFile: found %s when expecting {", com_token);
+ }
+
+ if (!ent)
+ {
+ ent = g_edicts;
+ }
+ else
+ {
+ ent = G_Spawn();
+ }
+
+ entities = ED_ParseEdict(entities, ent);
+
+ /* yet another map hack */
+ if (!Q_stricmp(level.mapname, "command") &&
+ !Q_stricmp(ent->classname, "trigger_once") &&
+ !Q_stricmp(ent->model, "*27"))
+ {
+ ent->spawnflags &= ~SPAWNFLAG_NOT_HARD;
+ }
+
+ /* remove things (except the world) from
+ different skill levels or deathmatch */
+ if (ent != g_edicts)
+ {
+ if (deathmatch->value)
+ {
+ if (ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH)
+ {
+ G_FreeEdict(ent);
+ inhibit++;
+ continue;
+ }
+ }
+ else
+ {
+ if (Spawn_CheckCoop_MapHacks(ent) || (
+ ((skill->value == SKILL_EASY) &&
+ (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
+ ((skill->value == SKILL_MEDIUM) &&
+ (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
+ (((skill->value == SKILL_HARD) ||
+ (skill->value == SKILL_HARDPLUS)) &&
+ (ent->spawnflags & SPAWNFLAG_NOT_HARD)))
+ )
+ {
+ G_FreeEdict(ent);
+ inhibit++;
+ continue;
+ }
+ }
+
+ ent->spawnflags &=
+ ~(SPAWNFLAG_NOT_EASY | SPAWNFLAG_NOT_MEDIUM |
+ SPAWNFLAG_NOT_HARD |
+ SPAWNFLAG_NOT_COOP | SPAWNFLAG_NOT_DEATHMATCH);
+ }
+
+ ED_CallSpawn(ent);
+ }
+
+ gi.dprintf("%i entities inhibited.\n", inhibit);
+
+ G_FindTeams();
+
+ PlayerTrail_Init();
+}
+
+/* =================================================================== */
+
+char *single_statusbar =
+ "yb -24 "
+
+/* health */
+ "xv 0 "
+ "hnum "
+ "xv 50 "
+ "pic 0 "
+
+/* ammo */
+ "if 2 "
+ " xv 100 "
+ " anum "
+ " xv 150 "
+ " pic 2 "
+ "endif "
+
+/* armor */
+ "if 4 "
+ " xv 200 "
+ " rnum "
+ " xv 250 "
+ " pic 4 "
+ "endif "
+
+/* selected item */
+ "if 6 "
+ " xv 296 "
+ " pic 6 "
+ "endif "
+
+ "yb -50 "
+
+/* picked up item */
+ "if 7 "
+ " xv 0 "
+ " pic 7 "
+ " xv 26 "
+ " yb -42 "
+ " stat_string 8 "
+ " yb -50 "
+ "endif "
+
+/* timer */
+ "if 9 "
+ " xv 262 "
+ " num 2 10 "
+ " xv 296 "
+ " pic 9 "
+ "endif "
+
+/* help / weapon icon */
+ "if 11 "
+ " xv 148 "
+ " pic 11 "
+ "endif "
+;
+
+char *dm_statusbar =
+ "yb -24 "
+
+/* health */
+ "xv 0 "
+ "hnum "
+ "xv 50 "
+ "pic 0 "
+
+/* ammo */
+ "if 2 "
+ " xv 100 "
+ " anum "
+ " xv 150 "
+ " pic 2 "
+ "endif "
+
+/* armor */
+ "if 4 "
+ " xv 200 "
+ " rnum "
+ " xv 250 "
+ " pic 4 "
+ "endif "
+
+/* selected item */
+ "if 6 "
+ " xv 296 "
+ " pic 6 "
+ "endif "
+
+ "yb -50 "
+
+/* picked up item */
+ "if 7 "
+ " xv 0 "
+ " pic 7 "
+ " xv 26 "
+ " yb -42 "
+ " stat_string 8 "
+ " yb -50 "
+ "endif "
+
+/* timer */
+ "if 9 "
+ " xv 246 "
+ " num 2 10 "
+ " xv 296 "
+ " pic 9 "
+ "endif "
+
+/* help / weapon icon */
+ "if 11 "
+ " xv 148 "
+ " pic 11 "
+ "endif "
+
+/* frags */
+ "xr -50 "
+ "yt 2 "
+ "num 3 14 "
+
+/* spectator */
+ "if 17 "
+ "xv 0 "
+ "yb -58 "
+ "string2 \"SPECTATOR MODE\" "
+ "endif "
+
+/* chase camera */
+ "if 16 "
+ "xv 0 "
+ "yb -68 "
+ "string \"Chasing\" "
+ "xv 64 "
+ "stat_string 16 "
+ "endif "
+;
+
+/*QUAKED worldspawn (0 0 0) ?
+ *
+ * Only used for the world.
+ * "sky" environment map name
+ * "skyaxis" vector axis for rotating sky
+ * "skyrotate" speed of rotation in degrees/second
+ * "sounds" music cd track number
+ * "gravity" 800 is default gravity
+ * "message" text to print at user logon
+ */
+void
+SP_worldspawn(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->movetype = MOVETYPE_PUSH;
+ ent->solid = SOLID_BSP;
+ ent->inuse = true; /* since the world doesn't use G_Spawn() */
+ ent->s.modelindex = 1; /* world model is always index 1 */
+
+ /* reserve some spots for dead player
+ bodies for coop / deathmatch */
+ InitBodyQue();
+
+ /* set configstrings for items */
+ SetItemNames();
+
+ if (st.nextmap)
+ {
+ strcpy(level.nextmap, st.nextmap);
+ }
+
+ /* make some data visible to the server */
+ if (ent->message && ent->message[0])
+ {
+ gi.configstring(CS_NAME, ent->message);
+ Q_strlcpy(level.level_name, ent->message, sizeof(level.level_name));
+ }
+ else
+ {
+ Q_strlcpy(level.level_name, level.mapname, sizeof(level.level_name));
+ }
+
+ if (st.sky && st.sky[0])
+ {
+ gi.configstring(CS_SKY, st.sky);
+ }
+ else
+ {
+ gi.configstring(CS_SKY, "unit1_");
+ }
+
+ gi.configstring(CS_SKYROTATE, va("%f", st.skyrotate));
+
+ gi.configstring(CS_SKYAXIS, va("%f %f %f", st.skyaxis[0],
+ st.skyaxis[1], st.skyaxis[2]));
+
+ gi.configstring(CS_CDTRACK, va("%i", ent->sounds));
+
+ gi.configstring(CS_MAXCLIENTS, va("%i", (int)(maxclients->value)));
+
+ /* status bar program */
+ if (deathmatch->value)
+ {
+ gi.configstring(CS_STATUSBAR, dm_statusbar);
+ }
+ else
+ {
+ gi.configstring(CS_STATUSBAR, single_statusbar);
+ }
+
+ /* help icon for statusbar */
+ gi.imageindex("i_help");
+ level.pic_health = gi.imageindex("i_health");
+ gi.imageindex("help");
+ gi.imageindex("field_3");
+
+ if (!st.gravity)
+ {
+ gi.cvar_set("sv_gravity", "800");
+ }
+ else
+ {
+ gi.cvar_set("sv_gravity", st.gravity);
+ }
+
+ snd_fry = gi.soundindex("player/fry.wav"); /* standing in lava / slime */
+
+ PrecacheItem(FindItem("Blaster"));
+
+ gi.soundindex("player/lava1.wav");
+ gi.soundindex("player/lava2.wav");
+
+ gi.soundindex("misc/pc_up.wav");
+ gi.soundindex("misc/talk1.wav");
+
+ gi.soundindex("misc/udeath.wav");
+
+ /* gibs */
+ gi.soundindex("items/respawn1.wav");
+
+ /* sexed sounds */
+ gi.soundindex("*death1.wav");
+ gi.soundindex("*death2.wav");
+ gi.soundindex("*death3.wav");
+ gi.soundindex("*death4.wav");
+ gi.soundindex("*fall1.wav");
+ gi.soundindex("*fall2.wav");
+ gi.soundindex("*gurp1.wav"); /* drowning damage */
+ gi.soundindex("*gurp2.wav");
+ gi.soundindex("*jump1.wav"); /* player jump */
+ gi.soundindex("*pain25_1.wav");
+ gi.soundindex("*pain25_2.wav");
+ gi.soundindex("*pain50_1.wav");
+ gi.soundindex("*pain50_2.wav");
+ gi.soundindex("*pain75_1.wav");
+ gi.soundindex("*pain75_2.wav");
+ gi.soundindex("*pain100_1.wav");
+ gi.soundindex("*pain100_2.wav");
+
+ /* sexed models. you can add more, max 19
+ THIS ORDER MUST MATCH THE DEFINES IN g_local.h
+ these models are only loaded in coop or deathmatch.
+ not singleplayer. */
+ if (coop->value || deathmatch->value)
+ {
+ gi.modelindex("#w_blaster.md2");
+ gi.modelindex("#w_shotgun.md2");
+ gi.modelindex("#w_sshotgun.md2");
+ gi.modelindex("#w_machinegun.md2");
+ gi.modelindex("#w_chaingun.md2");
+ gi.modelindex("#a_grenades.md2");
+ gi.modelindex("#w_glauncher.md2");
+ gi.modelindex("#w_rlauncher.md2");
+ gi.modelindex("#w_hyperblaster.md2");
+ gi.modelindex("#w_railgun.md2");
+ gi.modelindex("#w_bfg.md2");
+
+ gi.modelindex("#w_phalanx.md2");
+ gi.modelindex("#w_ripper.md2");
+ }
+
+ /* ------------------- */
+
+ gi.soundindex("player/gasp1.wav"); /* gasping for air */
+ gi.soundindex("player/gasp2.wav"); /* head breaking surface, not gasping */
+
+ gi.soundindex("player/watr_in.wav"); /* feet hitting water */
+ gi.soundindex("player/watr_out.wav"); /* feet leaving water */
+
+ gi.soundindex("player/watr_un.wav"); /* head going underwater */
+
+ gi.soundindex("player/u_breath1.wav");
+ gi.soundindex("player/u_breath2.wav");
+
+ gi.soundindex("items/pkup.wav"); /* bonus item pickup */
+ gi.soundindex("world/land.wav"); /* landing thud */
+ gi.soundindex("misc/h2ohit1.wav"); /* landing splash */
+
+ gi.soundindex("items/damage.wav");
+ gi.soundindex("items/protect.wav");
+ gi.soundindex("items/protect4.wav");
+ gi.soundindex("weapons/noammo.wav");
+
+ gi.soundindex("infantry/inflies1.wav");
+
+ sm_meat_index = gi.modelindex("models/objects/gibs/sm_meat/tris.md2");
+ gi.modelindex("models/objects/gibs/arm/tris.md2");
+ gi.modelindex("models/objects/gibs/bone/tris.md2");
+ gi.modelindex("models/objects/gibs/bone2/tris.md2");
+ gi.modelindex("models/objects/gibs/chest/tris.md2");
+ gi.modelindex("models/objects/gibs/skull/tris.md2");
+ gi.modelindex("models/objects/gibs/head2/tris.md2");
+
+ /* Setup light animation tables. 'a' is total darkness, 'z' is doublebright. */
+
+ /* 0 normal */
+ gi.configstring(CS_LIGHTS + 0, "m");
+
+ /* 1 FLICKER (first variety) */
+ gi.configstring(CS_LIGHTS + 1, "mmnmmommommnonmmonqnmmo");
+
+ /* 2 SLOW STRONG PULSE */
+ gi.configstring(CS_LIGHTS + 2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
+
+ /* 3 CANDLE (first variety) */
+ gi.configstring(CS_LIGHTS + 3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
+
+ /* 4 FAST STROBE */
+ gi.configstring(CS_LIGHTS + 4, "mamamamamama");
+
+ /* 5 GENTLE PULSE 1 */
+ gi.configstring(CS_LIGHTS + 5, "jklmnopqrstuvwxyzyxwvutsrqponmlkj");
+
+ /* 6 FLICKER (second variety) */
+ gi.configstring(CS_LIGHTS + 6, "nmonqnmomnmomomno");
+
+ /* 7 CANDLE (second variety) */
+ gi.configstring(CS_LIGHTS + 7, "mmmaaaabcdefgmmmmaaaammmaamm");
+
+ /* 8 CANDLE (third variety) */
+ gi.configstring(CS_LIGHTS + 8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
+
+ /* 9 SLOW STROBE (fourth variety) */
+ gi.configstring(CS_LIGHTS + 9, "aaaaaaaazzzzzzzz");
+
+ /* 10 FLUORESCENT FLICKER */
+ gi.configstring(CS_LIGHTS + 10, "mmamammmmammamamaaamammma");
+
+ /* 11 SLOW PULSE NOT FADE TO BLACK */
+ gi.configstring(CS_LIGHTS + 11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
+
+ /* styles 32-62 are assigned by the light program for switchable lights */
+
+ /* 63 testing */
+ gi.configstring(CS_LIGHTS + 63, "a");
+}
+
diff --git a/xatrix/src/g_svcmds.c b/xatrix/src/g_svcmds.c
new file mode 100644
index 0000000..3241ce3
--- /dev/null
+++ b/xatrix/src/g_svcmds.c
@@ -0,0 +1,337 @@
+/* =======================================================================
+ *
+ * Game side of server CMDs. At this time only the ipfilter.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define MAX_IPFILTERS 1024
+
+void
+Svcmd_Test_f(void)
+{
+ gi.cprintf(NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
+}
+
+/*
+ * ==============================================================================
+ *
+ * PACKET FILTERING
+ *
+ *
+ * You can add or remove addresses from the filter list with:
+ *
+ * addip <ip>
+ * removeip <ip>
+ *
+ * The ip address is specified in dot format, and any unspecified
+ * digits will match any value, so you can specify an entire class
+ * C network with "addip 192.246.40".
+ *
+ * Removeip will only remove an address specified exactly the same
+ * way. You cannot addip a subnet, then removeip a single host.
+ *
+ * listip
+ * Prints the current list of filters.
+ *
+ * writeip
+ * Dumps "addip <ip>" commands to listip.cfg so it can be execed
+ * at a later date. The filter lists are not saved and restored
+ * by default, because I belive it would cause too much confusion.
+ *
+ * filterban <0 or 1>
+ * If 1 (the default), then ip addresses matching the current list
+ * will be prohibited from entering the game.This is the default
+ * setting.
+ * If 0, then only addresses matching the list will be allowed.
+ * This lets you easily set up a private game, or a game that only
+ * allows players from your local network.
+ *
+ * ==============================================================================
+ */
+
+typedef struct
+{
+ unsigned mask;
+ unsigned compare;
+} ipfilter_t;
+
+ipfilter_t ipfilters[MAX_IPFILTERS];
+int numipfilters;
+
+qboolean
+StringToFilter(char *s, ipfilter_t *f)
+{
+ char num[128];
+ int i, j;
+ byte b[4];
+ byte m[4];
+
+ if (!s || !f)
+ {
+ return false;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ b[i] = 0;
+ m[i] = 0;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ if ((*s < '0') || (*s > '9'))
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s);
+ return false;
+ }
+
+ j = 0;
+
+ while (*s >= '0' && *s <= '9')
+ {
+ num[j++] = *s++;
+ }
+
+ num[j] = 0;
+ b[i] = atoi(num);
+
+ if (b[i] != 0)
+ {
+ m[i] = 255;
+ }
+
+ if (!*s)
+ {
+ break;
+ }
+
+ s++;
+ }
+
+ /* PVS NOTE: maybe use memcpy here instead? */
+ f->mask = *(unsigned *)m;
+ f->compare = *(unsigned *)b;
+
+ return true;
+}
+
+qboolean
+SV_FilterPacket(char *from)
+{
+ int i;
+ unsigned in;
+ byte m[4];
+ char *p;
+
+ if (!from)
+ {
+ return false;
+ }
+
+ i = 0;
+ p = from;
+
+ while (*p && i < 4)
+ {
+ m[i] = 0;
+
+ while (*p >= '0' && *p <= '9')
+ {
+ m[i] = m[i] * 10 + (*p - '0');
+ p++;
+ }
+
+ if (!*p || (*p == ':'))
+ {
+ break;
+ }
+
+ i++, p++;
+ }
+
+ /* PVS NOTE: maybe use memcpy instead? */
+ in = *(unsigned *)m;
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ if ((in & ipfilters[i].mask) == ipfilters[i].compare)
+ {
+ return (int)filterban->value;
+ }
+ }
+
+ return (int)!filterban->value;
+}
+
+void
+SVCmd_AddIP_f(void)
+{
+ int i;
+
+ if (gi.argc() < 3)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Usage: addip <ip-mask>\n");
+ return;
+ }
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ if (ipfilters[i].compare == 0xffffffff)
+ {
+ break; /* free spot */
+ }
+ }
+
+ if (i == numipfilters)
+ {
+ if (numipfilters == MAX_IPFILTERS)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "IP filter list is full\n");
+ return;
+ }
+
+ numipfilters++;
+ }
+
+ if (!StringToFilter(gi.argv(2), &ipfilters[i]))
+ {
+ ipfilters[i].compare = 0xffffffff;
+ }
+}
+
+void
+SVCmd_RemoveIP_f(void)
+{
+ ipfilter_t f;
+ int i, j;
+
+ if (gi.argc() < 3)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Usage: sv removeip <ip-mask>\n");
+ return;
+ }
+
+ if (!StringToFilter(gi.argv(2), &f))
+ {
+ return;
+ }
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ if ((ipfilters[i].mask == f.mask) &&
+ (ipfilters[i].compare == f.compare))
+ {
+ for (j = i + 1; j < numipfilters; j++)
+ {
+ ipfilters[j - 1] = ipfilters[j];
+ }
+
+ numipfilters--;
+ gi.cprintf(NULL, PRINT_HIGH, "Removed.\n");
+ return;
+ }
+ }
+
+ gi.cprintf(NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv(2));
+}
+
+void
+SVCmd_ListIP_f(void)
+{
+ int i;
+ byte b[4];
+
+ gi.cprintf(NULL, PRINT_HIGH, "Filter list:\n");
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ /* PVS NOTE: maybe use memcpy instead? */
+ *(unsigned *)b = ipfilters[i].compare;
+ gi.cprintf(NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n",
+ b[0], b[1], b[2], b[3]);
+ }
+}
+
+void
+SVCmd_WriteIP_f(void)
+{
+ FILE *f;
+ char name[MAX_OSPATH];
+ byte b[4];
+ int i;
+ cvar_t *game;
+
+ game = gi.cvar("game", "", 0);
+
+ if (!*game->string)
+ {
+ sprintf(name, "%s/listip.cfg", GAMEVERSION);
+ }
+ else
+ {
+ sprintf(name, "%s/listip.cfg", game->string);
+ }
+
+ gi.cprintf(NULL, PRINT_HIGH, "Writing %s.\n", name);
+
+ f = fopen(name, "wb");
+
+ if (!f)
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Couldn't open %s\n", name);
+ return;
+ }
+
+ fprintf(f, "set filterban %d\n", (int)filterban->value);
+
+ for (i = 0; i < numipfilters; i++)
+ {
+ /* PVS NOTE: maybe use memcpy instead? */
+ *(unsigned *)b = ipfilters[i].compare;
+ fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
+ }
+
+ fclose(f);
+}
+
+/*
+ * ServerCommand will be called when an "sv" command is issued.
+ * The game can issue gi.argc() / gi.argv() commands to get the rest
+ * of the parameters
+ */
+void
+ServerCommand(void)
+{
+ char *cmd;
+
+ cmd = gi.argv(1);
+
+ if (Q_stricmp(cmd, "test") == 0)
+ {
+ Svcmd_Test_f();
+ }
+ else if (Q_stricmp(cmd, "addip") == 0)
+ {
+ SVCmd_AddIP_f();
+ }
+ else if (Q_stricmp(cmd, "removeip") == 0)
+ {
+ SVCmd_RemoveIP_f();
+ }
+ else if (Q_stricmp(cmd, "listip") == 0)
+ {
+ SVCmd_ListIP_f();
+ }
+ else if (Q_stricmp(cmd, "writeip") == 0)
+ {
+ SVCmd_WriteIP_f();
+ }
+ else
+ {
+ gi.cprintf(NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
+ }
+}
+
diff --git a/xatrix/src/g_target.c b/xatrix/src/g_target.c
new file mode 100644
index 0000000..a591210
--- /dev/null
+++ b/xatrix/src/g_target.c
@@ -0,0 +1,1329 @@
+/* =======================================================================
+ *
+ * Targets.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+/* QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
+ * Fire an origin based temp entity event to the clients.
+ *
+ * "style" type byte
+ */
+void
+Use_Target_Tent(edict_t *ent, edict_t *other /* unused */ , edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(ent->style);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+}
+
+void
+SP_target_temp_entity(edict_t *ent)
+{
+ ent->use = Use_Target_Tent;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
+ *
+ * "noise" wav file to play
+ *
+ * "attenuation"
+ * -1 = none, send to whole level
+ * 1 = normal fighting sounds
+ * 2 = idle sound level
+ * 3 = ambient sound level
+ *
+ * "volume" 0.0 to 1.0
+ *
+ * Normal sounds play each time the target is used.
+ * The reliable flag can be set for crucial voiceovers.
+ *
+ * Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
+ * Multiple identical looping sounds will just increase volume without any speed cost.
+ */
+void
+Use_Target_Speaker(edict_t *ent, edict_t *other /* unused */ , edict_t *activator /* unused */)
+{
+ int chan;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->spawnflags & 3)
+ {
+ /* looping sound toggles */
+ if (ent->s.sound)
+ {
+ ent->s.sound = 0; /* turn it off */
+ }
+ else
+ {
+ ent->s.sound = ent->noise_index; /* start it */
+ }
+ }
+ else
+ {
+ /* normal sound */
+ if (ent->spawnflags & 4)
+ {
+ chan = CHAN_VOICE | CHAN_RELIABLE;
+ }
+ else
+ {
+ chan = CHAN_VOICE;
+ }
+
+ /* use a positioned_sound, because this entity won't
+ normally be sent to any clients because it is invisible */
+ gi.positioned_sound(ent->s.origin, ent, chan, ent->noise_index,
+ ent->volume, ent->attenuation, 0);
+ }
+}
+
+void
+SP_target_speaker(edict_t *ent)
+{
+ char buffer[MAX_QPATH];
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!st.noise)
+ {
+ gi.dprintf("target_speaker with no noise set at %s\n",
+ vtos(ent->s.origin));
+ return;
+ }
+
+ if (!strstr(st.noise, ".wav"))
+ {
+ Com_sprintf(buffer, sizeof(buffer), "%s.wav", st.noise);
+ }
+ else
+ {
+ Q_strlcpy(buffer, st.noise, sizeof(buffer));
+ }
+
+ ent->noise_index = gi.soundindex(buffer);
+
+ if (!ent->volume)
+ {
+ ent->volume = 1.0;
+ }
+
+ if (!ent->attenuation)
+ {
+ ent->attenuation = 1.0;
+ }
+ else if (ent->attenuation == -1) /* use -1 so 0 defaults to 1 */
+ {
+ ent->attenuation = 0;
+ }
+
+ /* check for prestarted looping sound */
+ if (ent->spawnflags & 1)
+ {
+ ent->s.sound = ent->noise_index;
+ }
+
+ ent->use = Use_Target_Speaker;
+
+ /* must link the entity so we get areas and clusters so
+ the server can determine who to send updates to */
+ gi.linkentity(ent);
+}
+
+/* ========================================================== */
+
+void
+Use_Target_Help(edict_t *ent, edict_t *other, edict_t *activator)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->spawnflags & 1)
+ {
+ strncpy(game.helpmessage1, ent->message, sizeof(game.helpmessage2) - 1);
+ }
+ else
+ {
+ strncpy(game.helpmessage2, ent->message, sizeof(game.helpmessage1) - 1);
+ }
+
+ game.helpchanged++;
+}
+
+/*
+ * QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
+ * When fired, the "message" key becomes the current personal computer string,
+ * and the message light will be set on all clients status bars.
+ */
+void
+SP_target_help(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!ent->message)
+ {
+ gi.dprintf("%s with no message at %s\n", ent->classname,
+ vtos(ent->s.origin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->use = Use_Target_Help;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
+ * Counts a secret found. These are single use targets.
+ */
+void
+use_target_secret(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+ level.found_secrets++;
+
+ G_UseTargets(ent, activator);
+ G_FreeEdict(ent);
+}
+
+void
+SP_target_secret(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->use = use_target_secret;
+
+ if (!st.noise)
+ {
+ st.noise = "misc/secret.wav";
+ }
+
+ ent->noise_index = gi.soundindex(st.noise);
+ ent->svflags = SVF_NOCLIENT;
+ level.total_secrets++;
+
+ /* Map quirk for mine3 */
+ if (!Q_stricmp(level.mapname, "mine3") && (ent->s.origin[0] == 280) &&
+ (ent->s.origin[1] == -2048) && (ent->s.origin[2] == -624))
+ {
+ ent->message = "You have found a secret area.";
+ }
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
+ * Counts a goal completed. These are single use targets.
+ */
+void
+use_target_goal(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+ level.found_goals++;
+
+ if (level.found_goals == level.total_goals)
+ {
+ gi.configstring(CS_CDTRACK, "0");
+ }
+
+ G_UseTargets(ent, activator);
+ G_FreeEdict(ent);
+}
+
+void
+SP_target_goal(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* auto-remove for deathmatch */
+ G_FreeEdict(ent);
+ return;
+ }
+
+ ent->use = use_target_goal;
+
+ if (!st.noise)
+ {
+ st.noise = "misc/secret.wav";
+ }
+
+ ent->noise_index = gi.soundindex(st.noise);
+ ent->svflags = SVF_NOCLIENT;
+ level.total_goals++;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
+ * Spawns an explosion temporary entity when used.
+ *
+ * "delay" wait this long before going off
+ * "dmg" how much radius damage should be done, defaults to 0
+ */
+void
+target_explosion_explode(edict_t *self)
+{
+ float save;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+
+ T_RadiusDamage(self, self->activator, self->dmg, NULL,
+ self->dmg + 40, MOD_EXPLOSIVE);
+
+ save = self->delay;
+ self->delay = 0;
+ G_UseTargets(self, self->activator);
+ self->delay = save;
+}
+
+void
+use_target_explosion(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ self->activator = activator;
+
+ if (!self->delay)
+ {
+ target_explosion_explode(self);
+ return;
+ }
+
+ self->think = target_explosion_explode;
+ self->nextthink = level.time + self->delay;
+}
+
+void
+SP_target_explosion(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->use = use_target_explosion;
+ ent->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
+ * Changes level to "map" when fired
+ */
+void
+use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator)
+{
+ if (!self || !other || !activator)
+ {
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return; /* already activated */
+ }
+
+ if (!deathmatch->value && !coop->value)
+ {
+ if (g_edicts[1].health <= 0)
+ {
+ return;
+ }
+ }
+
+ /* if noexit, do a ton of damage to other */
+ if (deathmatch->value && !((int)dmflags->value & DF_ALLOW_EXIT) &&
+ (other != world))
+ {
+ T_Damage(other, self, self, vec3_origin, other->s.origin,
+ vec3_origin, 10 * other->max_health, 1000,
+ 0, MOD_EXIT);
+ return;
+ }
+
+ /* if multiplayer, let everyone know who hit the exit */
+ if (deathmatch->value)
+ {
+ if (activator->client)
+ {
+ gi.bprintf(PRINT_HIGH, "%s exited the level.\n",
+ activator->client->pers.netname);
+ }
+ }
+
+ /* if going to a new unit, clear cross triggers */
+ if (strstr(self->map, "*"))
+ {
+ game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
+ }
+
+ BeginIntermission(self);
+}
+
+void
+SP_target_changelevel(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->map)
+ {
+ gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
+ G_FreeEdict(ent);
+ return;
+ }
+
+ /* ugly hack because *SOMEBODY* screwed up their map */
+ if ((Q_stricmp(level.mapname, "fact1") == 0) &&
+ (Q_stricmp(ent->map, "fact3") == 0))
+ {
+ ent->map = "fact3$secret1";
+ }
+
+ ent->use = use_target_changelevel;
+ ent->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
+ * Creates a particle splash effect when used.
+ *
+ * Set "sounds" to one of the following:
+ * 1) sparks
+ * 2) blue water
+ * 3) brown water
+ * 4) slime
+ * 5) lava
+ * 6) blood
+ *
+ * "count" how many pixels in the splash
+ * "dmg" if set, does a radius damage at this location when it splashes
+ * useful for lava/sparks
+ */
+void
+use_target_splash(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_SPLASH);
+ gi.WriteByte(self->count);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(self->movedir);
+ gi.WriteByte(self->sounds);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ if (self->dmg)
+ {
+ T_RadiusDamage(self, activator, self->dmg, NULL,
+ self->dmg + 40, MOD_SPLASH);
+ }
+}
+
+void
+SP_target_splash(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = use_target_splash;
+ G_SetMovedir(self->s.angles, self->movedir);
+
+ if (!self->count)
+ {
+ self->count = 32;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
+ * Set target to the type of entity you want spawned.
+ * Useful for spawning monsters and gibs in the factory levels.
+ *
+ * For monsters:
+ * Set direction to the facing you want it to have.
+ *
+ * For gibs:
+ * Set direction if you want it moving and
+ * speed how fast it should be moving otherwise it
+ * will just be dropped
+ */
+void ED_CallSpawn(edict_t *ent);
+
+void
+use_target_spawner(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = G_Spawn();
+ ent->classname = self->target;
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(self->s.angles, ent->s.angles);
+ ED_CallSpawn(ent);
+ gi.unlinkentity(ent);
+ KillBox(ent);
+ gi.linkentity(ent);
+
+ if (self->speed)
+ {
+ VectorCopy(self->movedir, ent->velocity);
+ }
+}
+
+void
+SP_target_spawner(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = use_target_spawner;
+ self->svflags = SVF_NOCLIENT;
+
+ if (self->speed)
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ VectorScale(self->movedir, self->speed, self->movedir);
+ }
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
+ * Fires a blaster bolt in the set direction when triggered.
+ *
+ * dmg default is 15
+ * speed default is 1000
+ */
+void
+use_target_blaster(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_blaster(self, self->s.origin, self->movedir,
+ self->dmg, self->speed, EF_BLASTER,
+ MOD_TARGET_BLASTER);
+ gi.sound(self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
+}
+
+void
+SP_target_blaster(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = use_target_blaster;
+ G_SetMovedir(self->s.angles, self->movedir);
+ self->noise_index = gi.soundindex("weapons/laser2.wav");
+
+ if (!self->dmg)
+ {
+ self->dmg = 15;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 1000;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+ * Once this trigger is touched/used, any trigger_crosslevel_target with
+ * the same trigger number is automatically used when a level is started
+ * within the same unit. It is OK to check multiple triggers. Message,
+ * delay, target, and killtarget also work.
+ */
+void
+trigger_crosslevel_trigger_use(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ game.serverflags |= self->spawnflags;
+ G_FreeEdict(self);
+}
+
+void
+SP_target_crosslevel_trigger(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+ self->use = trigger_crosslevel_trigger_use;
+}
+
+/*
+ * QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+ * Triggered by a trigger_crosslevel elsewhere within a unit.
+ * If multiple triggers are checked, all must be true. Delay,
+ * target and killtarget also work.
+ *
+ * "delay" delay before using targets if the trigger has been
+ * activated (default 1)
+ */
+void
+target_crosslevel_target_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags ==
+ (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
+ {
+ G_UseTargets(self, self);
+ G_FreeEdict(self);
+ }
+}
+
+void
+SP_target_crosslevel_target(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->delay)
+ {
+ self->delay = 1;
+ }
+
+ self->svflags = SVF_NOCLIENT;
+
+ self->think = target_crosslevel_target_think;
+ self->nextthink = level.time + self->delay;
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
+ * When triggered, fires a laser. You can either set a target
+ * or a direction.
+ */
+void
+target_laser_think(edict_t *self)
+{
+ edict_t *ignore;
+ vec3_t start;
+ vec3_t end;
+ trace_t tr;
+ vec3_t point;
+ vec3_t last_movedir;
+ int count;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 0x80000000)
+ {
+ count = 8;
+ }
+ else
+ {
+ count = 4;
+ }
+
+ if (self->enemy)
+ {
+ VectorCopy(self->movedir, last_movedir);
+ VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point);
+ VectorSubtract(point, self->s.origin, self->movedir);
+ VectorNormalize(self->movedir);
+
+ if (!VectorCompare(self->movedir, last_movedir))
+ {
+ self->spawnflags |= 0x80000000;
+ }
+ }
+
+ ignore = self;
+ VectorCopy(self->s.origin, start);
+ VectorMA(start, 2048, self->movedir, end);
+
+ while (1)
+ {
+ tr = gi.trace(start, NULL, NULL, end, ignore,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
+
+ if (!tr.ent)
+ {
+ break;
+ }
+
+ /* hurt it if we can */
+ if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
+ {
+ T_Damage(tr.ent, self, self->activator, self->movedir, tr.endpos,
+ vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
+ }
+
+ /* if we hit something that's not a monster or player or is immune to lasers, we're done */
+ if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+ {
+ if (self->spawnflags & 0x80000000)
+ {
+ self->spawnflags &= ~0x80000000;
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_LASER_SPARKS);
+ gi.WriteByte(count);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(self->s.skinnum);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+
+ break;
+ }
+
+ ignore = tr.ent;
+ VectorCopy(tr.endpos, start);
+ }
+
+ VectorCopy(tr.endpos, self->s.old_origin);
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+target_laser_on(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->activator)
+ {
+ self->activator = self;
+ }
+
+ self->spawnflags |= 0x80000001;
+ self->svflags &= ~SVF_NOCLIENT;
+ target_laser_think(self);
+}
+
+void
+target_laser_off(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->spawnflags &= ~1;
+ self->svflags |= SVF_NOCLIENT;
+ self->nextthink = 0;
+}
+
+void
+target_laser_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ if (self->spawnflags & 1)
+ {
+ target_laser_off(self);
+ }
+ else
+ {
+ target_laser_on(self);
+ }
+}
+
+void
+target_laser_start(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_NOT;
+ self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT;
+ self->s.modelindex = 1; /* must be non-zero */
+
+ /* set the beam diameter */
+ if (self->spawnflags & 64)
+ {
+ self->s.frame = 16;
+ }
+ else
+ {
+ self->s.frame = 4;
+ }
+
+ /* set the color */
+ if (self->spawnflags & 2)
+ {
+ self->s.skinnum = 0xf2f2f0f0;
+ }
+ else if (self->spawnflags & 4)
+ {
+ self->s.skinnum = 0xd0d1d2d3;
+ }
+ else if (self->spawnflags & 8)
+ {
+ self->s.skinnum = 0xf3f3f1f1;
+ }
+ else if (self->spawnflags & 16)
+ {
+ self->s.skinnum = 0xdcdddedf;
+ }
+ else if (self->spawnflags & 32)
+ {
+ self->s.skinnum = 0xe0e1e2e3;
+ }
+
+ if (!self->enemy)
+ {
+ if (self->target)
+ {
+ ent = G_Find(NULL, FOFS(targetname), self->target);
+
+ if (!ent)
+ {
+ gi.dprintf("%s at %s: %s is a bad target\n",
+ self->classname, vtos(self->s.origin),
+ self->target);
+ }
+
+ self->enemy = ent;
+ }
+ else
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+ }
+
+ self->use = target_laser_use;
+ self->think = target_laser_think;
+
+ if (!self->dmg)
+ {
+ self->dmg = 1;
+ }
+
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+ gi.linkentity(self);
+
+ if (self->spawnflags & 1)
+ {
+ target_laser_on(self);
+ }
+ else
+ {
+ target_laser_off(self);
+ }
+}
+
+void
+SP_target_laser(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* let everything else get spawned before we start firing */
+ self->think = target_laser_start;
+ self->nextthink = level.time + 1;
+}
+
+/* QUAKED target_mal_laser (1 0 0) (-4 -4 -4) (4 4 4) START_ON RED GREEN BLUE YELLOW ORANGE FAT
+ * Mal's laser
+ */
+void
+target_mal_laser_on(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->activator)
+ {
+ self->activator = self;
+ }
+
+ self->spawnflags |= 0x80000001;
+ self->svflags &= ~SVF_NOCLIENT;
+ self->nextthink = level.time + self->wait + self->delay;
+}
+
+void
+target_mal_laser_off(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->spawnflags &= ~1;
+ self->svflags |= SVF_NOCLIENT;
+ self->nextthink = 0;
+}
+
+void
+target_mal_laser_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->activator = activator;
+
+ if (self->spawnflags & 1)
+ {
+ target_mal_laser_off(self);
+ }
+ else
+ {
+ target_mal_laser_on(self);
+ }
+}
+
+void
+mal_laser_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ target_laser_think(self);
+ self->nextthink = level.time + self->wait + 0.1;
+ self->spawnflags |= 0x80000000;
+}
+
+void
+SP_target_mal_laser(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->movetype = MOVETYPE_NONE;
+ self->solid = SOLID_NOT;
+ self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT;
+ self->s.modelindex = 1; /* must be non-zero */
+
+ /* set the beam diameter */
+ if (self->spawnflags & 64)
+ {
+ self->s.frame = 16;
+ }
+ else
+ {
+ self->s.frame = 4;
+ }
+
+ /* set the color */
+ if (self->spawnflags & 2)
+ {
+ self->s.skinnum = 0xf2f2f0f0;
+ }
+ else if (self->spawnflags & 4)
+ {
+ self->s.skinnum = 0xd0d1d2d3;
+ }
+ else if (self->spawnflags & 8)
+ {
+ self->s.skinnum = 0xf3f3f1f1;
+ }
+ else if (self->spawnflags & 16)
+ {
+ self->s.skinnum = 0xdcdddedf;
+ }
+ else if (self->spawnflags & 32)
+ {
+ self->s.skinnum = 0xe0e1e2e3;
+ }
+
+ G_SetMovedir(self->s.angles, self->movedir);
+
+ if (!self->delay)
+ {
+ self->delay = 0.1;
+ }
+
+ if (!self->wait)
+ {
+ self->wait = 0.1;
+ }
+
+ if (!self->dmg)
+ {
+ self->dmg = 5;
+ }
+
+ VectorSet(self->mins, -8, -8, -8);
+ VectorSet(self->maxs, 8, 8, 8);
+
+ self->nextthink = level.time + self->delay;
+ self->think = mal_laser_think;
+
+ self->use = target_mal_laser_use;
+
+ gi.linkentity(self);
+
+ if (self->spawnflags & 1)
+ {
+ target_mal_laser_on(self);
+ }
+ else
+ {
+ target_mal_laser_off(self);
+ }
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
+ * speed How many seconds the ramping will take
+ * message two letters; starting lightlevel and ending lightlevel
+ */
+void
+target_lightramp_think(edict_t *self)
+{
+ char style[2];
+
+ if (!self)
+ {
+ return;
+ }
+
+ style[0] = 'a' + self->movedir[0] +
+ (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
+ style[1] = 0;
+ gi.configstring(CS_LIGHTS + self->enemy->style, style);
+
+ if ((level.time - self->timestamp) < self->speed)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+ else if (self->spawnflags & 1)
+ {
+ char temp;
+
+ temp = self->movedir[0];
+ self->movedir[0] = self->movedir[1];
+ self->movedir[1] = temp;
+ self->movedir[2] *= -1;
+ }
+}
+
+void
+target_lightramp_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ edict_t *e;
+
+ /* check all the targets */
+ e = NULL;
+
+ while (1)
+ {
+ e = G_Find(e, FOFS(targetname), self->target);
+
+ if (!e)
+ {
+ break;
+ }
+
+ if (strcmp(e->classname, "light") != 0)
+ {
+ gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
+ gi.dprintf("target %s (%s at %s) is not a light\n",
+ self->target, e->classname, vtos(e->s.origin));
+ }
+ else
+ {
+ self->enemy = e;
+ }
+ }
+
+ if (!self->enemy)
+ {
+ gi.dprintf("%s target %s not found at %s\n",
+ self->classname, self->target,
+ vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+ }
+
+ self->timestamp = level.time;
+ target_lightramp_think(self);
+}
+
+void
+SP_target_lightramp(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->message || (strlen(self->message) != 2) ||
+ (self->message[0] < 'a') || (self->message[0] > 'z') ||
+ (self->message[1] < 'a') || (self->message[1] > 'z') ||
+ (self->message[0] == self->message[1]))
+ {
+ gi.dprintf("target_lightramp has bad ramp (%s) at %s\n",
+ self->message, vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("%s with no target at %s\n", self->classname,
+ vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->svflags |= SVF_NOCLIENT;
+ self->use = target_lightramp_use;
+ self->think = target_lightramp_think;
+
+ self->movedir[0] = self->message[0] - 'a';
+ self->movedir[1] = self->message[1] - 'a';
+ self->movedir[2] =
+ (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
+}
+
+/* ========================================================== */
+
+/*
+ * QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
+ * When triggered, this initiates a level-wide earthquake.
+ * All players and monsters are affected.
+ * "speed" severity of the quake (default:200)
+ * "count" duration of the quake (default:5)
+ */
+void
+target_earthquake_think(edict_t *self)
+{
+ int i;
+ edict_t *e;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->last_move_time < level.time)
+ {
+ gi.positioned_sound(self->s.origin,
+ self,
+ CHAN_AUTO,
+ self->noise_index,
+ 1.0,
+ ATTN_NONE,
+ 0);
+ self->last_move_time = level.time + 0.5;
+ }
+
+ for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
+ {
+ if (!e->inuse)
+ {
+ continue;
+ }
+
+ if (!e->client)
+ {
+ continue;
+ }
+
+ if (!e->groundentity)
+ {
+ continue;
+ }
+
+ e->groundentity = NULL;
+ e->velocity[0] += crandom() * 150;
+ e->velocity[1] += crandom() * 150;
+ e->velocity[2] = self->speed * (100.0 / e->mass);
+ }
+
+ if (level.time < self->timestamp)
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+void
+target_earthquake_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ self->timestamp = level.time + self->count;
+ self->nextthink = level.time + FRAMETIME;
+ self->activator = activator;
+ self->last_move_time = 0;
+}
+
+void
+SP_target_earthquake(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->targetname)
+ {
+ gi.dprintf("untargeted %s at %s\n", self->classname,
+ vtos(self->s.origin));
+ }
+
+ if (!self->count)
+ {
+ self->count = 5;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 200;
+ }
+
+ self->svflags |= SVF_NOCLIENT;
+ self->think = target_earthquake_think;
+ self->use = target_earthquake_use;
+
+ self->noise_index = gi.soundindex("world/quake.wav");
+}
+
diff --git a/xatrix/src/g_trigger.c b/xatrix/src/g_trigger.c
new file mode 100644
index 0000000..674b84e
--- /dev/null
+++ b/xatrix/src/g_trigger.c
@@ -0,0 +1,905 @@
+/* =======================================================================
+ *
+ * Trigger.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define PUSH_ONCE 1
+
+void trigger_push_active(edict_t *self);
+
+static int windsound;
+
+void
+InitTrigger(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!VectorCompare(self->s.angles, vec3_origin))
+ {
+ G_SetMovedir(self->s.angles, self->movedir);
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->movetype = MOVETYPE_NONE;
+ gi.setmodel(self, self->model);
+ self->svflags = SVF_NOCLIENT;
+}
+
+/*
+ * The wait time has passed, so set
+ * back up for another activation
+ */
+void
+multi_wait(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->nextthink = 0;
+}
+
+/*
+ * The trigger was just activated. ent->activator should
+ * be set to the activator so it can be held through a delay
+ * so wait for the delay time before firing
+ */
+void
+multi_trigger(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->nextthink)
+ {
+ return; /* already been triggered */
+ }
+
+ G_UseTargets(ent, ent->activator);
+
+ if (ent->wait > 0)
+ {
+ ent->think = multi_wait;
+ ent->nextthink = level.time + ent->wait;
+ }
+ else
+ {
+ /* we can't just remove (self) here,
+ because this is a touch function
+ called while looping through area links... */
+ ent->touch = NULL;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->think = G_FreeEdict;
+ }
+}
+
+void
+Use_Multi(edict_t *ent, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!ent || !activator)
+ {
+ return;
+ }
+
+ ent->activator = activator;
+ multi_trigger(ent);
+}
+
+void
+Touch_Multi(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->client)
+ {
+ if (self->spawnflags & 2)
+ {
+ return;
+ }
+ }
+ else if (other->svflags & SVF_MONSTER)
+ {
+ if (!(self->spawnflags & 1))
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ if (!VectorCompare(self->movedir, vec3_origin))
+ {
+ vec3_t forward;
+
+ AngleVectors(other->s.angles, forward, NULL, NULL);
+
+ if (_DotProduct(forward, self->movedir) < 0)
+ {
+ return;
+ }
+ }
+
+ self->activator = other;
+ multi_trigger(self);
+}
+
+/*
+ * QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
+ * Variable sized repeatable trigger. Must be targeted at one or more
+ * entities. If "delay" is set, the trigger waits some time after
+ * activating before firing.
+ *
+ * "wait" : Seconds between triggerings. (.2 default)
+ *
+ * sounds
+ * 1) secret
+ * 2) beep beep
+ * 3) large switch
+ * 4)
+ *
+ * set "message" to text string
+ */
+void
+trigger_enable(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_TRIGGER;
+ self->use = Use_Multi;
+ gi.linkentity(self);
+}
+
+void
+SP_trigger_multiple(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->sounds == 1)
+ {
+ ent->noise_index = gi.soundindex("misc/secret.wav");
+ }
+ else if (ent->sounds == 2)
+ {
+ ent->noise_index = gi.soundindex("misc/talk.wav");
+ }
+ else if (ent->sounds == 3)
+ {
+ ent->noise_index = gi.soundindex("misc/trigger1.wav");
+ }
+
+ if (!ent->wait)
+ {
+ ent->wait = 0.2;
+ }
+
+ ent->touch = Touch_Multi;
+ ent->movetype = MOVETYPE_NONE;
+ ent->svflags |= SVF_NOCLIENT;
+
+ if (ent->spawnflags & 4)
+ {
+ ent->solid = SOLID_NOT;
+ ent->use = trigger_enable;
+ }
+ else
+ {
+ ent->solid = SOLID_TRIGGER;
+ ent->use = Use_Multi;
+ }
+
+ if (!VectorCompare(ent->s.angles, vec3_origin))
+ {
+ G_SetMovedir(ent->s.angles, ent->movedir);
+ }
+
+ gi.setmodel(ent, ent->model);
+ gi.linkentity(ent);
+}
+
+/*
+ * QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
+ * Triggers once, then removes itself.
+ *
+ * You must set the key "target" to the name of another
+ * object in the level that has a matching "targetname".
+ *
+ * If TRIGGERED, this trigger must be triggered before it is live.
+ *
+ * sounds
+ * 1) secret
+ * 2) beep beep
+ * 3) large switch
+ *
+ * "message" string to be displayed when triggered
+ */
+void
+SP_trigger_once(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* make old maps work because I messed up
+ on flag assignments here. triggered was
+ on bit 1 when it should have been on bit 4 */
+ if (ent->spawnflags & 1)
+ {
+ vec3_t v;
+
+ VectorMA(ent->mins, 0.5, ent->size, v);
+ ent->spawnflags &= ~1;
+ ent->spawnflags |= 4;
+ gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
+ }
+
+ ent->wait = -1;
+ SP_trigger_multiple(ent);
+}
+
+/*
+ * QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
+ * This fixed size trigger cannot be touched,
+ * it can only be fired by other events.
+ */
+void
+trigger_relay_use(edict_t *self, edict_t *other /* unused */,
+ edict_t *activator /* may be NULL */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ G_UseTargets(self, activator);
+}
+
+void
+SP_trigger_relay(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->use = trigger_relay_use;
+}
+
+/*
+ * QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
+ * A relay trigger that only fires it's targets if player
+ * has the proper key. Use "item" to specify the required key,
+ * for example "key_data_cd"
+ */
+void
+trigger_key_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ int index;
+
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (!self->item)
+ {
+ return;
+ }
+
+ if (!activator->client)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(self->item);
+
+ if (!activator->client->pers.inventory[index])
+ {
+ if (level.time < self->touch_debounce_time)
+ {
+ return;
+ }
+
+ self->touch_debounce_time = level.time + 5.0;
+ gi.centerprintf(activator, "You need the %s", self->item->pickup_name);
+ gi.sound(activator, CHAN_AUTO, gi.soundindex(
+ "misc/keytry.wav"), 1, ATTN_NORM, 0);
+ return;
+ }
+
+ gi.sound(activator, CHAN_AUTO, gi.soundindex(
+ "misc/keyuse.wav"), 1, ATTN_NORM, 0);
+
+ if (coop->value)
+ {
+ int player;
+ edict_t *ent;
+
+ if (strcmp(self->item->classname, "key_power_cube") == 0)
+ {
+ int cube;
+
+ for (cube = 0; cube < 8; cube++)
+ {
+ if (activator->client->pers.power_cubes & (1 << cube))
+ {
+ break;
+ }
+ }
+
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ if (ent->client->pers.power_cubes & (1 << cube))
+ {
+ ent->client->pers.inventory[index]--;
+ ent->client->pers.power_cubes &= ~(1 << cube);
+ }
+ }
+ }
+ else
+ {
+ for (player = 1; player <= game.maxclients; player++)
+ {
+ ent = &g_edicts[player];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ if (!ent->client)
+ {
+ continue;
+ }
+
+ ent->client->pers.inventory[index] = 0;
+ }
+ }
+ }
+ else
+ {
+ activator->client->pers.inventory[index]--;
+ }
+
+ G_UseTargets(self, activator);
+
+ self->use = NULL;
+}
+
+void
+SP_trigger_key(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!st.item)
+ {
+ gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
+ return;
+ }
+
+ self->item = FindItemByClassname(st.item);
+
+ if (!self->item)
+ {
+ gi.dprintf("item %s not found for trigger_key at %s\n", st.item,
+ vtos(self->s.origin));
+ return;
+ }
+
+ if (!self->target)
+ {
+ gi.dprintf("%s at %s has no target\n", self->classname,
+ vtos(self->s.origin));
+ return;
+ }
+
+ gi.soundindex("misc/keytry.wav");
+ gi.soundindex("misc/keyuse.wav");
+
+ self->use = trigger_key_use;
+}
+
+/*
+ * QUAKED trigger_counter (.5 .5 .5) ? nomessage
+ * Acts as an intermediary for an action that takes multiple inputs.
+ *
+ * If nomessage is not set, it will print "1 more.. " etc when
+ * triggered and "sequence complete" when finished.
+ *
+ * After the counter has been triggered "count" times (default 2),
+ * it will fire all of it's targets and remove itself.
+ */
+void
+trigger_counter_use(edict_t *self, edict_t *other /* unused */, edict_t *activator)
+{
+ if (!self || !activator)
+ {
+ return;
+ }
+
+ if (self->count == 0)
+ {
+ return;
+ }
+
+ self->count--;
+
+ if (self->count)
+ {
+ if (!(self->spawnflags & 1))
+ {
+ gi.centerprintf(activator, "%i more to go...", self->count);
+ gi.sound(activator, CHAN_AUTO, gi.soundindex(
+ "misc/talk1.wav"), 1, ATTN_NORM, 0);
+ }
+
+ return;
+ }
+
+ if (!(self->spawnflags & 1))
+ {
+ gi.centerprintf(activator, "Sequence completed!");
+ gi.sound(activator, CHAN_AUTO, gi.soundindex(
+ "misc/talk1.wav"), 1, ATTN_NORM, 0);
+ }
+
+ self->activator = activator;
+ multi_trigger(self);
+}
+
+void
+SP_trigger_counter(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->wait = -1;
+
+ if (!self->count)
+ {
+ self->count = 2;
+ }
+
+ self->use = trigger_counter_use;
+}
+
+/*
+ * QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
+ * This trigger will always fire. It is activated by the world.
+ */
+void
+SP_trigger_always(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* we must have some delay to make
+ sure our use targets are present */
+ if (ent->delay < 0.2)
+ {
+ ent->delay = 0.2;
+ }
+
+ G_UseTargets(ent, ent);
+}
+
+
+void
+trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (strcmp(other->classname, "grenade") == 0)
+ {
+ VectorScale(self->movedir, self->speed * 10, other->velocity);
+ }
+ else if (other->health > 0)
+ {
+ VectorScale(self->movedir, self->speed * 10, other->velocity);
+
+ if (other->client)
+ {
+ /* don't take falling damage immediately from this */
+ VectorCopy(other->velocity, other->client->oldvelocity);
+
+ if (other->fly_sound_debounce_time < level.time)
+ {
+ other->fly_sound_debounce_time = level.time + 1.5;
+ gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
+ }
+ }
+ }
+
+ if (self->spawnflags & PUSH_ONCE)
+ {
+ G_FreeEdict(self);
+ }
+}
+
+/*
+ * QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE
+ * Pushes the player
+ *
+ * "speed" defaults to 1000
+ */
+void
+trigger_effect(edict_t *self)
+{
+ vec3_t origin;
+ vec3_t size;
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorScale(self->size, 0.5, size);
+ VectorAdd(self->absmin, size, origin);
+
+ for (i = 0; i < 10; i++)
+ {
+ origin[2] += (self->speed * 0.01) * (i + random());
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_TUNNEL_SPARKS);
+ gi.WriteByte(1);
+ gi.WritePosition(origin);
+ gi.WriteDir(vec3_origin);
+ gi.WriteByte(0x74 + (rand() & 7));
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ }
+}
+
+void
+trigger_push_inactive(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->delay > level.time)
+ {
+ self->nextthink = level.time + 0.1;
+ }
+ else
+ {
+ self->touch = trigger_push_touch;
+ self->think = trigger_push_active;
+ self->nextthink = level.time + 0.1;
+ self->delay = self->nextthink + self->wait;
+ }
+}
+
+void
+trigger_push_active(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->delay > level.time)
+ {
+ self->nextthink = level.time + 0.1;
+ trigger_effect(self);
+ }
+ else
+ {
+ self->touch = NULL;
+ self->think = trigger_push_inactive;
+ self->nextthink = level.time + 0.1;
+ self->delay = self->nextthink + self->wait;
+ }
+}
+
+void
+SP_trigger_push(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ InitTrigger(self);
+ windsound = gi.soundindex("misc/windfly.wav");
+ self->touch = trigger_push_touch;
+
+ if (self->spawnflags & 2)
+ {
+ if (!self->wait)
+ {
+ self->wait = 10;
+ }
+
+ self->think = trigger_push_active;
+ self->nextthink = level.time + 0.1;
+ self->delay = self->nextthink + self->wait;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 1000;
+ }
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
+ * Any entity that touches this will be hurt.
+ *
+ * It does dmg points of damage each server frame
+ *
+ * SILENT supresses playing the sound
+ * SLOW changes the damage rate to once per second
+ * NO_PROTECTION *nothing* stops the damage
+ *
+ * "dmg" default 5 (whole numbers only)
+ */
+void
+hurt_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->solid == SOLID_NOT)
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+ else
+ {
+ self->solid = SOLID_NOT;
+ }
+
+ gi.linkentity(self);
+
+ if (!(self->spawnflags & 2))
+ {
+ self->use = NULL;
+ }
+}
+
+void
+hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */,
+ csurface_t *surf /* unused */)
+{
+ int dflags;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (!other->takedamage)
+ {
+ return;
+ }
+
+ if (self->timestamp > level.time)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 16)
+ {
+ self->timestamp = level.time + 1;
+ }
+ else
+ {
+ self->timestamp = level.time + FRAMETIME;
+ }
+
+ if (!(self->spawnflags & 4))
+ {
+ if ((level.framenum % 10) == 0)
+ {
+ gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
+ }
+ }
+
+ if (self->spawnflags & 8)
+ {
+ dflags = DAMAGE_NO_PROTECTION;
+ }
+ else
+ {
+ dflags = 0;
+ }
+
+ T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin,
+ self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
+}
+
+void
+SP_trigger_hurt(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ InitTrigger(self);
+
+ self->noise_index = gi.soundindex("world/electro.wav");
+ self->touch = hurt_touch;
+
+ if (!self->dmg)
+ {
+ self->dmg = 5;
+ }
+
+ if (self->spawnflags & 1)
+ {
+ self->solid = SOLID_NOT;
+ }
+ else
+ {
+ self->solid = SOLID_TRIGGER;
+ }
+
+ if (self->spawnflags & 2)
+ {
+ self->use = hurt_use;
+ }
+
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED trigger_gravity (.5 .5 .5) ?
+ * Changes the touching entites gravity to
+ * the value of "gravity". 1.0 is standard
+ * gravity for the level.
+ */
+void
+trigger_gravity_touch(edict_t *self, edict_t *other,
+ cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ other->gravity = self->gravity;
+}
+
+void
+SP_trigger_gravity(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (st.gravity == 0)
+ {
+ gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
+ G_FreeEdict(self);
+ return;
+ }
+
+ InitTrigger(self);
+ self->gravity = (int)strtol(st.gravity, (char **)NULL, 10);
+ self->touch = trigger_gravity_touch;
+}
+
+/*
+ * QUAKED trigger_monsterjump (.5 .5 .5) ?
+ * Walking monsters that touch this will jump in the direction of the trigger's angle
+ *
+ * "speed" default to 200, the speed thrown forward
+ * "height" default to 200, the speed thrown upwards
+ */
+void
+trigger_monsterjump_touch(edict_t *self, edict_t *other,
+ cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->flags & (FL_FLY | FL_SWIM))
+ {
+ return;
+ }
+
+ if (other->svflags & SVF_DEADMONSTER)
+ {
+ return;
+ }
+
+ if (!(other->svflags & SVF_MONSTER))
+ {
+ return;
+ }
+
+ /* set XY even if not on ground, so the jump will clear lips */
+ other->velocity[0] = self->movedir[0] * self->speed;
+ other->velocity[1] = self->movedir[1] * self->speed;
+
+ if (!other->groundentity)
+ {
+ return;
+ }
+
+ other->groundentity = NULL;
+ other->velocity[2] = self->movedir[2];
+}
+
+void
+SP_trigger_monsterjump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->speed)
+ {
+ self->speed = 200;
+ }
+
+ if (!st.height)
+ {
+ st.height = 200;
+ }
+
+ if (self->s.angles[YAW] == 0)
+ {
+ self->s.angles[YAW] = 360;
+ }
+
+ InitTrigger(self);
+ self->touch = trigger_monsterjump_touch;
+ self->movedir[2] = st.height;
+}
diff --git a/xatrix/src/g_turret.c b/xatrix/src/g_turret.c
new file mode 100644
index 0000000..7a6f6b2
--- /dev/null
+++ b/xatrix/src/g_turret.c
@@ -0,0 +1,589 @@
+/* =======================================================================
+ *
+ * Turrets aka big cannons with a driver.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void infantry_stand(edict_t *self);
+void monster_use(edict_t *self, edict_t *other, edict_t *activator);
+
+qboolean FindTarget(edict_t *self);
+
+void
+AnglesNormalize(vec3_t vec)
+{
+ while (vec[0] > 360)
+ {
+ vec[0] -= 360;
+ }
+
+ while (vec[0] < 0)
+ {
+ vec[0] += 360;
+ }
+
+ while (vec[1] > 360)
+ {
+ vec[1] -= 360;
+ }
+
+ while (vec[1] < 0)
+ {
+ vec[1] += 360;
+ }
+}
+
+float
+SnapToEights(float x)
+{
+ x *= 8.0;
+
+ if (x > 0.0)
+ {
+ x += 0.5;
+ }
+ else
+ {
+ x -= 0.5;
+ }
+
+ return 0.125 * (int)x;
+}
+
+void
+turret_blocked(edict_t *self, edict_t *other)
+{
+ edict_t *attacker;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other->takedamage)
+ {
+ if (self->teammaster->owner)
+ {
+ attacker = self->teammaster->owner;
+ }
+ else
+ {
+ attacker = self->teammaster;
+ }
+
+ T_Damage(other, self, attacker, vec3_origin, other->s.origin,
+ vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
+ }
+}
+
+/*
+ * QUAKED turret_breach (0 0 0) ?
+ * This portion of the turret can change both pitch and yaw.
+ * The model should be made with a flat pitch.
+ * It (and the associated base) need to be oriented towards 0.
+ * Use "angle" to set the starting angle.
+ *
+ * "speed" default 50
+ * "dmg" default 10
+ * "angle" point this forward
+ * "target" point this at an info_notnull at the muzzle tip
+ * "minpitch" min acceptable pitch angle : default -30
+ * "maxpitch" max acceptable pitch angle : default 30
+ * "minyaw" min acceptable yaw angle : default 0
+ * "maxyaw" max acceptable yaw angle : default 360
+ */
+void
+turret_breach_fire(edict_t *self)
+{
+ vec3_t f, r, u;
+ vec3_t start;
+ int damage;
+ int speed;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, u);
+ VectorMA(self->s.origin, self->move_origin[0], f, start);
+ VectorMA(start, self->move_origin[1], r, start);
+ VectorMA(start, self->move_origin[2], u, start);
+
+ damage = 100 + random() * 50;
+ speed = 550 + 50 * skill->value;
+ fire_rocket(self->teammaster->owner, start, f, damage, speed, 150, damage);
+ gi.positioned_sound(start, self, CHAN_WEAPON,
+ gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
+}
+
+void
+turret_breach_think(edict_t *self)
+{
+ edict_t *ent;
+ vec3_t current_angles;
+ vec3_t delta;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->s.angles, current_angles);
+ AnglesNormalize(current_angles);
+
+ AnglesNormalize(self->move_angles);
+
+ if (self->move_angles[PITCH] > 180)
+ {
+ self->move_angles[PITCH] -= 360;
+ }
+
+ /* clamp angles to mins & maxs */
+ if (self->move_angles[PITCH] > self->pos1[PITCH])
+ {
+ self->move_angles[PITCH] = self->pos1[PITCH];
+ }
+ else if (self->move_angles[PITCH] < self->pos2[PITCH])
+ {
+ self->move_angles[PITCH] = self->pos2[PITCH];
+ }
+
+ if ((self->move_angles[YAW] < self->pos1[YAW]) ||
+ (self->move_angles[YAW] > self->pos2[YAW]))
+ {
+ float dmin, dmax;
+
+ dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
+
+ if (dmin < -180)
+ {
+ dmin += 360;
+ }
+ else if (dmin > 180)
+ {
+ dmin -= 360;
+ }
+
+ dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
+
+ if (dmax < -180)
+ {
+ dmax += 360;
+ }
+ else if (dmax > 180)
+ {
+ dmax -= 360;
+ }
+
+ if (fabs(dmin) < fabs(dmax))
+ {
+ self->move_angles[YAW] = self->pos1[YAW];
+ }
+ else
+ {
+ self->move_angles[YAW] = self->pos2[YAW];
+ }
+ }
+
+ VectorSubtract(self->move_angles, current_angles, delta);
+
+ if (delta[0] < -180)
+ {
+ delta[0] += 360;
+ }
+ else if (delta[0] > 180)
+ {
+ delta[0] -= 360;
+ }
+
+ if (delta[1] < -180)
+ {
+ delta[1] += 360;
+ }
+ else if (delta[1] > 180)
+ {
+ delta[1] -= 360;
+ }
+
+ delta[2] = 0;
+
+ if (delta[0] > self->speed * FRAMETIME)
+ {
+ delta[0] = self->speed * FRAMETIME;
+ }
+
+ if (delta[0] < -1 * self->speed * FRAMETIME)
+ {
+ delta[0] = -1 * self->speed * FRAMETIME;
+ }
+
+ if (delta[1] > self->speed * FRAMETIME)
+ {
+ delta[1] = self->speed * FRAMETIME;
+ }
+
+ if (delta[1] < -1 * self->speed * FRAMETIME)
+ {
+ delta[1] = -1 * self->speed * FRAMETIME;
+ }
+
+ VectorScale(delta, 1.0 / FRAMETIME, self->avelocity);
+
+ self->nextthink = level.time + FRAMETIME;
+
+ for (ent = self->teammaster; ent; ent = ent->teamchain)
+ {
+ ent->avelocity[1] = self->avelocity[1];
+ }
+
+ /* if we have a driver, adjust his velocities */
+ if (self->owner)
+ {
+ float angle;
+ float target_z;
+ float diff;
+ vec3_t target;
+ vec3_t dir;
+
+ /* angular is easy, just copy ours */
+ self->owner->avelocity[0] = self->avelocity[0];
+ self->owner->avelocity[1] = self->avelocity[1];
+
+ /* x & y */
+ angle = self->s.angles[1] + self->owner->move_origin[1];
+ angle *= (M_PI * 2 / 360);
+ target[0] = SnapToEights(self->s.origin[0] + cos(
+ angle) * self->owner->move_origin[0]);
+ target[1] = SnapToEights(self->s.origin[1] + sin(
+ angle) * self->owner->move_origin[0]);
+ target[2] = self->owner->s.origin[2];
+
+ VectorSubtract(target, self->owner->s.origin, dir);
+ self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
+ self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
+
+ /* z */
+ angle = self->s.angles[PITCH] * (M_PI * 2 / 360);
+ target_z = SnapToEights(
+ self->s.origin[2] + self->owner->move_origin[0] * tan(
+ angle) + self->owner->move_origin[2]);
+
+ diff = target_z - self->owner->s.origin[2];
+ self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
+
+ if (self->spawnflags & 65536)
+ {
+ turret_breach_fire(self);
+ self->spawnflags &= ~65536;
+ }
+ }
+}
+
+void
+turret_breach_finish_init(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* get and save info for muzzle location */
+ if (!self->target)
+ {
+ gi.dprintf("%s at %s needs a target\n", self->classname,
+ vtos(self->s.origin));
+ }
+ else
+ {
+ self->target_ent = G_PickTarget(self->target);
+ VectorSubtract(self->target_ent->s.origin, self->s.origin,
+ self->move_origin);
+ G_FreeEdict(self->target_ent);
+ }
+
+ self->teammaster->dmg = self->dmg;
+ self->think = turret_breach_think;
+ self->think(self);
+}
+
+void
+SP_turret_breach(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BSP;
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+
+ if (!self->speed)
+ {
+ self->speed = 50;
+ }
+
+ if (!self->dmg)
+ {
+ self->dmg = 10;
+ }
+
+ if (!st.minpitch)
+ {
+ st.minpitch = -30;
+ }
+
+ if (!st.maxpitch)
+ {
+ st.maxpitch = 30;
+ }
+
+ if (!st.maxyaw)
+ {
+ st.maxyaw = 360;
+ }
+
+ self->pos1[PITCH] = -1 * st.minpitch;
+ self->pos1[YAW] = st.minyaw;
+ self->pos2[PITCH] = -1 * st.maxpitch;
+ self->pos2[YAW] = st.maxyaw;
+
+ self->ideal_yaw = self->s.angles[YAW];
+ self->move_angles[YAW] = self->ideal_yaw;
+
+ self->blocked = turret_blocked;
+
+ self->think = turret_breach_finish_init;
+ self->nextthink = level.time + FRAMETIME;
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED turret_base (0 0 0) ?
+ * This portion of the turret changes yaw only.
+ * MUST be teamed with a turret_breach.
+ */
+void
+SP_turret_base(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->solid = SOLID_BSP;
+ self->movetype = MOVETYPE_PUSH;
+ gi.setmodel(self, self->model);
+ self->blocked = turret_blocked;
+ gi.linkentity(self);
+}
+
+/*
+ * QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
+ * Must NOT be on the team with the rest of the turret parts.
+ * Instead it must target the turret_breach.
+ */
+void
+turret_driver_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point)
+{
+ edict_t *ent;
+
+ if (!self || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ /* level the gun */
+ self->target_ent->move_angles[0] = 0;
+
+ /* remove the driver from the end of them team chain */
+ for (ent = self->target_ent->teammaster;
+ ent->teamchain != self;
+ ent = ent->teamchain)
+ {
+ }
+
+ ent->teamchain = NULL;
+ self->teammaster = NULL;
+ self->flags &= ~FL_TEAMSLAVE;
+
+ self->target_ent->owner = NULL;
+ self->target_ent->teammaster->owner = NULL;
+
+ infantry_die(self, inflictor, attacker, damage, point);
+}
+
+void
+turret_driver_think(edict_t *self)
+{
+ vec3_t target;
+ vec3_t dir;
+ float reaction_time;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+
+ if (self->enemy && (!self->enemy->inuse || (self->enemy->health <= 0)))
+ {
+ self->enemy = NULL;
+ }
+
+ if (!self->enemy)
+ {
+ if (!FindTarget(self))
+ {
+ return;
+ }
+
+ self->monsterinfo.trail_time = level.time;
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ }
+ else
+ {
+ if (visible(self, self->enemy))
+ {
+ if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
+ {
+ self->monsterinfo.trail_time = level.time;
+ self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+ }
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_LOST_SIGHT;
+ return;
+ }
+ }
+
+ /* let the turret know where we want it to aim */
+ VectorCopy(self->enemy->s.origin, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, self->target_ent->s.origin, dir);
+ vectoangles(dir, self->target_ent->move_angles);
+
+ /* decide if we should shoot */
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return;
+ }
+
+ reaction_time = (3 - skill->value) * 1.0;
+
+ if ((level.time - self->monsterinfo.trail_time) < reaction_time)
+ {
+ return;
+ }
+
+ self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
+ self->target_ent->spawnflags |= 65536;
+}
+
+void
+turret_driver_link(edict_t *self)
+{
+ vec3_t vec;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = turret_driver_think;
+ self->nextthink = level.time + FRAMETIME;
+
+ self->target_ent = G_PickTarget(self->target);
+ self->target_ent->owner = self;
+ self->target_ent->teammaster->owner = self;
+ VectorCopy(self->target_ent->s.angles, self->s.angles);
+
+ vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
+ vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
+ vec[2] = 0;
+ self->move_origin[0] = VectorLength(vec);
+
+ VectorSubtract(self->s.origin, self->target_ent->s.origin, vec);
+ vectoangles(vec, vec);
+ AnglesNormalize(vec);
+ self->move_origin[1] = vec[1];
+
+ self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
+
+ /* add the driver to the end of them team chain */
+ for (ent = self->target_ent->teammaster;
+ ent->teamchain;
+ ent = ent->teamchain)
+ {
+ }
+
+ ent->teamchain = self;
+ self->teammaster = self->target_ent->teammaster;
+ self->flags |= FL_TEAMSLAVE;
+}
+
+void
+SP_turret_driver(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_PUSH;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 100;
+ self->gib_health = 0;
+ self->mass = 200;
+ self->viewheight = 24;
+
+ self->die = turret_driver_die;
+ self->monsterinfo.stand = infantry_stand;
+
+ self->flags |= FL_NO_KNOCKBACK;
+
+ level.total_monsters++;
+
+ self->svflags |= SVF_MONSTER;
+ self->s.renderfx |= RF_FRAMELERP;
+ self->takedamage = DAMAGE_AIM;
+ self->use = monster_use;
+ self->clipmask = MASK_MONSTERSOLID;
+ VectorCopy(self->s.origin, self->s.old_origin);
+ self->monsterinfo.aiflags |= AI_STAND_GROUND | AI_DUCKED;
+
+ if (st.item)
+ {
+ self->item = FindItemByClassname(st.item);
+
+ if (!self->item)
+ {
+ gi.dprintf("%s at %s has bad item: %s\n", self->classname,
+ vtos(self->s.origin), st.item);
+ }
+ }
+
+ self->think = turret_driver_link;
+ self->nextthink = level.time + FRAMETIME;
+
+ gi.linkentity(self);
+}
diff --git a/xatrix/src/g_utils.c b/xatrix/src/g_utils.c
new file mode 100644
index 0000000..d94b2d0
--- /dev/null
+++ b/xatrix/src/g_utils.c
@@ -0,0 +1,664 @@
+/* =======================================================================
+ *
+ * Misc. utility functions for the game logic.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+#define MAXCHOICES 8
+
+void
+G_ProjectSource(vec3_t point, vec3_t distance, vec3_t forward,
+ vec3_t right, vec3_t result)
+{
+ result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
+ result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
+ result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
+}
+
+/*
+ * Searches all active entities for the next one that holds
+ * the matching string at fieldofs (use the FOFS() macro) in the structure.
+ *
+ * Searches beginning at the edict after from, or the beginning if NULL
+ * NULL will be returned if the end of the list is reached.
+ */
+edict_t *
+G_Find(edict_t *from, int fieldofs, char *match)
+{
+ char *s;
+
+ if (!from)
+ {
+ from = g_edicts;
+ }
+ else
+ {
+ from++;
+ }
+
+ for ( ; from < &g_edicts[globals.num_edicts]; from++)
+ {
+ if (!from->inuse)
+ {
+ continue;
+ }
+
+ s = *(char **)((byte *)from + fieldofs);
+
+ if (!s)
+ {
+ continue;
+ }
+
+ if (!Q_stricmp(s, match))
+ {
+ return from;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Returns entities that have origins within a spherical area
+ */
+edict_t *
+findradius(edict_t *from, vec3_t org, float rad)
+{
+ vec3_t eorg;
+ int j;
+
+ if (!from)
+ {
+ from = g_edicts;
+ }
+ else
+ {
+ from++;
+ }
+
+ for ( ; from < &g_edicts[globals.num_edicts]; from++)
+ {
+ if (!from->inuse)
+ {
+ continue;
+ }
+
+ if (from->solid == SOLID_NOT)
+ {
+ continue;
+ }
+
+ for (j = 0; j < 3; j++)
+ {
+ eorg[j] = org[j] - (from->s.origin[j] +
+ (from->mins[j] + from->maxs[j]) * 0.5);
+ }
+
+ if (VectorLength(eorg) > rad)
+ {
+ continue;
+ }
+
+ return from;
+ }
+
+ return NULL;
+}
+
+/*
+ * Searches all active entities for the next one that holds
+ * the matching string at fieldofs (use the FOFS() macro) in the structure.
+ *
+ * Searches beginning at the edict after from, or the beginning if NULL
+ * NULL will be returned if the end of the list is reached.
+ */
+edict_t *
+G_PickTarget(char *targetname)
+{
+ edict_t *ent = NULL;
+ int num_choices = 0;
+ edict_t *choice[MAXCHOICES];
+
+ if (!targetname)
+ {
+ gi.dprintf("G_PickTarget called with NULL targetname\n");
+ return NULL;
+ }
+
+ while (1)
+ {
+ ent = G_Find(ent, FOFS(targetname), targetname);
+
+ if (!ent)
+ {
+ break;
+ }
+
+ choice[num_choices++] = ent;
+
+ if (num_choices == MAXCHOICES)
+ {
+ break;
+ }
+ }
+
+ if (!num_choices)
+ {
+ gi.dprintf("G_PickTarget: target %s not found\n", targetname);
+ return NULL;
+ }
+
+ return choice[rand() % num_choices];
+}
+
+void
+Think_Delay(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ G_UseTargets(ent, ent->activator);
+ G_FreeEdict(ent);
+}
+
+/*
+ * the global "activator" should be set to the entity that initiated the firing.
+ *
+ * If self.delay is set, a DelayedUse entity will be created that will actually
+ * do the SUB_UseTargets after that many seconds have passed.
+ *
+ * Centerprints any self.message to the activator.
+ *
+ * Search for (string)targetname in all entities that
+ * match (string)self.target and call their .use function
+ */
+void
+G_UseTargets(edict_t *ent, edict_t *activator /* may be NULL */)
+{
+ edict_t *t;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* check for a delay */
+ if (ent->delay)
+ {
+ /* create a temp object to fire at a later time */
+ t = G_Spawn();
+ t->classname = "DelayedUse";
+ t->nextthink = level.time + ent->delay;
+ t->think = Think_Delay;
+ t->activator = activator;
+
+ if (!activator)
+ {
+ gi.dprintf("Think_Delay with no activator\n");
+ }
+
+ t->message = ent->message;
+ t->target = ent->target;
+ t->killtarget = ent->killtarget;
+ return;
+ }
+
+ /* print the message */
+ if (activator && (ent->message) && !(activator->svflags & SVF_MONSTER))
+ {
+ gi.centerprintf(activator, "%s", ent->message);
+
+ if (ent->noise_index)
+ {
+ gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ }
+
+ /* kill killtargets */
+ if (ent->killtarget)
+ {
+ t = NULL;
+
+ while ((t = G_Find(t, FOFS(targetname), ent->killtarget)))
+ {
+ G_FreeEdict(t);
+
+ if (!ent->inuse)
+ {
+ gi.dprintf("entity was removed while using killtargets\n");
+ return;
+ }
+ }
+ }
+
+ /* fire targets */
+ if (ent->target)
+ {
+ t = NULL;
+
+ while ((t = G_Find(t, FOFS(targetname), ent->target)))
+ {
+ /* doors fire area portals in a specific way */
+ if (!Q_stricmp(t->classname, "func_areaportal") &&
+ (!Q_stricmp(ent->classname, "func_door") ||
+ !Q_stricmp(ent->classname, "func_door_rotating")))
+ {
+ continue;
+ }
+
+ if (t == ent)
+ {
+ gi.dprintf("WARNING: Entity used itself.\n");
+ }
+ else
+ {
+ if (t->use)
+ {
+ t->use(t, ent, activator);
+ }
+ }
+
+ if (!ent->inuse)
+ {
+ gi.dprintf("entity was removed while using targets\n");
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * This is just a convenience function
+ * for making temporary vectors for function calls
+ */
+float *
+tv(float x, float y, float z)
+{
+ static int index;
+ static vec3_t vecs[8];
+ float *v;
+
+ /* use an array so that multiple tempvectors
+ won't collide for a while */
+ v = vecs[index];
+ index = (index + 1) & 7;
+
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+
+ return v;
+}
+
+/*
+ * This is just a convenience function
+ * for printing vectors
+ */
+char *
+vtos(vec3_t v)
+{
+ static int index;
+ static char str[8][32];
+ char *s;
+
+ /* use an array so that multiple vtos won't collide */
+ s = str[index];
+ index = (index + 1) & 7;
+
+ Com_sprintf(s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]);
+
+ return s;
+}
+
+void
+get_normal_vector(const cplane_t *p, vec3_t normal)
+{
+ if (p)
+ {
+ VectorCopy(p->normal, normal);
+ }
+ else
+ {
+ VectorCopy(vec3_origin, normal);
+ }
+}
+
+vec3_t VEC_UP = {0, -1, 0};
+vec3_t MOVEDIR_UP = {0, 0, 1};
+vec3_t VEC_DOWN = {0, -2, 0};
+vec3_t MOVEDIR_DOWN = {0, 0, -1};
+
+void
+G_SetMovedir(vec3_t angles, vec3_t movedir)
+{
+ if (VectorCompare(angles, VEC_UP))
+ {
+ VectorCopy(MOVEDIR_UP, movedir);
+ }
+ else if (VectorCompare(angles, VEC_DOWN))
+ {
+ VectorCopy(MOVEDIR_DOWN, movedir);
+ }
+ else
+ {
+ AngleVectors(angles, movedir, NULL, NULL);
+ }
+
+ VectorClear(angles);
+}
+
+float
+vectoyaw(vec3_t vec)
+{
+ float yaw;
+
+ if (vec[PITCH] == 0)
+ {
+ yaw = 0;
+
+ if (vec[YAW] > 0)
+ {
+ yaw = 90;
+ }
+ else if (vec[YAW] < 0)
+ {
+ yaw = -90;
+ }
+ }
+ else
+ {
+ yaw = (int)(atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+ }
+
+ return yaw;
+}
+
+void
+vectoangles(vec3_t value1, vec3_t angles)
+{
+ float forward;
+ float yaw, pitch;
+
+ if ((value1[1] == 0) && (value1[0] == 0))
+ {
+ yaw = 0;
+
+ if (value1[2] > 0)
+ {
+ pitch = 90;
+ }
+ else
+ {
+ pitch = 270;
+ }
+ }
+ else
+ {
+ if (value1[0])
+ {
+ yaw = (int)(atan2(value1[1], value1[0]) * 180 / M_PI);
+ }
+ else if (value1[1] > 0)
+ {
+ yaw = 90;
+ }
+ else
+ {
+ yaw = -90;
+ }
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+
+ forward = sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
+ pitch = (int)(atan2(value1[2], forward) * 180 / M_PI);
+
+ if (pitch < 0)
+ {
+ pitch += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = 0;
+}
+
+char *
+G_CopyString(char *in)
+{
+ char *out;
+
+ out = gi.TagMalloc(strlen(in) + 1, TAG_LEVEL);
+ strcpy(out, in);
+ return out;
+}
+
+void
+G_InitEdict(edict_t *e)
+{
+ e->inuse = true;
+ e->classname = "noclass";
+ e->gravity = 1.0;
+ e->s.number = e - g_edicts;
+}
+
+/*
+ * Either finds a free edict, or allocates a
+ * new one. Try to avoid reusing an entity
+ * that was recently freed, because it can
+ * cause the client to think the entity
+ * morphed into something else instead of
+ * being removed and recreated, which can
+ * cause interpolated angles and bad trails.
+ */
+#define POLICY_DEFAULT 0
+#define POLICY_DESPERATE 1
+
+static edict_t *
+G_FindFreeEdict(int policy)
+{
+ edict_t *e;
+
+ for (e = g_edicts + game.maxclients + 1 ; e < &g_edicts[globals.num_edicts] ; e++)
+ {
+ /* the first couple seconds of server time can involve a lot of
+ freeing and allocating, so relax the replacement policy
+ */
+ if (!e->inuse && (policy == POLICY_DESPERATE || e->freetime < 2.0f || (level.time - e->freetime) > 0.5f))
+ {
+ G_InitEdict (e);
+ return e;
+ }
+ }
+
+ return NULL;
+}
+
+edict_t *
+G_SpawnOptional(void)
+{
+ edict_t *e = G_FindFreeEdict (POLICY_DEFAULT);
+
+ if (e)
+ {
+ return e;
+ }
+
+ if (globals.num_edicts >= game.maxentities)
+ {
+ return G_FindFreeEdict (POLICY_DESPERATE);
+ }
+
+ e = &g_edicts[globals.num_edicts++];
+ G_InitEdict (e);
+
+ return e;
+}
+
+edict_t *
+G_Spawn(void)
+{
+ edict_t *e = G_SpawnOptional();
+
+ if (!e)
+ gi.error ("ED_Alloc: no free edicts");
+
+ return e;
+}
+
+/*
+ * Marks the edict as free
+ */
+void
+G_FreeEdict(edict_t *ed)
+{
+ gi.unlinkentity(ed); /* unlink from world */
+
+ if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
+ {
+ return;
+ }
+
+ memset(ed, 0, sizeof(*ed));
+ ed->classname = "freed";
+ ed->freetime = level.time;
+ ed->inuse = false;
+}
+
+void
+G_TouchTriggers(edict_t *ent)
+{
+ int i, num;
+ edict_t *touch[MAX_EDICTS], *hit;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* dead things don't activate triggers! */
+ if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
+ {
+ return;
+ }
+
+ num = gi.BoxEdicts(ent->absmin, ent->absmax, touch,
+ MAX_EDICTS, AREA_TRIGGERS);
+
+ /* be careful, it is possible to have an entity in this
+ list removed before we get to it (killtriggered) */
+ for (i = 0; i < num; i++)
+ {
+ hit = touch[i];
+
+ if (!hit->inuse)
+ {
+ continue;
+ }
+
+ if (!hit->touch)
+ {
+ continue;
+ }
+
+ hit->touch(hit, ent, NULL, NULL);
+ }
+}
+
+/*
+ * Call after linking a new trigger in during gameplay
+ * to force all entities it covers to immediately touch it
+ */
+void
+G_TouchSolids(edict_t *ent)
+{
+ int i, num;
+ edict_t *touch[MAX_EDICTS], *hit;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ num = gi.BoxEdicts(ent->absmin, ent->absmax, touch,
+ MAX_EDICTS, AREA_SOLID);
+
+ /* be careful, it is possible to have an entity in this
+ list removed before we get to it (killtriggered) */
+ for (i = 0; i < num; i++)
+ {
+ hit = touch[i];
+
+ if (!hit->inuse)
+ {
+ continue;
+ }
+
+ if (ent->touch)
+ {
+ ent->touch(hit, ent, NULL, NULL);
+ }
+
+ if (!ent->inuse)
+ {
+ break;
+ }
+ }
+}
+
+/*
+ * Kills all entities that would touch the proposed new positioning
+ * of ent. Ent should be unlinked before calling this!
+ */
+qboolean
+KillBox(edict_t *ent)
+{
+ trace_t tr;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ while (1)
+ {
+ tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin,
+ NULL, MASK_PLAYERSOLID);
+
+ if (!tr.ent)
+ {
+ break;
+ }
+
+ /* nail it */
+ T_Damage(tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
+ 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+
+ /* if we didn't kill it, fail */
+ if (tr.ent->solid)
+ {
+ return false;
+ }
+ }
+
+ return true; /* all clear */
+}
diff --git a/xatrix/src/g_weapon.c b/xatrix/src/g_weapon.c
new file mode 100644
index 0000000..5553bc4
--- /dev/null
+++ b/xatrix/src/g_weapon.c
@@ -0,0 +1,1848 @@
+/* =======================================================================
+ *
+ * Weapon support functions.
+ *
+ * =======================================================================
+ */
+
+#include "header/local.h"
+
+extern void SP_item_foodcube(edict_t *best);
+
+/*
+ * This is a support routine used when a client is firing
+ * a non-instant attack weapon. It checks to see if a
+ * monster's dodge function should be called.
+ */
+void
+check_dodge(edict_t *self, vec3_t start, vec3_t dir, int speed)
+{
+ vec3_t end;
+ vec3_t v;
+ trace_t tr;
+ float eta;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* easy mode only ducks one quarter the time */
+ if (skill->value == SKILL_EASY)
+ {
+ if (random() > 0.25)
+ {
+ return;
+ }
+ }
+
+ VectorMA(start, 8192, dir, end);
+ tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
+
+ if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) &&
+ (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
+ {
+ VectorSubtract(tr.endpos, start, v);
+ eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
+ tr.ent->monsterinfo.dodge(tr.ent, self, eta);
+ }
+}
+
+/*
+ * Used for all impact (hit/punch/slash) attacks
+ */
+qboolean
+fire_hit(edict_t *self, vec3_t aim, int damage, int kick)
+{
+ trace_t tr;
+ vec3_t forward, right, up;
+ vec3_t v;
+ vec3_t point;
+ float range;
+ vec3_t dir;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy)
+ {
+ return false;
+ }
+
+ /* see if enemy is in range */
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+ range = VectorLength(dir);
+
+ if (range > aim[0])
+ {
+ return false;
+ }
+
+ if ((aim[1] > self->mins[0]) && (aim[1] < self->maxs[0]))
+ {
+ /* the hit is straight on so back the range up to the edge of their bbox */
+ range -= self->enemy->maxs[0];
+ }
+ else
+ {
+ /* this is a side hit so adjust the "right" value out to the edge of their bbox */
+ if (aim[1] < 0)
+ {
+ aim[1] = self->enemy->mins[0];
+ }
+ else
+ {
+ aim[1] = self->enemy->maxs[0];
+ }
+ }
+
+ VectorMA(self->s.origin, range, dir, point);
+
+ tr = gi.trace(self->s.origin, NULL, NULL, point, self, MASK_SHOT);
+
+ if (tr.fraction < 1)
+ {
+ if (!tr.ent->takedamage)
+ {
+ return false;
+ }
+
+ /* if it will hit any client/monster then hit the one we wanted to hit */
+ if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
+ {
+ tr.ent = self->enemy;
+ }
+ }
+
+ AngleVectors(self->s.angles, forward, right, up);
+ VectorMA(self->s.origin, range, forward, point);
+ VectorMA(point, aim[1], right, point);
+ VectorMA(point, aim[2], up, point);
+ VectorSubtract(point, self->enemy->s.origin, dir);
+
+ /* do the damage */
+ T_Damage(tr.ent, self, self, dir, point, vec3_origin,
+ damage, kick / 2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
+
+ if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+ {
+ return false;
+ }
+
+ /* do our special form of knockback here */
+ VectorMA(self->enemy->absmin, 0.5, self->enemy->size, v);
+ VectorSubtract(v, point, v);
+ VectorNormalize(v);
+ VectorMA(self->enemy->velocity, kick, v, self->enemy->velocity);
+
+ if (self->enemy->velocity[2] > 0)
+ {
+ self->enemy->groundentity = NULL;
+ }
+
+ return true;
+}
+
+/*
+ * This is an internal support routine used for bullet/pellet based weapons.
+ */
+void
+fire_lead(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick,
+ int te_impact, int hspread, int vspread, int mod)
+{
+ trace_t tr;
+ vec3_t dir;
+ vec3_t forward, right, up;
+ vec3_t end;
+ float r;
+ float u;
+ vec3_t water_start;
+ qboolean water = false;
+ int content_mask = MASK_SHOT | MASK_WATER;
+
+ if (!self)
+ {
+ return;
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+
+ if (!(tr.fraction < 1.0))
+ {
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ r = crandom() * hspread;
+ u = crandom() * vspread;
+ VectorMA(start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+
+ if (gi.pointcontents(start) & MASK_WATER)
+ {
+ water = true;
+ VectorCopy(start, water_start);
+ content_mask &= ~MASK_WATER;
+ }
+
+ tr = gi.trace(start, NULL, NULL, end, self, content_mask);
+
+ /* see if we hit water */
+ if (tr.contents & MASK_WATER)
+ {
+ int color;
+
+ water = true;
+ VectorCopy(tr.endpos, water_start);
+
+ if (!VectorCompare(start, tr.endpos))
+ {
+ if (tr.contents & CONTENTS_WATER)
+ {
+ if (strcmp(tr.surface->name, "*brwater") == 0)
+ {
+ color = SPLASH_BROWN_WATER;
+ }
+ else
+ {
+ color = SPLASH_BLUE_WATER;
+ }
+ }
+ else if (tr.contents & CONTENTS_SLIME)
+ {
+ color = SPLASH_SLIME;
+ }
+ else if (tr.contents & CONTENTS_LAVA)
+ {
+ color = SPLASH_LAVA;
+ }
+ else
+ {
+ color = SPLASH_UNKNOWN;
+ }
+
+ if (color != SPLASH_UNKNOWN)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_SPLASH);
+ gi.WriteByte(8);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(color);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+
+ /* change bullet's course when it enters water */
+ VectorSubtract(end, start, dir);
+ vectoangles(dir, dir);
+ AngleVectors(dir, forward, right, up);
+ r = crandom() * hspread * 2;
+ u = crandom() * vspread * 2;
+ VectorMA(water_start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+ }
+
+ /* re-trace ignoring water this time */
+ tr = gi.trace(water_start, NULL, NULL, end, self, MASK_SHOT);
+ }
+ }
+
+ /* send gun puff / flash */
+ if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+ {
+ if (tr.fraction < 1.0)
+ {
+ if (tr.ent->takedamage)
+ {
+ T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal,
+ damage, kick, DAMAGE_BULLET, mod);
+ }
+ else
+ {
+ if (strncmp(tr.surface->name, "sky", 3) != 0)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(te_impact);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+
+ if (self->client)
+ {
+ PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+ }
+ }
+ }
+ }
+ }
+
+ /* if went through water, determine where the end and make a bubble trail */
+ if (water)
+ {
+ vec3_t pos;
+
+ VectorSubtract(tr.endpos, water_start, dir);
+ VectorNormalize(dir);
+ VectorMA(tr.endpos, -2, dir, pos);
+
+ if (gi.pointcontents(pos) & MASK_WATER)
+ {
+ VectorCopy(pos, tr.endpos);
+ }
+ else
+ {
+ tr = gi.trace(pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+ }
+
+ VectorAdd(water_start, tr.endpos, pos);
+ VectorScale(pos, 0.5, pos);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BUBBLETRAIL);
+ gi.WritePosition(water_start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(pos, MULTICAST_PVS);
+ }
+}
+
+/*
+ * Fires a single round. Used for machinegun and chaingun.
+ * Would be fine for pistols, rifles, etc....
+ */
+void
+fire_bullet(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int mod)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ fire_lead(self, start, aimdir, damage, kick, TE_GUNSHOT,
+ hspread, vspread, mod);
+}
+
+/*
+ * Shoots shotgun pellets. Used by shotgun and super shotgun.
+ */
+void
+fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int count, int mod)
+{
+ int i;
+
+ if (!self)
+ {
+ return;
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ fire_lead(self, start, aimdir, damage, kick,
+ TE_SHOTGUN, hspread, vspread, mod);
+ }
+}
+
+/*
+ * Fires a single blaster bolt. Used by the blaster and hyper blaster.
+ */
+void
+blaster_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ int mod;
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ get_normal_vector(plane, normal);
+
+ if (other->takedamage)
+ {
+ if (self->spawnflags & 1)
+ {
+ mod = MOD_HYPERBLASTER;
+ }
+ else
+ {
+ mod = MOD_BLASTER;
+ }
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, self->dmg, 1, DAMAGE_ENERGY, mod);
+ }
+ else
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BLASTER);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(normal);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int effect, qboolean hyper)
+{
+ edict_t *bolt;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ bolt = G_Spawn();
+ bolt->svflags = SVF_DEADMONSTER;
+
+ /* yes, I know it looks weird that projectiles are deadmonsters
+ what this means is that when prediction is used against the object
+ (blaster/hyperblaster shots), the player won't be solid clipped against
+ the object. Right now trying to run into a firing hyperblaster
+ is very jerky since you are predicted 'against' the shots. */
+ VectorCopy(start, bolt->s.origin);
+ VectorCopy(start, bolt->s.old_origin);
+ vectoangles(dir, bolt->s.angles);
+ VectorScale(dir, speed, bolt->velocity);
+ bolt->movetype = MOVETYPE_FLYMISSILE;
+ bolt->clipmask = MASK_SHOT;
+ bolt->solid = SOLID_BBOX;
+ bolt->s.effects |= effect;
+ VectorClear(bolt->mins);
+ VectorClear(bolt->maxs);
+ bolt->s.modelindex = gi.modelindex("models/objects/laser/tris.md2");
+ bolt->s.sound = gi.soundindex("misc/lasfly.wav");
+ bolt->owner = self;
+ bolt->touch = blaster_touch;
+ bolt->nextthink = level.time + 2;
+ bolt->think = G_FreeEdict;
+ bolt->dmg = damage;
+ bolt->classname = "bolt";
+
+ if (hyper)
+ {
+ bolt->spawnflags = 1;
+ }
+
+ gi.linkentity(bolt);
+
+ if (self->client)
+ {
+ check_dodge(self, bolt->s.origin, dir, speed);
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+
+ if (tr.fraction < 1.0)
+ {
+ VectorMA(bolt->s.origin, -10, dir, bolt->s.origin);
+ bolt->touch(bolt, tr.ent, NULL, NULL);
+ }
+}
+
+void
+fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int effect)
+{
+ edict_t *bolt;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ bolt = G_Spawn();
+ VectorCopy(start, bolt->s.origin);
+ VectorCopy(start, bolt->s.old_origin);
+ vectoangles(dir, bolt->s.angles);
+ VectorScale(dir, speed, bolt->velocity);
+ bolt->movetype = MOVETYPE_FLYMISSILE;
+ bolt->clipmask = MASK_SHOT;
+ bolt->solid = SOLID_BBOX;
+ bolt->s.effects |= effect;
+ VectorClear(bolt->mins);
+ VectorClear(bolt->maxs);
+
+ bolt->s.modelindex = gi.modelindex("models/objects/blaser/tris.md2");
+ bolt->s.sound = gi.soundindex("misc/lasfly.wav");
+ bolt->owner = self;
+ bolt->touch = blaster_touch;
+ bolt->nextthink = level.time + 2;
+ bolt->think = G_FreeEdict;
+ bolt->dmg = damage;
+ bolt->classname = "bolt";
+ gi.linkentity(bolt);
+
+ if (self->client)
+ {
+ check_dodge(self, bolt->s.origin, dir, speed);
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+
+ if (tr.fraction < 1.0)
+ {
+ VectorMA(bolt->s.origin, -10, dir, bolt->s.origin);
+ bolt->touch(bolt, tr.ent, NULL, NULL);
+ }
+}
+
+void
+Grenade_Explode(edict_t *ent)
+{
+ vec3_t origin;
+ int mod;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->owner->client)
+ {
+ PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+ }
+
+ if (ent->enemy)
+ {
+ float points;
+ vec3_t v;
+ vec3_t dir;
+
+ VectorAdd(ent->enemy->mins, ent->enemy->maxs, v);
+ VectorMA(ent->enemy->s.origin, 0.5, v, v);
+ VectorSubtract(ent->s.origin, v, v);
+ points = ent->dmg - 0.5 * VectorLength(v);
+ VectorSubtract(ent->enemy->s.origin, ent->s.origin, dir);
+
+ if (ent->spawnflags & 1)
+ {
+ mod = MOD_HANDGRENADE;
+ }
+ else
+ {
+ mod = MOD_GRENADE;
+ }
+
+ T_Damage(ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin,
+ (int)points, (int)points, DAMAGE_RADIUS, mod);
+ }
+
+ if (ent->spawnflags & 2)
+ {
+ mod = MOD_HELD_GRENADE;
+ }
+ else if (ent->spawnflags & 1)
+ {
+ mod = MOD_HG_SPLASH;
+ }
+ else
+ {
+ mod = MOD_G_SPLASH;
+ }
+
+ T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
+
+ VectorMA(ent->s.origin, -0.02, ent->velocity, origin);
+ gi.WriteByte(svc_temp_entity);
+
+ if (ent->waterlevel)
+ {
+ if (ent->groundentity)
+ {
+ gi.WriteByte(TE_GRENADE_EXPLOSION_WATER);
+ }
+ else
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION_WATER);
+ }
+ }
+ else
+ {
+ if (ent->groundentity)
+ {
+ gi.WriteByte(TE_GRENADE_EXPLOSION);
+ }
+ else
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION);
+ }
+ }
+
+ gi.WritePosition(origin);
+ gi.multicast(ent->s.origin, MULTICAST_PHS);
+
+ G_FreeEdict(ent);
+}
+
+void
+Grenade_Touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ if (!ent || !other)
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (other == ent->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (!other->takedamage)
+ {
+ if (ent->spawnflags & 1)
+ {
+ if (random() > 0.5)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ }
+ else
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/grenlb1b.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ return;
+ }
+
+ ent->enemy = other;
+ Grenade_Explode(ent);
+}
+
+void
+fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius)
+{
+ edict_t *grenade;
+ vec3_t dir;
+ vec3_t forward, right, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ grenade = G_Spawn();
+ VectorCopy(start, grenade->s.origin);
+ VectorScale(aimdir, speed, grenade->velocity);
+ VectorMA(grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+ VectorMA(grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+ VectorSet(grenade->avelocity, 300, 300, 300);
+ grenade->movetype = MOVETYPE_BOUNCE;
+ grenade->clipmask = MASK_SHOT;
+ grenade->solid = SOLID_BBOX;
+ grenade->s.effects |= EF_GRENADE;
+ VectorClear(grenade->mins);
+ VectorClear(grenade->maxs);
+ grenade->s.modelindex = gi.modelindex("models/objects/grenade/tris.md2");
+ grenade->owner = self;
+ grenade->touch = Grenade_Touch;
+ grenade->nextthink = level.time + timer;
+ grenade->think = Grenade_Explode;
+ grenade->dmg = damage;
+ grenade->dmg_radius = damage_radius;
+ grenade->classname = "grenade";
+
+ gi.linkentity(grenade);
+}
+
+void
+fire_grenade2(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius, qboolean held)
+{
+ edict_t *grenade;
+ vec3_t dir;
+ vec3_t forward, right, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ grenade = G_Spawn();
+ VectorCopy(start, grenade->s.origin);
+ VectorScale(aimdir, speed, grenade->velocity);
+ VectorMA(grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+ VectorMA(grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+ VectorSet(grenade->avelocity, 300, 300, 300);
+ grenade->movetype = MOVETYPE_BOUNCE;
+ grenade->clipmask = MASK_SHOT;
+ grenade->solid = SOLID_BBOX;
+ grenade->s.effects |= EF_GRENADE;
+ VectorClear(grenade->mins);
+ VectorClear(grenade->maxs);
+ grenade->s.modelindex = gi.modelindex("models/objects/grenade2/tris.md2");
+ grenade->owner = self;
+ grenade->touch = Grenade_Touch;
+ grenade->nextthink = level.time + timer;
+ grenade->think = Grenade_Explode;
+ grenade->dmg = damage;
+ grenade->dmg_radius = damage_radius;
+ grenade->classname = "hgrenade";
+
+ if (held)
+ {
+ grenade->spawnflags = 3;
+ }
+ else
+ {
+ grenade->spawnflags = 1;
+ }
+
+ grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
+
+ if (timer <= 0.0)
+ {
+ Grenade_Explode(grenade);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, gi.soundindex("weapons/hgrent1a.wav"),
+ 1, ATTN_NORM, 0);
+ gi.linkentity(grenade);
+ }
+}
+
+void
+rocket_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t origin;
+ vec3_t normal;
+ int n;
+
+ if (!ent || !other)
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (other == ent->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (ent->owner->client)
+ {
+ PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+ }
+
+ /* calculate position for the explosion entity */
+ VectorMA(ent->s.origin, -0.02, ent->velocity, origin);
+
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, ent, ent->owner, ent->velocity, ent->s.origin,
+ normal, ent->dmg, 0, 0, MOD_ROCKET);
+ }
+ else
+ {
+ /* don't throw any debris in net games */
+ if (!deathmatch->value && !coop->value)
+ {
+ if ((surf) && !(surf->flags &
+ (SURF_WARP | SURF_TRANS33 | SURF_TRANS66 | SURF_FLOWING)))
+ {
+ n = rand() % 5;
+
+ while (n--)
+ {
+ ThrowDebris(ent, "models/objects/debris2/tris.md2",
+ 2, ent->s.origin);
+ }
+ }
+ }
+ }
+
+ T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other,
+ ent->dmg_radius, MOD_R_SPLASH);
+
+ gi.WriteByte(svc_temp_entity);
+
+ if (ent->waterlevel)
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION_WATER);
+ }
+ else
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION);
+ }
+
+ gi.WritePosition(origin);
+ gi.multicast(ent->s.origin, MULTICAST_PHS);
+
+ G_FreeEdict(ent);
+}
+
+void
+fire_rocket(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, float damage_radius, int radius_damage)
+{
+ edict_t *rocket;
+
+ if (!self)
+ {
+ return;
+ }
+
+ rocket = G_Spawn();
+ VectorCopy(start, rocket->s.origin);
+ VectorCopy(dir, rocket->movedir);
+ vectoangles(dir, rocket->s.angles);
+ VectorScale(dir, speed, rocket->velocity);
+ rocket->movetype = MOVETYPE_FLYMISSILE;
+ rocket->clipmask = MASK_SHOT;
+ rocket->solid = SOLID_BBOX;
+ rocket->s.effects |= EF_ROCKET;
+ VectorClear(rocket->mins);
+ VectorClear(rocket->maxs);
+ rocket->s.modelindex = gi.modelindex("models/objects/rocket/tris.md2");
+ rocket->owner = self;
+ rocket->touch = rocket_touch;
+ rocket->nextthink = level.time + (8000.0f / (float)speed);
+ rocket->think = G_FreeEdict;
+ rocket->dmg = damage;
+ rocket->radius_dmg = radius_damage;
+ rocket->dmg_radius = damage_radius;
+ rocket->s.sound = gi.soundindex("weapons/rockfly.wav");
+ rocket->classname = "rocket";
+
+ if (self->client)
+ {
+ check_dodge(self, rocket->s.origin, dir, speed);
+ }
+
+ gi.linkentity(rocket);
+}
+
+void
+fire_rail(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
+{
+ vec3_t from;
+ vec3_t end;
+ trace_t tr;
+ edict_t *ignore;
+ int mask;
+ qboolean water;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorMA(start, 8192, aimdir, end);
+ VectorCopy(start, from);
+ ignore = self;
+ water = false;
+ mask = MASK_SHOT | CONTENTS_SLIME | CONTENTS_LAVA;
+
+ while (ignore)
+ {
+ tr = gi.trace(from, NULL, NULL, end, ignore, mask);
+
+ if (tr.contents & (CONTENTS_SLIME | CONTENTS_LAVA))
+ {
+ mask &= ~(CONTENTS_SLIME | CONTENTS_LAVA);
+ water = true;
+ }
+ else
+ {
+ /* -added so rail goes through SOLID_BBOX entities (gibs, etc) */
+ if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) ||
+ (tr.ent->solid == SOLID_BBOX))
+ {
+ ignore = tr.ent;
+ }
+ else
+ {
+ ignore = NULL;
+ }
+
+ if ((tr.ent != self) && (tr.ent->takedamage))
+ {
+ T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal,
+ damage, kick, 0, MOD_RAILGUN);
+ }
+ else
+ {
+ ignore = NULL;
+ }
+ }
+
+ VectorCopy(tr.endpos, from);
+ }
+
+ /* send gun puff / flash */
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_RAILTRAIL);
+ gi.WritePosition(start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+
+ if (water)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_RAILTRAIL);
+ gi.WritePosition(start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(tr.endpos, MULTICAST_PHS);
+ }
+
+ if (self->client)
+ {
+ PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+ }
+}
+
+void
+bfg_explode(edict_t *self)
+{
+ edict_t *ent;
+ float points;
+ vec3_t v;
+ float dist;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == 0)
+ {
+ /* the BFG effect */
+ ent = NULL;
+
+ while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
+ {
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ if (ent == self->owner)
+ {
+ continue;
+ }
+
+ if (!CanDamage(ent, self))
+ {
+ continue;
+ }
+
+ if (!CanDamage(ent, self->owner))
+ {
+ continue;
+ }
+
+ VectorAdd(ent->mins, ent->maxs, v);
+ VectorMA(ent->s.origin, 0.5, v, v);
+ VectorSubtract(self->s.origin, v, v);
+ dist = VectorLength(v);
+ points = self->radius_dmg * (1.0 - sqrt(dist / self->dmg_radius));
+
+ if (ent == self->owner)
+ {
+ points = points * 0.5;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BFG_EXPLOSION);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_PHS);
+ T_Damage(ent, self, self->owner, self->velocity, ent->s.origin,
+ vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
+ }
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+ self->s.frame++;
+
+ if (self->s.frame == 5)
+ {
+ self->think = G_FreeEdict;
+ }
+}
+
+void
+bfg_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ /* core explosion - prevents firing it into the wall/floor */
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, 200, 0, 0, MOD_BFG_BLAST);
+ }
+
+ T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST);
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
+ self->solid = SOLID_NOT;
+ self->touch = NULL;
+
+ /* move it back a bit from walls so the effects aren't cut off */
+ if (!other->takedamage)
+ {
+ VectorNormalize(self->velocity);
+ VectorMA(self->s.origin, -40.0f, self->velocity, self->s.origin);
+ }
+
+ VectorClear(self->velocity);
+ self->s.modelindex = gi.modelindex("sprites/s_bfg3.sp2");
+ self->s.frame = 0;
+ self->s.sound = 0;
+ self->s.effects &= ~EF_ANIM_ALLFAST;
+ self->think = bfg_explode;
+ self->nextthink = level.time + FRAMETIME;
+ self->enemy = other;
+
+ gi.linkentity(self);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BFG_BIGEXPLOSION);
+ gi.WritePosition(self->s.origin);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+}
+
+void
+bfg_think(edict_t *self)
+{
+ edict_t *ent;
+ edict_t *ignore;
+ vec3_t point;
+ vec3_t dir;
+ vec3_t start;
+ vec3_t end;
+ int dmg;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ dmg = 5;
+ }
+ else
+ {
+ dmg = 10;
+ }
+
+ ent = NULL;
+
+ while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
+ {
+ if (ent == self)
+ {
+ continue;
+ }
+
+ if (ent == self->owner)
+ {
+ continue;
+ }
+
+ if (!ent->takedamage)
+ {
+ continue;
+ }
+
+ if (!(ent->svflags & SVF_MONSTER) && (!ent->client) &&
+ (strcmp(ent->classname, "misc_explobox") != 0))
+ {
+ continue;
+ }
+
+ VectorMA(ent->absmin, 0.5, ent->size, point);
+
+ VectorSubtract(point, self->s.origin, dir);
+ VectorNormalize(dir);
+
+ ignore = self;
+ VectorCopy(self->s.origin, start);
+ VectorMA(start, 2048, dir, end);
+
+ while (1)
+ {
+ tr = gi.trace(start, NULL, NULL, end, ignore,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
+
+ if (!tr.ent)
+ {
+ break;
+ }
+
+ /* hurt it if we can */
+ if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) &&
+ (tr.ent != self->owner))
+ {
+ T_Damage(tr.ent, self, self->owner, dir, tr.endpos,
+ vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
+ }
+
+ /* if we hit something that's not a monster or player we're done */
+ if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_LASER_SPARKS);
+ gi.WriteByte(4);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(self->s.skinnum);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ break;
+ }
+
+ ignore = tr.ent;
+ VectorCopy(tr.endpos, start);
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BFG_LASER);
+ gi.WritePosition(self->s.origin);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(self->s.origin, MULTICAST_PHS);
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+void
+fire_bfg(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, float damage_radius)
+{
+ edict_t *bfg;
+
+ if (!self)
+ {
+ return;
+ }
+
+ bfg = G_Spawn();
+ VectorCopy(start, bfg->s.origin);
+ VectorCopy(dir, bfg->movedir);
+ vectoangles(dir, bfg->s.angles);
+ VectorScale(dir, speed, bfg->velocity);
+ bfg->movetype = MOVETYPE_FLYMISSILE;
+ bfg->clipmask = MASK_SHOT;
+ bfg->solid = SOLID_BBOX;
+ bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
+ VectorClear(bfg->mins);
+ VectorClear(bfg->maxs);
+ bfg->s.modelindex = gi.modelindex("sprites/s_bfg1.sp2");
+ bfg->owner = self;
+ bfg->touch = bfg_touch;
+ bfg->nextthink = level.time + (8000.0f / (float)speed);
+ bfg->think = G_FreeEdict;
+ bfg->radius_dmg = damage;
+ bfg->dmg_radius = damage_radius;
+ bfg->classname = "bfg blast";
+ bfg->s.sound = gi.soundindex("weapons/bfg__l1a.wav");
+
+ bfg->think = bfg_think;
+ bfg->nextthink = level.time + FRAMETIME;
+ bfg->teammaster = bfg;
+ bfg->teamchain = NULL;
+
+ if (self->client)
+ {
+ check_dodge(self, bfg->s.origin, dir, speed);
+ }
+
+ gi.linkentity(bfg);
+}
+
+void
+ionripper_sparks(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WELDING_SPARKS);
+ gi.WriteByte(0);
+ gi.WritePosition(self->s.origin);
+ gi.WriteDir(vec3_origin);
+ gi.WriteByte(0xe4 + (rand() & 3));
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(self);
+}
+
+void
+ionripper_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER);
+
+ G_FreeEdict(self);
+ }
+}
+
+void
+fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int effect)
+{
+ edict_t *ion;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ ion = G_Spawn();
+ VectorCopy(start, ion->s.origin);
+ VectorCopy(start, ion->s.old_origin);
+ vectoangles(dir, ion->s.angles);
+ VectorScale(dir, speed, ion->velocity);
+
+ ion->movetype = MOVETYPE_WALLBOUNCE;
+ ion->clipmask = MASK_SHOT;
+ ion->solid = SOLID_BBOX;
+ ion->s.effects |= effect;
+
+ ion->s.renderfx |= RF_FULLBRIGHT;
+
+ VectorClear(ion->mins);
+ VectorClear(ion->maxs);
+ ion->s.modelindex = gi.modelindex("models/objects/boomrang/tris.md2");
+ ion->s.sound = gi.soundindex("misc/lasfly.wav");
+ ion->owner = self;
+ ion->touch = ionripper_touch;
+ ion->nextthink = level.time + 3;
+ ion->think = ionripper_sparks;
+ ion->dmg = damage;
+ ion->dmg_radius = 100;
+ gi.linkentity(ion);
+
+ if (self->client)
+ {
+ check_dodge(self, ion->s.origin, dir, speed);
+ }
+
+ tr = gi.trace(self->s.origin, NULL, NULL, ion->s.origin, ion, MASK_SHOT);
+
+ if (tr.fraction < 1.0)
+ {
+ VectorMA(ion->s.origin, -10, dir, ion->s.origin);
+ ion->touch(ion, tr.ent, NULL, NULL);
+ }
+}
+
+void
+heat_think(edict_t *self)
+{
+ edict_t *target = NULL;
+ edict_t *aquire = NULL;
+ vec3_t vec;
+ float len;
+ float oldlen = 0;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* aquire new target */
+ while ((target = findradius(target, self->s.origin, 1024)) != NULL)
+ {
+ if (self->owner == target)
+ {
+ continue;
+ }
+
+ if (!target->client)
+ {
+ continue;
+ }
+
+ if (target->health <= 0)
+ {
+ continue;
+ }
+
+ if (!infront(self, target))
+ {
+ continue;
+ }
+
+ if (!visible(self, target))
+ {
+ continue;
+ }
+
+ VectorSubtract(self->s.origin, target->s.origin, vec);
+ len = VectorLength(vec);
+
+ if ((!aquire) || (len < oldlen))
+ {
+ aquire = target;
+ oldlen = len;
+ }
+ }
+
+ if (aquire)
+ {
+ VectorSubtract(aquire->s.origin, self->s.origin, vec);
+ vectoangles(vec, self->s.angles);
+ VectorNormalize(vec);
+ VectorScale(vec, 500, self->velocity);
+ }
+
+ self->nextthink = level.time + 0.1;
+}
+
+void
+fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed,
+ float damage_radius, int radius_damage)
+{
+ edict_t *heat;
+
+ if (!self)
+ {
+ return;
+ }
+
+ heat = G_Spawn();
+ VectorCopy(start, heat->s.origin);
+ VectorCopy(dir, heat->movedir);
+ vectoangles(dir, heat->s.angles);
+ VectorScale(dir, speed, heat->velocity);
+ heat->movetype = MOVETYPE_FLYMISSILE;
+ heat->clipmask = MASK_SHOT;
+ heat->solid = SOLID_BBOX;
+ heat->s.effects |= EF_ROCKET;
+ VectorClear(heat->mins);
+ VectorClear(heat->maxs);
+ heat->s.modelindex = gi.modelindex("models/objects/rocket/tris.md2");
+ heat->owner = self;
+ heat->touch = rocket_touch;
+
+ heat->nextthink = level.time + 0.1;
+ heat->think = heat_think;
+
+ heat->dmg = damage;
+ heat->radius_dmg = radius_damage;
+ heat->dmg_radius = damage_radius;
+ heat->s.sound = gi.soundindex("weapons/rockfly.wav");
+
+ if (self->client)
+ {
+ check_dodge(self, heat->s.origin, dir, speed);
+ }
+
+ gi.linkentity(heat);
+}
+
+void
+plasma_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t origin;
+ vec3_t normal;
+
+ if (!ent || !other)
+ {
+ return;
+ }
+
+ if (other == ent->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(ent);
+ return;
+ }
+
+ if (ent->owner->client)
+ {
+ PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+ }
+
+ /* calculate position for the explosion entity */
+ VectorMA(ent->s.origin, -0.02, ent->velocity, origin);
+
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, ent, ent->owner, ent->velocity, ent->s.origin,
+ normal, ent->dmg, 0, 0, MOD_PHALANX);
+ }
+
+ T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other,
+ ent->dmg_radius, MOD_PHALANX);
+
+ gi.WriteByte(svc_temp_entity);
+
+ if (ent->waterlevel)
+ {
+ gi.WriteByte(TE_ROCKET_EXPLOSION_WATER);
+ }
+ else
+ {
+ gi.WriteByte(TE_PLASMA_EXPLOSION);
+ }
+
+ gi.WritePosition(origin);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ G_FreeEdict(ent);
+}
+
+void
+fire_plasma(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, float damage_radius, int radius_damage)
+{
+ edict_t *plasma;
+
+ if (!self)
+ {
+ return;
+ }
+
+ plasma = G_Spawn();
+ VectorCopy(start, plasma->s.origin);
+ VectorCopy(dir, plasma->movedir);
+ vectoangles(dir, plasma->s.angles);
+ VectorScale(dir, speed, plasma->velocity);
+ plasma->movetype = MOVETYPE_FLYMISSILE;
+ plasma->clipmask = MASK_SHOT;
+ plasma->solid = SOLID_BBOX;
+
+ VectorClear(plasma->mins);
+ VectorClear(plasma->maxs);
+
+ plasma->owner = self;
+ plasma->touch = plasma_touch;
+ plasma->nextthink = level.time + (8000.0f / (float)speed);
+ plasma->think = G_FreeEdict;
+ plasma->dmg = damage;
+ plasma->radius_dmg = radius_damage;
+ plasma->dmg_radius = damage_radius;
+ plasma->s.sound = gi.soundindex("weapons/rockfly.wav");
+
+ plasma->s.modelindex = gi.modelindex("sprites/s_photon.sp2");
+ plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST;
+
+ if (self->client)
+ {
+ check_dodge(self, plasma->s.origin, dir, speed);
+ }
+
+ gi.linkentity(plasma);
+}
+
+void
+Trap_Think(edict_t *ent)
+{
+ edict_t *target = NULL;
+ edict_t *best = NULL;
+ vec3_t vec;
+ int len, i;
+ int oldlen = 8000;
+ vec3_t forward, right, up;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->timestamp < level.time)
+ {
+ BecomeExplosion1(ent);
+ return;
+ }
+
+ ent->nextthink = level.time + 0.1;
+
+ if (!ent->groundentity)
+ {
+ return;
+ }
+
+ /* ok lets do the blood effect */
+ if (ent->s.frame > 4)
+ {
+ if (ent->s.frame == 5)
+ {
+ if (ent->wait == 64)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/trapdown.wav"),
+ 1, ATTN_IDLE, 0);
+ }
+
+ ent->wait -= 2;
+ ent->delay += level.time;
+
+ for (i = 0; i < 3; i++)
+ {
+ best = G_Spawn();
+
+ if (strcmp(ent->enemy->classname, "monster_gekk") == 0)
+ {
+ best->s.modelindex = gi.modelindex("models/objects/gekkgib/torso/tris.md2");
+ best->s.effects |= TE_GREENBLOOD;
+ }
+ else if (ent->mass > 200)
+ {
+ best->s.modelindex = gi.modelindex("models/objects/gibs/chest/tris.md2");
+ best->s.effects |= TE_BLOOD;
+ }
+ else
+ {
+ best->s.modelindex = gi.modelindex("models/objects/gibs/sm_meat/tris.md2");
+ best->s.effects |= TE_BLOOD;
+ }
+
+ AngleVectors(ent->s.angles, forward, right, up);
+
+ RotatePointAroundVector(vec, up, right, ((360.0 / 3) * i) + ent->delay);
+ VectorMA(vec, ent->wait / 2, vec, vec);
+ VectorAdd(vec, ent->s.origin, vec);
+ VectorAdd(vec, forward, best->s.origin);
+
+ best->s.origin[2] = ent->s.origin[2] + ent->wait;
+
+ VectorCopy(ent->s.angles, best->s.angles);
+
+ best->solid = SOLID_NOT;
+ best->s.effects |= EF_GIB;
+ best->takedamage = DAMAGE_YES;
+
+ best->movetype = MOVETYPE_TOSS;
+ best->svflags |= SVF_MONSTER;
+ best->deadflag = DEAD_DEAD;
+
+ VectorClear(best->mins);
+ VectorClear(best->maxs);
+
+ best->watertype = gi.pointcontents(best->s.origin);
+
+ if (best->watertype & MASK_WATER)
+ {
+ best->waterlevel = 1;
+ }
+
+ best->nextthink = level.time + 0.1;
+ best->think = G_FreeEdict;
+ gi.linkentity(best);
+ }
+
+ if (ent->wait < 19)
+ {
+ ent->s.frame++;
+ }
+
+ return;
+ }
+
+ ent->s.frame++;
+
+ if (ent->s.frame == 8)
+ {
+ ent->nextthink = level.time + 1.0;
+ ent->think = G_FreeEdict;
+
+ best = G_Spawn();
+ SP_item_foodcube(best);
+ VectorCopy(ent->s.origin, best->s.origin);
+ best->s.origin[2] += 16;
+ best->velocity[2] = 400;
+ best->count = ent->mass;
+ gi.linkentity(best);
+ return;
+ }
+
+ return;
+ }
+
+ ent->s.effects &= ~EF_TRAP;
+
+ if (ent->s.frame >= 4)
+ {
+ ent->s.effects |= EF_TRAP;
+ VectorClear(ent->mins);
+ VectorClear(ent->maxs);
+ }
+
+ if (ent->s.frame < 4)
+ {
+ ent->s.frame++;
+ }
+
+ while ((target = findradius(target, ent->s.origin, 256)) != NULL)
+ {
+ if (target == ent)
+ {
+ continue;
+ }
+
+ if (!(target->svflags & SVF_MONSTER) && !target->client)
+ {
+ continue;
+ }
+
+ if (target->health <= 0)
+ {
+ continue;
+ }
+
+ if (!visible(ent, target))
+ {
+ continue;
+ }
+
+ if (!best)
+ {
+ best = target;
+ continue;
+ }
+
+ VectorSubtract(ent->s.origin, target->s.origin, vec);
+ len = VectorLength(vec);
+
+ if (len < oldlen)
+ {
+ oldlen = len;
+ best = target;
+ }
+ }
+
+ /* pull the enemy in */
+ if (best)
+ {
+ vec3_t forward;
+
+ if (best->groundentity)
+ {
+ best->s.origin[2] += 1;
+ best->groundentity = NULL;
+ }
+
+ VectorSubtract(ent->s.origin, best->s.origin, vec);
+ len = VectorLength(vec);
+
+ if (best->client)
+ {
+ VectorNormalize(vec);
+ VectorMA(best->velocity, 250, vec, best->velocity);
+ }
+ else
+ {
+ best->ideal_yaw = vectoyaw(vec);
+ M_ChangeYaw(best);
+ AngleVectors(best->s.angles, forward, NULL, NULL);
+ VectorScale(forward, 256, best->velocity);
+ }
+
+ gi.sound(ent, CHAN_VOICE, gi.soundindex(
+ "weapons/trapsuck.wav"), 1, ATTN_IDLE, 0);
+
+ if (len < 32)
+ {
+ if (best->mass < 400)
+ {
+ T_Damage(best, ent, ent->owner, vec3_origin, best->s.origin,
+ vec3_origin, 100000, 1, 0, MOD_TRAP);
+ ent->enemy = best;
+ ent->wait = 64;
+ VectorCopy(ent->s.origin, ent->s.old_origin);
+ ent->timestamp = level.time + 30;
+
+ if (deathmatch->value)
+ {
+ ent->mass = best->mass / 4;
+ }
+ else
+ {
+ ent->mass = best->mass / 10;
+ }
+
+ /* ok spawn the food cube */
+ ent->s.frame = 5;
+ }
+ else
+ {
+ BecomeExplosion1(ent);
+ return;
+ }
+ }
+ }
+}
+
+void
+fire_trap(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius, qboolean held)
+{
+ edict_t *trap;
+ vec3_t dir;
+ vec3_t forward, right, up;
+
+ if (!self)
+ {
+ return;
+ }
+
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ trap = G_Spawn();
+ VectorCopy(start, trap->s.origin);
+ VectorScale(aimdir, speed, trap->velocity);
+ VectorMA(trap->velocity, 200 + crandom() * 10.0, up, trap->velocity);
+ VectorMA(trap->velocity, crandom() * 10.0, right, trap->velocity);
+ VectorSet(trap->avelocity, 0, 300, 0);
+ trap->movetype = MOVETYPE_BOUNCE;
+ trap->clipmask = MASK_SHOT;
+ trap->solid = SOLID_BBOX;
+ VectorSet(trap->mins, -4, -4, 0);
+ VectorSet(trap->maxs, 4, 4, 8);
+ trap->s.modelindex = gi.modelindex("models/weapons/z_trap/tris.md2");
+ trap->owner = self;
+ trap->nextthink = level.time + 1.0;
+ trap->think = Trap_Think;
+ trap->dmg = damage;
+ trap->dmg_radius = damage_radius;
+ trap->classname = "htrap";
+ trap->s.sound = gi.soundindex("weapons/traploop.wav");
+
+ if (held)
+ {
+ trap->spawnflags = 3;
+ }
+ else
+ {
+ trap->spawnflags = 1;
+ }
+
+ if (timer <= 0.0)
+ {
+ Grenade_Explode(trap);
+ }
+ else
+ {
+ gi.linkentity(trap);
+ }
+
+ trap->timestamp = level.time + 30;
+}
+
diff --git a/xatrix/src/header/game.h b/xatrix/src/header/game.h
new file mode 100644
index 0000000..025b5dc
--- /dev/null
+++ b/xatrix/src/header/game.h
@@ -0,0 +1,224 @@
+/* =======================================================================
+ *
+ * Here are the client, server and game are tied together.
+ *
+ * =======================================================================
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ *
+ * THIS FILE IS _VERY_ FRAGILE AND THERE'S NOTHING IN IT THAT CAN OR
+ * MUST BE CHANGED. IT'S MOST LIKELY A VERY GOOD IDEA TO CLOSE THE
+ * EDITOR NOW AND NEVER LOOK BACK. OTHERWISE YOU MAY SCREW UP EVERYTHING!
+ *
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ */
+
+#ifndef XATRIX_GAME_H
+#define XATRIX_GAME_H
+
+#define GAME_API_VERSION 3
+
+#define SVF_NOCLIENT 0x00000001 /* don't send entity to clients, even if it has effects */
+#define SVF_DEADMONSTER 0x00000002 /* treat as CONTENTS_DEADMONSTER for collision */
+#define SVF_MONSTER 0x00000004 /* treat as CONTENTS_MONSTER for collision */
+
+#define MAX_ENT_CLUSTERS 16
+
+typedef enum
+{
+ SOLID_NOT, /* no interaction with other objects */
+ SOLID_TRIGGER, /* only touch when inside, after moving */
+ SOLID_BBOX, /* touch on edge */
+ SOLID_BSP /* bsp clip, touch on edge */
+} solid_t;
+
+/* =============================================================== */
+
+/* link_t is only used for entity area links now */
+typedef struct link_s
+{
+ struct link_s *prev, *next;
+} link_t;
+
+
+typedef struct edict_s edict_t;
+typedef struct gclient_s gclient_t;
+
+#ifndef GAME_INCLUDE
+
+struct gclient_s
+{
+ player_state_t ps; /* communicated by server to clients */
+ int ping;
+ /* the game dll can add anything it wants
+ after this point in the structure */
+};
+
+struct edict_s
+{
+ entity_state_t s;
+ struct gclient_s *client;
+ qboolean inuse;
+ int linkcount;
+
+ link_t area; /* linked to a division node or leaf */
+
+ int num_clusters; /* if -1, use headnode instead */
+ int clusternums[MAX_ENT_CLUSTERS];
+ int headnode; /* unused if num_clusters != -1 */
+ int areanum, areanum2;
+
+ int svflags; /* SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc */
+ vec3_t mins, maxs;
+ vec3_t absmin, absmax, size;
+ solid_t solid;
+ int clipmask;
+ edict_t *owner;
+
+ /* the game dll can add anything it wants
+ after this point in the structure */
+};
+
+#endif /* GAME_INCLUDE */
+
+/* =============================================================== */
+
+/* functions provided by the main engine */
+typedef struct
+{
+ /* special messages */
+ void (*bprintf)(int printlevel, char *fmt, ...);
+ void (*dprintf)(char *fmt, ...);
+ void (*cprintf)(edict_t *ent, int printlevel, char *fmt, ...);
+ void (*centerprintf)(edict_t *ent, char *fmt, ...);
+ void (*sound)(edict_t *ent, int channel, int soundindex, float volume,
+ float attenuation, float timeofs);
+ void (*positioned_sound)(vec3_t origin, edict_t *ent, int channel,
+ int soundinedex, float volume, float attenuation, float timeofs);
+
+ /* config strings hold all the index strings, the lightstyles,
+ and misc data like the sky definition and cdtrack.
+ All of the current configstrings are sent to clients when
+ they connect, and changes are sent to all connected clients. */
+ void (*configstring)(int num, char *string);
+
+ void (*error)(char *fmt, ...);
+
+ /* the *index functions create configstrings
+ and some internal server state */
+ int (*modelindex)(char *name);
+ int (*soundindex)(char *name);
+ int (*imageindex)(char *name);
+
+ void (*setmodel)(edict_t *ent, char *name);
+
+ /* collision detection */
+ trace_t (*trace)(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end,
+ edict_t *passent, int contentmask);
+ int (*pointcontents)(vec3_t point);
+ qboolean (*inPVS)(vec3_t p1, vec3_t p2);
+ qboolean (*inPHS)(vec3_t p1, vec3_t p2);
+ void (*SetAreaPortalState)(int portalnum, qboolean open);
+ qboolean (*AreasConnected)(int area1, int area2);
+
+ /* an entity will never be sent to a client or used for collision
+ if it is not passed to linkentity. If the size, position, or
+ solidity changes, it must be relinked. */
+ void (*linkentity)(edict_t *ent);
+ void (*unlinkentity)(edict_t *ent); /* call before removing an interactive edict */
+ int (*BoxEdicts)(vec3_t mins, vec3_t maxs, edict_t **list, int maxcount,
+ int areatype);
+ void (*Pmove)(pmove_t *pmove); /* player movement code common with client prediction */
+
+ /* network messaging */
+ void (*multicast)(vec3_t origin, multicast_t to);
+ void (*unicast)(edict_t *ent, qboolean reliable);
+ void (*WriteChar)(int c);
+ void (*WriteByte)(int c);
+ void (*WriteShort)(int c);
+ void (*WriteLong)(int c);
+ void (*WriteFloat)(float f);
+ void (*WriteString)(char *s);
+ void (*WritePosition)(vec3_t pos); /* some fractional bits */
+ void (*WriteDir)(vec3_t pos); /* single byte encoded, very coarse */
+ void (*WriteAngle)(float f);
+
+ /* managed memory allocation */
+ void *(*TagMalloc)(int size, int tag);
+ void (*TagFree)(void *block);
+ void (*FreeTags)(int tag);
+
+ /* console variable interaction */
+ cvar_t *(*cvar)(char *var_name, char *value, int flags);
+ cvar_t *(*cvar_set)(char *var_name, char *value);
+ cvar_t *(*cvar_forceset)(char *var_name, char *value);
+
+ /* ClientCommand and ServerCommand parameter access */
+ int (*argc)(void);
+ char *(*argv)(int n);
+ char *(*args)(void); /* concatenation of all argv >= 1 */
+
+ /* add commands to the server console as if
+ they were typed in for map changing, etc */
+ void (*AddCommandString)(char *text);
+
+ void (*DebugGraph)(float value, int color);
+} game_import_t;
+
+/* functions exported by the game subsystem */
+typedef struct
+{
+ int apiversion;
+
+ /* the init function will only be called when a game starts,
+ not each time a level is loaded. Persistant data for clients
+ and the server can be allocated in init */
+ void (*Init)(void);
+ void (*Shutdown)(void);
+
+ /* each new level entered will cause a call to SpawnEntities */
+ void (*SpawnEntities)(char *mapname, char *entstring, char *spawnpoint);
+
+ /* Read/Write Game is for storing persistant cross level information
+ about the world state and the clients.
+ WriteGame is called every time a level is exited.
+ ReadGame is called on a loadgame. */
+ void (*WriteGame)(char *filename, qboolean autosave);
+ void (*ReadGame)(char *filename);
+
+ /* ReadLevel is called after the default
+ map information has been loaded with
+ SpawnEntities */
+ void (*WriteLevel)(char *filename);
+ void (*ReadLevel)(char *filename);
+
+ qboolean (*ClientConnect)(edict_t *ent, char *userinfo);
+ void (*ClientBegin)(edict_t *ent);
+ void (*ClientUserinfoChanged)(edict_t *ent, char *userinfo);
+ void (*ClientDisconnect)(edict_t *ent);
+ void (*ClientCommand)(edict_t *ent);
+ void (*ClientThink)(edict_t *ent, usercmd_t *cmd);
+
+ void (*RunFrame)(void);
+
+ /* ServerCommand will be called when an "sv <command>"
+ command is issued on the server console. The game can
+ issue gi.argc() / gi.argv() commands to get the rest
+ of the parameters */
+ void (*ServerCommand)(void);
+
+ /* global variables shared between game and server */
+
+ /* The edict array is allocated in the game dll so it
+ can vary in size from one game to another.
+ The size will be fixed when ge->Init() is called */
+ struct edict_s *edicts;
+ int edict_size;
+ int num_edicts; /* current number, <= max_edicts */
+ int max_edicts;
+} game_export_t;
+
+#endif /* XATRIX_GAME_H */
+
diff --git a/xatrix/src/header/local.h b/xatrix/src/header/local.h
new file mode 100644
index 0000000..034abcd
--- /dev/null
+++ b/xatrix/src/header/local.h
@@ -0,0 +1,1120 @@
+/* =======================================================================
+ *
+ * Main header file for the game module.
+ *
+ * =======================================================================
+ */
+
+#ifndef XATRIX_LOCAL_H
+#define XATRIX_LOCAL_H
+
+#include "shared.h"
+
+ /* define GAME_INCLUDE so that game.h does not define the
+ short, server-visible gclient_t and edict_t structures,
+ because we define the full size ones in this file */
+#define GAME_INCLUDE
+#include "game.h"
+
+ /* the "gameversion" client command will print this plus compile date */
+#define GAMEVERSION "xatrix"
+
+ /* protocol bytes that can be directly added to messages */
+#define svc_muzzleflash 1
+#define svc_muzzleflash2 2
+#define svc_temp_entity 3
+#define svc_layout 4
+#define svc_inventory 5
+#define svc_stufftext 11
+
+ /* ================================================================== */
+
+ /* view pitching times */
+#define DAMAGE_TIME 0.5
+#define FALL_TIME 0.3
+
+ /* these are set with checkboxes on each entity in the map editor */
+#define SPAWNFLAG_NOT_EASY 0x00000100
+#define SPAWNFLAG_NOT_MEDIUM 0x00000200
+#define SPAWNFLAG_NOT_HARD 0x00000400
+#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800
+#define SPAWNFLAG_NOT_COOP 0x00001000
+
+#define FL_FLY 0x00000001
+#define FL_SWIM 0x00000002 /* implied immunity to drowining */
+#define FL_IMMUNE_LASER 0x00000004
+#define FL_INWATER 0x00000008
+#define FL_GODMODE 0x00000010
+#define FL_NOTARGET 0x00000020
+#define FL_IMMUNE_SLIME 0x00000040
+#define FL_IMMUNE_LAVA 0x00000080
+#define FL_PARTIALGROUND 0x00000100 /* not all corners are valid */
+#define FL_WATERJUMP 0x00000200 /* player jumping out of water */
+#define FL_TEAMSLAVE 0x00000400 /* not the first on the team */
+#define FL_NO_KNOCKBACK 0x00000800
+#define FL_POWER_ARMOR 0x00001000 /* power armor (if any) is active */
+#define FL_COOP_TAKEN 0x00002000 /* Another client has already taken it */
+#define FL_RESPAWN 0x80000000 /* used for item respawning */
+
+#define FRAMETIME 0.1
+
+ /* memory tags to allow dynamic memory to be cleaned up */
+#define TAG_GAME 765 /* clear when unloading the dll */
+#define TAG_LEVEL 766 /* clear when loading a new level */
+
+#define MELEE_DISTANCE 80
+
+#define BODY_QUEUE_SIZE 8
+
+typedef enum
+{
+ DAMAGE_NO,
+ DAMAGE_YES, /* will take damage if hit */
+ DAMAGE_AIM /* auto targeting recognizes this */
+} damage_t;
+
+typedef enum
+{
+ WEAPON_READY,
+ WEAPON_ACTIVATING,
+ WEAPON_DROPPING,
+ WEAPON_FIRING
+} weaponstate_t;
+
+typedef enum
+{
+ AMMO_BULLETS,
+ AMMO_SHELLS,
+ AMMO_ROCKETS,
+ AMMO_GRENADES,
+ AMMO_CELLS,
+ AMMO_SLUGS,
+ AMMO_MAGSLUG,
+ AMMO_TRAP
+} ammo_t;
+
+/* Maximum debris / gibs per frame */
+#define MAX_GIBS 20
+#define MAX_DEBRIS 20
+
+/* deadflag */
+#define DEAD_NO 0
+#define DEAD_DYING 1
+#define DEAD_DEAD 2
+#define DEAD_RESPAWNABLE 3
+
+/* range */
+#define RANGE_MELEE 0
+#define RANGE_NEAR 1
+#define RANGE_MID 2
+#define RANGE_FAR 3
+
+/* gib types */
+#define GIB_ORGANIC 0
+#define GIB_METALLIC 1
+
+/* monster ai flags */
+#define AI_STAND_GROUND 0x00000001
+#define AI_TEMP_STAND_GROUND 0x00000002
+#define AI_SOUND_TARGET 0x00000004
+#define AI_LOST_SIGHT 0x00000008
+#define AI_PURSUIT_LAST_SEEN 0x00000010
+#define AI_PURSUE_NEXT 0x00000020
+#define AI_PURSUE_TEMP 0x00000040
+#define AI_HOLD_FRAME 0x00000080
+#define AI_GOOD_GUY 0x00000100
+#define AI_BRUTAL 0x00000200
+#define AI_NOSTEP 0x00000400
+#define AI_DUCKED 0x00000800
+#define AI_COMBAT_POINT 0x00001000
+#define AI_MEDIC 0x00002000
+#define AI_RESURRECTING 0x00004000
+#define AI_IGNORE_PAIN 0x00008000
+
+/* monster attack state */
+#define AS_STRAIGHT 1
+#define AS_SLIDING 2
+#define AS_MELEE 3
+#define AS_MISSILE 4
+
+/* armor types */
+#define ARMOR_NONE 0
+#define ARMOR_JACKET 1
+#define ARMOR_COMBAT 2
+#define ARMOR_BODY 3
+#define ARMOR_SHARD 4
+
+/* power armor types */
+#define POWER_ARMOR_NONE 0
+#define POWER_ARMOR_SCREEN 1
+#define POWER_ARMOR_SHIELD 2
+
+/* handedness values */
+#define RIGHT_HANDED 0
+#define LEFT_HANDED 1
+#define CENTER_HANDED 2
+
+/* game.serverflags values */
+#define SFL_CROSS_TRIGGER_1 0x00000001
+#define SFL_CROSS_TRIGGER_2 0x00000002
+#define SFL_CROSS_TRIGGER_3 0x00000004
+#define SFL_CROSS_TRIGGER_4 0x00000008
+#define SFL_CROSS_TRIGGER_5 0x00000010
+#define SFL_CROSS_TRIGGER_6 0x00000020
+#define SFL_CROSS_TRIGGER_7 0x00000040
+#define SFL_CROSS_TRIGGER_8 0x00000080
+#define SFL_CROSS_TRIGGER_MASK 0x000000ff
+
+/* noise types for PlayerNoise */
+#define PNOISE_SELF 0
+#define PNOISE_WEAPON 1
+#define PNOISE_IMPACT 2
+
+/* edict->movetype values */
+typedef enum
+{
+ MOVETYPE_NONE, /* never moves */
+ MOVETYPE_NOCLIP, /* origin and angles change with no interaction */
+ MOVETYPE_PUSH, /* no clip to world, push on box contact */
+ MOVETYPE_STOP, /* no clip to world, stops on box contact */
+
+ MOVETYPE_WALK, /* gravity */
+ MOVETYPE_STEP, /* gravity, special edge handling */
+ MOVETYPE_FLY,
+ MOVETYPE_TOSS, /* gravity */
+ MOVETYPE_FLYMISSILE, /* extra size to monsters */
+ MOVETYPE_BOUNCE, /* added this (the comma at the end of line) */
+ MOVETYPE_WALLBOUNCE
+} movetype_t;
+
+typedef struct
+{
+ int base_count;
+ int max_count;
+ float normal_protection;
+ float energy_protection;
+ int armor;
+} gitem_armor_t;
+
+/* gitem_t->flags */
+#define IT_WEAPON 1 /* use makes active weapon */
+#define IT_AMMO 2
+#define IT_ARMOR 4
+#define IT_STAY_COOP 8
+#define IT_KEY 16
+#define IT_POWERUP 32
+#define IT_INSTANT_USE 64 /* item is insta-used on pickup if dmflag is set */
+
+/* gitem_t->weapmodel for weapons indicates model index */
+#define WEAP_BLASTER 1
+#define WEAP_SHOTGUN 2
+#define WEAP_SUPERSHOTGUN 3
+#define WEAP_MACHINEGUN 4
+#define WEAP_CHAINGUN 5
+#define WEAP_GRENADES 6
+#define WEAP_GRENADELAUNCHER 7
+#define WEAP_ROCKETLAUNCHER 8
+#define WEAP_HYPERBLASTER 9
+#define WEAP_RAILGUN 10
+#define WEAP_BFG 11
+#define WEAP_PHALANX 12
+#define WEAP_BOOMER 13
+
+typedef struct gitem_s
+{
+ char *classname; /* spawning name */
+ qboolean (*pickup)(struct edict_s *ent, struct edict_s *other);
+ void (*use)(struct edict_s *ent, struct gitem_s *item);
+ void (*drop)(struct edict_s *ent, struct gitem_s *item);
+ void (*weaponthink)(struct edict_s *ent);
+ char *pickup_sound;
+ char *world_model;
+ int world_model_flags;
+ char *view_model;
+
+ /* client side info */
+ char *icon;
+ char *pickup_name; /* for printing on pickup */
+ int count_width; /* number of digits to display by icon */
+
+ int quantity; /* for ammo how much, for weapons how much is used per shot */
+ char *ammo; /* for weapons */
+ int flags; /* IT_* flags */
+
+ int weapmodel; /* weapon model index (for weapons) */
+
+ void *info;
+ int tag;
+
+ char *precaches; /* string of all models, sounds, and images this item will use */
+} gitem_t;
+
+/* this structure is left intact through an entire game
+ it should be initialized at dll load time, and read/written to
+ the server.ssv file for savegames */
+typedef struct
+{
+ char helpmessage1[512];
+ char helpmessage2[512];
+ int helpchanged; /* flash F1 icon if non 0, play sound
+ and increment only if 1, 2, or 3 */
+
+ gclient_t *clients; /* [maxclients] */
+
+ /* can't store spawnpoint in level, because
+ it would get overwritten by the savegame restore */
+ char spawnpoint[512]; /* needed for coop respawns */
+
+ /* store latched cvars here that we want to get at often */
+ int maxclients;
+ int maxentities;
+
+ /* cross level triggers */
+ int serverflags;
+
+ /* items */
+ int num_items;
+
+ qboolean autosaved;
+} game_locals_t;
+
+/* this structure is cleared as each map is entered
+ it is read/written to the level.sav file for savegames */
+typedef struct
+{
+ int framenum;
+ float time;
+
+ char level_name[MAX_QPATH]; /* the descriptive name (Outer Base, etc) */
+ char mapname[MAX_QPATH]; /* the server name (base1, etc) */
+ char nextmap[MAX_QPATH]; /* go here when fraglimit is hit */
+
+ /* intermission state */
+ float intermissiontime; /* time the intermission was started */
+ char *changemap;
+ int exitintermission;
+ vec3_t intermission_origin;
+ vec3_t intermission_angle;
+
+ edict_t *sight_client; /* changed once each frame for coop games */
+
+ edict_t *sight_entity;
+ int sight_entity_framenum;
+ edict_t *sound_entity;
+ int sound_entity_framenum;
+ edict_t *sound2_entity;
+ int sound2_entity_framenum;
+
+ int pic_health;
+
+ int total_secrets;
+ int found_secrets;
+
+ int total_goals;
+ int found_goals;
+
+ int total_monsters;
+ int killed_monsters;
+
+ edict_t *current_entity; /* entity running from G_RunFrame */
+ int body_que; /* dead bodies */
+
+ int power_cubes; /* ugly necessity for coop */
+} level_locals_t;
+
+/* spawn_temp_t is only used to hold entity field values that
+ can be set from the editor, but aren't actualy present
+ in edict_t during gameplay */
+typedef struct
+{
+ /* world vars */
+ char *sky;
+ float skyrotate;
+ vec3_t skyaxis;
+ char *nextmap;
+
+ int lip;
+ int distance;
+ int height;
+ char *noise;
+ float pausetime;
+ char *item;
+ char *gravity;
+
+ float minyaw;
+ float maxyaw;
+ float minpitch;
+ float maxpitch;
+} spawn_temp_t;
+
+typedef struct
+{
+ /* fixed data */
+ vec3_t start_origin;
+ vec3_t start_angles;
+ vec3_t end_origin;
+ vec3_t end_angles;
+
+ int sound_start;
+ int sound_middle;
+ int sound_end;
+
+ float accel;
+ float speed;
+ float decel;
+ float distance;
+
+ float wait;
+
+ /* state data */
+ int state;
+ vec3_t dir;
+ float current_speed;
+ float move_speed;
+ float next_speed;
+ float remaining_distance;
+ float decel_distance;
+ void (*endfunc)(edict_t *);
+} moveinfo_t;
+
+typedef struct
+{
+ void (*aifunc)(edict_t *self, float dist);
+ float dist;
+ void (*thinkfunc)(edict_t *self);
+} mframe_t;
+
+typedef struct
+{
+ int firstframe;
+ int lastframe;
+ mframe_t *frame;
+ void (*endfunc)(edict_t *self);
+} mmove_t;
+
+typedef struct
+{
+ mmove_t *currentmove;
+ int aiflags;
+ int nextframe;
+ float scale;
+
+ void (*stand)(edict_t *self);
+ void (*idle)(edict_t *self);
+ void (*search)(edict_t *self);
+ void (*walk)(edict_t *self);
+ void (*run)(edict_t *self);
+ void (*dodge)(edict_t *self, edict_t *other, float eta);
+ void (*attack)(edict_t *self);
+ void (*melee)(edict_t *self);
+ void (*sight)(edict_t *self, edict_t *other);
+ qboolean (*checkattack)(edict_t *self);
+
+ float pausetime;
+ float attack_finished;
+
+ vec3_t saved_goal;
+ float search_time;
+ float trail_time;
+ vec3_t last_sighting;
+ int attack_state;
+ int lefty;
+ float idle_time;
+ int linkcount;
+
+ int power_armor_type;
+ int power_armor_power;
+} monsterinfo_t;
+
+extern game_locals_t game;
+extern level_locals_t level;
+extern game_import_t gi;
+extern game_export_t globals;
+extern spawn_temp_t st;
+
+extern int sm_meat_index;
+extern int snd_fry;
+
+extern int debristhisframe;
+extern int gibsthisframe;
+
+/* means of death */
+#define MOD_UNKNOWN 0
+#define MOD_BLASTER 1
+#define MOD_SHOTGUN 2
+#define MOD_SSHOTGUN 3
+#define MOD_MACHINEGUN 4
+#define MOD_CHAINGUN 5
+#define MOD_GRENADE 6
+#define MOD_G_SPLASH 7
+#define MOD_ROCKET 8
+#define MOD_R_SPLASH 9
+#define MOD_HYPERBLASTER 10
+#define MOD_RAILGUN 11
+#define MOD_BFG_LASER 12
+#define MOD_BFG_BLAST 13
+#define MOD_BFG_EFFECT 14
+#define MOD_HANDGRENADE 15
+#define MOD_HG_SPLASH 16
+#define MOD_WATER 17
+#define MOD_SLIME 18
+#define MOD_LAVA 19
+#define MOD_CRUSH 20
+#define MOD_TELEFRAG 21
+#define MOD_FALLING 22
+#define MOD_SUICIDE 23
+#define MOD_HELD_GRENADE 24
+#define MOD_EXPLOSIVE 25
+#define MOD_BARREL 26
+#define MOD_BOMB 27
+#define MOD_EXIT 28
+#define MOD_SPLASH 29
+#define MOD_TARGET_LASER 30
+#define MOD_TRIGGER_HURT 31
+#define MOD_HIT 32
+#define MOD_TARGET_BLASTER 33
+#define MOD_RIPPER 34
+#define MOD_PHALANX 35
+#define MOD_BRAINTENTACLE 36
+#define MOD_BLASTOFF 37
+#define MOD_GEKK 38
+#define MOD_TRAP 39
+#define MOD_FRIENDLY_FIRE 0x8000000
+
+/* Easier handling of AI skill levels */
+#define SKILL_EASY 0
+#define SKILL_MEDIUM 1
+#define SKILL_HARD 2
+#define SKILL_HARDPLUS 3
+
+extern int meansOfDeath;
+extern edict_t *g_edicts;
+
+#define FOFS(x) (size_t)&(((edict_t *)NULL)->x)
+#define STOFS(x) (size_t)&(((spawn_temp_t *)NULL)->x)
+#define LLOFS(x) (size_t)&(((level_locals_t *)NULL)->x)
+#define CLOFS(x) (size_t)&(((gclient_t *)NULL)->x)
+
+#define random() ((randk() & 0x7fff) / ((float)0x7fff))
+#define crandom() (2.0 * (random() - 0.5))
+
+extern cvar_t *maxentities;
+extern cvar_t *deathmatch;
+extern cvar_t *coop;
+extern cvar_t *coop_elevator_delay;
+extern cvar_t *coop_pickup_weapons;
+extern cvar_t *dmflags;
+extern cvar_t *skill;
+extern cvar_t *fraglimit;
+extern cvar_t *timelimit;
+extern cvar_t *password;
+extern cvar_t *spectator_password;
+extern cvar_t *needpass;
+extern cvar_t *g_select_empty;
+extern cvar_t *dedicated;
+extern cvar_t *g_footsteps;
+extern cvar_t *g_fix_triggered;
+
+extern cvar_t *filterban;
+
+extern cvar_t *sv_gravity;
+extern cvar_t *sv_maxvelocity;
+
+extern cvar_t *gun_x, *gun_y, *gun_z;
+extern cvar_t *sv_rollspeed;
+extern cvar_t *sv_rollangle;
+
+extern cvar_t *run_pitch;
+extern cvar_t *run_roll;
+extern cvar_t *bob_up;
+extern cvar_t *bob_pitch;
+extern cvar_t *bob_roll;
+
+extern cvar_t *sv_cheats;
+extern cvar_t *maxclients;
+extern cvar_t *maxspectators;
+
+extern cvar_t *flood_msgs;
+extern cvar_t *flood_persecond;
+extern cvar_t *flood_waitdelay;
+
+extern cvar_t *sv_maplist;
+
+extern cvar_t *aimfix;
+extern cvar_t *g_machinegun_norecoil;
+extern cvar_t *g_swap_speed;
+
+#define world (&g_edicts[0])
+
+ /* item spawnflags */
+#define ITEM_TRIGGER_SPAWN 0x00000001
+#define ITEM_NO_TOUCH 0x00000002
+ /* 6 bits reserved for editor flags
+ 8 bits used as power cube id bits
+ for coop games */
+#define DROPPED_ITEM 0x00010000
+#define DROPPED_PLAYER_ITEM 0x00020000
+#define ITEM_TARGETS_USED 0x00040000
+
+ /* fields are needed for spawning from the
+ entity string and saving / loading games */
+#define FFL_SPAWNTEMP 1
+#define FFL_NOSPAWN 2
+
+typedef enum
+{
+ F_INT,
+ F_FLOAT,
+ F_LSTRING, /* string on disk, pointer in memory, TAG_LEVEL */
+ F_GSTRING, /* string on disk, pointer in memory, TAG_GAME */
+ F_VECTOR,
+ F_ANGLEHACK,
+ F_EDICT, /* index on disk, pointer in memory */
+ F_ITEM, /* index on disk, pointer in memory */
+ F_CLIENT, /* index on disk, pointer in memory */
+ F_FUNCTION,
+ F_MMOVE,
+ F_IGNORE
+} fieldtype_t;
+
+typedef struct
+{
+ char *name;
+ int ofs;
+ fieldtype_t type;
+ int flags;
+ short save_ver;
+} field_t;
+
+extern field_t fields[];
+extern gitem_t itemlist[];
+
+/* g_cmds.c */
+void Cmd_Help_f(edict_t *ent);
+
+/* g_items.c */
+void PrecacheItem(gitem_t *it);
+void InitItems(void);
+void SetItemNames(void);
+gitem_t *FindItem(char *pickup_name);
+gitem_t *FindItemByClassname(char *classname);
+
+#define ITEM_INDEX(x) ((x) - itemlist)
+edict_t *Drop_Item(edict_t *ent, gitem_t *item);
+void SetRespawn(edict_t *ent, float delay);
+void ChangeWeapon(edict_t *ent);
+void SpawnItem(edict_t *ent, gitem_t *item);
+void Think_Weapon(edict_t *ent);
+int ArmorIndex(edict_t *ent);
+int PowerArmorType(edict_t *ent);
+gitem_t *GetItemByIndex(int index);
+qboolean Add_Ammo(edict_t *ent, gitem_t *item, int count);
+void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+/* g_utils.c */
+qboolean KillBox(edict_t *ent);
+void G_ProjectSource(vec3_t point, vec3_t distance, vec3_t forward,
+ vec3_t right, vec3_t result);
+edict_t *G_Find(edict_t *from, int fieldofs, char *match);
+edict_t *findradius(edict_t *from, vec3_t org, float rad);
+edict_t *G_PickTarget(char *targetname);
+void G_UseTargets(edict_t *ent, edict_t *activator);
+void G_SetMovedir(vec3_t angles, vec3_t movedir);
+
+void G_InitEdict(edict_t *e);
+edict_t *G_SpawnOptional(void);
+edict_t *G_Spawn(void);
+void G_FreeEdict(edict_t *e);
+
+void G_TouchTriggers(edict_t *ent);
+void G_TouchSolids(edict_t *ent);
+
+char *G_CopyString(char *in);
+
+float *tv(float x, float y, float z);
+char *vtos(vec3_t v);
+void get_normal_vector(const cplane_t *p, vec3_t normal);
+
+float vectoyaw(vec3_t vec);
+void vectoangles(vec3_t vec, vec3_t angles);
+
+/* g_combat.c */
+qboolean OnSameTeam(edict_t *ent1, edict_t *ent2);
+qboolean CanDamage(edict_t *targ, edict_t *inflictor);
+void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker,
+ vec3_t dir, vec3_t point, vec3_t normal, int damage,
+ int knockback, int dflags, int mod);
+void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage,
+ edict_t *ignore, float radius, int mod);
+
+/* damage flags */
+#define DAMAGE_RADIUS 0x00000001 /* damage was indirect */
+#define DAMAGE_NO_ARMOR 0x00000002 /* armour does not protect from this damage */
+#define DAMAGE_ENERGY 0x00000004 /* damage is from an energy based weapon */
+#define DAMAGE_NO_KNOCKBACK 0x00000008 /* do not affect velocity, just view angles */
+#define DAMAGE_BULLET 0x00000010 /* damage is from a bullet (used for ricochets) */
+#define DAMAGE_NO_PROTECTION 0x00000020 /* armor, shields, invulnerability, and godmode have no effect */
+
+#define DEFAULT_BULLET_HSPREAD 300
+#define DEFAULT_BULLET_VSPREAD 500
+#define DEFAULT_SHOTGUN_HSPREAD 1000
+#define DEFAULT_SHOTGUN_VSPREAD 500
+#define DEFAULT_SHOTGUN_COUNT 12
+#define DEFAULT_SSHOTGUN_COUNT 20
+
+/* g_monster.c */
+void monster_fire_bullet(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int kick, int hspread, int vspread, int flashtype);
+void monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int count, int flashtype);
+void monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect);
+void monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int flashtype);
+void monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype);
+void monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int flashtype);
+void monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int kick, float damage_radius, int flashtype);
+void monster_fire_ionripper(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect);
+void monster_fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype);
+void monster_dabeam(edict_t *self);
+void monster_fire_blueblaster(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, int flashtype, int effect);
+
+void M_droptofloor(edict_t *ent);
+void monster_think(edict_t *self);
+void walkmonster_start(edict_t *self);
+void swimmonster_start(edict_t *self);
+void flymonster_start(edict_t *self);
+void AttackFinished(edict_t *self, float time);
+void monster_death_use(edict_t *self);
+void M_CatagorizePosition(edict_t *ent);
+qboolean M_CheckAttack(edict_t *self);
+void M_FlyCheck(edict_t *self);
+void M_CheckGround(edict_t *ent);
+
+/* g_misc.c */
+void ThrowHead(edict_t *self, char *gibname, int damage, int type);
+void ThrowClientHead(edict_t *self, int damage);
+void ThrowGib(edict_t *self, char *gibname, int damage, int type);
+void BecomeExplosion1(edict_t *self);
+void ThrowHeadACID(edict_t *self, char *gibname, int damage, int type);
+void ThrowGibACID(edict_t *self, char *gibname, int damage, int type);
+
+/* g_ai.c */
+void AI_SetSightClient(void);
+void ai_stand(edict_t *self, float dist);
+void ai_move(edict_t *self, float dist);
+void ai_walk(edict_t *self, float dist);
+void ai_turn(edict_t *self, float dist);
+void ai_run(edict_t *self, float dist);
+void ai_charge(edict_t *self, float dist);
+int range(edict_t *self, edict_t *other);
+
+void FoundTarget(edict_t *self);
+qboolean infront(edict_t *self, edict_t *other);
+qboolean visible(edict_t *self, edict_t *other);
+qboolean FacingIdeal(edict_t *self);
+
+/* g_weapon.c */
+void ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin);
+qboolean fire_hit(edict_t *self, vec3_t aim, int damage, int kick);
+void fire_bullet(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int mod);
+void fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int hspread, int vspread, int count, int mod);
+void fire_blaster(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int effect, qboolean hyper);
+void fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius);
+void fire_grenade2(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius, qboolean held);
+void fire_rocket(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, float damage_radius,
+ int radius_damage);
+void fire_rail(edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick);
+void fire_bfg(edict_t *self, vec3_t start, vec3_t dir, int damage,
+ int speed, float damage_radius);
+void fire_ionripper(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int effect);
+void fire_heat(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed,
+ float damage_radius, int radius_damage);
+void fire_blueblaster(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, int effect);
+void fire_plasma(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed,
+ float damage_radius, int radius_damage);
+void fire_trap(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int speed, float timer, float damage_radius, qboolean held);
+
+/* g_ptrail.c */
+void PlayerTrail_Init(void);
+void PlayerTrail_Add(vec3_t spot);
+void PlayerTrail_New(vec3_t spot);
+edict_t *PlayerTrail_PickFirst(edict_t *self);
+edict_t *PlayerTrail_PickNext(edict_t *self);
+edict_t *PlayerTrail_LastSpot(void);
+
+/* g_client.c */
+void respawn(edict_t *ent);
+void BeginIntermission(edict_t *targ);
+void PutClientInServer(edict_t *ent);
+void InitClientPersistant(gclient_t *client);
+void InitClientResp(gclient_t *client);
+void InitBodyQue(void);
+void ClientBeginServerFrame(edict_t *ent);
+
+ /* g_player.c */
+void player_pain(edict_t *self, edict_t *other, float kick, int damage);
+void player_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+/* g_svcmds.c */
+void ServerCommand(void);
+qboolean SV_FilterPacket(char *from);
+
+/* p_view.c */
+void ClientEndServerFrame(edict_t *ent);
+
+/* p_hud.c */
+void MoveClientToIntermission(edict_t *client);
+void G_SetStats(edict_t *ent);
+void G_SetSpectatorStats(edict_t *ent);
+void G_CheckChaseStats(edict_t *ent);
+void ValidateSelectedItem(edict_t *ent);
+void DeathmatchScoreboardMessage(edict_t *client, edict_t *killer);
+void HelpComputerMessage(edict_t *client);
+void InventoryMessage(edict_t *client);
+
+/* g_pweapon.c */
+void PlayerNoise(edict_t *who, vec3_t where, int type);
+
+/* m_move.c */
+qboolean M_CheckBottom(edict_t *ent);
+qboolean M_walkmove(edict_t *ent, float yaw, float dist);
+void M_MoveToGoal(edict_t *ent, float dist);
+void M_ChangeYaw(edict_t *ent);
+
+/* g_phys.c */
+void G_RunEntity(edict_t *ent);
+
+/* g_main.c */
+void SaveClientData(void);
+void FetchClientEntData(edict_t *ent);
+
+/* g_chase.c */
+void UpdateChaseCam(edict_t *ent);
+void ChaseNext(edict_t *ent);
+void ChasePrev(edict_t *ent);
+void GetChaseTarget(edict_t *ent);
+
+ /* ============================================================================ */
+
+/* client_t->anim_priority */
+#define ANIM_BASIC 0 /* stand / run */
+#define ANIM_WAVE 1
+#define ANIM_JUMP 2
+#define ANIM_PAIN 3
+#define ANIM_ATTACK 4
+#define ANIM_DEATH 5
+#define ANIM_REVERSE 6
+
+/* client data that stays across multiple level loads */
+typedef struct
+{
+ char userinfo[MAX_INFO_STRING];
+ char netname[16];
+ int hand;
+
+ qboolean connected; /* a loadgame will leave valid entities that
+ just don't have a connection yet */
+
+ /* values saved and restored from
+ edicts when changing levels */
+ int health;
+ int max_health;
+ int savedFlags;
+
+ int selected_item;
+ int inventory[MAX_ITEMS];
+
+ /* ammo capacities */
+ int max_bullets;
+ int max_shells;
+ int max_rockets;
+ int max_grenades;
+ int max_cells;
+ int max_slugs;
+ int max_magslug;
+ int max_trap;
+
+ gitem_t *weapon;
+ gitem_t *lastweapon;
+
+ int power_cubes; /* used for tracking the cubes in coop games */
+ int score; /* for calculating total unit score in coop games */
+
+ int game_helpchanged;
+ int helpchanged;
+
+ qboolean spectator; /* client is a spectator */
+} client_persistant_t;
+
+/* client data that stays across deathmatch respawns */
+typedef struct
+{
+ client_persistant_t coop_respawn; /* what to set client->pers to on a respawn */
+ int enterframe; /* level.framenum the client entered the game */
+ int score; /* frags, etc */
+ vec3_t cmd_angles; /* angles sent over in the last command */
+
+ qboolean spectator; /* client is a spectator */
+} client_respawn_t;
+
+/* this structure is cleared on each
+ PutClientInServer(), except for
+ 'client->pers' */
+struct gclient_s
+{
+ /* known to server */
+ player_state_t ps; /* communicated by server to clients */
+ int ping;
+
+ /* private to game */
+ client_persistant_t pers;
+ client_respawn_t resp;
+ pmove_state_t old_pmove; /* for detecting out-of-pmove changes */
+
+ qboolean showscores; /* set layout stat */
+ qboolean showinventory; /* set layout stat */
+ qboolean showhelp;
+ qboolean showhelpicon;
+
+ int ammo_index;
+
+ int buttons;
+ int oldbuttons;
+ int latched_buttons;
+
+ qboolean weapon_thunk;
+
+ gitem_t *newweapon;
+
+ /* sum up damage over an entire frame, so
+ shotgun blasts give a single big kick */
+ int damage_armor; /* damage absorbed by armor */
+ int damage_parmor; /* damage absorbed by power armor */
+ int damage_blood; /* damage taken out of health */
+ int damage_knockback; /* impact damage */
+ vec3_t damage_from; /* origin for vector calculation */
+
+ float killer_yaw; /* when dead, look at killer */
+
+ weaponstate_t weaponstate;
+ vec3_t kick_angles; /* weapon kicks */
+ vec3_t kick_origin;
+ float v_dmg_roll, v_dmg_pitch, v_dmg_time; /* damage kicks */
+ float fall_time, fall_value; /* for view drop on fall */
+ float damage_alpha;
+ float bonus_alpha;
+ vec3_t damage_blend;
+ vec3_t v_angle; /* aiming direction */
+ float bobtime; /* so off-ground doesn't change it */
+ vec3_t oldviewangles;
+ vec3_t oldvelocity;
+
+ float next_drown_time;
+ int old_waterlevel;
+ int breather_sound;
+
+ int machinegun_shots; /* for weapon raising */
+
+ /* animation vars */
+ int anim_end;
+ int anim_priority;
+ qboolean anim_duck;
+ qboolean anim_run;
+
+ /* powerup timers */
+ float quad_framenum;
+ float invincible_framenum;
+ float breather_framenum;
+ float enviro_framenum;
+
+ qboolean grenade_blew_up;
+ float grenade_time;
+ float quadfire_framenum;
+ qboolean trap_blew_up;
+ float trap_time;
+
+ int silencer_shots;
+ int weapon_sound;
+
+ float pickup_msg_time;
+
+ float flood_locktill; /* locked from talking */
+ float flood_when[10]; /* when messages were said */
+ int flood_whenhead; /* head pointer for when said */
+
+ float respawn_time; /* can respawn when time > this */
+
+ edict_t *chase_target; /* player we are chasing */
+ qboolean update_chase; /* need to update chase info? */
+};
+
+struct edict_s
+{
+ entity_state_t s;
+ struct gclient_s *client; /* NULL if not a player */
+
+ /* the server expects the first part
+ of gclient_s to be a player_state_t
+ but the rest of it is opaque */
+
+ qboolean inuse;
+ int linkcount;
+
+ link_t area; /* linked to a division node or leaf */
+
+ int num_clusters; /* if -1, use headnode instead */
+ int clusternums[MAX_ENT_CLUSTERS];
+ int headnode; /* unused if num_clusters != -1 */
+ int areanum, areanum2;
+
+ /* ================================ */
+
+ int svflags;
+ vec3_t mins, maxs;
+ vec3_t absmin, absmax, size;
+ solid_t solid;
+ int clipmask;
+ edict_t *owner;
+
+ /* DO NOT MODIFY ANYTHING ABOVE THIS, THE
+ SERVER EXPECTS THE FIELDS IN THAT ORDER! */
+
+ /* ================================ */
+
+ int movetype;
+ int flags;
+
+ char *model;
+ float freetime; /* sv.time when the object was freed */
+
+ /* only used locally in game, not by server */
+ char *message;
+ char *classname;
+ int spawnflags;
+
+ float timestamp;
+
+ float angle; /* set in qe3, -1 = up, -2 = down */
+ char *target;
+ char *targetname;
+ char *killtarget;
+ char *team;
+ char *pathtarget;
+ char *deathtarget;
+ char *combattarget;
+ edict_t *target_ent;
+
+ float speed, accel, decel;
+ vec3_t movedir;
+ vec3_t pos1, pos2;
+
+ vec3_t velocity;
+ vec3_t avelocity;
+ int mass;
+ float air_finished;
+ float gravity; /* per entity gravity multiplier (1.0 is
+ normal) use for lowgrav artifact, flares */
+
+ edict_t *goalentity;
+ edict_t *movetarget;
+ float yaw_speed;
+ float ideal_yaw;
+
+ float nextthink;
+ void (*prethink)(edict_t *ent);
+ void (*think)(edict_t *self);
+ void (*blocked)(edict_t *self, edict_t *other); /* move to moveinfo? */
+ void (*touch)(edict_t *self, edict_t *other, cplane_t *plane,
+ csurface_t *surf);
+ void (*use)(edict_t *self, edict_t *other, edict_t *activator);
+ void (*pain)(edict_t *self, edict_t *other, float kick, int damage);
+ void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+ float touch_debounce_time; /* now also used by fixbots for timeouts when getting stuck */
+ float pain_debounce_time;
+ float damage_debounce_time;
+ float fly_sound_debounce_time; /* now also used by insane marines to store pain sound timeout */
+ /* and by fixbots for storing object_repair timeout when getting stuck */
+ float last_move_time;
+
+ int health;
+ int max_health;
+ int gib_health;
+ int deadflag;
+
+ float show_hostile;
+ float powerarmor_time;
+
+ char *map; /* target_changelevel */
+
+ int viewheight; /* height above origin where eyesight is determined */
+ int takedamage;
+ int dmg;
+ int radius_dmg;
+ float dmg_radius;
+ int sounds; /* now also used for player death sound aggregation */
+ int count;
+
+ edict_t *chain;
+ edict_t *enemy;
+ edict_t *oldenemy;
+ edict_t *activator;
+ edict_t *groundentity;
+ int groundentity_linkcount;
+ edict_t *teamchain;
+ edict_t *teammaster;
+
+ edict_t *mynoise; /* can go in client only */
+ edict_t *mynoise2;
+
+ int noise_index;
+ int noise_index2;
+ float volume;
+ float attenuation;
+
+ /* timing variables */
+ float wait;
+ float delay; /* before firing targets */
+ float random;
+
+ float last_sound_time;
+
+ int watertype;
+ int waterlevel;
+
+ vec3_t move_origin;
+ vec3_t move_angles;
+
+ /* move this to clientinfo? */
+ int light_level;
+
+ int style; /* also used as areaportal number */
+
+ gitem_t *item; /* for bonus items */
+
+ /* common data blocks */
+ moveinfo_t moveinfo;
+ monsterinfo_t monsterinfo;
+
+ int orders;
+};
+
+#endif /* XATRIX_LOCAL_H */
diff --git a/xatrix/src/header/shared.h b/xatrix/src/header/shared.h
new file mode 100644
index 0000000..f623cc3
--- /dev/null
+++ b/xatrix/src/header/shared.h
@@ -0,0 +1,1082 @@
+/*
+ * =======================================================================
+ *
+ * This is the main header file shared between client, renderer, server
+ * and the game.
+ *
+ * =======================================================================
+ */
+
+#ifndef XATRIX_SHARED_H
+#define XATRIX_SHARED_H
+
+#include <assert.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+typedef unsigned char byte;
+typedef enum {false, true} qboolean;
+
+#ifndef NULL
+ #define NULL ((void *)0)
+#endif
+
+/* angle indexes */
+#define PITCH 0 /* up / down */
+#define YAW 1 /* left / right */
+#define ROLL 2 /* fall over */
+
+#define MAX_STRING_CHARS 1024 /* max length of a string passed to Cmd_TokenizeString */
+#define MAX_STRING_TOKENS 80 /* max tokens resulting from Cmd_TokenizeString */
+#define MAX_TOKEN_CHARS 128 /* max length of an individual token */
+
+#define MAX_QPATH 64 /* max length of a quake game pathname */
+
+#ifdef _WIN32
+#define MAX_OSPATH 256 /* max length of a filesystem pathname (same as MAX_PATH) */
+#else
+#define MAX_OSPATH 128 /* max length of a filesystem pathname */
+#endif
+
+/* */
+/* per-level limits */
+/* */
+#define MAX_CLIENTS 256 /* absolute limit */
+#define MAX_EDICTS 1024 /* must change protocol to increase more */
+#define MAX_LIGHTSTYLES 256
+#define MAX_MODELS 256 /* these are sent over the net as bytes */
+#define MAX_SOUNDS 256 /* so they cannot be blindly increased */
+#define MAX_IMAGES 256
+#define MAX_ITEMS 256
+#define MAX_GENERAL (MAX_CLIENTS * 2) /* general config strings */
+
+/* game print flags */
+#define PRINT_LOW 0 /* pickup messages */
+#define PRINT_MEDIUM 1 /* death messages */
+#define PRINT_HIGH 2 /* critical messages */
+#define PRINT_CHAT 3 /* chat messages */
+
+#define ERR_FATAL 0 /* exit the entire game with a popup window */
+#define ERR_DROP 1 /* print to console and disconnect from game */
+#define ERR_DISCONNECT 2 /* don't kill server */
+
+#define PRINT_ALL 0
+#define PRINT_DEVELOPER 1 /* only print when "developer 1" */
+#define PRINT_ALERT 2
+
+/* destination class for gi.multicast() */
+typedef enum
+{
+ MULTICAST_ALL,
+ MULTICAST_PHS,
+ MULTICAST_PVS,
+ MULTICAST_ALL_R,
+ MULTICAST_PHS_R,
+ MULTICAST_PVS_R
+} multicast_t;
+
+/*
+ * ==============================================================
+ *
+ * MATHLIB
+ *
+ * ==============================================================
+ */
+
+typedef float vec_t;
+typedef vec_t vec3_t[3];
+typedef vec_t vec5_t[5];
+
+typedef int fixed4_t;
+typedef int fixed8_t;
+typedef int fixed16_t;
+
+#ifndef M_PI
+ #define M_PI 3.14159265358979323846 /* matches value in gcc v2 math.h */
+#endif
+
+struct cplane_s;
+
+extern vec3_t vec3_origin;
+
+#define nanmask (255 << 23)
+
+#define IS_NAN(x) (((*(int *)&x) & nanmask) == nanmask)
+
+#define Q_ftol(f) (long)(f)
+
+#define DotProduct(x, y) (x[0] * y[0] + x[1] * y[1] + x[2] * y[2])
+#define VectorSubtract(a, b, c) (c[0] = a[0] - b[0], c[1] = a[1] - b[1], c[2] = \
+ a[2] - b[2])
+#define VectorAdd(a, b, c) (c[0] = a[0] + b[0], c[1] = a[1] + b[1], c[2] = \
+ a[2] + b[2])
+#define VectorCopy(a, b) (b[0] = a[0], b[1] = a[1], b[2] = a[2])
+#define VectorClear(a) (a[0] = a[1] = a[2] = 0)
+#define VectorNegate(a, b) (b[0] = -a[0], b[1] = -a[1], b[2] = -a[2])
+#define VectorSet(v, x, y, z) (v[0] = (x), v[1] = (y), v[2] = (z))
+
+void VectorMA(vec3_t veca, float scale, vec3_t vecb, vec3_t vecc);
+
+/* just in case you do't want to use the macros */
+vec_t _DotProduct(vec3_t v1, vec3_t v2);
+void _VectorSubtract(vec3_t veca, vec3_t vecb, vec3_t out);
+void _VectorAdd(vec3_t veca, vec3_t vecb, vec3_t out);
+void _VectorCopy(vec3_t in, vec3_t out);
+
+void ClearBounds(vec3_t mins, vec3_t maxs);
+void AddPointToBounds(vec3_t v, vec3_t mins, vec3_t maxs);
+int VectorCompare(vec3_t v1, vec3_t v2);
+vec_t VectorLength(vec3_t v);
+void CrossProduct(vec3_t v1, vec3_t v2, vec3_t cross);
+vec_t VectorNormalize(vec3_t v); /* returns vector length */
+vec_t VectorNormalize2(vec3_t v, vec3_t out);
+void VectorInverse(vec3_t v);
+void VectorScale(vec3_t in, vec_t scale, vec3_t out);
+int Q_log2(int val);
+
+void R_ConcatRotations(float in1[3][3], float in2[3][3], float out[3][3]);
+void R_ConcatTransforms(float in1[3][4], float in2[3][4], float out[3][4]);
+
+void AngleVectors(vec3_t angles, vec3_t forward, vec3_t right, vec3_t up);
+void AngleVectors2(vec3_t value1, vec3_t angles);
+int BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *plane);
+float anglemod(float a);
+float LerpAngle(float a1, float a2, float frac);
+
+#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \
+ (((p)->type < 3) ? \
+ ( \
+ ((p)->dist <= (emins)[(p)->type]) ? \
+ 1 \
+ : \
+ ( \
+ ((p)->dist >= (emaxs)[(p)->type]) ? \
+ 2 \
+ : \
+ 3 \
+ ) \
+ ) \
+ : \
+ BoxOnPlaneSide((emins), (emaxs), (p)))
+
+void ProjectPointOnPlane(vec3_t dst, const vec3_t p, const vec3_t normal);
+void PerpendicularVector(vec3_t dst, const vec3_t src);
+void RotatePointAroundVector(vec3_t dst, const vec3_t dir,
+ const vec3_t point, float degrees);
+
+/* ============================================= */
+
+char *COM_SkipPath(char *pathname);
+void COM_StripExtension(char *in, char *out);
+void COM_FileBase(char *in, char *out);
+void COM_FilePath(const char *in, char *out);
+void COM_DefaultExtension(char *path, const char *extension);
+
+char *COM_Parse(char **data_p);
+
+/* data is an in/out parm, returns a parsed out token */
+void Com_sprintf(char *dest, int size, char *fmt, ...);
+
+void Com_PageInMemory(byte *buffer, int size);
+
+char *strlwr ( char *s );
+
+/* ============================================= */
+
+/* portable case insensitive compare */
+int Q_stricmp(const char *s1, const char *s2);
+int Q_strcasecmp(char *s1, char *s2);
+int Q_strncasecmp(char *s1, char *s2, int n);
+
+/* ============================================= */
+
+short BigShort(short l);
+short LittleShort(short l);
+int BigLong(int l);
+int LittleLong(int l);
+float BigFloat(float l);
+float LittleFloat(float l);
+
+void Swap_Init(void);
+char *va(const char *format, ...);
+
+/* ============================================= */
+
+/* key / value info strings */
+#define MAX_INFO_KEY 64
+#define MAX_INFO_VALUE 64
+#define MAX_INFO_STRING 512
+
+char *Info_ValueForKey(char *s, char *key);
+void Info_RemoveKey(char *s, char *key);
+void Info_SetValueForKey(char *s, char *key, char *value);
+qboolean Info_Validate(char *s);
+
+/* ============================================= */
+
+/* Random number generator */
+int randk(void);
+float frandk(void);
+float crandk(void);
+void randk_seed(void);
+
+/*
+ * ==============================================================
+ *
+ * SYSTEM SPECIFIC
+ *
+ * ==============================================================
+ */
+
+extern int curtime; /* time returned by last Sys_Milliseconds */
+
+int Sys_Milliseconds(void);
+void Sys_Mkdir(char *path);
+char *strlwr(char *s);
+/* portable safe string copy/concatenate */
+int Q_strlcpy(char *dst, const char *src, int size);
+int Q_strlcat(char *dst, const char *src, int size);
+
+/* large block stack allocation routines */
+void *Hunk_Begin(int maxsize);
+void *Hunk_Alloc(int size);
+void Hunk_Free(void *buf);
+int Hunk_End(void);
+
+/* directory searching */
+#define SFF_ARCH 0x01
+#define SFF_HIDDEN 0x02
+#define SFF_RDONLY 0x04
+#define SFF_SUBDIR 0x08
+#define SFF_SYSTEM 0x10
+
+/* pass in an attribute mask of things you wish to REJECT */
+char *Sys_FindFirst(char *path, unsigned musthave, unsigned canthave);
+char *Sys_FindNext(unsigned musthave, unsigned canthave);
+void Sys_FindClose(void);
+
+/* this is only here so the functions in q_shared.c and q_shwin.c can link */
+void Sys_Error(char *error, ...);
+void Com_Printf(char *msg, ...);
+
+/*
+ * ==========================================================
+ *
+ * CVARS (console variables)
+ *
+ * ==========================================================
+ */
+
+#ifndef CVAR
+ #define CVAR
+
+ #define CVAR_ARCHIVE 1 /* set to cause it to be saved to vars.rc */
+ #define CVAR_USERINFO 2 /* added to userinfo when changed */
+ #define CVAR_SERVERINFO 4 /* added to serverinfo when changed */
+ #define CVAR_NOSET 8 /* don't allow change from console at all, */
+ /* but can be set from the command line */
+ #define CVAR_LATCH 16 /* save changes until server restart */
+
+/* nothing outside the Cvar_*() functions should modify these fields! */
+typedef struct cvar_s
+{
+ char *name;
+ char *string;
+ char *latched_string; /* for CVAR_LATCH vars */
+ int flags;
+ qboolean modified; /* set each time the cvar is changed */
+ float value;
+ struct cvar_s *next;
+} cvar_t;
+
+#endif /* CVAR */
+
+/*
+ * ==============================================================
+ *
+ * COLLISION DETECTION
+ *
+ * ==============================================================
+ */
+
+/* lower bits are stronger, and will eat weaker brushes completely */
+#define CONTENTS_SOLID 1 /* an eye is never valid in a solid */
+#define CONTENTS_WINDOW 2 /* translucent, but not watery */
+#define CONTENTS_AUX 4
+#define CONTENTS_LAVA 8
+#define CONTENTS_SLIME 16
+#define CONTENTS_WATER 32
+#define CONTENTS_MIST 64
+#define LAST_VISIBLE_CONTENTS 64
+
+/* remaining contents are non-visible, and don't eat brushes */
+#define CONTENTS_AREAPORTAL 0x8000
+
+#define CONTENTS_PLAYERCLIP 0x10000
+#define CONTENTS_MONSTERCLIP 0x20000
+
+/* currents can be added to any other contents, and may be mixed */
+#define CONTENTS_CURRENT_0 0x40000
+#define CONTENTS_CURRENT_90 0x80000
+#define CONTENTS_CURRENT_180 0x100000
+#define CONTENTS_CURRENT_270 0x200000
+#define CONTENTS_CURRENT_UP 0x400000
+#define CONTENTS_CURRENT_DOWN 0x800000
+
+#define CONTENTS_ORIGIN 0x1000000 /* removed before bsping an entity */
+
+#define CONTENTS_MONSTER 0x2000000 /* should never be on a brush, only in game */
+#define CONTENTS_DEADMONSTER 0x4000000
+#define CONTENTS_DETAIL 0x8000000 /* brushes to be added after vis leafs */
+#define CONTENTS_TRANSLUCENT 0x10000000 /* auto set if any surface has trans */
+#define CONTENTS_LADDER 0x20000000
+
+#define SURF_LIGHT 0x1 /* value will hold the light strength */
+
+#define SURF_SLICK 0x2 /* effects game physics */
+
+#define SURF_SKY 0x4 /* don't draw, but add to skybox */
+#define SURF_WARP 0x8 /* turbulent water warp */
+#define SURF_TRANS33 0x10
+#define SURF_TRANS66 0x20
+#define SURF_FLOWING 0x40 /* scroll towards angle */
+#define SURF_NODRAW 0x80 /* don't bother referencing the texture */
+
+/* content masks */
+#define MASK_ALL (-1)
+#define MASK_SOLID (CONTENTS_SOLID | CONTENTS_WINDOW)
+#define MASK_PLAYERSOLID (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | \
+ CONTENTS_WINDOW | CONTENTS_MONSTER)
+#define MASK_DEADSOLID (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW)
+#define MASK_MONSTERSOLID (CONTENTS_SOLID | CONTENTS_MONSTERCLIP | \
+ CONTENTS_WINDOW | CONTENTS_MONSTER)
+#define MASK_WATER (CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME)
+#define MASK_OPAQUE (CONTENTS_SOLID | CONTENTS_SLIME | CONTENTS_LAVA)
+#define MASK_SHOT (CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_WINDOW | \
+ CONTENTS_DEADMONSTER)
+#define MASK_CURRENT (CONTENTS_CURRENT_0 | CONTENTS_CURRENT_90 | \
+ CONTENTS_CURRENT_180 | CONTENTS_CURRENT_270 | \
+ CONTENTS_CURRENT_UP | \
+ CONTENTS_CURRENT_DOWN)
+
+/* gi.BoxEdicts() can return a list of either solid or trigger entities */
+#define AREA_SOLID 1
+#define AREA_TRIGGERS 2
+
+/* plane_t structure */
+typedef struct cplane_s
+{
+ vec3_t normal;
+ float dist;
+ byte type; /* for fast side tests */
+ byte signbits; /* signx + (signy<<1) + (signz<<1) */
+ byte pad[2];
+} cplane_t;
+
+/* structure offset for asm code */
+#define CPLANE_NORMAL_X 0
+#define CPLANE_NORMAL_Y 4
+#define CPLANE_NORMAL_Z 8
+#define CPLANE_DIST 12
+#define CPLANE_TYPE 16
+#define CPLANE_SIGNBITS 17
+#define CPLANE_PAD0 18
+#define CPLANE_PAD1 19
+
+typedef struct cmodel_s
+{
+ vec3_t mins, maxs;
+ vec3_t origin; /* for sounds or lights */
+ int headnode;
+} cmodel_t;
+
+typedef struct csurface_s
+{
+ char name[16];
+ int flags;
+ int value;
+} csurface_t;
+
+typedef struct mapsurface_s /* used internally due to name len probs */
+{
+ csurface_t c;
+ char rname[32];
+} mapsurface_t;
+
+/* a trace is returned when a box is swept through the world */
+typedef struct
+{
+ qboolean allsolid; /* if true, plane is not valid */
+ qboolean startsolid; /* if true, the initial point was in a solid area */
+ float fraction; /* time completed, 1.0 = didn't hit anything */
+ vec3_t endpos; /* final position */
+ cplane_t plane; /* surface normal at impact */
+ csurface_t *surface; /* surface hit */
+ int contents; /* contents on other side of surface hit */
+ struct edict_s *ent; /* not set by CM_*() functions */
+} trace_t;
+
+/* pmove_state_t is the information necessary for client side movement */
+/* prediction */
+typedef enum
+{
+ /* can accelerate and turn */
+ PM_NORMAL,
+ PM_SPECTATOR,
+ /* no acceleration or turning */
+ PM_DEAD,
+ PM_GIB, /* different bounding box */
+ PM_FREEZE
+} pmtype_t;
+
+/* pmove->pm_flags */
+#define PMF_DUCKED 1
+#define PMF_JUMP_HELD 2
+#define PMF_ON_GROUND 4
+#define PMF_TIME_WATERJUMP 8 /* pm_time is waterjump */
+#define PMF_TIME_LAND 16 /* pm_time is time before rejump */
+#define PMF_TIME_TELEPORT 32 /* pm_time is non-moving time */
+#define PMF_NO_PREDICTION 64 /* temporarily disables prediction (used for grappling hook) */
+
+/* this structure needs to be communicated bit-accurate/
+ from the server to the client to guarantee that
+ prediction stays in sync, so no floats are used.
+ if any part of the game code modifies this struct, it
+ will result in a prediction error of some degree. */
+typedef struct
+{
+ pmtype_t pm_type;
+
+ short origin[3]; /* 12.3 */
+ short velocity[3]; /* 12.3 */
+ byte pm_flags; /* ducked, jump_held, etc */
+ byte pm_time; /* each unit = 8 ms */
+ short gravity;
+ short delta_angles[3]; /* add to command angles to get view direction
+ changed by spawns, rotating objects, and teleporters */
+} pmove_state_t;
+
+/* button bits */
+#define BUTTON_ATTACK 1
+#define BUTTON_USE 2
+#define BUTTON_ANY 128 /* any key whatsoever */
+
+/* usercmd_t is sent to the server each client frame */
+typedef struct usercmd_s
+{
+ byte msec;
+ byte buttons;
+ short angles[3];
+ short forwardmove, sidemove, upmove;
+ byte impulse; /* remove? */
+ byte lightlevel; /* light level the player is standing on */
+} usercmd_t;
+
+#define MAXTOUCH 32
+typedef struct
+{
+ /* state (in / out) */
+ pmove_state_t s;
+
+ /* command (in) */
+ usercmd_t cmd;
+ qboolean snapinitial; /* if s has been changed outside pmove */
+
+ /* results (out) */
+ int numtouch;
+ struct edict_s *touchents[MAXTOUCH];
+
+ vec3_t viewangles; /* clamped */
+ float viewheight;
+
+ vec3_t mins, maxs; /* bounding box size */
+
+ struct edict_s *groundentity;
+ int watertype;
+ int waterlevel;
+
+ /* callbacks to test the world */
+ trace_t (*trace)(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end);
+ int (*pointcontents)(vec3_t point);
+} pmove_t;
+
+/* entity_state_t->effects
+ Effects are things handled on the client side (lights, particles,
+ frame animations) that happen constantly on the given entity.
+ An entity that has effects will be sent to the client even if
+ it has a zero index model. */
+#define EF_ROTATE 0x00000001 /* rotate (bonus items) */
+#define EF_GIB 0x00000002 /* leave a trail */
+#define EF_BLASTER 0x00000008 /* redlight + trail */
+#define EF_ROCKET 0x00000010 /* redlight + trail */
+#define EF_GRENADE 0x00000020
+#define EF_HYPERBLASTER 0x00000040
+#define EF_BFG 0x00000080
+#define EF_COLOR_SHELL 0x00000100
+#define EF_POWERSCREEN 0x00000200
+#define EF_ANIM01 0x00000400 /* automatically cycle between frames 0 and 1 at 2 hz */
+#define EF_ANIM23 0x00000800 /* automatically cycle between frames 2 and 3 at 2 hz */
+#define EF_ANIM_ALL 0x00001000 /* automatically cycle through all frames at 2hz */
+#define EF_ANIM_ALLFAST 0x00002000 /* automatically cycle through all frames at 10hz */
+#define EF_FLIES 0x00004000
+#define EF_QUAD 0x00008000
+#define EF_PENT 0x00010000
+#define EF_TELEPORTER 0x00020000 /* particle fountain */
+#define EF_FLAG1 0x00040000
+#define EF_FLAG2 0x00080000
+#define EF_IONRIPPER 0x00100000
+#define EF_GREENGIB 0x00200000
+#define EF_BLUEHYPERBLASTER 0x00400000
+#define EF_SPINNINGLIGHTS 0x00800000
+#define EF_PLASMA 0x01000000
+#define EF_TRAP 0x02000000
+#define EF_TRACKER 0x04000000
+#define EF_DOUBLE 0x08000000
+#define EF_SPHERETRANS 0x10000000
+#define EF_TAGTRAIL 0x20000000
+#define EF_HALF_DAMAGE 0x40000000
+#define EF_TRACKERTRAIL 0x80000000
+
+/* entity_state_t->renderfx flags */
+#define RF_MINLIGHT 1 /* allways have some light (viewmodel) */
+#define RF_VIEWERMODEL 2 /* don't draw through eyes, only mirrors */
+#define RF_WEAPONMODEL 4 /* only draw through eyes */
+#define RF_FULLBRIGHT 8 /* allways draw full intensity */
+#define RF_DEPTHHACK 16 /* for view weapon Z crunching */
+#define RF_TRANSLUCENT 32
+#define RF_FRAMELERP 64
+#define RF_BEAM 128
+#define RF_CUSTOMSKIN 256 /* skin is an index in image_precache */
+#define RF_GLOW 512 /* pulse lighting for bonus items */
+#define RF_SHELL_RED 1024
+#define RF_SHELL_GREEN 2048
+#define RF_SHELL_BLUE 4096
+#define RF_NOSHADOW 8192 /* don't draw a shadow */
+#define RF_IR_VISIBLE 0x00008000 /* 32768 */
+#define RF_SHELL_DOUBLE 0x00010000 /* 65536 */
+#define RF_SHELL_HALF_DAM 0x00020000
+#define RF_USE_DISGUISE 0x00040000
+
+/* player_state_t->refdef flags */
+#define RDF_UNDERWATER 1 /* warp the screen as apropriate */
+#define RDF_NOWORLDMODEL 2 /* used for player configuration screen */
+#define RDF_IRGOGGLES 4
+#define RDF_UVGOGGLES 8
+
+/* muzzle flashes / player effects */
+#define MZ_BLASTER 0
+#define MZ_MACHINEGUN 1
+#define MZ_SHOTGUN 2
+#define MZ_CHAINGUN1 3
+#define MZ_CHAINGUN2 4
+#define MZ_CHAINGUN3 5
+#define MZ_RAILGUN 6
+#define MZ_ROCKET 7
+#define MZ_GRENADE 8
+#define MZ_LOGIN 9
+#define MZ_LOGOUT 10
+#define MZ_RESPAWN 11
+#define MZ_BFG 12
+#define MZ_SSHOTGUN 13
+#define MZ_HYPERBLASTER 14
+#define MZ_ITEMRESPAWN 15
+#define MZ_IONRIPPER 16
+#define MZ_BLUEHYPERBLASTER 17
+#define MZ_PHALANX 18
+#define MZ_SILENCED 128 /* bit flag ORed with one of the above numbers */
+#define MZ_ETF_RIFLE 30
+#define MZ_UNUSED 31
+#define MZ_SHOTGUN2 32
+#define MZ_HEATBEAM 33
+#define MZ_BLASTER2 34
+#define MZ_TRACKER 35
+#define MZ_NUKE1 36
+#define MZ_NUKE2 37
+#define MZ_NUKE4 38
+#define MZ_NUKE8 39
+
+/* monster muzzle flashes */
+#define MZ2_TANK_BLASTER_1 1
+#define MZ2_TANK_BLASTER_2 2
+#define MZ2_TANK_BLASTER_3 3
+#define MZ2_TANK_MACHINEGUN_1 4
+#define MZ2_TANK_MACHINEGUN_2 5
+#define MZ2_TANK_MACHINEGUN_3 6
+#define MZ2_TANK_MACHINEGUN_4 7
+#define MZ2_TANK_MACHINEGUN_5 8
+#define MZ2_TANK_MACHINEGUN_6 9
+#define MZ2_TANK_MACHINEGUN_7 10
+#define MZ2_TANK_MACHINEGUN_8 11
+#define MZ2_TANK_MACHINEGUN_9 12
+#define MZ2_TANK_MACHINEGUN_10 13
+#define MZ2_TANK_MACHINEGUN_11 14
+#define MZ2_TANK_MACHINEGUN_12 15
+#define MZ2_TANK_MACHINEGUN_13 16
+#define MZ2_TANK_MACHINEGUN_14 17
+#define MZ2_TANK_MACHINEGUN_15 18
+#define MZ2_TANK_MACHINEGUN_16 19
+#define MZ2_TANK_MACHINEGUN_17 20
+#define MZ2_TANK_MACHINEGUN_18 21
+#define MZ2_TANK_MACHINEGUN_19 22
+#define MZ2_TANK_ROCKET_1 23
+#define MZ2_TANK_ROCKET_2 24
+#define MZ2_TANK_ROCKET_3 25
+
+#define MZ2_INFANTRY_MACHINEGUN_1 26
+#define MZ2_INFANTRY_MACHINEGUN_2 27
+#define MZ2_INFANTRY_MACHINEGUN_3 28
+#define MZ2_INFANTRY_MACHINEGUN_4 29
+#define MZ2_INFANTRY_MACHINEGUN_5 30
+#define MZ2_INFANTRY_MACHINEGUN_6 31
+#define MZ2_INFANTRY_MACHINEGUN_7 32
+#define MZ2_INFANTRY_MACHINEGUN_8 33
+#define MZ2_INFANTRY_MACHINEGUN_9 34
+#define MZ2_INFANTRY_MACHINEGUN_10 35
+#define MZ2_INFANTRY_MACHINEGUN_11 36
+#define MZ2_INFANTRY_MACHINEGUN_12 37
+#define MZ2_INFANTRY_MACHINEGUN_13 38
+
+#define MZ2_SOLDIER_BLASTER_1 39
+#define MZ2_SOLDIER_BLASTER_2 40
+#define MZ2_SOLDIER_SHOTGUN_1 41
+#define MZ2_SOLDIER_SHOTGUN_2 42
+#define MZ2_SOLDIER_MACHINEGUN_1 43
+#define MZ2_SOLDIER_MACHINEGUN_2 44
+
+#define MZ2_GUNNER_MACHINEGUN_1 45
+#define MZ2_GUNNER_MACHINEGUN_2 46
+#define MZ2_GUNNER_MACHINEGUN_3 47
+#define MZ2_GUNNER_MACHINEGUN_4 48
+#define MZ2_GUNNER_MACHINEGUN_5 49
+#define MZ2_GUNNER_MACHINEGUN_6 50
+#define MZ2_GUNNER_MACHINEGUN_7 51
+#define MZ2_GUNNER_MACHINEGUN_8 52
+#define MZ2_GUNNER_GRENADE_1 53
+#define MZ2_GUNNER_GRENADE_2 54
+#define MZ2_GUNNER_GRENADE_3 55
+#define MZ2_GUNNER_GRENADE_4 56
+
+#define MZ2_CHICK_ROCKET_1 57
+
+#define MZ2_FLYER_BLASTER_1 58
+#define MZ2_FLYER_BLASTER_2 59
+
+#define MZ2_MEDIC_BLASTER_1 60
+
+#define MZ2_GLADIATOR_RAILGUN_1 61
+
+#define MZ2_HOVER_BLASTER_1 62
+
+#define MZ2_ACTOR_MACHINEGUN_1 63
+
+#define MZ2_SUPERTANK_MACHINEGUN_1 64
+#define MZ2_SUPERTANK_MACHINEGUN_2 65
+#define MZ2_SUPERTANK_MACHINEGUN_3 66
+#define MZ2_SUPERTANK_MACHINEGUN_4 67
+#define MZ2_SUPERTANK_MACHINEGUN_5 68
+#define MZ2_SUPERTANK_MACHINEGUN_6 69
+#define MZ2_SUPERTANK_ROCKET_1 70
+#define MZ2_SUPERTANK_ROCKET_2 71
+#define MZ2_SUPERTANK_ROCKET_3 72
+
+#define MZ2_BOSS2_MACHINEGUN_L1 73
+#define MZ2_BOSS2_MACHINEGUN_L2 74
+#define MZ2_BOSS2_MACHINEGUN_L3 75
+#define MZ2_BOSS2_MACHINEGUN_L4 76
+#define MZ2_BOSS2_MACHINEGUN_L5 77
+#define MZ2_BOSS2_ROCKET_1 78
+#define MZ2_BOSS2_ROCKET_2 79
+#define MZ2_BOSS2_ROCKET_3 80
+#define MZ2_BOSS2_ROCKET_4 81
+
+#define MZ2_FLOAT_BLASTER_1 82
+
+#define MZ2_SOLDIER_BLASTER_3 83
+#define MZ2_SOLDIER_SHOTGUN_3 84
+#define MZ2_SOLDIER_MACHINEGUN_3 85
+#define MZ2_SOLDIER_BLASTER_4 86
+#define MZ2_SOLDIER_SHOTGUN_4 87
+#define MZ2_SOLDIER_MACHINEGUN_4 88
+#define MZ2_SOLDIER_BLASTER_5 89
+#define MZ2_SOLDIER_SHOTGUN_5 90
+#define MZ2_SOLDIER_MACHINEGUN_5 91
+#define MZ2_SOLDIER_BLASTER_6 92
+#define MZ2_SOLDIER_SHOTGUN_6 93
+#define MZ2_SOLDIER_MACHINEGUN_6 94
+#define MZ2_SOLDIER_BLASTER_7 95
+#define MZ2_SOLDIER_SHOTGUN_7 96
+#define MZ2_SOLDIER_MACHINEGUN_7 97
+#define MZ2_SOLDIER_BLASTER_8 98
+#define MZ2_SOLDIER_SHOTGUN_8 99
+#define MZ2_SOLDIER_MACHINEGUN_8 100
+
+#define MZ2_MAKRON_BFG 101
+#define MZ2_MAKRON_BLASTER_1 102
+#define MZ2_MAKRON_BLASTER_2 103
+#define MZ2_MAKRON_BLASTER_3 104
+#define MZ2_MAKRON_BLASTER_4 105
+#define MZ2_MAKRON_BLASTER_5 106
+#define MZ2_MAKRON_BLASTER_6 107
+#define MZ2_MAKRON_BLASTER_7 108
+#define MZ2_MAKRON_BLASTER_8 109
+#define MZ2_MAKRON_BLASTER_9 110
+#define MZ2_MAKRON_BLASTER_10 111
+#define MZ2_MAKRON_BLASTER_11 112
+#define MZ2_MAKRON_BLASTER_12 113
+#define MZ2_MAKRON_BLASTER_13 114
+#define MZ2_MAKRON_BLASTER_14 115
+#define MZ2_MAKRON_BLASTER_15 116
+#define MZ2_MAKRON_BLASTER_16 117
+#define MZ2_MAKRON_BLASTER_17 118
+#define MZ2_MAKRON_RAILGUN_1 119
+#define MZ2_JORG_MACHINEGUN_L1 120
+#define MZ2_JORG_MACHINEGUN_L2 121
+#define MZ2_JORG_MACHINEGUN_L3 122
+#define MZ2_JORG_MACHINEGUN_L4 123
+#define MZ2_JORG_MACHINEGUN_L5 124
+#define MZ2_JORG_MACHINEGUN_L6 125
+#define MZ2_JORG_MACHINEGUN_R1 126
+#define MZ2_JORG_MACHINEGUN_R2 127
+#define MZ2_JORG_MACHINEGUN_R3 128
+#define MZ2_JORG_MACHINEGUN_R4 129
+#define MZ2_JORG_MACHINEGUN_R5 130
+#define MZ2_JORG_MACHINEGUN_R6 131
+#define MZ2_JORG_BFG_1 132
+#define MZ2_BOSS2_MACHINEGUN_R1 133
+#define MZ2_BOSS2_MACHINEGUN_R2 134
+#define MZ2_BOSS2_MACHINEGUN_R3 135
+#define MZ2_BOSS2_MACHINEGUN_R4 136
+#define MZ2_BOSS2_MACHINEGUN_R5 137
+
+#define MZ2_CARRIER_MACHINEGUN_L1 138
+#define MZ2_CARRIER_MACHINEGUN_R1 139
+#define MZ2_CARRIER_GRENADE 140
+#define MZ2_TURRET_MACHINEGUN 141
+#define MZ2_TURRET_ROCKET 142
+#define MZ2_TURRET_BLASTER 143
+#define MZ2_STALKER_BLASTER 144
+#define MZ2_DAEDALUS_BLASTER 145
+#define MZ2_MEDIC_BLASTER_2 146
+#define MZ2_CARRIER_RAILGUN 147
+#define MZ2_WIDOW_DISRUPTOR 148
+#define MZ2_WIDOW_BLASTER 149
+#define MZ2_WIDOW_RAIL 150
+#define MZ2_WIDOW_PLASMABEAM 151
+#define MZ2_CARRIER_MACHINEGUN_L2 152
+#define MZ2_CARRIER_MACHINEGUN_R2 153
+#define MZ2_WIDOW_RAIL_LEFT 154
+#define MZ2_WIDOW_RAIL_RIGHT 155
+#define MZ2_WIDOW_BLASTER_SWEEP1 156
+#define MZ2_WIDOW_BLASTER_SWEEP2 157
+#define MZ2_WIDOW_BLASTER_SWEEP3 158
+#define MZ2_WIDOW_BLASTER_SWEEP4 159
+#define MZ2_WIDOW_BLASTER_SWEEP5 160
+#define MZ2_WIDOW_BLASTER_SWEEP6 161
+#define MZ2_WIDOW_BLASTER_SWEEP7 162
+#define MZ2_WIDOW_BLASTER_SWEEP8 163
+#define MZ2_WIDOW_BLASTER_SWEEP9 164
+#define MZ2_WIDOW_BLASTER_100 165
+#define MZ2_WIDOW_BLASTER_90 166
+#define MZ2_WIDOW_BLASTER_80 167
+#define MZ2_WIDOW_BLASTER_70 168
+#define MZ2_WIDOW_BLASTER_60 169
+#define MZ2_WIDOW_BLASTER_50 170
+#define MZ2_WIDOW_BLASTER_40 171
+#define MZ2_WIDOW_BLASTER_30 172
+#define MZ2_WIDOW_BLASTER_20 173
+#define MZ2_WIDOW_BLASTER_10 174
+#define MZ2_WIDOW_BLASTER_0 175
+#define MZ2_WIDOW_BLASTER_10L 176
+#define MZ2_WIDOW_BLASTER_20L 177
+#define MZ2_WIDOW_BLASTER_30L 178
+#define MZ2_WIDOW_BLASTER_40L 179
+#define MZ2_WIDOW_BLASTER_50L 180
+#define MZ2_WIDOW_BLASTER_60L 181
+#define MZ2_WIDOW_BLASTER_70L 182
+#define MZ2_WIDOW_RUN_1 183
+#define MZ2_WIDOW_RUN_2 184
+#define MZ2_WIDOW_RUN_3 185
+#define MZ2_WIDOW_RUN_4 186
+#define MZ2_WIDOW_RUN_5 187
+#define MZ2_WIDOW_RUN_6 188
+#define MZ2_WIDOW_RUN_7 189
+#define MZ2_WIDOW_RUN_8 190
+#define MZ2_CARRIER_ROCKET_1 191
+#define MZ2_CARRIER_ROCKET_2 192
+#define MZ2_CARRIER_ROCKET_3 193
+#define MZ2_CARRIER_ROCKET_4 194
+#define MZ2_WIDOW2_BEAMER_1 195
+#define MZ2_WIDOW2_BEAMER_2 196
+#define MZ2_WIDOW2_BEAMER_3 197
+#define MZ2_WIDOW2_BEAMER_4 198
+#define MZ2_WIDOW2_BEAMER_5 199
+#define MZ2_WIDOW2_BEAM_SWEEP_1 200
+#define MZ2_WIDOW2_BEAM_SWEEP_2 201
+#define MZ2_WIDOW2_BEAM_SWEEP_3 202
+#define MZ2_WIDOW2_BEAM_SWEEP_4 203
+#define MZ2_WIDOW2_BEAM_SWEEP_5 204
+#define MZ2_WIDOW2_BEAM_SWEEP_6 205
+#define MZ2_WIDOW2_BEAM_SWEEP_7 206
+#define MZ2_WIDOW2_BEAM_SWEEP_8 207
+#define MZ2_WIDOW2_BEAM_SWEEP_9 208
+#define MZ2_WIDOW2_BEAM_SWEEP_10 209
+#define MZ2_WIDOW2_BEAM_SWEEP_11 210
+
+extern vec3_t monster_flash_offset[];
+
+/* Temp entity events are for things that happen
+ at a location seperate from any existing entity.
+ Temporary entity messages are explicitly constructed
+ and broadcast. */
+typedef enum
+{
+ TE_GUNSHOT,
+ TE_BLOOD,
+ TE_BLASTER,
+ TE_RAILTRAIL,
+ TE_SHOTGUN,
+ TE_EXPLOSION1,
+ TE_EXPLOSION2,
+ TE_ROCKET_EXPLOSION,
+ TE_GRENADE_EXPLOSION,
+ TE_SPARKS,
+ TE_SPLASH,
+ TE_BUBBLETRAIL,
+ TE_SCREEN_SPARKS,
+ TE_SHIELD_SPARKS,
+ TE_BULLET_SPARKS,
+ TE_LASER_SPARKS,
+ TE_PARASITE_ATTACK,
+ TE_ROCKET_EXPLOSION_WATER,
+ TE_GRENADE_EXPLOSION_WATER,
+ TE_MEDIC_CABLE_ATTACK,
+ TE_BFG_EXPLOSION,
+ TE_BFG_BIGEXPLOSION,
+ TE_BOSSTPORT, /* used as '22' in a map, so DON'T RENUMBER!!! */
+ TE_BFG_LASER,
+ TE_GRAPPLE_CABLE,
+ TE_WELDING_SPARKS,
+ TE_GREENBLOOD,
+ TE_BLUEHYPERBLASTER,
+ TE_PLASMA_EXPLOSION,
+ TE_TUNNEL_SPARKS,
+ TE_BLASTER2,
+ TE_RAILTRAIL2,
+ TE_FLAME,
+ TE_LIGHTNING,
+ TE_DEBUGTRAIL,
+ TE_PLAIN_EXPLOSION,
+ TE_FLASHLIGHT,
+ TE_FORCEWALL,
+ TE_HEATBEAM,
+ TE_MONSTER_HEATBEAM,
+ TE_STEAM,
+ TE_BUBBLETRAIL2,
+ TE_MOREBLOOD,
+ TE_HEATBEAM_SPARKS,
+ TE_HEATBEAM_STEAM,
+ TE_CHAINFIST_SMOKE,
+ TE_ELECTRIC_SPARKS,
+ TE_TRACKER_EXPLOSION,
+ TE_TELEPORT_EFFECT,
+ TE_DBALL_GOAL,
+ TE_WIDOWBEAMOUT,
+ TE_NUKEBLAST,
+ TE_WIDOWSPLASH,
+ TE_EXPLOSION1_BIG,
+ TE_EXPLOSION1_NP,
+ TE_FLECHETTE
+} temp_event_t;
+
+#define SPLASH_UNKNOWN 0
+#define SPLASH_SPARKS 1
+#define SPLASH_BLUE_WATER 2
+#define SPLASH_BROWN_WATER 3
+#define SPLASH_SLIME 4
+#define SPLASH_LAVA 5
+#define SPLASH_BLOOD 6
+
+/* sound channels:
+ channel 0 never willingly overrides
+ other channels (1-7) allways override
+ a playing sound on that channel */
+#define CHAN_AUTO 0
+#define CHAN_WEAPON 1
+#define CHAN_VOICE 2
+#define CHAN_ITEM 3
+#define CHAN_BODY 4
+/* modifier flags */
+#define CHAN_NO_PHS_ADD 8 /* send to all clients, not just ones in PHS (ATTN 0 will also do this) */
+#define CHAN_RELIABLE 16 /* send by reliable message, not datagram */
+
+/* sound attenuation values */
+#define ATTN_NONE 0 /* full volume the entire level */
+#define ATTN_NORM 1
+#define ATTN_IDLE 2
+#define ATTN_STATIC 3 /* diminish very rapidly with distance */
+
+/* player_state->stats[] indexes */
+#define STAT_HEALTH_ICON 0
+#define STAT_HEALTH 1
+#define STAT_AMMO_ICON 2
+#define STAT_AMMO 3
+#define STAT_ARMOR_ICON 4
+#define STAT_ARMOR 5
+#define STAT_SELECTED_ICON 6
+#define STAT_PICKUP_ICON 7
+#define STAT_PICKUP_STRING 8
+#define STAT_TIMER_ICON 9
+#define STAT_TIMER 10
+#define STAT_HELPICON 11
+#define STAT_SELECTED_ITEM 12
+#define STAT_LAYOUTS 13
+#define STAT_FRAGS 14
+#define STAT_FLASHES 15 /* cleared each frame, 1 = health, 2 = armor */
+#define STAT_CHASE 16
+#define STAT_SPECTATOR 17
+
+#define MAX_STATS 32
+
+/* dmflags->value flags */
+#define DF_NO_HEALTH 0x00000001 /* 1 */
+#define DF_NO_ITEMS 0x00000002 /* 2 */
+#define DF_WEAPONS_STAY 0x00000004 /* 4 */
+#define DF_NO_FALLING 0x00000008 /* 8 */
+#define DF_INSTANT_ITEMS 0x00000010 /* 16 */
+#define DF_SAME_LEVEL 0x00000020 /* 32 */
+#define DF_SKINTEAMS 0x00000040 /* 64 */
+#define DF_MODELTEAMS 0x00000080 /* 128 */
+#define DF_NO_FRIENDLY_FIRE 0x00000100 /* 256 */
+#define DF_SPAWN_FARTHEST 0x00000200 /* 512 */
+#define DF_FORCE_RESPAWN 0x00000400 /* 1024 */
+#define DF_NO_ARMOR 0x00000800 /* 2048 */
+#define DF_ALLOW_EXIT 0x00001000 /* 4096 */
+#define DF_INFINITE_AMMO 0x00002000 /* 8192 */
+#define DF_QUAD_DROP 0x00004000 /* 16384 */
+#define DF_FIXED_FOV 0x00008000 /* 32768 */
+#define DF_QUADFIRE_DROP 0x00010000 /* 65536 */
+#define DF_NO_MINES 0x00020000
+#define DF_NO_STACK_DOUBLE 0x00040000
+#define DF_NO_NUKES 0x00080000
+#define DF_NO_SPHERES 0x00100000
+
+#define ROGUE_VERSION_STRING "08/21/1998 Beta 2 for Ensemble"
+
+/*
+ * ==========================================================
+ *
+ * ELEMENTS COMMUNICATED ACROSS THE NET
+ *
+ * ==========================================================
+ */
+
+#define ANGLE2SHORT(x) ((int)((x) * 65536 / 360) & 65535)
+#define SHORT2ANGLE(x) ((x) * (360.0 / 65536))
+
+/* config strings are a general means of communication from
+ the server to all connected clients. Each config string
+ can be at most MAX_QPATH characters. */
+#define CS_NAME 0
+#define CS_CDTRACK 1
+#define CS_SKY 2
+#define CS_SKYAXIS 3 /* %f %f %f format */
+#define CS_SKYROTATE 4
+#define CS_STATUSBAR 5 /* display program string */
+
+#define CS_AIRACCEL 29 /* air acceleration control */
+#define CS_MAXCLIENTS 30
+#define CS_MAPCHECKSUM 31 /* for catching cheater maps */
+
+#define CS_MODELS 32
+#define CS_SOUNDS (CS_MODELS + MAX_MODELS)
+#define CS_IMAGES (CS_SOUNDS + MAX_SOUNDS)
+#define CS_LIGHTS (CS_IMAGES + MAX_IMAGES)
+#define CS_ITEMS (CS_LIGHTS + MAX_LIGHTSTYLES)
+#define CS_PLAYERSKINS (CS_ITEMS + MAX_ITEMS)
+#define CS_GENERAL (CS_PLAYERSKINS + MAX_CLIENTS)
+#define MAX_CONFIGSTRINGS (CS_GENERAL + MAX_GENERAL)
+
+/* ============================================== */
+
+/* entity_state_t->event values
+ entity events are for effects that take place reletive
+ to an existing entities origin. Very network efficient.
+ All muzzle flashes really should be converted to events... */
+typedef enum
+{
+ EV_NONE,
+ EV_ITEM_RESPAWN,
+ EV_FOOTSTEP,
+ EV_FALLSHORT,
+ EV_FALL,
+ EV_FALLFAR,
+ EV_PLAYER_TELEPORT,
+ EV_OTHER_TELEPORT
+} entity_event_t;
+
+/* entity_state_t is the information conveyed from the server
+ in an update message about entities that the client will
+ need to render in some way */
+typedef struct entity_state_s
+{
+ int number; /* edict index */
+
+ vec3_t origin;
+ vec3_t angles;
+ vec3_t old_origin; /* for lerping */
+ int modelindex;
+ int modelindex2, modelindex3, modelindex4; /* weapons, CTF flags, etc */
+ int frame;
+ int skinnum;
+ unsigned int effects;
+ int renderfx;
+ int solid; /* for client side prediction, 8*(bits 0-4) is x/y radius */
+ /* 8*(bits 5-9) is z down distance, 8(bits10-15) is z up */
+ /* gi.linkentity sets this properly */
+ int sound; /* for looping sounds, to guarantee shutoff */
+ int event; /* impulse events -- muzzle flashes, footsteps, etc */
+ /* events only go out for a single frame, they */
+ /* are automatically cleared each frame */
+} entity_state_t;
+
+/* ============================================== */
+
+/* player_state_t is the information needed in addition to pmove_state_t
+ to rendered a view. There will only be 10 player_state_t sent each second,
+ but the number of pmove_state_t changes will be reletive to client
+ frame rates */
+typedef struct
+{
+ pmove_state_t pmove; /* for prediction */
+
+ vec3_t viewangles; /* for fixed views */
+ vec3_t viewoffset; /* add to pmovestate->origin */
+ vec3_t kick_angles; /* add to view direction to get render angles */
+ /* set by weapon kicks, pain effects, etc */
+
+ vec3_t gunangles;
+ vec3_t gunoffset;
+ int gunindex;
+ int gunframe;
+
+ float blend[4]; /* rgba full screen effect */
+ float fov; /* horizontal field of view */
+ int rdflags; /* refdef flags */
+
+ short stats[MAX_STATS]; /* fast status bar updates */
+} player_state_t;
+
+#define VIDREF_GL 1
+#define VIDREF_SOFT 2
+#define VIDREF_OTHER 3
+
+extern int vidref_val;
+
+size_t verify_fread(void *, size_t, size_t, FILE *);
+size_t verify_fwrite(void *, size_t, size_t, FILE *);
+
+#endif /* XATRIX_SHARED_H */
diff --git a/xatrix/src/monster/berserker/berserker.c b/xatrix/src/monster/berserker/berserker.c
new file mode 100644
index 0000000..3060455
--- /dev/null
+++ b/xatrix/src/monster/berserker/berserker.c
@@ -0,0 +1,559 @@
+/* =======================================================================
+ *
+ * The berserker.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "berserker.h"
+
+static int sound_pain;
+static int sound_die;
+static int sound_idle;
+static int sound_punch;
+static int sound_sight;
+static int sound_search;
+
+void
+berserk_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+berserk_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void berserk_fidget(edict_t *self);
+
+mframe_t berserk_frames_stand[] = {
+ {ai_stand, 0, berserk_fidget},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t berserk_move_stand = {
+ FRAME_stand1,
+ FRAME_stand5,
+ berserk_frames_stand,
+ NULL
+};
+
+void
+berserk_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &berserk_move_stand;
+}
+
+mframe_t berserk_frames_stand_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t berserk_move_stand_fidget = {
+ FRAME_standb1,
+ FRAME_standb20,
+ berserk_frames_stand_fidget,
+ berserk_stand
+};
+
+void
+berserk_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ return;
+ }
+
+ if (random() > 0.15)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &berserk_move_stand_fidget;
+ gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t berserk_frames_walk[] = {
+ {ai_walk, 9.1, NULL},
+ {ai_walk, 6.3, NULL},
+ {ai_walk, 4.9, NULL},
+ {ai_walk, 6.7, NULL},
+ {ai_walk, 6.0, NULL},
+ {ai_walk, 8.2, NULL},
+ {ai_walk, 7.2, NULL},
+ {ai_walk, 6.1, NULL},
+ {ai_walk, 4.9, NULL},
+ {ai_walk, 4.7, NULL},
+ {ai_walk, 4.7, NULL},
+ {ai_walk, 4.8, NULL}
+};
+
+mmove_t berserk_move_walk = {
+ FRAME_walkc1,
+ FRAME_walkc11,
+ berserk_frames_walk,
+ NULL
+};
+
+void
+berserk_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &berserk_move_walk;
+}
+
+mframe_t berserk_frames_run1[] = {
+ {ai_run, 21, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 21, NULL},
+ {ai_run, 25, NULL},
+ {ai_run, 18, NULL},
+ {ai_run, 19, NULL}
+};
+
+mmove_t berserk_move_run1 = {
+ FRAME_run1,
+ FRAME_run6,
+ berserk_frames_run1,
+ NULL
+};
+
+void
+berserk_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &berserk_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_run1;
+ }
+}
+
+void
+berserk_attack_spike(edict_t *self)
+{
+ static vec3_t aim = {MELEE_DISTANCE, 0, -24};
+
+ if (!self)
+ {
+ return;
+ }
+
+ fire_hit(self, aim, (15 + (rand() % 6)), 400); /* Faster attack -- upwards and backwards */
+}
+
+void
+berserk_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0);
+}
+
+mframe_t berserk_frames_attack_spike[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, berserk_swing},
+ {ai_charge, 0, berserk_attack_spike},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t berserk_move_attack_spike = {
+ FRAME_att_c1,
+ FRAME_att_c8,
+ berserk_frames_attack_spike,
+ berserk_run
+};
+
+void
+berserk_attack_club(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4);
+ fire_hit(self, aim, (5 + (rand() % 6)), 400); /* Slower attack */
+}
+
+mframe_t berserk_frames_attack_club[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, berserk_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, berserk_attack_club},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t berserk_move_attack_club = {
+ FRAME_att_c9,
+ FRAME_att_c20,
+ berserk_frames_attack_club,
+ berserk_run
+};
+
+void
+berserk_strike(edict_t *self)
+{
+ /* Unused, but removal is
+ very PITA. Let it be... */
+}
+
+mframe_t berserk_frames_attack_strike[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, berserk_swing},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, berserk_strike},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 9.7, NULL},
+ {ai_move, 13.6, NULL}
+};
+
+mmove_t berserk_move_attack_strike = {
+ FRAME_att_c21,
+ FRAME_att_c34,
+ berserk_frames_attack_strike,
+ berserk_run
+};
+
+void
+berserk_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((rand() % 2) == 0)
+ {
+ self->monsterinfo.currentmove = &berserk_move_attack_spike;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_attack_club;
+ }
+}
+
+mframe_t berserk_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_pain1 = {
+ FRAME_painc1,
+ FRAME_painc4,
+ berserk_frames_pain1,
+ berserk_run
+};
+
+mframe_t berserk_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_pain2 = {
+ FRAME_painb1,
+ FRAME_painb20,
+ berserk_frames_pain2,
+ berserk_run
+};
+
+void
+berserk_pain(edict_t *self, edict_t *other /* unsued */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if ((damage < 20) || (random() < 0.5))
+ {
+ self->monsterinfo.currentmove = &berserk_move_pain1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_pain2;
+ }
+}
+
+void
+berserk_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t berserk_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_death1 = {
+ FRAME_death1,
+ FRAME_death13,
+ berserk_frames_death1,
+ berserk_dead
+};
+
+mframe_t berserk_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t berserk_move_death2 = {
+ FRAME_deathc1,
+ FRAME_deathc8,
+ berserk_frames_death2,
+ berserk_dead
+};
+
+void
+berserk_die(edict_t *self, edict_t *inflictor /* unsued */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex( "misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ if (damage >= 50)
+ {
+ self->monsterinfo.currentmove = &berserk_move_death1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &berserk_move_death2;
+ }
+}
+
+/*
+ * QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_berserk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ /* pre-caches */
+ sound_pain = gi.soundindex("berserk/berpain2.wav");
+ sound_die = gi.soundindex("berserk/berdeth2.wav");
+ sound_idle = gi.soundindex("berserk/beridle1.wav");
+ sound_punch = gi.soundindex("berserk/attack.wav");
+ sound_search = gi.soundindex("berserk/bersrch1.wav");
+ sound_sight = gi.soundindex("berserk/sight.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->health = 240;
+ self->gib_health = -60;
+ self->mass = 250;
+
+ self->pain = berserk_pain;
+ self->die = berserk_die;
+
+ self->monsterinfo.stand = berserk_stand;
+ self->monsterinfo.walk = berserk_walk;
+ self->monsterinfo.run = berserk_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = NULL;
+ self->monsterinfo.melee = berserk_melee;
+ self->monsterinfo.sight = berserk_sight;
+ self->monsterinfo.search = berserk_search;
+
+ self->monsterinfo.currentmove = &berserk_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ gi.linkentity(self);
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/berserker/berserker.h b/xatrix/src/monster/berserker/berserker.h
new file mode 100644
index 0000000..5fb231c
--- /dev/null
+++ b/xatrix/src/monster/berserker/berserker.h
@@ -0,0 +1,253 @@
+/* =======================================================================
+ *
+ * Berserker animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand1 0
+#define FRAME_stand2 1
+#define FRAME_stand3 2
+#define FRAME_stand4 3
+#define FRAME_stand5 4
+#define FRAME_standb1 5
+#define FRAME_standb2 6
+#define FRAME_standb3 7
+#define FRAME_standb4 8
+#define FRAME_standb5 9
+#define FRAME_standb6 10
+#define FRAME_standb7 11
+#define FRAME_standb8 12
+#define FRAME_standb9 13
+#define FRAME_standb10 14
+#define FRAME_standb11 15
+#define FRAME_standb12 16
+#define FRAME_standb13 17
+#define FRAME_standb14 18
+#define FRAME_standb15 19
+#define FRAME_standb16 20
+#define FRAME_standb17 21
+#define FRAME_standb18 22
+#define FRAME_standb19 23
+#define FRAME_standb20 24
+#define FRAME_walkc1 25
+#define FRAME_walkc2 26
+#define FRAME_walkc3 27
+#define FRAME_walkc4 28
+#define FRAME_walkc5 29
+#define FRAME_walkc6 30
+#define FRAME_walkc7 31
+#define FRAME_walkc8 32
+#define FRAME_walkc9 33
+#define FRAME_walkc10 34
+#define FRAME_walkc11 35
+#define FRAME_run1 36
+#define FRAME_run2 37
+#define FRAME_run3 38
+#define FRAME_run4 39
+#define FRAME_run5 40
+#define FRAME_run6 41
+#define FRAME_att_a1 42
+#define FRAME_att_a2 43
+#define FRAME_att_a3 44
+#define FRAME_att_a4 45
+#define FRAME_att_a5 46
+#define FRAME_att_a6 47
+#define FRAME_att_a7 48
+#define FRAME_att_a8 49
+#define FRAME_att_a9 50
+#define FRAME_att_a10 51
+#define FRAME_att_a11 52
+#define FRAME_att_a12 53
+#define FRAME_att_a13 54
+#define FRAME_att_b1 55
+#define FRAME_att_b2 56
+#define FRAME_att_b3 57
+#define FRAME_att_b4 58
+#define FRAME_att_b5 59
+#define FRAME_att_b6 60
+#define FRAME_att_b7 61
+#define FRAME_att_b8 62
+#define FRAME_att_b9 63
+#define FRAME_att_b10 64
+#define FRAME_att_b11 65
+#define FRAME_att_b12 66
+#define FRAME_att_b13 67
+#define FRAME_att_b14 68
+#define FRAME_att_b15 69
+#define FRAME_att_b16 70
+#define FRAME_att_b17 71
+#define FRAME_att_b18 72
+#define FRAME_att_b19 73
+#define FRAME_att_b20 74
+#define FRAME_att_b21 75
+#define FRAME_att_c1 76
+#define FRAME_att_c2 77
+#define FRAME_att_c3 78
+#define FRAME_att_c4 79
+#define FRAME_att_c5 80
+#define FRAME_att_c6 81
+#define FRAME_att_c7 82
+#define FRAME_att_c8 83
+#define FRAME_att_c9 84
+#define FRAME_att_c10 85
+#define FRAME_att_c11 86
+#define FRAME_att_c12 87
+#define FRAME_att_c13 88
+#define FRAME_att_c14 89
+#define FRAME_att_c15 90
+#define FRAME_att_c16 91
+#define FRAME_att_c17 92
+#define FRAME_att_c18 93
+#define FRAME_att_c19 94
+#define FRAME_att_c20 95
+#define FRAME_att_c21 96
+#define FRAME_att_c22 97
+#define FRAME_att_c23 98
+#define FRAME_att_c24 99
+#define FRAME_att_c25 100
+#define FRAME_att_c26 101
+#define FRAME_att_c27 102
+#define FRAME_att_c28 103
+#define FRAME_att_c29 104
+#define FRAME_att_c30 105
+#define FRAME_att_c31 106
+#define FRAME_att_c32 107
+#define FRAME_att_c33 108
+#define FRAME_att_c34 109
+#define FRAME_r_att1 110
+#define FRAME_r_att2 111
+#define FRAME_r_att3 112
+#define FRAME_r_att4 113
+#define FRAME_r_att5 114
+#define FRAME_r_att6 115
+#define FRAME_r_att7 116
+#define FRAME_r_att8 117
+#define FRAME_r_att9 118
+#define FRAME_r_att10 119
+#define FRAME_r_att11 120
+#define FRAME_r_att12 121
+#define FRAME_r_att13 122
+#define FRAME_r_att14 123
+#define FRAME_r_att15 124
+#define FRAME_r_att16 125
+#define FRAME_r_att17 126
+#define FRAME_r_att18 127
+#define FRAME_r_attb1 128
+#define FRAME_r_attb2 129
+#define FRAME_r_attb3 130
+#define FRAME_r_attb4 131
+#define FRAME_r_attb5 132
+#define FRAME_r_attb6 133
+#define FRAME_r_attb7 134
+#define FRAME_r_attb8 135
+#define FRAME_r_attb9 136
+#define FRAME_r_attb10 137
+#define FRAME_r_attb11 138
+#define FRAME_r_attb12 139
+#define FRAME_r_attb13 140
+#define FRAME_r_attb14 141
+#define FRAME_r_attb15 142
+#define FRAME_r_attb16 143
+#define FRAME_r_attb17 144
+#define FRAME_r_attb18 145
+#define FRAME_slam1 146
+#define FRAME_slam2 147
+#define FRAME_slam3 148
+#define FRAME_slam4 149
+#define FRAME_slam5 150
+#define FRAME_slam6 151
+#define FRAME_slam7 152
+#define FRAME_slam8 153
+#define FRAME_slam9 154
+#define FRAME_slam10 155
+#define FRAME_slam11 156
+#define FRAME_slam12 157
+#define FRAME_slam13 158
+#define FRAME_slam14 159
+#define FRAME_slam15 160
+#define FRAME_slam16 161
+#define FRAME_slam17 162
+#define FRAME_slam18 163
+#define FRAME_slam19 164
+#define FRAME_slam20 165
+#define FRAME_slam21 166
+#define FRAME_slam22 167
+#define FRAME_slam23 168
+#define FRAME_duck1 169
+#define FRAME_duck2 170
+#define FRAME_duck3 171
+#define FRAME_duck4 172
+#define FRAME_duck5 173
+#define FRAME_duck6 174
+#define FRAME_duck7 175
+#define FRAME_duck8 176
+#define FRAME_duck9 177
+#define FRAME_duck10 178
+#define FRAME_fall1 179
+#define FRAME_fall2 180
+#define FRAME_fall3 181
+#define FRAME_fall4 182
+#define FRAME_fall5 183
+#define FRAME_fall6 184
+#define FRAME_fall7 185
+#define FRAME_fall8 186
+#define FRAME_fall9 187
+#define FRAME_fall10 188
+#define FRAME_fall11 189
+#define FRAME_fall12 190
+#define FRAME_fall13 191
+#define FRAME_fall14 192
+#define FRAME_fall15 193
+#define FRAME_fall16 194
+#define FRAME_fall17 195
+#define FRAME_fall18 196
+#define FRAME_fall19 197
+#define FRAME_fall20 198
+#define FRAME_painc1 199
+#define FRAME_painc2 200
+#define FRAME_painc3 201
+#define FRAME_painc4 202
+#define FRAME_painb1 203
+#define FRAME_painb2 204
+#define FRAME_painb3 205
+#define FRAME_painb4 206
+#define FRAME_painb5 207
+#define FRAME_painb6 208
+#define FRAME_painb7 209
+#define FRAME_painb8 210
+#define FRAME_painb9 211
+#define FRAME_painb10 212
+#define FRAME_painb11 213
+#define FRAME_painb12 214
+#define FRAME_painb13 215
+#define FRAME_painb14 216
+#define FRAME_painb15 217
+#define FRAME_painb16 218
+#define FRAME_painb17 219
+#define FRAME_painb18 220
+#define FRAME_painb19 221
+#define FRAME_painb20 222
+#define FRAME_death1 223
+#define FRAME_death2 224
+#define FRAME_death3 225
+#define FRAME_death4 226
+#define FRAME_death5 227
+#define FRAME_death6 228
+#define FRAME_death7 229
+#define FRAME_death8 230
+#define FRAME_death9 231
+#define FRAME_death10 232
+#define FRAME_death11 233
+#define FRAME_death12 234
+#define FRAME_death13 235
+#define FRAME_deathc1 236
+#define FRAME_deathc2 237
+#define FRAME_deathc3 238
+#define FRAME_deathc4 239
+#define FRAME_deathc5 240
+#define FRAME_deathc6 241
+#define FRAME_deathc7 242
+#define FRAME_deathc8 243
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/boss2/boss2.c b/xatrix/src/monster/boss2/boss2.c
new file mode 100644
index 0000000..ad2c239
--- /dev/null
+++ b/xatrix/src/monster/boss2/boss2.c
@@ -0,0 +1,803 @@
+/* =======================================================================
+ *
+ * Boss 2 aka Hornet.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "boss2.h"
+
+void BossExplode(edict_t *self);
+qboolean infront(edict_t *self, edict_t *other);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_death;
+static int sound_search1;
+
+void
+boss2_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+ }
+}
+
+void boss2_run(edict_t *self);
+void boss2_stand(edict_t *self);
+void boss2_dead(edict_t *self);
+void boss2_attack(edict_t *self);
+void boss2_attack_mg(edict_t *self);
+void boss2_reattack_mg(edict_t *self);
+void boss2_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+void
+Boss2Rocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1);
+
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
+
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3);
+
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4],
+ forward, right, start);
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4);
+}
+
+void
+boss2_firebullet_right(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1);
+}
+
+void
+boss2_firebullet_left(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1);
+}
+
+void
+Boss2MachineGun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ boss2_firebullet_left(self);
+ boss2_firebullet_right(self);
+}
+
+mframe_t boss2_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t boss2_move_stand = {
+ FRAME_stand30,
+ FRAME_stand50,
+ boss2_frames_stand,
+ NULL
+};
+
+mframe_t boss2_frames_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t boss2_move_fidget = {
+ FRAME_stand1,
+ FRAME_stand30,
+ boss2_frames_fidget,
+ NULL
+};
+
+mframe_t boss2_frames_walk[] = {
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL}
+};
+
+mmove_t boss2_move_walk = {
+ FRAME_walk1,
+ FRAME_walk20,
+ boss2_frames_walk,
+ NULL
+};
+
+mframe_t boss2_frames_run[] = {
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL}
+};
+
+mmove_t boss2_move_run = {
+ FRAME_walk1,
+ FRAME_walk20,
+ boss2_frames_run,
+ NULL
+};
+
+mframe_t boss2_frames_attack_pre_mg[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, boss2_attack_mg}
+};
+
+mmove_t boss2_move_attack_pre_mg = {
+ FRAME_attack1,
+ FRAME_attack9,
+ boss2_frames_attack_pre_mg,
+ NULL
+};
+
+mframe_t boss2_frames_attack_mg[] = {
+ {ai_charge, 1, Boss2MachineGun},
+ {ai_charge, 1, Boss2MachineGun},
+ {ai_charge, 1, Boss2MachineGun},
+ {ai_charge, 1, Boss2MachineGun},
+ {ai_charge, 1, Boss2MachineGun},
+ {ai_charge, 1, boss2_reattack_mg}
+};
+
+mmove_t boss2_move_attack_mg = {
+ FRAME_attack10,
+ FRAME_attack15,
+ boss2_frames_attack_mg,
+ NULL
+};
+
+mframe_t boss2_frames_attack_post_mg[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t boss2_move_attack_post_mg = {
+ FRAME_attack16,
+ FRAME_attack19,
+ boss2_frames_attack_post_mg,
+ boss2_run
+};
+
+mframe_t boss2_frames_attack_rocket[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_move, -20, Boss2Rocket},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t boss2_move_attack_rocket = {
+ FRAME_attack20,
+ FRAME_attack40,
+ boss2_frames_attack_rocket,
+ boss2_run
+};
+
+mframe_t boss2_frames_pain_heavy[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss2_move_pain_heavy = {
+ FRAME_pain2,
+ FRAME_pain19,
+ boss2_frames_pain_heavy,
+ boss2_run
+};
+
+mframe_t boss2_frames_pain_light[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss2_move_pain_light = {
+ FRAME_pain20,
+ FRAME_pain23,
+ boss2_frames_pain_light,
+ boss2_run
+};
+
+mframe_t boss2_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, BossExplode}
+};
+
+mmove_t boss2_move_death = {
+ FRAME_death2,
+ FRAME_death50,
+ boss2_frames_death,
+ boss2_dead
+};
+
+void
+boss2_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss2_move_stand;
+}
+
+void
+boss2_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &boss2_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_run;
+ }
+}
+
+void
+boss2_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss2_move_walk;
+}
+
+void
+boss2_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ if (range <= 125)
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+ }
+ else
+ {
+ if (random() <= 0.6)
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_rocket;
+ }
+ }
+}
+
+void
+boss2_attack_mg(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss2_move_attack_mg;
+}
+
+void
+boss2_reattack_mg(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (infront(self, self->enemy))
+ {
+ if (random() <= 0.7)
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_mg;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+ }
+}
+
+void
+boss2_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (damage < 10)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &boss2_move_pain_light;
+ }
+ else if (damage < 30)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &boss2_move_pain_light;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &boss2_move_pain_heavy;
+ }
+}
+
+void
+boss2_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -56, -56, 0);
+ VectorSet(self->maxs, 56, 56, 80);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+boss2_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ self->monsterinfo.currentmove = &boss2_move_death;
+}
+
+qboolean
+Boss2_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace( spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ return false;
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.8;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.8;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (random() < chance)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.3)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_boss2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("bosshovr/bhvpain1.wav");
+ sound_pain2 = gi.soundindex("bosshovr/bhvpain2.wav");
+ sound_pain3 = gi.soundindex("bosshovr/bhvpain3.wav");
+ sound_death = gi.soundindex("bosshovr/bhvdeth1.wav");
+ sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav");
+
+ self->s.sound = gi.soundindex("bosshovr/bhvengn1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss2/tris.md2");
+ VectorSet(self->mins, -56, -56, 0);
+ VectorSet(self->maxs, 56, 56, 80);
+
+ self->health = 2000;
+ self->gib_health = -200;
+ self->mass = 1000;
+
+ self->flags |= FL_IMMUNE_LASER;
+
+ self->pain = boss2_pain;
+ self->die = boss2_die;
+
+ self->monsterinfo.stand = boss2_stand;
+ self->monsterinfo.walk = boss2_walk;
+ self->monsterinfo.run = boss2_run;
+ self->monsterinfo.attack = boss2_attack;
+ self->monsterinfo.search = boss2_search;
+ self->monsterinfo.checkattack = Boss2_CheckAttack;
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &boss2_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/xatrix/src/monster/boss2/boss2.h b/xatrix/src/monster/boss2/boss2.h
new file mode 100644
index 0000000..2e5a246
--- /dev/null
+++ b/xatrix/src/monster/boss2/boss2.h
@@ -0,0 +1,190 @@
+/* =======================================================================
+ *
+ * Animations for boss2.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand30 0
+#define FRAME_stand31 1
+#define FRAME_stand32 2
+#define FRAME_stand33 3
+#define FRAME_stand34 4
+#define FRAME_stand35 5
+#define FRAME_stand36 6
+#define FRAME_stand37 7
+#define FRAME_stand38 8
+#define FRAME_stand39 9
+#define FRAME_stand40 10
+#define FRAME_stand41 11
+#define FRAME_stand42 12
+#define FRAME_stand43 13
+#define FRAME_stand44 14
+#define FRAME_stand45 15
+#define FRAME_stand46 16
+#define FRAME_stand47 17
+#define FRAME_stand48 18
+#define FRAME_stand49 19
+#define FRAME_stand50 20
+#define FRAME_stand1 21
+#define FRAME_stand2 22
+#define FRAME_stand3 23
+#define FRAME_stand4 24
+#define FRAME_stand5 25
+#define FRAME_stand6 26
+#define FRAME_stand7 27
+#define FRAME_stand8 28
+#define FRAME_stand9 29
+#define FRAME_stand10 30
+#define FRAME_stand11 31
+#define FRAME_stand12 32
+#define FRAME_stand13 33
+#define FRAME_stand14 34
+#define FRAME_stand15 35
+#define FRAME_stand16 36
+#define FRAME_stand17 37
+#define FRAME_stand18 38
+#define FRAME_stand19 39
+#define FRAME_stand20 40
+#define FRAME_stand21 41
+#define FRAME_stand22 42
+#define FRAME_stand23 43
+#define FRAME_stand24 44
+#define FRAME_stand25 45
+#define FRAME_stand26 46
+#define FRAME_stand27 47
+#define FRAME_stand28 48
+#define FRAME_stand29 49
+#define FRAME_walk1 50
+#define FRAME_walk2 51
+#define FRAME_walk3 52
+#define FRAME_walk4 53
+#define FRAME_walk5 54
+#define FRAME_walk6 55
+#define FRAME_walk7 56
+#define FRAME_walk8 57
+#define FRAME_walk9 58
+#define FRAME_walk10 59
+#define FRAME_walk11 60
+#define FRAME_walk12 61
+#define FRAME_walk13 62
+#define FRAME_walk14 63
+#define FRAME_walk15 64
+#define FRAME_walk16 65
+#define FRAME_walk17 66
+#define FRAME_walk18 67
+#define FRAME_walk19 68
+#define FRAME_walk20 69
+#define FRAME_attack1 70
+#define FRAME_attack2 71
+#define FRAME_attack3 72
+#define FRAME_attack4 73
+#define FRAME_attack5 74
+#define FRAME_attack6 75
+#define FRAME_attack7 76
+#define FRAME_attack8 77
+#define FRAME_attack9 78
+#define FRAME_attack10 79
+#define FRAME_attack11 80
+#define FRAME_attack12 81
+#define FRAME_attack13 82
+#define FRAME_attack14 83
+#define FRAME_attack15 84
+#define FRAME_attack16 85
+#define FRAME_attack17 86
+#define FRAME_attack18 87
+#define FRAME_attack19 88
+#define FRAME_attack20 89
+#define FRAME_attack21 90
+#define FRAME_attack22 91
+#define FRAME_attack23 92
+#define FRAME_attack24 93
+#define FRAME_attack25 94
+#define FRAME_attack26 95
+#define FRAME_attack27 96
+#define FRAME_attack28 97
+#define FRAME_attack29 98
+#define FRAME_attack30 99
+#define FRAME_attack31 100
+#define FRAME_attack32 101
+#define FRAME_attack33 102
+#define FRAME_attack34 103
+#define FRAME_attack35 104
+#define FRAME_attack36 105
+#define FRAME_attack37 106
+#define FRAME_attack38 107
+#define FRAME_attack39 108
+#define FRAME_attack40 109
+#define FRAME_pain2 110
+#define FRAME_pain3 111
+#define FRAME_pain4 112
+#define FRAME_pain5 113
+#define FRAME_pain6 114
+#define FRAME_pain7 115
+#define FRAME_pain8 116
+#define FRAME_pain9 117
+#define FRAME_pain10 118
+#define FRAME_pain11 119
+#define FRAME_pain12 120
+#define FRAME_pain13 121
+#define FRAME_pain14 122
+#define FRAME_pain15 123
+#define FRAME_pain16 124
+#define FRAME_pain17 125
+#define FRAME_pain18 126
+#define FRAME_pain19 127
+#define FRAME_pain20 128
+#define FRAME_pain21 129
+#define FRAME_pain22 130
+#define FRAME_pain23 131
+#define FRAME_death2 132
+#define FRAME_death3 133
+#define FRAME_death4 134
+#define FRAME_death5 135
+#define FRAME_death6 136
+#define FRAME_death7 137
+#define FRAME_death8 138
+#define FRAME_death9 139
+#define FRAME_death10 140
+#define FRAME_death11 141
+#define FRAME_death12 142
+#define FRAME_death13 143
+#define FRAME_death14 144
+#define FRAME_death15 145
+#define FRAME_death16 146
+#define FRAME_death17 147
+#define FRAME_death18 148
+#define FRAME_death19 149
+#define FRAME_death20 150
+#define FRAME_death21 151
+#define FRAME_death22 152
+#define FRAME_death23 153
+#define FRAME_death24 154
+#define FRAME_death25 155
+#define FRAME_death26 156
+#define FRAME_death27 157
+#define FRAME_death28 158
+#define FRAME_death29 159
+#define FRAME_death30 160
+#define FRAME_death31 161
+#define FRAME_death32 162
+#define FRAME_death33 163
+#define FRAME_death34 164
+#define FRAME_death35 165
+#define FRAME_death36 166
+#define FRAME_death37 167
+#define FRAME_death38 168
+#define FRAME_death39 169
+#define FRAME_death40 170
+#define FRAME_death41 171
+#define FRAME_death42 172
+#define FRAME_death43 173
+#define FRAME_death44 174
+#define FRAME_death45 175
+#define FRAME_death46 176
+#define FRAME_death47 177
+#define FRAME_death48 178
+#define FRAME_death49 179
+#define FRAME_death50 180
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/boss3/boss3.c b/xatrix/src/monster/boss3/boss3.c
new file mode 100644
index 0000000..d2ed211
--- /dev/null
+++ b/xatrix/src/monster/boss3/boss3.c
@@ -0,0 +1,74 @@
+#include "../../header/local.h"
+#include "boss32.h"
+
+void
+Use_Boss3(edict_t *ent, edict_t *other /* unused */,
+ edict_t *activator /* unused */)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BOSSTPORT);
+ gi.WritePosition(ent->s.origin);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+ G_FreeEdict(ent);
+}
+
+void
+Think_Boss3Stand(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.frame == FRAME_stand260)
+ {
+ ent->s.frame = FRAME_stand201;
+ }
+ else
+ {
+ ent->s.frame++;
+ }
+
+ ent->nextthink = level.time + FRAMETIME;
+}
+
+/*
+ * QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90)
+ *
+ * Just stands and cycles in one place until targeted, then teleports away.
+ */
+void
+SP_monster_boss3_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->model = "models/monsters/boss3/rider/tris.md2";
+ self->s.modelindex = gi.modelindex(self->model);
+ self->s.frame = FRAME_stand201;
+
+ gi.soundindex("misc/bigtele.wav");
+
+ VectorSet(self->mins, -32, -32, 0);
+ VectorSet(self->maxs, 32, 32, 90);
+
+ self->use = Use_Boss3;
+ self->think = Think_Boss3Stand;
+ self->nextthink = level.time + FRAMETIME;
+ gi.linkentity(self);
+}
diff --git a/xatrix/src/monster/boss3/boss31.c b/xatrix/src/monster/boss3/boss31.c
new file mode 100644
index 0000000..a8f70d0
--- /dev/null
+++ b/xatrix/src/monster/boss3/boss31.c
@@ -0,0 +1,910 @@
+/* =======================================================================
+ *
+ * Final boss, stage 1 (jorg).
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "boss31.h"
+
+extern void SP_monster_makron(edict_t *self);
+qboolean visible(edict_t *self, edict_t *other);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_idle;
+static int sound_death;
+static int sound_search1;
+static int sound_search2;
+static int sound_search3;
+static int sound_attack1;
+static int sound_attack2;
+static int sound_firegun;
+static int sound_step_left;
+static int sound_step_right;
+static int sound_death_hit;
+
+void BossExplode(edict_t *self);
+void MakronToss(edict_t *self);
+void MakronPrecache(void);
+void jorg_dead(edict_t *self);
+void jorgBFG(edict_t *self);
+void jorgMachineGun(edict_t *self);
+void jorg_firebullet(edict_t *self);
+void jorg_reattack1(edict_t *self);
+void jorg_attack1(edict_t *self);
+void jorg_idle(edict_t *self);
+void jorg_step_left(edict_t *self);
+void jorg_step_right(edict_t *self);
+void jorg_death_hit(edict_t *self);
+
+void
+jorg_search(edict_t *self)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r <= 0.3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+ }
+ else if (r <= 0.6)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0);
+ }
+}
+
+/* stand */
+mframe_t jorg_frames_stand[] = {
+ {ai_stand, 0, jorg_idle},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 10 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 20 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 30 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 19, NULL},
+ {ai_stand, 11, jorg_step_left},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 6, NULL},
+ {ai_stand, 9, jorg_step_right},
+ {ai_stand, 0, NULL}, /* 40 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -2, NULL},
+ {ai_stand, -17, jorg_step_left},
+ {ai_stand, 0, NULL},
+ {ai_stand, -12, NULL}, /* 50 */
+ {ai_stand, -14, jorg_step_right} /* 51 */
+};
+
+mmove_t jorg_move_stand = {
+ FRAME_stand01,
+ FRAME_stand51,
+ jorg_frames_stand,
+ NULL
+};
+
+void
+jorg_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_death_hit(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_step_left(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_step_right(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0);
+}
+
+void
+jorg_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &jorg_move_stand;
+}
+
+mframe_t jorg_frames_run[] = {
+ {ai_run, 17, jorg_step_left},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 33, jorg_step_right},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, NULL}
+};
+
+mmove_t jorg_move_run = {
+ FRAME_walk06,
+ FRAME_walk19,
+ jorg_frames_run,
+ NULL
+};
+
+/* walk */
+mframe_t jorg_frames_start_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 15, NULL}
+};
+
+mmove_t jorg_move_start_walk = {
+ FRAME_walk01,
+ FRAME_walk05,
+ jorg_frames_start_walk,
+ NULL
+};
+
+mframe_t jorg_frames_walk[] = {
+ {ai_walk, 17, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 33, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 9, NULL}
+};
+
+mmove_t jorg_move_walk = {
+ FRAME_walk06,
+ FRAME_walk19,
+ jorg_frames_walk,
+ NULL
+};
+
+mframe_t jorg_frames_end_walk[] = {
+ {ai_walk, 11, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, -8, NULL}
+};
+
+mmove_t jorg_move_end_walk = {
+ FRAME_walk20,
+ FRAME_walk25,
+ jorg_frames_end_walk,
+ NULL
+};
+
+void
+jorg_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &jorg_move_walk;
+}
+
+void
+jorg_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &jorg_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &jorg_move_run;
+ }
+}
+
+mframe_t jorg_frames_pain3[] = {
+ {ai_move, -28, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -3, jorg_step_left},
+ {ai_move, -9, NULL},
+ {ai_move, 0, jorg_step_right},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -11, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 7, jorg_step_left},
+ {ai_move, 17, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, jorg_step_right}
+};
+
+mmove_t jorg_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain325,
+ jorg_frames_pain3,
+ jorg_run
+};
+
+mframe_t jorg_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain203,
+ jorg_frames_pain2,
+ jorg_run
+};
+
+mframe_t jorg_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain103,
+ jorg_frames_pain1,
+ jorg_run
+};
+
+mframe_t jorg_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 10 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 20 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 30 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 40 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, MakronToss},
+ {ai_move, 0, BossExplode} /* 50 */
+};
+
+mmove_t jorg_move_death = {
+ FRAME_death01,
+ FRAME_death50,
+ jorg_frames_death1,
+ jorg_dead
+};
+
+mframe_t jorg_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, jorgBFG},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak213,
+ jorg_frames_attack2,
+ jorg_run
+};
+
+mframe_t jorg_frames_start_attack1[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t jorg_move_start_attack1 = {
+ FRAME_attak101,
+ FRAME_attak108,
+ jorg_frames_start_attack1,
+ jorg_attack1
+};
+
+mframe_t jorg_frames_attack1[] = {
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet},
+ {ai_charge, 0, jorg_firebullet}
+};
+
+mmove_t jorg_move_attack1 = {
+ FRAME_attak109,
+ FRAME_attak114,
+ jorg_frames_attack1,
+ jorg_reattack1
+};
+
+mframe_t jorg_frames_end_attack1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t jorg_move_end_attack1 = {
+ FRAME_attak115,
+ FRAME_attak118,
+ jorg_frames_end_attack1,
+ jorg_run
+};
+
+void
+jorg_reattack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ if (random() < 0.9)
+ {
+ self->monsterinfo.currentmove = &jorg_move_attack1;
+ }
+ else
+ {
+ self->s.sound = 0;
+ self->monsterinfo.currentmove = &jorg_move_end_attack1;
+ }
+ }
+ else
+ {
+ self->s.sound = 0;
+ self->monsterinfo.currentmove = &jorg_move_end_attack1;
+ }
+}
+
+void
+jorg_attack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &jorg_move_attack1;
+}
+
+void
+jorg_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ self->s.sound = 0;
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ /* Lessen the chance of him going into his
+ pain frames if he takes little damage */
+ if (damage <= 40)
+ {
+ if (random() <= 0.6)
+ {
+ return;
+ }
+ }
+
+ /* If he's entering his attack1 or using attack1,
+ lessen the chance of him going into pain */
+ if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108))
+ {
+ if (random() <= 0.005)
+ {
+ return;
+ }
+ }
+
+ if ((self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114))
+ {
+ if (random() <= 0.00005)
+ {
+ return;
+ }
+ }
+
+ if ((self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208))
+ {
+ if (random() <= 0.005)
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 50)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_pain1;
+ }
+ else if (damage <= 100)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_pain2;
+ }
+ else
+ {
+ if (random() <= 0.3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_pain3;
+ }
+ }
+}
+
+void
+jorgBFG(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0);
+ monster_fire_bfg(self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1);
+}
+
+void
+jorg_firebullet_right(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1);
+}
+
+void
+jorg_firebullet_left(edict_t *self)
+{
+ vec3_t forward, right, target;
+ vec3_t start;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1],
+ forward, right, start);
+
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1);
+}
+
+void
+jorg_firebullet(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ jorg_firebullet_left(self);
+ jorg_firebullet_right(self);
+}
+
+void
+jorg_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.75)
+ {
+ gi.sound(self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM, 0);
+ self->s.sound = gi.soundindex("boss3/w_loop.wav");
+ self->monsterinfo.currentmove = &jorg_move_start_attack1;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &jorg_move_attack2;
+ }
+}
+
+void
+jorg_dead(edict_t *self /* unused */)
+{
+ /* unused, but removal is PITA */
+}
+
+void
+jorg_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->s.sound = 0;
+ self->count = 0;
+ self->monsterinfo.currentmove = &jorg_move_death;
+}
+
+qboolean
+Jorg_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ return false;
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.2;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (random() < chance)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.3)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_jorg(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("boss3/bs3pain1.wav");
+ sound_pain2 = gi.soundindex("boss3/bs3pain2.wav");
+ sound_pain3 = gi.soundindex("boss3/bs3pain3.wav");
+ sound_death = gi.soundindex("boss3/bs3deth1.wav");
+ sound_attack1 = gi.soundindex("boss3/bs3atck1.wav");
+ sound_attack2 = gi.soundindex("boss3/bs3atck2.wav");
+ sound_search1 = gi.soundindex("boss3/bs3srch1.wav");
+ sound_search2 = gi.soundindex("boss3/bs3srch2.wav");
+ sound_search3 = gi.soundindex("boss3/bs3srch3.wav");
+ sound_idle = gi.soundindex("boss3/bs3idle1.wav");
+ sound_step_left = gi.soundindex("boss3/step1.wav");
+ sound_step_right = gi.soundindex("boss3/step2.wav");
+ sound_firegun = gi.soundindex("boss3/xfire.wav");
+ sound_death_hit = gi.soundindex("boss3/d_hit.wav");
+
+ MakronPrecache();
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss3/jorg/tris.md2");
+ self->s.modelindex2 = gi.modelindex("models/monsters/boss3/rider/tris.md2");
+ VectorSet(self->mins, -80, -80, 0);
+ VectorSet(self->maxs, 80, 80, 140);
+
+ self->health = 3000;
+ self->gib_health = -2000;
+ self->mass = 1000;
+
+ self->pain = jorg_pain;
+ self->die = jorg_die;
+ self->monsterinfo.stand = jorg_stand;
+ self->monsterinfo.walk = jorg_walk;
+ self->monsterinfo.run = jorg_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = jorg_attack;
+ self->monsterinfo.search = jorg_search;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = NULL;
+ self->monsterinfo.checkattack = Jorg_CheckAttack;
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &jorg_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/boss3/boss31.h b/xatrix/src/monster/boss3/boss31.h
new file mode 100644
index 0000000..328d621
--- /dev/null
+++ b/xatrix/src/monster/boss3/boss31.h
@@ -0,0 +1,197 @@
+/* =======================================================================
+ *
+ * Animations for final boss stage 1.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak113 12
+#define FRAME_attak114 13
+#define FRAME_attak115 14
+#define FRAME_attak116 15
+#define FRAME_attak117 16
+#define FRAME_attak118 17
+#define FRAME_attak201 18
+#define FRAME_attak202 19
+#define FRAME_attak203 20
+#define FRAME_attak204 21
+#define FRAME_attak205 22
+#define FRAME_attak206 23
+#define FRAME_attak207 24
+#define FRAME_attak208 25
+#define FRAME_attak209 26
+#define FRAME_attak210 27
+#define FRAME_attak211 28
+#define FRAME_attak212 29
+#define FRAME_attak213 30
+#define FRAME_death01 31
+#define FRAME_death02 32
+#define FRAME_death03 33
+#define FRAME_death04 34
+#define FRAME_death05 35
+#define FRAME_death06 36
+#define FRAME_death07 37
+#define FRAME_death08 38
+#define FRAME_death09 39
+#define FRAME_death10 40
+#define FRAME_death11 41
+#define FRAME_death12 42
+#define FRAME_death13 43
+#define FRAME_death14 44
+#define FRAME_death15 45
+#define FRAME_death16 46
+#define FRAME_death17 47
+#define FRAME_death18 48
+#define FRAME_death19 49
+#define FRAME_death20 50
+#define FRAME_death21 51
+#define FRAME_death22 52
+#define FRAME_death23 53
+#define FRAME_death24 54
+#define FRAME_death25 55
+#define FRAME_death26 56
+#define FRAME_death27 57
+#define FRAME_death28 58
+#define FRAME_death29 59
+#define FRAME_death30 60
+#define FRAME_death31 61
+#define FRAME_death32 62
+#define FRAME_death33 63
+#define FRAME_death34 64
+#define FRAME_death35 65
+#define FRAME_death36 66
+#define FRAME_death37 67
+#define FRAME_death38 68
+#define FRAME_death39 69
+#define FRAME_death40 70
+#define FRAME_death41 71
+#define FRAME_death42 72
+#define FRAME_death43 73
+#define FRAME_death44 74
+#define FRAME_death45 75
+#define FRAME_death46 76
+#define FRAME_death47 77
+#define FRAME_death48 78
+#define FRAME_death49 79
+#define FRAME_death50 80
+#define FRAME_pain101 81
+#define FRAME_pain102 82
+#define FRAME_pain103 83
+#define FRAME_pain201 84
+#define FRAME_pain202 85
+#define FRAME_pain203 86
+#define FRAME_pain301 87
+#define FRAME_pain302 88
+#define FRAME_pain303 89
+#define FRAME_pain304 90
+#define FRAME_pain305 91
+#define FRAME_pain306 92
+#define FRAME_pain307 93
+#define FRAME_pain308 94
+#define FRAME_pain309 95
+#define FRAME_pain310 96
+#define FRAME_pain311 97
+#define FRAME_pain312 98
+#define FRAME_pain313 99
+#define FRAME_pain314 100
+#define FRAME_pain315 101
+#define FRAME_pain316 102
+#define FRAME_pain317 103
+#define FRAME_pain318 104
+#define FRAME_pain319 105
+#define FRAME_pain320 106
+#define FRAME_pain321 107
+#define FRAME_pain322 108
+#define FRAME_pain323 109
+#define FRAME_pain324 110
+#define FRAME_pain325 111
+#define FRAME_stand01 112
+#define FRAME_stand02 113
+#define FRAME_stand03 114
+#define FRAME_stand04 115
+#define FRAME_stand05 116
+#define FRAME_stand06 117
+#define FRAME_stand07 118
+#define FRAME_stand08 119
+#define FRAME_stand09 120
+#define FRAME_stand10 121
+#define FRAME_stand11 122
+#define FRAME_stand12 123
+#define FRAME_stand13 124
+#define FRAME_stand14 125
+#define FRAME_stand15 126
+#define FRAME_stand16 127
+#define FRAME_stand17 128
+#define FRAME_stand18 129
+#define FRAME_stand19 130
+#define FRAME_stand20 131
+#define FRAME_stand21 132
+#define FRAME_stand22 133
+#define FRAME_stand23 134
+#define FRAME_stand24 135
+#define FRAME_stand25 136
+#define FRAME_stand26 137
+#define FRAME_stand27 138
+#define FRAME_stand28 139
+#define FRAME_stand29 140
+#define FRAME_stand30 141
+#define FRAME_stand31 142
+#define FRAME_stand32 143
+#define FRAME_stand33 144
+#define FRAME_stand34 145
+#define FRAME_stand35 146
+#define FRAME_stand36 147
+#define FRAME_stand37 148
+#define FRAME_stand38 149
+#define FRAME_stand39 150
+#define FRAME_stand40 151
+#define FRAME_stand41 152
+#define FRAME_stand42 153
+#define FRAME_stand43 154
+#define FRAME_stand44 155
+#define FRAME_stand45 156
+#define FRAME_stand46 157
+#define FRAME_stand47 158
+#define FRAME_stand48 159
+#define FRAME_stand49 160
+#define FRAME_stand50 161
+#define FRAME_stand51 162
+#define FRAME_walk01 163
+#define FRAME_walk02 164
+#define FRAME_walk03 165
+#define FRAME_walk04 166
+#define FRAME_walk05 167
+#define FRAME_walk06 168
+#define FRAME_walk07 169
+#define FRAME_walk08 170
+#define FRAME_walk09 171
+#define FRAME_walk10 172
+#define FRAME_walk11 173
+#define FRAME_walk12 174
+#define FRAME_walk13 175
+#define FRAME_walk14 176
+#define FRAME_walk15 177
+#define FRAME_walk16 178
+#define FRAME_walk17 179
+#define FRAME_walk18 180
+#define FRAME_walk19 181
+#define FRAME_walk20 182
+#define FRAME_walk21 183
+#define FRAME_walk22 184
+#define FRAME_walk23 185
+#define FRAME_walk24 186
+#define FRAME_walk25 187
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/boss3/boss32.c b/xatrix/src/monster/boss3/boss32.c
new file mode 100644
index 0000000..95ffddd
--- /dev/null
+++ b/xatrix/src/monster/boss3/boss32.c
@@ -0,0 +1,1252 @@
+/* =======================================================================
+ *
+ * Final boss, stage 2 (makron).
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "boss32.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+
+void MakronRailgun(edict_t *self);
+void MakronSaveloc(edict_t *self);
+void MakronHyperblaster(edict_t *self);
+void makron_step_left(edict_t *self);
+void makron_step_right(edict_t *self);
+void makronBFG(edict_t *self);
+void makron_dead(edict_t *self);
+
+static int sound_pain4;
+static int sound_pain5;
+static int sound_pain6;
+static int sound_death;
+static int sound_step_left;
+static int sound_step_right;
+static int sound_attack_bfg;
+static int sound_brainsplorch;
+static int sound_prerailgun;
+static int sound_popup;
+static int sound_taunt1;
+static int sound_taunt2;
+static int sound_taunt3;
+static int sound_hit;
+
+void
+makron_taunt(edict_t *self)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r <= 0.3)
+ {
+ gi.sound(self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0);
+ }
+ else if (r <= 0.6)
+ {
+ gi.sound(self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0);
+ }
+}
+
+/* stand */
+mframe_t makron_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 10 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 20 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 30 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 40 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 50 */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL} /* 60 */
+};
+
+mmove_t makron_move_stand = {
+ FRAME_stand201,
+ FRAME_stand260,
+ makron_frames_stand,
+ NULL
+};
+
+void
+makron_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &makron_move_stand;
+}
+
+mframe_t makron_frames_run[] = {
+ {ai_run, 3, makron_step_left},
+ {ai_run, 12, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, makron_step_right},
+ {ai_run, 6, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 12, NULL}
+};
+
+mmove_t makron_move_run = {
+ FRAME_walk204,
+ FRAME_walk213,
+ makron_frames_run,
+ NULL
+};
+
+void
+makron_hit(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_AUTO, sound_hit, 1, ATTN_NONE, 0);
+}
+
+void
+makron_popup(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_popup, 1, ATTN_NONE, 0);
+}
+
+void
+makron_step_left(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0);
+}
+
+void
+makron_step_right(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0);
+}
+
+void
+makron_brainsplorch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM, 0);
+}
+
+void
+makron_prerailgun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM, 0);
+}
+
+mframe_t makron_frames_walk[] = {
+ {ai_walk, 3, makron_step_left},
+ {ai_walk, 12, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 8, makron_step_right},
+ {ai_walk, 6, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 12, NULL}
+};
+
+mmove_t makron_move_walk = {
+ FRAME_walk204,
+ FRAME_walk213,
+ makron_frames_run,
+ NULL
+};
+
+void
+makron_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &makron_move_walk;
+}
+
+void
+makron_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &makron_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &makron_move_run;
+ }
+}
+
+mframe_t makron_frames_pain6[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 10 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, makron_popup},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 20 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, makron_taunt},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_pain6 = {
+ FRAME_pain601,
+ FRAME_pain627,
+ makron_frames_pain6,
+ makron_run
+};
+
+mframe_t makron_frames_pain5[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_pain5 = {
+ FRAME_pain501,
+ FRAME_pain504,
+ makron_frames_pain5,
+ makron_run
+};
+
+mframe_t makron_frames_pain4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_pain4 = {
+ FRAME_pain401,
+ FRAME_pain404,
+ makron_frames_pain4,
+ makron_run
+};
+
+mframe_t makron_frames_death2[] = {
+ {ai_move, -15, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -12, NULL},
+ {ai_move, 0, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 10 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 12, NULL},
+ {ai_move, 11, makron_step_right},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 20 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 30 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 6, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 2, NULL}, /* 40 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 50 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -6, makron_step_right},
+ {ai_move, -4, NULL},
+ {ai_move, -4, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 60 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -3, makron_step_right},
+ {ai_move, -8, NULL},
+ {ai_move, -3, makron_step_left},
+ {ai_move, -7, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -4, makron_step_right}, /* 70 */
+ {ai_move, -6, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 0, makron_step_left},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 80 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL}, /* 90 */
+ {ai_move, 27, makron_hit},
+ {ai_move, 26, NULL},
+ {ai_move, 0, makron_brainsplorch},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL} /* 95 */
+};
+
+mmove_t makron_move_death2 = {
+ FRAME_death201,
+ FRAME_death295,
+ makron_frames_death2,
+ makron_dead
+};
+
+mframe_t makron_frames_death3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_death3 = {
+ FRAME_death301,
+ FRAME_death320,
+ makron_frames_death3,
+ NULL
+};
+
+mframe_t makron_frames_sight[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_sight = {
+ FRAME_active01,
+ FRAME_active13,
+ makron_frames_sight,
+ makron_run
+};
+
+void
+makronBFG(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+ gi.sound(self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0);
+ monster_fire_bfg(self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG);
+}
+
+mframe_t makron_frames_attack3[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, makronBFG},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_attack3 = {
+ FRAME_attak301,
+ FRAME_attak308,
+ makron_frames_attack3,
+ makron_run
+};
+
+mframe_t makron_frames_attack4[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, MakronHyperblaster}, /* fire */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_attack4 = {
+ FRAME_attak401,
+ FRAME_attak426,
+ makron_frames_attack4,
+ makron_run
+};
+
+mframe_t makron_frames_attack5[] = {
+ {ai_charge, 0, makron_prerailgun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, MakronSaveloc},
+ {ai_move, 0, MakronRailgun}, /* Fire railgun */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t makron_move_attack5 = {
+ FRAME_attak501,
+ FRAME_attak516,
+ makron_frames_attack5,
+ makron_run
+};
+
+void
+MakronSaveloc(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+}
+
+void
+MakronRailgun(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1],
+ forward, right, start);
+
+ /* calc direction to where we targted */
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_railgun(self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1);
+}
+
+void
+MakronHyperblaster(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t vec;
+ vec3_t start;
+ vec3_t forward, right;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, vec);
+ vectoangles(vec, vec);
+ dir[0] = vec[0];
+ }
+ else
+ {
+ dir[0] = 0;
+ }
+
+ if (self->s.frame <= FRAME_attak413)
+ {
+ dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413);
+ }
+ else
+ {
+ dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421);
+ }
+
+ dir[2] = 0;
+
+ AngleVectors(dir, forward, NULL, NULL);
+
+ monster_fire_blaster(self, start, forward, 15, 1000,
+ MZ2_MAKRON_BLASTER_1, EF_BLASTER);
+}
+
+void
+makron_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ /* Lessen the chance of him
+ going into his pain frames */
+ if (damage <= 25)
+ {
+ if (random() < 0.2)
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 40)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain4;
+ }
+ else if (damage <= 110)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain5;
+ }
+ else
+ {
+ if (damage <= 150)
+ {
+ if (random() <= 0.45)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain6;
+ }
+ }
+ else
+ {
+ if (random() <= 0.35)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0);
+ self->monsterinfo.currentmove = &makron_move_pain6;
+ }
+ }
+ }
+}
+
+void
+makron_sight(edict_t *self, edict_t *other /* unused */)
+{
+}
+
+void
+makron_attack(edict_t *self)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r <= 0.3)
+ {
+ self->monsterinfo.currentmove = &makron_move_attack3;
+ }
+ else if (r <= 0.6)
+ {
+ self->monsterinfo.currentmove = &makron_move_attack4;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &makron_move_attack5;
+ }
+}
+
+/* Makron Torso. This needs to be spawned in */
+
+void
+makron_torso_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* detach from the makron if the legs are gone completely */
+ if (self->owner && (!self->owner->inuse || (self->owner->health <= self->owner->gib_health)))
+ {
+ self->owner = NULL;
+ }
+
+ /* if the makron is revived the torso was put back on him */
+ if (self->owner && self->owner->deadflag != DEAD_DEAD)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (++self->s.frame >= FRAME_death320)
+ {
+ self->s.frame = FRAME_death301;
+ }
+
+ self->nextthink = level.time + FRAMETIME;
+}
+
+static void
+makron_torso_origin(edict_t *self, edict_t *torso)
+{
+ vec3_t v;
+ trace_t tr;
+
+ AngleVectors(self->s.angles, v, NULL, NULL);
+ VectorMA(self->s.origin, -84.0f, v, v);
+
+ tr = gi.trace(self->s.origin, torso->mins, torso->maxs, v, self, MASK_SOLID);
+
+ VectorCopy (tr.endpos, torso->s.origin);
+}
+
+void
+makron_torso_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ int n;
+
+ if (self->health > self->gib_health)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex( "misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2",
+ damage, GIB_METALLIC);
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+makron_torso(edict_t *self)
+{
+ edict_t *torso;
+
+ if (!self)
+ {
+ return;
+ }
+
+ torso = G_SpawnOptional();
+
+ if (!torso)
+ {
+ return;
+ }
+
+ VectorCopy(self->s.angles, torso->s.angles);
+ VectorSet(torso->mins, -24, -24, 0);
+ VectorSet(torso->maxs, 24, 24, 16);
+ makron_torso_origin(self, torso);
+
+ torso->gib_health = -800;
+ torso->takedamage = DAMAGE_YES;
+ torso->die = makron_torso_die;
+ torso->deadflag = DEAD_DEAD;
+
+ torso->owner = self;
+ torso->movetype = MOVETYPE_TOSS;
+ torso->solid = SOLID_BBOX;
+ torso->svflags = SVF_MONSTER|SVF_DEADMONSTER;
+ torso->clipmask = MASK_MONSTERSOLID;
+ torso->s.frame = FRAME_death301;
+ torso->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2");
+ torso->think = makron_torso_think;
+ torso->nextthink = level.time + 2 * FRAMETIME;
+ torso->s.sound = gi.soundindex("makron/spine.wav");
+
+ gi.linkentity(torso);
+}
+
+/* death */
+void
+makron_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -48, -48, 0);
+ VectorSet(self->maxs, 48, 48, 24);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+makron_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.sound = 0;
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 1 /*4*/; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2",
+ damage, GIB_METALLIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/gear/tris.md2",
+ damage, GIB_METALLIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ makron_torso(self);
+
+ /* lower bbox since the torso is gone */
+ self->maxs[2] = 64;
+ gi.linkentity (self);
+
+ self->monsterinfo.currentmove = &makron_move_death2;
+}
+
+qboolean
+Makron_CheckAttack(edict_t *self)
+{
+ vec3_t spot1, spot2;
+ vec3_t temp;
+ float chance;
+ trace_t tr;
+ int enemy_range;
+ float enemy_yaw;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ /* see if any entities are in the way of the shot */
+ VectorCopy(self->s.origin, spot1);
+ spot1[2] += self->viewheight;
+ VectorCopy(self->enemy->s.origin, spot2);
+ spot2[2] += self->enemy->viewheight;
+
+ tr = gi.trace(spot1, NULL, NULL, spot2, self,
+ CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME |
+ CONTENTS_LAVA);
+
+ /* do we have a clear shot? */
+ if (tr.ent != self->enemy)
+ {
+ return false;
+ }
+ }
+
+ enemy_range = range(self, self->enemy);
+ VectorSubtract(self->enemy->s.origin, self->s.origin, temp);
+ enemy_yaw = vectoyaw(temp);
+
+ self->ideal_yaw = enemy_yaw;
+
+ /* melee attack */
+ if (enemy_range == RANGE_MELEE)
+ {
+ if (self->monsterinfo.melee)
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ }
+
+ return true;
+ }
+
+ /* missile attack */
+ if (!self->monsterinfo.attack)
+ {
+ return false;
+ }
+
+ if (level.time < self->monsterinfo.attack_finished)
+ {
+ return false;
+ }
+
+ if (enemy_range == RANGE_FAR)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_NEAR)
+ {
+ chance = 0.4;
+ }
+ else if (enemy_range == RANGE_MID)
+ {
+ chance = 0.2;
+ }
+ else
+ {
+ return false;
+ }
+
+ if (random() < chance)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ self->monsterinfo.attack_finished = level.time + 2 * random();
+ return true;
+ }
+
+ if (self->flags & FL_FLY)
+ {
+ if (random() < 0.3)
+ {
+ self->monsterinfo.attack_state = AS_SLIDING;
+ }
+ else
+ {
+ self->monsterinfo.attack_state = AS_STRAIGHT;
+ }
+ }
+
+ return false;
+}
+
+/* monster_makron */
+
+void
+MakronPrecache(void)
+{
+ sound_pain4 = gi.soundindex("makron/pain3.wav");
+ sound_pain5 = gi.soundindex("makron/pain2.wav");
+ sound_pain6 = gi.soundindex("makron/pain1.wav");
+ sound_death = gi.soundindex("makron/death.wav");
+ sound_step_left = gi.soundindex("makron/step1.wav");
+ sound_step_right = gi.soundindex("makron/step2.wav");
+ sound_attack_bfg = gi.soundindex("makron/bfg_fire.wav");
+ sound_brainsplorch = gi.soundindex("makron/brain1.wav");
+ sound_prerailgun = gi.soundindex("makron/rail_up.wav");
+ sound_popup = gi.soundindex("makron/popup.wav");
+ sound_taunt1 = gi.soundindex("makron/voice4.wav");
+ sound_taunt2 = gi.soundindex("makron/voice3.wav");
+ sound_taunt3 = gi.soundindex("makron/voice.wav");
+ sound_hit = gi.soundindex("makron/bhit.wav");
+
+ gi.modelindex("models/monsters/boss3/rider/tris.md2");
+}
+
+/*
+ * QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_makron(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ MakronPrecache();
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2");
+ VectorSet(self->mins, -30, -30, 0);
+ VectorSet(self->maxs, 30, 30, 90);
+
+ self->health = 3000;
+ self->gib_health = -2000;
+ self->mass = 500;
+
+ self->pain = makron_pain;
+ self->die = makron_die;
+ self->monsterinfo.stand = makron_stand;
+ self->monsterinfo.walk = makron_walk;
+ self->monsterinfo.run = makron_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = makron_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = makron_sight;
+ self->monsterinfo.checkattack = Makron_CheckAttack;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &makron_move_sight;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
+
+void
+MakronSpawn(edict_t *self)
+{
+ vec3_t vec;
+ edict_t *enemy;
+ edict_t *oldenemy;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* spawning can mess with enemy state so clear it temporarily */
+ enemy = self->enemy;
+ self->enemy = NULL;
+
+ oldenemy = self->oldenemy;
+ self->oldenemy = NULL;
+
+ SP_monster_makron(self);
+ if (self->think)
+ {
+ self->think(self);
+ }
+
+ /* and re-link enemy state now that he's spawned */
+ if (enemy && enemy->inuse && enemy->deadflag != DEAD_DEAD)
+ {
+ self->enemy = enemy;
+ }
+
+ if (oldenemy && oldenemy->inuse && oldenemy->deadflag != DEAD_DEAD)
+ {
+ self->oldenemy = oldenemy;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = self->oldenemy;
+ self->oldenemy = NULL;
+ }
+
+ enemy = self->enemy;
+
+ if (enemy)
+ {
+ FoundTarget(self);
+ VectorCopy(self->pos1, self->monsterinfo.last_sighting);
+ }
+
+ if (enemy && visible(self, enemy))
+ {
+ VectorSubtract(enemy->s.origin, self->s.origin, vec);
+ self->s.angles[YAW] = vectoyaw(vec);
+ VectorNormalize(vec);
+ }
+ else
+ {
+ AngleVectors(self->s.angles, vec, NULL, NULL);
+ }
+
+ VectorScale(vec, 400, self->velocity);
+ /* the jump frames are fixed length so best to normalize the up speed */
+ self->velocity[2] = 200.0f * (sv_gravity->value / 800.0f);
+
+ self->groundentity = NULL;
+ self->s.origin[2] += 1;
+ gi.linkentity(self);
+
+ self->pain_debounce_time = level.time + 1;
+
+ self->monsterinfo.currentmove = &makron_move_sight;
+}
+
+/*
+ * Jorg is just about dead, so set up to launch Makron out
+ */
+void
+MakronToss(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = G_Spawn();
+ ent->classname = "monster_makron";
+ ent->nextthink = level.time + 0.8;
+ ent->think = MakronSpawn;
+ ent->target = self->target;
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(self->s.angles, ent->s.angles);
+ VectorCopy(self->monsterinfo.last_sighting, ent->pos1);
+
+ ent->enemy = self->enemy;
+ ent->oldenemy = self->oldenemy;
+}
diff --git a/xatrix/src/monster/boss3/boss32.h b/xatrix/src/monster/boss3/boss32.h
new file mode 100644
index 0000000..ac583df
--- /dev/null
+++ b/xatrix/src/monster/boss3/boss32.h
@@ -0,0 +1,500 @@
+/* =======================================================================
+ *
+ * Final boss, stage 2 (makron).
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak113 12
+#define FRAME_attak114 13
+#define FRAME_attak115 14
+#define FRAME_attak116 15
+#define FRAME_attak117 16
+#define FRAME_attak118 17
+#define FRAME_attak201 18
+#define FRAME_attak202 19
+#define FRAME_attak203 20
+#define FRAME_attak204 21
+#define FRAME_attak205 22
+#define FRAME_attak206 23
+#define FRAME_attak207 24
+#define FRAME_attak208 25
+#define FRAME_attak209 26
+#define FRAME_attak210 27
+#define FRAME_attak211 28
+#define FRAME_attak212 29
+#define FRAME_attak213 30
+#define FRAME_death01 31
+#define FRAME_death02 32
+#define FRAME_death03 33
+#define FRAME_death04 34
+#define FRAME_death05 35
+#define FRAME_death06 36
+#define FRAME_death07 37
+#define FRAME_death08 38
+#define FRAME_death09 39
+#define FRAME_death10 40
+#define FRAME_death11 41
+#define FRAME_death12 42
+#define FRAME_death13 43
+#define FRAME_death14 44
+#define FRAME_death15 45
+#define FRAME_death16 46
+#define FRAME_death17 47
+#define FRAME_death18 48
+#define FRAME_death19 49
+#define FRAME_death20 50
+#define FRAME_death21 51
+#define FRAME_death22 52
+#define FRAME_death23 53
+#define FRAME_death24 54
+#define FRAME_death25 55
+#define FRAME_death26 56
+#define FRAME_death27 57
+#define FRAME_death28 58
+#define FRAME_death29 59
+#define FRAME_death30 60
+#define FRAME_death31 61
+#define FRAME_death32 62
+#define FRAME_death33 63
+#define FRAME_death34 64
+#define FRAME_death35 65
+#define FRAME_death36 66
+#define FRAME_death37 67
+#define FRAME_death38 68
+#define FRAME_death39 69
+#define FRAME_death40 70
+#define FRAME_death41 71
+#define FRAME_death42 72
+#define FRAME_death43 73
+#define FRAME_death44 74
+#define FRAME_death45 75
+#define FRAME_death46 76
+#define FRAME_death47 77
+#define FRAME_death48 78
+#define FRAME_death49 79
+#define FRAME_death50 80
+#define FRAME_pain101 81
+#define FRAME_pain102 82
+#define FRAME_pain103 83
+#define FRAME_pain201 84
+#define FRAME_pain202 85
+#define FRAME_pain203 86
+#define FRAME_pain301 87
+#define FRAME_pain302 88
+#define FRAME_pain303 89
+#define FRAME_pain304 90
+#define FRAME_pain305 91
+#define FRAME_pain306 92
+#define FRAME_pain307 93
+#define FRAME_pain308 94
+#define FRAME_pain309 95
+#define FRAME_pain310 96
+#define FRAME_pain311 97
+#define FRAME_pain312 98
+#define FRAME_pain313 99
+#define FRAME_pain314 100
+#define FRAME_pain315 101
+#define FRAME_pain316 102
+#define FRAME_pain317 103
+#define FRAME_pain318 104
+#define FRAME_pain319 105
+#define FRAME_pain320 106
+#define FRAME_pain321 107
+#define FRAME_pain322 108
+#define FRAME_pain323 109
+#define FRAME_pain324 110
+#define FRAME_pain325 111
+#define FRAME_stand01 112
+#define FRAME_stand02 113
+#define FRAME_stand03 114
+#define FRAME_stand04 115
+#define FRAME_stand05 116
+#define FRAME_stand06 117
+#define FRAME_stand07 118
+#define FRAME_stand08 119
+#define FRAME_stand09 120
+#define FRAME_stand10 121
+#define FRAME_stand11 122
+#define FRAME_stand12 123
+#define FRAME_stand13 124
+#define FRAME_stand14 125
+#define FRAME_stand15 126
+#define FRAME_stand16 127
+#define FRAME_stand17 128
+#define FRAME_stand18 129
+#define FRAME_stand19 130
+#define FRAME_stand20 131
+#define FRAME_stand21 132
+#define FRAME_stand22 133
+#define FRAME_stand23 134
+#define FRAME_stand24 135
+#define FRAME_stand25 136
+#define FRAME_stand26 137
+#define FRAME_stand27 138
+#define FRAME_stand28 139
+#define FRAME_stand29 140
+#define FRAME_stand30 141
+#define FRAME_stand31 142
+#define FRAME_stand32 143
+#define FRAME_stand33 144
+#define FRAME_stand34 145
+#define FRAME_stand35 146
+#define FRAME_stand36 147
+#define FRAME_stand37 148
+#define FRAME_stand38 149
+#define FRAME_stand39 150
+#define FRAME_stand40 151
+#define FRAME_stand41 152
+#define FRAME_stand42 153
+#define FRAME_stand43 154
+#define FRAME_stand44 155
+#define FRAME_stand45 156
+#define FRAME_stand46 157
+#define FRAME_stand47 158
+#define FRAME_stand48 159
+#define FRAME_stand49 160
+#define FRAME_stand50 161
+#define FRAME_stand51 162
+#define FRAME_walk01 163
+#define FRAME_walk02 164
+#define FRAME_walk03 165
+#define FRAME_walk04 166
+#define FRAME_walk05 167
+#define FRAME_walk06 168
+#define FRAME_walk07 169
+#define FRAME_walk08 170
+#define FRAME_walk09 171
+#define FRAME_walk10 172
+#define FRAME_walk11 173
+#define FRAME_walk12 174
+#define FRAME_walk13 175
+#define FRAME_walk14 176
+#define FRAME_walk15 177
+#define FRAME_walk16 178
+#define FRAME_walk17 179
+#define FRAME_walk18 180
+#define FRAME_walk19 181
+#define FRAME_walk20 182
+#define FRAME_walk21 183
+#define FRAME_walk22 184
+#define FRAME_walk23 185
+#define FRAME_walk24 186
+#define FRAME_walk25 187
+#define FRAME_active01 188
+#define FRAME_active02 189
+#define FRAME_active03 190
+#define FRAME_active04 191
+#define FRAME_active05 192
+#define FRAME_active06 193
+#define FRAME_active07 194
+#define FRAME_active08 195
+#define FRAME_active09 196
+#define FRAME_active10 197
+#define FRAME_active11 198
+#define FRAME_active12 199
+#define FRAME_active13 200
+#define FRAME_attak301 201
+#define FRAME_attak302 202
+#define FRAME_attak303 203
+#define FRAME_attak304 204
+#define FRAME_attak305 205
+#define FRAME_attak306 206
+#define FRAME_attak307 207
+#define FRAME_attak308 208
+#define FRAME_attak401 209
+#define FRAME_attak402 210
+#define FRAME_attak403 211
+#define FRAME_attak404 212
+#define FRAME_attak405 213
+#define FRAME_attak406 214
+#define FRAME_attak407 215
+#define FRAME_attak408 216
+#define FRAME_attak409 217
+#define FRAME_attak410 218
+#define FRAME_attak411 219
+#define FRAME_attak412 220
+#define FRAME_attak413 221
+#define FRAME_attak414 222
+#define FRAME_attak415 223
+#define FRAME_attak416 224
+#define FRAME_attak417 225
+#define FRAME_attak418 226
+#define FRAME_attak419 227
+#define FRAME_attak420 228
+#define FRAME_attak421 229
+#define FRAME_attak422 230
+#define FRAME_attak423 231
+#define FRAME_attak424 232
+#define FRAME_attak425 233
+#define FRAME_attak426 234
+#define FRAME_attak501 235
+#define FRAME_attak502 236
+#define FRAME_attak503 237
+#define FRAME_attak504 238
+#define FRAME_attak505 239
+#define FRAME_attak506 240
+#define FRAME_attak507 241
+#define FRAME_attak508 242
+#define FRAME_attak509 243
+#define FRAME_attak510 244
+#define FRAME_attak511 245
+#define FRAME_attak512 246
+#define FRAME_attak513 247
+#define FRAME_attak514 248
+#define FRAME_attak515 249
+#define FRAME_attak516 250
+#define FRAME_death201 251
+#define FRAME_death202 252
+#define FRAME_death203 253
+#define FRAME_death204 254
+#define FRAME_death205 255
+#define FRAME_death206 256
+#define FRAME_death207 257
+#define FRAME_death208 258
+#define FRAME_death209 259
+#define FRAME_death210 260
+#define FRAME_death211 261
+#define FRAME_death212 262
+#define FRAME_death213 263
+#define FRAME_death214 264
+#define FRAME_death215 265
+#define FRAME_death216 266
+#define FRAME_death217 267
+#define FRAME_death218 268
+#define FRAME_death219 269
+#define FRAME_death220 270
+#define FRAME_death221 271
+#define FRAME_death222 272
+#define FRAME_death223 273
+#define FRAME_death224 274
+#define FRAME_death225 275
+#define FRAME_death226 276
+#define FRAME_death227 277
+#define FRAME_death228 278
+#define FRAME_death229 279
+#define FRAME_death230 280
+#define FRAME_death231 281
+#define FRAME_death232 282
+#define FRAME_death233 283
+#define FRAME_death234 284
+#define FRAME_death235 285
+#define FRAME_death236 286
+#define FRAME_death237 287
+#define FRAME_death238 288
+#define FRAME_death239 289
+#define FRAME_death240 290
+#define FRAME_death241 291
+#define FRAME_death242 292
+#define FRAME_death243 293
+#define FRAME_death244 294
+#define FRAME_death245 295
+#define FRAME_death246 296
+#define FRAME_death247 297
+#define FRAME_death248 298
+#define FRAME_death249 299
+#define FRAME_death250 300
+#define FRAME_death251 301
+#define FRAME_death252 302
+#define FRAME_death253 303
+#define FRAME_death254 304
+#define FRAME_death255 305
+#define FRAME_death256 306
+#define FRAME_death257 307
+#define FRAME_death258 308
+#define FRAME_death259 309
+#define FRAME_death260 310
+#define FRAME_death261 311
+#define FRAME_death262 312
+#define FRAME_death263 313
+#define FRAME_death264 314
+#define FRAME_death265 315
+#define FRAME_death266 316
+#define FRAME_death267 317
+#define FRAME_death268 318
+#define FRAME_death269 319
+#define FRAME_death270 320
+#define FRAME_death271 321
+#define FRAME_death272 322
+#define FRAME_death273 323
+#define FRAME_death274 324
+#define FRAME_death275 325
+#define FRAME_death276 326
+#define FRAME_death277 327
+#define FRAME_death278 328
+#define FRAME_death279 329
+#define FRAME_death280 330
+#define FRAME_death281 331
+#define FRAME_death282 332
+#define FRAME_death283 333
+#define FRAME_death284 334
+#define FRAME_death285 335
+#define FRAME_death286 336
+#define FRAME_death287 337
+#define FRAME_death288 338
+#define FRAME_death289 339
+#define FRAME_death290 340
+#define FRAME_death291 341
+#define FRAME_death292 342
+#define FRAME_death293 343
+#define FRAME_death294 344
+#define FRAME_death295 345
+#define FRAME_death301 346
+#define FRAME_death302 347
+#define FRAME_death303 348
+#define FRAME_death304 349
+#define FRAME_death305 350
+#define FRAME_death306 351
+#define FRAME_death307 352
+#define FRAME_death308 353
+#define FRAME_death309 354
+#define FRAME_death310 355
+#define FRAME_death311 356
+#define FRAME_death312 357
+#define FRAME_death313 358
+#define FRAME_death314 359
+#define FRAME_death315 360
+#define FRAME_death316 361
+#define FRAME_death317 362
+#define FRAME_death318 363
+#define FRAME_death319 364
+#define FRAME_death320 365
+#define FRAME_jump01 366
+#define FRAME_jump02 367
+#define FRAME_jump03 368
+#define FRAME_jump04 369
+#define FRAME_jump05 370
+#define FRAME_jump06 371
+#define FRAME_jump07 372
+#define FRAME_jump08 373
+#define FRAME_jump09 374
+#define FRAME_jump10 375
+#define FRAME_jump11 376
+#define FRAME_jump12 377
+#define FRAME_jump13 378
+#define FRAME_pain401 379
+#define FRAME_pain402 380
+#define FRAME_pain403 381
+#define FRAME_pain404 382
+#define FRAME_pain501 383
+#define FRAME_pain502 384
+#define FRAME_pain503 385
+#define FRAME_pain504 386
+#define FRAME_pain601 387
+#define FRAME_pain602 388
+#define FRAME_pain603 389
+#define FRAME_pain604 390
+#define FRAME_pain605 391
+#define FRAME_pain606 392
+#define FRAME_pain607 393
+#define FRAME_pain608 394
+#define FRAME_pain609 395
+#define FRAME_pain610 396
+#define FRAME_pain611 397
+#define FRAME_pain612 398
+#define FRAME_pain613 399
+#define FRAME_pain614 400
+#define FRAME_pain615 401
+#define FRAME_pain616 402
+#define FRAME_pain617 403
+#define FRAME_pain618 404
+#define FRAME_pain619 405
+#define FRAME_pain620 406
+#define FRAME_pain621 407
+#define FRAME_pain622 408
+#define FRAME_pain623 409
+#define FRAME_pain624 410
+#define FRAME_pain625 411
+#define FRAME_pain626 412
+#define FRAME_pain627 413
+#define FRAME_stand201 414
+#define FRAME_stand202 415
+#define FRAME_stand203 416
+#define FRAME_stand204 417
+#define FRAME_stand205 418
+#define FRAME_stand206 419
+#define FRAME_stand207 420
+#define FRAME_stand208 421
+#define FRAME_stand209 422
+#define FRAME_stand210 423
+#define FRAME_stand211 424
+#define FRAME_stand212 425
+#define FRAME_stand213 426
+#define FRAME_stand214 427
+#define FRAME_stand215 428
+#define FRAME_stand216 429
+#define FRAME_stand217 430
+#define FRAME_stand218 431
+#define FRAME_stand219 432
+#define FRAME_stand220 433
+#define FRAME_stand221 434
+#define FRAME_stand222 435
+#define FRAME_stand223 436
+#define FRAME_stand224 437
+#define FRAME_stand225 438
+#define FRAME_stand226 439
+#define FRAME_stand227 440
+#define FRAME_stand228 441
+#define FRAME_stand229 442
+#define FRAME_stand230 443
+#define FRAME_stand231 444
+#define FRAME_stand232 445
+#define FRAME_stand233 446
+#define FRAME_stand234 447
+#define FRAME_stand235 448
+#define FRAME_stand236 449
+#define FRAME_stand237 450
+#define FRAME_stand238 451
+#define FRAME_stand239 452
+#define FRAME_stand240 453
+#define FRAME_stand241 454
+#define FRAME_stand242 455
+#define FRAME_stand243 456
+#define FRAME_stand244 457
+#define FRAME_stand245 458
+#define FRAME_stand246 459
+#define FRAME_stand247 460
+#define FRAME_stand248 461
+#define FRAME_stand249 462
+#define FRAME_stand250 463
+#define FRAME_stand251 464
+#define FRAME_stand252 465
+#define FRAME_stand253 466
+#define FRAME_stand254 467
+#define FRAME_stand255 468
+#define FRAME_stand256 469
+#define FRAME_stand257 470
+#define FRAME_stand258 471
+#define FRAME_stand259 472
+#define FRAME_stand260 473
+#define FRAME_walk201 474
+#define FRAME_walk202 475
+#define FRAME_walk203 476
+#define FRAME_walk204 477
+#define FRAME_walk205 478
+#define FRAME_walk206 479
+#define FRAME_walk207 480
+#define FRAME_walk208 481
+#define FRAME_walk209 482
+#define FRAME_walk210 483
+#define FRAME_walk211 484
+#define FRAME_walk212 485
+#define FRAME_walk213 486
+#define FRAME_walk214 487
+#define FRAME_walk215 488
+#define FRAME_walk216 489
+#define FRAME_walk217 490
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/boss5/boss5.c b/xatrix/src/monster/boss5/boss5.c
new file mode 100644
index 0000000..befc7b9
--- /dev/null
+++ b/xatrix/src/monster/boss5/boss5.c
@@ -0,0 +1,886 @@
+/* =======================================================================
+ *
+ * boss 5, only found in xatrix
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "../supertank/supertank.h"
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_death;
+static int sound_search1;
+static int sound_search2;
+static int tread_sound;
+
+qboolean visible(edict_t *self, edict_t *other);
+void BossExplode2(edict_t *self);
+void boss5_dead(edict_t *self);
+void boss5Rocket(edict_t *self);
+void boss5MachineGun(edict_t *self);
+void boss5_reattack1(edict_t *self);
+
+void
+TreadSound2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0);
+}
+
+void
+boss5_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+ }
+}
+
+/* stand */
+mframe_t boss5_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t boss5_move_stand = {
+ FRAME_stand_1,
+ FRAME_stand_60,
+ boss5_frames_stand,
+ NULL
+};
+
+void
+boss5_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss5_move_stand;
+}
+
+mframe_t boss5_frames_run[] = {
+ {ai_run, 12, TreadSound2},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL}
+};
+
+mmove_t boss5_move_run = {
+ FRAME_forwrd_1,
+ FRAME_forwrd_18,
+ boss5_frames_run,
+ NULL
+};
+
+/* walk */
+mframe_t boss5_frames_forward[] = {
+ {ai_walk, 4, TreadSound2},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t boss5_move_forward = {
+ FRAME_forwrd_1,
+ FRAME_forwrd_18,
+ boss5_frames_forward,
+ NULL
+};
+
+void
+boss5_forward(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss5_move_forward;
+}
+
+void
+boss5_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &boss5_move_forward;
+}
+
+void
+boss5_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &boss5_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss5_move_run;
+ }
+}
+
+mframe_t boss5_frames_turn_right[] = {
+ {ai_move, 0, TreadSound2},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_turn_right = {
+ FRAME_right_1,
+ FRAME_right_18,
+ boss5_frames_turn_right,
+ boss5_run
+};
+
+mframe_t boss5_frames_turn_left[] = {
+ {ai_move, 0, TreadSound2},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_turn_left = {
+ FRAME_left_1,
+ FRAME_left_18,
+ boss5_frames_turn_left,
+ boss5_run
+};
+
+mframe_t boss5_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_pain3 = {
+ FRAME_pain3_9,
+ FRAME_pain3_12,
+ boss5_frames_pain3,
+ boss5_run
+};
+
+mframe_t boss5_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_pain2 = {
+ FRAME_pain2_5,
+ FRAME_pain2_8,
+ boss5_frames_pain2,
+ boss5_run
+};
+
+mframe_t boss5_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_pain1 = {
+ FRAME_pain1_1,
+ FRAME_pain1_4,
+ boss5_frames_pain1,
+ boss5_run
+};
+
+mframe_t boss5_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, BossExplode2}
+};
+
+mmove_t boss5_move_death = {
+ FRAME_death_1,
+ FRAME_death_24,
+ boss5_frames_death1,
+ boss5_dead
+};
+
+mframe_t boss5_frames_backward[] = {
+ {ai_walk, 0, TreadSound2},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t boss5_move_backward = {
+ FRAME_backwd_1,
+ FRAME_backwd_18,
+ boss5_frames_backward,
+ NULL
+};
+
+mframe_t boss5_frames_attack4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_attack4 = {
+ FRAME_attak4_1,
+ FRAME_attak4_6,
+ boss5_frames_attack4,
+ boss5_run
+};
+
+mframe_t boss5_frames_attack3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_attack3 = {
+ FRAME_attak3_1,
+ FRAME_attak3_27,
+ boss5_frames_attack3,
+ boss5_run
+};
+
+mframe_t boss5_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, boss5Rocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, boss5Rocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, boss5Rocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_attack2 = {
+ FRAME_attak2_1,
+ FRAME_attak2_27,
+ boss5_frames_attack2,
+ boss5_run
+};
+
+mframe_t boss5_frames_attack1[] = {
+ {ai_charge, 0, boss5MachineGun},
+ {ai_charge, 0, boss5MachineGun},
+ {ai_charge, 0, boss5MachineGun},
+ {ai_charge, 0, boss5MachineGun},
+ {ai_charge, 0, boss5MachineGun},
+ {ai_charge, 0, boss5MachineGun},
+};
+
+mmove_t boss5_move_attack1 = {
+ FRAME_attak1_1,
+ FRAME_attak1_6,
+ boss5_frames_attack1,
+ boss5_reattack1
+};
+
+mframe_t boss5_frames_end_attack1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t boss5_move_end_attack1 = {
+ FRAME_attak1_7,
+ FRAME_attak1_20,
+ boss5_frames_end_attack1,
+ boss5_run
+};
+
+void
+boss5_reattack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ if (random() < 0.9)
+ {
+ self->monsterinfo.currentmove = &boss5_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss5_move_end_attack1;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss5_move_end_attack1;
+ }
+}
+
+void
+boss5_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ /* Lessen the chance of him going into his pain frames */
+ if (damage <= 25)
+ {
+ if (random() < 0.2)
+ {
+ return;
+ }
+ }
+
+ /* Don't go into pain if he's firing his rockets */
+ if (skill->value >= SKILL_HARD)
+ {
+ if ((self->s.frame >= FRAME_attak2_1) &&
+ (self->s.frame <= FRAME_attak2_14))
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (damage <= 10)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &boss5_move_pain1;
+ }
+ else if (damage <= 25)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &boss5_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &boss5_move_pain3;
+ }
+}
+
+void
+boss5Rocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak2_8)
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_1;
+ }
+ else if (self->s.frame == FRAME_attak2_11)
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_2;
+ }
+ else
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_3;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_rocket(self, start, dir, 50, 500, flash_number);
+}
+
+void
+boss5MachineGun(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t vec;
+ vec3_t start;
+ vec3_t forward, right;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1);
+
+ dir[0] = 0;
+ dir[1] = self->s.angles[1];
+ dir[2] = 0;
+
+ AngleVectors(dir, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ VectorCopy(self->enemy->s.origin, vec);
+ VectorMA(vec, 0, self->enemy->velocity, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, forward);
+ VectorNormalize(forward);
+ }
+
+ monster_fire_bullet(self, start, forward, 6, 4,
+ DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD,
+ flash_number);
+}
+
+void
+boss5_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ if (range <= 160)
+ {
+ self->monsterinfo.currentmove = &boss5_move_attack1;
+ }
+ else
+ {
+ /* fire rockets more often at distance */
+ if (random() < 0.3)
+ {
+ self->monsterinfo.currentmove = &boss5_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &boss5_move_attack2;
+ }
+ }
+}
+
+/* death */
+
+void
+boss5_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -60, -60, 0);
+ VectorSet(self->maxs, 60, 60, 72);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+BossExplode2(edict_t *self)
+{
+ vec3_t org;
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = BossExplode2;
+ VectorCopy(self->s.origin, org);
+ org[2] += 24 + (rand() & 15);
+
+ switch (self->count++)
+ {
+ case 0:
+ org[0] -= 24;
+ org[1] -= 24;
+ break;
+ case 1:
+ org[0] += 24;
+ org[1] += 24;
+ break;
+ case 2:
+ org[0] += 24;
+ org[1] -= 24;
+ break;
+ case 3:
+ org[0] -= 24;
+ org[1] += 24;
+ break;
+ case 4:
+ org[0] -= 48;
+ org[1] -= 48;
+ break;
+ case 5:
+ org[0] += 48;
+ org[1] += 48;
+ break;
+ case 6:
+ org[0] -= 48;
+ org[1] += 48;
+ break;
+ case 7:
+ org[0] += 48;
+ org[1] -= 48;
+ break;
+ case 8:
+ self->s.sound = 0;
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ 500, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 8; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2",
+ 500, GIB_METALLIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2",
+ 500, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/gear/tris.md2",
+ 500, GIB_METALLIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(org);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ self->nextthink = level.time + 0.1;
+}
+
+void
+boss5_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage /* unused */,
+ vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ self->monsterinfo.currentmove = &boss5_move_death;
+}
+
+/*
+ * QUAKED monster_boss5 (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_boss5(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("bosstank/btkpain1.wav");
+ sound_pain2 = gi.soundindex("bosstank/btkpain2.wav");
+ sound_pain3 = gi.soundindex("bosstank/btkpain3.wav");
+ sound_death = gi.soundindex("bosstank/btkdeth1.wav");
+ sound_search1 = gi.soundindex("bosstank/btkunqv1.wav");
+ sound_search2 = gi.soundindex("bosstank/btkunqv2.wav");
+
+ tread_sound = gi.soundindex("bosstank/btkengn1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss5/tris.md2");
+ VectorSet(self->mins, -64, -64, 0);
+ VectorSet(self->maxs, 64, 64, 112);
+
+ self->health = 1500;
+ self->gib_health = -500;
+ self->mass = 800;
+
+ self->pain = boss5_pain;
+ self->die = boss5_die;
+ self->monsterinfo.stand = boss5_stand;
+ self->monsterinfo.walk = boss5_walk;
+ self->monsterinfo.run = boss5_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = boss5_attack;
+ self->monsterinfo.search = boss5_search;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = NULL;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &boss5_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+ self->monsterinfo.power_armor_power = 400;
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/brain/brain.c b/xatrix/src/monster/brain/brain.c
new file mode 100644
index 0000000..3c40a73
--- /dev/null
+++ b/xatrix/src/monster/brain/brain.c
@@ -0,0 +1,1117 @@
+/* =======================================================================
+ *
+ * Brain.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "brain.h"
+
+static int sound_chest_open;
+static int sound_tentacles_extend;
+static int sound_tentacles_retract;
+static int sound_death;
+static int sound_idle1;
+static int sound_idle2;
+static int sound_idle3;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_sight;
+static int sound_search;
+static int sound_melee1;
+static int sound_melee2;
+static int sound_melee3;
+void brain_run(edict_t *self);
+void brain_dead(edict_t *self);
+
+void
+brain_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+brain_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+/* STAND */
+
+mframe_t brain_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t brain_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ brain_frames_stand,
+ NULL
+};
+
+void
+brain_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &brain_move_stand;
+}
+
+/* IDLE */
+
+mframe_t brain_frames_idle[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t brain_move_idle = {
+ FRAME_stand31,
+ FRAME_stand60,
+ brain_frames_idle,
+ brain_stand
+};
+
+void
+brain_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0);
+ self->monsterinfo.currentmove = &brain_move_idle;
+}
+
+/* WALK */
+
+mframe_t brain_frames_walk1[] = {
+ {ai_walk, 7, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, -4, NULL},
+ {ai_walk, -1, NULL},
+ {ai_walk, 2, NULL}
+};
+
+mmove_t brain_move_walk1 = {
+ FRAME_walk101,
+ FRAME_walk111,
+ brain_frames_walk1,
+ NULL
+};
+
+void
+brain_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &brain_move_walk1;
+}
+
+mframe_t brain_frames_defense[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t brain_move_defense = {
+ FRAME_defens01,
+ FRAME_defens08,
+ brain_frames_defense,
+ NULL
+};
+
+mframe_t brain_frames_pain3[] = {
+ {ai_move, -2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -4, NULL}
+};
+
+mmove_t brain_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain306,
+ brain_frames_pain3,
+ brain_run
+};
+
+mframe_t brain_frames_pain2[] = {
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -2, NULL}
+};
+
+mmove_t brain_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain208,
+ brain_frames_pain2,
+ brain_run
+};
+
+mframe_t brain_frames_pain1[] = {
+ {ai_move, -6, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -1, NULL}
+};
+
+mmove_t brain_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain121,
+ brain_frames_pain1,
+ brain_run
+};
+
+/* DUCK */
+
+void
+brain_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ gi.linkentity(self);
+}
+
+void
+brain_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+void
+brain_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+mframe_t brain_frames_duck[] = {
+ {ai_move, 0, NULL},
+ {ai_move, -2, brain_duck_down},
+ {ai_move, 17, brain_duck_hold},
+ {ai_move, -3, NULL},
+ {ai_move, -1, brain_duck_up},
+ {ai_move, -5, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -6, NULL}
+};
+
+mmove_t brain_move_duck = {
+ FRAME_duck01,
+ FRAME_duck08,
+ brain_frames_duck,
+ brain_run
+};
+
+void
+brain_dodge(edict_t *self, edict_t *attacker, float eta)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if (random() > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ self->monsterinfo.pausetime = level.time + eta + 0.5;
+ self->monsterinfo.currentmove = &brain_move_duck;
+}
+
+mframe_t brain_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t brain_move_death2 = {
+ FRAME_death201,
+ FRAME_death205,
+ brain_frames_death2,
+ brain_dead
+};
+
+mframe_t brain_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t brain_move_death1 = {
+ FRAME_death101,
+ FRAME_death118,
+ brain_frames_death1,
+ brain_dead
+};
+
+/* MELEE */
+
+void
+brain_swing_right(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0);
+}
+
+void
+brain_hit_right(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8);
+
+ if (fire_hit(self, aim, (15 + (rand() % 5)), 40))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+brain_swing_left(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0);
+}
+
+void
+brain_hit_left(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8);
+
+ if (fire_hit(self, aim, (15 + (rand() % 5)), 40))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t brain_frames_attack1[] = {
+ {ai_charge, 8, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -3, brain_swing_right},
+ {ai_charge, 0, NULL},
+ {ai_charge, -5, NULL},
+ {ai_charge, -7, brain_hit_right},
+ {ai_charge, 0, NULL},
+ {ai_charge, 6, brain_swing_left},
+ {ai_charge, 1, NULL},
+ {ai_charge, 2, brain_hit_left},
+ {ai_charge, -3, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, -11, NULL}
+};
+
+mmove_t brain_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak118,
+ brain_frames_attack1,
+ brain_run
+};
+
+void
+brain_chest_open(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->spawnflags &= ~65536;
+ self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+ gi.sound(self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0);
+}
+
+void
+brain_tentacle_attack(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 8);
+
+ if (fire_hit(self, aim, (10 + (rand() % 5)), -600) && (skill->value > SKILL_EASY))
+ {
+ self->spawnflags |= 65536;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+}
+
+void
+brain_chest_closed(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+
+ if (self->spawnflags & 65536)
+ {
+ self->spawnflags &= ~65536;
+ self->monsterinfo.currentmove = &brain_move_attack1;
+ }
+}
+
+mframe_t brain_frames_attack2[] = {
+ {ai_charge, 5, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 0, brain_chest_open},
+ {ai_charge, 0, NULL},
+ {ai_charge, 13, brain_tentacle_attack},
+ {ai_charge, 0, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -9, brain_chest_closed},
+ {ai_charge, 0, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, -6, NULL}
+};
+
+mmove_t brain_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak217,
+ brain_frames_attack2,
+ brain_run
+};
+
+void
+brain_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &brain_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &brain_move_attack2;
+ }
+}
+
+qboolean
+brain_tounge_attack_ok(vec3_t start, vec3_t end)
+{
+ vec3_t dir, angles;
+
+ /* check for max distance */
+ VectorSubtract(start, end, dir);
+
+ if (VectorLength(dir) > 512)
+ {
+ return false;
+ }
+
+ /* check for min/max pitch */
+ vectoangles(dir, angles);
+
+ if (angles[0] < -180)
+ {
+ angles[0] += 360;
+ }
+
+ if (fabs(angles[0]) > 30)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+brain_tounge_attack(edict_t *self)
+{
+ vec3_t offset, start, f, r, end, dir;
+ trace_t tr;
+ int damage;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorSet(offset, 24, 0, 16);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (!brain_tounge_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+
+ if (!brain_tounge_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+
+ if (!brain_tounge_attack_ok(start, end))
+ {
+ return;
+ }
+ }
+ }
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
+
+ if (tr.ent != self->enemy)
+ {
+ return;
+ }
+
+ damage = 5;
+ gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_PARASITE_ATTACK);
+ gi.WriteShort(self - g_edicts);
+ gi.WritePosition(start);
+ gi.WritePosition(end);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ VectorSubtract(start, end, dir);
+ T_Damage(self->enemy, self, self, dir, self->enemy->s.origin,
+ vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_BRAINTENTACLE);
+
+ /* pull the enemy in */
+ vec3_t forward;
+ self->s.origin[2] += 1;
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ VectorScale(forward, -1200, self->enemy->velocity);
+}
+
+/* Brian right eye center */
+struct r_eyeball
+{
+ float x;
+ float y;
+ float z;
+} brain_reye[11] = {
+ {0.746700, 0.238370, 34.167690},
+ {-1.076390, 0.238370, 33.386372},
+ {-1.335500, 5.334300, 32.177170},
+ {-0.175360, 8.846370, 30.635479},
+ {-2.757590, 7.804610, 30.150860},
+ {-5.575090, 5.152840, 30.056160},
+ {-7.017550, 3.262470, 30.552521},
+ {-7.915740, 0.638800, 33.176189},
+ {-3.915390, 8.285730, 33.976349},
+ {-0.913540, 10.933030, 34.141811},
+ {-0.369900, 8.923900, 34.189079}
+};
+
+/* Brain left eye center */
+struct l_eyeball
+{
+ float x;
+ float y;
+ float z;
+} brain_leye[11] = {
+ {-3.364710, 0.327750, 33.938381},
+ {-5.140450, 0.493480, 32.659851},
+ {-5.341980, 5.646980, 31.277901},
+ {-4.134480, 9.277440, 29.925621},
+ {-6.598340, 6.815090, 29.322620},
+ {-8.610840, 2.529650, 29.251591},
+ {-9.231360, 0.093280, 29.747959},
+ {-11.004110, 1.936930, 32.395260},
+ {-7.878310, 7.648190, 33.148151},
+ {-4.947370, 11.430050, 33.313610},
+ {-4.332820, 9.444570, 33.526340}
+};
+
+void
+brain_laserbeam(edict_t *self)
+{
+ vec3_t forward, right, up;
+ vec3_t tempang, start;
+ vec3_t dir, angles, end;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.8)
+ {
+ gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"),
+ 1, ATTN_STATIC, 0);
+ }
+
+ /* check for max distance */
+ VectorCopy(self->s.origin, start);
+ VectorCopy(self->enemy->s.origin, end);
+ VectorSubtract(end, start, dir);
+ vectoangles(dir, angles);
+
+ /* dis is my right eye */
+ ent = G_Spawn();
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(angles, tempang);
+ AngleVectors(tempang, forward, right, up);
+ VectorCopy(tempang, ent->s.angles);
+ VectorCopy(ent->s.origin, start);
+ VectorMA(start, brain_reye[self->s.frame - FRAME_walk101].x, right, start);
+ VectorMA(start, brain_reye[self->s.frame - FRAME_walk101].y, forward, start);
+ VectorMA(start, brain_reye[self->s.frame - FRAME_walk101].z, up, start);
+ VectorCopy(start, ent->s.origin);
+ ent->enemy = self->enemy;
+ ent->owner = self;
+ ent->dmg = 1;
+ monster_dabeam(ent);
+
+ /* dis is me left eye */
+ ent = G_Spawn();
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(angles, tempang);
+ AngleVectors(tempang, forward, right, up);
+ VectorCopy(tempang, ent->s.angles);
+ VectorCopy(ent->s.origin, start);
+ VectorMA(start, brain_leye[self->s.frame - FRAME_walk101].x, right, start);
+ VectorMA(start, brain_leye[self->s.frame - FRAME_walk101].y, forward, start);
+ VectorMA(start, brain_leye[self->s.frame - FRAME_walk101].z, up, start);
+ VectorCopy(start, ent->s.origin);
+ ent->enemy = self->enemy;
+ ent->owner = self;
+ ent->dmg = 1;
+ monster_dabeam(ent);
+}
+
+void
+brain_laserbeam_reattack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (self->enemy->health > 0)
+ {
+ self->s.frame = FRAME_walk101;
+ }
+ }
+ }
+}
+
+mframe_t brain_frames_attack3[] = {
+ {ai_charge, 5, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 0, brain_chest_open},
+ {ai_charge, 0, brain_tounge_attack},
+ {ai_charge, 13, NULL},
+ {ai_charge, 0, brain_tentacle_attack},
+ {ai_charge, 2, NULL},
+ {ai_charge, 0, brain_tounge_attack},
+ {ai_charge, -9, brain_chest_closed},
+ {ai_charge, 0, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, -6, NULL}
+};
+
+mmove_t brain_move_attack3 = {
+ FRAME_attak201,
+ FRAME_attak217,
+ brain_frames_attack3,
+ brain_run
+};
+
+mframe_t brain_frames_attack4[] = {
+ {ai_charge, 9, brain_laserbeam},
+ {ai_charge, 2, brain_laserbeam},
+ {ai_charge, 3, brain_laserbeam},
+ {ai_charge, 3, brain_laserbeam},
+ {ai_charge, 1, brain_laserbeam},
+ {ai_charge, 0, brain_laserbeam},
+ {ai_charge, 0, brain_laserbeam},
+ {ai_charge, 10, brain_laserbeam},
+ {ai_charge, -4, brain_laserbeam},
+ {ai_charge, -1, brain_laserbeam},
+ {ai_charge, 2, brain_laserbeam_reattack}
+};
+
+mmove_t brain_move_attack4 = {
+ FRAME_walk101,
+ FRAME_walk111,
+ brain_frames_attack4,
+ brain_run
+};
+
+void
+brain_attack(edict_t *self)
+{
+ int r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.8)
+ {
+ r = range(self, self->enemy);
+
+ if (r == RANGE_NEAR)
+ {
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &brain_move_attack3;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &brain_move_attack4;
+ }
+ }
+ else if (r > RANGE_NEAR)
+ {
+ self->monsterinfo.currentmove = &brain_move_attack4;
+ }
+ }
+}
+
+/* RUN */
+
+mframe_t brain_frames_run[] = {
+ {ai_run, 9, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 1, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, -4, NULL},
+ {ai_run, -1, NULL},
+ {ai_run, 2, NULL}
+};
+
+mmove_t brain_move_run = {
+ FRAME_walk101,
+ FRAME_walk111,
+ brain_frames_run,
+ NULL
+};
+
+void
+brain_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &brain_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &brain_move_run;
+ }
+}
+
+void
+brain_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &brain_move_pain1;
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &brain_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &brain_move_pain3;
+ }
+}
+
+void
+brain_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+brain_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.effects = 0;
+ self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"),
+ 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2",
+ damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &brain_move_death1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &brain_move_death2;
+ }
+}
+
+/*
+ * QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_brain(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_chest_open = gi.soundindex("brain/brnatck1.wav");
+ sound_tentacles_extend = gi.soundindex("brain/brnatck2.wav");
+ sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav");
+ sound_death = gi.soundindex("brain/brndeth1.wav");
+ sound_idle1 = gi.soundindex("brain/brnidle1.wav");
+ sound_idle2 = gi.soundindex("brain/brnidle2.wav");
+ sound_idle3 = gi.soundindex("brain/brnlens1.wav");
+ sound_pain1 = gi.soundindex("brain/brnpain1.wav");
+ sound_pain2 = gi.soundindex("brain/brnpain2.wav");
+ sound_sight = gi.soundindex("brain/brnsght1.wav");
+ sound_search = gi.soundindex("brain/brnsrch1.wav");
+ sound_melee1 = gi.soundindex("brain/melee1.wav");
+ sound_melee2 = gi.soundindex("brain/melee2.wav");
+ sound_melee3 = gi.soundindex("brain/melee3.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/brain/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 300;
+ self->gib_health = -150;
+ self->mass = 400;
+
+ self->pain = brain_pain;
+ self->die = brain_die;
+
+ self->monsterinfo.stand = brain_stand;
+ self->monsterinfo.walk = brain_walk;
+ self->monsterinfo.run = brain_run;
+ self->monsterinfo.attack = brain_attack;
+ self->monsterinfo.dodge = brain_dodge;
+ self->monsterinfo.melee = brain_melee;
+ self->monsterinfo.sight = brain_sight;
+ self->monsterinfo.search = brain_search;
+ self->monsterinfo.idle = brain_idle;
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+ self->monsterinfo.power_armor_power = 100;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &brain_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/brain/brain.h b/xatrix/src/monster/brain/brain.h
new file mode 100644
index 0000000..bd5a740
--- /dev/null
+++ b/xatrix/src/monster/brain/brain.h
@@ -0,0 +1,232 @@
+/* =======================================================================
+ *
+ * Brain animations.
+ *
+ * =======================================================================
+ *
+ */
+
+#define FRAME_walk101 0
+#define FRAME_walk102 1
+#define FRAME_walk103 2
+#define FRAME_walk104 3
+#define FRAME_walk105 4
+#define FRAME_walk106 5
+#define FRAME_walk107 6
+#define FRAME_walk108 7
+#define FRAME_walk109 8
+#define FRAME_walk110 9
+#define FRAME_walk111 10
+#define FRAME_walk112 11
+#define FRAME_walk113 12
+#define FRAME_walk201 13
+#define FRAME_walk202 14
+#define FRAME_walk203 15
+#define FRAME_walk204 16
+#define FRAME_walk205 17
+#define FRAME_walk206 18
+#define FRAME_walk207 19
+#define FRAME_walk208 20
+#define FRAME_walk209 21
+#define FRAME_walk210 22
+#define FRAME_walk211 23
+#define FRAME_walk212 24
+#define FRAME_walk213 25
+#define FRAME_walk214 26
+#define FRAME_walk215 27
+#define FRAME_walk216 28
+#define FRAME_walk217 29
+#define FRAME_walk218 30
+#define FRAME_walk219 31
+#define FRAME_walk220 32
+#define FRAME_walk221 33
+#define FRAME_walk222 34
+#define FRAME_walk223 35
+#define FRAME_walk224 36
+#define FRAME_walk225 37
+#define FRAME_walk226 38
+#define FRAME_walk227 39
+#define FRAME_walk228 40
+#define FRAME_walk229 41
+#define FRAME_walk230 42
+#define FRAME_walk231 43
+#define FRAME_walk232 44
+#define FRAME_walk233 45
+#define FRAME_walk234 46
+#define FRAME_walk235 47
+#define FRAME_walk236 48
+#define FRAME_walk237 49
+#define FRAME_walk238 50
+#define FRAME_walk239 51
+#define FRAME_walk240 52
+#define FRAME_attak101 53
+#define FRAME_attak102 54
+#define FRAME_attak103 55
+#define FRAME_attak104 56
+#define FRAME_attak105 57
+#define FRAME_attak106 58
+#define FRAME_attak107 59
+#define FRAME_attak108 60
+#define FRAME_attak109 61
+#define FRAME_attak110 62
+#define FRAME_attak111 63
+#define FRAME_attak112 64
+#define FRAME_attak113 65
+#define FRAME_attak114 66
+#define FRAME_attak115 67
+#define FRAME_attak116 68
+#define FRAME_attak117 69
+#define FRAME_attak118 70
+#define FRAME_attak201 71
+#define FRAME_attak202 72
+#define FRAME_attak203 73
+#define FRAME_attak204 74
+#define FRAME_attak205 75
+#define FRAME_attak206 76
+#define FRAME_attak207 77
+#define FRAME_attak208 78
+#define FRAME_attak209 79
+#define FRAME_attak210 80
+#define FRAME_attak211 81
+#define FRAME_attak212 82
+#define FRAME_attak213 83
+#define FRAME_attak214 84
+#define FRAME_attak215 85
+#define FRAME_attak216 86
+#define FRAME_attak217 87
+#define FRAME_pain101 88
+#define FRAME_pain102 89
+#define FRAME_pain103 90
+#define FRAME_pain104 91
+#define FRAME_pain105 92
+#define FRAME_pain106 93
+#define FRAME_pain107 94
+#define FRAME_pain108 95
+#define FRAME_pain109 96
+#define FRAME_pain110 97
+#define FRAME_pain111 98
+#define FRAME_pain112 99
+#define FRAME_pain113 100
+#define FRAME_pain114 101
+#define FRAME_pain115 102
+#define FRAME_pain116 103
+#define FRAME_pain117 104
+#define FRAME_pain118 105
+#define FRAME_pain119 106
+#define FRAME_pain120 107
+#define FRAME_pain121 108
+#define FRAME_pain201 109
+#define FRAME_pain202 110
+#define FRAME_pain203 111
+#define FRAME_pain204 112
+#define FRAME_pain205 113
+#define FRAME_pain206 114
+#define FRAME_pain207 115
+#define FRAME_pain208 116
+#define FRAME_pain301 117
+#define FRAME_pain302 118
+#define FRAME_pain303 119
+#define FRAME_pain304 120
+#define FRAME_pain305 121
+#define FRAME_pain306 122
+#define FRAME_death101 123
+#define FRAME_death102 124
+#define FRAME_death103 125
+#define FRAME_death104 126
+#define FRAME_death105 127
+#define FRAME_death106 128
+#define FRAME_death107 129
+#define FRAME_death108 130
+#define FRAME_death109 131
+#define FRAME_death110 132
+#define FRAME_death111 133
+#define FRAME_death112 134
+#define FRAME_death113 135
+#define FRAME_death114 136
+#define FRAME_death115 137
+#define FRAME_death116 138
+#define FRAME_death117 139
+#define FRAME_death118 140
+#define FRAME_death201 141
+#define FRAME_death202 142
+#define FRAME_death203 143
+#define FRAME_death204 144
+#define FRAME_death205 145
+#define FRAME_duck01 146
+#define FRAME_duck02 147
+#define FRAME_duck03 148
+#define FRAME_duck04 149
+#define FRAME_duck05 150
+#define FRAME_duck06 151
+#define FRAME_duck07 152
+#define FRAME_duck08 153
+#define FRAME_defens01 154
+#define FRAME_defens02 155
+#define FRAME_defens03 156
+#define FRAME_defens04 157
+#define FRAME_defens05 158
+#define FRAME_defens06 159
+#define FRAME_defens07 160
+#define FRAME_defens08 161
+#define FRAME_stand01 162
+#define FRAME_stand02 163
+#define FRAME_stand03 164
+#define FRAME_stand04 165
+#define FRAME_stand05 166
+#define FRAME_stand06 167
+#define FRAME_stand07 168
+#define FRAME_stand08 169
+#define FRAME_stand09 170
+#define FRAME_stand10 171
+#define FRAME_stand11 172
+#define FRAME_stand12 173
+#define FRAME_stand13 174
+#define FRAME_stand14 175
+#define FRAME_stand15 176
+#define FRAME_stand16 177
+#define FRAME_stand17 178
+#define FRAME_stand18 179
+#define FRAME_stand19 180
+#define FRAME_stand20 181
+#define FRAME_stand21 182
+#define FRAME_stand22 183
+#define FRAME_stand23 184
+#define FRAME_stand24 185
+#define FRAME_stand25 186
+#define FRAME_stand26 187
+#define FRAME_stand27 188
+#define FRAME_stand28 189
+#define FRAME_stand29 190
+#define FRAME_stand30 191
+#define FRAME_stand31 192
+#define FRAME_stand32 193
+#define FRAME_stand33 194
+#define FRAME_stand34 195
+#define FRAME_stand35 196
+#define FRAME_stand36 197
+#define FRAME_stand37 198
+#define FRAME_stand38 199
+#define FRAME_stand39 200
+#define FRAME_stand40 201
+#define FRAME_stand41 202
+#define FRAME_stand42 203
+#define FRAME_stand43 204
+#define FRAME_stand44 205
+#define FRAME_stand45 206
+#define FRAME_stand46 207
+#define FRAME_stand47 208
+#define FRAME_stand48 209
+#define FRAME_stand49 210
+#define FRAME_stand50 211
+#define FRAME_stand51 212
+#define FRAME_stand52 213
+#define FRAME_stand53 214
+#define FRAME_stand54 215
+#define FRAME_stand55 216
+#define FRAME_stand56 217
+#define FRAME_stand57 218
+#define FRAME_stand58 219
+#define FRAME_stand59 220
+#define FRAME_stand60 221
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/chick/chick.c b/xatrix/src/monster/chick/chick.c
new file mode 100644
index 0000000..c331a97
--- /dev/null
+++ b/xatrix/src/monster/chick/chick.c
@@ -0,0 +1,959 @@
+/* =======================================================================
+ *
+ * Iron Maiden.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "chick.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+
+void chick_stand(edict_t *self);
+void chick_run(edict_t *self);
+void chick_reslash(edict_t *self);
+void chick_rerocket(edict_t *self);
+void chick_attack1(edict_t *self);
+
+static int sound_missile_prelaunch;
+static int sound_missile_launch;
+static int sound_melee_swing;
+static int sound_melee_hit;
+static int sound_missile_reload;
+static int sound_death1;
+static int sound_death2;
+static int sound_fall_down;
+static int sound_idle1;
+static int sound_idle2;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_sight;
+static int sound_search;
+
+void
+ChickMoan(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0);
+ }
+}
+
+mframe_t chick_frames_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, ChickMoan},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t chick_move_fidget = {
+ FRAME_stand201,
+ FRAME_stand230,
+ chick_frames_fidget,
+ chick_stand
+};
+
+void
+chick_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ return;
+ }
+
+ if (random() <= 0.3)
+ {
+ self->monsterinfo.currentmove = &chick_move_fidget;
+ }
+}
+
+mframe_t chick_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, chick_fidget},
+};
+
+mmove_t chick_move_stand = {
+ FRAME_stand101,
+ FRAME_stand130,
+ chick_frames_stand,
+ NULL
+};
+
+void
+chick_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_stand;
+}
+
+mframe_t chick_frames_start_run[] = {
+ {ai_run, 1, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, -1, NULL},
+ {ai_run, -1, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 1, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 3, NULL}
+};
+
+mmove_t chick_move_start_run = {
+ FRAME_walk01,
+ FRAME_walk10,
+ chick_frames_start_run,
+ chick_run
+};
+
+mframe_t chick_frames_run[] = {
+ {ai_run, 6, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 7, NULL},
+ {ai_run, 4, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 7, NULL}
+};
+
+mmove_t chick_move_run = {
+ FRAME_walk11,
+ FRAME_walk20,
+ chick_frames_run,
+ NULL
+};
+
+mframe_t chick_frames_walk[] = {
+ {ai_walk, 6, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 13, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 11, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 7, NULL}
+};
+
+mmove_t chick_move_walk = {
+ FRAME_walk11,
+ FRAME_walk20,
+ chick_frames_walk,
+ NULL
+};
+
+void
+chick_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_walk;
+}
+
+void
+chick_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &chick_move_stand;
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &chick_move_walk) ||
+ (self->monsterinfo.currentmove == &chick_move_start_run))
+ {
+ self->monsterinfo.currentmove = &chick_move_run;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_start_run;
+ }
+}
+
+mframe_t chick_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t chick_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain105,
+ chick_frames_pain1,
+ chick_run
+};
+
+mframe_t chick_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t chick_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain205,
+ chick_frames_pain2,
+ chick_run
+};
+
+mframe_t chick_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t chick_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain321,
+ chick_frames_pain3,
+ chick_run
+};
+
+void
+chick_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 10)
+ {
+ self->monsterinfo.currentmove = &chick_move_pain1;
+ }
+ else if (damage <= 25)
+ {
+ self->monsterinfo.currentmove = &chick_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_pain3;
+ }
+}
+
+void
+chick_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 16);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t chick_frames_death2[] = {
+ {ai_move, -6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 15, NULL},
+ {ai_move, 14, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t chick_move_death2 = {
+ FRAME_death201,
+ FRAME_death223,
+ chick_frames_death2,
+ chick_dead
+};
+
+mframe_t chick_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t chick_move_death1 = {
+ FRAME_death101,
+ FRAME_death112,
+ chick_frames_death1,
+ chick_dead
+};
+
+void
+chick_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /*unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"),
+ 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2",
+ damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ n = rand() % 2;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &chick_move_death1;
+ gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_death2;
+ gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+chick_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.pausetime = level.time + 1;
+ gi.linkentity(self);
+}
+
+void
+chick_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+void
+chick_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+mframe_t chick_frames_duck[] = {
+ {ai_move, 0, chick_duck_down},
+ {ai_move, 1, NULL},
+ {ai_move, 4, chick_duck_hold},
+ {ai_move, -4, NULL},
+ {ai_move, -5, chick_duck_up},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t chick_move_duck = {
+ FRAME_duck01,
+ FRAME_duck07,
+ chick_frames_duck,
+ chick_run
+};
+
+void
+chick_dodge(edict_t *self, edict_t *attacker, float eta)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if (random() > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ self->monsterinfo.currentmove = &chick_move_duck;
+}
+
+void
+ChickSlash(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 10);
+ gi.sound(self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0);
+ fire_hit(self, aim, (10 + (rand() % 6)), 100);
+}
+
+void
+ChickRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ if (!strcmp(self->classname, "monster_chick_heat"))
+ {
+ monster_fire_heat(self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1);
+ }
+ else
+ {
+ monster_fire_rocket(self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1);
+ }
+}
+
+void
+Chick_PreAttack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0);
+}
+
+void
+ChickReload(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0);
+}
+
+mframe_t chick_frames_start_attack1[] = {
+ {ai_charge, 0, Chick_PreAttack1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 7, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, chick_attack1}
+};
+
+mmove_t chick_move_start_attack1 = {
+ FRAME_attak101,
+ FRAME_attak113,
+ chick_frames_start_attack1,
+ NULL
+};
+
+mframe_t chick_frames_attack1[] = {
+ {ai_charge, 19, ChickRocket},
+ {ai_charge, -6, NULL},
+ {ai_charge, -5, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -7, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 10, ChickReload},
+ {ai_charge, 4, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 3, chick_rerocket}
+};
+
+mmove_t chick_move_attack1 = {
+ FRAME_attak114,
+ FRAME_attak127,
+ chick_frames_attack1,
+ NULL
+};
+
+mframe_t chick_frames_end_attack1[] = {
+ {ai_charge, -3, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -6, NULL},
+ {ai_charge, -4, NULL},
+ {ai_charge, -2, NULL}
+};
+
+mmove_t chick_move_end_attack1 = {
+ FRAME_attak128,
+ FRAME_attak132,
+ chick_frames_end_attack1,
+ chick_run
+};
+
+void
+chick_rerocket(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (range(self, self->enemy) > RANGE_MELEE)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.6)
+ {
+ self->monsterinfo.currentmove = &chick_move_attack1;
+ return;
+ }
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &chick_move_end_attack1;
+}
+
+void
+chick_attack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_attack1;
+}
+
+mframe_t chick_frames_slash[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 7, ChickSlash},
+ {ai_charge, -7, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, -2, chick_reslash}
+};
+
+mmove_t chick_move_slash = {
+ FRAME_attak204,
+ FRAME_attak212,
+ chick_frames_slash,
+ NULL
+};
+
+mframe_t chick_frames_end_slash[] = {
+ {ai_charge, -6, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -6, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t chick_move_end_slash = {
+ FRAME_attak213,
+ FRAME_attak216,
+ chick_frames_end_slash,
+ chick_run
+};
+
+void
+chick_reslash(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ if (random() <= 0.9)
+ {
+ self->monsterinfo.currentmove = &chick_move_slash;
+ return;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &chick_move_end_slash;
+ return;
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &chick_move_end_slash;
+}
+
+void
+chick_slash(edict_t *self)
+{
+ self->monsterinfo.currentmove = &chick_move_slash;
+}
+
+mframe_t chick_frames_start_slash[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 8, NULL},
+ {ai_charge, 3, NULL}
+};
+
+mmove_t chick_move_start_slash = {
+ FRAME_attak201,
+ FRAME_attak203,
+ chick_frames_start_slash,
+ chick_slash
+};
+
+void
+chick_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_start_slash;
+}
+
+void
+chick_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &chick_move_start_attack1;
+}
+
+void
+chick_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+/*
+ * QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_chick(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_missile_prelaunch = gi.soundindex("chick/chkatck1.wav");
+ sound_missile_launch = gi.soundindex("chick/chkatck2.wav");
+ sound_melee_swing = gi.soundindex("chick/chkatck3.wav");
+ sound_melee_hit = gi.soundindex("chick/chkatck4.wav");
+ sound_missile_reload = gi.soundindex("chick/chkatck5.wav");
+ sound_death1 = gi.soundindex("chick/chkdeth1.wav");
+ sound_death2 = gi.soundindex("chick/chkdeth2.wav");
+ sound_fall_down = gi.soundindex("chick/chkfall1.wav");
+ sound_idle1 = gi.soundindex("chick/chkidle1.wav");
+ sound_idle2 = gi.soundindex("chick/chkidle2.wav");
+ sound_pain1 = gi.soundindex("chick/chkpain1.wav");
+ sound_pain2 = gi.soundindex("chick/chkpain2.wav");
+ sound_pain3 = gi.soundindex("chick/chkpain3.wav");
+ sound_sight = gi.soundindex("chick/chksght1.wav");
+ sound_search = gi.soundindex("chick/chksrch1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 56);
+
+ self->health = 175;
+ self->gib_health = -70;
+ self->mass = 200;
+
+ self->pain = chick_pain;
+ self->die = chick_die;
+
+ self->monsterinfo.stand = chick_stand;
+ self->monsterinfo.walk = chick_walk;
+ self->monsterinfo.run = chick_run;
+ self->monsterinfo.dodge = chick_dodge;
+ self->monsterinfo.attack = chick_attack;
+ self->monsterinfo.melee = chick_melee;
+ self->monsterinfo.sight = chick_sight;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &chick_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
+
+/*
+ * QUAKED monster_chick_heat (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_chick_heat(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ SP_monster_chick(self);
+
+ /* have to check this since the regular spawn function can free the entity */
+ if (self->inuse)
+ {
+ self->s.skinnum = 3;
+ }
+}
diff --git a/xatrix/src/monster/chick/chick.h b/xatrix/src/monster/chick/chick.h
new file mode 100644
index 0000000..14be3b2
--- /dev/null
+++ b/xatrix/src/monster/chick/chick.h
@@ -0,0 +1,297 @@
+/* =======================================================================
+ *
+ * Iron Maiden animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak113 12
+#define FRAME_attak114 13
+#define FRAME_attak115 14
+#define FRAME_attak116 15
+#define FRAME_attak117 16
+#define FRAME_attak118 17
+#define FRAME_attak119 18
+#define FRAME_attak120 19
+#define FRAME_attak121 20
+#define FRAME_attak122 21
+#define FRAME_attak123 22
+#define FRAME_attak124 23
+#define FRAME_attak125 24
+#define FRAME_attak126 25
+#define FRAME_attak127 26
+#define FRAME_attak128 27
+#define FRAME_attak129 28
+#define FRAME_attak130 29
+#define FRAME_attak131 30
+#define FRAME_attak132 31
+#define FRAME_attak201 32
+#define FRAME_attak202 33
+#define FRAME_attak203 34
+#define FRAME_attak204 35
+#define FRAME_attak205 36
+#define FRAME_attak206 37
+#define FRAME_attak207 38
+#define FRAME_attak208 39
+#define FRAME_attak209 40
+#define FRAME_attak210 41
+#define FRAME_attak211 42
+#define FRAME_attak212 43
+#define FRAME_attak213 44
+#define FRAME_attak214 45
+#define FRAME_attak215 46
+#define FRAME_attak216 47
+#define FRAME_death101 48
+#define FRAME_death102 49
+#define FRAME_death103 50
+#define FRAME_death104 51
+#define FRAME_death105 52
+#define FRAME_death106 53
+#define FRAME_death107 54
+#define FRAME_death108 55
+#define FRAME_death109 56
+#define FRAME_death110 57
+#define FRAME_death111 58
+#define FRAME_death112 59
+#define FRAME_death201 60
+#define FRAME_death202 61
+#define FRAME_death203 62
+#define FRAME_death204 63
+#define FRAME_death205 64
+#define FRAME_death206 65
+#define FRAME_death207 66
+#define FRAME_death208 67
+#define FRAME_death209 68
+#define FRAME_death210 69
+#define FRAME_death211 70
+#define FRAME_death212 71
+#define FRAME_death213 72
+#define FRAME_death214 73
+#define FRAME_death215 74
+#define FRAME_death216 75
+#define FRAME_death217 76
+#define FRAME_death218 77
+#define FRAME_death219 78
+#define FRAME_death220 79
+#define FRAME_death221 80
+#define FRAME_death222 81
+#define FRAME_death223 82
+#define FRAME_duck01 83
+#define FRAME_duck02 84
+#define FRAME_duck03 85
+#define FRAME_duck04 86
+#define FRAME_duck05 87
+#define FRAME_duck06 88
+#define FRAME_duck07 89
+#define FRAME_pain101 90
+#define FRAME_pain102 91
+#define FRAME_pain103 92
+#define FRAME_pain104 93
+#define FRAME_pain105 94
+#define FRAME_pain201 95
+#define FRAME_pain202 96
+#define FRAME_pain203 97
+#define FRAME_pain204 98
+#define FRAME_pain205 99
+#define FRAME_pain301 100
+#define FRAME_pain302 101
+#define FRAME_pain303 102
+#define FRAME_pain304 103
+#define FRAME_pain305 104
+#define FRAME_pain306 105
+#define FRAME_pain307 106
+#define FRAME_pain308 107
+#define FRAME_pain309 108
+#define FRAME_pain310 109
+#define FRAME_pain311 110
+#define FRAME_pain312 111
+#define FRAME_pain313 112
+#define FRAME_pain314 113
+#define FRAME_pain315 114
+#define FRAME_pain316 115
+#define FRAME_pain317 116
+#define FRAME_pain318 117
+#define FRAME_pain319 118
+#define FRAME_pain320 119
+#define FRAME_pain321 120
+#define FRAME_stand101 121
+#define FRAME_stand102 122
+#define FRAME_stand103 123
+#define FRAME_stand104 124
+#define FRAME_stand105 125
+#define FRAME_stand106 126
+#define FRAME_stand107 127
+#define FRAME_stand108 128
+#define FRAME_stand109 129
+#define FRAME_stand110 130
+#define FRAME_stand111 131
+#define FRAME_stand112 132
+#define FRAME_stand113 133
+#define FRAME_stand114 134
+#define FRAME_stand115 135
+#define FRAME_stand116 136
+#define FRAME_stand117 137
+#define FRAME_stand118 138
+#define FRAME_stand119 139
+#define FRAME_stand120 140
+#define FRAME_stand121 141
+#define FRAME_stand122 142
+#define FRAME_stand123 143
+#define FRAME_stand124 144
+#define FRAME_stand125 145
+#define FRAME_stand126 146
+#define FRAME_stand127 147
+#define FRAME_stand128 148
+#define FRAME_stand129 149
+#define FRAME_stand130 150
+#define FRAME_stand201 151
+#define FRAME_stand202 152
+#define FRAME_stand203 153
+#define FRAME_stand204 154
+#define FRAME_stand205 155
+#define FRAME_stand206 156
+#define FRAME_stand207 157
+#define FRAME_stand208 158
+#define FRAME_stand209 159
+#define FRAME_stand210 160
+#define FRAME_stand211 161
+#define FRAME_stand212 162
+#define FRAME_stand213 163
+#define FRAME_stand214 164
+#define FRAME_stand215 165
+#define FRAME_stand216 166
+#define FRAME_stand217 167
+#define FRAME_stand218 168
+#define FRAME_stand219 169
+#define FRAME_stand220 170
+#define FRAME_stand221 171
+#define FRAME_stand222 172
+#define FRAME_stand223 173
+#define FRAME_stand224 174
+#define FRAME_stand225 175
+#define FRAME_stand226 176
+#define FRAME_stand227 177
+#define FRAME_stand228 178
+#define FRAME_stand229 179
+#define FRAME_stand230 180
+#define FRAME_walk01 181
+#define FRAME_walk02 182
+#define FRAME_walk03 183
+#define FRAME_walk04 184
+#define FRAME_walk05 185
+#define FRAME_walk06 186
+#define FRAME_walk07 187
+#define FRAME_walk08 188
+#define FRAME_walk09 189
+#define FRAME_walk10 190
+#define FRAME_walk11 191
+#define FRAME_walk12 192
+#define FRAME_walk13 193
+#define FRAME_walk14 194
+#define FRAME_walk15 195
+#define FRAME_walk16 196
+#define FRAME_walk17 197
+#define FRAME_walk18 198
+#define FRAME_walk19 199
+#define FRAME_walk20 200
+#define FRAME_walk21 201
+#define FRAME_walk22 202
+#define FRAME_walk23 203
+#define FRAME_walk24 204
+#define FRAME_walk25 205
+#define FRAME_walk26 206
+#define FRAME_walk27 207
+#define FRAME_recln201 208
+#define FRAME_recln202 209
+#define FRAME_recln203 210
+#define FRAME_recln204 211
+#define FRAME_recln205 212
+#define FRAME_recln206 213
+#define FRAME_recln207 214
+#define FRAME_recln208 215
+#define FRAME_recln209 216
+#define FRAME_recln210 217
+#define FRAME_recln211 218
+#define FRAME_recln212 219
+#define FRAME_recln213 220
+#define FRAME_recln214 221
+#define FRAME_recln215 222
+#define FRAME_recln216 223
+#define FRAME_recln217 224
+#define FRAME_recln218 225
+#define FRAME_recln219 226
+#define FRAME_recln220 227
+#define FRAME_recln221 228
+#define FRAME_recln222 229
+#define FRAME_recln223 230
+#define FRAME_recln224 231
+#define FRAME_recln225 232
+#define FRAME_recln226 233
+#define FRAME_recln227 234
+#define FRAME_recln228 235
+#define FRAME_recln229 236
+#define FRAME_recln230 237
+#define FRAME_recln231 238
+#define FRAME_recln232 239
+#define FRAME_recln233 240
+#define FRAME_recln234 241
+#define FRAME_recln235 242
+#define FRAME_recln236 243
+#define FRAME_recln237 244
+#define FRAME_recln238 245
+#define FRAME_recln239 246
+#define FRAME_recln240 247
+#define FRAME_recln101 248
+#define FRAME_recln102 249
+#define FRAME_recln103 250
+#define FRAME_recln104 251
+#define FRAME_recln105 252
+#define FRAME_recln106 253
+#define FRAME_recln107 254
+#define FRAME_recln108 255
+#define FRAME_recln109 256
+#define FRAME_recln110 257
+#define FRAME_recln111 258
+#define FRAME_recln112 259
+#define FRAME_recln113 260
+#define FRAME_recln114 261
+#define FRAME_recln115 262
+#define FRAME_recln116 263
+#define FRAME_recln117 264
+#define FRAME_recln118 265
+#define FRAME_recln119 266
+#define FRAME_recln120 267
+#define FRAME_recln121 268
+#define FRAME_recln122 269
+#define FRAME_recln123 270
+#define FRAME_recln124 271
+#define FRAME_recln125 272
+#define FRAME_recln126 273
+#define FRAME_recln127 274
+#define FRAME_recln128 275
+#define FRAME_recln129 276
+#define FRAME_recln130 277
+#define FRAME_recln131 278
+#define FRAME_recln132 279
+#define FRAME_recln133 280
+#define FRAME_recln134 281
+#define FRAME_recln135 282
+#define FRAME_recln136 283
+#define FRAME_recln137 284
+#define FRAME_recln138 285
+#define FRAME_recln139 286
+#define FRAME_recln140 287
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/fixbot/fixbot.c b/xatrix/src/monster/fixbot/fixbot.c
new file mode 100644
index 0000000..bb942ac
--- /dev/null
+++ b/xatrix/src/monster/fixbot/fixbot.c
@@ -0,0 +1,1682 @@
+/* ==============================================================
+ *
+ * Fixbot
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "fixbot.h"
+
+#define MZ2_fixbot_BLASTER_1 MZ2_HOVER_BLASTER_1
+
+#define FIXBOT_MAX_STUCK_FRAMES 10
+#define FIXBOT_GOAL_TIMEOUT 15
+#define FIXBOT_WELD_GOAL_TIMEOUT 15
+
+qboolean visible(edict_t *self, edict_t *other);
+qboolean infront(edict_t *self, edict_t *other);
+
+static int sound_pain1;
+static int sound_die;
+static int sound_weld1;
+static int sound_weld2;
+static int sound_weld3;
+
+void fixbot_run(edict_t *self);
+void fixbot_stand(edict_t *self);
+void fixbot_dead(edict_t *self);
+void fixbot_attack(edict_t *self);
+void fixbot_fire_blaster(edict_t *self);
+void fixbot_fire_welder(edict_t *self);
+void fixbot_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+void M_MoveToGoal(edict_t *ent, float dist);
+
+void use_scanner(edict_t *self);
+void change_to_roam(edict_t *self);
+void fly_vertical(edict_t *self);
+
+extern mmove_t fixbot_move_forward;
+extern mmove_t fixbot_move_stand;
+extern mmove_t fixbot_move_stand2;
+extern mmove_t fixbot_move_roamgoal;
+
+extern mmove_t fixbot_move_weld_start;
+extern mmove_t fixbot_move_weld;
+extern mmove_t fixbot_move_weld_end;
+extern mmove_t fixbot_move_takeoff;
+extern mmove_t fixbot_move_landing;
+extern mmove_t fixbot_move_turn;
+
+extern void roam_goal(edict_t *self);
+void ED_CallSpawn(edict_t *ent);
+
+float
+crand(void)
+{
+ return (rand() & 32767) * (2.0 / 32767) - 1;
+}
+
+edict_t *
+fixbot_FindDeadMonster(edict_t *self)
+{
+ edict_t *ent = NULL;
+ edict_t *best = NULL;
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
+ {
+ if (ent == self)
+ {
+ continue;
+ }
+
+ if (!(ent->svflags & SVF_MONSTER))
+ {
+ continue;
+ }
+
+ if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
+ {
+ continue;
+ }
+
+ if (ent->owner)
+ {
+ continue;
+ }
+
+ if (ent->health > 0)
+ {
+ continue;
+ }
+
+ if (ent->nextthink)
+ {
+ continue;
+ }
+
+ if (!visible(self, ent))
+ {
+ continue;
+ }
+
+ if (!best)
+ {
+ best = ent;
+ continue;
+ }
+
+ if (ent->max_health <= best->max_health)
+ {
+ continue;
+ }
+
+ best = ent;
+ }
+
+ return best;
+}
+
+int
+fixbot_search(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return 0;
+ }
+
+ if (!self->goalentity)
+ {
+ ent = fixbot_FindDeadMonster(self);
+
+ if (ent)
+ {
+ self->oldenemy = self->enemy;
+ self->enemy = ent;
+ self->enemy->owner = self;
+ self->monsterinfo.aiflags |= AI_MEDIC;
+ FoundTarget(self);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+bot_goal_think(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* clean up the bot_goal if the fixbot loses it (to avoid entity leaks) */
+ if (!self->owner || !self->owner->inuse || self->owner->goalentity != self)
+ {
+ G_FreeEdict(self);
+ }
+ else
+ {
+ self->nextthink = level.time + FRAMETIME;
+ }
+}
+
+static edict_t *
+make_bot_goal(edict_t *self)
+{
+ edict_t *ent = G_Spawn();
+
+ ent->classname = "bot_goal";
+ ent->solid = SOLID_BBOX;
+ ent->owner = self;
+
+ ent->think = bot_goal_think;
+ ent->nextthink = level.time + FRAMETIME;
+ ent->touch_debounce_time = level.time + FIXBOT_GOAL_TIMEOUT;
+
+ return ent;
+}
+
+void
+landing_goal(edict_t *self)
+{
+ trace_t tr;
+ vec3_t forward, right, up;
+ vec3_t end;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = make_bot_goal(self);
+ VectorSet(ent->mins, -32, -32, -24);
+ VectorSet(ent->maxs, 32, 32, 24);
+ gi.linkentity(ent);
+
+ AngleVectors(self->s.angles, forward, right, up);
+ VectorMA(self->s.origin, 32, forward, end);
+ VectorMA(self->s.origin, -8096, up, end);
+
+ tr = gi.trace(self->s.origin, ent->mins, ent->maxs,
+ end, self, MASK_MONSTERSOLID);
+
+ VectorCopy(tr.endpos, ent->s.origin);
+ gi.linkentity(ent);
+
+ self->goalentity = self->enemy = ent;
+ self->monsterinfo.currentmove = &fixbot_move_landing;
+}
+
+void
+takeoff_goal(edict_t *self)
+{
+ trace_t tr;
+ vec3_t forward, right, up;
+ vec3_t end;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ ent = make_bot_goal(self);
+
+ VectorSet(ent->mins, -32, -32, -24);
+ VectorSet(ent->maxs, 32, 32, 24);
+ gi.linkentity(ent);
+
+ AngleVectors(self->s.angles, forward, right, up);
+ VectorMA(self->s.origin, 32, forward, end);
+ VectorMA(self->s.origin, 128, up, end);
+
+ tr = gi.trace(self->s.origin, ent->mins, ent->maxs,
+ end, self, MASK_MONSTERSOLID);
+
+ VectorCopy(tr.endpos, ent->s.origin);
+ gi.linkentity(ent);
+
+ self->goalentity = self->enemy = ent;
+ self->monsterinfo.currentmove = &fixbot_move_takeoff;
+}
+
+void
+change_to_roam(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (fixbot_search(self))
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &fixbot_move_roamgoal;
+
+ if (self->spawnflags & 16)
+ {
+ landing_goal(self);
+ self->monsterinfo.currentmove = &fixbot_move_landing;
+ self->spawnflags = 32;
+ }
+
+ if (self->spawnflags & 8)
+ {
+ takeoff_goal(self);
+ self->monsterinfo.currentmove = &fixbot_move_takeoff;
+ self->spawnflags = 32;
+ }
+
+ if (self->spawnflags & 4)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_roamgoal;
+ self->spawnflags = 32;
+ }
+
+ if (!self->spawnflags)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_stand2;
+ }
+}
+
+void
+roam_goal(edict_t *self)
+{
+ trace_t tr;
+ vec3_t forward;
+ vec3_t end;
+ edict_t *ent;
+ vec3_t dang;
+ int len, oldlen, i;
+ vec3_t vec;
+ vec3_t whichvec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorClear(whichvec);
+
+ oldlen = 0;
+
+ for (i = 0; i < 12; i++)
+ {
+ VectorCopy(self->s.angles, dang);
+
+ if (i < 6)
+ {
+ dang[YAW] += 30 * i;
+ }
+ else
+ {
+ dang[YAW] -= 30 * (i - 6);
+ }
+
+ AngleVectors(dang, forward, NULL, NULL);
+ VectorMA(self->s.origin, 8192, forward, end);
+
+ tr = gi.trace(self->s.origin, NULL, NULL, end, self, MASK_SHOT);
+
+ VectorSubtract(self->s.origin, tr.endpos, vec);
+ len = VectorLength(vec);
+
+ if (len > oldlen)
+ {
+ oldlen = len;
+ VectorCopy(tr.endpos, whichvec);
+ }
+ }
+
+ ent = make_bot_goal(self);
+ VectorCopy(whichvec, ent->s.origin);
+ gi.linkentity(ent);
+
+ self->goalentity = self->enemy = ent;
+
+ self->monsterinfo.currentmove = &fixbot_move_turn;
+}
+
+void
+use_scanner(edict_t *self)
+{
+ edict_t *ent = NULL;
+ vec3_t vec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->fly_sound_debounce_time < level.time &&
+ strcmp(self->goalentity->classname, "object_repair") != 0)
+ {
+ while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
+ {
+ if (strcmp(ent->classname, "object_repair") != 0)
+ {
+ continue;
+ }
+
+ if (ent->health < 100)
+ {
+ continue;
+ }
+
+ if (!visible(self, ent))
+ {
+ continue;
+ }
+
+ /* remove the old one */
+ if (strcmp(self->goalentity->classname, "bot_goal") == 0)
+ {
+ self->goalentity->nextthink = level.time + 0.1;
+ self->goalentity->think = G_FreeEdict;
+ }
+
+ self->goalentity = self->enemy = ent;
+
+ break;
+ }
+ }
+
+ if (strcmp(self->goalentity->classname, "object_repair") == 0)
+ {
+ VectorSubtract(self->s.origin, self->goalentity->s.origin, vec);
+
+ if (VectorLength(vec) < 56)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_weld_start;
+ return;
+ }
+ }
+ else if (strcmp(self->goalentity->classname, "bot_goal") == 0)
+ {
+ VectorSubtract(self->s.origin, self->goalentity->s.origin, vec);
+
+ if (self->goalentity->touch_debounce_time < level.time || VectorLength(vec) < 32)
+ {
+ self->goalentity->nextthink = level.time + 0.1;
+ self->goalentity->think = G_FreeEdict;
+ self->goalentity = self->enemy = NULL;
+
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+
+ return;
+ }
+ }
+
+ VectorSubtract(self->s.origin, self->s.old_origin, vec);
+
+ if (VectorLength(vec) == 0)
+ {
+ self->count++;
+ }
+ else
+ {
+ self->count = 0;
+ }
+
+ if (self->count > FIXBOT_MAX_STUCK_FRAMES)
+ {
+ /* bot is stuck, get new goalentity */
+
+ self->count = 0;
+
+ if (strcmp(self->goalentity->classname, "bot_goal") == 0)
+ {
+ self->goalentity->nextthink = level.time + 0.1;
+ self->goalentity->think = G_FreeEdict;
+ self->goalentity = self->enemy = NULL;
+ }
+ else if (strcmp(self->goalentity->classname, "object_repair") == 0)
+ {
+ /* don't try to go for welding targets again for a while */
+ self->fly_sound_debounce_time = level.time + FIXBOT_WELD_GOAL_TIMEOUT;
+ }
+
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ }
+}
+
+/*
+ * When the bot has found a landing pad
+ * it will proceed to its goalentity
+ * just above the landing pad and
+ * decend translated along the z the current
+ * frames are at 10fps
+ */
+void
+blastoff(edict_t *self, vec3_t start, vec3_t aimdir, int damage,
+ int kick, int te_impact, int hspread, int vspread)
+{
+ trace_t tr;
+ vec3_t dir;
+ vec3_t forward, right, up;
+ vec3_t end;
+ float r;
+ float u;
+ vec3_t water_start;
+ qboolean water = false;
+ int content_mask = MASK_SHOT | MASK_WATER;
+
+ if (!self)
+ {
+ return;
+ }
+
+ hspread += (self->s.frame - FRAME_takeoff_01);
+ vspread += (self->s.frame - FRAME_takeoff_01);
+
+ tr = gi.trace(self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+
+ if (!(tr.fraction < 1.0))
+ {
+ vectoangles(aimdir, dir);
+ AngleVectors(dir, forward, right, up);
+
+ r = crandom() * hspread;
+ u = crandom() * vspread;
+ VectorMA(start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+
+ if (gi.pointcontents(start) & MASK_WATER)
+ {
+ water = true;
+ VectorCopy(start, water_start);
+ content_mask &= ~MASK_WATER;
+ }
+
+ tr = gi.trace(start, NULL, NULL, end, self, content_mask);
+
+ /* see if we hit water */
+ if (tr.contents & MASK_WATER)
+ {
+ int color;
+
+ water = true;
+ VectorCopy(tr.endpos, water_start);
+
+ if (!VectorCompare(start, tr.endpos))
+ {
+ if (tr.contents & CONTENTS_WATER)
+ {
+ if (strcmp(tr.surface->name, "*brwater") == 0)
+ {
+ color = SPLASH_BROWN_WATER;
+ }
+ else
+ {
+ color = SPLASH_BLUE_WATER;
+ }
+ }
+ else if (tr.contents & CONTENTS_SLIME)
+ {
+ color = SPLASH_SLIME;
+ }
+ else if (tr.contents & CONTENTS_LAVA)
+ {
+ color = SPLASH_LAVA;
+ }
+ else
+ {
+ color = SPLASH_UNKNOWN;
+ }
+
+ if (color != SPLASH_UNKNOWN)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_SPLASH);
+ gi.WriteByte(8);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.WriteByte(color);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+ }
+
+ /* change bullet's course when it enters water */
+ VectorSubtract(end, start, dir);
+ vectoangles(dir, dir);
+ AngleVectors(dir, forward, right, up);
+ r = crandom() * hspread * 2;
+ u = crandom() * vspread * 2;
+ VectorMA(water_start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+ }
+
+ /* re-trace ignoring water this time */
+ tr = gi.trace(water_start, NULL, NULL, end, self, MASK_SHOT);
+ }
+ }
+
+ /* send gun puff / flash */
+ if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+ {
+ if (tr.fraction < 1.0)
+ {
+ if (tr.ent->takedamage)
+ {
+ T_Damage(tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal,
+ damage, kick, DAMAGE_BULLET, MOD_BLASTOFF);
+ }
+ else
+ {
+ if (strncmp(tr.surface->name, "sky", 3) != 0)
+ {
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(te_impact);
+ gi.WritePosition(tr.endpos);
+ gi.WriteDir(tr.plane.normal);
+ gi.multicast(tr.endpos, MULTICAST_PVS);
+
+ if (self->client)
+ {
+ PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+ }
+ }
+ }
+ }
+ }
+
+ /* if went through water, determine where
+ the end and make a bubble trail */
+ if (water)
+ {
+ vec3_t pos;
+
+ VectorSubtract(tr.endpos, water_start, dir);
+ VectorNormalize(dir);
+ VectorMA(tr.endpos, -2, dir, pos);
+
+ if (gi.pointcontents(pos) & MASK_WATER)
+ {
+ VectorCopy(pos, tr.endpos);
+ }
+ else
+ {
+ tr = gi.trace(pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+ }
+
+ VectorAdd(water_start, tr.endpos, pos);
+ VectorScale(pos, 0.5, pos);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_BUBBLETRAIL);
+ gi.WritePosition(water_start);
+ gi.WritePosition(tr.endpos);
+ gi.multicast(pos, MULTICAST_PVS);
+ }
+}
+
+void
+fly_vertical(edict_t *self)
+{
+ int i;
+ vec3_t v;
+ vec3_t forward, right, up;
+ vec3_t start;
+ vec3_t tempvec;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->ideal_yaw = vectoyaw(v);
+ M_ChangeYaw(self);
+
+ if ((self->s.frame == FRAME_landing_58) ||
+ (self->s.frame == FRAME_takeoff_16))
+ {
+ self->goalentity->nextthink = level.time + 0.1;
+ self->goalentity->think = G_FreeEdict;
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ self->goalentity = self->enemy = NULL;
+ }
+
+ /* kick up some particles */
+ VectorCopy(self->s.angles, tempvec);
+ tempvec[PITCH] += 90;
+
+ AngleVectors(tempvec, forward, right, up);
+ VectorCopy(self->s.origin, start);
+
+ for (i = 0; i < 10; i++)
+ {
+ blastoff(self, start, forward, 2, 1, TE_SHOTGUN,
+ DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD);
+ }
+}
+
+void
+fly_vertical2(edict_t *self)
+{
+ vec3_t v;
+ int len;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ len = VectorLength(v);
+ self->ideal_yaw = vectoyaw(v);
+ M_ChangeYaw(self);
+
+ if (len < 32)
+ {
+ self->goalentity->nextthink = level.time + 0.1;
+ self->goalentity->think = G_FreeEdict;
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ self->goalentity = self->enemy = NULL;
+ }
+}
+
+mframe_t fixbot_frames_landing[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2},
+ {ai_move, 0, fly_vertical2}
+};
+
+mmove_t fixbot_move_landing = {
+ FRAME_landing_01,
+ FRAME_landing_58,
+ fixbot_frames_landing,
+ NULL
+};
+
+/* generic ambient stand */
+mframe_t fixbot_frames_stand[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, change_to_roam}
+};
+
+mmove_t fixbot_move_stand = {
+ FRAME_ambient_01,
+ FRAME_ambient_19,
+ fixbot_frames_stand,
+ NULL
+};
+
+mframe_t fixbot_frames_stand2[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t fixbot_move_stand2 = {
+ FRAME_ambient_01,
+ FRAME_ambient_19,
+ fixbot_frames_stand2,
+ NULL
+};
+
+/*
+ * will need the pickup offset for the front pincers
+ * object will need to stop forward of the object
+ * and take the object with it ( this may require a
+ * variant of liftoff and landing )
+ */
+mframe_t fixbot_frames_pickup[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t fixbot_move_pickup = {
+ FRAME_pickup_01,
+ FRAME_pickup_27,
+ fixbot_frames_pickup,
+ NULL
+};
+
+/* generic frame to move bot */
+mframe_t fixbot_frames_roamgoal[] = {
+ {ai_move, 0, roam_goal}
+};
+
+mmove_t fixbot_move_roamgoal = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_roamgoal,
+ NULL
+};
+
+void
+ai_facing(edict_t *self, float dist)
+{
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (infront(self, self->goalentity))
+ {
+ self->monsterinfo.currentmove = &fixbot_move_forward;
+ }
+ else
+ {
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->ideal_yaw = vectoyaw(v);
+ M_ChangeYaw(self);
+ }
+}
+
+mframe_t fixbot_frames_turn[] = {
+ {ai_facing, 0, NULL}
+};
+
+mmove_t fixbot_move_turn = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_turn,
+ NULL
+};
+
+void
+go_roam(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+}
+
+/* takeoff */
+mframe_t fixbot_frames_takeoff[] = {
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical},
+ {ai_move, 0.01, fly_vertical}
+};
+
+mmove_t fixbot_move_takeoff = {
+ FRAME_takeoff_01,
+ FRAME_takeoff_16,
+ fixbot_frames_takeoff,
+ NULL
+};
+
+/* findout what this is */
+mframe_t fixbot_frames_paina[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t fixbot_move_paina = {
+ FRAME_paina_01,
+ FRAME_paina_06,
+ fixbot_frames_paina,
+ fixbot_run
+};
+
+/* findout what this is */
+mframe_t fixbot_frames_painb[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t fixbot_move_painb = {
+ FRAME_painb_01,
+ FRAME_painb_08,
+ fixbot_frames_painb,
+ fixbot_run
+};
+
+/*
+ * backup from pain
+ * call a generic painsound
+ * some spark effects
+ */
+mframe_t fixbot_frames_pain3[] = {
+ {ai_move, -1, NULL}
+};
+
+mmove_t fixbot_move_pain3 = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_pain3,
+ fixbot_run
+};
+
+/*
+ * bot has compleated landing
+ * and is now on the grownd
+ * ( may need second land if the
+ * bot is releasing jib into jib vat )
+ */
+mframe_t fixbot_frames_land[] = {
+ {ai_move, 0, NULL}
+};
+
+mmove_t fixbot_move_land = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_land,
+ NULL
+};
+
+void
+ai_movetogoal(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ M_MoveToGoal(self, dist);
+}
+
+mframe_t fixbot_frames_forward[] = {
+ {ai_movetogoal, 5, use_scanner}
+};
+
+mmove_t fixbot_move_forward = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_forward,
+ NULL
+};
+
+mframe_t fixbot_frames_walk[] = {
+ {ai_walk, 5, NULL}
+};
+
+mmove_t fixbot_move_walk = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_walk,
+ NULL
+};
+
+mframe_t fixbot_frames_run[] = {
+ {ai_run, 10, NULL}
+};
+
+mmove_t fixbot_move_run = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_run,
+ NULL
+};
+
+mframe_t fixbot_frames_death1[] = {
+ {ai_move, 0, NULL}
+
+};
+mmove_t fixbot_move_death1 = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_death1,
+ fixbot_dead
+};
+
+mframe_t fixbot_frames_backward[] = {
+ {ai_move, 0, NULL}
+};
+
+mmove_t fixbot_move_backward = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_backward,
+ NULL
+};
+
+mframe_t fixbot_frames_start_attack[] = {
+ {ai_charge, 0, NULL}
+};
+
+mmove_t fixbot_move_start_attack = {
+ FRAME_freeze_01,
+ FRAME_freeze_01,
+ fixbot_frames_start_attack,
+ fixbot_attack
+};
+
+mframe_t fixbot_frames_attack1[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -10, fixbot_fire_blaster}
+};
+
+mmove_t fixbot_move_attack1 = {
+ FRAME_shoot_01,
+ FRAME_shoot_06,
+ fixbot_frames_attack1,
+ NULL
+};
+
+int
+check_telefrag(edict_t *self)
+{
+ vec3_t end, up;
+ trace_t tr;
+
+ if (!self)
+ {
+ return 0;
+ }
+
+ AngleVectors(self->enemy->s.angles, NULL, NULL, up);
+ VectorMA(self->enemy->s.origin, 48, up, end);
+
+ tr = gi.trace(self->enemy->s.origin, self->enemy->mins, self->enemy->maxs,
+ end, self, MASK_MONSTERSOLID);
+
+ if (tr.ent && tr.ent->takedamage)
+ {
+ tr.ent->health = 0;
+
+ T_Damage(tr.ent, self, self,
+ vec3_origin, vec3_origin, vec3_origin,
+ 10000, 0, 0, MOD_UNKNOWN);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+void
+fixbot_fire_laser(edict_t *self)
+{
+ vec3_t forward, right, up;
+ vec3_t tempang, start;
+ vec3_t dir, angles, end;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* critter dun got blown up while bein' fixed */
+ if (self->enemy->health <= self->enemy->gib_health)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ self->monsterinfo.aiflags &= ~AI_MEDIC;
+ return;
+ }
+
+ gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"),
+ 1, ATTN_STATIC, 0);
+
+ VectorCopy(self->s.origin, start);
+ VectorCopy(self->enemy->s.origin, end);
+ VectorSubtract(end, start, dir);
+ vectoangles(dir, angles);
+
+ ent = G_Spawn();
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(angles, tempang);
+ AngleVectors(tempang, forward, right, up);
+ VectorCopy(tempang, ent->s.angles);
+ VectorCopy(ent->s.origin, start);
+
+ VectorMA(start, 16, forward, start);
+
+ VectorCopy(start, ent->s.origin);
+ ent->enemy = self->enemy;
+ ent->owner = self;
+ ent->dmg = -1;
+ monster_dabeam(ent);
+
+ if (self->enemy->health > (self->enemy->mass / 10))
+ {
+ if (check_telefrag(self))
+ {
+ self->enemy->spawnflags = 0;
+ self->enemy->monsterinfo.aiflags = 0;
+ self->enemy->target = NULL;
+ self->enemy->targetname = NULL;
+ self->enemy->combattarget = NULL;
+ self->enemy->deathtarget = NULL;
+ self->enemy->owner = self;
+ ED_CallSpawn(self->enemy);
+ self->enemy->owner = NULL;
+ self->s.origin[2] += 1;
+
+ self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ self->monsterinfo.aiflags &= ~AI_MEDIC;
+ }
+ }
+ else
+ {
+ self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+ }
+}
+
+mframe_t fixbot_frames_laserattack[] = {
+ {ai_charge, 0, fixbot_fire_laser},
+ {ai_charge, 0, fixbot_fire_laser},
+ {ai_charge, 0, fixbot_fire_laser},
+ {ai_charge, 0, fixbot_fire_laser},
+ {ai_charge, 0, fixbot_fire_laser},
+ {ai_charge, 0, fixbot_fire_laser}
+};
+
+mmove_t fixbot_move_laserattack = {
+ FRAME_shoot_01,
+ FRAME_shoot_06,
+ fixbot_frames_laserattack,
+ NULL
+};
+
+/* need to get forward translation
+ data for the charge attack */
+mframe_t fixbot_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+ {ai_charge, -10, NULL},
+
+ {ai_charge, 0, fixbot_fire_blaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+
+ {ai_charge, 0, NULL}
+};
+
+mmove_t fixbot_move_attack2 = {
+ FRAME_charging_01,
+ FRAME_charging_31,
+ fixbot_frames_attack2,
+ fixbot_run
+};
+
+void
+weldstate(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_weldstart_10)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_weld;
+ }
+ else if (self->s.frame == FRAME_weldmiddle_07)
+ {
+ if (self->goalentity->health < 0)
+ {
+ self->enemy->owner = NULL;
+ self->monsterinfo.currentmove = &fixbot_move_weld_end;
+ }
+ else
+ {
+ self->goalentity->health -= 10;
+ }
+ }
+ else
+ {
+ self->goalentity = self->enemy = NULL;
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ }
+}
+
+void
+ai_move2(edict_t *self, float dist)
+{
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (dist)
+ {
+ M_walkmove(self, self->s.angles[YAW], dist);
+ }
+
+ VectorSubtract(self->goalentity->s.origin, self->s.origin, v);
+ self->ideal_yaw = vectoyaw(v);
+ M_ChangeYaw(self);
+}
+
+mframe_t fixbot_frames_weld_start[] = {
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, NULL},
+ {ai_move2, 0, weldstate}
+};
+
+mmove_t fixbot_move_weld_start = {
+ FRAME_weldstart_01,
+ FRAME_weldstart_10,
+ fixbot_frames_weld_start,
+ NULL
+};
+
+mframe_t fixbot_frames_weld[] = {
+ {ai_move2, 0, fixbot_fire_welder},
+ {ai_move2, 0, fixbot_fire_welder},
+ {ai_move2, 0, fixbot_fire_welder},
+ {ai_move2, 0, fixbot_fire_welder},
+ {ai_move2, 0, fixbot_fire_welder},
+ {ai_move2, 0, fixbot_fire_welder},
+ {ai_move2, 0, weldstate}
+};
+
+mmove_t fixbot_move_weld = {
+ FRAME_weldmiddle_01,
+ FRAME_weldmiddle_07,
+ fixbot_frames_weld,
+ NULL
+};
+
+mframe_t fixbot_frames_weld_end[] = {
+ {ai_move2, -2, NULL},
+ {ai_move2, -2, NULL},
+ {ai_move2, -2, NULL},
+ {ai_move2, -2, NULL},
+ {ai_move2, -2, NULL},
+ {ai_move2, -2, NULL},
+ {ai_move2, -2, weldstate}
+};
+
+mmove_t fixbot_move_weld_end = {
+ FRAME_weldend_01,
+ FRAME_weldend_07,
+ fixbot_frames_weld_end,
+ NULL
+};
+
+void
+fixbot_fire_welder(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t vec;
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ vec[0] = 24.0;
+ vec[1] = -0.8;
+ vec[2] = -10.0;
+
+ AngleVectors(self->s.angles, forward, right, up);
+ G_ProjectSource(self->s.origin, vec, forward, right, start);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_WELDING_SPARKS);
+ gi.WriteByte(10);
+ gi.WritePosition(start);
+ gi.WriteDir(vec3_origin);
+ gi.WriteByte(0xe0 + (rand() & 7));
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ if (random() > 0.8)
+ {
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_weld1, 1, ATTN_IDLE, 0);
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_weld2, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_weld3, 1, ATTN_IDLE, 0);
+ }
+ }
+}
+
+void
+fixbot_fire_blaster(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t end;
+ vec3_t dir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!visible(self, self->enemy))
+ {
+ self->monsterinfo.currentmove = &fixbot_move_run;
+ }
+
+ AngleVectors(self->s.angles, forward, right, up);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_fixbot_BLASTER_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 15, 1000, MZ2_fixbot_BLASTER_1,
+ EF_BLASTER);
+}
+
+void
+fixbot_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+}
+
+void
+fixbot_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &fixbot_move_run;
+ }
+}
+
+void
+fixbot_walk(edict_t *self)
+{
+ vec3_t vec;
+ int len;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (strcmp(self->goalentity->classname, "object_repair") == 0)
+ {
+ VectorSubtract(self->s.origin, self->goalentity->s.origin, vec);
+ len = VectorLength(vec);
+
+ if (len < 32)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_weld_start;
+ return;
+ }
+ }
+
+ self->monsterinfo.currentmove = &fixbot_move_walk;
+}
+
+void
+fixbot_start_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &fixbot_move_start_attack;
+}
+
+void
+fixbot_attack(edict_t *self)
+{
+ vec3_t vec;
+ int len;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ if (!visible(self, self->goalentity))
+ {
+ return;
+ }
+
+ VectorSubtract(self->s.origin, self->enemy->s.origin, vec);
+ len = VectorLength(vec);
+
+ if (len > 128)
+ {
+ return;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &fixbot_move_laserattack;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &fixbot_move_attack2;
+ }
+}
+
+void
+fixbot_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+
+ if (damage <= 10)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_pain3;
+ }
+ else if (damage <= 25)
+ {
+ self->monsterinfo.currentmove = &fixbot_move_painb;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &fixbot_move_paina;
+ }
+}
+
+void
+fixbot_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+fixbot_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ BecomeExplosion1(self);
+}
+
+/*
+ * QUAKED monster_fixbot (1 .5 0) (-32 -32 -24) (32 32 24) Ambush Trigger_Spawn Fixit Takeoff Landing
+ */
+void
+SP_monster_fixbot(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("flyer/flypain1.wav");
+ sound_die = gi.soundindex("flyer/flydeth1.wav");
+
+ sound_weld1 = gi.soundindex("misc/welder1.wav");
+ sound_weld2 = gi.soundindex("misc/welder2.wav");
+ sound_weld3 = gi.soundindex("misc/welder3.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/fixbot/tris.md2");
+
+ VectorSet(self->mins, -32, -32, -24);
+ VectorSet(self->maxs, 32, 32, 24);
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->health = 150;
+ self->mass = 150;
+ self->viewheight = 16;
+
+ self->pain = fixbot_pain;
+ self->die = fixbot_die;
+
+ self->monsterinfo.stand = fixbot_stand;
+ self->monsterinfo.walk = fixbot_walk;
+ self->monsterinfo.run = fixbot_run;
+ self->monsterinfo.attack = fixbot_attack;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &fixbot_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/xatrix/src/monster/fixbot/fixbot.h b/xatrix/src/monster/fixbot/fixbot.h
new file mode 100644
index 0000000..22646af
--- /dev/null
+++ b/xatrix/src/monster/fixbot/fixbot.h
@@ -0,0 +1,221 @@
+/* =======================================================================
+ *
+ * Fixbot animations
+ *
+ * =======================================================================
+ */
+
+#define FRAME_charging_01 0
+#define FRAME_charging_02 1
+#define FRAME_charging_03 2
+#define FRAME_charging_04 3
+#define FRAME_charging_05 4
+#define FRAME_charging_06 5
+#define FRAME_charging_07 6
+#define FRAME_charging_08 7
+#define FRAME_charging_09 8
+#define FRAME_charging_10 9
+#define FRAME_charging_11 10
+#define FRAME_charging_12 11
+#define FRAME_charging_13 12
+#define FRAME_charging_14 13
+#define FRAME_charging_15 14
+#define FRAME_charging_16 15
+#define FRAME_charging_17 16
+#define FRAME_charging_18 17
+#define FRAME_charging_19 18
+#define FRAME_charging_20 19
+#define FRAME_charging_21 20
+#define FRAME_charging_22 21
+#define FRAME_charging_23 22
+#define FRAME_charging_24 23
+#define FRAME_charging_25 24
+#define FRAME_charging_26 25
+#define FRAME_charging_27 26
+#define FRAME_charging_28 27
+#define FRAME_charging_29 28
+#define FRAME_charging_30 29
+#define FRAME_charging_31 30
+#define FRAME_landing_01 31
+#define FRAME_landing_02 32
+#define FRAME_landing_03 33
+#define FRAME_landing_04 34
+#define FRAME_landing_05 35
+#define FRAME_landing_06 36
+#define FRAME_landing_07 37
+#define FRAME_landing_08 38
+#define FRAME_landing_09 39
+#define FRAME_landing_10 40
+#define FRAME_landing_11 41
+#define FRAME_landing_12 42
+#define FRAME_landing_13 43
+#define FRAME_landing_14 44
+#define FRAME_landing_15 45
+#define FRAME_landing_16 46
+#define FRAME_landing_17 47
+#define FRAME_landing_18 48
+#define FRAME_landing_19 49
+#define FRAME_landing_20 50
+#define FRAME_landing_21 51
+#define FRAME_landing_22 52
+#define FRAME_landing_23 53
+#define FRAME_landing_24 54
+#define FRAME_landing_25 55
+#define FRAME_landing_26 56
+#define FRAME_landing_27 57
+#define FRAME_landing_28 58
+#define FRAME_landing_29 59
+#define FRAME_landing_30 60
+#define FRAME_landing_31 61
+#define FRAME_landing_32 62
+#define FRAME_landing_33 63
+#define FRAME_landing_34 64
+#define FRAME_landing_35 65
+#define FRAME_landing_36 66
+#define FRAME_landing_37 67
+#define FRAME_landing_38 68
+#define FRAME_landing_39 69
+#define FRAME_landing_40 70
+#define FRAME_landing_41 71
+#define FRAME_landing_42 72
+#define FRAME_landing_43 73
+#define FRAME_landing_44 74
+#define FRAME_landing_45 75
+#define FRAME_landing_46 76
+#define FRAME_landing_47 77
+#define FRAME_landing_48 78
+#define FRAME_landing_49 79
+#define FRAME_landing_50 80
+#define FRAME_landing_51 81
+#define FRAME_landing_52 82
+#define FRAME_landing_53 83
+#define FRAME_landing_54 84
+#define FRAME_landing_55 85
+#define FRAME_landing_56 86
+#define FRAME_landing_57 87
+#define FRAME_landing_58 88
+#define FRAME_pushback_01 89
+#define FRAME_pushback_02 90
+#define FRAME_pushback_03 91
+#define FRAME_pushback_04 92
+#define FRAME_pushback_05 93
+#define FRAME_pushback_06 94
+#define FRAME_pushback_07 95
+#define FRAME_pushback_08 96
+#define FRAME_pushback_09 97
+#define FRAME_pushback_10 98
+#define FRAME_pushback_11 99
+#define FRAME_pushback_12 100
+#define FRAME_pushback_13 101
+#define FRAME_pushback_14 102
+#define FRAME_pushback_15 103
+#define FRAME_pushback_16 104
+#define FRAME_takeoff_01 105
+#define FRAME_takeoff_02 106
+#define FRAME_takeoff_03 107
+#define FRAME_takeoff_04 108
+#define FRAME_takeoff_05 109
+#define FRAME_takeoff_06 110
+#define FRAME_takeoff_07 111
+#define FRAME_takeoff_08 112
+#define FRAME_takeoff_09 113
+#define FRAME_takeoff_10 114
+#define FRAME_takeoff_11 115
+#define FRAME_takeoff_12 116
+#define FRAME_takeoff_13 117
+#define FRAME_takeoff_14 118
+#define FRAME_takeoff_15 119
+#define FRAME_takeoff_16 120
+#define FRAME_ambient_01 121
+#define FRAME_ambient_02 122
+#define FRAME_ambient_03 123
+#define FRAME_ambient_04 124
+#define FRAME_ambient_05 125
+#define FRAME_ambient_06 126
+#define FRAME_ambient_07 127
+#define FRAME_ambient_08 128
+#define FRAME_ambient_09 129
+#define FRAME_ambient_10 130
+#define FRAME_ambient_11 131
+#define FRAME_ambient_12 132
+#define FRAME_ambient_13 133
+#define FRAME_ambient_14 134
+#define FRAME_ambient_15 135
+#define FRAME_ambient_16 136
+#define FRAME_ambient_17 137
+#define FRAME_ambient_18 138
+#define FRAME_ambient_19 139
+#define FRAME_paina_01 140
+#define FRAME_paina_02 141
+#define FRAME_paina_03 142
+#define FRAME_paina_04 143
+#define FRAME_paina_05 144
+#define FRAME_paina_06 145
+#define FRAME_painb_01 146
+#define FRAME_painb_02 147
+#define FRAME_painb_03 148
+#define FRAME_painb_04 149
+#define FRAME_painb_05 150
+#define FRAME_painb_06 151
+#define FRAME_painb_07 152
+#define FRAME_painb_08 153
+#define FRAME_pickup_01 154
+#define FRAME_pickup_02 155
+#define FRAME_pickup_03 156
+#define FRAME_pickup_04 157
+#define FRAME_pickup_05 158
+#define FRAME_pickup_06 159
+#define FRAME_pickup_07 160
+#define FRAME_pickup_08 161
+#define FRAME_pickup_09 162
+#define FRAME_pickup_10 163
+#define FRAME_pickup_11 164
+#define FRAME_pickup_12 165
+#define FRAME_pickup_13 166
+#define FRAME_pickup_14 167
+#define FRAME_pickup_15 168
+#define FRAME_pickup_16 169
+#define FRAME_pickup_17 170
+#define FRAME_pickup_18 171
+#define FRAME_pickup_19 172
+#define FRAME_pickup_20 173
+#define FRAME_pickup_21 174
+#define FRAME_pickup_22 175
+#define FRAME_pickup_23 176
+#define FRAME_pickup_24 177
+#define FRAME_pickup_25 178
+#define FRAME_pickup_26 179
+#define FRAME_pickup_27 180
+#define FRAME_freeze_01 181
+#define FRAME_shoot_01 182
+#define FRAME_shoot_02 183
+#define FRAME_shoot_03 184
+#define FRAME_shoot_04 185
+#define FRAME_shoot_05 186
+#define FRAME_shoot_06 187
+#define FRAME_weldstart_01 188
+#define FRAME_weldstart_02 189
+#define FRAME_weldstart_03 190
+#define FRAME_weldstart_04 191
+#define FRAME_weldstart_05 192
+#define FRAME_weldstart_06 193
+#define FRAME_weldstart_07 194
+#define FRAME_weldstart_08 195
+#define FRAME_weldstart_09 196
+#define FRAME_weldstart_10 197
+#define FRAME_weldmiddle_01 198
+#define FRAME_weldmiddle_02 199
+#define FRAME_weldmiddle_03 200
+#define FRAME_weldmiddle_04 201
+#define FRAME_weldmiddle_05 202
+#define FRAME_weldmiddle_06 203
+#define FRAME_weldmiddle_07 204
+#define FRAME_weldend_01 205
+#define FRAME_weldend_02 206
+#define FRAME_weldend_03 207
+#define FRAME_weldend_04 208
+#define FRAME_weldend_05 209
+#define FRAME_weldend_06 210
+#define FRAME_weldend_07 211
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/flipper/flipper.c b/xatrix/src/monster/flipper/flipper.c
new file mode 100644
index 0000000..2a5863d
--- /dev/null
+++ b/xatrix/src/monster/flipper/flipper.c
@@ -0,0 +1,536 @@
+/* =======================================================================
+ *
+ * Baracuda Shark.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "flipper.h"
+
+#define FLIPPER_RUN_SPEED 24
+
+static int sound_chomp;
+static int sound_attack;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_death;
+static int sound_idle;
+static int sound_search;
+static int sound_sight;
+
+void flipper_stand(edict_t *self);
+
+mframe_t flipper_frames_stand[] = {
+ {ai_stand, 0, NULL}
+};
+
+mmove_t flipper_move_stand = {
+ FRAME_flphor01,
+ FRAME_flphor01,
+ flipper_frames_stand,
+ NULL
+};
+
+void
+flipper_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_stand;
+}
+
+mframe_t flipper_frames_run[] = {
+ {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 6 */
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 10 */
+
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL}, /* 20 */
+
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL},
+ {ai_run, FLIPPER_RUN_SPEED, NULL} /* 29 */
+};
+
+mmove_t flipper_move_run_loop = {
+ FRAME_flpver06,
+ FRAME_flpver29,
+ flipper_frames_run,
+ NULL
+};
+
+void
+flipper_run_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_run_loop;
+}
+
+mframe_t flipper_frames_run_start[] = {
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL}
+};
+
+mmove_t flipper_move_run_start = {
+ FRAME_flpver01,
+ FRAME_flpver06,
+ flipper_frames_run_start,
+ flipper_run_loop
+};
+
+void
+flipper_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_run_start;
+}
+
+/* Standard Swimming */
+mframe_t flipper_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t flipper_move_walk = {
+ FRAME_flphor01,
+ FRAME_flphor24,
+ flipper_frames_walk,
+ NULL
+};
+
+void
+flipper_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_walk;
+}
+
+mframe_t flipper_frames_start_run[] = {
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 8, flipper_run}
+};
+
+mmove_t flipper_move_start_run = {
+ FRAME_flphor01, FRAME_flphor05,
+ flipper_frames_start_run,
+ NULL
+};
+
+void
+flipper_start_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_start_run;
+}
+
+mframe_t flipper_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flipper_move_pain2 = {
+ FRAME_flppn101,
+ FRAME_flppn105,
+ flipper_frames_pain2,
+ flipper_run
+};
+
+mframe_t flipper_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flipper_move_pain1 = {
+ FRAME_flppn201,
+ FRAME_flppn205,
+ flipper_frames_pain1,
+ flipper_run
+};
+
+void
+flipper_bite(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 0);
+ fire_hit(self, aim, 5, 0);
+}
+
+void
+flipper_preattack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0);
+}
+
+mframe_t flipper_frames_attack[] = {
+ {ai_charge, 0, flipper_preattack},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flipper_bite},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flipper_bite},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flipper_move_attack = {
+ FRAME_flpbit01,
+ FRAME_flpbit20,
+ flipper_frames_attack,
+ flipper_run
+};
+
+void
+flipper_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flipper_move_attack;
+}
+
+void
+flipper_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = (rand() + 1) % 2;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flipper_move_pain1;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flipper_move_pain2;
+ }
+}
+
+void
+flipper_dead(edict_t *self)
+{
+ vec3_t p;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* original dead bbox was wrong - and make sure the bbox adjustment stays in solidity */
+
+ p[0] = self->s.origin[0];
+ p[1] = self->s.origin[1];
+ p[2] = self->s.origin[2] - 8;
+
+ tr = gi.trace(self->s.origin, self->mins, self->maxs, p, self, self->clipmask);
+
+ self->mins[2] = tr.endpos[2] - self->s.origin[2];
+
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t flipper_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flipper_move_death = {
+ FRAME_flpdth01,
+ FRAME_flpdth56,
+ flipper_frames_death,
+ flipper_dead
+};
+
+void
+flipper_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+flipper_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"),
+ 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &flipper_move_death;
+}
+
+/*
+ * QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_flipper(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("flipper/flppain1.wav");
+ sound_pain2 = gi.soundindex("flipper/flppain2.wav");
+ sound_death = gi.soundindex("flipper/flpdeth1.wav");
+ sound_chomp = gi.soundindex("flipper/flpatck1.wav");
+ sound_attack = gi.soundindex("flipper/flpatck2.wav");
+ sound_idle = gi.soundindex("flipper/flpidle1.wav");
+ sound_search = gi.soundindex("flipper/flpsrch1.wav");
+ sound_sight = gi.soundindex("flipper/flpsght1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/flipper/tris.md2");
+ VectorSet(self->mins, -16, -16, 0);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 50;
+ self->gib_health = -30;
+ self->mass = 100;
+
+ self->pain = flipper_pain;
+ self->die = flipper_die;
+
+ self->monsterinfo.stand = flipper_stand;
+ self->monsterinfo.walk = flipper_walk;
+ self->monsterinfo.run = flipper_start_run;
+ self->monsterinfo.melee = flipper_melee;
+ self->monsterinfo.sight = flipper_sight;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &flipper_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ swimmonster_start(self);
+}
diff --git a/xatrix/src/monster/flipper/flipper.h b/xatrix/src/monster/flipper/flipper.h
new file mode 100644
index 0000000..55768ec
--- /dev/null
+++ b/xatrix/src/monster/flipper/flipper.h
@@ -0,0 +1,169 @@
+/* =======================================================================
+ *
+ * Baracuda Shark animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_flpbit01 0
+#define FRAME_flpbit02 1
+#define FRAME_flpbit03 2
+#define FRAME_flpbit04 3
+#define FRAME_flpbit05 4
+#define FRAME_flpbit06 5
+#define FRAME_flpbit07 6
+#define FRAME_flpbit08 7
+#define FRAME_flpbit09 8
+#define FRAME_flpbit10 9
+#define FRAME_flpbit11 10
+#define FRAME_flpbit12 11
+#define FRAME_flpbit13 12
+#define FRAME_flpbit14 13
+#define FRAME_flpbit15 14
+#define FRAME_flpbit16 15
+#define FRAME_flpbit17 16
+#define FRAME_flpbit18 17
+#define FRAME_flpbit19 18
+#define FRAME_flpbit20 19
+#define FRAME_flptal01 20
+#define FRAME_flptal02 21
+#define FRAME_flptal03 22
+#define FRAME_flptal04 23
+#define FRAME_flptal05 24
+#define FRAME_flptal06 25
+#define FRAME_flptal07 26
+#define FRAME_flptal08 27
+#define FRAME_flptal09 28
+#define FRAME_flptal10 29
+#define FRAME_flptal11 30
+#define FRAME_flptal12 31
+#define FRAME_flptal13 32
+#define FRAME_flptal14 33
+#define FRAME_flptal15 34
+#define FRAME_flptal16 35
+#define FRAME_flptal17 36
+#define FRAME_flptal18 37
+#define FRAME_flptal19 38
+#define FRAME_flptal20 39
+#define FRAME_flptal21 40
+#define FRAME_flphor01 41
+#define FRAME_flphor02 42
+#define FRAME_flphor03 43
+#define FRAME_flphor04 44
+#define FRAME_flphor05 45
+#define FRAME_flphor06 46
+#define FRAME_flphor07 47
+#define FRAME_flphor08 48
+#define FRAME_flphor09 49
+#define FRAME_flphor10 50
+#define FRAME_flphor11 51
+#define FRAME_flphor12 52
+#define FRAME_flphor13 53
+#define FRAME_flphor14 54
+#define FRAME_flphor15 55
+#define FRAME_flphor16 56
+#define FRAME_flphor17 57
+#define FRAME_flphor18 58
+#define FRAME_flphor19 59
+#define FRAME_flphor20 60
+#define FRAME_flphor21 61
+#define FRAME_flphor22 62
+#define FRAME_flphor23 63
+#define FRAME_flphor24 64
+#define FRAME_flpver01 65
+#define FRAME_flpver02 66
+#define FRAME_flpver03 67
+#define FRAME_flpver04 68
+#define FRAME_flpver05 69
+#define FRAME_flpver06 70
+#define FRAME_flpver07 71
+#define FRAME_flpver08 72
+#define FRAME_flpver09 73
+#define FRAME_flpver10 74
+#define FRAME_flpver11 75
+#define FRAME_flpver12 76
+#define FRAME_flpver13 77
+#define FRAME_flpver14 78
+#define FRAME_flpver15 79
+#define FRAME_flpver16 80
+#define FRAME_flpver17 81
+#define FRAME_flpver18 82
+#define FRAME_flpver19 83
+#define FRAME_flpver20 84
+#define FRAME_flpver21 85
+#define FRAME_flpver22 86
+#define FRAME_flpver23 87
+#define FRAME_flpver24 88
+#define FRAME_flpver25 89
+#define FRAME_flpver26 90
+#define FRAME_flpver27 91
+#define FRAME_flpver28 92
+#define FRAME_flpver29 93
+#define FRAME_flppn101 94
+#define FRAME_flppn102 95
+#define FRAME_flppn103 96
+#define FRAME_flppn104 97
+#define FRAME_flppn105 98
+#define FRAME_flppn201 99
+#define FRAME_flppn202 100
+#define FRAME_flppn203 101
+#define FRAME_flppn204 102
+#define FRAME_flppn205 103
+#define FRAME_flpdth01 104
+#define FRAME_flpdth02 105
+#define FRAME_flpdth03 106
+#define FRAME_flpdth04 107
+#define FRAME_flpdth05 108
+#define FRAME_flpdth06 109
+#define FRAME_flpdth07 110
+#define FRAME_flpdth08 111
+#define FRAME_flpdth09 112
+#define FRAME_flpdth10 113
+#define FRAME_flpdth11 114
+#define FRAME_flpdth12 115
+#define FRAME_flpdth13 116
+#define FRAME_flpdth14 117
+#define FRAME_flpdth15 118
+#define FRAME_flpdth16 119
+#define FRAME_flpdth17 120
+#define FRAME_flpdth18 121
+#define FRAME_flpdth19 122
+#define FRAME_flpdth20 123
+#define FRAME_flpdth21 124
+#define FRAME_flpdth22 125
+#define FRAME_flpdth23 126
+#define FRAME_flpdth24 127
+#define FRAME_flpdth25 128
+#define FRAME_flpdth26 129
+#define FRAME_flpdth27 130
+#define FRAME_flpdth28 131
+#define FRAME_flpdth29 132
+#define FRAME_flpdth30 133
+#define FRAME_flpdth31 134
+#define FRAME_flpdth32 135
+#define FRAME_flpdth33 136
+#define FRAME_flpdth34 137
+#define FRAME_flpdth35 138
+#define FRAME_flpdth36 139
+#define FRAME_flpdth37 140
+#define FRAME_flpdth38 141
+#define FRAME_flpdth39 142
+#define FRAME_flpdth40 143
+#define FRAME_flpdth41 144
+#define FRAME_flpdth42 145
+#define FRAME_flpdth43 146
+#define FRAME_flpdth44 147
+#define FRAME_flpdth45 148
+#define FRAME_flpdth46 149
+#define FRAME_flpdth47 150
+#define FRAME_flpdth48 151
+#define FRAME_flpdth49 152
+#define FRAME_flpdth50 153
+#define FRAME_flpdth51 154
+#define FRAME_flpdth52 155
+#define FRAME_flpdth53 156
+#define FRAME_flpdth54 157
+#define FRAME_flpdth55 158
+#define FRAME_flpdth56 159
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/float/float.c b/xatrix/src/monster/float/float.c
new file mode 100644
index 0000000..9584d22
--- /dev/null
+++ b/xatrix/src/monster/float/float.c
@@ -0,0 +1,816 @@
+/* =======================================================================
+ *
+ * Mechanic.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "float.h"
+
+static int sound_attack2;
+static int sound_attack3;
+static int sound_death1;
+static int sound_idle;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_sight;
+
+void floater_dead(edict_t *self);
+void floater_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+void floater_run(edict_t *self);
+void floater_wham(edict_t *self);
+void floater_zap(edict_t *self);
+
+void
+floater_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+floater_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+floater_fire_blaster(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect);
+}
+
+mframe_t floater_frames_stand1[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t floater_move_stand1 = {
+ FRAME_stand101,
+ FRAME_stand152,
+ floater_frames_stand1,
+ NULL
+};
+
+mframe_t floater_frames_stand2[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t floater_move_stand2 = {
+ FRAME_stand201,
+ FRAME_stand252,
+ floater_frames_stand2,
+ NULL
+};
+
+void
+floater_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &floater_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_stand2;
+ }
+}
+
+mframe_t floater_frames_activate[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_activate = {
+ FRAME_actvat01,
+ FRAME_actvat31,
+ floater_frames_activate,
+ NULL
+};
+
+mframe_t floater_frames_attack1[] = {
+ {ai_charge, 0, NULL}, /* Blaster attack */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, floater_fire_blaster}, /* BOOM (0, -25.8, 32.5) -- LOOP Starts */
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, floater_fire_blaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL} /* -- LOOP Ends */
+};
+
+mmove_t floater_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak114,
+ floater_frames_attack1,
+ floater_run
+};
+
+mframe_t floater_frames_attack2[] = {
+ {ai_charge, 0, NULL}, /* Claws */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, floater_wham}, /* WHAM (0, -45, 29}.6) -- LOOP Starts */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* -- LOOP Ends */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t floater_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak225,
+ floater_frames_attack2,
+ floater_run
+};
+
+mframe_t floater_frames_attack3[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, floater_zap}, /* -- LOOP Starts */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* -- LOOP Ends */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t floater_move_attack3 = {
+ FRAME_attak301,
+ FRAME_attak334,
+ floater_frames_attack3,
+ floater_run
+};
+
+mframe_t floater_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_death = {
+ FRAME_death01,
+ FRAME_death13,
+ floater_frames_death,
+ floater_dead
+};
+
+mframe_t floater_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain107,
+ floater_frames_pain1,
+ floater_run
+};
+
+mframe_t floater_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain208,
+ floater_frames_pain2,
+ floater_run
+};
+
+mframe_t floater_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t floater_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain312,
+ floater_frames_pain3,
+ floater_run
+};
+
+mframe_t floater_frames_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL}
+};
+
+mmove_t floater_move_walk = {
+ FRAME_stand101,
+ FRAME_stand152,
+ floater_frames_walk,
+ NULL
+};
+
+mframe_t floater_frames_run[] = {
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 13, NULL}
+};
+
+mmove_t floater_move_run = {
+ FRAME_stand101,
+ FRAME_stand152,
+ floater_frames_run,
+ NULL
+};
+
+void
+floater_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &floater_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_run;
+ }
+}
+
+void
+floater_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &floater_move_walk;
+}
+
+void
+floater_wham(edict_t *self)
+{
+ static vec3_t aim = {MELEE_DISTANCE, 0, 0};
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0);
+ fire_hit(self, aim, 5 + rand() % 6, -50);
+}
+
+void
+floater_zap(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t origin;
+ vec3_t dir;
+ vec3_t offset;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ VectorSet(offset, 18.5, -0.9, 10);
+ G_ProjectSource(self->s.origin, offset, forward, right, origin);
+
+ gi.sound(self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0);
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_SPLASH);
+ gi.WriteByte(32);
+ gi.WritePosition(origin);
+ gi.WriteDir(dir);
+ gi.WriteByte(1); /* sparks */
+ gi.multicast(origin, MULTICAST_PVS);
+
+ if (range(self, self->enemy) == RANGE_MELEE && infront(self, self->enemy) &&
+ visible(self, self->enemy))
+ {
+ T_Damage(self->enemy, self, self, dir, self->enemy->s.origin,
+ vec3_origin, 5 + rand() % 6, -10, DAMAGE_ENERGY,
+ MOD_UNKNOWN);
+ }
+}
+
+void
+floater_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &floater_move_attack1;
+}
+
+void
+floater_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &floater_move_attack3;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_attack2;
+ }
+}
+
+void
+floater_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = (rand() + 1) % 3;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &floater_move_pain1;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &floater_move_pain2;
+ }
+}
+
+void
+floater_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+floater_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage /* unused */, vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+ BecomeExplosion1(self);
+}
+
+/*
+ * QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_floater(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_attack2 = gi.soundindex("floater/fltatck2.wav");
+ sound_attack3 = gi.soundindex("floater/fltatck3.wav");
+ sound_death1 = gi.soundindex("floater/fltdeth1.wav");
+ sound_idle = gi.soundindex("floater/fltidle1.wav");
+ sound_pain1 = gi.soundindex("floater/fltpain1.wav");
+ sound_pain2 = gi.soundindex("floater/fltpain2.wav");
+ sound_sight = gi.soundindex("floater/fltsght1.wav");
+
+ gi.soundindex("floater/fltatck1.wav");
+
+ self->s.sound = gi.soundindex("floater/fltsrch1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/float/tris.md2");
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 32);
+
+ self->health = 200;
+ self->gib_health = -80;
+ self->mass = 300;
+
+ self->pain = floater_pain;
+ self->die = floater_die;
+
+ self->monsterinfo.stand = floater_stand;
+ self->monsterinfo.walk = floater_walk;
+ self->monsterinfo.run = floater_run;
+ self->monsterinfo.attack = floater_attack;
+ self->monsterinfo.melee = floater_melee;
+ self->monsterinfo.sight = floater_sight;
+ self->monsterinfo.idle = floater_idle;
+
+ gi.linkentity(self);
+
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &floater_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &floater_move_stand2;
+ }
+
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/xatrix/src/monster/float/float.h b/xatrix/src/monster/float/float.h
new file mode 100644
index 0000000..5c44c0a
--- /dev/null
+++ b/xatrix/src/monster/float/float.h
@@ -0,0 +1,257 @@
+/* =======================================================================
+ *
+ * Mechanic animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_actvat01 0
+#define FRAME_actvat02 1
+#define FRAME_actvat03 2
+#define FRAME_actvat04 3
+#define FRAME_actvat05 4
+#define FRAME_actvat06 5
+#define FRAME_actvat07 6
+#define FRAME_actvat08 7
+#define FRAME_actvat09 8
+#define FRAME_actvat10 9
+#define FRAME_actvat11 10
+#define FRAME_actvat12 11
+#define FRAME_actvat13 12
+#define FRAME_actvat14 13
+#define FRAME_actvat15 14
+#define FRAME_actvat16 15
+#define FRAME_actvat17 16
+#define FRAME_actvat18 17
+#define FRAME_actvat19 18
+#define FRAME_actvat20 19
+#define FRAME_actvat21 20
+#define FRAME_actvat22 21
+#define FRAME_actvat23 22
+#define FRAME_actvat24 23
+#define FRAME_actvat25 24
+#define FRAME_actvat26 25
+#define FRAME_actvat27 26
+#define FRAME_actvat28 27
+#define FRAME_actvat29 28
+#define FRAME_actvat30 29
+#define FRAME_actvat31 30
+#define FRAME_attak101 31
+#define FRAME_attak102 32
+#define FRAME_attak103 33
+#define FRAME_attak104 34
+#define FRAME_attak105 35
+#define FRAME_attak106 36
+#define FRAME_attak107 37
+#define FRAME_attak108 38
+#define FRAME_attak109 39
+#define FRAME_attak110 40
+#define FRAME_attak111 41
+#define FRAME_attak112 42
+#define FRAME_attak113 43
+#define FRAME_attak114 44
+#define FRAME_attak201 45
+#define FRAME_attak202 46
+#define FRAME_attak203 47
+#define FRAME_attak204 48
+#define FRAME_attak205 49
+#define FRAME_attak206 50
+#define FRAME_attak207 51
+#define FRAME_attak208 52
+#define FRAME_attak209 53
+#define FRAME_attak210 54
+#define FRAME_attak211 55
+#define FRAME_attak212 56
+#define FRAME_attak213 57
+#define FRAME_attak214 58
+#define FRAME_attak215 59
+#define FRAME_attak216 60
+#define FRAME_attak217 61
+#define FRAME_attak218 62
+#define FRAME_attak219 63
+#define FRAME_attak220 64
+#define FRAME_attak221 65
+#define FRAME_attak222 66
+#define FRAME_attak223 67
+#define FRAME_attak224 68
+#define FRAME_attak225 69
+#define FRAME_attak301 70
+#define FRAME_attak302 71
+#define FRAME_attak303 72
+#define FRAME_attak304 73
+#define FRAME_attak305 74
+#define FRAME_attak306 75
+#define FRAME_attak307 76
+#define FRAME_attak308 77
+#define FRAME_attak309 78
+#define FRAME_attak310 79
+#define FRAME_attak311 80
+#define FRAME_attak312 81
+#define FRAME_attak313 82
+#define FRAME_attak314 83
+#define FRAME_attak315 84
+#define FRAME_attak316 85
+#define FRAME_attak317 86
+#define FRAME_attak318 87
+#define FRAME_attak319 88
+#define FRAME_attak320 89
+#define FRAME_attak321 90
+#define FRAME_attak322 91
+#define FRAME_attak323 92
+#define FRAME_attak324 93
+#define FRAME_attak325 94
+#define FRAME_attak326 95
+#define FRAME_attak327 96
+#define FRAME_attak328 97
+#define FRAME_attak329 98
+#define FRAME_attak330 99
+#define FRAME_attak331 100
+#define FRAME_attak332 101
+#define FRAME_attak333 102
+#define FRAME_attak334 103
+#define FRAME_death01 104
+#define FRAME_death02 105
+#define FRAME_death03 106
+#define FRAME_death04 107
+#define FRAME_death05 108
+#define FRAME_death06 109
+#define FRAME_death07 110
+#define FRAME_death08 111
+#define FRAME_death09 112
+#define FRAME_death10 113
+#define FRAME_death11 114
+#define FRAME_death12 115
+#define FRAME_death13 116
+#define FRAME_pain101 117
+#define FRAME_pain102 118
+#define FRAME_pain103 119
+#define FRAME_pain104 120
+#define FRAME_pain105 121
+#define FRAME_pain106 122
+#define FRAME_pain107 123
+#define FRAME_pain201 124
+#define FRAME_pain202 125
+#define FRAME_pain203 126
+#define FRAME_pain204 127
+#define FRAME_pain205 128
+#define FRAME_pain206 129
+#define FRAME_pain207 130
+#define FRAME_pain208 131
+#define FRAME_pain301 132
+#define FRAME_pain302 133
+#define FRAME_pain303 134
+#define FRAME_pain304 135
+#define FRAME_pain305 136
+#define FRAME_pain306 137
+#define FRAME_pain307 138
+#define FRAME_pain308 139
+#define FRAME_pain309 140
+#define FRAME_pain310 141
+#define FRAME_pain311 142
+#define FRAME_pain312 143
+#define FRAME_stand101 144
+#define FRAME_stand102 145
+#define FRAME_stand103 146
+#define FRAME_stand104 147
+#define FRAME_stand105 148
+#define FRAME_stand106 149
+#define FRAME_stand107 150
+#define FRAME_stand108 151
+#define FRAME_stand109 152
+#define FRAME_stand110 153
+#define FRAME_stand111 154
+#define FRAME_stand112 155
+#define FRAME_stand113 156
+#define FRAME_stand114 157
+#define FRAME_stand115 158
+#define FRAME_stand116 159
+#define FRAME_stand117 160
+#define FRAME_stand118 161
+#define FRAME_stand119 162
+#define FRAME_stand120 163
+#define FRAME_stand121 164
+#define FRAME_stand122 165
+#define FRAME_stand123 166
+#define FRAME_stand124 167
+#define FRAME_stand125 168
+#define FRAME_stand126 169
+#define FRAME_stand127 170
+#define FRAME_stand128 171
+#define FRAME_stand129 172
+#define FRAME_stand130 173
+#define FRAME_stand131 174
+#define FRAME_stand132 175
+#define FRAME_stand133 176
+#define FRAME_stand134 177
+#define FRAME_stand135 178
+#define FRAME_stand136 179
+#define FRAME_stand137 180
+#define FRAME_stand138 181
+#define FRAME_stand139 182
+#define FRAME_stand140 183
+#define FRAME_stand141 184
+#define FRAME_stand142 185
+#define FRAME_stand143 186
+#define FRAME_stand144 187
+#define FRAME_stand145 188
+#define FRAME_stand146 189
+#define FRAME_stand147 190
+#define FRAME_stand148 191
+#define FRAME_stand149 192
+#define FRAME_stand150 193
+#define FRAME_stand151 194
+#define FRAME_stand152 195
+#define FRAME_stand201 196
+#define FRAME_stand202 197
+#define FRAME_stand203 198
+#define FRAME_stand204 199
+#define FRAME_stand205 200
+#define FRAME_stand206 201
+#define FRAME_stand207 202
+#define FRAME_stand208 203
+#define FRAME_stand209 204
+#define FRAME_stand210 205
+#define FRAME_stand211 206
+#define FRAME_stand212 207
+#define FRAME_stand213 208
+#define FRAME_stand214 209
+#define FRAME_stand215 210
+#define FRAME_stand216 211
+#define FRAME_stand217 212
+#define FRAME_stand218 213
+#define FRAME_stand219 214
+#define FRAME_stand220 215
+#define FRAME_stand221 216
+#define FRAME_stand222 217
+#define FRAME_stand223 218
+#define FRAME_stand224 219
+#define FRAME_stand225 220
+#define FRAME_stand226 221
+#define FRAME_stand227 222
+#define FRAME_stand228 223
+#define FRAME_stand229 224
+#define FRAME_stand230 225
+#define FRAME_stand231 226
+#define FRAME_stand232 227
+#define FRAME_stand233 228
+#define FRAME_stand234 229
+#define FRAME_stand235 230
+#define FRAME_stand236 231
+#define FRAME_stand237 232
+#define FRAME_stand238 233
+#define FRAME_stand239 234
+#define FRAME_stand240 235
+#define FRAME_stand241 236
+#define FRAME_stand242 237
+#define FRAME_stand243 238
+#define FRAME_stand244 239
+#define FRAME_stand245 240
+#define FRAME_stand246 241
+#define FRAME_stand247 242
+#define FRAME_stand248 243
+#define FRAME_stand249 244
+#define FRAME_stand250 245
+#define FRAME_stand251 246
+#define FRAME_stand252 247
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/flyer/flyer.c b/xatrix/src/monster/flyer/flyer.c
new file mode 100644
index 0000000..7fb6e64
--- /dev/null
+++ b/xatrix/src/monster/flyer/flyer.c
@@ -0,0 +1,830 @@
+/* =======================================================================
+ *
+ * Flyer.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "flyer.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+
+static int nextmove; /* Used for start/stop frames */
+static int sound_sight;
+static int sound_idle;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_slash;
+static int sound_sproing;
+static int sound_die;
+
+void flyer_check_melee(edict_t *self);
+void flyer_loop_melee(edict_t *self);
+void flyer_melee(edict_t *self);
+void flyer_setstart(edict_t *self);
+void flyer_stand(edict_t *self);
+void flyer_nextmove(edict_t *self);
+
+void
+flyer_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+flyer_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+flyer_pop_blades(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0);
+}
+
+mframe_t flyer_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t flyer_move_stand = {
+ FRAME_stand01,
+ FRAME_stand45,
+ flyer_frames_stand,
+ NULL
+};
+
+mframe_t flyer_frames_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL}
+};
+
+mmove_t flyer_move_walk = {
+ FRAME_stand01,
+ FRAME_stand45,
+ flyer_frames_walk,
+ NULL
+};
+
+mframe_t flyer_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL}
+};
+
+mmove_t flyer_move_run = {
+ FRAME_stand01,
+ FRAME_stand45,
+ flyer_frames_run,
+ NULL
+};
+
+void
+flyer_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &flyer_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_run;
+ }
+}
+
+void
+flyer_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_walk;
+}
+
+void
+flyer_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_stand;
+}
+
+mframe_t flyer_frames_start[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, flyer_nextmove}
+};
+
+mmove_t flyer_move_start = {
+ FRAME_start01,
+ FRAME_start06,
+ flyer_frames_start,
+ NULL
+};
+
+mframe_t flyer_frames_stop[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, flyer_nextmove}
+};
+
+mmove_t flyer_move_stop = {
+ FRAME_stop01,
+ FRAME_stop07,
+ flyer_frames_stop,
+ NULL
+};
+
+void
+flyer_stop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_stop;
+}
+
+void
+flyer_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+mframe_t flyer_frames_rollright[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_rollright = {
+ FRAME_rollr01,
+ FRAME_rollr09,
+ flyer_frames_rollright,
+ NULL
+};
+
+mframe_t flyer_frames_rollleft[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_rollleft = {
+ FRAME_rollf01,
+ FRAME_rollf09,
+ flyer_frames_rollleft,
+ NULL
+};
+
+mframe_t flyer_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain304,
+ flyer_frames_pain3,
+ flyer_run
+};
+
+mframe_t flyer_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain204,
+ flyer_frames_pain2,
+ flyer_run
+};
+
+mframe_t flyer_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain109,
+ flyer_frames_pain1,
+ flyer_run
+};
+
+mframe_t flyer_frames_defense[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* Hold this frame */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_defense = {
+ FRAME_defens01,
+ FRAME_defens06,
+ flyer_frames_defense,
+ NULL
+};
+
+mframe_t flyer_frames_bankright[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_bankright = {
+ FRAME_bankr01,
+ FRAME_bankr07,
+ flyer_frames_bankright,
+ NULL
+};
+
+mframe_t flyer_frames_bankleft[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t flyer_move_bankleft = {
+ FRAME_bankl01,
+ FRAME_bankl07,
+ flyer_frames_bankleft,
+ NULL
+};
+
+void
+flyer_fire(edict_t *self, int flash_number)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->s.frame == FRAME_attak204) ||
+ (self->s.frame == FRAME_attak207) || (self->s.frame == FRAME_attak210))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number], forward,
+ right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 1, 1000, flash_number, effect);
+}
+
+void
+flyer_fireleft(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ flyer_fire(self, MZ2_FLYER_BLASTER_1);
+}
+
+void
+flyer_fireright(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ flyer_fire(self, MZ2_FLYER_BLASTER_2);
+}
+
+mframe_t flyer_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, -10, flyer_fireleft}, /* left gun */
+ {ai_charge, -10, flyer_fireright}, /* right gun */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flyer_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak217,
+ flyer_frames_attack2,
+ flyer_run
+};
+
+void
+flyer_slash_left(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 0);
+ fire_hit(self, aim, 5, 0);
+ gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+void
+flyer_slash_right(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 0);
+ fire_hit(self, aim, 5, 0);
+ gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+mframe_t flyer_frames_start_melee[] = {
+ {ai_charge, 0, flyer_pop_blades},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flyer_move_start_melee = {
+ FRAME_attak101,
+ FRAME_attak106,
+ flyer_frames_start_melee,
+ flyer_loop_melee
+};
+
+mframe_t flyer_frames_end_melee[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t flyer_move_end_melee = {
+ FRAME_attak119,
+ FRAME_attak121,
+ flyer_frames_end_melee,
+ flyer_run
+};
+
+mframe_t flyer_frames_loop_melee[] = {
+ {ai_charge, 0, NULL}, /* Loop Start */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flyer_slash_left}, /* Left Wing Strike */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, flyer_slash_right}, /* Right Wing Strike */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL} /* Loop Ends */
+};
+
+mmove_t flyer_move_loop_melee = {
+ FRAME_attak107,
+ FRAME_attak118,
+ flyer_frames_loop_melee,
+ flyer_check_melee
+};
+
+void
+flyer_loop_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_loop_melee;
+}
+
+void
+flyer_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_attack2;
+}
+
+void
+flyer_setstart(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ nextmove = ACTION_run;
+ self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+void
+flyer_nextmove(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (nextmove == ACTION_attack1)
+ {
+ self->monsterinfo.currentmove = &flyer_move_start_melee;
+ }
+ else if (nextmove == ACTION_attack2)
+ {
+ self->monsterinfo.currentmove = &flyer_move_attack2;
+ }
+ else if (nextmove == ACTION_run)
+ {
+ self->monsterinfo.currentmove = &flyer_move_run;
+ }
+}
+
+void
+flyer_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &flyer_move_start_melee;
+}
+
+void
+flyer_check_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ if (random() <= 0.8)
+ {
+ self->monsterinfo.currentmove = &flyer_move_loop_melee;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_end_melee;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &flyer_move_end_melee;
+ }
+}
+
+void
+flyer_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = rand() % 3;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flyer_move_pain1;
+ }
+ else if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flyer_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &flyer_move_pain3;
+ }
+}
+
+void
+flyer_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage /* unused */,
+ vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ BecomeExplosion1(self);
+}
+
+/*
+ * QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_flyer(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_sight = gi.soundindex("flyer/flysght1.wav");
+ sound_idle = gi.soundindex("flyer/flysrch1.wav");
+ sound_pain1 = gi.soundindex("flyer/flypain1.wav");
+ sound_pain2 = gi.soundindex("flyer/flypain2.wav");
+ sound_slash = gi.soundindex("flyer/flyatck2.wav");
+ sound_sproing = gi.soundindex("flyer/flyatck1.wav");
+ sound_die = gi.soundindex("flyer/flydeth1.wav");
+
+ gi.soundindex("flyer/flyatck3.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->s.sound = gi.soundindex("flyer/flyidle1.wav");
+
+ self->health = 50;
+ self->mass = 50;
+
+ self->pain = flyer_pain;
+ self->die = flyer_die;
+
+ self->monsterinfo.stand = flyer_stand;
+ self->monsterinfo.walk = flyer_walk;
+ self->monsterinfo.run = flyer_run;
+ self->monsterinfo.attack = flyer_attack;
+ self->monsterinfo.melee = flyer_melee;
+ self->monsterinfo.sight = flyer_sight;
+ self->monsterinfo.idle = flyer_idle;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &flyer_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/xatrix/src/monster/flyer/flyer.h b/xatrix/src/monster/flyer/flyer.h
new file mode 100644
index 0000000..1ff00ef
--- /dev/null
+++ b/xatrix/src/monster/flyer/flyer.h
@@ -0,0 +1,166 @@
+/* =======================================================================
+ *
+ * Flyer animations.
+ *
+ * =======================================================================
+ */
+
+#define ACTION_nothing 0
+#define ACTION_attack1 1
+#define ACTION_attack2 2
+#define ACTION_run 3
+#define ACTION_walk 4
+
+#define FRAME_start01 0
+#define FRAME_start02 1
+#define FRAME_start03 2
+#define FRAME_start04 3
+#define FRAME_start05 4
+#define FRAME_start06 5
+#define FRAME_stop01 6
+#define FRAME_stop02 7
+#define FRAME_stop03 8
+#define FRAME_stop04 9
+#define FRAME_stop05 10
+#define FRAME_stop06 11
+#define FRAME_stop07 12
+#define FRAME_stand01 13
+#define FRAME_stand02 14
+#define FRAME_stand03 15
+#define FRAME_stand04 16
+#define FRAME_stand05 17
+#define FRAME_stand06 18
+#define FRAME_stand07 19
+#define FRAME_stand08 20
+#define FRAME_stand09 21
+#define FRAME_stand10 22
+#define FRAME_stand11 23
+#define FRAME_stand12 24
+#define FRAME_stand13 25
+#define FRAME_stand14 26
+#define FRAME_stand15 27
+#define FRAME_stand16 28
+#define FRAME_stand17 29
+#define FRAME_stand18 30
+#define FRAME_stand19 31
+#define FRAME_stand20 32
+#define FRAME_stand21 33
+#define FRAME_stand22 34
+#define FRAME_stand23 35
+#define FRAME_stand24 36
+#define FRAME_stand25 37
+#define FRAME_stand26 38
+#define FRAME_stand27 39
+#define FRAME_stand28 40
+#define FRAME_stand29 41
+#define FRAME_stand30 42
+#define FRAME_stand31 43
+#define FRAME_stand32 44
+#define FRAME_stand33 45
+#define FRAME_stand34 46
+#define FRAME_stand35 47
+#define FRAME_stand36 48
+#define FRAME_stand37 49
+#define FRAME_stand38 50
+#define FRAME_stand39 51
+#define FRAME_stand40 52
+#define FRAME_stand41 53
+#define FRAME_stand42 54
+#define FRAME_stand43 55
+#define FRAME_stand44 56
+#define FRAME_stand45 57
+#define FRAME_attak101 58
+#define FRAME_attak102 59
+#define FRAME_attak103 60
+#define FRAME_attak104 61
+#define FRAME_attak105 62
+#define FRAME_attak106 63
+#define FRAME_attak107 64
+#define FRAME_attak108 65
+#define FRAME_attak109 66
+#define FRAME_attak110 67
+#define FRAME_attak111 68
+#define FRAME_attak112 69
+#define FRAME_attak113 70
+#define FRAME_attak114 71
+#define FRAME_attak115 72
+#define FRAME_attak116 73
+#define FRAME_attak117 74
+#define FRAME_attak118 75
+#define FRAME_attak119 76
+#define FRAME_attak120 77
+#define FRAME_attak121 78
+#define FRAME_attak201 79
+#define FRAME_attak202 80
+#define FRAME_attak203 81
+#define FRAME_attak204 82
+#define FRAME_attak205 83
+#define FRAME_attak206 84
+#define FRAME_attak207 85
+#define FRAME_attak208 86
+#define FRAME_attak209 87
+#define FRAME_attak210 88
+#define FRAME_attak211 89
+#define FRAME_attak212 90
+#define FRAME_attak213 91
+#define FRAME_attak214 92
+#define FRAME_attak215 93
+#define FRAME_attak216 94
+#define FRAME_attak217 95
+#define FRAME_bankl01 96
+#define FRAME_bankl02 97
+#define FRAME_bankl03 98
+#define FRAME_bankl04 99
+#define FRAME_bankl05 100
+#define FRAME_bankl06 101
+#define FRAME_bankl07 102
+#define FRAME_bankr01 103
+#define FRAME_bankr02 104
+#define FRAME_bankr03 105
+#define FRAME_bankr04 106
+#define FRAME_bankr05 107
+#define FRAME_bankr06 108
+#define FRAME_bankr07 109
+#define FRAME_rollf01 110
+#define FRAME_rollf02 111
+#define FRAME_rollf03 112
+#define FRAME_rollf04 113
+#define FRAME_rollf05 114
+#define FRAME_rollf06 115
+#define FRAME_rollf07 116
+#define FRAME_rollf08 117
+#define FRAME_rollf09 118
+#define FRAME_rollr01 119
+#define FRAME_rollr02 120
+#define FRAME_rollr03 121
+#define FRAME_rollr04 122
+#define FRAME_rollr05 123
+#define FRAME_rollr06 124
+#define FRAME_rollr07 125
+#define FRAME_rollr08 126
+#define FRAME_rollr09 127
+#define FRAME_defens01 128
+#define FRAME_defens02 129
+#define FRAME_defens03 130
+#define FRAME_defens04 131
+#define FRAME_defens05 132
+#define FRAME_defens06 133
+#define FRAME_pain101 134
+#define FRAME_pain102 135
+#define FRAME_pain103 136
+#define FRAME_pain104 137
+#define FRAME_pain105 138
+#define FRAME_pain106 139
+#define FRAME_pain107 140
+#define FRAME_pain108 141
+#define FRAME_pain109 142
+#define FRAME_pain201 143
+#define FRAME_pain202 144
+#define FRAME_pain203 145
+#define FRAME_pain204 146
+#define FRAME_pain301 147
+#define FRAME_pain302 148
+#define FRAME_pain303 149
+#define FRAME_pain304 150
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/gekk/gekk.c b/xatrix/src/monster/gekk/gekk.c
new file mode 100644
index 0000000..ee2e9f4
--- /dev/null
+++ b/xatrix/src/monster/gekk/gekk.c
@@ -0,0 +1,2012 @@
+/* =======================================================================
+ *
+ * Gekk (only found in Xatrix).
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "gekk.h"
+
+#define SPAWNFLAG_CHANT 8
+
+static int sound_swing;
+static int sound_hit;
+static int sound_hit2;
+static int sound_death;
+static int sound_pain1;
+static int sound_sight;
+static int sound_search;
+static int sound_step1;
+static int sound_step2;
+static int sound_step3;
+static int sound_thud;
+static int sound_chantlow;
+static int sound_chantmid;
+static int sound_chanthigh;
+
+mmove_t gekk_move_attack1;
+mmove_t gekk_move_attack2;
+mmove_t gekk_move_swim_start;
+mmove_t gekk_move_swim_loop;
+mmove_t gekk_move_spit;
+mmove_t gekk_move_run_start;
+mmove_t gekk_move_run;
+qboolean gekk_check_jump(edict_t *self);
+
+void gekk_swim(edict_t *self);
+void gekk_jump_takeoff(edict_t *self);
+void gekk_jump_takeoff2(edict_t *self);
+void gekk_check_landing(edict_t *self);
+void gekk_stop_skid(edict_t *self);
+void water_to_land(edict_t *self);
+void land_to_water(edict_t *self);
+void gekk_check_underwater(edict_t *self);
+void gekk_bite(edict_t *self);
+void gekk_hit_left(edict_t *self);
+void gekk_hit_right(edict_t *self);
+void gekk_run_start(edict_t *self);
+void gekk_walk(edict_t *self);
+
+qboolean
+gekk_check_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy || (self->enemy->health <= 0))
+ {
+ return false;
+ }
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+gekk_check_jump(edict_t *self)
+{
+ vec3_t v;
+ float distance;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2]))
+ {
+ return false;
+ }
+
+ if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2]))
+ {
+ return false;
+ }
+
+ v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+ v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+ v[2] = 0;
+ distance = VectorLength(v);
+
+ if (distance < 100)
+ {
+ return false;
+ }
+
+ if (distance > 100)
+ {
+ if (random() < 0.9)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+qboolean
+gekk_check_jump_close(edict_t *self)
+{
+ vec3_t v;
+ float distance;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+ v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+ v[2] = 0;
+
+ distance = VectorLength(v);
+
+ if (distance < 100)
+ {
+ if (self->s.origin[2] < self->enemy->s.origin[2])
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+qboolean
+gekk_checkattack(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy || (self->enemy->health <= 0))
+ {
+ return false;
+ }
+
+ if (gekk_check_melee(self))
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ return true;
+ }
+
+ if (gekk_check_jump(self))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ if (gekk_check_jump_close(self) && !self->waterlevel)
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ return false;
+}
+
+void
+gekk_step(edict_t *self)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ n = (rand() + 1) % 3;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0);
+ }
+ else if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+gekk_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+gekk_search(edict_t *self)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWNFLAG_CHANT)
+ {
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_chantlow, 1, ATTN_NORM, 0);
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_chantmid, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_chanthigh, 1, ATTN_NORM, 0);
+ }
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+ }
+
+ self->health += 10 + (10 * random());
+
+ if (self->health > self->max_health)
+ {
+ self->health = self->max_health;
+ }
+
+ if (self->health < (self->max_health / 4))
+ {
+ self->s.skinnum = 2;
+ }
+ else if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+ else
+ {
+ self->s.skinnum = 0;
+ }
+}
+
+void
+gekk_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0);
+}
+
+void
+gekk_face(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gekk_move_run;
+}
+
+void
+ai_stand2(edict_t *self, float dist)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWNFLAG_CHANT)
+ {
+ ai_move(self, dist);
+
+ if (!(self->spawnflags & 1) && (self->monsterinfo.idle) &&
+ (level.time > self->monsterinfo.idle_time))
+ {
+ if (self->monsterinfo.idle_time)
+ {
+ self->monsterinfo.idle(self);
+ self->monsterinfo.idle_time = level.time + 15 + random() * 15;
+ }
+ else
+ {
+ self->monsterinfo.idle_time = level.time + random() * 15;
+ }
+ }
+ }
+ else if (self->enemy)
+ {
+ ai_move(self, dist);
+ }
+ else
+ {
+ ai_stand(self, dist);
+ }
+}
+
+mframe_t gekk_frames_stand[] = {
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL}, /* 10 */
+
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL}, /* 20 */
+
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL}, /* 30 */
+
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+
+ {ai_stand2, 0, gekk_check_underwater},
+};
+
+mmove_t gekk_move_stand = {
+ FRAME_stand_01,
+ FRAME_stand_39,
+ gekk_frames_stand,
+ NULL
+};
+
+mframe_t gekk_frames_standunderwater[] = {
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+
+ {ai_stand2, 0, gekk_check_underwater}
+};
+
+mmove_t gekk_move_standunderwater = {
+ FRAME_amb_01,
+ FRAME_amb_04,
+ gekk_frames_standunderwater,
+ NULL
+};
+
+void
+gekk_swim_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags |= FL_SWIM;
+ self->monsterinfo.currentmove = &gekk_move_swim_loop;
+}
+
+mframe_t gekk_frames_swim[] = {
+ {ai_run, 16, NULL},
+ {ai_run, 16, NULL},
+ {ai_run, 16, NULL},
+
+ {ai_run, 16, gekk_swim}
+};
+
+mmove_t gekk_move_swim_loop = {
+ FRAME_amb_01,
+ FRAME_amb_04,
+ gekk_frames_swim,
+ gekk_swim_loop
+};
+
+mframe_t gekk_frames_swim_start[] = {
+ {ai_run, 14, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 16, NULL},
+ {ai_run, 16, NULL},
+ {ai_run, 16, NULL},
+ {ai_run, 18, NULL},
+ {ai_run, 18, gekk_hit_left},
+ {ai_run, 18, NULL},
+
+ {ai_run, 20, NULL},
+ {ai_run, 20, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 24, gekk_hit_right},
+ {ai_run, 24, NULL},
+ {ai_run, 26, NULL},
+ {ai_run, 26, NULL},
+ {ai_run, 24, NULL},
+ {ai_run, 24, NULL},
+
+ {ai_run, 22, gekk_bite},
+ {ai_run, 22, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 18, NULL},
+ {ai_run, 18, NULL},
+
+ {ai_run, 18, NULL},
+ {ai_run, 18, NULL}
+};
+
+mmove_t gekk_move_swim_start = {
+ FRAME_swim_01,
+ FRAME_swim_32,
+ gekk_frames_swim_start,
+ gekk_swim_loop
+};
+
+void
+gekk_swim(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ return;
+ }
+
+ if (!self->enemy->waterlevel && (random() > 0.7))
+ {
+ water_to_land(self);
+ }
+}
+
+void
+gekk_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ self->monsterinfo.currentmove = &gekk_move_standunderwater;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_stand;
+ }
+}
+
+void
+gekk_idle_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((random() > 0.75) && (self->health < self->max_health))
+ {
+ self->monsterinfo.nextframe = FRAME_idle_01;
+ }
+}
+
+mframe_t gekk_frames_idle[] = {
+ {ai_stand2, 0, gekk_search},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, NULL},
+
+ {ai_stand2, 0, NULL},
+ {ai_stand2, 0, gekk_idle_loop}
+};
+
+mmove_t gekk_move_idle = {
+ FRAME_idle_01,
+ FRAME_idle_32,
+ gekk_frames_idle,
+ gekk_stand
+};
+
+mmove_t gekk_move_idle2 = {
+ FRAME_idle_01,
+ FRAME_idle_32,
+ gekk_frames_idle,
+ gekk_face
+};
+
+void
+gekk_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->waterlevel)
+ {
+ self->monsterinfo.currentmove = &gekk_move_idle;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_swim_start;
+ }
+}
+
+mframe_t gekk_frames_walk[] = {
+ {ai_walk, 3.849, gekk_check_underwater}, /* frame 0 */
+ {ai_walk, 19.606, NULL}, /* frame 1 */
+ {ai_walk, 25.583, NULL}, /* frame 2 */
+ {ai_walk, 34.625, gekk_step}, /* frame 3 */
+ {ai_walk, 27.365, NULL}, /* frame 4 */
+ {ai_walk, 28.480, NULL}, /* frame 5 */
+};
+
+mmove_t gekk_move_walk = {
+ FRAME_run_01,
+ FRAME_run_06,
+ gekk_frames_walk,
+ NULL
+};
+
+void
+gekk_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gekk_move_walk;
+}
+
+void
+gekk_run_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ self->monsterinfo.currentmove = &gekk_move_swim_start;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_run_start;
+ }
+}
+
+void
+gekk_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ self->monsterinfo.currentmove = &gekk_move_swim_start;
+ return;
+ }
+ else
+ {
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &gekk_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_run;
+ }
+ }
+}
+
+mframe_t gekk_frames_run[] = {
+ {ai_run, 3.849, gekk_check_underwater}, /* frame 0 */
+ {ai_run, 19.606, NULL}, /* frame 1 */
+ {ai_run, 25.583, NULL}, /* frame 2 */
+ {ai_run, 34.625, gekk_step}, /* frame 3 */
+ {ai_run, 27.365, NULL}, /* frame 4 */
+ {ai_run, 28.480, NULL}, /* frame 5 */
+};
+
+mmove_t gekk_move_run = {
+ FRAME_run_01,
+ FRAME_run_06,
+ gekk_frames_run,
+ NULL
+};
+
+mframe_t gekk_frames_run_st[] = {
+ {ai_run, 0.212, NULL}, /* frame 0 */
+ {ai_run, 19.753, NULL}, /* frame 1 */
+};
+
+mmove_t gekk_move_run_start = {
+ FRAME_stand_01,
+ FRAME_stand_02,
+ gekk_frames_run_st,
+ gekk_run
+};
+
+void
+gekk_hit_left(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8);
+
+ if (fire_hit(self, aim, (15 + (rand() % 5)), 100))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+gekk_hit_right(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8);
+
+ if (fire_hit(self, aim, (15 + (rand() % 5)), 100))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+gekk_check_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse || (self->enemy->health <= 0))
+ {
+ return;
+ }
+
+ if (random() < (skill->value * 0.1))
+ {
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ if (self->s.frame == FRAME_clawatk3_09)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack2;
+ }
+ else if (self->s.frame == FRAME_clawatk5_09)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack1;
+ }
+ }
+ }
+}
+
+void
+loogie_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+ vec3_t normal;
+
+ if (!self || !other)
+ {
+ return;
+ }
+
+ if (other == self->owner)
+ {
+ return;
+ }
+
+ if (surf && (surf->flags & SURF_SKY))
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ if (self->owner->client)
+ {
+ PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+ }
+
+ if (other->takedamage)
+ {
+ get_normal_vector(plane, normal);
+
+ T_Damage(other, self, self->owner, self->velocity, self->s.origin,
+ normal, self->dmg, 1, DAMAGE_ENERGY,
+ MOD_GEKK);
+ }
+
+ G_FreeEdict(self);
+}
+
+void
+fire_loogie(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed)
+{
+ edict_t *loogie;
+ trace_t tr;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorNormalize(dir);
+
+ loogie = G_Spawn();
+ VectorCopy(start, loogie->s.origin);
+ VectorCopy(start, loogie->s.old_origin);
+ vectoangles(dir, loogie->s.angles);
+ VectorScale(dir, speed, loogie->velocity);
+ loogie->movetype = MOVETYPE_FLYMISSILE;
+ loogie->clipmask = MASK_SHOT;
+ loogie->solid = SOLID_BBOX;
+ loogie->s.effects |= RF_FULLBRIGHT;
+ VectorClear(loogie->mins);
+ VectorClear(loogie->maxs);
+
+ loogie->s.modelindex = gi.modelindex("models/objects/loogy/tris.md2");
+ loogie->owner = self;
+ loogie->touch = loogie_touch;
+ loogie->nextthink = level.time + 2;
+ loogie->think = G_FreeEdict;
+ loogie->dmg = damage;
+ gi.linkentity(loogie);
+
+ tr = gi.trace(self->s.origin, NULL, NULL, loogie->s.origin,
+ loogie, MASK_SHOT);
+
+ if (tr.fraction < 1.0)
+ {
+ VectorMA(loogie->s.origin, -10, dir, loogie->s.origin);
+ loogie->touch(loogie, tr.ent, NULL, NULL);
+ }
+}
+
+void
+loogie(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t end;
+ vec3_t dir;
+ vec3_t gekkoffset;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(gekkoffset, -18, -0.8, 24);
+
+ if (!self->enemy || (self->enemy->health <= 0))
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, up);
+ G_ProjectSource(self->s.origin, gekkoffset, forward, right, start);
+
+ VectorMA(start, 2, up, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ fire_loogie(self, start, dir, 5, 550);
+}
+
+void
+reloogie(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((random() > 0.8) && (self->health < self->max_health))
+ {
+ self->monsterinfo.currentmove = &gekk_move_idle2;
+ return;
+ }
+
+ if (self->enemy->health >= 0)
+ {
+ if ((random() > 0.7) && (range(self, self->enemy) == RANGE_NEAR))
+ {
+ self->monsterinfo.currentmove = &gekk_move_spit;
+ }
+ }
+}
+
+mframe_t gekk_frames_spit[] = {
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, NULL},
+
+ {ai_charge, 0.000, loogie},
+ {ai_charge, 0.000, reloogie}
+};
+
+mmove_t gekk_move_spit = {
+ FRAME_spit_01,
+ FRAME_spit_07,
+ gekk_frames_spit,
+ gekk_run_start
+};
+
+mframe_t gekk_frames_attack1[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+
+ {ai_charge, 0, gekk_hit_left},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gekk_check_refire}
+};
+
+mmove_t gekk_move_attack1 = {
+ FRAME_clawatk3_01,
+ FRAME_clawatk3_09,
+ gekk_frames_attack1,
+ gekk_run_start
+};
+
+mframe_t gekk_frames_attack2[] = {
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, gekk_hit_left},
+
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, gekk_hit_right},
+
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, NULL},
+ {ai_charge, 0.000, gekk_check_refire}
+};
+
+mmove_t gekk_move_attack2 = {
+ FRAME_clawatk5_01,
+ FRAME_clawatk5_09,
+ gekk_frames_attack2,
+ gekk_run_start
+};
+
+void
+gekk_check_underwater(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ land_to_water(self);
+ }
+}
+
+mframe_t gekk_frames_leapatk[] = {
+ {ai_charge, 0.000, NULL}, /* frame 0 */
+ {ai_charge, -0.387, NULL}, /* frame 1 */
+ {ai_charge, -1.113, NULL}, /* frame 2 */
+ {ai_charge, -0.237, NULL}, /* frame 3 */
+ {ai_charge, 6.720, gekk_jump_takeoff}, /* frame 4 last frame on ground */
+ {ai_charge, 6.414, NULL}, /* frame 5 leaves ground */
+ {ai_charge, 0.163, NULL}, /* frame 6 */
+ {ai_charge, 28.316, NULL}, /* frame 7 */
+ {ai_charge, 24.198, NULL}, /* frame 8 */
+ {ai_charge, 31.742, NULL}, /* frame 9 */
+ {ai_charge, 35.977, gekk_check_landing}, /* frame 10 last frame in air */
+ {ai_charge, 12.303, gekk_stop_skid}, /* frame 11 feet back on ground */
+ {ai_charge, 20.122, gekk_stop_skid}, /* frame 12 */
+ {ai_charge, -1.042, gekk_stop_skid}, /* frame 13 */
+ {ai_charge, 2.556, gekk_stop_skid}, /* frame 14 */
+ {ai_charge, 0.544, gekk_stop_skid}, /* frame 15 */
+ {ai_charge, 1.862, gekk_stop_skid}, /* frame 16 */
+ {ai_charge, 1.224, gekk_stop_skid}, /* frame 17 */
+
+ {ai_charge, -0.457, gekk_check_underwater}, /* frame 18 */
+};
+
+mmove_t gekk_move_leapatk = {
+ FRAME_leapatk_01,
+ FRAME_leapatk_19,
+ gekk_frames_leapatk,
+ gekk_run_start
+};
+
+mframe_t gekk_frames_leapatk2[] = {
+ {ai_charge, 0.000, NULL}, /* frame 0 */
+ {ai_charge, -0.387, NULL}, /* frame 1 */
+ {ai_charge, -1.113, NULL}, /* frame 2 */
+ {ai_charge, -0.237, NULL}, /* frame 3 */
+ {ai_charge, 6.720, gekk_jump_takeoff2}, /* frame 4 last frame on ground */
+ {ai_charge, 6.414, NULL}, /* frame 5 leaves ground */
+ {ai_charge, 0.163, NULL}, /* frame 6 */
+ {ai_charge, 28.316, NULL}, /* frame 7 */
+ {ai_charge, 24.198, NULL}, /* frame 8 */
+ {ai_charge, 31.742, NULL}, /* frame 9 */
+ {ai_charge, 35.977, gekk_check_landing}, /* frame 10 last frame in air */
+ {ai_charge, 12.303, gekk_stop_skid}, /* frame 11 feet back on ground */
+ {ai_charge, 20.122, gekk_stop_skid}, /* frame 12 */
+ {ai_charge, -1.042, gekk_stop_skid}, /* frame 13 */
+ {ai_charge, 2.556, gekk_stop_skid}, /* frame 14 */
+ {ai_charge, 0.544, gekk_stop_skid}, /* frame 15 */
+ {ai_charge, 1.862, gekk_stop_skid}, /* frame 16 */
+ {ai_charge, 1.224, gekk_stop_skid}, /* frame 17 */
+
+ {ai_charge, -0.457, gekk_check_underwater}, /* frame 18 */
+};
+
+mmove_t gekk_move_leapatk2 = {
+ FRAME_leapatk_01,
+ FRAME_leapatk_19,
+ gekk_frames_leapatk2,
+ gekk_run_start
+};
+
+void
+gekk_bite(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 0);
+ fire_hit(self, aim, 5, 0);
+}
+
+void
+gekk_preattack(edict_t *self)
+{
+ /* Unused but PITA to remove */
+}
+
+mframe_t gekk_frames_attack[] = {
+ {ai_charge, 16, gekk_preattack},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, gekk_bite},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, gekk_bite},
+
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, gekk_hit_left},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, NULL},
+ {ai_charge, 16, gekk_hit_right},
+ {ai_charge, 16, NULL},
+
+ {ai_charge, 16, NULL}
+};
+
+mmove_t gekk_move_attack = {
+ FRAME_attack_01,
+ FRAME_attack_21,
+ gekk_frames_attack,
+ gekk_run_start
+};
+
+void
+gekk_melee(edict_t *self)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->waterlevel)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack;
+ }
+ else
+ {
+ r = random();
+
+ if (r > 0.66)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack2;
+ }
+ }
+}
+
+void
+gekk_jump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unsued */,
+ csurface_t *surf /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= 0)
+ {
+ self->touch = NULL;
+ return;
+ }
+
+ if (other && other->takedamage)
+ {
+ if (VectorLength(self->velocity) > 200)
+ {
+ vec3_t point;
+ vec3_t normal;
+ int damage;
+
+ VectorCopy(self->velocity, normal);
+ VectorNormalize(normal);
+ VectorMA(self->s.origin, self->maxs[0], normal, point);
+ damage = 10 + 10 * random();
+ T_Damage(other, self, self, self->velocity, point,
+ normal, damage, damage, 0, MOD_GEKK);
+ }
+ }
+
+ if (!M_CheckBottom(self))
+ {
+ if (self->groundentity)
+ {
+ self->monsterinfo.nextframe = FRAME_leapatk_11;
+ self->monsterinfo.aiflags &= ~AI_IGNORE_PAIN;
+ self->touch = NULL;
+ }
+
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_IGNORE_PAIN;
+ self->touch = NULL;
+}
+
+void
+gekk_jump_takeoff(edict_t *self)
+{
+ vec3_t forward;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ self->s.origin[2] += 1;
+
+ /* high jump */
+ if (gekk_check_jump(self))
+ {
+ VectorScale(forward, 700, self->velocity);
+ self->velocity[2] = 250;
+ }
+ else
+ {
+ VectorScale(forward, 250, self->velocity);
+ self->velocity[2] = 400;
+ }
+
+ self->groundentity = NULL;
+ self->monsterinfo.aiflags |= AI_IGNORE_PAIN;
+ self->monsterinfo.attack_finished = level.time + 3;
+ self->touch = gekk_jump_touch;
+}
+
+void
+gekk_jump_takeoff2(edict_t *self)
+{
+ vec3_t forward;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ self->s.origin[2] = self->enemy->s.origin[2];
+
+ if (gekk_check_jump(self))
+ {
+ VectorScale(forward, 300, self->velocity);
+ self->velocity[2] = 250;
+ }
+ else
+ {
+ VectorScale(forward, 150, self->velocity);
+ self->velocity[2] = 300;
+ }
+
+ self->groundentity = NULL;
+ self->monsterinfo.aiflags |= AI_IGNORE_PAIN;
+ self->monsterinfo.attack_finished = level.time + 3;
+ self->touch = gekk_jump_touch;
+}
+
+void
+gekk_stop_skid(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity)
+ {
+ VectorClear(self->velocity);
+ }
+}
+
+void
+gekk_check_landing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
+ self->monsterinfo.attack_finished = 0;
+ self->monsterinfo.aiflags &= ~AI_IGNORE_PAIN;
+
+ VectorClear(self->velocity);
+
+ return;
+ }
+
+ if (level.time > self->monsterinfo.attack_finished)
+ {
+ self->monsterinfo.nextframe = FRAME_leapatk_11;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_leapatk_12;
+ }
+}
+
+void
+gekk_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->flags & FL_SWIM || self->waterlevel)
+ {
+ return;
+ }
+ else
+ {
+ {
+ if ((random() > 0.5) && (range(self, self->enemy) >= RANGE_NEAR))
+ {
+ self->monsterinfo.currentmove = &gekk_move_spit;
+ }
+ else if (random() > 0.8)
+ {
+ self->monsterinfo.currentmove = &gekk_move_spit;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_leapatk;
+ }
+ }
+ }
+}
+
+mframe_t gekk_frames_pain[] = {
+ {ai_move, 0.000, NULL}, /* frame 0 */
+ {ai_move, 0.000, NULL}, /* frame 1 */
+ {ai_move, 0.000, NULL}, /* frame 2 */
+ {ai_move, 0.000, NULL}, /* frame 3 */
+ {ai_move, 0.000, NULL}, /* frame 4 */
+ {ai_move, 0.000, NULL}, /* frame 5 */
+};
+
+mmove_t gekk_move_pain = {
+ FRAME_pain_01,
+ FRAME_pain_06,
+ gekk_frames_pain,
+ gekk_run_start
+};
+
+mframe_t gekk_frames_pain1[] = {
+ {ai_move, 0.000, NULL}, /* frame 0 */
+ {ai_move, 0.000, NULL}, /* frame 1 */
+ {ai_move, 0.000, NULL}, /* frame 2 */
+ {ai_move, 0.000, NULL}, /* frame 3 */
+ {ai_move, 0.000, NULL}, /* frame 4 */
+ {ai_move, 0.000, NULL}, /* frame 5 */
+ {ai_move, 0.000, NULL}, /* frame 6 */
+ {ai_move, 0.000, NULL}, /* frame 7 */
+ {ai_move, 0.000, NULL}, /* frame 8 */
+ {ai_move, 0.000, NULL}, /* frame 9 */
+
+ {ai_move, 0.000, gekk_check_underwater}
+};
+
+mmove_t gekk_move_pain1 = {
+ FRAME_pain3_01,
+ FRAME_pain3_11,
+ gekk_frames_pain1,
+ gekk_run_start
+};
+
+mframe_t gekk_frames_pain2[] = {
+ {ai_move, 0.000, NULL}, /* frame 0 */
+ {ai_move, 0.000, NULL}, /* frame 1 */
+ {ai_move, 0.000, NULL}, /* frame 2 */
+ {ai_move, 0.000, NULL}, /* frame 3 */
+ {ai_move, 0.000, NULL}, /* frame 4 */
+ {ai_move, 0.000, NULL}, /* frame 5 */
+ {ai_move, 0.000, NULL}, /* frame 6 */
+ {ai_move, 0.000, NULL}, /* frame 7 */
+ {ai_move, 0.000, NULL}, /* frame 8 */
+ {ai_move, 0.000, NULL}, /* frame 9 */
+
+ {ai_move, 0.000, NULL}, /* frame 10 */
+ {ai_move, 0.000, NULL}, /* frame 11 */
+ {ai_move, 0.000, gekk_check_underwater},
+};
+
+mmove_t gekk_move_pain2 = {
+ FRAME_pain4_01,
+ FRAME_pain4_13,
+ gekk_frames_pain2,
+ gekk_run_start
+};
+
+void
+gekk_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWNFLAG_CHANT)
+ {
+ self->spawnflags &= ~SPAWNFLAG_CHANT;
+ return;
+ }
+
+ if (self->health < (self->max_health / 4))
+ {
+ self->s.skinnum = 2;
+ }
+ else if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+
+ if (self->waterlevel)
+ {
+ if ((!self->flags) & FL_SWIM)
+ {
+ self->flags |= FL_SWIM;
+ }
+
+ self->monsterinfo.currentmove = &gekk_move_pain;
+ }
+ else
+ {
+ r = random();
+
+ if (r > 0.5)
+ {
+ self->monsterinfo.currentmove = &gekk_move_pain1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_pain2;
+ }
+ }
+}
+
+void
+gekk_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* fix this because of no blocking problem */
+ if (self->waterlevel)
+ {
+ return;
+ }
+ else
+ {
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+ }
+}
+
+void
+gekk_gibfest(edict_t *self)
+{
+ int damage = 20;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ ThrowGibACID(self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+
+ ThrowHeadACID(self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC);
+
+ self->deadflag = DEAD_DEAD;
+}
+
+void
+isgibfest(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.9)
+ {
+ gekk_gibfest(self);
+ }
+}
+
+mframe_t gekk_frames_death1[] = {
+ {ai_move, -5.151, NULL}, /* frame 0 */
+ {ai_move, -12.223, NULL}, /* frame 1 */
+ {ai_move, -11.484, NULL}, /* frame 2 */
+ {ai_move, -17.952, NULL}, /* frame 3 */
+ {ai_move, -6.953, NULL}, /* frame 4 */
+ {ai_move, -7.393, NULL}, /* frame 5 */
+ {ai_move, -10.713, NULL}, /* frame 6 */
+ {ai_move, -17.464, NULL}, /* frame 7 */
+ {ai_move, -11.678, NULL}, /* frame 8 */
+ {ai_move, -11.678, NULL} /* frame 9 */
+};
+
+mmove_t gekk_move_death1 = {
+ FRAME_death1_01,
+ FRAME_death1_10,
+ gekk_frames_death1,
+ gekk_dead
+};
+
+mframe_t gekk_frames_death3[] = {
+ {ai_move, 0.000, NULL}, /* frame 0 */
+ {ai_move, 0.022, NULL}, /* frame 1 */
+ {ai_move, 0.169, NULL}, /* frame 2 */
+ {ai_move, -0.710, NULL}, /* frame 3 */
+ {ai_move, -13.446, NULL}, /* frame 4 */
+ {ai_move, -7.654, isgibfest}, /* frame 5 */
+ {ai_move, -31.951, NULL}, /* frame 6 */
+};
+
+mmove_t gekk_move_death3 = {
+ FRAME_death3_01,
+ FRAME_death3_07,
+ gekk_frames_death3,
+ gekk_dead
+};
+
+mframe_t gekk_frames_death4[] = {
+ {ai_move, 5.103, NULL}, /* frame 0 */
+ {ai_move, -4.808, NULL}, /* frame 1 */
+ {ai_move, -10.509, NULL}, /* frame 2 */
+ {ai_move, -9.899, NULL}, /* frame 3 */
+ {ai_move, 4.033, isgibfest}, /* frame 4 */
+ {ai_move, -5.197, NULL}, /* frame 5 */
+ {ai_move, -0.919, NULL}, /* frame 6 */
+ {ai_move, -8.821, NULL}, /* frame 7 */
+ {ai_move, -5.626, NULL}, /* frame 8 */
+ {ai_move, -8.865, isgibfest}, /* frame 9 */
+ {ai_move, -0.845, NULL}, /* frame 10 */
+ {ai_move, 1.986, NULL}, /* frame 11 */
+ {ai_move, 0.170, NULL}, /* frame 12 */
+ {ai_move, 1.339, isgibfest}, /* frame 13 */
+ {ai_move, -0.922, NULL}, /* frame 14 */
+ {ai_move, 0.818, NULL}, /* frame 15 */
+ {ai_move, -1.288, NULL}, /* frame 16 */
+ {ai_move, -1.408, isgibfest}, /* frame 17 */
+ {ai_move, -7.787, NULL}, /* frame 18 */
+ {ai_move, -3.995, NULL}, /* frame 19 */
+ {ai_move, -4.604, NULL}, /* frame 20 */
+ {ai_move, -1.715, isgibfest}, /* frame 21 */
+ {ai_move, -0.564, NULL}, /* frame 22 */
+ {ai_move, -0.597, NULL}, /* frame 23 */
+ {ai_move, 0.074, NULL}, /* frame 24 */
+ {ai_move, -0.309, isgibfest}, /* frame 25 */
+ {ai_move, -0.395, NULL}, /* frame 26 */
+ {ai_move, -0.501, NULL}, /* frame 27 */
+ {ai_move, -0.325, NULL}, /* frame 28 */
+ {ai_move, -0.931, isgibfest}, /* frame 29 */
+ {ai_move, -1.433, NULL}, /* frame 30 */
+ {ai_move, -1.626, NULL}, /* frame 31 */
+ {ai_move, 4.680, NULL}, /* frame 32 */
+ {ai_move, 0.560, NULL}, /* frame 33 */
+ {ai_move, -0.549, gekk_gibfest} /* frame 34 */
+};
+
+mmove_t gekk_move_death4 = {
+ FRAME_death4_01,
+ FRAME_death4_35,
+ gekk_frames_death4,
+ gekk_dead
+};
+
+mframe_t gekk_frames_wdeath[] = {
+ {ai_move, 0.000, NULL}, /* frame 0 */
+ {ai_move, 0.000, NULL}, /* frame 1 */
+ {ai_move, 0.000, NULL}, /* frame 2 */
+ {ai_move, 0.000, NULL}, /* frame 3 */
+ {ai_move, 0.000, NULL}, /* frame 4 */
+ {ai_move, 0.000, NULL}, /* frame 5 */
+ {ai_move, 0.000, NULL}, /* frame 6 */
+ {ai_move, 0.000, NULL}, /* frame 7 */
+ {ai_move, 0.000, NULL}, /* frame 8 */
+ {ai_move, 0.000, NULL}, /* frame 9 */
+ {ai_move, 0.000, NULL}, /* frame 10 */
+ {ai_move, 0.000, NULL}, /* frame 11 */
+ {ai_move, 0.000, NULL}, /* frame 12 */
+ {ai_move, 0.000, NULL}, /* frame 13 */
+ {ai_move, 0.000, NULL}, /* frame 14 */
+ {ai_move, 0.000, NULL}, /* frame 15 */
+ {ai_move, 0.000, NULL}, /* frame 16 */
+ {ai_move, 0.000, NULL}, /* frame 17 */
+ {ai_move, 0.000, NULL}, /* frame 18 */
+ {ai_move, 0.000, NULL}, /* frame 19 */
+ {ai_move, 0.000, NULL}, /* frame 20 */
+ {ai_move, 0.000, NULL}, /* frame 21 */
+ {ai_move, 0.000, NULL}, /* frame 22 */
+ {ai_move, 0.000, NULL}, /* frame 23 */
+ {ai_move, 0.000, NULL}, /* frame 24 */
+ {ai_move, 0.000, NULL}, /* frame 25 */
+ {ai_move, 0.000, NULL}, /* frame 26 */
+ {ai_move, 0.000, NULL}, /* frame 27 */
+ {ai_move, 0.000, NULL}, /* frame 28 */
+ {ai_move, 0.000, NULL}, /* frame 29 */
+ {ai_move, 0.000, NULL}, /* frame 30 */
+ {ai_move, 0.000, NULL}, /* frame 31 */
+ {ai_move, 0.000, NULL}, /* frame 32 */
+ {ai_move, 0.000, NULL}, /* frame 33 */
+ {ai_move, 0.000, NULL}, /* frame 34 */
+ {ai_move, 0.000, NULL}, /* frame 35 */
+ {ai_move, 0.000, NULL}, /* frame 36 */
+ {ai_move, 0.000, NULL}, /* frame 37 */
+ {ai_move, 0.000, NULL}, /* frame 38 */
+ {ai_move, 0.000, NULL}, /* frame 39 */
+ {ai_move, 0.000, NULL}, /* frame 40 */
+ {ai_move, 0.000, NULL}, /* frame 41 */
+ {ai_move, 0.000, NULL}, /* frame 42 */
+ {ai_move, 0.000, NULL}, /* frame 43 */
+ {ai_move, 0.000, NULL} /* frame 44 */
+};
+
+mmove_t gekk_move_wdeath = {
+ FRAME_wdeath_01,
+ FRAME_wdeath_45,
+ gekk_frames_wdeath,
+ gekk_dead
+};
+
+void
+gekk_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */,
+ int damage, vec3_t point /* unused */)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ ThrowGibACID(self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+ ThrowGibACID(self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+
+ ThrowHeadACID(self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC);
+
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum = 2;
+
+ if (self->waterlevel)
+ {
+ self->monsterinfo.currentmove = &gekk_move_wdeath;
+ }
+ else
+ {
+ r = random();
+
+ if (r > 0.66)
+ {
+ self->monsterinfo.currentmove = &gekk_move_death1;
+ }
+ else if (r > 0.33)
+ {
+ self->monsterinfo.currentmove = &gekk_move_death3;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_death4;
+ }
+ }
+}
+
+void
+gekk_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.pausetime = level.time + 1;
+ gi.linkentity(self);
+}
+
+void
+gekk_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+void
+gekk_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+mframe_t gekk_frames_lduck[] = {
+ {ai_move, 0.000, NULL}, /* frame 0 */
+ {ai_move, 0.000, NULL}, /* frame 1 */
+ {ai_move, 0.000, NULL}, /* frame 2 */
+ {ai_move, 0.000, NULL}, /* frame 3 */
+ {ai_move, 0.000, NULL}, /* frame 4 */
+ {ai_move, 0.000, NULL}, /* frame 5 */
+ {ai_move, 0.000, NULL}, /* frame 6 */
+ {ai_move, 0.000, NULL}, /* frame 7 */
+ {ai_move, 0.000, NULL}, /* frame 8 */
+ {ai_move, 0.000, NULL}, /* frame 9 */
+ {ai_move, 0.000, NULL}, /* frame 10 */
+ {ai_move, 0.000, NULL}, /* frame 11 */
+ {ai_move, 0.000, NULL} /* frame 12 */
+};
+
+mmove_t gekk_move_lduck = {
+ FRAME_lduck_01,
+ FRAME_lduck_13,
+ gekk_frames_lduck,
+ gekk_run_start
+};
+
+mframe_t gekk_frames_rduck[] = {
+ {ai_move, 0.000, NULL}, /* frame 0 */
+ {ai_move, 0.000, NULL}, /* frame 1 */
+ {ai_move, 0.000, NULL}, /* frame 2 */
+ {ai_move, 0.000, NULL}, /* frame 3 */
+ {ai_move, 0.000, NULL}, /* frame 4 */
+ {ai_move, 0.000, NULL}, /* frame 5 */
+ {ai_move, 0.000, NULL}, /* frame 6 */
+ {ai_move, 0.000, NULL}, /* frame 7 */
+ {ai_move, 0.000, NULL}, /* frame 8 */
+ {ai_move, 0.000, NULL}, /* frame 9 */
+ {ai_move, 0.000, NULL}, /* frame 10 */
+ {ai_move, 0.000, NULL}, /* frame 11 */
+ {ai_move, 0.000, NULL} /* frame 12 */
+};
+
+mmove_t gekk_move_rduck = {
+ FRAME_rduck_01,
+ FRAME_rduck_13,
+ gekk_frames_rduck,
+ gekk_run_start
+};
+
+void
+gekk_dodge(edict_t *self, edict_t *attacker, float eta)
+{
+ float r;
+
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ if (self->waterlevel)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack;
+ return;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ r = random();
+
+ if (r > 0.5)
+ {
+ self->monsterinfo.currentmove = &gekk_move_lduck;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_rduck;
+ }
+
+ return;
+ }
+
+ self->monsterinfo.pausetime = level.time + eta + 0.3;
+ r = random();
+
+ if (skill->value == SKILL_MEDIUM)
+ {
+ if (r > 0.33)
+ {
+ r = random();
+
+ if (r > 0.5)
+ {
+ self->monsterinfo.currentmove = &gekk_move_lduck;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_rduck;
+ }
+ }
+ else
+ {
+ r = random();
+
+ if (r > 0.66)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack2;
+ }
+ }
+
+ return;
+ }
+
+ if (skill->value == SKILL_HARD)
+ {
+ if (r > 0.66)
+ {
+ r = random();
+
+ if (r > 0.5)
+ {
+ self->monsterinfo.currentmove = &gekk_move_lduck;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_rduck;
+ }
+ }
+ else
+ {
+ r = random();
+
+ if (r > 0.66)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack2;
+ }
+ }
+
+ return;
+ }
+
+ r = random();
+
+ if (r > 0.66)
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gekk_move_attack2;
+ }
+}
+
+/*
+ * QUAKED monster_gekk (1 .5 0) (-24 -24 -24) (24 24 24) Ambush Trigger_Spawn Sight Chant
+ */
+void
+SP_monster_gekk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_swing = gi.soundindex("gek/gk_atck1.wav");
+ sound_hit = gi.soundindex("gek/gk_atck2.wav");
+ sound_hit2 = gi.soundindex("gek/gk_atck3.wav");
+ sound_death = gi.soundindex("gek/gk_deth1.wav");
+ sound_pain1 = gi.soundindex("gek/gk_pain1.wav");
+ sound_sight = gi.soundindex("gek/gk_sght1.wav");
+ sound_search = gi.soundindex("gek/gk_idle1.wav");
+ sound_step1 = gi.soundindex("gek/gk_step1.wav");
+ sound_step2 = gi.soundindex("gek/gk_step2.wav");
+ sound_step3 = gi.soundindex("gek/gk_step3.wav");
+ sound_thud = gi.soundindex("mutant/thud1.wav");
+
+ sound_chantlow = gi.soundindex("gek/gek_low.wav");
+ sound_chantmid = gi.soundindex("gek/gek_mid.wav");
+ sound_chanthigh = gi.soundindex("gek/gek_high.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/gekk/tris.md2");
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 24);
+
+ gi.modelindex("models/objects/gekkgib/pelvis/tris.md2");
+ gi.modelindex("models/objects/gekkgib/arm/tris.md2");
+ gi.modelindex("models/objects/gekkgib/torso/tris.md2");
+ gi.modelindex("models/objects/gekkgib/claw/tris.md2");
+ gi.modelindex("models/objects/gekkgib/leg/tris.md2");
+ gi.modelindex("models/objects/gekkgib/head/tris.md2");
+
+ self->health = 125;
+ self->gib_health = -30;
+ self->mass = 300;
+
+ self->pain = gekk_pain;
+ self->die = gekk_die;
+
+ self->monsterinfo.stand = gekk_stand;
+
+ self->monsterinfo.walk = gekk_walk;
+ self->monsterinfo.run = gekk_run_start;
+ self->monsterinfo.dodge = gekk_dodge;
+ self->monsterinfo.attack = gekk_jump;
+ self->monsterinfo.melee = gekk_melee;
+ self->monsterinfo.sight = gekk_sight;
+
+ self->monsterinfo.search = gekk_search;
+ self->monsterinfo.idle = gekk_idle;
+ self->monsterinfo.checkattack = gekk_checkattack;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &gekk_move_stand;
+
+ self->monsterinfo.scale = MODEL_SCALE;
+ walkmonster_start(self);
+}
+
+void
+water_to_land(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags &= ~FL_SWIM;
+ self->yaw_speed = 20;
+ self->viewheight = 25;
+
+ self->monsterinfo.currentmove = &gekk_move_leapatk2;
+
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 24);
+}
+
+void
+land_to_water(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->flags |= FL_SWIM;
+ self->yaw_speed = 10;
+ self->viewheight = 10;
+
+ self->monsterinfo.currentmove = &gekk_move_swim_start;
+
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 16);
+}
diff --git a/xatrix/src/monster/gekk/gekk.h b/xatrix/src/monster/gekk/gekk.h
new file mode 100644
index 0000000..00ea792
--- /dev/null
+++ b/xatrix/src/monster/gekk/gekk.h
@@ -0,0 +1,359 @@
+/* =======================================================================
+ *
+ * Gekk animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand_01 0
+#define FRAME_stand_02 1
+#define FRAME_stand_03 2
+#define FRAME_stand_04 3
+#define FRAME_stand_05 4
+#define FRAME_stand_06 5
+#define FRAME_stand_07 6
+#define FRAME_stand_08 7
+#define FRAME_stand_09 8
+#define FRAME_stand_10 9
+#define FRAME_stand_11 10
+#define FRAME_stand_12 11
+#define FRAME_stand_13 12
+#define FRAME_stand_14 13
+#define FRAME_stand_15 14
+#define FRAME_stand_16 15
+#define FRAME_stand_17 16
+#define FRAME_stand_18 17
+#define FRAME_stand_19 18
+#define FRAME_stand_20 19
+#define FRAME_stand_21 20
+#define FRAME_stand_22 21
+#define FRAME_stand_23 22
+#define FRAME_stand_24 23
+#define FRAME_stand_25 24
+#define FRAME_stand_26 25
+#define FRAME_stand_27 26
+#define FRAME_stand_28 27
+#define FRAME_stand_29 28
+#define FRAME_stand_30 29
+#define FRAME_stand_31 30
+#define FRAME_stand_32 31
+#define FRAME_stand_33 32
+#define FRAME_stand_34 33
+#define FRAME_stand_35 34
+#define FRAME_stand_36 35
+#define FRAME_stand_37 36
+#define FRAME_stand_38 37
+#define FRAME_stand_39 38
+#define FRAME_run_01 39
+#define FRAME_run_02 40
+#define FRAME_run_03 41
+#define FRAME_run_04 42
+#define FRAME_run_05 43
+#define FRAME_run_06 44
+#define FRAME_clawatk3_01 45
+#define FRAME_clawatk3_02 46
+#define FRAME_clawatk3_03 47
+#define FRAME_clawatk3_04 48
+#define FRAME_clawatk3_05 49
+#define FRAME_clawatk3_06 50
+#define FRAME_clawatk3_07 51
+#define FRAME_clawatk3_08 52
+#define FRAME_clawatk3_09 53
+#define FRAME_clawatk4_01 54
+#define FRAME_clawatk4_02 55
+#define FRAME_clawatk4_03 56
+#define FRAME_clawatk4_04 57
+#define FRAME_clawatk4_05 58
+#define FRAME_clawatk4_06 59
+#define FRAME_clawatk4_07 60
+#define FRAME_clawatk4_08 61
+#define FRAME_clawatk5_01 62
+#define FRAME_clawatk5_02 63
+#define FRAME_clawatk5_03 64
+#define FRAME_clawatk5_04 65
+#define FRAME_clawatk5_05 66
+#define FRAME_clawatk5_06 67
+#define FRAME_clawatk5_07 68
+#define FRAME_clawatk5_08 69
+#define FRAME_clawatk5_09 70
+#define FRAME_leapatk_01 71
+#define FRAME_leapatk_02 72
+#define FRAME_leapatk_03 73
+#define FRAME_leapatk_04 74
+#define FRAME_leapatk_05 75
+#define FRAME_leapatk_06 76
+#define FRAME_leapatk_07 77
+#define FRAME_leapatk_08 78
+#define FRAME_leapatk_09 79
+#define FRAME_leapatk_10 80
+#define FRAME_leapatk_11 81
+#define FRAME_leapatk_12 82
+#define FRAME_leapatk_13 83
+#define FRAME_leapatk_14 84
+#define FRAME_leapatk_15 85
+#define FRAME_leapatk_16 86
+#define FRAME_leapatk_17 87
+#define FRAME_leapatk_18 88
+#define FRAME_leapatk_19 89
+#define FRAME_pain3_01 90
+#define FRAME_pain3_02 91
+#define FRAME_pain3_03 92
+#define FRAME_pain3_04 93
+#define FRAME_pain3_05 94
+#define FRAME_pain3_06 95
+#define FRAME_pain3_07 96
+#define FRAME_pain3_08 97
+#define FRAME_pain3_09 98
+#define FRAME_pain3_10 99
+#define FRAME_pain3_11 100
+#define FRAME_pain4_01 101
+#define FRAME_pain4_02 102
+#define FRAME_pain4_03 103
+#define FRAME_pain4_04 104
+#define FRAME_pain4_05 105
+#define FRAME_pain4_06 106
+#define FRAME_pain4_07 107
+#define FRAME_pain4_08 108
+#define FRAME_pain4_09 109
+#define FRAME_pain4_10 110
+#define FRAME_pain4_11 111
+#define FRAME_pain4_12 112
+#define FRAME_pain4_13 113
+#define FRAME_death1_01 114
+#define FRAME_death1_02 115
+#define FRAME_death1_03 116
+#define FRAME_death1_04 117
+#define FRAME_death1_05 118
+#define FRAME_death1_06 119
+#define FRAME_death1_07 120
+#define FRAME_death1_08 121
+#define FRAME_death1_09 122
+#define FRAME_death1_10 123
+#define FRAME_death2_01 124
+#define FRAME_death2_02 125
+#define FRAME_death2_03 126
+#define FRAME_death2_04 127
+#define FRAME_death2_05 128
+#define FRAME_death2_06 129
+#define FRAME_death2_07 130
+#define FRAME_death2_08 131
+#define FRAME_death2_09 132
+#define FRAME_death2_10 133
+#define FRAME_death2_11 134
+#define FRAME_death3_01 135
+#define FRAME_death3_02 136
+#define FRAME_death3_03 137
+#define FRAME_death3_04 138
+#define FRAME_death3_05 139
+#define FRAME_death3_06 140
+#define FRAME_death3_07 141
+#define FRAME_death4_01 142
+#define FRAME_death4_02 143
+#define FRAME_death4_03 144
+#define FRAME_death4_04 145
+#define FRAME_death4_05 146
+#define FRAME_death4_06 147
+#define FRAME_death4_07 148
+#define FRAME_death4_08 149
+#define FRAME_death4_09 150
+#define FRAME_death4_10 151
+#define FRAME_death4_11 152
+#define FRAME_death4_12 153
+#define FRAME_death4_13 154
+#define FRAME_death4_14 155
+#define FRAME_death4_15 156
+#define FRAME_death4_16 157
+#define FRAME_death4_17 158
+#define FRAME_death4_18 159
+#define FRAME_death4_19 160
+#define FRAME_death4_20 161
+#define FRAME_death4_21 162
+#define FRAME_death4_22 163
+#define FRAME_death4_23 164
+#define FRAME_death4_24 165
+#define FRAME_death4_25 166
+#define FRAME_death4_26 167
+#define FRAME_death4_27 168
+#define FRAME_death4_28 169
+#define FRAME_death4_29 170
+#define FRAME_death4_30 171
+#define FRAME_death4_31 172
+#define FRAME_death4_32 173
+#define FRAME_death4_33 174
+#define FRAME_death4_34 175
+#define FRAME_death4_35 176
+#define FRAME_rduck_01 177
+#define FRAME_rduck_02 178
+#define FRAME_rduck_03 179
+#define FRAME_rduck_04 180
+#define FRAME_rduck_05 181
+#define FRAME_rduck_06 182
+#define FRAME_rduck_07 183
+#define FRAME_rduck_08 184
+#define FRAME_rduck_09 185
+#define FRAME_rduck_10 186
+#define FRAME_rduck_11 187
+#define FRAME_rduck_12 188
+#define FRAME_rduck_13 189
+#define FRAME_lduck_01 190
+#define FRAME_lduck_02 191
+#define FRAME_lduck_03 192
+#define FRAME_lduck_04 193
+#define FRAME_lduck_05 194
+#define FRAME_lduck_06 195
+#define FRAME_lduck_07 196
+#define FRAME_lduck_08 197
+#define FRAME_lduck_09 198
+#define FRAME_lduck_10 199
+#define FRAME_lduck_11 200
+#define FRAME_lduck_12 201
+#define FRAME_lduck_13 202
+#define FRAME_idle_01 203
+#define FRAME_idle_02 204
+#define FRAME_idle_03 205
+#define FRAME_idle_04 206
+#define FRAME_idle_05 207
+#define FRAME_idle_06 208
+#define FRAME_idle_07 209
+#define FRAME_idle_08 210
+#define FRAME_idle_09 211
+#define FRAME_idle_10 212
+#define FRAME_idle_11 213
+#define FRAME_idle_12 214
+#define FRAME_idle_13 215
+#define FRAME_idle_14 216
+#define FRAME_idle_15 217
+#define FRAME_idle_16 218
+#define FRAME_idle_17 219
+#define FRAME_idle_18 220
+#define FRAME_idle_19 221
+#define FRAME_idle_20 222
+#define FRAME_idle_21 223
+#define FRAME_idle_22 224
+#define FRAME_idle_23 225
+#define FRAME_idle_24 226
+#define FRAME_idle_25 227
+#define FRAME_idle_26 228
+#define FRAME_idle_27 229
+#define FRAME_idle_28 230
+#define FRAME_idle_29 231
+#define FRAME_idle_30 232
+#define FRAME_idle_31 233
+#define FRAME_idle_32 234
+#define FRAME_spit_01 235
+#define FRAME_spit_02 236
+#define FRAME_spit_03 237
+#define FRAME_spit_04 238
+#define FRAME_spit_05 239
+#define FRAME_spit_06 240
+#define FRAME_spit_07 241
+#define FRAME_amb_01 242
+#define FRAME_amb_02 243
+#define FRAME_amb_03 244
+#define FRAME_amb_04 245
+#define FRAME_wdeath_01 246
+#define FRAME_wdeath_02 247
+#define FRAME_wdeath_03 248
+#define FRAME_wdeath_04 249
+#define FRAME_wdeath_05 250
+#define FRAME_wdeath_06 251
+#define FRAME_wdeath_07 252
+#define FRAME_wdeath_08 253
+#define FRAME_wdeath_09 254
+#define FRAME_wdeath_10 255
+#define FRAME_wdeath_11 256
+#define FRAME_wdeath_12 257
+#define FRAME_wdeath_13 258
+#define FRAME_wdeath_14 259
+#define FRAME_wdeath_15 260
+#define FRAME_wdeath_16 261
+#define FRAME_wdeath_17 262
+#define FRAME_wdeath_18 263
+#define FRAME_wdeath_19 264
+#define FRAME_wdeath_20 265
+#define FRAME_wdeath_21 266
+#define FRAME_wdeath_22 267
+#define FRAME_wdeath_23 268
+#define FRAME_wdeath_24 269
+#define FRAME_wdeath_25 270
+#define FRAME_wdeath_26 271
+#define FRAME_wdeath_27 272
+#define FRAME_wdeath_28 273
+#define FRAME_wdeath_29 274
+#define FRAME_wdeath_30 275
+#define FRAME_wdeath_31 276
+#define FRAME_wdeath_32 277
+#define FRAME_wdeath_33 278
+#define FRAME_wdeath_34 279
+#define FRAME_wdeath_35 280
+#define FRAME_wdeath_36 281
+#define FRAME_wdeath_37 282
+#define FRAME_wdeath_38 283
+#define FRAME_wdeath_39 284
+#define FRAME_wdeath_40 285
+#define FRAME_wdeath_41 286
+#define FRAME_wdeath_42 287
+#define FRAME_wdeath_43 288
+#define FRAME_wdeath_44 289
+#define FRAME_wdeath_45 290
+#define FRAME_swim_01 291
+#define FRAME_swim_02 292
+#define FRAME_swim_03 293
+#define FRAME_swim_04 294
+#define FRAME_swim_05 295
+#define FRAME_swim_06 296
+#define FRAME_swim_07 297
+#define FRAME_swim_08 298
+#define FRAME_swim_09 299
+#define FRAME_swim_10 300
+#define FRAME_swim_11 301
+#define FRAME_swim_12 302
+#define FRAME_swim_13 303
+#define FRAME_swim_14 304
+#define FRAME_swim_15 305
+#define FRAME_swim_16 306
+#define FRAME_swim_17 307
+#define FRAME_swim_18 308
+#define FRAME_swim_19 309
+#define FRAME_swim_20 310
+#define FRAME_swim_21 311
+#define FRAME_swim_22 312
+#define FRAME_swim_23 313
+#define FRAME_swim_24 314
+#define FRAME_swim_25 315
+#define FRAME_swim_26 316
+#define FRAME_swim_27 317
+#define FRAME_swim_28 318
+#define FRAME_swim_29 319
+#define FRAME_swim_30 320
+#define FRAME_swim_31 321
+#define FRAME_swim_32 322
+#define FRAME_attack_01 323
+#define FRAME_attack_02 324
+#define FRAME_attack_03 325
+#define FRAME_attack_04 326
+#define FRAME_attack_05 327
+#define FRAME_attack_06 328
+#define FRAME_attack_07 329
+#define FRAME_attack_08 330
+#define FRAME_attack_09 331
+#define FRAME_attack_10 332
+#define FRAME_attack_11 333
+#define FRAME_attack_12 334
+#define FRAME_attack_13 335
+#define FRAME_attack_14 336
+#define FRAME_attack_15 337
+#define FRAME_attack_16 338
+#define FRAME_attack_17 339
+#define FRAME_attack_18 340
+#define FRAME_attack_19 341
+#define FRAME_attack_20 342
+#define FRAME_attack_21 343
+#define FRAME_pain_01 344
+#define FRAME_pain_02 345
+#define FRAME_pain_03 346
+#define FRAME_pain_04 347
+#define FRAME_pain_05 348
+#define FRAME_pain_06 349
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/gladiator/gladb.c b/xatrix/src/monster/gladiator/gladb.c
new file mode 100644
index 0000000..6a7e5da
--- /dev/null
+++ b/xatrix/src/monster/gladiator/gladb.c
@@ -0,0 +1,540 @@
+/* =======================================================================
+ *
+ * Gladiator B. (Special variant of gladiator only found in Xatrix)
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "gladiator.h"
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die;
+static int sound_gun;
+static int sound_cleaver_swing;
+static int sound_cleaver_hit;
+static int sound_cleaver_miss;
+static int sound_idle;
+static int sound_search;
+static int sound_sight;
+
+void
+gladb_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+gladb_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+gladb_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void
+gladb_cleaver_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladb_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t gladb_move_stand = {
+ FRAME_stand1,
+ FRAME_stand7,
+ gladb_frames_stand,
+ NULL
+};
+
+void
+gladb_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladb_move_stand;
+}
+
+mframe_t gladb_frames_walk[] = {
+ {ai_walk, 15, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 8, NULL}
+};
+
+mmove_t gladb_move_walk = {
+ FRAME_walk1,
+ FRAME_walk16,
+ gladb_frames_walk,
+ NULL
+};
+
+void
+gladb_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladb_move_walk;
+}
+
+mframe_t gladb_frames_run[] = {
+ {ai_run, 23, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 21, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 13, NULL}
+};
+
+mmove_t gladb_move_run = {
+ FRAME_run1,
+ FRAME_run6,
+ gladb_frames_run,
+ NULL
+};
+
+void
+gladb_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &gladb_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gladb_move_run;
+ }
+}
+
+void
+GladbMelee(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4);
+
+ if (fire_hit(self, aim, (20 + (rand() % 5)), 300))
+ {
+ gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t gladb_frames_attack_melee[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladb_cleaver_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GladbMelee},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladb_cleaver_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GladbMelee},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gladb_move_attack_melee = {
+ FRAME_melee1,
+ FRAME_melee17,
+ gladb_frames_attack_melee,
+ gladb_run
+};
+
+void
+gladb_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladb_move_attack_melee;
+}
+
+void
+gladbGun(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1],
+ forward, right, start);
+
+ /* calc direction to where we targted */
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ fire_plasma(self, start, dir, 100, 725, 60, 60);
+}
+
+void
+gladbGun_check(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ gladbGun(self);
+ }
+}
+
+mframe_t gladb_frames_attack_gun[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladbGun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladbGun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladbGun_check}
+};
+
+mmove_t gladb_move_attack_gun = {
+ FRAME_attack1,
+ FRAME_attack9,
+ gladb_frames_attack_gun,
+ gladb_run
+};
+
+void
+gladb_attack(edict_t *self)
+{
+ float range;
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* a small safe zone
+ but not for stand-ground ones since players can
+ abuse it by standing still inside this range
+ */
+ if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+ {
+ VectorSubtract(self->s.origin, self->enemy->s.origin, v);
+ range = VectorLength(v);
+
+ if (range <= (MELEE_DISTANCE + 32))
+ {
+ return;
+ }
+ }
+
+ /* charge up the railgun */
+ gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0);
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+ self->monsterinfo.currentmove = &gladb_move_attack_gun;
+}
+
+mframe_t gladb_frames_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladb_move_pain = {
+ FRAME_pain1,
+ FRAME_pain6,
+ gladb_frames_pain, gladb_run
+};
+
+mframe_t gladb_frames_pain_air[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladb_move_pain_air = {
+ FRAME_painup1,
+ FRAME_painup7,
+ gladb_frames_pain_air,
+ gladb_run
+};
+
+void
+gladb_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ if ((self->velocity[2] > 100) &&
+ (self->monsterinfo.currentmove == &gladb_move_pain))
+ {
+ self->monsterinfo.currentmove = &gladb_move_pain_air;
+ }
+
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ if (self->velocity[2] > 100)
+ {
+ self->monsterinfo.currentmove = &gladb_move_pain_air;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gladb_move_pain;
+ }
+}
+
+void
+gladb_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t gladb_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladb_move_death = {
+ FRAME_death1,
+ FRAME_death22,
+ gladb_frames_death,
+ gladb_dead
+};
+
+void
+gladb_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage /*unused */,
+ vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ self->monsterinfo.currentmove = &gladb_move_death;
+}
+
+/*
+ * QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_gladb(edict_t *self)
+{
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("gladiator/pain.wav");
+ sound_pain2 = gi.soundindex("gladiator/gldpain2.wav");
+ sound_die = gi.soundindex("gladiator/glddeth2.wav");
+ sound_gun = gi.soundindex("weapons/plasshot.wav");
+
+ sound_cleaver_swing = gi.soundindex("gladiator/melee1.wav");
+ sound_cleaver_hit = gi.soundindex("gladiator/melee2.wav");
+ sound_cleaver_miss = gi.soundindex("gladiator/melee3.wav");
+ sound_idle = gi.soundindex("gladiator/gldidle1.wav");
+ sound_search = gi.soundindex("gladiator/gldsrch1.wav");
+ sound_sight = gi.soundindex("gladiator/sight.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/gladb/tris.md2");
+ VectorSet(self->mins, -32, -32, -24);
+ VectorSet(self->maxs, 32, 32, 64);
+
+ self->health = 800;
+ self->gib_health = -175;
+ self->mass = 350;
+
+ self->pain = gladb_pain;
+ self->die = gladb_die;
+
+ self->monsterinfo.stand = gladb_stand;
+ self->monsterinfo.walk = gladb_walk;
+ self->monsterinfo.run = gladb_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = gladb_attack;
+ self->monsterinfo.melee = gladb_melee;
+ self->monsterinfo.sight = gladb_sight;
+ self->monsterinfo.idle = gladb_idle;
+ self->monsterinfo.search = gladb_search;
+
+ gi.linkentity(self);
+ self->monsterinfo.currentmove = &gladb_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+ self->monsterinfo.power_armor_power = 400;
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/gladiator/gladiator.c b/xatrix/src/monster/gladiator/gladiator.c
new file mode 100644
index 0000000..0d52a02
--- /dev/null
+++ b/xatrix/src/monster/gladiator/gladiator.c
@@ -0,0 +1,537 @@
+/* =======================================================================
+ *
+ * Gladiator.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "gladiator.h"
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die;
+static int sound_gun;
+static int sound_cleaver_swing;
+static int sound_cleaver_hit;
+static int sound_cleaver_miss;
+static int sound_idle;
+static int sound_search;
+static int sound_sight;
+
+void
+gladiator_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+gladiator_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+gladiator_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void
+gladiator_cleaver_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladiator_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t gladiator_move_stand = {
+ FRAME_stand1,
+ FRAME_stand7,
+ gladiator_frames_stand,
+ NULL
+};
+
+void
+gladiator_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladiator_move_stand;
+}
+
+mframe_t gladiator_frames_walk[] = {
+ {ai_walk, 15, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 8, NULL}
+};
+
+mmove_t gladiator_move_walk = {
+ FRAME_walk1,
+ FRAME_walk16,
+ gladiator_frames_walk,
+ NULL
+};
+
+void
+gladiator_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladiator_move_walk;
+}
+
+mframe_t gladiator_frames_run[] = {
+ {ai_run, 23, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 14, NULL},
+ {ai_run, 21, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 13, NULL}
+};
+
+mmove_t gladiator_move_run = {
+ FRAME_run1,
+ FRAME_run6,
+ gladiator_frames_run,
+ NULL
+};
+
+void
+gladiator_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &gladiator_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gladiator_move_run;
+ }
+}
+
+void
+GaldiatorMelee(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], -4);
+
+ if (fire_hit(self, aim, (20 + (rand() % 5)), 300))
+ {
+ gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t gladiator_frames_attack_melee[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladiator_cleaver_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GaldiatorMelee},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, gladiator_cleaver_swing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GaldiatorMelee},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gladiator_move_attack_melee = {
+ FRAME_melee1,
+ FRAME_melee17,
+ gladiator_frames_attack_melee,
+ gladiator_run
+};
+
+void
+gladiator_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gladiator_move_attack_melee;
+}
+
+void
+GladiatorGun(edict_t *self)
+{
+ vec3_t start;
+ vec3_t dir;
+ vec3_t forward, right;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1],
+ forward, right, start);
+
+ /* calc direction to where we targted */
+ VectorSubtract(self->pos1, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_railgun(self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1);
+}
+
+mframe_t gladiator_frames_attack_gun[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GladiatorGun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gladiator_move_attack_gun = {
+ FRAME_attack1,
+ FRAME_attack9,
+ gladiator_frames_attack_gun,
+ gladiator_run
+};
+
+void
+gladiator_attack(edict_t *self)
+{
+ float range;
+ vec3_t v;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* a small safe zone
+ but not for stand-ground ones since players can
+ abuse it by standing still inside this range
+ */
+ if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+ {
+ VectorSubtract(self->s.origin, self->enemy->s.origin, v);
+ range = VectorLength(v);
+
+ if (range <= (MELEE_DISTANCE + 32))
+ {
+ return;
+ }
+ }
+
+ /* charge up the railgun */
+ gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0);
+ VectorCopy(self->enemy->s.origin, self->pos1); /* save for aiming the shot */
+ self->pos1[2] += self->enemy->viewheight;
+ self->monsterinfo.currentmove = &gladiator_move_attack_gun;
+}
+
+mframe_t gladiator_frames_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladiator_move_pain = {
+ FRAME_pain1,
+ FRAME_pain6,
+ gladiator_frames_pain,
+ gladiator_run
+};
+
+mframe_t gladiator_frames_pain_air[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladiator_move_pain_air = {
+ FRAME_painup1,
+ FRAME_painup7,
+ gladiator_frames_pain_air,
+ gladiator_run
+};
+
+void
+gladiator_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ if ((self->velocity[2] > 100) &&
+ (self->monsterinfo.currentmove == &gladiator_move_pain))
+ {
+ self->monsterinfo.currentmove = &gladiator_move_pain_air;
+ }
+
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (self->velocity[2] > 100)
+ {
+ self->monsterinfo.currentmove = &gladiator_move_pain_air;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gladiator_move_pain;
+ }
+}
+
+void
+gladiator_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t gladiator_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gladiator_move_death = {
+ FRAME_death1,
+ FRAME_death22,
+ gladiator_frames_death,
+ gladiator_dead
+};
+
+void
+gladiator_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage /*unused */,
+ vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex(
+ "misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2",
+ damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ self->monsterinfo.currentmove = &gladiator_move_death;
+}
+
+/*
+ * QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_gladiator(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("gladiator/pain.wav");
+ sound_pain2 = gi.soundindex("gladiator/gldpain2.wav");
+ sound_die = gi.soundindex("gladiator/glddeth2.wav");
+ sound_gun = gi.soundindex("gladiator/railgun.wav");
+ sound_cleaver_swing = gi.soundindex("gladiator/melee1.wav");
+ sound_cleaver_hit = gi.soundindex("gladiator/melee2.wav");
+ sound_cleaver_miss = gi.soundindex("gladiator/melee3.wav");
+ sound_idle = gi.soundindex("gladiator/gldidle1.wav");
+ sound_search = gi.soundindex("gladiator/gldsrch1.wav");
+ sound_sight = gi.soundindex("gladiator/sight.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/gladiatr/tris.md2");
+ VectorSet(self->mins, -32, -32, -24);
+ VectorSet(self->maxs, 32, 32, 64);
+
+ self->health = 400;
+ self->gib_health = -175;
+ self->mass = 400;
+
+ self->pain = gladiator_pain;
+ self->die = gladiator_die;
+
+ self->monsterinfo.stand = gladiator_stand;
+ self->monsterinfo.walk = gladiator_walk;
+ self->monsterinfo.run = gladiator_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = gladiator_attack;
+ self->monsterinfo.melee = gladiator_melee;
+ self->monsterinfo.sight = gladiator_sight;
+ self->monsterinfo.idle = gladiator_idle;
+ self->monsterinfo.search = gladiator_search;
+
+ gi.linkentity(self);
+ self->monsterinfo.currentmove = &gladiator_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/gladiator/gladiator.h b/xatrix/src/monster/gladiator/gladiator.h
new file mode 100644
index 0000000..ffa4e70
--- /dev/null
+++ b/xatrix/src/monster/gladiator/gladiator.h
@@ -0,0 +1,99 @@
+/* =======================================================================
+ *
+ * Gladiator animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand1 0
+#define FRAME_stand2 1
+#define FRAME_stand3 2
+#define FRAME_stand4 3
+#define FRAME_stand5 4
+#define FRAME_stand6 5
+#define FRAME_stand7 6
+#define FRAME_walk1 7
+#define FRAME_walk2 8
+#define FRAME_walk3 9
+#define FRAME_walk4 10
+#define FRAME_walk5 11
+#define FRAME_walk6 12
+#define FRAME_walk7 13
+#define FRAME_walk8 14
+#define FRAME_walk9 15
+#define FRAME_walk10 16
+#define FRAME_walk11 17
+#define FRAME_walk12 18
+#define FRAME_walk13 19
+#define FRAME_walk14 20
+#define FRAME_walk15 21
+#define FRAME_walk16 22
+#define FRAME_run1 23
+#define FRAME_run2 24
+#define FRAME_run3 25
+#define FRAME_run4 26
+#define FRAME_run5 27
+#define FRAME_run6 28
+#define FRAME_melee1 29
+#define FRAME_melee2 30
+#define FRAME_melee3 31
+#define FRAME_melee4 32
+#define FRAME_melee5 33
+#define FRAME_melee6 34
+#define FRAME_melee7 35
+#define FRAME_melee8 36
+#define FRAME_melee9 37
+#define FRAME_melee10 38
+#define FRAME_melee11 39
+#define FRAME_melee12 40
+#define FRAME_melee13 41
+#define FRAME_melee14 42
+#define FRAME_melee15 43
+#define FRAME_melee16 44
+#define FRAME_melee17 45
+#define FRAME_attack1 46
+#define FRAME_attack2 47
+#define FRAME_attack3 48
+#define FRAME_attack4 49
+#define FRAME_attack5 50
+#define FRAME_attack6 51
+#define FRAME_attack7 52
+#define FRAME_attack8 53
+#define FRAME_attack9 54
+#define FRAME_pain1 55
+#define FRAME_pain2 56
+#define FRAME_pain3 57
+#define FRAME_pain4 58
+#define FRAME_pain5 59
+#define FRAME_pain6 60
+#define FRAME_death1 61
+#define FRAME_death2 62
+#define FRAME_death3 63
+#define FRAME_death4 64
+#define FRAME_death5 65
+#define FRAME_death6 66
+#define FRAME_death7 67
+#define FRAME_death8 68
+#define FRAME_death9 69
+#define FRAME_death10 70
+#define FRAME_death11 71
+#define FRAME_death12 72
+#define FRAME_death13 73
+#define FRAME_death14 74
+#define FRAME_death15 75
+#define FRAME_death16 76
+#define FRAME_death17 77
+#define FRAME_death18 78
+#define FRAME_death19 79
+#define FRAME_death20 80
+#define FRAME_death21 81
+#define FRAME_death22 82
+#define FRAME_painup1 83
+#define FRAME_painup2 84
+#define FRAME_painup3 85
+#define FRAME_painup4 86
+#define FRAME_painup5 87
+#define FRAME_painup6 88
+#define FRAME_painup7 89
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/gunner/gunner.c b/xatrix/src/monster/gunner/gunner.c
new file mode 100644
index 0000000..8b88ba6
--- /dev/null
+++ b/xatrix/src/monster/gunner/gunner.c
@@ -0,0 +1,866 @@
+/* =======================================================================
+ *
+ * Gunner.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "gunner.h"
+
+static int sound_pain;
+static int sound_pain2;
+static int sound_death;
+static int sound_idle;
+static int sound_open;
+static int sound_search;
+static int sound_sight;
+
+qboolean visible(edict_t *self, edict_t *other);
+void GunnerGrenade(edict_t *self);
+void GunnerFire(edict_t *self);
+void gunner_fire_chain(edict_t *self);
+void gunner_refire_chain(edict_t *self);
+void gunner_stand(edict_t *self);
+
+void
+gunner_idlesound(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void
+gunner_sight(edict_t *self, edict_t *other)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+gunner_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+mframe_t gunner_frames_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_idlesound},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t gunner_move_fidget = {
+ FRAME_stand31,
+ FRAME_stand70,
+ gunner_frames_fidget,
+ gunner_stand
+};
+
+void
+gunner_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ return;
+ }
+
+ if (random() <= 0.05)
+ {
+ self->monsterinfo.currentmove = &gunner_move_fidget;
+ }
+}
+
+mframe_t gunner_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_fidget},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_fidget},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, gunner_fidget}
+};
+
+mmove_t gunner_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ gunner_frames_stand,
+ NULL
+};
+
+void
+gunner_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_stand;
+}
+
+mframe_t gunner_frames_walk[] = {
+ {ai_walk, 0, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t gunner_move_walk = {
+ FRAME_walk07,
+ FRAME_walk19,
+ gunner_frames_walk,
+ NULL
+};
+
+void
+gunner_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_walk;
+}
+
+mframe_t gunner_frames_run[] = {
+ {ai_run, 26, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 9, NULL},
+ {ai_run, 15, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 13, NULL},
+ {ai_run, 6, NULL}
+};
+
+mmove_t gunner_move_run = {
+ FRAME_run01,
+ FRAME_run08,
+ gunner_frames_run,
+ NULL
+};
+
+void
+gunner_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &gunner_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gunner_move_run;
+ }
+}
+
+mframe_t gunner_frames_runandshoot[] = {
+ {ai_run, 32, NULL},
+ {ai_run, 15, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 18, NULL},
+ {ai_run, 8, NULL},
+ {ai_run, 20, NULL}
+};
+
+mmove_t gunner_move_runandshoot = {
+ FRAME_runs01,
+ FRAME_runs06,
+ gunner_frames_runandshoot,
+ NULL
+};
+
+void
+gunner_runandshoot(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_runandshoot;
+}
+
+mframe_t gunner_frames_pain3[] = {
+ {ai_move, -3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t gunner_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain305,
+ gunner_frames_pain3,
+ gunner_run
+};
+
+mframe_t gunner_frames_pain2[] = {
+ {ai_move, -2, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -7, NULL}
+};
+
+mmove_t gunner_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain208,
+ gunner_frames_pain2,
+ gunner_run
+};
+
+mframe_t gunner_frames_pain1[] = {
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gunner_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain118,
+ gunner_frames_pain1,
+ gunner_run
+};
+
+void
+gunner_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (rand() & 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 10)
+ {
+ self->monsterinfo.currentmove = &gunner_move_pain3;
+ }
+ else if (damage <= 25)
+ {
+ self->monsterinfo.currentmove = &gunner_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gunner_move_pain1;
+ }
+}
+
+void
+gunner_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t gunner_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 8, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t gunner_move_death = {
+ FRAME_death01,
+ FRAME_death11,
+ gunner_frames_death,
+ gunner_dead
+};
+
+void
+gunner_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage /* unused */,
+ vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &gunner_move_death;
+}
+
+void
+gunner_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+
+ if (skill->value >= SKILL_HARD)
+ {
+ if (random() > 0.5)
+ {
+ GunnerGrenade(self);
+ }
+ }
+
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.pausetime = level.time + 1;
+ gi.linkentity(self);
+}
+
+void
+gunner_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+void
+gunner_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+mframe_t gunner_frames_duck[] = {
+ {ai_move, 1, gunner_duck_down},
+ {ai_move, 1, NULL},
+ {ai_move, 1, gunner_duck_hold},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 0, gunner_duck_up},
+ {ai_move, -1, NULL}
+};
+
+mmove_t gunner_move_duck = {
+ FRAME_duck01,
+ FRAME_duck08,
+ gunner_frames_duck,
+ gunner_run
+};
+
+void
+gunner_dodge(edict_t *self, edict_t *attacker, float eta /* unsued */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if (random() > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_duck;
+}
+
+void
+gunner_opengun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0);
+}
+
+void
+GunnerFire(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t target;
+ vec3_t aim;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ /* project enemy back a bit and target there */
+ VectorCopy(self->enemy->s.origin, target);
+ VectorMA(target, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+
+ VectorSubtract(target, start, aim);
+ VectorNormalize(aim);
+ monster_fire_bullet(self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void
+GunnerGrenade(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t aim;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak105)
+ {
+ flash_number = MZ2_GUNNER_GRENADE_1;
+ }
+ else if (self->s.frame == FRAME_attak108)
+ {
+ flash_number = MZ2_GUNNER_GRENADE_2;
+ }
+ else if (self->s.frame == FRAME_attak111)
+ {
+ flash_number = MZ2_GUNNER_GRENADE_3;
+ }
+ else
+ {
+ flash_number = MZ2_GUNNER_GRENADE_4;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+ VectorCopy(forward, aim);
+ monster_fire_grenade(self, start, aim, 50, 600, flash_number);
+}
+
+mframe_t gunner_frames_attack_chain[] = {
+ {ai_charge, 0, gunner_opengun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gunner_move_attack_chain = {
+ FRAME_attak209,
+ FRAME_attak215,
+ gunner_frames_attack_chain,
+ gunner_fire_chain
+};
+
+mframe_t gunner_frames_fire_chain[] = {
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire},
+ {ai_charge, 0, GunnerFire}
+};
+
+mmove_t gunner_move_fire_chain = {
+ FRAME_attak216,
+ FRAME_attak223,
+ gunner_frames_fire_chain,
+ gunner_refire_chain
+};
+
+mframe_t gunner_frames_endfire_chain[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gunner_move_endfire_chain = {
+ FRAME_attak224,
+ FRAME_attak230,
+ gunner_frames_endfire_chain,
+ gunner_run
+};
+
+mframe_t gunner_frames_attack_grenade[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, GunnerGrenade},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t gunner_move_attack_grenade = {
+ FRAME_attak101,
+ FRAME_attak121,
+ gunner_frames_attack_grenade,
+ gunner_run
+};
+
+void
+gunner_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ self->monsterinfo.currentmove = &gunner_move_attack_chain;
+ }
+ else
+ {
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &gunner_move_attack_grenade;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &gunner_move_attack_chain;
+ }
+ }
+}
+
+void
+gunner_fire_chain(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_fire_chain;
+}
+
+void
+gunner_refire_chain(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &gunner_move_fire_chain;
+ return;
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &gunner_move_endfire_chain;
+}
+
+/*
+ * QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_gunner(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_death = gi.soundindex("gunner/death1.wav");
+ sound_pain = gi.soundindex("gunner/gunpain2.wav");
+ sound_pain2 = gi.soundindex("gunner/gunpain1.wav");
+ sound_idle = gi.soundindex("gunner/gunidle1.wav");
+ sound_open = gi.soundindex("gunner/gunatck1.wav");
+ sound_search = gi.soundindex("gunner/gunsrch1.wav");
+ sound_sight = gi.soundindex("gunner/sight1.wav");
+
+ gi.soundindex("gunner/gunatck2.wav");
+ gi.soundindex("gunner/gunatck3.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 175;
+ self->gib_health = -70;
+ self->mass = 200;
+
+ self->pain = gunner_pain;
+ self->die = gunner_die;
+
+ self->monsterinfo.stand = gunner_stand;
+ self->monsterinfo.walk = gunner_walk;
+ self->monsterinfo.run = gunner_run;
+ self->monsterinfo.dodge = gunner_dodge;
+ self->monsterinfo.attack = gunner_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = gunner_sight;
+ self->monsterinfo.search = gunner_search;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &gunner_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
+
diff --git a/xatrix/src/monster/gunner/gunner.h b/xatrix/src/monster/gunner/gunner.h
new file mode 100644
index 0000000..bf9ea2b
--- /dev/null
+++ b/xatrix/src/monster/gunner/gunner.h
@@ -0,0 +1,218 @@
+/* =======================================================================
+ *
+ * Gunner animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_stand31 30
+#define FRAME_stand32 31
+#define FRAME_stand33 32
+#define FRAME_stand34 33
+#define FRAME_stand35 34
+#define FRAME_stand36 35
+#define FRAME_stand37 36
+#define FRAME_stand38 37
+#define FRAME_stand39 38
+#define FRAME_stand40 39
+#define FRAME_stand41 40
+#define FRAME_stand42 41
+#define FRAME_stand43 42
+#define FRAME_stand44 43
+#define FRAME_stand45 44
+#define FRAME_stand46 45
+#define FRAME_stand47 46
+#define FRAME_stand48 47
+#define FRAME_stand49 48
+#define FRAME_stand50 49
+#define FRAME_stand51 50
+#define FRAME_stand52 51
+#define FRAME_stand53 52
+#define FRAME_stand54 53
+#define FRAME_stand55 54
+#define FRAME_stand56 55
+#define FRAME_stand57 56
+#define FRAME_stand58 57
+#define FRAME_stand59 58
+#define FRAME_stand60 59
+#define FRAME_stand61 60
+#define FRAME_stand62 61
+#define FRAME_stand63 62
+#define FRAME_stand64 63
+#define FRAME_stand65 64
+#define FRAME_stand66 65
+#define FRAME_stand67 66
+#define FRAME_stand68 67
+#define FRAME_stand69 68
+#define FRAME_stand70 69
+#define FRAME_walk01 70
+#define FRAME_walk02 71
+#define FRAME_walk03 72
+#define FRAME_walk04 73
+#define FRAME_walk05 74
+#define FRAME_walk06 75
+#define FRAME_walk07 76
+#define FRAME_walk08 77
+#define FRAME_walk09 78
+#define FRAME_walk10 79
+#define FRAME_walk11 80
+#define FRAME_walk12 81
+#define FRAME_walk13 82
+#define FRAME_walk14 83
+#define FRAME_walk15 84
+#define FRAME_walk16 85
+#define FRAME_walk17 86
+#define FRAME_walk18 87
+#define FRAME_walk19 88
+#define FRAME_walk20 89
+#define FRAME_walk21 90
+#define FRAME_walk22 91
+#define FRAME_walk23 92
+#define FRAME_walk24 93
+#define FRAME_run01 94
+#define FRAME_run02 95
+#define FRAME_run03 96
+#define FRAME_run04 97
+#define FRAME_run05 98
+#define FRAME_run06 99
+#define FRAME_run07 100
+#define FRAME_run08 101
+#define FRAME_runs01 102
+#define FRAME_runs02 103
+#define FRAME_runs03 104
+#define FRAME_runs04 105
+#define FRAME_runs05 106
+#define FRAME_runs06 107
+#define FRAME_attak101 108
+#define FRAME_attak102 109
+#define FRAME_attak103 110
+#define FRAME_attak104 111
+#define FRAME_attak105 112
+#define FRAME_attak106 113
+#define FRAME_attak107 114
+#define FRAME_attak108 115
+#define FRAME_attak109 116
+#define FRAME_attak110 117
+#define FRAME_attak111 118
+#define FRAME_attak112 119
+#define FRAME_attak113 120
+#define FRAME_attak114 121
+#define FRAME_attak115 122
+#define FRAME_attak116 123
+#define FRAME_attak117 124
+#define FRAME_attak118 125
+#define FRAME_attak119 126
+#define FRAME_attak120 127
+#define FRAME_attak121 128
+#define FRAME_attak201 129
+#define FRAME_attak202 130
+#define FRAME_attak203 131
+#define FRAME_attak204 132
+#define FRAME_attak205 133
+#define FRAME_attak206 134
+#define FRAME_attak207 135
+#define FRAME_attak208 136
+#define FRAME_attak209 137
+#define FRAME_attak210 138
+#define FRAME_attak211 139
+#define FRAME_attak212 140
+#define FRAME_attak213 141
+#define FRAME_attak214 142
+#define FRAME_attak215 143
+#define FRAME_attak216 144
+#define FRAME_attak217 145
+#define FRAME_attak218 146
+#define FRAME_attak219 147
+#define FRAME_attak220 148
+#define FRAME_attak221 149
+#define FRAME_attak222 150
+#define FRAME_attak223 151
+#define FRAME_attak224 152
+#define FRAME_attak225 153
+#define FRAME_attak226 154
+#define FRAME_attak227 155
+#define FRAME_attak228 156
+#define FRAME_attak229 157
+#define FRAME_attak230 158
+#define FRAME_pain101 159
+#define FRAME_pain102 160
+#define FRAME_pain103 161
+#define FRAME_pain104 162
+#define FRAME_pain105 163
+#define FRAME_pain106 164
+#define FRAME_pain107 165
+#define FRAME_pain108 166
+#define FRAME_pain109 167
+#define FRAME_pain110 168
+#define FRAME_pain111 169
+#define FRAME_pain112 170
+#define FRAME_pain113 171
+#define FRAME_pain114 172
+#define FRAME_pain115 173
+#define FRAME_pain116 174
+#define FRAME_pain117 175
+#define FRAME_pain118 176
+#define FRAME_pain201 177
+#define FRAME_pain202 178
+#define FRAME_pain203 179
+#define FRAME_pain204 180
+#define FRAME_pain205 181
+#define FRAME_pain206 182
+#define FRAME_pain207 183
+#define FRAME_pain208 184
+#define FRAME_pain301 185
+#define FRAME_pain302 186
+#define FRAME_pain303 187
+#define FRAME_pain304 188
+#define FRAME_pain305 189
+#define FRAME_death01 190
+#define FRAME_death02 191
+#define FRAME_death03 192
+#define FRAME_death04 193
+#define FRAME_death05 194
+#define FRAME_death06 195
+#define FRAME_death07 196
+#define FRAME_death08 197
+#define FRAME_death09 198
+#define FRAME_death10 199
+#define FRAME_death11 200
+#define FRAME_duck01 201
+#define FRAME_duck02 202
+#define FRAME_duck03 203
+#define FRAME_duck04 204
+#define FRAME_duck05 205
+#define FRAME_duck06 206
+#define FRAME_duck07 207
+#define FRAME_duck08 208
+
+#define MODEL_SCALE 1.150000
diff --git a/xatrix/src/monster/hover/hover.c b/xatrix/src/monster/hover/hover.c
new file mode 100644
index 0000000..0474e1b
--- /dev/null
+++ b/xatrix/src/monster/hover/hover.c
@@ -0,0 +1,799 @@
+/* =======================================================================
+ *
+ * Icarus.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "hover.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+static int sound_pain1;
+static int sound_pain2;
+static int sound_death1;
+static int sound_death2;
+static int sound_sight;
+static int sound_search1;
+static int sound_search2;
+
+void hover_run(edict_t *self);
+void hover_stand(edict_t *self);
+void hover_dead(edict_t *self);
+void hover_attack(edict_t *self);
+void hover_reattack(edict_t *self);
+void hover_fire_blaster(edict_t *self);
+void hover_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point);
+
+void
+hover_sight(edict_t *self, edict_t *other)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+hover_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t hover_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t hover_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ hover_frames_stand,
+ NULL
+};
+
+mframe_t hover_frames_stop1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_stop1 = {
+ FRAME_stop101,
+ FRAME_stop109,
+ hover_frames_stop1,
+ NULL
+};
+
+mframe_t hover_frames_stop2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_stop2 = {
+ FRAME_stop201,
+ FRAME_stop208,
+ hover_frames_stop2,
+ NULL
+};
+
+mframe_t hover_frames_takeoff[] = {
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -9, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_takeoff = {
+ FRAME_takeof01,
+ FRAME_takeof30,
+ hover_frames_takeoff,
+ NULL
+};
+
+mframe_t hover_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain309,
+ hover_frames_pain3,
+ hover_run
+};
+
+mframe_t hover_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain212,
+ hover_frames_pain2,
+ hover_run
+};
+
+mframe_t hover_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 4, NULL}
+};
+
+mmove_t hover_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain128,
+ hover_frames_pain1,
+ hover_run
+};
+
+mframe_t hover_frames_land[] = {
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_land = {
+ FRAME_land01,
+ FRAME_land01,
+ hover_frames_land,
+ NULL
+};
+
+mframe_t hover_frames_forward[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_forward = {
+ FRAME_forwrd01,
+ FRAME_forwrd35,
+ hover_frames_forward,
+ NULL
+};
+
+mframe_t hover_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t hover_move_walk = {
+ FRAME_forwrd01,
+ FRAME_forwrd35,
+ hover_frames_walk,
+ NULL
+};
+
+mframe_t hover_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 10, NULL}
+};
+
+mmove_t hover_move_run = {
+ FRAME_forwrd01,
+ FRAME_forwrd35,
+ hover_frames_run,
+ NULL
+};
+
+mframe_t hover_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 7, NULL}
+};
+
+mmove_t hover_move_death1 = {
+ FRAME_death101,
+ FRAME_death111,
+ hover_frames_death1,
+ hover_dead
+};
+
+mframe_t hover_frames_backward[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t hover_move_backward = {
+ FRAME_backwd01,
+ FRAME_backwd24,
+ hover_frames_backward,
+ NULL
+};
+
+mframe_t hover_frames_start_attack[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t hover_move_start_attack = {
+ FRAME_attak101,
+ FRAME_attak103,
+ hover_frames_start_attack,
+ hover_attack
+};
+
+mframe_t hover_frames_attack1[] = {
+ {ai_charge, -10, hover_fire_blaster},
+ {ai_charge, -10, hover_fire_blaster},
+ {ai_charge, 0, hover_reattack},
+};
+
+mmove_t hover_move_attack1 = {
+ FRAME_attak104,
+ FRAME_attak106,
+ hover_frames_attack1,
+ NULL
+};
+
+mframe_t hover_frames_end_attack[] = {
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t hover_move_end_attack = {
+ FRAME_attak107,
+ FRAME_attak108,
+ hover_frames_end_attack,
+ hover_run
+};
+
+void
+hover_reattack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health > 0)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.6)
+ {
+ self->monsterinfo.currentmove = &hover_move_attack1;
+ return;
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &hover_move_end_attack;
+}
+
+void
+hover_fire_blaster(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak104)
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 1, 1000, MZ2_HOVER_BLASTER_1, effect);
+}
+
+void
+hover_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_stand;
+}
+
+void
+hover_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &hover_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &hover_move_run;
+ }
+}
+
+void
+hover_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_walk;
+}
+
+void
+hover_start_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_start_attack;
+}
+
+void
+hover_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &hover_move_attack1;
+}
+
+void
+hover_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 25)
+ {
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &hover_move_pain3;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &hover_move_pain2;
+ }
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &hover_move_pain1;
+ }
+}
+
+void
+hover_deadthink(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->groundentity && (level.time < self->timestamp))
+ {
+ self->nextthink = level.time + FRAMETIME;
+ return;
+ }
+
+ BecomeExplosion1(self);
+}
+
+void
+hover_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->think = hover_deadthink;
+ self->nextthink = level.time + FRAMETIME;
+ self->timestamp = level.time + 15;
+ gi.linkentity(self);
+}
+
+void
+hover_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+ }
+
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &hover_move_death1;
+}
+
+/*
+ * QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_hover(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("hover/hovpain1.wav");
+ sound_pain2 = gi.soundindex("hover/hovpain2.wav");
+ sound_death1 = gi.soundindex("hover/hovdeth1.wav");
+ sound_death2 = gi.soundindex("hover/hovdeth2.wav");
+ sound_sight = gi.soundindex("hover/hovsght1.wav");
+ sound_search1 = gi.soundindex("hover/hovsrch1.wav");
+ sound_search2 = gi.soundindex("hover/hovsrch2.wav");
+
+ gi.soundindex("hover/hovatck1.wav");
+
+ self->s.sound = gi.soundindex("hover/hovidle1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2");
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 32);
+
+ self->health = 240;
+ self->gib_health = -100;
+ self->mass = 150;
+
+ self->pain = hover_pain;
+ self->die = hover_die;
+
+ self->monsterinfo.stand = hover_stand;
+ self->monsterinfo.walk = hover_walk;
+ self->monsterinfo.run = hover_run;
+ self->monsterinfo.attack = hover_start_attack;
+ self->monsterinfo.sight = hover_sight;
+ self->monsterinfo.search = hover_search;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &hover_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ flymonster_start(self);
+}
diff --git a/xatrix/src/monster/hover/hover.h b/xatrix/src/monster/hover/hover.h
new file mode 100644
index 0000000..3ead30b
--- /dev/null
+++ b/xatrix/src/monster/hover/hover.h
@@ -0,0 +1,214 @@
+/* =======================================================================
+ *
+ * Icarus animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_forwrd01 30
+#define FRAME_forwrd02 31
+#define FRAME_forwrd03 32
+#define FRAME_forwrd04 33
+#define FRAME_forwrd05 34
+#define FRAME_forwrd06 35
+#define FRAME_forwrd07 36
+#define FRAME_forwrd08 37
+#define FRAME_forwrd09 38
+#define FRAME_forwrd10 39
+#define FRAME_forwrd11 40
+#define FRAME_forwrd12 41
+#define FRAME_forwrd13 42
+#define FRAME_forwrd14 43
+#define FRAME_forwrd15 44
+#define FRAME_forwrd16 45
+#define FRAME_forwrd17 46
+#define FRAME_forwrd18 47
+#define FRAME_forwrd19 48
+#define FRAME_forwrd20 49
+#define FRAME_forwrd21 50
+#define FRAME_forwrd22 51
+#define FRAME_forwrd23 52
+#define FRAME_forwrd24 53
+#define FRAME_forwrd25 54
+#define FRAME_forwrd26 55
+#define FRAME_forwrd27 56
+#define FRAME_forwrd28 57
+#define FRAME_forwrd29 58
+#define FRAME_forwrd30 59
+#define FRAME_forwrd31 60
+#define FRAME_forwrd32 61
+#define FRAME_forwrd33 62
+#define FRAME_forwrd34 63
+#define FRAME_forwrd35 64
+#define FRAME_stop101 65
+#define FRAME_stop102 66
+#define FRAME_stop103 67
+#define FRAME_stop104 68
+#define FRAME_stop105 69
+#define FRAME_stop106 70
+#define FRAME_stop107 71
+#define FRAME_stop108 72
+#define FRAME_stop109 73
+#define FRAME_stop201 74
+#define FRAME_stop202 75
+#define FRAME_stop203 76
+#define FRAME_stop204 77
+#define FRAME_stop205 78
+#define FRAME_stop206 79
+#define FRAME_stop207 80
+#define FRAME_stop208 81
+#define FRAME_takeof01 82
+#define FRAME_takeof02 83
+#define FRAME_takeof03 84
+#define FRAME_takeof04 85
+#define FRAME_takeof05 86
+#define FRAME_takeof06 87
+#define FRAME_takeof07 88
+#define FRAME_takeof08 89
+#define FRAME_takeof09 90
+#define FRAME_takeof10 91
+#define FRAME_takeof11 92
+#define FRAME_takeof12 93
+#define FRAME_takeof13 94
+#define FRAME_takeof14 95
+#define FRAME_takeof15 96
+#define FRAME_takeof16 97
+#define FRAME_takeof17 98
+#define FRAME_takeof18 99
+#define FRAME_takeof19 100
+#define FRAME_takeof20 101
+#define FRAME_takeof21 102
+#define FRAME_takeof22 103
+#define FRAME_takeof23 104
+#define FRAME_takeof24 105
+#define FRAME_takeof25 106
+#define FRAME_takeof26 107
+#define FRAME_takeof27 108
+#define FRAME_takeof28 109
+#define FRAME_takeof29 110
+#define FRAME_takeof30 111
+#define FRAME_land01 112
+#define FRAME_pain101 113
+#define FRAME_pain102 114
+#define FRAME_pain103 115
+#define FRAME_pain104 116
+#define FRAME_pain105 117
+#define FRAME_pain106 118
+#define FRAME_pain107 119
+#define FRAME_pain108 120
+#define FRAME_pain109 121
+#define FRAME_pain110 122
+#define FRAME_pain111 123
+#define FRAME_pain112 124
+#define FRAME_pain113 125
+#define FRAME_pain114 126
+#define FRAME_pain115 127
+#define FRAME_pain116 128
+#define FRAME_pain117 129
+#define FRAME_pain118 130
+#define FRAME_pain119 131
+#define FRAME_pain120 132
+#define FRAME_pain121 133
+#define FRAME_pain122 134
+#define FRAME_pain123 135
+#define FRAME_pain124 136
+#define FRAME_pain125 137
+#define FRAME_pain126 138
+#define FRAME_pain127 139
+#define FRAME_pain128 140
+#define FRAME_pain201 141
+#define FRAME_pain202 142
+#define FRAME_pain203 143
+#define FRAME_pain204 144
+#define FRAME_pain205 145
+#define FRAME_pain206 146
+#define FRAME_pain207 147
+#define FRAME_pain208 148
+#define FRAME_pain209 149
+#define FRAME_pain210 150
+#define FRAME_pain211 151
+#define FRAME_pain212 152
+#define FRAME_pain301 153
+#define FRAME_pain302 154
+#define FRAME_pain303 155
+#define FRAME_pain304 156
+#define FRAME_pain305 157
+#define FRAME_pain306 158
+#define FRAME_pain307 159
+#define FRAME_pain308 160
+#define FRAME_pain309 161
+#define FRAME_death101 162
+#define FRAME_death102 163
+#define FRAME_death103 164
+#define FRAME_death104 165
+#define FRAME_death105 166
+#define FRAME_death106 167
+#define FRAME_death107 168
+#define FRAME_death108 169
+#define FRAME_death109 170
+#define FRAME_death110 171
+#define FRAME_death111 172
+#define FRAME_backwd01 173
+#define FRAME_backwd02 174
+#define FRAME_backwd03 175
+#define FRAME_backwd04 176
+#define FRAME_backwd05 177
+#define FRAME_backwd06 178
+#define FRAME_backwd07 179
+#define FRAME_backwd08 180
+#define FRAME_backwd09 181
+#define FRAME_backwd10 182
+#define FRAME_backwd11 183
+#define FRAME_backwd12 184
+#define FRAME_backwd13 185
+#define FRAME_backwd14 186
+#define FRAME_backwd15 187
+#define FRAME_backwd16 188
+#define FRAME_backwd17 189
+#define FRAME_backwd18 190
+#define FRAME_backwd19 191
+#define FRAME_backwd20 192
+#define FRAME_backwd21 193
+#define FRAME_backwd22 194
+#define FRAME_backwd23 195
+#define FRAME_backwd24 196
+#define FRAME_attak101 197
+#define FRAME_attak102 198
+#define FRAME_attak103 199
+#define FRAME_attak104 200
+#define FRAME_attak105 201
+#define FRAME_attak106 202
+#define FRAME_attak107 203
+#define FRAME_attak108 204
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/infantry/infantry.c b/xatrix/src/monster/infantry/infantry.c
new file mode 100644
index 0000000..7148210
--- /dev/null
+++ b/xatrix/src/monster/infantry/infantry.c
@@ -0,0 +1,812 @@
+/* =======================================================================
+ *
+ * Infantry.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "infantry.h"
+
+void InfantryMachineGun(edict_t *self);
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die1;
+static int sound_die2;
+
+static int sound_gunshot;
+static int sound_weapon_cock;
+static int sound_punch_swing;
+static int sound_punch_hit;
+static int sound_sight;
+static int sound_search;
+static int sound_idle;
+
+mframe_t infantry_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t infantry_move_stand = {
+ FRAME_stand50,
+ FRAME_stand71,
+ infantry_frames_stand,
+ NULL
+};
+
+void
+infantry_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &infantry_move_stand;
+}
+
+mframe_t infantry_frames_fidget[] = {
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 3, NULL},
+ {ai_stand, 6, NULL},
+ {ai_stand, 3, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -2, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, -1, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, -3, NULL},
+ {ai_stand, -2, NULL},
+ {ai_stand, -3, NULL},
+ {ai_stand, -3, NULL},
+ {ai_stand, -2, NULL}
+};
+
+mmove_t infantry_move_fidget = {
+ FRAME_stand01,
+ FRAME_stand49,
+ infantry_frames_fidget,
+ infantry_stand
+};
+
+void
+infantry_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &infantry_move_fidget;
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t infantry_frames_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL}
+};
+
+mmove_t infantry_move_walk = {
+ FRAME_walk03,
+ FRAME_walk14,
+ infantry_frames_walk,
+ NULL
+};
+
+void
+infantry_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &infantry_move_walk;
+}
+
+mframe_t infantry_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 20, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 7, NULL},
+ {ai_run, 30, NULL},
+ {ai_run, 35, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 6, NULL}
+};
+
+mmove_t infantry_move_run = {
+ FRAME_run01,
+ FRAME_run08,
+ infantry_frames_run,
+ NULL
+};
+
+void
+infantry_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &infantry_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_run;
+ }
+}
+
+mframe_t infantry_frames_pain1[] = {
+ {ai_move, -3, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t infantry_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain110,
+ infantry_frames_pain1,
+ infantry_run
+};
+
+mframe_t infantry_frames_pain2[] = {
+ {ai_move, -3, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t infantry_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain210,
+ infantry_frames_pain2,
+ infantry_run
+};
+
+void
+infantry_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ n = rand() % 2;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &infantry_move_pain1;
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_pain2;
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+}
+
+vec3_t aimangles[] = {
+ {0.0, 5.0, 0.0},
+ {10.0, 15.0, 0.0},
+ {20.0, 25.0, 0.0},
+ {25.0, 35.0, 0.0},
+ {30.0, 40.0, 0.0},
+ {30.0, 45.0, 0.0},
+ {25.0, 50.0, 0.0},
+ {20.0, 40.0, 0.0},
+ {15.0, 35.0, 0.0},
+ {40.0, 35.0, 0.0},
+ {70.0, 35.0, 0.0},
+ {90.0, 35.0, 0.0}
+};
+
+void
+InfantryMachineGun(edict_t *self)
+{
+ vec3_t start, target;
+ vec3_t forward, right;
+ vec3_t vec;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak103)
+ {
+ flash_number = MZ2_INFANTRY_MACHINEGUN_1;
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ VectorMA(self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+ target[2] += self->enemy->viewheight;
+ VectorSubtract(target, start, forward);
+ VectorNormalize(forward);
+ }
+ else
+ {
+ AngleVectors(self->s.angles, forward, right, NULL);
+ }
+ }
+ else
+ {
+ flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorSubtract(self->s.angles, aimangles[flash_number - MZ2_INFANTRY_MACHINEGUN_2], vec);
+ AngleVectors(vec, forward, NULL, NULL);
+ }
+
+ monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void
+infantry_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+infantry_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ gi.linkentity(self);
+
+ M_FlyCheck(self);
+}
+
+mframe_t infantry_frames_death1[] = {
+ {ai_move, -4, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 9, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -3, NULL}
+};
+
+mmove_t infantry_move_death1 = {
+ FRAME_death101,
+ FRAME_death120,
+ infantry_frames_death1,
+ infantry_dead
+};
+
+/* Off with his head */
+mframe_t infantry_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, InfantryMachineGun},
+ {ai_move, -2, InfantryMachineGun},
+ {ai_move, -3, InfantryMachineGun},
+ {ai_move, -1, InfantryMachineGun},
+ {ai_move, -2, InfantryMachineGun},
+ {ai_move, 0, InfantryMachineGun},
+ {ai_move, 2, InfantryMachineGun},
+ {ai_move, 2, InfantryMachineGun},
+ {ai_move, 3, InfantryMachineGun},
+ {ai_move, -10, InfantryMachineGun},
+ {ai_move, -7, InfantryMachineGun},
+ {ai_move, -8, InfantryMachineGun},
+ {ai_move, -6, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_death2 = {
+ FRAME_death201,
+ FRAME_death225,
+ infantry_frames_death2,
+ infantry_dead
+};
+
+mframe_t infantry_frames_death3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -11, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -11, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_death3 = {
+ FRAME_death301,
+ FRAME_death309,
+ infantry_frames_death3,
+ infantry_dead
+};
+
+void
+infantry_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum = 1; /* switch to bloody skin */
+
+ n = rand() % 3;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &infantry_move_death1;
+ gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+ }
+ else if (n == 1)
+ {
+ self->monsterinfo.currentmove = &infantry_move_death2;
+ gi.sound(self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_death3;
+ gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+infantry_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.pausetime = level.time + 1;
+ gi.linkentity(self);
+}
+
+void
+infantry_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+void
+infantry_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+mframe_t infantry_frames_duck[] = {
+ {ai_move, -2, infantry_duck_down},
+ {ai_move, -5, infantry_duck_hold},
+ {ai_move, 3, NULL},
+ {ai_move, 4, infantry_duck_up},
+ {ai_move, 0, NULL}
+};
+
+mmove_t infantry_move_duck = {
+ FRAME_duck01,
+ FRAME_duck05,
+ infantry_frames_duck,
+ infantry_run
+};
+
+void
+infantry_dodge(edict_t *self, edict_t *attacker, float eta /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if (random() > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ self->monsterinfo.currentmove = &infantry_move_duck;
+}
+
+void
+infantry_set_firetime(edict_t *self)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ n = (rand() & 15) + 5;
+ self->monsterinfo.pausetime = level.time + n * FRAMETIME;
+}
+
+void
+infantry_cock_gun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0);
+}
+
+void
+infantry_fire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ InfantryMachineGun(self);
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+mframe_t infantry_frames_attack1[] = {
+ {ai_charge, 10, infantry_set_firetime},
+ {ai_charge, 6, NULL},
+ {ai_charge, 0, infantry_fire},
+ {ai_charge, 0, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, -7, NULL},
+ {ai_charge, -6, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, 0, infantry_cock_gun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -1, NULL}
+};
+
+mmove_t infantry_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak115,
+ infantry_frames_attack1,
+ infantry_run
+};
+
+void
+infantry_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0);
+}
+
+void
+infantry_smack(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, 0, 0);
+
+ if (fire_hit(self, aim, (5 + (rand() % 5)), 50))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t infantry_frames_attack2[] = {
+ {ai_charge, 3, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 0, infantry_swing},
+ {ai_charge, 8, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 8, infantry_smack},
+ {ai_charge, 6, NULL},
+ {ai_charge, 3, NULL},
+};
+
+mmove_t infantry_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak208,
+ infantry_frames_attack2,
+ infantry_run
+};
+
+void
+infantry_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ self->monsterinfo.currentmove = &infantry_move_attack2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &infantry_move_attack1;
+ }
+}
+
+/*
+ * QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_infantry(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("infantry/infpain1.wav");
+ sound_pain2 = gi.soundindex("infantry/infpain2.wav");
+ sound_die1 = gi.soundindex("infantry/infdeth1.wav");
+ sound_die2 = gi.soundindex("infantry/infdeth2.wav");
+
+ sound_gunshot = gi.soundindex("infantry/infatck1.wav");
+ sound_weapon_cock = gi.soundindex("infantry/infatck3.wav");
+ sound_punch_swing = gi.soundindex("infantry/infatck2.wav");
+ sound_punch_hit = gi.soundindex("infantry/melee2.wav");
+
+ sound_sight = gi.soundindex("infantry/infsght1.wav");
+ sound_search = gi.soundindex("infantry/infsrch1.wav");
+ sound_idle = gi.soundindex("infantry/infidle1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 100;
+ self->gib_health = -40;
+ self->mass = 200;
+
+ self->pain = infantry_pain;
+ self->die = infantry_die;
+
+ self->monsterinfo.stand = infantry_stand;
+ self->monsterinfo.walk = infantry_walk;
+ self->monsterinfo.run = infantry_run;
+ self->monsterinfo.dodge = infantry_dodge;
+ self->monsterinfo.attack = infantry_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = infantry_sight;
+ self->monsterinfo.idle = infantry_fidget;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &infantry_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
+
diff --git a/xatrix/src/monster/infantry/infantry.h b/xatrix/src/monster/infantry/infantry.h
new file mode 100644
index 0000000..b36e5e1
--- /dev/null
+++ b/xatrix/src/monster/infantry/infantry.h
@@ -0,0 +1,216 @@
+/* =======================================================================
+ *
+ * Infantry animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_gun02 0
+#define FRAME_stand01 1
+#define FRAME_stand02 2
+#define FRAME_stand03 3
+#define FRAME_stand04 4
+#define FRAME_stand05 5
+#define FRAME_stand06 6
+#define FRAME_stand07 7
+#define FRAME_stand08 8
+#define FRAME_stand09 9
+#define FRAME_stand10 10
+#define FRAME_stand11 11
+#define FRAME_stand12 12
+#define FRAME_stand13 13
+#define FRAME_stand14 14
+#define FRAME_stand15 15
+#define FRAME_stand16 16
+#define FRAME_stand17 17
+#define FRAME_stand18 18
+#define FRAME_stand19 19
+#define FRAME_stand20 20
+#define FRAME_stand21 21
+#define FRAME_stand22 22
+#define FRAME_stand23 23
+#define FRAME_stand24 24
+#define FRAME_stand25 25
+#define FRAME_stand26 26
+#define FRAME_stand27 27
+#define FRAME_stand28 28
+#define FRAME_stand29 29
+#define FRAME_stand30 30
+#define FRAME_stand31 31
+#define FRAME_stand32 32
+#define FRAME_stand33 33
+#define FRAME_stand34 34
+#define FRAME_stand35 35
+#define FRAME_stand36 36
+#define FRAME_stand37 37
+#define FRAME_stand38 38
+#define FRAME_stand39 39
+#define FRAME_stand40 40
+#define FRAME_stand41 41
+#define FRAME_stand42 42
+#define FRAME_stand43 43
+#define FRAME_stand44 44
+#define FRAME_stand45 45
+#define FRAME_stand46 46
+#define FRAME_stand47 47
+#define FRAME_stand48 48
+#define FRAME_stand49 49
+#define FRAME_stand50 50
+#define FRAME_stand51 51
+#define FRAME_stand52 52
+#define FRAME_stand53 53
+#define FRAME_stand54 54
+#define FRAME_stand55 55
+#define FRAME_stand56 56
+#define FRAME_stand57 57
+#define FRAME_stand58 58
+#define FRAME_stand59 59
+#define FRAME_stand60 60
+#define FRAME_stand61 61
+#define FRAME_stand62 62
+#define FRAME_stand63 63
+#define FRAME_stand64 64
+#define FRAME_stand65 65
+#define FRAME_stand66 66
+#define FRAME_stand67 67
+#define FRAME_stand68 68
+#define FRAME_stand69 69
+#define FRAME_stand70 70
+#define FRAME_stand71 71
+#define FRAME_walk01 72
+#define FRAME_walk02 73
+#define FRAME_walk03 74
+#define FRAME_walk04 75
+#define FRAME_walk05 76
+#define FRAME_walk06 77
+#define FRAME_walk07 78
+#define FRAME_walk08 79
+#define FRAME_walk09 80
+#define FRAME_walk10 81
+#define FRAME_walk11 82
+#define FRAME_walk12 83
+#define FRAME_walk13 84
+#define FRAME_walk14 85
+#define FRAME_walk15 86
+#define FRAME_walk16 87
+#define FRAME_walk17 88
+#define FRAME_walk18 89
+#define FRAME_walk19 90
+#define FRAME_walk20 91
+#define FRAME_run01 92
+#define FRAME_run02 93
+#define FRAME_run03 94
+#define FRAME_run04 95
+#define FRAME_run05 96
+#define FRAME_run06 97
+#define FRAME_run07 98
+#define FRAME_run08 99
+#define FRAME_pain101 100
+#define FRAME_pain102 101
+#define FRAME_pain103 102
+#define FRAME_pain104 103
+#define FRAME_pain105 104
+#define FRAME_pain106 105
+#define FRAME_pain107 106
+#define FRAME_pain108 107
+#define FRAME_pain109 108
+#define FRAME_pain110 109
+#define FRAME_pain201 110
+#define FRAME_pain202 111
+#define FRAME_pain203 112
+#define FRAME_pain204 113
+#define FRAME_pain205 114
+#define FRAME_pain206 115
+#define FRAME_pain207 116
+#define FRAME_pain208 117
+#define FRAME_pain209 118
+#define FRAME_pain210 119
+#define FRAME_duck01 120
+#define FRAME_duck02 121
+#define FRAME_duck03 122
+#define FRAME_duck04 123
+#define FRAME_duck05 124
+#define FRAME_death101 125
+#define FRAME_death102 126
+#define FRAME_death103 127
+#define FRAME_death104 128
+#define FRAME_death105 129
+#define FRAME_death106 130
+#define FRAME_death107 131
+#define FRAME_death108 132
+#define FRAME_death109 133
+#define FRAME_death110 134
+#define FRAME_death111 135
+#define FRAME_death112 136
+#define FRAME_death113 137
+#define FRAME_death114 138
+#define FRAME_death115 139
+#define FRAME_death116 140
+#define FRAME_death117 141
+#define FRAME_death118 142
+#define FRAME_death119 143
+#define FRAME_death120 144
+#define FRAME_death201 145
+#define FRAME_death202 146
+#define FRAME_death203 147
+#define FRAME_death204 148
+#define FRAME_death205 149
+#define FRAME_death206 150
+#define FRAME_death207 151
+#define FRAME_death208 152
+#define FRAME_death209 153
+#define FRAME_death210 154
+#define FRAME_death211 155
+#define FRAME_death212 156
+#define FRAME_death213 157
+#define FRAME_death214 158
+#define FRAME_death215 159
+#define FRAME_death216 160
+#define FRAME_death217 161
+#define FRAME_death218 162
+#define FRAME_death219 163
+#define FRAME_death220 164
+#define FRAME_death221 165
+#define FRAME_death222 166
+#define FRAME_death223 167
+#define FRAME_death224 168
+#define FRAME_death225 169
+#define FRAME_death301 170
+#define FRAME_death302 171
+#define FRAME_death303 172
+#define FRAME_death304 173
+#define FRAME_death305 174
+#define FRAME_death306 175
+#define FRAME_death307 176
+#define FRAME_death308 177
+#define FRAME_death309 178
+#define FRAME_block01 179
+#define FRAME_block02 180
+#define FRAME_block03 181
+#define FRAME_block04 182
+#define FRAME_block05 183
+#define FRAME_attak101 184
+#define FRAME_attak102 185
+#define FRAME_attak103 186
+#define FRAME_attak104 187
+#define FRAME_attak105 188
+#define FRAME_attak106 189
+#define FRAME_attak107 190
+#define FRAME_attak108 191
+#define FRAME_attak109 192
+#define FRAME_attak110 193
+#define FRAME_attak111 194
+#define FRAME_attak112 195
+#define FRAME_attak113 196
+#define FRAME_attak114 197
+#define FRAME_attak115 198
+#define FRAME_attak201 199
+#define FRAME_attak202 200
+#define FRAME_attak203 201
+#define FRAME_attak204 202
+#define FRAME_attak205 203
+#define FRAME_attak206 204
+#define FRAME_attak207 205
+#define FRAME_attak208 206
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/insane/insane.c b/xatrix/src/monster/insane/insane.c
new file mode 100644
index 0000000..e4549ff
--- /dev/null
+++ b/xatrix/src/monster/insane/insane.c
@@ -0,0 +1,934 @@
+/* =======================================================================
+ *
+ * The insane earth soldiers.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "insane.h"
+
+#define SPAWNFLAG_CRUSIFIED 8
+
+static int sound_fist;
+static int sound_shake;
+static int sound_moan;
+static int sound_scream[8];
+
+void insane_stand(edict_t *self);
+void insane_dead(edict_t *self);
+void insane_cross(edict_t *self);
+void insane_walk(edict_t *self);
+void insane_run(edict_t *self);
+void insane_checkdown(edict_t *self);
+void insane_checkup(edict_t *self);
+void insane_onground(edict_t *self);
+
+void
+insane_fist(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0);
+}
+
+void
+insane_shake(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0);
+}
+
+void
+insane_moan(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* suppress screaming so pain sound can play */
+ if (self->fly_sound_debounce_time > level.time)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0);
+}
+
+void
+insane_scream(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* suppress screaming so pain sound can play */
+ if (self->fly_sound_debounce_time > level.time)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_scream[rand() % 8], 1, ATTN_IDLE, 0);
+}
+
+mframe_t insane_frames_stand_normal[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, insane_checkdown}
+};
+
+mmove_t insane_move_stand_normal = {
+ FRAME_stand60,
+ FRAME_stand65,
+ insane_frames_stand_normal,
+ insane_stand
+};
+
+mframe_t insane_frames_stand_insane[] = {
+ {ai_stand, 0, insane_shake},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, insane_checkdown}
+};
+
+mmove_t insane_move_stand_insane = {
+ FRAME_stand65,
+ FRAME_stand94,
+ insane_frames_stand_insane,
+ insane_stand
+};
+
+mframe_t insane_frames_uptodown[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_moan},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 2.7, NULL},
+ {ai_move, 4.1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 7.6, NULL},
+ {ai_move, 3.6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_fist},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_fist},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_uptodown = {
+ FRAME_stand1,
+ FRAME_stand40,
+ insane_frames_uptodown,
+ insane_onground
+};
+
+mframe_t insane_frames_downtoup[] = {
+ {ai_move, -0.7, NULL}, /* 41 */
+ {ai_move, -1.2, NULL}, /* 42 */
+ {ai_move, -1.5, NULL}, /* 43 */
+ {ai_move, -4.5, NULL}, /* 44 */
+ {ai_move, -3.5, NULL}, /* 45 */
+ {ai_move, -0.2, NULL}, /* 46 */
+ {ai_move, 0, NULL}, /* 47 */
+ {ai_move, -1.3, NULL}, /* 48 */
+ {ai_move, -3, NULL}, /* 49 */
+ {ai_move, -2, NULL}, /* 50 */
+ {ai_move, 0, NULL}, /* 51 */
+ {ai_move, 0, NULL}, /* 52 */
+ {ai_move, 0, NULL}, /* 53 */
+ {ai_move, -3.3, NULL}, /* 54 */
+ {ai_move, -1.6, NULL}, /* 55 */
+ {ai_move, -0.3, NULL}, /* 56 */
+ {ai_move, 0, NULL}, /* 57 */
+ {ai_move, 0, NULL}, /* 58 */
+ {ai_move, 0, NULL} /* 59 */
+};
+
+mmove_t insane_move_downtoup = {
+ FRAME_stand41,
+ FRAME_stand59,
+ insane_frames_downtoup,
+ insane_stand
+};
+
+mframe_t insane_frames_jumpdown[] = {
+ {ai_move, 0.2, NULL},
+ {ai_move, 11.5, NULL},
+ {ai_move, 5.1, NULL},
+ {ai_move, 7.1, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_jumpdown = {
+ FRAME_stand96,
+ FRAME_stand100,
+ insane_frames_jumpdown,
+ insane_onground
+};
+
+mframe_t insane_frames_down[] = {
+ {ai_move, 0, NULL}, /* 100 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 110 */
+ {ai_move, -1.7, NULL},
+ {ai_move, -1.6, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_fist},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 120 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 130 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, insane_moan},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 140 */
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}, /* 150 */
+ {ai_move, 0.5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -0.2, insane_scream},
+ {ai_move, 0, NULL},
+ {ai_move, 0.2, NULL},
+ {ai_move, 0.4, NULL},
+ {ai_move, 0.6, NULL},
+ {ai_move, 0.8, NULL},
+ {ai_move, 0.7, NULL},
+ {ai_move, 0, insane_checkup} /* 160 */
+};
+
+mmove_t insane_move_down = {
+ FRAME_stand100,
+ FRAME_stand160,
+ insane_frames_down,
+ insane_onground
+};
+
+mframe_t insane_frames_walk_normal[] = {
+ {ai_walk, 0, insane_scream},
+ {ai_walk, 2.5, NULL},
+ {ai_walk, 3.5, NULL},
+ {ai_walk, 1.7, NULL},
+ {ai_walk, 2.3, NULL},
+ {ai_walk, 2.4, NULL},
+ {ai_walk, 2.2, NULL},
+ {ai_walk, 4.2, NULL},
+ {ai_walk, 5.6, NULL},
+ {ai_walk, 3.3, NULL},
+ {ai_walk, 2.4, NULL},
+ {ai_walk, 0.9, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t insane_move_walk_normal = {
+ FRAME_walk27,
+ FRAME_walk39,
+ insane_frames_walk_normal,
+ insane_walk
+};
+
+mmove_t insane_move_run_normal = {
+ FRAME_walk27,
+ FRAME_walk39,
+ insane_frames_walk_normal,
+ insane_run
+};
+
+mframe_t insane_frames_walk_insane[] = {
+ {ai_walk, 0, insane_scream}, /* walk 1 */
+ {ai_walk, 3.4, NULL}, /* walk 2 */
+ {ai_walk, 3.6, NULL}, /* 3 */
+ {ai_walk, 2.9, NULL}, /* 4 */
+ {ai_walk, 2.2, NULL}, /* 5 */
+ {ai_walk, 2.6, NULL}, /* 6 */
+ {ai_walk, 0, NULL}, /* 7 */
+ {ai_walk, 0.7, NULL}, /* 8 */
+ {ai_walk, 4.8, NULL}, /* 9 */
+ {ai_walk, 5.3, NULL}, /* 10 */
+ {ai_walk, 1.1, NULL}, /* 11 */
+ {ai_walk, 2, NULL}, /* 12 */
+ {ai_walk, 0.5, NULL}, /* 13 */
+ {ai_walk, 0, NULL}, /* 14 */
+ {ai_walk, 0, NULL}, /* 15 */
+ {ai_walk, 4.9, NULL}, /* 16 */
+ {ai_walk, 6.7, NULL}, /* 17 */
+ {ai_walk, 3.8, NULL}, /* 18 */
+ {ai_walk, 2, NULL}, /* 19 */
+ {ai_walk, 0.2, NULL}, /* 20 */
+ {ai_walk, 0, NULL}, /* 21 */
+ {ai_walk, 3.4, NULL}, /* 22 */
+ {ai_walk, 6.4, NULL}, /* 23 */
+ {ai_walk, 5, NULL}, /* 24 */
+ {ai_walk, 1.8, NULL}, /* 25 */
+ {ai_walk, 0, NULL} /* 26 */
+};
+
+mmove_t insane_move_walk_insane = {
+ FRAME_walk1,
+ FRAME_walk26,
+ insane_frames_walk_insane,
+ insane_walk
+};
+
+mmove_t insane_move_run_insane = {
+ FRAME_walk1,
+ FRAME_walk26,
+ insane_frames_walk_insane,
+ insane_run
+};
+
+mframe_t insane_frames_stand_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_stand_pain = {
+ FRAME_st_pain2,
+ FRAME_st_pain12,
+ insane_frames_stand_pain,
+ insane_run
+};
+
+mframe_t insane_frames_stand_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_stand_death = {
+ FRAME_st_death2,
+ FRAME_st_death18,
+ insane_frames_stand_death,
+ insane_dead
+};
+
+mframe_t insane_frames_crawl[] = {
+ {ai_walk, 0, insane_scream},
+ {ai_walk, 1.5, NULL},
+ {ai_walk, 2.1, NULL},
+ {ai_walk, 3.6, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 0.9, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 3.4, NULL},
+ {ai_walk, 2.4, NULL}
+};
+
+mmove_t insane_move_crawl = {
+ FRAME_crawl1,
+ FRAME_crawl9,
+ insane_frames_crawl,
+ NULL
+};
+
+mmove_t insane_move_runcrawl = {
+ FRAME_crawl1,
+ FRAME_crawl9,
+ insane_frames_crawl,
+ NULL
+};
+
+mframe_t insane_frames_crawl_pain[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_crawl_pain = {
+ FRAME_cr_pain2,
+ FRAME_cr_pain10,
+ insane_frames_crawl_pain,
+ insane_run
+};
+
+mframe_t insane_frames_crawl_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_crawl_death = {
+ FRAME_cr_death10,
+ FRAME_cr_death16,
+ insane_frames_crawl_death,
+ insane_dead
+};
+
+mframe_t insane_frames_cross[] = {
+ {ai_move, 0, insane_moan},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_cross = {
+ FRAME_cross1,
+ FRAME_cross15,
+ insane_frames_cross,
+ insane_cross
+};
+
+mframe_t insane_frames_struggle_cross[] = {
+ {ai_move, 0, insane_scream},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t insane_move_struggle_cross = {
+ FRAME_cross16,
+ FRAME_cross30,
+ insane_frames_struggle_cross,
+ insane_cross
+};
+
+void
+insane_cross(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.8)
+ {
+ self->monsterinfo.currentmove = &insane_move_cross;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_struggle_cross;
+ }
+}
+
+void
+insane_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 16) /* Hold Ground? */
+ {
+ if (self->s.frame == FRAME_cr_pain10)
+ {
+ self->monsterinfo.currentmove = &insane_move_down;
+ return;
+ }
+ }
+
+ if (self->spawnflags & 4)
+ {
+ self->monsterinfo.currentmove = &insane_move_crawl;
+ }
+ else
+ if (random() <= 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_walk_normal;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_walk_insane;
+ }
+}
+
+void
+insane_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 16) /* Hold Ground? */
+ {
+ if (self->s.frame == FRAME_cr_pain10)
+ {
+ self->monsterinfo.currentmove = &insane_move_down;
+ return;
+ }
+ }
+
+ if (self->spawnflags & 4) /* Crawling? */
+ {
+ self->monsterinfo.currentmove = &insane_move_runcrawl;
+ }
+ else
+ if (random() <= 0.5) /* Else, mix it up */
+ {
+ self->monsterinfo.currentmove = &insane_move_run_normal;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_run_insane;
+ }
+}
+
+void
+insane_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ int l, r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ r = 1 + (rand() & 1);
+
+ if (self->health < 25)
+ {
+ l = 25;
+ }
+ else if (self->health < 50)
+ {
+ l = 50;
+ }
+ else if (self->health < 75)
+ {
+ l = 75;
+ }
+ else
+ {
+ l = 100;
+ }
+
+ gi.sound(self, CHAN_VOICE,
+ gi.soundindex(va("player/male/pain%i_%i.wav", l, r)), 1, ATTN_IDLE, 0);
+
+ /* suppress screaming and moaning for 1 second so pain sound plays */
+ self->fly_sound_debounce_time = level.time + 1;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ /* Don't go into pain frames if crucified. */
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED)
+ {
+ self->monsterinfo.currentmove = &insane_move_struggle_cross;
+ return;
+ }
+
+ if (((self->s.frame >= FRAME_crawl1) &&
+ (self->s.frame <= FRAME_crawl9)) ||
+ ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)))
+ {
+ self->monsterinfo.currentmove = &insane_move_crawl_pain;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_pain;
+ }
+}
+
+void
+insane_onground(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &insane_move_down;
+}
+
+void
+insane_checkdown(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & 32) /* Always stand */
+ {
+ return;
+ }
+
+ if (random() < 0.3)
+ {
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_uptodown;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_jumpdown;
+ }
+ }
+}
+
+void
+insane_checkup(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->spawnflags & 4) && (self->spawnflags & 16))
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_downtoup;
+ }
+}
+
+void
+insane_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED) /* If crucified */
+ {
+ self->monsterinfo.currentmove = &insane_move_cross;
+ self->monsterinfo.aiflags |= AI_STAND_GROUND;
+ }
+ else if ((self->spawnflags & 4) && (self->spawnflags & 16))
+ {
+ self->monsterinfo.currentmove = &insane_move_down;
+ }
+ else
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_normal;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_insane;
+ }
+}
+
+void
+insane_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED)
+ {
+ self->flags |= FL_FLY;
+ }
+ else
+ {
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ }
+
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+insane_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_IDLE, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav",
+ (rand() % 4) + 1)), 1, ATTN_IDLE, 0);
+
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED)
+ {
+ insane_dead(self);
+ }
+ else
+ {
+ if (((self->s.frame >= FRAME_crawl1) &&
+ (self->s.frame <= FRAME_crawl9)) ||
+ ((self->s.frame >= FRAME_stand99) &&
+ (self->s.frame <= FRAME_stand160)))
+ {
+ self->monsterinfo.currentmove = &insane_move_crawl_death;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &insane_move_stand_death;
+ }
+ }
+}
+
+/*
+ * QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND
+ */
+void
+SP_misc_insane(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_fist = gi.soundindex("insane/insane11.wav");
+ sound_shake = gi.soundindex("insane/insane5.wav");
+ sound_moan = gi.soundindex("insane/insane7.wav");
+ sound_scream[0] = gi.soundindex("insane/insane1.wav");
+ sound_scream[1] = gi.soundindex("insane/insane2.wav");
+ sound_scream[2] = gi.soundindex("insane/insane3.wav");
+ sound_scream[3] = gi.soundindex("insane/insane4.wav");
+ sound_scream[4] = gi.soundindex("insane/insane6.wav");
+ sound_scream[5] = gi.soundindex("insane/insane8.wav");
+ sound_scream[6] = gi.soundindex("insane/insane9.wav");
+ sound_scream[7] = gi.soundindex("insane/insane10.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2");
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+
+ self->health = 100;
+ self->gib_health = -50;
+ self->mass = 300;
+
+ self->pain = insane_pain;
+ self->die = insane_die;
+
+ self->monsterinfo.stand = insane_stand;
+ self->monsterinfo.walk = insane_walk;
+ self->monsterinfo.run = insane_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = NULL;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = NULL;
+ self->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+ gi.linkentity(self);
+
+ if (self->spawnflags & 16) /* Stand Ground */
+ {
+ self->monsterinfo.aiflags |= AI_STAND_GROUND;
+ }
+
+ self->monsterinfo.currentmove = &insane_move_stand_normal;
+
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ if (self->spawnflags & SPAWNFLAG_CRUSIFIED) /* Crucified ? */
+ {
+ VectorSet(self->mins, -16, 0, 0);
+ VectorSet(self->maxs, 16, 8, 32);
+ self->flags |= FL_NO_KNOCKBACK;
+ flymonster_start(self);
+ }
+ else
+ {
+ walkmonster_start(self);
+ self->s.skinnum = rand() % 3;
+ }
+}
diff --git a/xatrix/src/monster/insane/insane.h b/xatrix/src/monster/insane/insane.h
new file mode 100644
index 0000000..96e99bf
--- /dev/null
+++ b/xatrix/src/monster/insane/insane.h
@@ -0,0 +1,291 @@
+/* =======================================================================
+ *
+ * Insane animations
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand1 0
+#define FRAME_stand2 1
+#define FRAME_stand3 2
+#define FRAME_stand4 3
+#define FRAME_stand5 4
+#define FRAME_stand6 5
+#define FRAME_stand7 6
+#define FRAME_stand8 7
+#define FRAME_stand9 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_stand31 30
+#define FRAME_stand32 31
+#define FRAME_stand33 32
+#define FRAME_stand34 33
+#define FRAME_stand35 34
+#define FRAME_stand36 35
+#define FRAME_stand37 36
+#define FRAME_stand38 37
+#define FRAME_stand39 38
+#define FRAME_stand40 39
+#define FRAME_stand41 40
+#define FRAME_stand42 41
+#define FRAME_stand43 42
+#define FRAME_stand44 43
+#define FRAME_stand45 44
+#define FRAME_stand46 45
+#define FRAME_stand47 46
+#define FRAME_stand48 47
+#define FRAME_stand49 48
+#define FRAME_stand50 49
+#define FRAME_stand51 50
+#define FRAME_stand52 51
+#define FRAME_stand53 52
+#define FRAME_stand54 53
+#define FRAME_stand55 54
+#define FRAME_stand56 55
+#define FRAME_stand57 56
+#define FRAME_stand58 57
+#define FRAME_stand59 58
+#define FRAME_stand60 59
+#define FRAME_stand61 60
+#define FRAME_stand62 61
+#define FRAME_stand63 62
+#define FRAME_stand64 63
+#define FRAME_stand65 64
+#define FRAME_stand66 65
+#define FRAME_stand67 66
+#define FRAME_stand68 67
+#define FRAME_stand69 68
+#define FRAME_stand70 69
+#define FRAME_stand71 70
+#define FRAME_stand72 71
+#define FRAME_stand73 72
+#define FRAME_stand74 73
+#define FRAME_stand75 74
+#define FRAME_stand76 75
+#define FRAME_stand77 76
+#define FRAME_stand78 77
+#define FRAME_stand79 78
+#define FRAME_stand80 79
+#define FRAME_stand81 80
+#define FRAME_stand82 81
+#define FRAME_stand83 82
+#define FRAME_stand84 83
+#define FRAME_stand85 84
+#define FRAME_stand86 85
+#define FRAME_stand87 86
+#define FRAME_stand88 87
+#define FRAME_stand89 88
+#define FRAME_stand90 89
+#define FRAME_stand91 90
+#define FRAME_stand92 91
+#define FRAME_stand93 92
+#define FRAME_stand94 93
+#define FRAME_stand95 94
+#define FRAME_stand96 95
+#define FRAME_stand97 96
+#define FRAME_stand98 97
+#define FRAME_stand99 98
+#define FRAME_stand100 99
+#define FRAME_stand101 100
+#define FRAME_stand102 101
+#define FRAME_stand103 102
+#define FRAME_stand104 103
+#define FRAME_stand105 104
+#define FRAME_stand106 105
+#define FRAME_stand107 106
+#define FRAME_stand108 107
+#define FRAME_stand109 108
+#define FRAME_stand110 109
+#define FRAME_stand111 110
+#define FRAME_stand112 111
+#define FRAME_stand113 112
+#define FRAME_stand114 113
+#define FRAME_stand115 114
+#define FRAME_stand116 115
+#define FRAME_stand117 116
+#define FRAME_stand118 117
+#define FRAME_stand119 118
+#define FRAME_stand120 119
+#define FRAME_stand121 120
+#define FRAME_stand122 121
+#define FRAME_stand123 122
+#define FRAME_stand124 123
+#define FRAME_stand125 124
+#define FRAME_stand126 125
+#define FRAME_stand127 126
+#define FRAME_stand128 127
+#define FRAME_stand129 128
+#define FRAME_stand130 129
+#define FRAME_stand131 130
+#define FRAME_stand132 131
+#define FRAME_stand133 132
+#define FRAME_stand134 133
+#define FRAME_stand135 134
+#define FRAME_stand136 135
+#define FRAME_stand137 136
+#define FRAME_stand138 137
+#define FRAME_stand139 138
+#define FRAME_stand140 139
+#define FRAME_stand141 140
+#define FRAME_stand142 141
+#define FRAME_stand143 142
+#define FRAME_stand144 143
+#define FRAME_stand145 144
+#define FRAME_stand146 145
+#define FRAME_stand147 146
+#define FRAME_stand148 147
+#define FRAME_stand149 148
+#define FRAME_stand150 149
+#define FRAME_stand151 150
+#define FRAME_stand152 151
+#define FRAME_stand153 152
+#define FRAME_stand154 153
+#define FRAME_stand155 154
+#define FRAME_stand156 155
+#define FRAME_stand157 156
+#define FRAME_stand158 157
+#define FRAME_stand159 158
+#define FRAME_stand160 159
+#define FRAME_walk27 160
+#define FRAME_walk28 161
+#define FRAME_walk29 162
+#define FRAME_walk30 163
+#define FRAME_walk31 164
+#define FRAME_walk32 165
+#define FRAME_walk33 166
+#define FRAME_walk34 167
+#define FRAME_walk35 168
+#define FRAME_walk36 169
+#define FRAME_walk37 170
+#define FRAME_walk38 171
+#define FRAME_walk39 172
+#define FRAME_walk1 173
+#define FRAME_walk2 174
+#define FRAME_walk3 175
+#define FRAME_walk4 176
+#define FRAME_walk5 177
+#define FRAME_walk6 178
+#define FRAME_walk7 179
+#define FRAME_walk8 180
+#define FRAME_walk9 181
+#define FRAME_walk10 182
+#define FRAME_walk11 183
+#define FRAME_walk12 184
+#define FRAME_walk13 185
+#define FRAME_walk14 186
+#define FRAME_walk15 187
+#define FRAME_walk16 188
+#define FRAME_walk17 189
+#define FRAME_walk18 190
+#define FRAME_walk19 191
+#define FRAME_walk20 192
+#define FRAME_walk21 193
+#define FRAME_walk22 194
+#define FRAME_walk23 195
+#define FRAME_walk24 196
+#define FRAME_walk25 197
+#define FRAME_walk26 198
+#define FRAME_st_pain2 199
+#define FRAME_st_pain3 200
+#define FRAME_st_pain4 201
+#define FRAME_st_pain5 202
+#define FRAME_st_pain6 203
+#define FRAME_st_pain7 204
+#define FRAME_st_pain8 205
+#define FRAME_st_pain9 206
+#define FRAME_st_pain10 207
+#define FRAME_st_pain11 208
+#define FRAME_st_pain12 209
+#define FRAME_st_death2 210
+#define FRAME_st_death3 211
+#define FRAME_st_death4 212
+#define FRAME_st_death5 213
+#define FRAME_st_death6 214
+#define FRAME_st_death7 215
+#define FRAME_st_death8 216
+#define FRAME_st_death9 217
+#define FRAME_st_death10 218
+#define FRAME_st_death11 219
+#define FRAME_st_death12 220
+#define FRAME_st_death13 221
+#define FRAME_st_death14 222
+#define FRAME_st_death15 223
+#define FRAME_st_death16 224
+#define FRAME_st_death17 225
+#define FRAME_st_death18 226
+#define FRAME_crawl1 227
+#define FRAME_crawl2 228
+#define FRAME_crawl3 229
+#define FRAME_crawl4 230
+#define FRAME_crawl5 231
+#define FRAME_crawl6 232
+#define FRAME_crawl7 233
+#define FRAME_crawl8 234
+#define FRAME_crawl9 235
+#define FRAME_cr_pain2 236
+#define FRAME_cr_pain3 237
+#define FRAME_cr_pain4 238
+#define FRAME_cr_pain5 239
+#define FRAME_cr_pain6 240
+#define FRAME_cr_pain7 241
+#define FRAME_cr_pain8 242
+#define FRAME_cr_pain9 243
+#define FRAME_cr_pain10 244
+#define FRAME_cr_death10 245
+#define FRAME_cr_death11 246
+#define FRAME_cr_death12 247
+#define FRAME_cr_death13 248
+#define FRAME_cr_death14 249
+#define FRAME_cr_death15 250
+#define FRAME_cr_death16 251
+#define FRAME_cross1 252
+#define FRAME_cross2 253
+#define FRAME_cross3 254
+#define FRAME_cross4 255
+#define FRAME_cross5 256
+#define FRAME_cross6 257
+#define FRAME_cross7 258
+#define FRAME_cross8 259
+#define FRAME_cross9 260
+#define FRAME_cross10 261
+#define FRAME_cross11 262
+#define FRAME_cross12 263
+#define FRAME_cross13 264
+#define FRAME_cross14 265
+#define FRAME_cross15 266
+#define FRAME_cross16 267
+#define FRAME_cross17 268
+#define FRAME_cross18 269
+#define FRAME_cross19 270
+#define FRAME_cross20 271
+#define FRAME_cross21 272
+#define FRAME_cross22 273
+#define FRAME_cross23 274
+#define FRAME_cross24 275
+#define FRAME_cross25 276
+#define FRAME_cross26 277
+#define FRAME_cross27 278
+#define FRAME_cross28 279
+#define FRAME_cross29 280
+#define FRAME_cross30 281
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/medic/medic.c b/xatrix/src/monster/medic/medic.c
new file mode 100644
index 0000000..b578ed0
--- /dev/null
+++ b/xatrix/src/monster/medic/medic.c
@@ -0,0 +1,1016 @@
+/* =======================================================================
+ *
+ * Medic.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "medic.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+static int sound_idle1;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die;
+static int sound_sight;
+static int sound_search;
+static int sound_hook_launch;
+static int sound_hook_hit;
+static int sound_hook_heal;
+static int sound_hook_retract;
+
+void ED_CallSpawn(edict_t *ent);
+
+edict_t *
+medic_FindDeadMonster(edict_t *self)
+{
+ edict_t *ent = NULL;
+ edict_t *best = NULL;
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
+ {
+ if (ent == self)
+ {
+ continue;
+ }
+
+ if (!(ent->svflags & SVF_MONSTER))
+ {
+ continue;
+ }
+
+ if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
+ {
+ continue;
+ }
+
+ if (ent->owner)
+ {
+ continue;
+ }
+
+ if (ent->health > 0)
+ {
+ continue;
+ }
+
+ if (ent->nextthink)
+ {
+ continue;
+ }
+
+ if (!visible(self, ent))
+ {
+ continue;
+ }
+
+ if (!best)
+ {
+ best = ent;
+ continue;
+ }
+
+ if (ent->max_health <= best->max_health)
+ {
+ continue;
+ }
+
+ best = ent;
+ }
+
+ return best;
+}
+
+void
+medic_idle(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+
+ ent = medic_FindDeadMonster(self);
+
+ if (ent)
+ {
+ self->enemy = ent;
+ self->enemy->owner = self;
+ self->monsterinfo.aiflags |= AI_MEDIC;
+ FoundTarget(self);
+ }
+}
+
+void
+medic_search(edict_t *self)
+{
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0);
+
+ if (!self->oldenemy)
+ {
+ ent = medic_FindDeadMonster(self);
+
+ if (ent)
+ {
+ self->oldenemy = self->enemy;
+ self->enemy = ent;
+ self->enemy->owner = self;
+ self->monsterinfo.aiflags |= AI_MEDIC;
+ FoundTarget(self);
+ }
+ }
+}
+
+void
+medic_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+mframe_t medic_frames_stand[] = {
+ {ai_stand, 0, medic_idle},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+};
+
+mmove_t medic_move_stand = {
+ FRAME_wait1,
+ FRAME_wait90,
+ medic_frames_stand,
+ NULL
+};
+
+void
+medic_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &medic_move_stand;
+}
+
+mframe_t medic_frames_walk[] = {
+ {ai_walk, 6.2, NULL},
+ {ai_walk, 18.1, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 11, NULL},
+ {ai_walk, 11.6, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 9.9, NULL},
+ {ai_walk, 14, NULL},
+ {ai_walk, 9.3, NULL}
+};
+
+mmove_t medic_move_walk = {
+ FRAME_walk1,
+ FRAME_walk12,
+ medic_frames_walk,
+ NULL
+};
+
+void
+medic_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &medic_move_walk;
+}
+
+mframe_t medic_frames_run[] = {
+ {ai_run, 18, NULL},
+ {ai_run, 22.5, NULL},
+ {ai_run, 25.4, NULL},
+ {ai_run, 23.4, NULL},
+ {ai_run, 24, NULL},
+ {ai_run, 35.6, NULL}
+};
+
+mmove_t medic_move_run = {
+ FRAME_run1,
+ FRAME_run6,
+ medic_frames_run,
+ NULL
+};
+
+void
+medic_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!(self->monsterinfo.aiflags & AI_MEDIC))
+ {
+ edict_t *ent;
+
+ ent = medic_FindDeadMonster(self);
+
+ if (ent)
+ {
+ self->oldenemy = self->enemy;
+ self->enemy = ent;
+ self->enemy->owner = self;
+ self->monsterinfo.aiflags |= AI_MEDIC;
+ FoundTarget(self);
+ return;
+ }
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &medic_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_run;
+ }
+}
+
+mframe_t medic_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t medic_move_pain1 = {
+ FRAME_paina1,
+ FRAME_paina8,
+ medic_frames_pain1,
+ medic_run
+};
+
+mframe_t medic_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t medic_move_pain2 = {
+ FRAME_painb1,
+ FRAME_painb15,
+ medic_frames_pain2,
+ medic_run
+};
+
+void
+medic_pain(edict_t *self, edict_t *other /* unused */,
+ float kick, int damage /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &medic_move_pain1;
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_pain2;
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+medic_fire_blaster(edict_t *self)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t end;
+ vec3_t dir;
+ int effect;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12))
+ {
+ effect = EF_BLASTER;
+ }
+ else if ((self->s.frame == FRAME_attack19) ||
+ (self->s.frame == FRAME_attack22) ||
+ (self->s.frame == FRAME_attack25) ||
+ (self->s.frame == FRAME_attack28))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 2, 1000, MZ2_MEDIC_BLASTER_1, effect);
+}
+
+void
+medic_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t medic_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t medic_move_death = {
+ FRAME_death1,
+ FRAME_death30,
+ medic_frames_death,
+ medic_dead
+};
+
+void
+medic_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* if we had a pending patient, free him up for another medic */
+ if ((self->enemy) && (self->enemy->owner == self))
+ {
+ self->enemy->owner = NULL;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ self->monsterinfo.currentmove = &medic_move_death;
+}
+
+void
+medic_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.pausetime = level.time + 1;
+ gi.linkentity(self);
+}
+
+void
+medic_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+void
+medic_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+mframe_t medic_frames_duck[] = {
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, medic_duck_down},
+ {ai_move, -1, medic_duck_hold},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, medic_duck_up},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL}
+};
+
+mmove_t medic_move_duck = {
+ FRAME_duck1,
+ FRAME_duck16,
+ medic_frames_duck,
+ medic_run
+};
+
+void
+medic_dodge(edict_t *self, edict_t *attacker, float eta /* unused */)
+{
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ if (random() > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ self->monsterinfo.currentmove = &medic_move_duck;
+}
+
+mframe_t medic_frames_attackHyperBlaster[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, medic_fire_blaster}
+};
+
+mmove_t medic_move_attackHyperBlaster = {
+ FRAME_attack15,
+ FRAME_attack30,
+ medic_frames_attackHyperBlaster,
+ medic_run
+};
+
+void
+medic_continue(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.95)
+ {
+ self->monsterinfo.currentmove = &medic_move_attackHyperBlaster;
+ }
+ }
+}
+
+mframe_t medic_frames_attackBlaster[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_fire_blaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, medic_continue}
+};
+
+mmove_t medic_move_attackBlaster = {
+ FRAME_attack1,
+ FRAME_attack14,
+ medic_frames_attackBlaster,
+ medic_run
+};
+
+void
+medic_hook_launch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0);
+}
+
+static vec3_t medic_cable_offsets[] = {
+ {45.0, -9.2, 15.5},
+ {48.4, -9.7, 15.2},
+ {47.8, -9.8, 15.8},
+ {47.3, -9.3, 14.3},
+ {45.4, -10.1, 13.1},
+ {41.9, -12.7, 12.0},
+ {37.8, -15.8, 11.2},
+ {34.3, -18.4, 10.7},
+ {32.7, -19.7, 10.4},
+ {32.7, -19.7, 10.4}
+};
+
+void
+medic_cable_attack(edict_t *self)
+{
+ vec3_t offset, start, end, f, r;
+ trace_t tr;
+ vec3_t dir, angles;
+ float distance;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy->inuse)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorCopy(medic_cable_offsets[self->s.frame - FRAME_attack42], offset);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ /* check for max distance */
+ VectorSubtract(start, self->enemy->s.origin, dir);
+ distance = VectorLength(dir);
+
+ if (distance > 256)
+ {
+ return;
+ }
+
+ /* check for min/max pitch */
+ vectoangles(dir, angles);
+
+ if (angles[0] < -180)
+ {
+ angles[0] += 360;
+ }
+
+ if (fabs(angles[0]) > 45)
+ {
+ return;
+ }
+
+ tr = gi.trace(start, NULL, NULL, self->enemy->s.origin, self, MASK_SHOT);
+
+ if ((tr.fraction != 1.0) && (tr.ent != self->enemy))
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attack43)
+ {
+ gi.sound(self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0);
+ self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+ }
+ else if (self->s.frame == FRAME_attack50)
+ {
+ self->enemy->spawnflags = 0;
+ self->enemy->monsterinfo.aiflags = 0;
+ self->enemy->target = NULL;
+ self->enemy->targetname = NULL;
+ self->enemy->combattarget = NULL;
+ self->enemy->deathtarget = NULL;
+ self->enemy->owner = self;
+ ED_CallSpawn(self->enemy);
+ self->enemy->owner = NULL;
+
+ if (self->enemy->think)
+ {
+ self->enemy->nextthink = level.time;
+ self->enemy->think(self->enemy);
+ }
+
+ self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+
+ if (self->oldenemy && self->oldenemy->client)
+ {
+ self->enemy->enemy = self->oldenemy;
+ FoundTarget(self->enemy);
+ }
+ }
+ else
+ {
+ if (self->s.frame == FRAME_attack44)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0);
+ }
+ }
+
+ /* adjust start for beam origin being in middle of a segment */
+ VectorMA(start, 8, f, start);
+
+ /* adjust end z for end spot since the monster is currently dead */
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2;
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_MEDIC_CABLE_ATTACK);
+ gi.WriteShort(self - g_edicts);
+ gi.WritePosition(start);
+ gi.WritePosition(end);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+}
+
+void
+medic_hook_retract(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
+ self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+}
+
+mframe_t medic_frames_attackCable[] = {
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 4.4, NULL},
+ {ai_charge, 4.7, NULL},
+ {ai_charge, 5, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 0, NULL},
+ {ai_move, 0, medic_hook_launch},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, 0, medic_cable_attack},
+ {ai_move, -15, medic_hook_retract},
+ {ai_move, -1.5, NULL},
+ {ai_move, -1.2, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0.3, NULL},
+ {ai_move, 0.7, NULL},
+ {ai_move, 1.2, NULL},
+ {ai_move, 1.3, NULL}
+};
+
+mmove_t medic_move_attackCable = {
+ FRAME_attack33,
+ FRAME_attack60,
+ medic_frames_attackCable,
+ medic_run
+};
+
+void
+medic_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ self->monsterinfo.currentmove = &medic_move_attackCable;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &medic_move_attackBlaster;
+ }
+}
+
+qboolean
+medic_checkattack(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->monsterinfo.aiflags & AI_MEDIC)
+ {
+ medic_attack(self);
+ return true;
+ }
+
+ return M_CheckAttack(self);
+}
+
+/*
+ * QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_medic(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_idle1 = gi.soundindex("medic/idle.wav");
+ sound_pain1 = gi.soundindex("medic/medpain1.wav");
+ sound_pain2 = gi.soundindex("medic/medpain2.wav");
+ sound_die = gi.soundindex("medic/meddeth1.wav");
+ sound_sight = gi.soundindex("medic/medsght1.wav");
+ sound_search = gi.soundindex("medic/medsrch1.wav");
+ sound_hook_launch = gi.soundindex("medic/medatck2.wav");
+ sound_hook_hit = gi.soundindex("medic/medatck3.wav");
+ sound_hook_heal = gi.soundindex("medic/medatck4.wav");
+ sound_hook_retract = gi.soundindex("medic/medatck5.wav");
+
+ gi.soundindex("medic/medatck1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/medic/tris.md2");
+ VectorSet(self->mins, -24, -24, -24);
+ VectorSet(self->maxs, 24, 24, 32);
+
+ self->health = 300;
+ self->gib_health = -130;
+ self->mass = 400;
+
+ self->pain = medic_pain;
+ self->die = medic_die;
+
+ self->monsterinfo.stand = medic_stand;
+ self->monsterinfo.walk = medic_walk;
+ self->monsterinfo.run = medic_run;
+ self->monsterinfo.dodge = medic_dodge;
+ self->monsterinfo.attack = medic_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = medic_sight;
+ self->monsterinfo.idle = medic_idle;
+ self->monsterinfo.search = medic_search;
+ self->monsterinfo.checkattack = medic_checkattack;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &medic_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/medic/medic.h b/xatrix/src/monster/medic/medic.h
new file mode 100644
index 0000000..b027e91
--- /dev/null
+++ b/xatrix/src/monster/medic/medic.h
@@ -0,0 +1,246 @@
+/* =======================================================================
+ *
+ * Medic animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_walk1 0
+#define FRAME_walk2 1
+#define FRAME_walk3 2
+#define FRAME_walk4 3
+#define FRAME_walk5 4
+#define FRAME_walk6 5
+#define FRAME_walk7 6
+#define FRAME_walk8 7
+#define FRAME_walk9 8
+#define FRAME_walk10 9
+#define FRAME_walk11 10
+#define FRAME_walk12 11
+#define FRAME_wait1 12
+#define FRAME_wait2 13
+#define FRAME_wait3 14
+#define FRAME_wait4 15
+#define FRAME_wait5 16
+#define FRAME_wait6 17
+#define FRAME_wait7 18
+#define FRAME_wait8 19
+#define FRAME_wait9 20
+#define FRAME_wait10 21
+#define FRAME_wait11 22
+#define FRAME_wait12 23
+#define FRAME_wait13 24
+#define FRAME_wait14 25
+#define FRAME_wait15 26
+#define FRAME_wait16 27
+#define FRAME_wait17 28
+#define FRAME_wait18 29
+#define FRAME_wait19 30
+#define FRAME_wait20 31
+#define FRAME_wait21 32
+#define FRAME_wait22 33
+#define FRAME_wait23 34
+#define FRAME_wait24 35
+#define FRAME_wait25 36
+#define FRAME_wait26 37
+#define FRAME_wait27 38
+#define FRAME_wait28 39
+#define FRAME_wait29 40
+#define FRAME_wait30 41
+#define FRAME_wait31 42
+#define FRAME_wait32 43
+#define FRAME_wait33 44
+#define FRAME_wait34 45
+#define FRAME_wait35 46
+#define FRAME_wait36 47
+#define FRAME_wait37 48
+#define FRAME_wait38 49
+#define FRAME_wait39 50
+#define FRAME_wait40 51
+#define FRAME_wait41 52
+#define FRAME_wait42 53
+#define FRAME_wait43 54
+#define FRAME_wait44 55
+#define FRAME_wait45 56
+#define FRAME_wait46 57
+#define FRAME_wait47 58
+#define FRAME_wait48 59
+#define FRAME_wait49 60
+#define FRAME_wait50 61
+#define FRAME_wait51 62
+#define FRAME_wait52 63
+#define FRAME_wait53 64
+#define FRAME_wait54 65
+#define FRAME_wait55 66
+#define FRAME_wait56 67
+#define FRAME_wait57 68
+#define FRAME_wait58 69
+#define FRAME_wait59 70
+#define FRAME_wait60 71
+#define FRAME_wait61 72
+#define FRAME_wait62 73
+#define FRAME_wait63 74
+#define FRAME_wait64 75
+#define FRAME_wait65 76
+#define FRAME_wait66 77
+#define FRAME_wait67 78
+#define FRAME_wait68 79
+#define FRAME_wait69 80
+#define FRAME_wait70 81
+#define FRAME_wait71 82
+#define FRAME_wait72 83
+#define FRAME_wait73 84
+#define FRAME_wait74 85
+#define FRAME_wait75 86
+#define FRAME_wait76 87
+#define FRAME_wait77 88
+#define FRAME_wait78 89
+#define FRAME_wait79 90
+#define FRAME_wait80 91
+#define FRAME_wait81 92
+#define FRAME_wait82 93
+#define FRAME_wait83 94
+#define FRAME_wait84 95
+#define FRAME_wait85 96
+#define FRAME_wait86 97
+#define FRAME_wait87 98
+#define FRAME_wait88 99
+#define FRAME_wait89 100
+#define FRAME_wait90 101
+#define FRAME_run1 102
+#define FRAME_run2 103
+#define FRAME_run3 104
+#define FRAME_run4 105
+#define FRAME_run5 106
+#define FRAME_run6 107
+#define FRAME_paina1 108
+#define FRAME_paina2 109
+#define FRAME_paina3 110
+#define FRAME_paina4 111
+#define FRAME_paina5 112
+#define FRAME_paina6 113
+#define FRAME_paina7 114
+#define FRAME_paina8 115
+#define FRAME_painb1 116
+#define FRAME_painb2 117
+#define FRAME_painb3 118
+#define FRAME_painb4 119
+#define FRAME_painb5 120
+#define FRAME_painb6 121
+#define FRAME_painb7 122
+#define FRAME_painb8 123
+#define FRAME_painb9 124
+#define FRAME_painb10 125
+#define FRAME_painb11 126
+#define FRAME_painb12 127
+#define FRAME_painb13 128
+#define FRAME_painb14 129
+#define FRAME_painb15 130
+#define FRAME_duck1 131
+#define FRAME_duck2 132
+#define FRAME_duck3 133
+#define FRAME_duck4 134
+#define FRAME_duck5 135
+#define FRAME_duck6 136
+#define FRAME_duck7 137
+#define FRAME_duck8 138
+#define FRAME_duck9 139
+#define FRAME_duck10 140
+#define FRAME_duck11 141
+#define FRAME_duck12 142
+#define FRAME_duck13 143
+#define FRAME_duck14 144
+#define FRAME_duck15 145
+#define FRAME_duck16 146
+#define FRAME_death1 147
+#define FRAME_death2 148
+#define FRAME_death3 149
+#define FRAME_death4 150
+#define FRAME_death5 151
+#define FRAME_death6 152
+#define FRAME_death7 153
+#define FRAME_death8 154
+#define FRAME_death9 155
+#define FRAME_death10 156
+#define FRAME_death11 157
+#define FRAME_death12 158
+#define FRAME_death13 159
+#define FRAME_death14 160
+#define FRAME_death15 161
+#define FRAME_death16 162
+#define FRAME_death17 163
+#define FRAME_death18 164
+#define FRAME_death19 165
+#define FRAME_death20 166
+#define FRAME_death21 167
+#define FRAME_death22 168
+#define FRAME_death23 169
+#define FRAME_death24 170
+#define FRAME_death25 171
+#define FRAME_death26 172
+#define FRAME_death27 173
+#define FRAME_death28 174
+#define FRAME_death29 175
+#define FRAME_death30 176
+#define FRAME_attack1 177
+#define FRAME_attack2 178
+#define FRAME_attack3 179
+#define FRAME_attack4 180
+#define FRAME_attack5 181
+#define FRAME_attack6 182
+#define FRAME_attack7 183
+#define FRAME_attack8 184
+#define FRAME_attack9 185
+#define FRAME_attack10 186
+#define FRAME_attack11 187
+#define FRAME_attack12 188
+#define FRAME_attack13 189
+#define FRAME_attack14 190
+#define FRAME_attack15 191
+#define FRAME_attack16 192
+#define FRAME_attack17 193
+#define FRAME_attack18 194
+#define FRAME_attack19 195
+#define FRAME_attack20 196
+#define FRAME_attack21 197
+#define FRAME_attack22 198
+#define FRAME_attack23 199
+#define FRAME_attack24 200
+#define FRAME_attack25 201
+#define FRAME_attack26 202
+#define FRAME_attack27 203
+#define FRAME_attack28 204
+#define FRAME_attack29 205
+#define FRAME_attack30 206
+#define FRAME_attack31 207
+#define FRAME_attack32 208
+#define FRAME_attack33 209
+#define FRAME_attack34 210
+#define FRAME_attack35 211
+#define FRAME_attack36 212
+#define FRAME_attack37 213
+#define FRAME_attack38 214
+#define FRAME_attack39 215
+#define FRAME_attack40 216
+#define FRAME_attack41 217
+#define FRAME_attack42 218
+#define FRAME_attack43 219
+#define FRAME_attack44 220
+#define FRAME_attack45 221
+#define FRAME_attack46 222
+#define FRAME_attack47 223
+#define FRAME_attack48 224
+#define FRAME_attack49 225
+#define FRAME_attack50 226
+#define FRAME_attack51 227
+#define FRAME_attack52 228
+#define FRAME_attack53 229
+#define FRAME_attack54 230
+#define FRAME_attack55 231
+#define FRAME_attack56 232
+#define FRAME_attack57 233
+#define FRAME_attack58 234
+#define FRAME_attack59 235
+#define FRAME_attack60 236
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/misc/move.c b/xatrix/src/monster/misc/move.c
new file mode 100644
index 0000000..7016998
--- /dev/null
+++ b/xatrix/src/monster/misc/move.c
@@ -0,0 +1,708 @@
+/* =======================================================================
+ *
+ * Monster movement support functions.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+
+#define DI_NODIR -1
+#define STEPSIZE 18
+
+/*
+ * Returns false if any part of the bottom
+ * of the entity is off an edge that is not
+ * a staircase.
+ */
+qboolean
+M_CheckBottom(edict_t *ent)
+{
+ vec3_t mins, maxs, start, stop;
+ trace_t trace;
+ int x, y;
+ float mid, bottom;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ VectorAdd(ent->s.origin, ent->mins, mins);
+ VectorAdd(ent->s.origin, ent->maxs, maxs);
+
+ /* if all of the points under the corners are solid
+ world, don't bother with the tougher checks
+ the corners must be within 16 of the midpoint */
+ start[2] = mins[2] - 1;
+
+ for (x = 0; x <= 1; x++)
+ {
+ for (y = 0; y <= 1; y++)
+ {
+ start[0] = x ? maxs[0] : mins[0];
+ start[1] = y ? maxs[1] : mins[1];
+
+ if (gi.pointcontents(start) != CONTENTS_SOLID)
+ {
+ goto realcheck;
+ }
+ }
+ }
+
+ return true; /* we got out easy */
+
+realcheck:
+ /* check it for real... */
+ start[2] = mins[2];
+
+ /* the midpoint must be within 16 of the bottom */
+ start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5;
+ start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5;
+ stop[2] = start[2] - 2 * STEPSIZE;
+ trace = gi.trace(start, vec3_origin, vec3_origin,
+ stop, ent, MASK_MONSTERSOLID);
+
+ if (trace.fraction == 1.0)
+ {
+ return false;
+ }
+
+ mid = bottom = trace.endpos[2];
+
+ /* the corners must be within 16 of the midpoint */
+ for (x = 0; x <= 1; x++)
+ {
+ for (y = 0; y <= 1; y++)
+ {
+ start[0] = stop[0] = x ? maxs[0] : mins[0];
+ start[1] = stop[1] = y ? maxs[1] : mins[1];
+
+ trace = gi.trace(start, vec3_origin, vec3_origin,
+ stop, ent, MASK_MONSTERSOLID);
+
+ if ((trace.fraction != 1.0) && (trace.endpos[2] > bottom))
+ {
+ bottom = trace.endpos[2];
+ }
+
+ if ((trace.fraction == 1.0) || (mid - trace.endpos[2] > STEPSIZE))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/*
+ * Called by monster program code.
+ * The move will be adjusted for slopes
+ * and stairs, but if the move isn't
+ * possible, no move is done, false is
+ * returned, and pr_global_struct->trace_normal
+ * is set to the normal of the blocking wall
+ */
+qboolean
+SV_movestep(edict_t *ent, vec3_t move, qboolean relink)
+{
+ float dz;
+ vec3_t oldorg, neworg, end;
+ trace_t trace;
+ int i;
+ float stepsize;
+ vec3_t test;
+ int contents;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ /* try the move */
+ VectorCopy(ent->s.origin, oldorg);
+ VectorAdd(ent->s.origin, move, neworg);
+
+ /* flying monsters don't step up */
+ if (ent->flags & (FL_SWIM | FL_FLY))
+ {
+ /* try one move with vertical motion, then one without */
+ for (i = 0; i < 2; i++)
+ {
+ VectorAdd(ent->s.origin, move, neworg);
+
+ if ((i == 0) && ent->enemy)
+ {
+ if (!ent->goalentity)
+ {
+ ent->goalentity = ent->enemy;
+ }
+
+ dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
+
+ if (ent->goalentity->client)
+ {
+ if (dz > 40)
+ {
+ neworg[2] -= 8;
+ }
+
+ if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
+ {
+ if (dz < 30)
+ {
+ neworg[2] += 8;
+ }
+ }
+ }
+ else
+ {
+ if (strcmp(ent->classname, "monster_fixbot") == 0)
+ {
+ if ((ent->s.frame >= 105) && (ent->s.frame <= 120))
+ {
+ if (dz > 12)
+ {
+ neworg[2]--;
+ }
+ else if (dz < -12)
+ {
+ neworg[2]++;
+ }
+ }
+ else if ((ent->s.frame >= 31) && (ent->s.frame <= 88))
+ {
+ if (dz > 12)
+ {
+ neworg[2] -= 12;
+ }
+ else if (dz < -12)
+ {
+ neworg[2] += 12;
+ }
+ }
+ else
+ {
+ if (dz > 12)
+ {
+ neworg[2] -= 8;
+ }
+ else if (dz < -12)
+ {
+ neworg[2] += 8;
+ }
+ }
+ }
+ else
+ {
+ if (dz > 8)
+ {
+ neworg[2] -= 8;
+ }
+ else if (dz > 0)
+ {
+ neworg[2] -= dz;
+ }
+ else if (dz < -8)
+ {
+ neworg[2] += 8;
+ }
+ else
+ {
+ neworg[2] += dz;
+ }
+ }
+ }
+ }
+
+ trace = gi.trace(ent->s.origin, ent->mins, ent->maxs,
+ neworg, ent, MASK_MONSTERSOLID);
+
+ /* fly monsters don't enter water voluntarily */
+ if (ent->flags & FL_FLY)
+ {
+ if (!ent->waterlevel)
+ {
+ test[0] = trace.endpos[0];
+ test[1] = trace.endpos[1];
+ test[2] = trace.endpos[2] + ent->mins[2] + 1;
+ contents = gi.pointcontents(test);
+
+ if (contents & MASK_WATER)
+ {
+ return false;
+ }
+ }
+ }
+
+ /* swim monsters don't exit water voluntarily */
+ if (ent->flags & FL_SWIM)
+ {
+ if (ent->waterlevel < 2)
+ {
+ test[0] = trace.endpos[0];
+ test[1] = trace.endpos[1];
+ test[2] = trace.endpos[2] + ent->mins[2] + 1;
+ contents = gi.pointcontents(test);
+
+ if (!(contents & MASK_WATER))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (trace.fraction == 1)
+ {
+ VectorCopy(trace.endpos, ent->s.origin);
+
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ return true;
+ }
+
+ if (!ent->enemy)
+ {
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ /* push down from a step height above the wished position */
+ if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
+ {
+ stepsize = STEPSIZE;
+ }
+ else
+ {
+ stepsize = 1;
+ }
+
+ neworg[2] += stepsize;
+ VectorCopy(neworg, end);
+ end[2] -= stepsize * 2;
+
+ trace = gi.trace(neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+ if (trace.allsolid)
+ {
+ return false;
+ }
+
+ if (trace.startsolid)
+ {
+ neworg[2] -= stepsize;
+ trace = gi.trace(neworg, ent->mins, ent->maxs,
+ end, ent, MASK_MONSTERSOLID);
+
+ if (trace.allsolid || trace.startsolid)
+ {
+ return false;
+ }
+ }
+
+ /* don't go in to water */
+ if (ent->waterlevel == 0)
+ {
+ test[0] = trace.endpos[0];
+ test[1] = trace.endpos[1];
+ test[2] = trace.endpos[2] + ent->mins[2] + 1;
+ contents = gi.pointcontents(test);
+
+ if (contents & MASK_WATER)
+ {
+ return false;
+ }
+ }
+
+ if (trace.fraction == 1)
+ {
+ /* if monster had the ground pulled out, go ahead and fall */
+ if (ent->flags & FL_PARTIALGROUND)
+ {
+ VectorAdd(ent->s.origin, move, ent->s.origin);
+
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ ent->groundentity = NULL;
+ return true;
+ }
+
+ return false; /* walked off an edge */
+ }
+
+ /* check point traces down for dangling corners */
+ VectorCopy(trace.endpos, ent->s.origin);
+
+ if (!M_CheckBottom(ent))
+ {
+ if (ent->flags & FL_PARTIALGROUND)
+ {
+ /* entity had floor mostly pulled out
+ from underneath it and is trying to
+ correct */
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ return true;
+ }
+
+ VectorCopy(oldorg, ent->s.origin);
+ return false;
+ }
+
+ if (ent->flags & FL_PARTIALGROUND)
+ {
+ ent->flags &= ~FL_PARTIALGROUND;
+ }
+
+ ent->groundentity = trace.ent;
+ ent->groundentity_linkcount = trace.ent->linkcount;
+
+ /* the move is ok */
+ if (relink)
+ {
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ }
+
+ return true;
+}
+
+/* ============================================================================ */
+
+
+void
+M_ChangeYaw(edict_t *ent)
+{
+ float ideal;
+ float current;
+ float move;
+ float speed;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ current = anglemod(ent->s.angles[YAW]);
+ ideal = ent->ideal_yaw;
+
+ if (current == ideal)
+ {
+ return;
+ }
+
+ move = ideal - current;
+ speed = ent->yaw_speed;
+
+ if (ideal > current)
+ {
+ if (move >= 180)
+ {
+ move = move - 360;
+ }
+ }
+ else
+ {
+ if (move <= -180)
+ {
+ move = move + 360;
+ }
+ }
+
+ if (move > 0)
+ {
+ if (move > speed)
+ {
+ move = speed;
+ }
+ }
+ else
+ {
+ if (move < -speed)
+ {
+ move = -speed;
+ }
+ }
+
+ ent->s.angles[YAW] = anglemod(current + move);
+}
+
+/*
+ * Turns to the movement direction, and
+ * walks the current distance if facing it.
+ */
+qboolean
+SV_StepDirection(edict_t *ent, float yaw, float dist)
+{
+ vec3_t move, oldorigin;
+ float delta;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ ent->ideal_yaw = yaw;
+ M_ChangeYaw(ent);
+
+ yaw = yaw * M_PI * 2 / 360;
+ move[0] = cos(yaw) * dist;
+ move[1] = sin(yaw) * dist;
+ move[2] = 0;
+
+ VectorCopy(ent->s.origin, oldorigin);
+
+ if (SV_movestep(ent, move, false))
+ {
+ delta = ent->s.angles[YAW] - ent->ideal_yaw;
+
+ if ((delta > 45) && (delta < 315))
+ {
+ /* not turned far enough, so don't take the step */
+ VectorCopy(oldorigin, ent->s.origin);
+ }
+
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ return true;
+ }
+
+ gi.linkentity(ent);
+ G_TouchTriggers(ent);
+ return false;
+}
+
+void
+SV_FixCheckBottom(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->flags |= FL_PARTIALGROUND;
+}
+
+void
+SV_NewChaseDir(edict_t *actor, edict_t *enemy, float dist)
+{
+ float deltax, deltay;
+ float d[3];
+ float tdir, olddir, turnaround;
+
+ if (!actor || !enemy)
+ {
+ return;
+ }
+
+ olddir = anglemod((int)(actor->ideal_yaw / 45) * 45);
+ turnaround = anglemod(olddir - 180);
+
+ deltax = enemy->s.origin[0] - actor->s.origin[0];
+ deltay = enemy->s.origin[1] - actor->s.origin[1];
+
+ if (deltax > 10)
+ {
+ d[1] = 0;
+ }
+ else if (deltax < -10)
+ {
+ d[1] = 180;
+ }
+ else
+ {
+ d[1] = DI_NODIR;
+ }
+
+ if (deltay < -10)
+ {
+ d[2] = 270;
+ }
+ else if (deltay > 10)
+ {
+ d[2] = 90;
+ }
+ else
+ {
+ d[2] = DI_NODIR;
+ }
+
+ /* try direct route */
+ if ((d[1] != DI_NODIR) && (d[2] != DI_NODIR))
+ {
+ if (d[1] == 0)
+ {
+ tdir = d[2] == 90 ? 45 : 315;
+ }
+ else
+ {
+ tdir = d[2] == 90 ? 135 : 215;
+ }
+
+ if ((tdir != turnaround) && SV_StepDirection(actor, tdir, dist))
+ {
+ return;
+ }
+ }
+
+ /* try other directions */
+ if (((rand() & 3) & 1) || (fabsf(deltay) > fabsf(deltax)))
+ {
+ tdir = d[1];
+ d[1] = d[2];
+ d[2] = tdir;
+ }
+
+ if ((d[1] != DI_NODIR) && (d[1] != turnaround) &&
+ SV_StepDirection(actor, d[1], dist))
+ {
+ return;
+ }
+
+ if ((d[2] != DI_NODIR) && (d[2] != turnaround) &&
+ SV_StepDirection(actor, d[2], dist))
+ {
+ return;
+ }
+
+ /* there is no direct path to the player, so pick another direction */
+ if ((olddir != DI_NODIR) && SV_StepDirection(actor, olddir, dist))
+ {
+ return;
+ }
+
+ if (rand() & 1) /*randomly determine direction of search*/
+ {
+ for (tdir = 0; tdir <= 315; tdir += 45)
+ {
+ if ((tdir != turnaround) && SV_StepDirection(actor, tdir, dist))
+ {
+ return;
+ }
+ }
+ }
+ else
+ {
+ for (tdir = 315; tdir >= 0; tdir -= 45)
+ {
+ if ((tdir != turnaround) && SV_StepDirection(actor, tdir, dist))
+ {
+ return;
+ }
+ }
+ }
+
+ if ((turnaround != DI_NODIR) && SV_StepDirection(actor, turnaround, dist))
+ {
+ return;
+ }
+
+ actor->ideal_yaw = olddir; /* can't move */
+
+ /* if a bridge was pulled out from underneath
+ a monster, it may not have a valid standing
+ position at all */
+ if (!M_CheckBottom(actor))
+ {
+ SV_FixCheckBottom(actor);
+ }
+}
+
+qboolean
+SV_CloseEnough(edict_t *ent, edict_t *goal, float dist)
+{
+ int i;
+
+ if (!ent || !goal)
+ {
+ return false;
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ if (goal->absmin[i] > ent->absmax[i] + dist)
+ {
+ return false;
+ }
+
+ if (goal->absmax[i] < ent->absmin[i] - dist)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void
+M_MoveToGoal(edict_t *ent, float dist)
+{
+ edict_t *goal;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ goal = ent->goalentity;
+
+ if (!ent->groundentity && !(ent->flags & (FL_FLY | FL_SWIM)))
+ {
+ return;
+ }
+
+ /* if the next step hits the enemy, return immediately */
+ if (ent->enemy && SV_CloseEnough(ent, ent->enemy, dist))
+ {
+ return;
+ }
+
+ /* bump around... */
+ if (((rand() & 3) == 1) || !SV_StepDirection(ent, ent->ideal_yaw, dist))
+ {
+ if (ent->inuse)
+ {
+ SV_NewChaseDir(ent, goal, dist);
+ }
+ }
+}
+
+qboolean
+M_walkmove(edict_t *ent, float yaw, float dist)
+{
+ vec3_t move;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!ent->groundentity && !(ent->flags & (FL_FLY | FL_SWIM)))
+ {
+ return false;
+ }
+
+ yaw = yaw * M_PI * 2 / 360;
+
+ move[0] = cos(yaw) * dist;
+ move[1] = sin(yaw) * dist;
+ move[2] = 0;
+
+ return SV_movestep(ent, move, true);
+}
diff --git a/xatrix/src/monster/misc/player.h b/xatrix/src/monster/misc/player.h
new file mode 100644
index 0000000..6976e03
--- /dev/null
+++ b/xatrix/src/monster/misc/player.h
@@ -0,0 +1,207 @@
+/* =======================================================================
+ *
+ * Player (the arm and the weapons) animation.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_stand31 30
+#define FRAME_stand32 31
+#define FRAME_stand33 32
+#define FRAME_stand34 33
+#define FRAME_stand35 34
+#define FRAME_stand36 35
+#define FRAME_stand37 36
+#define FRAME_stand38 37
+#define FRAME_stand39 38
+#define FRAME_stand40 39
+#define FRAME_run1 40
+#define FRAME_run2 41
+#define FRAME_run3 42
+#define FRAME_run4 43
+#define FRAME_run5 44
+#define FRAME_run6 45
+#define FRAME_attack1 46
+#define FRAME_attack2 47
+#define FRAME_attack3 48
+#define FRAME_attack4 49
+#define FRAME_attack5 50
+#define FRAME_attack6 51
+#define FRAME_attack7 52
+#define FRAME_attack8 53
+#define FRAME_pain101 54
+#define FRAME_pain102 55
+#define FRAME_pain103 56
+#define FRAME_pain104 57
+#define FRAME_pain201 58
+#define FRAME_pain202 59
+#define FRAME_pain203 60
+#define FRAME_pain204 61
+#define FRAME_pain301 62
+#define FRAME_pain302 63
+#define FRAME_pain303 64
+#define FRAME_pain304 65
+#define FRAME_jump1 66
+#define FRAME_jump2 67
+#define FRAME_jump3 68
+#define FRAME_jump4 69
+#define FRAME_jump5 70
+#define FRAME_jump6 71
+#define FRAME_flip01 72
+#define FRAME_flip02 73
+#define FRAME_flip03 74
+#define FRAME_flip04 75
+#define FRAME_flip05 76
+#define FRAME_flip06 77
+#define FRAME_flip07 78
+#define FRAME_flip08 79
+#define FRAME_flip09 80
+#define FRAME_flip10 81
+#define FRAME_flip11 82
+#define FRAME_flip12 83
+#define FRAME_salute01 84
+#define FRAME_salute02 85
+#define FRAME_salute03 86
+#define FRAME_salute04 87
+#define FRAME_salute05 88
+#define FRAME_salute06 89
+#define FRAME_salute07 90
+#define FRAME_salute08 91
+#define FRAME_salute09 92
+#define FRAME_salute10 93
+#define FRAME_salute11 94
+#define FRAME_taunt01 95
+#define FRAME_taunt02 96
+#define FRAME_taunt03 97
+#define FRAME_taunt04 98
+#define FRAME_taunt05 99
+#define FRAME_taunt06 100
+#define FRAME_taunt07 101
+#define FRAME_taunt08 102
+#define FRAME_taunt09 103
+#define FRAME_taunt10 104
+#define FRAME_taunt11 105
+#define FRAME_taunt12 106
+#define FRAME_taunt13 107
+#define FRAME_taunt14 108
+#define FRAME_taunt15 109
+#define FRAME_taunt16 110
+#define FRAME_taunt17 111
+#define FRAME_wave01 112
+#define FRAME_wave02 113
+#define FRAME_wave03 114
+#define FRAME_wave04 115
+#define FRAME_wave05 116
+#define FRAME_wave06 117
+#define FRAME_wave07 118
+#define FRAME_wave08 119
+#define FRAME_wave09 120
+#define FRAME_wave10 121
+#define FRAME_wave11 122
+#define FRAME_point01 123
+#define FRAME_point02 124
+#define FRAME_point03 125
+#define FRAME_point04 126
+#define FRAME_point05 127
+#define FRAME_point06 128
+#define FRAME_point07 129
+#define FRAME_point08 130
+#define FRAME_point09 131
+#define FRAME_point10 132
+#define FRAME_point11 133
+#define FRAME_point12 134
+#define FRAME_crstnd01 135
+#define FRAME_crstnd02 136
+#define FRAME_crstnd03 137
+#define FRAME_crstnd04 138
+#define FRAME_crstnd05 139
+#define FRAME_crstnd06 140
+#define FRAME_crstnd07 141
+#define FRAME_crstnd08 142
+#define FRAME_crstnd09 143
+#define FRAME_crstnd10 144
+#define FRAME_crstnd11 145
+#define FRAME_crstnd12 146
+#define FRAME_crstnd13 147
+#define FRAME_crstnd14 148
+#define FRAME_crstnd15 149
+#define FRAME_crstnd16 150
+#define FRAME_crstnd17 151
+#define FRAME_crstnd18 152
+#define FRAME_crstnd19 153
+#define FRAME_crwalk1 154
+#define FRAME_crwalk2 155
+#define FRAME_crwalk3 156
+#define FRAME_crwalk4 157
+#define FRAME_crwalk5 158
+#define FRAME_crwalk6 159
+#define FRAME_crattak1 160
+#define FRAME_crattak2 161
+#define FRAME_crattak3 162
+#define FRAME_crattak4 163
+#define FRAME_crattak5 164
+#define FRAME_crattak6 165
+#define FRAME_crattak7 166
+#define FRAME_crattak8 167
+#define FRAME_crattak9 168
+#define FRAME_crpain1 169
+#define FRAME_crpain2 170
+#define FRAME_crpain3 171
+#define FRAME_crpain4 172
+#define FRAME_crdeath1 173
+#define FRAME_crdeath2 174
+#define FRAME_crdeath3 175
+#define FRAME_crdeath4 176
+#define FRAME_crdeath5 177
+#define FRAME_death101 178
+#define FRAME_death102 179
+#define FRAME_death103 180
+#define FRAME_death104 181
+#define FRAME_death105 182
+#define FRAME_death106 183
+#define FRAME_death201 184
+#define FRAME_death202 185
+#define FRAME_death203 186
+#define FRAME_death204 187
+#define FRAME_death205 188
+#define FRAME_death206 189
+#define FRAME_death301 190
+#define FRAME_death302 191
+#define FRAME_death303 192
+#define FRAME_death304 193
+#define FRAME_death305 194
+#define FRAME_death306 195
+#define FRAME_death307 196
+#define FRAME_death308 197
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/mutant/mutant.c b/xatrix/src/monster/mutant/mutant.c
new file mode 100644
index 0000000..b3121bd
--- /dev/null
+++ b/xatrix/src/monster/mutant/mutant.c
@@ -0,0 +1,869 @@
+/* =======================================================================
+ *
+ * Mutant.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "mutant.h"
+
+static int sound_swing;
+static int sound_hit;
+static int sound_hit2;
+static int sound_death;
+static int sound_idle;
+static int sound_pain1;
+static int sound_pain2;
+static int sound_sight;
+static int sound_search;
+static int sound_step1;
+static int sound_step2;
+static int sound_step3;
+static int sound_thud;
+
+void mutant_walk(edict_t *self);
+
+void
+mutant_step(edict_t *self)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ n = (rand() + 1) % 3;
+
+ if (n == 0)
+ {
+ gi.sound(self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0);
+ }
+ else if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+mutant_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+mutant_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void
+mutant_swing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t mutant_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 10 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 20 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 30 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 40 */
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* 50 */
+
+ {ai_stand, 0, NULL}
+};
+
+mmove_t mutant_move_stand = {
+ FRAME_stand101,
+ FRAME_stand151,
+ mutant_frames_stand,
+ NULL
+};
+
+void
+mutant_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_stand;
+}
+
+void
+mutant_idle_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.75)
+ {
+ self->monsterinfo.nextframe = FRAME_stand155;
+ }
+}
+
+mframe_t mutant_frames_idle[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}, /* scratch loop start */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, mutant_idle_loop}, /* scratch loop end */
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t mutant_move_idle = {
+ FRAME_stand152,
+ FRAME_stand164,
+ mutant_frames_idle,
+ mutant_stand
+};
+
+void
+mutant_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_idle;
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t mutant_frames_walk[] = {
+ {ai_walk, 3, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 13, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 16, NULL},
+ {ai_walk, 15, NULL},
+ {ai_walk, 6, NULL}
+};
+
+mmove_t mutant_move_walk = {
+ FRAME_walk05,
+ FRAME_walk16,
+ mutant_frames_walk,
+ NULL
+};
+
+void
+mutant_walk_loop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_walk;
+}
+
+mframe_t mutant_frames_start_walk[] = {
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, -2, NULL},
+ {ai_walk, 1, NULL}
+};
+
+mmove_t mutant_move_start_walk = {
+ FRAME_walk01,
+ FRAME_walk04,
+ mutant_frames_start_walk,
+ mutant_walk_loop
+};
+
+void
+mutant_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_start_walk;
+}
+
+mframe_t mutant_frames_run[] = {
+ {ai_run, 40, NULL},
+ {ai_run, 40, mutant_step},
+ {ai_run, 24, NULL},
+ {ai_run, 5, mutant_step},
+ {ai_run, 17, NULL},
+ {ai_run, 10, NULL}
+};
+
+mmove_t mutant_move_run = {
+ FRAME_run03,
+ FRAME_run08,
+ mutant_frames_run,
+ NULL
+};
+
+void
+mutant_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &mutant_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &mutant_move_run;
+ }
+}
+
+void
+mutant_hit_left(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->mins[0], 8);
+
+ if (fire_hit(self, aim, (10 + (rand() % 5)), 100))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+mutant_hit_right(edict_t *self)
+{
+ vec3_t aim;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(aim, MELEE_DISTANCE, self->maxs[0], 8);
+
+ if (fire_hit(self, aim, (10 + (rand() % 5)), 100))
+ {
+ gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+ }
+}
+
+void
+mutant_check_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!self->enemy || !self->enemy->inuse || (self->enemy->health <= 0))
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attack09;
+ }
+}
+
+mframe_t mutant_frames_attack[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, mutant_hit_left},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, mutant_hit_right},
+ {ai_charge, 0, mutant_check_refire}
+};
+
+mmove_t mutant_move_attack = {
+ FRAME_attack09,
+ FRAME_attack15,
+ mutant_frames_attack,
+ mutant_run
+};
+
+void
+mutant_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_attack;
+}
+void
+mutant_jump_touch(edict_t *self, edict_t *other,
+ cplane_t *plane /* unused */, csurface_t *surf /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= 0)
+ {
+ self->touch = NULL;
+ return;
+ }
+
+ if (other->takedamage)
+ {
+ if (VectorLength(self->velocity) > 400)
+ {
+ vec3_t point;
+ vec3_t normal;
+ int damage;
+
+ VectorCopy(self->velocity, normal);
+ VectorNormalize(normal);
+ VectorMA(self->s.origin, self->maxs[0], normal, point);
+ damage = 40 + 10 * random();
+ T_Damage(other, self, self, self->velocity, point,
+ normal, damage, damage, 0, MOD_UNKNOWN);
+ }
+ }
+
+ if (!M_CheckBottom(self))
+ {
+ if (self->groundentity)
+ {
+ self->monsterinfo.nextframe = FRAME_attack02;
+ self->touch = NULL;
+ }
+
+ return;
+ }
+
+ self->touch = NULL;
+}
+
+void
+mutant_jump_takeoff(edict_t *self)
+{
+ vec3_t forward;
+
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+ AngleVectors(self->s.angles, forward, NULL, NULL);
+ self->s.origin[2] += 1;
+ VectorScale(forward, 600, self->velocity);
+ self->velocity[2] = 250;
+ self->groundentity = NULL;
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->monsterinfo.attack_finished = level.time + 3;
+ self->touch = mutant_jump_touch;
+}
+
+void
+mutant_check_landing(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->groundentity)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
+ self->monsterinfo.attack_finished = 0;
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ return;
+ }
+
+ if (level.time > self->monsterinfo.attack_finished)
+ {
+ self->monsterinfo.nextframe = FRAME_attack02;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attack05;
+ }
+}
+
+mframe_t mutant_frames_jump[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 17, NULL},
+ {ai_charge, 15, mutant_jump_takeoff},
+ {ai_charge, 15, NULL},
+ {ai_charge, 15, mutant_check_landing},
+ {ai_charge, 0, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t mutant_move_jump = {
+ FRAME_attack01,
+ FRAME_attack08,
+ mutant_frames_jump,
+ mutant_run
+};
+
+void
+mutant_jump(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &mutant_move_jump;
+}
+
+qboolean
+mutant_check_melee(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (range(self, self->enemy) == RANGE_MELEE)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+mutant_check_jump(edict_t *self)
+{
+ vec3_t v;
+ float distance;
+
+ if (!self)
+ {
+ return false;
+ }
+
+ if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2]))
+ {
+ return false;
+ }
+
+ if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2]))
+ {
+ return false;
+ }
+
+ v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+ v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+ v[2] = 0;
+ distance = VectorLength(v);
+
+ if (distance < 100)
+ {
+ return false;
+ }
+
+ if (distance > 100)
+ {
+ if (random() < 0.9)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+qboolean
+mutant_checkattack(edict_t *self)
+{
+ if (!self)
+ {
+ return false;
+ }
+
+ if (!self->enemy || (self->enemy->health <= 0))
+ {
+ return false;
+ }
+
+ if (mutant_check_melee(self))
+ {
+ self->monsterinfo.attack_state = AS_MELEE;
+ return true;
+ }
+
+ if (mutant_check_jump(self))
+ {
+ self->monsterinfo.attack_state = AS_MISSILE;
+ return true;
+ }
+
+ return false;
+}
+
+mframe_t mutant_frames_pain1[] = {
+ {ai_move, 4, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -8, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 5, NULL}
+};
+
+mmove_t mutant_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain105,
+ mutant_frames_pain1,
+ mutant_run
+};
+
+mframe_t mutant_frames_pain2[] = {
+ {ai_move, -24, NULL},
+ {ai_move, 11, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 4, NULL}
+};
+
+mmove_t mutant_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain206,
+ mutant_frames_pain2,
+ mutant_run
+};
+
+mframe_t mutant_frames_pain3[] = {
+ {ai_move, -22, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL}
+};
+
+mmove_t mutant_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain311,
+ mutant_frames_pain3,
+ mutant_run
+};
+
+void
+mutant_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &mutant_move_pain1;
+ }
+ else if (r < 0.66)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &mutant_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &mutant_move_pain3;
+ }
+}
+
+void
+mutant_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ gi.linkentity(self);
+
+ M_FlyCheck(self);
+}
+
+mframe_t mutant_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t mutant_move_death1 = {
+ FRAME_death101,
+ FRAME_death109,
+ mutant_frames_death1,
+ mutant_dead
+};
+
+mframe_t mutant_frames_death2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t mutant_move_death2 = {
+ FRAME_death201,
+ FRAME_death210,
+ mutant_frames_death2,
+ mutant_dead
+};
+
+void
+mutant_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum = 1;
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &mutant_move_death1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &mutant_move_death2;
+ }
+}
+
+/*
+ * QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_mutant(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_swing = gi.soundindex("mutant/mutatck1.wav");
+ sound_hit = gi.soundindex("mutant/mutatck2.wav");
+ sound_hit2 = gi.soundindex("mutant/mutatck3.wav");
+ sound_death = gi.soundindex("mutant/mutdeth1.wav");
+ sound_idle = gi.soundindex("mutant/mutidle1.wav");
+ sound_pain1 = gi.soundindex("mutant/mutpain1.wav");
+ sound_pain2 = gi.soundindex("mutant/mutpain2.wav");
+ sound_sight = gi.soundindex("mutant/mutsght1.wav");
+ sound_search = gi.soundindex("mutant/mutsrch1.wav");
+ sound_step1 = gi.soundindex("mutant/step1.wav");
+ sound_step2 = gi.soundindex("mutant/step2.wav");
+ sound_step3 = gi.soundindex("mutant/step3.wav");
+ sound_thud = gi.soundindex("mutant/thud1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/mutant/tris.md2");
+ VectorSet(self->mins, -32, -32, -24);
+ VectorSet(self->maxs, 32, 32, 48);
+
+ self->health = 300;
+ self->gib_health = -120;
+ self->mass = 300;
+
+ self->pain = mutant_pain;
+ self->die = mutant_die;
+
+ self->monsterinfo.stand = mutant_stand;
+ self->monsterinfo.walk = mutant_walk;
+ self->monsterinfo.run = mutant_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = mutant_jump;
+ self->monsterinfo.melee = mutant_melee;
+ self->monsterinfo.sight = mutant_sight;
+ self->monsterinfo.search = mutant_search;
+ self->monsterinfo.idle = mutant_idle;
+ self->monsterinfo.checkattack = mutant_checkattack;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &mutant_move_stand;
+
+ self->monsterinfo.scale = MODEL_SCALE;
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/mutant/mutant.h b/xatrix/src/monster/mutant/mutant.h
new file mode 100644
index 0000000..66bc3f7
--- /dev/null
+++ b/xatrix/src/monster/mutant/mutant.h
@@ -0,0 +1,158 @@
+/* =======================================================================
+ *
+ * Mutant animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attack01 0
+#define FRAME_attack02 1
+#define FRAME_attack03 2
+#define FRAME_attack04 3
+#define FRAME_attack05 4
+#define FRAME_attack06 5
+#define FRAME_attack07 6
+#define FRAME_attack08 7
+#define FRAME_attack09 8
+#define FRAME_attack10 9
+#define FRAME_attack11 10
+#define FRAME_attack12 11
+#define FRAME_attack13 12
+#define FRAME_attack14 13
+#define FRAME_attack15 14
+#define FRAME_death101 15
+#define FRAME_death102 16
+#define FRAME_death103 17
+#define FRAME_death104 18
+#define FRAME_death105 19
+#define FRAME_death106 20
+#define FRAME_death107 21
+#define FRAME_death108 22
+#define FRAME_death109 23
+#define FRAME_death201 24
+#define FRAME_death202 25
+#define FRAME_death203 26
+#define FRAME_death204 27
+#define FRAME_death205 28
+#define FRAME_death206 29
+#define FRAME_death207 30
+#define FRAME_death208 31
+#define FRAME_death209 32
+#define FRAME_death210 33
+#define FRAME_pain101 34
+#define FRAME_pain102 35
+#define FRAME_pain103 36
+#define FRAME_pain104 37
+#define FRAME_pain105 38
+#define FRAME_pain201 39
+#define FRAME_pain202 40
+#define FRAME_pain203 41
+#define FRAME_pain204 42
+#define FRAME_pain205 43
+#define FRAME_pain206 44
+#define FRAME_pain301 45
+#define FRAME_pain302 46
+#define FRAME_pain303 47
+#define FRAME_pain304 48
+#define FRAME_pain305 49
+#define FRAME_pain306 50
+#define FRAME_pain307 51
+#define FRAME_pain308 52
+#define FRAME_pain309 53
+#define FRAME_pain310 54
+#define FRAME_pain311 55
+#define FRAME_run03 56
+#define FRAME_run04 57
+#define FRAME_run05 58
+#define FRAME_run06 59
+#define FRAME_run07 60
+#define FRAME_run08 61
+#define FRAME_stand101 62
+#define FRAME_stand102 63
+#define FRAME_stand103 64
+#define FRAME_stand104 65
+#define FRAME_stand105 66
+#define FRAME_stand106 67
+#define FRAME_stand107 68
+#define FRAME_stand108 69
+#define FRAME_stand109 70
+#define FRAME_stand110 71
+#define FRAME_stand111 72
+#define FRAME_stand112 73
+#define FRAME_stand113 74
+#define FRAME_stand114 75
+#define FRAME_stand115 76
+#define FRAME_stand116 77
+#define FRAME_stand117 78
+#define FRAME_stand118 79
+#define FRAME_stand119 80
+#define FRAME_stand120 81
+#define FRAME_stand121 82
+#define FRAME_stand122 83
+#define FRAME_stand123 84
+#define FRAME_stand124 85
+#define FRAME_stand125 86
+#define FRAME_stand126 87
+#define FRAME_stand127 88
+#define FRAME_stand128 89
+#define FRAME_stand129 90
+#define FRAME_stand130 91
+#define FRAME_stand131 92
+#define FRAME_stand132 93
+#define FRAME_stand133 94
+#define FRAME_stand134 95
+#define FRAME_stand135 96
+#define FRAME_stand136 97
+#define FRAME_stand137 98
+#define FRAME_stand138 99
+#define FRAME_stand139 100
+#define FRAME_stand140 101
+#define FRAME_stand141 102
+#define FRAME_stand142 103
+#define FRAME_stand143 104
+#define FRAME_stand144 105
+#define FRAME_stand145 106
+#define FRAME_stand146 107
+#define FRAME_stand147 108
+#define FRAME_stand148 109
+#define FRAME_stand149 110
+#define FRAME_stand150 111
+#define FRAME_stand151 112
+#define FRAME_stand152 113
+#define FRAME_stand153 114
+#define FRAME_stand154 115
+#define FRAME_stand155 116
+#define FRAME_stand156 117
+#define FRAME_stand157 118
+#define FRAME_stand158 119
+#define FRAME_stand159 120
+#define FRAME_stand160 121
+#define FRAME_stand161 122
+#define FRAME_stand162 123
+#define FRAME_stand163 124
+#define FRAME_stand164 125
+#define FRAME_walk01 126
+#define FRAME_walk02 127
+#define FRAME_walk03 128
+#define FRAME_walk04 129
+#define FRAME_walk05 130
+#define FRAME_walk06 131
+#define FRAME_walk07 132
+#define FRAME_walk08 133
+#define FRAME_walk09 134
+#define FRAME_walk10 135
+#define FRAME_walk11 136
+#define FRAME_walk12 137
+#define FRAME_walk13 138
+#define FRAME_walk14 139
+#define FRAME_walk15 140
+#define FRAME_walk16 141
+#define FRAME_walk17 142
+#define FRAME_walk18 143
+#define FRAME_walk19 144
+#define FRAME_walk20 145
+#define FRAME_walk21 146
+#define FRAME_walk22 147
+#define FRAME_walk23 148
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/parasite/parasite.c b/xatrix/src/monster/parasite/parasite.c
new file mode 100644
index 0000000..7e22a2f
--- /dev/null
+++ b/xatrix/src/monster/parasite/parasite.c
@@ -0,0 +1,755 @@
+/* =======================================================================
+ *
+ * Parasite.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "parasite.h"
+
+static int sound_pain1;
+static int sound_pain2;
+static int sound_die;
+static int sound_launch;
+static int sound_impact;
+static int sound_suck;
+static int sound_reelin;
+static int sound_sight;
+static int sound_tap;
+static int sound_scratch;
+static int sound_search;
+
+void parasite_stand(edict_t *self);
+void parasite_start_run(edict_t *self);
+void parasite_run(edict_t *self);
+void parasite_walk(edict_t *self);
+void parasite_start_walk(edict_t *self);
+void parasite_end_fidget(edict_t *self);
+void parasite_do_fidget(edict_t *self);
+void parasite_refidget(edict_t *self);
+
+void
+parasite_launch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0);
+}
+
+void
+parasite_reel_in(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0);
+}
+
+void
+parasite_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+parasite_tap(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0);
+}
+
+void
+parasite_scratch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0);
+}
+
+void
+parasite_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0);
+}
+
+mframe_t parasite_frames_start_fidget[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t parasite_move_start_fidget = {
+ FRAME_stand18,
+ FRAME_stand21,
+ parasite_frames_start_fidget,
+ parasite_do_fidget
+};
+
+mframe_t parasite_frames_fidget[] = {
+ {ai_stand, 0, parasite_scratch},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_scratch},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t parasite_move_fidget = {
+ FRAME_stand22,
+ FRAME_stand27,
+ parasite_frames_fidget,
+ parasite_refidget
+};
+
+mframe_t parasite_frames_end_fidget[] = {
+ {ai_stand, 0, parasite_scratch},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t parasite_move_end_fidget = {
+ FRAME_stand28,
+ FRAME_stand35,
+ parasite_frames_end_fidget,
+ parasite_stand
+};
+
+void
+parasite_end_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_end_fidget;
+}
+
+void
+parasite_do_fidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_fidget;
+}
+
+void
+parasite_refidget(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() <= 0.8)
+ {
+ self->monsterinfo.currentmove = &parasite_move_fidget;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &parasite_move_end_fidget;
+ }
+}
+
+void
+parasite_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_start_fidget;
+}
+
+mframe_t parasite_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, parasite_tap}
+};
+
+mmove_t parasite_move_stand = {
+ FRAME_stand01,
+ FRAME_stand17,
+ parasite_frames_stand,
+ parasite_stand
+};
+
+void
+parasite_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_stand;
+}
+
+mframe_t parasite_frames_run[] = {
+ {ai_run, 30, NULL},
+ {ai_run, 30, NULL},
+ {ai_run, 22, NULL},
+ {ai_run, 19, NULL},
+ {ai_run, 24, NULL},
+ {ai_run, 28, NULL},
+ {ai_run, 25, NULL}
+};
+
+mmove_t parasite_move_run = {
+ FRAME_run03,
+ FRAME_run09,
+ parasite_frames_run,
+ NULL
+};
+
+mframe_t parasite_frames_start_run[] = {
+ {ai_run, 0, NULL},
+ {ai_run, 30, NULL},
+};
+
+mmove_t parasite_move_start_run = {
+ FRAME_run01,
+ FRAME_run02,
+ parasite_frames_start_run,
+ parasite_run
+};
+
+mframe_t parasite_frames_stop_run[] = {
+ {ai_run, 20, NULL},
+ {ai_run, 20, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 0, NULL},
+ {ai_run, 0, NULL}
+};
+
+mmove_t parasite_move_stop_run = {
+ FRAME_run10,
+ FRAME_run15,
+ parasite_frames_stop_run,
+ NULL
+};
+
+void
+parasite_start_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &parasite_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &parasite_move_start_run;
+ }
+}
+
+void
+parasite_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &parasite_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &parasite_move_run;
+ }
+}
+
+mframe_t parasite_frames_walk[] = {
+ {ai_walk, 30, NULL},
+ {ai_walk, 30, NULL},
+ {ai_walk, 22, NULL},
+ {ai_walk, 19, NULL},
+ {ai_walk, 24, NULL},
+ {ai_walk, 28, NULL},
+ {ai_walk, 25, NULL}
+};
+
+mmove_t parasite_move_walk = {
+ FRAME_run03,
+ FRAME_run09,
+ parasite_frames_walk,
+ parasite_walk
+};
+
+mframe_t parasite_frames_start_walk[] = {
+ {ai_walk, 0, NULL},
+ {ai_walk, 30, parasite_walk}
+};
+
+mmove_t parasite_move_start_walk = {
+ FRAME_run01,
+ FRAME_run02,
+ parasite_frames_start_walk,
+ NULL
+};
+
+mframe_t parasite_frames_stop_walk[] = {
+ {ai_walk, 20, NULL},
+ {ai_walk, 20, NULL},
+ {ai_walk, 12, NULL},
+ {ai_walk, 10, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t parasite_move_stop_walk = {
+ FRAME_run10,
+ FRAME_run15,
+ parasite_frames_stop_walk,
+ NULL
+};
+
+void
+parasite_start_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_start_walk;
+}
+
+void
+parasite_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_walk;
+}
+
+mframe_t parasite_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 16, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t parasite_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain111,
+ parasite_frames_pain1,
+ parasite_start_run
+};
+
+void
+parasite_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_pain1;
+}
+
+qboolean
+parasite_drain_attack_ok(vec3_t start, vec3_t end)
+{
+ vec3_t dir, angles;
+
+ /* check for max distance */
+ VectorSubtract(start, end, dir);
+
+ if (VectorLength(dir) > 256)
+ {
+ return false;
+ }
+
+ /* check for min/max pitch */
+ vectoangles(dir, angles);
+
+ if (angles[0] < -180)
+ {
+ angles[0] += 360;
+ }
+
+ if (fabs(angles[0]) > 30)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+parasite_drain_attack(edict_t *self)
+{
+ vec3_t offset, start, f, r, end, dir;
+ trace_t tr;
+ int damage;
+
+ if (!self)
+ {
+ return;
+ }
+
+ AngleVectors(self->s.angles, f, r, NULL);
+ VectorSet(offset, 24, 0, 6);
+ G_ProjectSource(self->s.origin, offset, f, r, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+
+ if (!parasite_drain_attack_ok(start, end))
+ {
+ return;
+ }
+ }
+ }
+
+ VectorCopy(self->enemy->s.origin, end);
+
+ tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
+
+ if (tr.ent != self->enemy)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_drain03)
+ {
+ damage = 5;
+ gi.sound(self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ if (self->s.frame == FRAME_drain04)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0);
+ }
+
+ damage = 2;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_PARASITE_ATTACK);
+ gi.WriteShort(self - g_edicts);
+ gi.WritePosition(start);
+ gi.WritePosition(end);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ VectorSubtract(start, end, dir);
+ T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin,
+ damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+}
+
+mframe_t parasite_frames_drain[] = {
+ {ai_charge, 0, parasite_launch},
+ {ai_charge, 0, NULL},
+ {ai_charge, 15, parasite_drain_attack}, /* Target hits */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, -2, parasite_drain_attack}, /* drain */
+ {ai_charge, -2, parasite_drain_attack}, /* drain */
+ {ai_charge, -3, parasite_drain_attack}, /* drain */
+ {ai_charge, -2, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_drain_attack}, /* drain */
+ {ai_charge, -1, parasite_drain_attack}, /* drain */
+ {ai_charge, 0, parasite_reel_in}, /* let go */
+ {ai_charge, -2, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t parasite_move_drain = {
+ FRAME_drain01,
+ FRAME_drain18,
+ parasite_frames_drain,
+ parasite_start_run
+};
+
+mframe_t parasite_frames_break[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, -3, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -18, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 9, NULL},
+ {ai_charge, 6, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -18, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 8, NULL},
+ {ai_charge, 9, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -18, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* airborne */
+ {ai_charge, 0, NULL}, /* airborne */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 0, NULL}, /* slides */
+ {ai_charge, 4, NULL},
+ {ai_charge, 11, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -5, NULL},
+ {ai_charge, 1, NULL}
+};
+
+mmove_t parasite_move_break = {
+ FRAME_break01,
+ FRAME_break32,
+ parasite_frames_break,
+ parasite_start_run
+};
+
+
+void
+parasite_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &parasite_move_drain;
+}
+
+void
+parasite_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t parasite_frames_death[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t parasite_move_death = {
+ FRAME_death101,
+ FRAME_death107,
+ parasite_frames_death,
+ parasite_dead
+};
+
+void
+parasite_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 2; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.currentmove = &parasite_move_death;
+}
+
+/*
+ * QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_parasite(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("parasite/parpain1.wav");
+ sound_pain2 = gi.soundindex("parasite/parpain2.wav");
+ sound_die = gi.soundindex("parasite/pardeth1.wav");
+ sound_launch = gi.soundindex("parasite/paratck1.wav");
+ sound_impact = gi.soundindex("parasite/paratck2.wav");
+ sound_suck = gi.soundindex("parasite/paratck3.wav");
+ sound_reelin = gi.soundindex("parasite/paratck4.wav");
+ sound_sight = gi.soundindex("parasite/parsght1.wav");
+ sound_tap = gi.soundindex("parasite/paridle1.wav");
+ sound_scratch = gi.soundindex("parasite/paridle2.wav");
+ sound_search = gi.soundindex("parasite/parsrch1.wav");
+
+ self->s.modelindex = gi.modelindex("models/monsters/parasite/tris.md2");
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 24);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ self->health = 175;
+ self->gib_health = -50;
+ self->mass = 250;
+ self->viewheight = 16;
+
+ self->pain = parasite_pain;
+ self->die = parasite_die;
+
+ self->monsterinfo.stand = parasite_stand;
+ self->monsterinfo.walk = parasite_start_walk;
+ self->monsterinfo.run = parasite_start_run;
+ self->monsterinfo.attack = parasite_attack;
+ self->monsterinfo.sight = parasite_sight;
+ self->monsterinfo.idle = parasite_idle;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &parasite_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/parasite/parasite.h b/xatrix/src/monster/parasite/parasite.h
new file mode 100644
index 0000000..9c96449
--- /dev/null
+++ b/xatrix/src/monster/parasite/parasite.h
@@ -0,0 +1,127 @@
+/* =======================================================================
+ *
+ * Parasite animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_break01 0
+#define FRAME_break02 1
+#define FRAME_break03 2
+#define FRAME_break04 3
+#define FRAME_break05 4
+#define FRAME_break06 5
+#define FRAME_break07 6
+#define FRAME_break08 7
+#define FRAME_break09 8
+#define FRAME_break10 9
+#define FRAME_break11 10
+#define FRAME_break12 11
+#define FRAME_break13 12
+#define FRAME_break14 13
+#define FRAME_break15 14
+#define FRAME_break16 15
+#define FRAME_break17 16
+#define FRAME_break18 17
+#define FRAME_break19 18
+#define FRAME_break20 19
+#define FRAME_break21 20
+#define FRAME_break22 21
+#define FRAME_break23 22
+#define FRAME_break24 23
+#define FRAME_break25 24
+#define FRAME_break26 25
+#define FRAME_break27 26
+#define FRAME_break28 27
+#define FRAME_break29 28
+#define FRAME_break30 29
+#define FRAME_break31 30
+#define FRAME_break32 31
+#define FRAME_death101 32
+#define FRAME_death102 33
+#define FRAME_death103 34
+#define FRAME_death104 35
+#define FRAME_death105 36
+#define FRAME_death106 37
+#define FRAME_death107 38
+#define FRAME_drain01 39
+#define FRAME_drain02 40
+#define FRAME_drain03 41
+#define FRAME_drain04 42
+#define FRAME_drain05 43
+#define FRAME_drain06 44
+#define FRAME_drain07 45
+#define FRAME_drain08 46
+#define FRAME_drain09 47
+#define FRAME_drain10 48
+#define FRAME_drain11 49
+#define FRAME_drain12 50
+#define FRAME_drain13 51
+#define FRAME_drain14 52
+#define FRAME_drain15 53
+#define FRAME_drain16 54
+#define FRAME_drain17 55
+#define FRAME_drain18 56
+#define FRAME_pain101 57
+#define FRAME_pain102 58
+#define FRAME_pain103 59
+#define FRAME_pain104 60
+#define FRAME_pain105 61
+#define FRAME_pain106 62
+#define FRAME_pain107 63
+#define FRAME_pain108 64
+#define FRAME_pain109 65
+#define FRAME_pain110 66
+#define FRAME_pain111 67
+#define FRAME_run01 68
+#define FRAME_run02 69
+#define FRAME_run03 70
+#define FRAME_run04 71
+#define FRAME_run05 72
+#define FRAME_run06 73
+#define FRAME_run07 74
+#define FRAME_run08 75
+#define FRAME_run09 76
+#define FRAME_run10 77
+#define FRAME_run11 78
+#define FRAME_run12 79
+#define FRAME_run13 80
+#define FRAME_run14 81
+#define FRAME_run15 82
+#define FRAME_stand01 83
+#define FRAME_stand02 84
+#define FRAME_stand03 85
+#define FRAME_stand04 86
+#define FRAME_stand05 87
+#define FRAME_stand06 88
+#define FRAME_stand07 89
+#define FRAME_stand08 90
+#define FRAME_stand09 91
+#define FRAME_stand10 92
+#define FRAME_stand11 93
+#define FRAME_stand12 94
+#define FRAME_stand13 95
+#define FRAME_stand14 96
+#define FRAME_stand15 97
+#define FRAME_stand16 98
+#define FRAME_stand17 99
+#define FRAME_stand18 100
+#define FRAME_stand19 101
+#define FRAME_stand20 102
+#define FRAME_stand21 103
+#define FRAME_stand22 104
+#define FRAME_stand23 105
+#define FRAME_stand24 106
+#define FRAME_stand25 107
+#define FRAME_stand26 108
+#define FRAME_stand27 109
+#define FRAME_stand28 110
+#define FRAME_stand29 111
+#define FRAME_stand30 112
+#define FRAME_stand31 113
+#define FRAME_stand32 114
+#define FRAME_stand33 115
+#define FRAME_stand34 116
+#define FRAME_stand35 117
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/soldier/soldier.c b/xatrix/src/monster/soldier/soldier.c
new file mode 100644
index 0000000..f8fed2e
--- /dev/null
+++ b/xatrix/src/monster/soldier/soldier.c
@@ -0,0 +1,3357 @@
+/* =======================================================================
+ *
+ * Soldier aka "Guard". This is the most complex enemy in Quake 2, since
+ * it uses all AI features (dodging, sight, crouching, etc) and comes
+ * in a myriad of variants. In Xatrix it's even more complex due to
+ * another 4 variants added.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "soldier.h"
+#include "soldierh.h"
+
+static int sound_idle;
+static int sound_sight1;
+static int sound_sight2;
+static int sound_pain_light;
+static int sound_pain;
+static int sound_pain_ss;
+static int sound_death_light;
+static int sound_death;
+static int sound_death_ss;
+static int sound_cock;
+
+void soldier_stand(edict_t *self);
+void soldier_run(edict_t *self);
+void soldierh_stand(edict_t *self);
+void soldierh_run(edict_t *self);
+extern void brain_dabeam(edict_t *self);
+
+void
+soldier_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.8)
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+ }
+}
+
+void
+soldier_cock(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_stand322)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t soldier_frames_stand1[] = {
+ {ai_stand, 0, soldier_idle},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t soldier_move_stand1 = {
+ FRAME_stand101,
+ FRAME_stand130,
+ soldier_frames_stand1,
+ soldier_stand
+};
+
+mframe_t soldier_frames_stand3[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, soldier_cock},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t soldier_move_stand3 = {
+ FRAME_stand301,
+ FRAME_stand339,
+ soldier_frames_stand3,
+ soldier_stand
+};
+
+void
+soldier_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &soldier_move_stand3) ||
+ (random() < 0.8))
+ {
+ self->monsterinfo.currentmove = &soldier_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_stand3;
+ }
+}
+
+void
+soldier_walk1_random(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.1)
+ {
+ self->monsterinfo.nextframe = FRAME_walk101;
+ }
+}
+
+mframe_t soldier_frames_walk1[] = {
+ {ai_walk, 3, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, -1, soldier_walk1_random},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t soldier_move_walk1 = {
+ FRAME_walk101,
+ FRAME_walk133,
+ soldier_frames_walk1,
+ NULL
+};
+
+mframe_t soldier_frames_walk2[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 7, NULL}
+};
+
+mmove_t soldier_move_walk2 = {
+ FRAME_walk209,
+ FRAME_walk218,
+ soldier_frames_walk2,
+ NULL
+};
+
+void
+soldier_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &soldier_move_walk1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_walk2;
+ }
+}
+
+mframe_t soldier_frames_start_run[] = {
+ {ai_run, 7, NULL},
+ {ai_run, 5, NULL}
+};
+
+mmove_t soldier_move_start_run = {
+ FRAME_run01,
+ FRAME_run02,
+ soldier_frames_start_run,
+ soldier_run
+};
+
+mframe_t soldier_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 16, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 15, NULL}
+};
+
+mmove_t soldier_move_run = {
+ FRAME_run03,
+ FRAME_run08,
+ soldier_frames_run,
+ NULL
+};
+
+void
+soldier_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &soldier_move_stand1;
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &soldier_move_walk1) ||
+ (self->monsterinfo.currentmove == &soldier_move_walk2) ||
+ (self->monsterinfo.currentmove == &soldier_move_start_run))
+ {
+ self->monsterinfo.currentmove = &soldier_move_run;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_start_run;
+ }
+}
+
+mframe_t soldier_frames_pain1[] = {
+ {ai_move, -3, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain105,
+ soldier_frames_pain1,
+ soldier_run
+};
+
+mframe_t soldier_frames_pain2[] = {
+ {ai_move, -13, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t soldier_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain207,
+ soldier_frames_pain2,
+ soldier_run
+};
+
+mframe_t soldier_frames_pain3[] = {
+ {ai_move, -8, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t soldier_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain318,
+ soldier_frames_pain3,
+ soldier_run
+};
+
+mframe_t soldier_frames_pain4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, 8, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_pain4 = {
+ FRAME_pain401,
+ FRAME_pain417,
+ soldier_frames_pain4,
+ soldier_run
+};
+
+void
+soldier_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ float r;
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum |= 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ if ((self->velocity[2] > 100) &&
+ ((self->monsterinfo.currentmove == &soldier_move_pain1) ||
+ (self->monsterinfo.currentmove == &soldier_move_pain2) ||
+ (self->monsterinfo.currentmove == &soldier_move_pain3)))
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain4;
+ }
+
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ n = self->s.skinnum | 1;
+
+ if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0);
+ }
+ else if (n == 3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0);
+ }
+
+ if (self->velocity[2] > 100)
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain4;
+ return;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain1;
+ }
+ else if (r < 0.66)
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_pain3;
+ }
+}
+
+static int blaster_flash[] = {
+ MZ2_SOLDIER_BLASTER_1,
+ MZ2_SOLDIER_BLASTER_2,
+ MZ2_SOLDIER_BLASTER_3,
+ MZ2_SOLDIER_BLASTER_4,
+ MZ2_SOLDIER_BLASTER_5,
+ MZ2_SOLDIER_BLASTER_6,
+ MZ2_SOLDIER_BLASTER_7,
+ MZ2_SOLDIER_BLASTER_8
+};
+
+static int shotgun_flash[] = {
+ MZ2_SOLDIER_SHOTGUN_1,
+ MZ2_SOLDIER_SHOTGUN_2,
+ MZ2_SOLDIER_SHOTGUN_3,
+ MZ2_SOLDIER_SHOTGUN_4,
+ MZ2_SOLDIER_SHOTGUN_5,
+ MZ2_SOLDIER_SHOTGUN_6,
+ MZ2_SOLDIER_SHOTGUN_7,
+ MZ2_SOLDIER_SHOTGUN_8
+};
+
+static int machinegun_flash[] = {
+ MZ2_SOLDIER_MACHINEGUN_1,
+ MZ2_SOLDIER_MACHINEGUN_2,
+ MZ2_SOLDIER_MACHINEGUN_3,
+ MZ2_SOLDIER_MACHINEGUN_4,
+ MZ2_SOLDIER_MACHINEGUN_5,
+ MZ2_SOLDIER_MACHINEGUN_6,
+ MZ2_SOLDIER_MACHINEGUN_7,
+ MZ2_SOLDIER_MACHINEGUN_8
+};
+
+void
+soldier_fire(edict_t *self, int flash_number)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t aim;
+ vec3_t dir;
+ vec3_t end;
+ float r, u;
+ int flash_index;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ flash_index = blaster_flash[flash_number];
+ }
+ else if (self->s.skinnum < 4)
+ {
+ flash_index = shotgun_flash[flash_number];
+ }
+ else
+ {
+ flash_index = machinegun_flash[flash_number];
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_index],
+ forward, right, start);
+
+ if ((flash_number == 5) || (flash_number == 6))
+ {
+ VectorCopy(forward, aim);
+ }
+ else
+ {
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, aim);
+ vectoangles(aim, dir);
+ AngleVectors(dir, forward, right, up);
+
+ r = crandom() * 1000;
+ u = crandom() * 500;
+ VectorMA(start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+
+ VectorSubtract(end, start, aim);
+ VectorNormalize(aim);
+ }
+
+ if (self->s.skinnum <= 1)
+ {
+ monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER);
+ }
+ else if (self->s.skinnum <= 3)
+ {
+ monster_fire_shotgun(self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD,
+ DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index);
+ }
+ else
+ {
+ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+ {
+ self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME;
+ }
+
+ monster_fire_bullet(self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_index);
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+ }
+}
+
+void
+soldier_fire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 0);
+}
+
+void
+soldier_attack1_refire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum > 1)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak102;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attak110;
+ }
+}
+
+void
+soldier_attack1_refire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak102;
+ }
+}
+
+mframe_t soldier_frames_attack1[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack1_refire1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_cock},
+ {ai_charge, 0, soldier_attack1_refire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak112,
+ soldier_frames_attack1,
+ soldier_run
+};
+
+void
+soldier_fire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 1);
+}
+
+void
+soldier_attack2_refire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum > 1)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak204;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attak216;
+ }
+}
+
+void
+soldier_attack2_refire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak204;
+ }
+}
+
+mframe_t soldier_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack2_refire1},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_cock},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack2_refire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak218,
+ soldier_frames_attack2,
+ soldier_run
+};
+
+void
+soldier_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.pausetime = level.time + 1;
+ gi.linkentity(self);
+}
+
+void
+soldier_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+void
+soldier_fire3(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_duck_down(self);
+ soldier_fire(self, 2);
+}
+
+void
+soldier_attack3_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((level.time + 0.4) < self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.nextframe = FRAME_attak303;
+ }
+}
+
+mframe_t soldier_frames_attack3[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire3},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_attack3_refire},
+ {ai_charge, 0, soldier_duck_up},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack3 = {
+ FRAME_attak301,
+ FRAME_attak309,
+ soldier_frames_attack3,
+ soldier_run
+};
+
+void
+soldier_fire4(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 3);
+}
+
+mframe_t soldier_frames_attack4[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldier_fire4},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldier_move_attack4 = {
+ FRAME_attak401,
+ FRAME_attak406,
+ soldier_frames_attack4,
+ soldier_run
+};
+
+void
+soldier_fire8(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 7);
+}
+
+void
+soldier_attack6_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (range(self, self->enemy) < RANGE_MID)
+ {
+ return;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ self->monsterinfo.nextframe = FRAME_runs03;
+ }
+}
+
+mframe_t soldier_frames_attack6[] = {
+ {ai_charge, 10, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 12, NULL},
+ {ai_charge, 11, soldier_fire8},
+ {ai_charge, 13, NULL},
+ {ai_charge, 18, NULL},
+ {ai_charge, 15, NULL},
+ {ai_charge, 14, NULL},
+ {ai_charge, 11, NULL},
+ {ai_charge, 8, NULL},
+ {ai_charge, 11, NULL},
+ {ai_charge, 12, NULL},
+ {ai_charge, 12, NULL},
+ {ai_charge, 17, soldier_attack6_refire}
+};
+
+mmove_t soldier_move_attack6 = {
+ FRAME_runs01,
+ FRAME_runs14,
+ soldier_frames_attack6,
+ soldier_run
+};
+
+void
+soldier_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 4)
+ {
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack2;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack4;
+ }
+}
+
+void
+soldier_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
+ }
+
+ if ((skill->value > SKILL_EASY) && (range(self, self->enemy) >= RANGE_MID))
+ {
+ if (random() > 0.5)
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack6;
+ }
+ }
+}
+
+void
+soldier_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+mframe_t soldier_frames_duck[] = {
+ {ai_move, 5, soldier_duck_down},
+ {ai_move, -1, soldier_duck_hold},
+ {ai_move, 1, NULL},
+ {ai_move, 0, soldier_duck_up},
+ {ai_move, 5, NULL}
+};
+
+mmove_t soldier_move_duck = {
+ FRAME_duck01,
+ FRAME_duck05,
+ soldier_frames_duck,
+ soldier_run
+};
+
+void
+soldier_dodge(edict_t *self, edict_t *attacker, float eta)
+{
+ float r;
+
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ FoundTarget(self);
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ self->monsterinfo.currentmove = &soldier_move_duck;
+ return;
+ }
+
+ self->monsterinfo.pausetime = level.time + eta + 0.3;
+ r = random();
+
+ if (skill->value == SKILL_MEDIUM)
+ {
+ if (r > 0.33)
+ {
+ self->monsterinfo.currentmove = &soldier_move_duck;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack3;
+ }
+
+ return;
+ }
+
+ if (skill->value >= SKILL_HARD)
+ {
+ if (r > 0.66)
+ {
+ self->monsterinfo.currentmove = &soldier_move_duck;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_attack3;
+ }
+
+ return;
+ }
+
+ self->monsterinfo.currentmove = &soldier_move_attack3;
+}
+
+void
+soldier_fire6(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 5);
+}
+
+void
+soldier_fire7(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldier_fire(self, 6);
+}
+
+void
+soldier_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t soldier_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, soldier_fire6},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, soldier_fire7},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death1 = {
+ FRAME_death101,
+ FRAME_death136,
+ soldier_frames_death1,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death2[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death2 = {
+ FRAME_death201,
+ FRAME_death235,
+ soldier_frames_death2,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death3[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+};
+
+mmove_t soldier_move_death3 = {
+ FRAME_death301,
+ FRAME_death345,
+ soldier_frames_death3,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death4 = {
+ FRAME_death401,
+ FRAME_death453,
+ soldier_frames_death4,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death5[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death5 = {
+ FRAME_death501,
+ FRAME_death524,
+ soldier_frames_death5,
+ soldier_dead
+};
+
+mframe_t soldier_frames_death6[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldier_move_death6 = {
+ FRAME_death601,
+ FRAME_death610,
+ soldier_frames_death6,
+ soldier_dead
+};
+
+void
+soldier_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 3; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum |= 1;
+
+ if (self->s.skinnum == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0);
+ }
+ else if (self->s.skinnum == 3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0);
+ }
+
+ if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4)
+ {
+ /* head shot */
+ self->monsterinfo.currentmove = &soldier_move_death3;
+ return;
+ }
+
+ n = rand() % 5;
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death1;
+ }
+ else if (n == 1)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death2;
+ }
+ else if (n == 2)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death4;
+ }
+ else if (n == 3)
+ {
+ self->monsterinfo.currentmove = &soldier_move_death5;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldier_move_death6;
+ }
+}
+
+void
+SP_monster_soldier_x(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.modelindex = gi.modelindex("models/monsters/soldier/tris.md2");
+ self->monsterinfo.scale = MODEL_SCALE;
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ sound_idle = gi.soundindex("soldier/solidle1.wav");
+ sound_sight1 = gi.soundindex("soldier/solsght1.wav");
+ sound_sight2 = gi.soundindex("soldier/solsrch1.wav");
+ sound_cock = gi.soundindex("infantry/infatck3.wav");
+
+ self->mass = 100;
+
+ self->pain = soldier_pain;
+ self->die = soldier_die;
+
+ self->monsterinfo.stand = soldier_stand;
+ self->monsterinfo.walk = soldier_walk;
+ self->monsterinfo.run = soldier_run;
+ self->monsterinfo.dodge = soldier_dodge;
+ self->monsterinfo.attack = soldier_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = soldier_sight;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.stand(self);
+
+ walkmonster_start(self);
+}
+
+/*
+ * QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_soldier_light(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 20;
+ self->gib_health = -30;
+
+ SP_monster_soldier_x(self);
+
+ sound_pain_light = gi.soundindex("soldier/solpain2.wav");
+ sound_death_light = gi.soundindex("soldier/soldeth2.wav");
+ gi.modelindex("models/objects/laser/tris.md2");
+ gi.soundindex("misc/lasfly.wav");
+ gi.soundindex("soldier/solatck2.wav");
+
+ self->s.skinnum = 0;
+}
+
+/*
+ * QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_soldier(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 30;
+ self->gib_health = -30;
+
+ SP_monster_soldier_x(self);
+
+ sound_pain = gi.soundindex("soldier/solpain1.wav");
+ sound_death = gi.soundindex("soldier/soldeth1.wav");
+ gi.soundindex("soldier/solatck1.wav");
+
+ self->s.skinnum = 2;
+}
+
+/*
+ * QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_soldier_ss(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 40;
+ self->gib_health = -30;
+
+ SP_monster_soldier_x(self);
+
+ sound_pain_ss = gi.soundindex("soldier/solpain3.wav");
+ sound_death_ss = gi.soundindex("soldier/soldeth3.wav");
+ gi.soundindex("soldier/solatck3.wav");
+
+ self->s.skinnum = 4;
+}
+
+void
+soldierh_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.8)
+ {
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+ }
+}
+
+void
+soldierh_cock(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_stand322)
+ {
+ gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t soldierh_frames_stand1[] = {
+ {ai_stand, 0, soldierh_idle},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t soldierh_move_stand1 = {
+ FRAME_stand101,
+ FRAME_stand130,
+ soldierh_frames_stand1,
+ soldierh_stand
+};
+
+mframe_t soldierh_frames_stand3[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, soldierh_cock},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t soldierh_move_stand3 = {
+ FRAME_stand301,
+ FRAME_stand339,
+ soldierh_frames_stand3,
+ soldierh_stand
+};
+
+void
+soldierh_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &soldierh_move_stand3) || (random() < 0.8))
+ {
+ self->monsterinfo.currentmove = &soldierh_move_stand1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_stand3;
+ }
+}
+
+void
+soldierh_walk1_random(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.1)
+ {
+ self->monsterinfo.nextframe = FRAME_walk101;
+ }
+}
+
+mframe_t soldierh_frames_walk1[] = {
+ {ai_walk, 3, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, -1, soldierh_walk1_random},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t soldierh_move_walk1 = {
+ FRAME_walk101,
+ FRAME_walk133,
+ soldierh_frames_walk1,
+ NULL
+};
+
+mframe_t soldierh_frames_walk2[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 9, NULL},
+ {ai_walk, 8, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 1, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 7, NULL}
+};
+
+mmove_t soldierh_move_walk2 = {
+ FRAME_walk209,
+ FRAME_walk218,
+ soldierh_frames_walk2,
+ NULL
+};
+
+void
+soldierh_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_walk1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_walk2;
+ }
+}
+
+mframe_t soldierh_frames_start_run[] = {
+ {ai_run, 7, NULL},
+ {ai_run, 5, NULL}
+};
+
+mmove_t soldierh_move_start_run = {
+ FRAME_run01,
+ FRAME_run02,
+ soldierh_frames_start_run,
+ soldierh_run
+};
+
+mframe_t soldierh_frames_run[] = {
+ {ai_run, 10, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 11, NULL},
+ {ai_run, 16, NULL},
+ {ai_run, 10, NULL},
+ {ai_run, 15, NULL}
+};
+
+mmove_t soldierh_move_run = {
+ FRAME_run03,
+ FRAME_run08,
+ soldierh_frames_run,
+ NULL
+};
+
+void
+soldierh_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_stand1;
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &soldierh_move_walk1) ||
+ (self->monsterinfo.currentmove == &soldierh_move_walk2) ||
+ (self->monsterinfo.currentmove == &soldierh_move_start_run))
+ {
+ self->monsterinfo.currentmove = &soldierh_move_run;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_start_run;
+ }
+}
+
+mframe_t soldierh_frames_pain1[] = {
+ {ai_move, -3, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldierh_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain105,
+ soldierh_frames_pain1,
+ soldierh_run
+};
+
+mframe_t soldierh_frames_pain2[] = {
+ {ai_move, -13, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t soldierh_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain207,
+ soldierh_frames_pain2,
+ soldierh_run
+};
+
+mframe_t soldierh_frames_pain3[] = {
+ {ai_move, -8, NULL},
+ {ai_move, 10, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL}
+};
+
+mmove_t soldierh_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain318,
+ soldierh_frames_pain3,
+ soldierh_run
+};
+
+mframe_t soldierh_frames_pain4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, 8, NULL},
+ {ai_move, 4, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 5, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldierh_move_pain4 = {
+ FRAME_pain401,
+ FRAME_pain417,
+ soldierh_frames_pain4,
+ soldierh_run
+};
+
+void
+soldierh_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ float r;
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum |= 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ if ((self->velocity[2] > 100) &&
+ ((self->monsterinfo.currentmove == &soldierh_move_pain1) ||
+ (self->monsterinfo.currentmove == &soldierh_move_pain2) ||
+ (self->monsterinfo.currentmove == &soldierh_move_pain3)))
+ {
+ self->monsterinfo.currentmove = &soldierh_move_pain4;
+ }
+
+ return;
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ n = self->s.skinnum | 1;
+
+ if (n == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0);
+ }
+ else if (n == 3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0);
+ }
+
+ if (self->velocity[2] > 100)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_pain4;
+ return;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ r = random();
+
+ if (r < 0.33)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_pain1;
+ }
+ else if (r < 0.66)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_pain3;
+ }
+}
+
+void
+soldierh_laserbeam(edict_t *self, int flash_index)
+{
+ vec3_t forward, right, up;
+ vec3_t tempang, start;
+ vec3_t dir, angles, end;
+ vec3_t tempvec;
+ edict_t *ent;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() > 0.8)
+ {
+ gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0);
+ }
+
+ VectorCopy(self->s.origin, start);
+ VectorCopy(self->enemy->s.origin, end);
+ VectorSubtract(end, start, dir);
+ vectoangles(dir, angles);
+ VectorCopy(monster_flash_offset[flash_index], tempvec);
+
+ ent = G_Spawn();
+ VectorCopy(self->s.origin, ent->s.origin);
+ VectorCopy(angles, tempang);
+ AngleVectors(tempang, forward, right, up);
+ VectorCopy(tempang, ent->s.angles);
+ VectorCopy(ent->s.origin, start);
+
+ if (flash_index == 85)
+ {
+ VectorMA(start, tempvec[0] - 14, right, start);
+ VectorMA(start, tempvec[2] + 8, up, start);
+ VectorMA(start, tempvec[1], forward, start);
+ }
+ else
+ {
+ VectorMA(start, tempvec[0] + 2, right, start);
+ VectorMA(start, tempvec[2] + 8, up, start);
+ VectorMA(start, tempvec[1], forward, start);
+ }
+
+ VectorCopy(start, ent->s.origin);
+ ent->enemy = self->enemy;
+ ent->owner = self;
+
+ ent->dmg = 1;
+
+ monster_dabeam(ent);
+}
+
+void
+soldierh_fire(edict_t *self, int flash_number)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t aim;
+ vec3_t dir;
+ vec3_t end;
+ float r, u;
+ int flash_index;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ flash_index = blaster_flash[flash_number]; /* ripper */
+ }
+ else if (self->s.skinnum < 4)
+ {
+ flash_index = blaster_flash[flash_number]; /* hyperblaster */
+ }
+ else
+ {
+ flash_index = machinegun_flash[flash_number]; /* laserbeam */
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_index],
+ forward, right, start);
+
+ if ((flash_number == 5) || (flash_number == 6))
+ {
+ VectorCopy(forward, aim);
+ }
+ else
+ {
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, aim);
+ vectoangles(aim, dir);
+ AngleVectors(dir, forward, right, up);
+
+ r = crandom() * 100;
+ u = crandom() * 50;
+ VectorMA(start, 8192, forward, end);
+ VectorMA(end, r, right, end);
+ VectorMA(end, u, up, end);
+
+ VectorSubtract(end, start, aim);
+ VectorNormalize(aim);
+ }
+
+ if (self->s.skinnum <= 1)
+ {
+ monster_fire_ionripper(self, start, aim, 5, 600,
+ flash_index, EF_IONRIPPER);
+ }
+ else if (self->s.skinnum <= 3)
+ {
+ monster_fire_blueblaster(self, start, aim, 1, 600,
+ MZ_BLUEHYPERBLASTER, EF_BLUEHYPERBLASTER);
+ }
+ else
+ {
+ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+ {
+ self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME;
+ }
+
+ soldierh_laserbeam(self, flash_index);
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+ }
+}
+
+void
+soldierh_hyper_refire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+ else if (self->s.skinnum < 4)
+ {
+ if (random() < 0.7)
+ {
+ self->s.frame = FRAME_attak103;
+ }
+ else
+ {
+ gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+}
+
+void
+soldierh_ripper1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ soldierh_fire(self, 0);
+ }
+ else if (self->s.skinnum < 4)
+ {
+ soldierh_fire(self, 0);
+ }
+}
+
+void
+soldierh_fire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldierh_fire(self, 0);
+}
+
+void
+soldierh_attack1_refire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum > 1)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak102;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attak110;
+ }
+}
+
+void
+soldierh_attack1_refire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak102;
+ }
+}
+
+void
+soldierh_hyper_sound(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+ else if (self->s.skinnum < 4)
+ {
+ gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbl1a.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ return;
+ }
+}
+
+mframe_t soldierh_frames_attack1[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldierh_hyper_sound},
+ {ai_charge, 0, soldierh_fire1},
+ {ai_charge, 0, soldierh_ripper1},
+ {ai_charge, 0, soldierh_ripper1},
+ {ai_charge, 0, soldierh_attack1_refire1},
+ {ai_charge, 0, soldierh_hyper_refire1},
+ {ai_charge, 0, soldierh_cock},
+ {ai_charge, 0, soldierh_attack1_refire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldierh_move_attack1 = {
+ FRAME_attak101,
+ FRAME_attak112,
+ soldierh_frames_attack1,
+ soldierh_run
+};
+
+void
+soldierh_hyper_refire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+ else if (self->s.skinnum < 4)
+ {
+ if (random() < 0.7)
+ {
+ self->s.frame = FRAME_attak205;
+ }
+ else
+ {
+ gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+}
+
+void
+soldierh_ripper2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ soldierh_fire(self, 1);
+ }
+ else if (self->s.skinnum < 4)
+ {
+ soldierh_fire(self, 1);
+ }
+}
+
+void
+soldierh_fire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldierh_fire(self, 1);
+}
+
+void
+soldierh_attack2_refire1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum > 1)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+ {
+ self->monsterinfo.nextframe = FRAME_attak204;
+ }
+ else
+ {
+ self->monsterinfo.nextframe = FRAME_attak216;
+ }
+}
+
+void
+soldierh_attack2_refire2(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 2)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (((skill->value == SKILL_HARDPLUS) &&
+ (random() < 0.5)) ||
+ ((range(self, self->enemy) == RANGE_MELEE) && (self->s.skinnum < 4)))
+ {
+ self->monsterinfo.nextframe = FRAME_attak204;
+ }
+}
+
+mframe_t soldierh_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldierh_hyper_sound},
+ {ai_charge, 0, soldierh_fire2},
+ {ai_charge, 0, soldierh_ripper2},
+ {ai_charge, 0, soldierh_ripper2},
+ {ai_charge, 0, soldierh_attack2_refire1},
+ {ai_charge, 0, soldierh_hyper_refire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldierh_cock},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldierh_attack2_refire2},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldierh_move_attack2 = {
+ FRAME_attak201,
+ FRAME_attak218,
+ soldierh_frames_attack2,
+ soldierh_run
+};
+
+void
+soldierh_duck_down(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_DUCKED)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags |= AI_DUCKED;
+ self->maxs[2] -= 32;
+ self->takedamage = DAMAGE_YES;
+ self->monsterinfo.pausetime = level.time + 1;
+ gi.linkentity(self);
+}
+
+void
+soldierh_duck_up(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.aiflags &= ~AI_DUCKED;
+ self->maxs[2] += 32;
+ self->takedamage = DAMAGE_AIM;
+ gi.linkentity(self);
+}
+
+void
+soldierh_fire3(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldierh_duck_down(self);
+ soldierh_fire(self, 2);
+}
+
+void
+soldierh_attack3_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if ((level.time + 0.4) < self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.nextframe = FRAME_attak303;
+ }
+}
+
+mframe_t soldierh_frames_attack3[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldierh_fire3},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldierh_attack3_refire},
+ {ai_charge, 0, soldierh_duck_up},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldierh_move_attack3 = {
+ FRAME_attak301,
+ FRAME_attak309,
+ soldierh_frames_attack3,
+ soldierh_run
+};
+
+void
+soldierh_fire4(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldierh_fire(self, 3);
+}
+
+mframe_t soldierh_frames_attack4[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, soldierh_fire4},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t soldierh_move_attack4 = {
+ FRAME_attak401,
+ FRAME_attak406,
+ soldierh_frames_attack4,
+ soldierh_run
+};
+
+void
+soldierh_fire8(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ soldierh_fire(self, 7);
+}
+
+void
+soldierh_attack6_refire(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health <= 0)
+ {
+ return;
+ }
+
+ if (range(self, self->enemy) < RANGE_MID)
+ {
+ return;
+ }
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ self->monsterinfo.nextframe = FRAME_runs03;
+ }
+}
+
+mframe_t soldierh_frames_attack6[] = {
+ {ai_charge, 10, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 12, NULL},
+ {ai_charge, 11, soldierh_fire8},
+ {ai_charge, 13, NULL},
+ {ai_charge, 18, NULL},
+ {ai_charge, 15, NULL},
+ {ai_charge, 14, NULL},
+ {ai_charge, 11, NULL},
+ {ai_charge, 8, NULL},
+ {ai_charge, 11, NULL},
+ {ai_charge, 12, NULL},
+ {ai_charge, 12, NULL},
+ {ai_charge, 17, soldierh_attack6_refire}
+};
+
+mmove_t soldierh_move_attack6 = {
+ FRAME_runs01,
+ FRAME_runs14,
+ soldierh_frames_attack6,
+ soldierh_run
+};
+
+void
+soldierh_attack(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.skinnum < 4)
+ {
+ if (random() < 0.5)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_attack2;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_attack4;
+ }
+}
+
+void
+soldierh_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
+ }
+
+ if ((skill->value > SKILL_EASY) && (range(self, self->enemy) >= RANGE_MID))
+ {
+ if (random() > 0.5)
+ {
+ if (self->s.skinnum < 4)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_attack6;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_attack4;
+ }
+ }
+ }
+}
+
+void
+soldierh_duck_hold(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (level.time >= self->monsterinfo.pausetime)
+ {
+ self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+ }
+ else
+ {
+ self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+ }
+}
+
+mframe_t soldierh_frames_duck[] = {
+ {ai_move, 5, soldierh_duck_down},
+ {ai_move, -1, soldierh_duck_hold},
+ {ai_move, 1, NULL},
+ {ai_move, 0, soldierh_duck_up},
+ {ai_move, 5, NULL}
+};
+
+mmove_t soldierh_move_duck = {
+ FRAME_duck01,
+ FRAME_duck05,
+ soldierh_frames_duck,
+ soldierh_run
+};
+
+void
+soldierh_dodge(edict_t *self, edict_t *attacker, float eta)
+{
+ float r;
+
+ if (!self || !attacker)
+ {
+ return;
+ }
+
+ r = random();
+
+ if (r > 0.25)
+ {
+ return;
+ }
+
+ if (!self->enemy)
+ {
+ self->enemy = attacker;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_duck;
+ return;
+ }
+
+ self->monsterinfo.pausetime = level.time + eta + 0.3;
+ r = random();
+
+ if (skill->value == SKILL_MEDIUM)
+ {
+ if (r > 0.33)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_duck;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_attack3;
+ }
+
+ return;
+ }
+
+ if (skill->value >= SKILL_HARD)
+ {
+ if (r > 0.66)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_duck;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_attack3;
+ }
+
+ return;
+ }
+
+ self->monsterinfo.currentmove = &soldierh_move_attack3;
+}
+
+void
+soldierh_fire6(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* no fire laser */
+ if (self->s.skinnum < 4)
+ {
+ soldierh_fire(self, 5);
+ }
+}
+
+void
+soldierh_fire7(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* no fire laser */
+ if (self->s.skinnum < 4)
+ {
+ soldierh_fire(self, 6);
+ }
+}
+
+void
+soldierh_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, -8);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t soldierh_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, soldierh_fire6},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, soldierh_fire7},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldierh_move_death1 = {
+ FRAME_death101,
+ FRAME_death136,
+ soldierh_frames_death1,
+ soldierh_dead
+};
+
+mframe_t soldierh_frames_death2[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldierh_move_death2 = {
+ FRAME_death201,
+ FRAME_death235,
+ soldierh_frames_death2,
+ soldierh_dead
+};
+
+mframe_t soldierh_frames_death3[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+};
+
+mmove_t soldierh_move_death3 = {
+ FRAME_death301,
+ FRAME_death345,
+ soldierh_frames_death3,
+ soldierh_dead
+};
+
+mframe_t soldierh_frames_death4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldierh_move_death4 = {
+ FRAME_death401,
+ FRAME_death453,
+ soldierh_frames_death4,
+ soldierh_dead
+};
+
+mframe_t soldierh_frames_death5[] = {
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldierh_move_death5 = {
+ FRAME_death501,
+ FRAME_death524,
+ soldierh_frames_death5,
+ soldierh_dead
+};
+
+mframe_t soldierh_frames_death6[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t soldierh_move_death6 = {
+ FRAME_death601,
+ FRAME_death610,
+ soldierh_frames_death6,
+ soldierh_dead
+};
+
+void
+soldierh_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage, vec3_t point)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 3; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+ self->s.skinnum |= 1;
+
+ if (self->s.skinnum == 1)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0);
+ }
+ else if (self->s.skinnum == 3)
+ {
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0);
+ }
+
+ if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4)
+ {
+ /* head shot */
+ self->monsterinfo.currentmove = &soldierh_move_death3;
+ return;
+ }
+
+ n = (self->s.skinnum < 4) ? (rand() % 5) : (1 + (rand() % 4));
+
+ if (n == 0)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_death1;
+ }
+ else if (n == 1)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_death2;
+ }
+ else if (n == 2)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_death4;
+ }
+ else if (n == 3)
+ {
+ self->monsterinfo.currentmove = &soldierh_move_death5;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &soldierh_move_death6;
+ }
+}
+
+void
+SP_monster_soldier_h(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->s.modelindex = gi.modelindex("models/monsters/soldierh/tris.md2");
+ self->monsterinfo.scale = MODEL_SCALE;
+ VectorSet(self->mins, -16, -16, -24);
+ VectorSet(self->maxs, 16, 16, 32);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ sound_idle = gi.soundindex("soldier/solidle1.wav");
+ sound_sight1 = gi.soundindex("soldier/solsght1.wav");
+ sound_sight2 = gi.soundindex("soldier/solsrch1.wav");
+ sound_cock = gi.soundindex("infantry/infatck3.wav");
+
+ self->mass = 100;
+
+ self->pain = soldierh_pain;
+ self->die = soldierh_die;
+
+ self->monsterinfo.stand = soldierh_stand;
+ self->monsterinfo.walk = soldierh_walk;
+ self->monsterinfo.run = soldierh_run;
+ self->monsterinfo.dodge = soldierh_dodge;
+ self->monsterinfo.attack = soldierh_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = soldierh_sight;
+
+ gi.linkentity(self);
+
+ /* self->monsterinfo.stand (self); */
+ self->monsterinfo.currentmove = &soldierh_move_stand3;
+
+ walkmonster_start(self);
+}
+
+/*
+ * QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_soldier_ripper(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 50;
+ self->gib_health = -30;
+
+ SP_monster_soldier_h(self);
+
+ sound_pain_light = gi.soundindex("soldier/solpain2.wav");
+ sound_death_light = gi.soundindex("soldier/soldeth2.wav");
+
+ gi.modelindex("models/objects/boomrang/tris.md2");
+ gi.soundindex("misc/lasfly.wav");
+ gi.soundindex("soldier/solatck2.wav");
+
+ self->s.skinnum = 0;
+}
+
+/*
+ * QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_soldier_hypergun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 60;
+ self->gib_health = -30;
+
+ SP_monster_soldier_h(self);
+
+ gi.modelindex("models/objects/blaser/tris.md2");
+ sound_pain = gi.soundindex("soldier/solpain1.wav");
+ sound_death = gi.soundindex("soldier/soldeth1.wav");
+ gi.soundindex("soldier/solatck1.wav");
+
+ self->s.skinnum = 2;
+}
+
+/*
+ * QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_soldier_lasergun(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->health = 70;
+ self->gib_health = -30;
+
+ SP_monster_soldier_h(self);
+
+ sound_pain_ss = gi.soundindex("soldier/solpain3.wav");
+ sound_death_ss = gi.soundindex("soldier/soldeth3.wav");
+ gi.soundindex("soldier/solatck3.wav");
+
+ self->s.skinnum = 4;
+}
diff --git a/xatrix/src/monster/soldier/soldier.h b/xatrix/src/monster/soldier/soldier.h
new file mode 100644
index 0000000..10acd88
--- /dev/null
+++ b/xatrix/src/monster/soldier/soldier.h
@@ -0,0 +1,484 @@
+/* =======================================================================
+ *
+ * Soldier aka "Guard" animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak201 12
+#define FRAME_attak202 13
+#define FRAME_attak203 14
+#define FRAME_attak204 15
+#define FRAME_attak205 16
+#define FRAME_attak206 17
+#define FRAME_attak207 18
+#define FRAME_attak208 19
+#define FRAME_attak209 20
+#define FRAME_attak210 21
+#define FRAME_attak211 22
+#define FRAME_attak212 23
+#define FRAME_attak213 24
+#define FRAME_attak214 25
+#define FRAME_attak215 26
+#define FRAME_attak216 27
+#define FRAME_attak217 28
+#define FRAME_attak218 29
+#define FRAME_attak301 30
+#define FRAME_attak302 31
+#define FRAME_attak303 32
+#define FRAME_attak304 33
+#define FRAME_attak305 34
+#define FRAME_attak306 35
+#define FRAME_attak307 36
+#define FRAME_attak308 37
+#define FRAME_attak309 38
+#define FRAME_attak401 39
+#define FRAME_attak402 40
+#define FRAME_attak403 41
+#define FRAME_attak404 42
+#define FRAME_attak405 43
+#define FRAME_attak406 44
+#define FRAME_duck01 45
+#define FRAME_duck02 46
+#define FRAME_duck03 47
+#define FRAME_duck04 48
+#define FRAME_duck05 49
+#define FRAME_pain101 50
+#define FRAME_pain102 51
+#define FRAME_pain103 52
+#define FRAME_pain104 53
+#define FRAME_pain105 54
+#define FRAME_pain201 55
+#define FRAME_pain202 56
+#define FRAME_pain203 57
+#define FRAME_pain204 58
+#define FRAME_pain205 59
+#define FRAME_pain206 60
+#define FRAME_pain207 61
+#define FRAME_pain301 62
+#define FRAME_pain302 63
+#define FRAME_pain303 64
+#define FRAME_pain304 65
+#define FRAME_pain305 66
+#define FRAME_pain306 67
+#define FRAME_pain307 68
+#define FRAME_pain308 69
+#define FRAME_pain309 70
+#define FRAME_pain310 71
+#define FRAME_pain311 72
+#define FRAME_pain312 73
+#define FRAME_pain313 74
+#define FRAME_pain314 75
+#define FRAME_pain315 76
+#define FRAME_pain316 77
+#define FRAME_pain317 78
+#define FRAME_pain318 79
+#define FRAME_pain401 80
+#define FRAME_pain402 81
+#define FRAME_pain403 82
+#define FRAME_pain404 83
+#define FRAME_pain405 84
+#define FRAME_pain406 85
+#define FRAME_pain407 86
+#define FRAME_pain408 87
+#define FRAME_pain409 88
+#define FRAME_pain410 89
+#define FRAME_pain411 90
+#define FRAME_pain412 91
+#define FRAME_pain413 92
+#define FRAME_pain414 93
+#define FRAME_pain415 94
+#define FRAME_pain416 95
+#define FRAME_pain417 96
+#define FRAME_run01 97
+#define FRAME_run02 98
+#define FRAME_run03 99
+#define FRAME_run04 100
+#define FRAME_run05 101
+#define FRAME_run06 102
+#define FRAME_run07 103
+#define FRAME_run08 104
+#define FRAME_run09 105
+#define FRAME_run10 106
+#define FRAME_run11 107
+#define FRAME_run12 108
+#define FRAME_runs01 109
+#define FRAME_runs02 110
+#define FRAME_runs03 111
+#define FRAME_runs04 112
+#define FRAME_runs05 113
+#define FRAME_runs06 114
+#define FRAME_runs07 115
+#define FRAME_runs08 116
+#define FRAME_runs09 117
+#define FRAME_runs10 118
+#define FRAME_runs11 119
+#define FRAME_runs12 120
+#define FRAME_runs13 121
+#define FRAME_runs14 122
+#define FRAME_runs15 123
+#define FRAME_runs16 124
+#define FRAME_runs17 125
+#define FRAME_runs18 126
+#define FRAME_runt01 127
+#define FRAME_runt02 128
+#define FRAME_runt03 129
+#define FRAME_runt04 130
+#define FRAME_runt05 131
+#define FRAME_runt06 132
+#define FRAME_runt07 133
+#define FRAME_runt08 134
+#define FRAME_runt09 135
+#define FRAME_runt10 136
+#define FRAME_runt11 137
+#define FRAME_runt12 138
+#define FRAME_runt13 139
+#define FRAME_runt14 140
+#define FRAME_runt15 141
+#define FRAME_runt16 142
+#define FRAME_runt17 143
+#define FRAME_runt18 144
+#define FRAME_runt19 145
+#define FRAME_stand101 146
+#define FRAME_stand102 147
+#define FRAME_stand103 148
+#define FRAME_stand104 149
+#define FRAME_stand105 150
+#define FRAME_stand106 151
+#define FRAME_stand107 152
+#define FRAME_stand108 153
+#define FRAME_stand109 154
+#define FRAME_stand110 155
+#define FRAME_stand111 156
+#define FRAME_stand112 157
+#define FRAME_stand113 158
+#define FRAME_stand114 159
+#define FRAME_stand115 160
+#define FRAME_stand116 161
+#define FRAME_stand117 162
+#define FRAME_stand118 163
+#define FRAME_stand119 164
+#define FRAME_stand120 165
+#define FRAME_stand121 166
+#define FRAME_stand122 167
+#define FRAME_stand123 168
+#define FRAME_stand124 169
+#define FRAME_stand125 170
+#define FRAME_stand126 171
+#define FRAME_stand127 172
+#define FRAME_stand128 173
+#define FRAME_stand129 174
+#define FRAME_stand130 175
+#define FRAME_stand301 176
+#define FRAME_stand302 177
+#define FRAME_stand303 178
+#define FRAME_stand304 179
+#define FRAME_stand305 180
+#define FRAME_stand306 181
+#define FRAME_stand307 182
+#define FRAME_stand308 183
+#define FRAME_stand309 184
+#define FRAME_stand310 185
+#define FRAME_stand311 186
+#define FRAME_stand312 187
+#define FRAME_stand313 188
+#define FRAME_stand314 189
+#define FRAME_stand315 190
+#define FRAME_stand316 191
+#define FRAME_stand317 192
+#define FRAME_stand318 193
+#define FRAME_stand319 194
+#define FRAME_stand320 195
+#define FRAME_stand321 196
+#define FRAME_stand322 197
+#define FRAME_stand323 198
+#define FRAME_stand324 199
+#define FRAME_stand325 200
+#define FRAME_stand326 201
+#define FRAME_stand327 202
+#define FRAME_stand328 203
+#define FRAME_stand329 204
+#define FRAME_stand330 205
+#define FRAME_stand331 206
+#define FRAME_stand332 207
+#define FRAME_stand333 208
+#define FRAME_stand334 209
+#define FRAME_stand335 210
+#define FRAME_stand336 211
+#define FRAME_stand337 212
+#define FRAME_stand338 213
+#define FRAME_stand339 214
+#define FRAME_walk101 215
+#define FRAME_walk102 216
+#define FRAME_walk103 217
+#define FRAME_walk104 218
+#define FRAME_walk105 219
+#define FRAME_walk106 220
+#define FRAME_walk107 221
+#define FRAME_walk108 222
+#define FRAME_walk109 223
+#define FRAME_walk110 224
+#define FRAME_walk111 225
+#define FRAME_walk112 226
+#define FRAME_walk113 227
+#define FRAME_walk114 228
+#define FRAME_walk115 229
+#define FRAME_walk116 230
+#define FRAME_walk117 231
+#define FRAME_walk118 232
+#define FRAME_walk119 233
+#define FRAME_walk120 234
+#define FRAME_walk121 235
+#define FRAME_walk122 236
+#define FRAME_walk123 237
+#define FRAME_walk124 238
+#define FRAME_walk125 239
+#define FRAME_walk126 240
+#define FRAME_walk127 241
+#define FRAME_walk128 242
+#define FRAME_walk129 243
+#define FRAME_walk130 244
+#define FRAME_walk131 245
+#define FRAME_walk132 246
+#define FRAME_walk133 247
+#define FRAME_walk201 248
+#define FRAME_walk202 249
+#define FRAME_walk203 250
+#define FRAME_walk204 251
+#define FRAME_walk205 252
+#define FRAME_walk206 253
+#define FRAME_walk207 254
+#define FRAME_walk208 255
+#define FRAME_walk209 256
+#define FRAME_walk210 257
+#define FRAME_walk211 258
+#define FRAME_walk212 259
+#define FRAME_walk213 260
+#define FRAME_walk214 261
+#define FRAME_walk215 262
+#define FRAME_walk216 263
+#define FRAME_walk217 264
+#define FRAME_walk218 265
+#define FRAME_walk219 266
+#define FRAME_walk220 267
+#define FRAME_walk221 268
+#define FRAME_walk222 269
+#define FRAME_walk223 270
+#define FRAME_walk224 271
+#define FRAME_death101 272
+#define FRAME_death102 273
+#define FRAME_death103 274
+#define FRAME_death104 275
+#define FRAME_death105 276
+#define FRAME_death106 277
+#define FRAME_death107 278
+#define FRAME_death108 279
+#define FRAME_death109 280
+#define FRAME_death110 281
+#define FRAME_death111 282
+#define FRAME_death112 283
+#define FRAME_death113 284
+#define FRAME_death114 285
+#define FRAME_death115 286
+#define FRAME_death116 287
+#define FRAME_death117 288
+#define FRAME_death118 289
+#define FRAME_death119 290
+#define FRAME_death120 291
+#define FRAME_death121 292
+#define FRAME_death122 293
+#define FRAME_death123 294
+#define FRAME_death124 295
+#define FRAME_death125 296
+#define FRAME_death126 297
+#define FRAME_death127 298
+#define FRAME_death128 299
+#define FRAME_death129 300
+#define FRAME_death130 301
+#define FRAME_death131 302
+#define FRAME_death132 303
+#define FRAME_death133 304
+#define FRAME_death134 305
+#define FRAME_death135 306
+#define FRAME_death136 307
+#define FRAME_death201 308
+#define FRAME_death202 309
+#define FRAME_death203 310
+#define FRAME_death204 311
+#define FRAME_death205 312
+#define FRAME_death206 313
+#define FRAME_death207 314
+#define FRAME_death208 315
+#define FRAME_death209 316
+#define FRAME_death210 317
+#define FRAME_death211 318
+#define FRAME_death212 319
+#define FRAME_death213 320
+#define FRAME_death214 321
+#define FRAME_death215 322
+#define FRAME_death216 323
+#define FRAME_death217 324
+#define FRAME_death218 325
+#define FRAME_death219 326
+#define FRAME_death220 327
+#define FRAME_death221 328
+#define FRAME_death222 329
+#define FRAME_death223 330
+#define FRAME_death224 331
+#define FRAME_death225 332
+#define FRAME_death226 333
+#define FRAME_death227 334
+#define FRAME_death228 335
+#define FRAME_death229 336
+#define FRAME_death230 337
+#define FRAME_death231 338
+#define FRAME_death232 339
+#define FRAME_death233 340
+#define FRAME_death234 341
+#define FRAME_death235 342
+#define FRAME_death301 343
+#define FRAME_death302 344
+#define FRAME_death303 345
+#define FRAME_death304 346
+#define FRAME_death305 347
+#define FRAME_death306 348
+#define FRAME_death307 349
+#define FRAME_death308 350
+#define FRAME_death309 351
+#define FRAME_death310 352
+#define FRAME_death311 353
+#define FRAME_death312 354
+#define FRAME_death313 355
+#define FRAME_death314 356
+#define FRAME_death315 357
+#define FRAME_death316 358
+#define FRAME_death317 359
+#define FRAME_death318 360
+#define FRAME_death319 361
+#define FRAME_death320 362
+#define FRAME_death321 363
+#define FRAME_death322 364
+#define FRAME_death323 365
+#define FRAME_death324 366
+#define FRAME_death325 367
+#define FRAME_death326 368
+#define FRAME_death327 369
+#define FRAME_death328 370
+#define FRAME_death329 371
+#define FRAME_death330 372
+#define FRAME_death331 373
+#define FRAME_death332 374
+#define FRAME_death333 375
+#define FRAME_death334 376
+#define FRAME_death335 377
+#define FRAME_death336 378
+#define FRAME_death337 379
+#define FRAME_death338 380
+#define FRAME_death339 381
+#define FRAME_death340 382
+#define FRAME_death341 383
+#define FRAME_death342 384
+#define FRAME_death343 385
+#define FRAME_death344 386
+#define FRAME_death345 387
+#define FRAME_death401 388
+#define FRAME_death402 389
+#define FRAME_death403 390
+#define FRAME_death404 391
+#define FRAME_death405 392
+#define FRAME_death406 393
+#define FRAME_death407 394
+#define FRAME_death408 395
+#define FRAME_death409 396
+#define FRAME_death410 397
+#define FRAME_death411 398
+#define FRAME_death412 399
+#define FRAME_death413 400
+#define FRAME_death414 401
+#define FRAME_death415 402
+#define FRAME_death416 403
+#define FRAME_death417 404
+#define FRAME_death418 405
+#define FRAME_death419 406
+#define FRAME_death420 407
+#define FRAME_death421 408
+#define FRAME_death422 409
+#define FRAME_death423 410
+#define FRAME_death424 411
+#define FRAME_death425 412
+#define FRAME_death426 413
+#define FRAME_death427 414
+#define FRAME_death428 415
+#define FRAME_death429 416
+#define FRAME_death430 417
+#define FRAME_death431 418
+#define FRAME_death432 419
+#define FRAME_death433 420
+#define FRAME_death434 421
+#define FRAME_death435 422
+#define FRAME_death436 423
+#define FRAME_death437 424
+#define FRAME_death438 425
+#define FRAME_death439 426
+#define FRAME_death440 427
+#define FRAME_death441 428
+#define FRAME_death442 429
+#define FRAME_death443 430
+#define FRAME_death444 431
+#define FRAME_death445 432
+#define FRAME_death446 433
+#define FRAME_death447 434
+#define FRAME_death448 435
+#define FRAME_death449 436
+#define FRAME_death450 437
+#define FRAME_death451 438
+#define FRAME_death452 439
+#define FRAME_death453 440
+#define FRAME_death501 441
+#define FRAME_death502 442
+#define FRAME_death503 443
+#define FRAME_death504 444
+#define FRAME_death505 445
+#define FRAME_death506 446
+#define FRAME_death507 447
+#define FRAME_death508 448
+#define FRAME_death509 449
+#define FRAME_death510 450
+#define FRAME_death511 451
+#define FRAME_death512 452
+#define FRAME_death513 453
+#define FRAME_death514 454
+#define FRAME_death515 455
+#define FRAME_death516 456
+#define FRAME_death517 457
+#define FRAME_death518 458
+#define FRAME_death519 459
+#define FRAME_death520 460
+#define FRAME_death521 461
+#define FRAME_death522 462
+#define FRAME_death523 463
+#define FRAME_death524 464
+#define FRAME_death601 465
+#define FRAME_death602 466
+#define FRAME_death603 467
+#define FRAME_death604 468
+#define FRAME_death605 469
+#define FRAME_death606 470
+#define FRAME_death607 471
+#define FRAME_death608 472
+#define FRAME_death609 473
+#define FRAME_death610 474
+
+#define MODEL_SCALE 1.200000
diff --git a/xatrix/src/monster/soldier/soldierh.h b/xatrix/src/monster/soldier/soldierh.h
new file mode 100644
index 0000000..903505c
--- /dev/null
+++ b/xatrix/src/monster/soldier/soldierh.h
@@ -0,0 +1,485 @@
+/* =======================================================================
+ *
+ * Soldier aka "Guard" animations. This is the new model added in
+ * Xatrix, used for the new variants of the enemy.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak101 0
+#define FRAME_attak102 1
+#define FRAME_attak103 2
+#define FRAME_attak104 3
+#define FRAME_attak105 4
+#define FRAME_attak106 5
+#define FRAME_attak107 6
+#define FRAME_attak108 7
+#define FRAME_attak109 8
+#define FRAME_attak110 9
+#define FRAME_attak111 10
+#define FRAME_attak112 11
+#define FRAME_attak201 12
+#define FRAME_attak202 13
+#define FRAME_attak203 14
+#define FRAME_attak204 15
+#define FRAME_attak205 16
+#define FRAME_attak206 17
+#define FRAME_attak207 18
+#define FRAME_attak208 19
+#define FRAME_attak209 20
+#define FRAME_attak210 21
+#define FRAME_attak211 22
+#define FRAME_attak212 23
+#define FRAME_attak213 24
+#define FRAME_attak214 25
+#define FRAME_attak215 26
+#define FRAME_attak216 27
+#define FRAME_attak217 28
+#define FRAME_attak218 29
+#define FRAME_attak301 30
+#define FRAME_attak302 31
+#define FRAME_attak303 32
+#define FRAME_attak304 33
+#define FRAME_attak305 34
+#define FRAME_attak306 35
+#define FRAME_attak307 36
+#define FRAME_attak308 37
+#define FRAME_attak309 38
+#define FRAME_attak401 39
+#define FRAME_attak402 40
+#define FRAME_attak403 41
+#define FRAME_attak404 42
+#define FRAME_attak405 43
+#define FRAME_attak406 44
+#define FRAME_duck01 45
+#define FRAME_duck02 46
+#define FRAME_duck03 47
+#define FRAME_duck04 48
+#define FRAME_duck05 49
+#define FRAME_pain101 50
+#define FRAME_pain102 51
+#define FRAME_pain103 52
+#define FRAME_pain104 53
+#define FRAME_pain105 54
+#define FRAME_pain201 55
+#define FRAME_pain202 56
+#define FRAME_pain203 57
+#define FRAME_pain204 58
+#define FRAME_pain205 59
+#define FRAME_pain206 60
+#define FRAME_pain207 61
+#define FRAME_pain301 62
+#define FRAME_pain302 63
+#define FRAME_pain303 64
+#define FRAME_pain304 65
+#define FRAME_pain305 66
+#define FRAME_pain306 67
+#define FRAME_pain307 68
+#define FRAME_pain308 69
+#define FRAME_pain309 70
+#define FRAME_pain310 71
+#define FRAME_pain311 72
+#define FRAME_pain312 73
+#define FRAME_pain313 74
+#define FRAME_pain314 75
+#define FRAME_pain315 76
+#define FRAME_pain316 77
+#define FRAME_pain317 78
+#define FRAME_pain318 79
+#define FRAME_pain401 80
+#define FRAME_pain402 81
+#define FRAME_pain403 82
+#define FRAME_pain404 83
+#define FRAME_pain405 84
+#define FRAME_pain406 85
+#define FRAME_pain407 86
+#define FRAME_pain408 87
+#define FRAME_pain409 88
+#define FRAME_pain410 89
+#define FRAME_pain411 90
+#define FRAME_pain412 91
+#define FRAME_pain413 92
+#define FRAME_pain414 93
+#define FRAME_pain415 94
+#define FRAME_pain416 95
+#define FRAME_pain417 96
+#define FRAME_run01 97
+#define FRAME_run02 98
+#define FRAME_run03 99
+#define FRAME_run04 100
+#define FRAME_run05 101
+#define FRAME_run06 102
+#define FRAME_run07 103
+#define FRAME_run08 104
+#define FRAME_run09 105
+#define FRAME_run10 106
+#define FRAME_run11 107
+#define FRAME_run12 108
+#define FRAME_runs01 109
+#define FRAME_runs02 110
+#define FRAME_runs03 111
+#define FRAME_runs04 112
+#define FRAME_runs05 113
+#define FRAME_runs06 114
+#define FRAME_runs07 115
+#define FRAME_runs08 116
+#define FRAME_runs09 117
+#define FRAME_runs10 118
+#define FRAME_runs11 119
+#define FRAME_runs12 120
+#define FRAME_runs13 121
+#define FRAME_runs14 122
+#define FRAME_runs15 123
+#define FRAME_runs16 124
+#define FRAME_runs17 125
+#define FRAME_runs18 126
+#define FRAME_runt01 127
+#define FRAME_runt02 128
+#define FRAME_runt03 129
+#define FRAME_runt04 130
+#define FRAME_runt05 131
+#define FRAME_runt06 132
+#define FRAME_runt07 133
+#define FRAME_runt08 134
+#define FRAME_runt09 135
+#define FRAME_runt10 136
+#define FRAME_runt11 137
+#define FRAME_runt12 138
+#define FRAME_runt13 139
+#define FRAME_runt14 140
+#define FRAME_runt15 141
+#define FRAME_runt16 142
+#define FRAME_runt17 143
+#define FRAME_runt18 144
+#define FRAME_runt19 145
+#define FRAME_stand101 146
+#define FRAME_stand102 147
+#define FRAME_stand103 148
+#define FRAME_stand104 149
+#define FRAME_stand105 150
+#define FRAME_stand106 151
+#define FRAME_stand107 152
+#define FRAME_stand108 153
+#define FRAME_stand109 154
+#define FRAME_stand110 155
+#define FRAME_stand111 156
+#define FRAME_stand112 157
+#define FRAME_stand113 158
+#define FRAME_stand114 159
+#define FRAME_stand115 160
+#define FRAME_stand116 161
+#define FRAME_stand117 162
+#define FRAME_stand118 163
+#define FRAME_stand119 164
+#define FRAME_stand120 165
+#define FRAME_stand121 166
+#define FRAME_stand122 167
+#define FRAME_stand123 168
+#define FRAME_stand124 169
+#define FRAME_stand125 170
+#define FRAME_stand126 171
+#define FRAME_stand127 172
+#define FRAME_stand128 173
+#define FRAME_stand129 174
+#define FRAME_stand130 175
+#define FRAME_stand301 176
+#define FRAME_stand302 177
+#define FRAME_stand303 178
+#define FRAME_stand304 179
+#define FRAME_stand305 180
+#define FRAME_stand306 181
+#define FRAME_stand307 182
+#define FRAME_stand308 183
+#define FRAME_stand309 184
+#define FRAME_stand310 185
+#define FRAME_stand311 186
+#define FRAME_stand312 187
+#define FRAME_stand313 188
+#define FRAME_stand314 189
+#define FRAME_stand315 190
+#define FRAME_stand316 191
+#define FRAME_stand317 192
+#define FRAME_stand318 193
+#define FRAME_stand319 194
+#define FRAME_stand320 195
+#define FRAME_stand321 196
+#define FRAME_stand322 197
+#define FRAME_stand323 198
+#define FRAME_stand324 199
+#define FRAME_stand325 200
+#define FRAME_stand326 201
+#define FRAME_stand327 202
+#define FRAME_stand328 203
+#define FRAME_stand329 204
+#define FRAME_stand330 205
+#define FRAME_stand331 206
+#define FRAME_stand332 207
+#define FRAME_stand333 208
+#define FRAME_stand334 209
+#define FRAME_stand335 210
+#define FRAME_stand336 211
+#define FRAME_stand337 212
+#define FRAME_stand338 213
+#define FRAME_stand339 214
+#define FRAME_walk101 215
+#define FRAME_walk102 216
+#define FRAME_walk103 217
+#define FRAME_walk104 218
+#define FRAME_walk105 219
+#define FRAME_walk106 220
+#define FRAME_walk107 221
+#define FRAME_walk108 222
+#define FRAME_walk109 223
+#define FRAME_walk110 224
+#define FRAME_walk111 225
+#define FRAME_walk112 226
+#define FRAME_walk113 227
+#define FRAME_walk114 228
+#define FRAME_walk115 229
+#define FRAME_walk116 230
+#define FRAME_walk117 231
+#define FRAME_walk118 232
+#define FRAME_walk119 233
+#define FRAME_walk120 234
+#define FRAME_walk121 235
+#define FRAME_walk122 236
+#define FRAME_walk123 237
+#define FRAME_walk124 238
+#define FRAME_walk125 239
+#define FRAME_walk126 240
+#define FRAME_walk127 241
+#define FRAME_walk128 242
+#define FRAME_walk129 243
+#define FRAME_walk130 244
+#define FRAME_walk131 245
+#define FRAME_walk132 246
+#define FRAME_walk133 247
+#define FRAME_walk201 248
+#define FRAME_walk202 249
+#define FRAME_walk203 250
+#define FRAME_walk204 251
+#define FRAME_walk205 252
+#define FRAME_walk206 253
+#define FRAME_walk207 254
+#define FRAME_walk208 255
+#define FRAME_walk209 256
+#define FRAME_walk210 257
+#define FRAME_walk211 258
+#define FRAME_walk212 259
+#define FRAME_walk213 260
+#define FRAME_walk214 261
+#define FRAME_walk215 262
+#define FRAME_walk216 263
+#define FRAME_walk217 264
+#define FRAME_walk218 265
+#define FRAME_walk219 266
+#define FRAME_walk220 267
+#define FRAME_walk221 268
+#define FRAME_walk222 269
+#define FRAME_walk223 270
+#define FRAME_walk224 271
+#define FRAME_death101 272
+#define FRAME_death102 273
+#define FRAME_death103 274
+#define FRAME_death104 275
+#define FRAME_death105 276
+#define FRAME_death106 277
+#define FRAME_death107 278
+#define FRAME_death108 279
+#define FRAME_death109 280
+#define FRAME_death110 281
+#define FRAME_death111 282
+#define FRAME_death112 283
+#define FRAME_death113 284
+#define FRAME_death114 285
+#define FRAME_death115 286
+#define FRAME_death116 287
+#define FRAME_death117 288
+#define FRAME_death118 289
+#define FRAME_death119 290
+#define FRAME_death120 291
+#define FRAME_death121 292
+#define FRAME_death122 293
+#define FRAME_death123 294
+#define FRAME_death124 295
+#define FRAME_death125 296
+#define FRAME_death126 297
+#define FRAME_death127 298
+#define FRAME_death128 299
+#define FRAME_death129 300
+#define FRAME_death130 301
+#define FRAME_death131 302
+#define FRAME_death132 303
+#define FRAME_death133 304
+#define FRAME_death134 305
+#define FRAME_death135 306
+#define FRAME_death136 307
+#define FRAME_death201 308
+#define FRAME_death202 309
+#define FRAME_death203 310
+#define FRAME_death204 311
+#define FRAME_death205 312
+#define FRAME_death206 313
+#define FRAME_death207 314
+#define FRAME_death208 315
+#define FRAME_death209 316
+#define FRAME_death210 317
+#define FRAME_death211 318
+#define FRAME_death212 319
+#define FRAME_death213 320
+#define FRAME_death214 321
+#define FRAME_death215 322
+#define FRAME_death216 323
+#define FRAME_death217 324
+#define FRAME_death218 325
+#define FRAME_death219 326
+#define FRAME_death220 327
+#define FRAME_death221 328
+#define FRAME_death222 329
+#define FRAME_death223 330
+#define FRAME_death224 331
+#define FRAME_death225 332
+#define FRAME_death226 333
+#define FRAME_death227 334
+#define FRAME_death228 335
+#define FRAME_death229 336
+#define FRAME_death230 337
+#define FRAME_death231 338
+#define FRAME_death232 339
+#define FRAME_death233 340
+#define FRAME_death234 341
+#define FRAME_death235 342
+#define FRAME_death301 343
+#define FRAME_death302 344
+#define FRAME_death303 345
+#define FRAME_death304 346
+#define FRAME_death305 347
+#define FRAME_death306 348
+#define FRAME_death307 349
+#define FRAME_death308 350
+#define FRAME_death309 351
+#define FRAME_death310 352
+#define FRAME_death311 353
+#define FRAME_death312 354
+#define FRAME_death313 355
+#define FRAME_death314 356
+#define FRAME_death315 357
+#define FRAME_death316 358
+#define FRAME_death317 359
+#define FRAME_death318 360
+#define FRAME_death319 361
+#define FRAME_death320 362
+#define FRAME_death321 363
+#define FRAME_death322 364
+#define FRAME_death323 365
+#define FRAME_death324 366
+#define FRAME_death325 367
+#define FRAME_death326 368
+#define FRAME_death327 369
+#define FRAME_death328 370
+#define FRAME_death329 371
+#define FRAME_death330 372
+#define FRAME_death331 373
+#define FRAME_death332 374
+#define FRAME_death333 375
+#define FRAME_death334 376
+#define FRAME_death335 377
+#define FRAME_death336 378
+#define FRAME_death337 379
+#define FRAME_death338 380
+#define FRAME_death339 381
+#define FRAME_death340 382
+#define FRAME_death341 383
+#define FRAME_death342 384
+#define FRAME_death343 385
+#define FRAME_death344 386
+#define FRAME_death345 387
+#define FRAME_death401 388
+#define FRAME_death402 389
+#define FRAME_death403 390
+#define FRAME_death404 391
+#define FRAME_death405 392
+#define FRAME_death406 393
+#define FRAME_death407 394
+#define FRAME_death408 395
+#define FRAME_death409 396
+#define FRAME_death410 397
+#define FRAME_death411 398
+#define FRAME_death412 399
+#define FRAME_death413 400
+#define FRAME_death414 401
+#define FRAME_death415 402
+#define FRAME_death416 403
+#define FRAME_death417 404
+#define FRAME_death418 405
+#define FRAME_death419 406
+#define FRAME_death420 407
+#define FRAME_death421 408
+#define FRAME_death422 409
+#define FRAME_death423 410
+#define FRAME_death424 411
+#define FRAME_death425 412
+#define FRAME_death426 413
+#define FRAME_death427 414
+#define FRAME_death428 415
+#define FRAME_death429 416
+#define FRAME_death430 417
+#define FRAME_death431 418
+#define FRAME_death432 419
+#define FRAME_death433 420
+#define FRAME_death434 421
+#define FRAME_death435 422
+#define FRAME_death436 423
+#define FRAME_death437 424
+#define FRAME_death438 425
+#define FRAME_death439 426
+#define FRAME_death440 427
+#define FRAME_death441 428
+#define FRAME_death442 429
+#define FRAME_death443 430
+#define FRAME_death444 431
+#define FRAME_death445 432
+#define FRAME_death446 433
+#define FRAME_death447 434
+#define FRAME_death448 435
+#define FRAME_death449 436
+#define FRAME_death450 437
+#define FRAME_death451 438
+#define FRAME_death452 439
+#define FRAME_death453 440
+#define FRAME_death501 441
+#define FRAME_death502 442
+#define FRAME_death503 443
+#define FRAME_death504 444
+#define FRAME_death505 445
+#define FRAME_death506 446
+#define FRAME_death507 447
+#define FRAME_death508 448
+#define FRAME_death509 449
+#define FRAME_death510 450
+#define FRAME_death511 451
+#define FRAME_death512 452
+#define FRAME_death513 453
+#define FRAME_death514 454
+#define FRAME_death515 455
+#define FRAME_death516 456
+#define FRAME_death517 457
+#define FRAME_death518 458
+#define FRAME_death519 459
+#define FRAME_death520 460
+#define FRAME_death521 461
+#define FRAME_death522 462
+#define FRAME_death523 463
+#define FRAME_death524 464
+#define FRAME_death601 465
+#define FRAME_death602 466
+#define FRAME_death603 467
+#define FRAME_death604 468
+#define FRAME_death605 469
+#define FRAME_death606 470
+#define FRAME_death607 471
+#define FRAME_death608 472
+#define FRAME_death609 473
+#define FRAME_death610 474
+
+#define MODEL_SCALE 1.200000
diff --git a/xatrix/src/monster/supertank/supertank.c b/xatrix/src/monster/supertank/supertank.c
new file mode 100644
index 0000000..064d5fd
--- /dev/null
+++ b/xatrix/src/monster/supertank/supertank.c
@@ -0,0 +1,886 @@
+/* =======================================================================
+ *
+ * Supertank aka "Boss1". This enhanced version features a nice
+ * powershield.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "supertank.h"
+
+qboolean visible(edict_t *self, edict_t *other);
+static int sound_pain1;
+static int sound_pain2;
+static int sound_pain3;
+static int sound_death;
+static int sound_search1;
+static int sound_search2;
+
+static int tread_sound;
+void BossExplode(edict_t *self);
+void supertank_dead(edict_t *self);
+void supertankRocket(edict_t *self);
+void supertankMachineGun(edict_t *self);
+void supertank_reattack1(edict_t *self);
+
+void
+TreadSound(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0);
+}
+
+void
+supertank_search(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (random() < 0.5)
+ {
+ gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+ }
+}
+
+mframe_t supertank_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t supertank_move_stand = {
+ FRAME_stand_1,
+ FRAME_stand_60,
+ supertank_frames_stand,
+ NULL
+};
+
+void
+supertank_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &supertank_move_stand;
+}
+
+mframe_t supertank_frames_run[] = {
+ {ai_run, 12, TreadSound},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL},
+ {ai_run, 12, NULL}
+};
+
+mmove_t supertank_move_run = {
+ FRAME_forwrd_1,
+ FRAME_forwrd_18,
+ supertank_frames_run,
+ NULL
+};
+
+mframe_t supertank_frames_forward[] = {
+ {ai_walk, 4, TreadSound},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, NULL}
+};
+
+mmove_t supertank_move_forward = {
+ FRAME_forwrd_1,
+ FRAME_forwrd_18,
+ supertank_frames_forward,
+ NULL
+};
+
+void
+supertank_forward(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void
+supertank_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void
+supertank_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &supertank_move_stand;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_run;
+ }
+}
+
+mframe_t supertank_frames_turn_right[] = {
+ {ai_move, 0, TreadSound},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_turn_right = {
+ FRAME_right_1,
+ FRAME_right_18,
+ supertank_frames_turn_right,
+ supertank_run
+};
+
+mframe_t supertank_frames_turn_left[] = {
+ {ai_move, 0, TreadSound},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_turn_left = {
+ FRAME_left_1,
+ FRAME_left_18,
+ supertank_frames_turn_left,
+ supertank_run
+};
+
+mframe_t supertank_frames_pain3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_pain3 = {
+ FRAME_pain3_9,
+ FRAME_pain3_12,
+ supertank_frames_pain3,
+ supertank_run
+};
+
+mframe_t supertank_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_pain2 = {
+ FRAME_pain2_5,
+ FRAME_pain2_8,
+ supertank_frames_pain2,
+ supertank_run
+};
+
+mframe_t supertank_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_pain1 = {
+ FRAME_pain1_1,
+ FRAME_pain1_4,
+ supertank_frames_pain1,
+ supertank_run
+};
+
+mframe_t supertank_frames_death1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, BossExplode}
+};
+
+mmove_t supertank_move_death = {
+ FRAME_death_1,
+ FRAME_death_24,
+ supertank_frames_death1,
+ supertank_dead
+};
+
+mframe_t supertank_frames_backward[] = {
+ {ai_walk, 0, TreadSound},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL},
+ {ai_walk, 0, NULL}
+};
+
+mmove_t supertank_move_backward = {
+ FRAME_backwd_1,
+ FRAME_backwd_18,
+ supertank_frames_backward,
+ NULL
+};
+
+mframe_t supertank_frames_attack4[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_attack4 = {
+ FRAME_attak4_1,
+ FRAME_attak4_6,
+ supertank_frames_attack4,
+ supertank_run
+};
+
+mframe_t supertank_frames_attack3[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_attack3 = {
+ FRAME_attak3_1,
+ FRAME_attak3_27,
+ supertank_frames_attack3,
+ supertank_run
+};
+
+mframe_t supertank_frames_attack2[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, supertankRocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, supertankRocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, supertankRocket},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_attack2 = {
+ FRAME_attak2_1,
+ FRAME_attak2_27,
+ supertank_frames_attack2,
+ supertank_run
+};
+
+mframe_t supertank_frames_attack1[] = {
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+ {ai_charge, 0, supertankMachineGun},
+};
+
+mmove_t supertank_move_attack1 = {
+ FRAME_attak1_1,
+ FRAME_attak1_6,
+ supertank_frames_attack1,
+ supertank_reattack1
+};
+
+mframe_t supertank_frames_end_attack1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t supertank_move_end_attack1 = {
+ FRAME_attak1_7,
+ FRAME_attak1_20,
+ supertank_frames_end_attack1,
+ supertank_run
+};
+
+void
+supertank_reattack1(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (visible(self, self->enemy))
+ {
+ if (random() < 0.9)
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_end_attack1;
+ }
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_end_attack1;
+ }
+}
+
+void
+supertank_pain(edict_t *self, edict_t *other /* unused */,
+ float kick /* unused */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum = 1;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ /* Lessen the chance of him going into his pain frames */
+ if (damage <= 25)
+ {
+ if (random() < 0.2)
+ {
+ return;
+ }
+ }
+
+ /* Don't go into pain if he's firing his rockets */
+ if (skill->value >= SKILL_HARD)
+ {
+ if ((self->s.frame >= FRAME_attak2_1) &&
+ (self->s.frame <= FRAME_attak2_14))
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 10)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &supertank_move_pain1;
+ }
+ else if (damage <= 25)
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &supertank_move_pain2;
+ }
+ else
+ {
+ gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+ self->monsterinfo.currentmove = &supertank_move_pain3;
+ }
+}
+
+void
+supertankRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak2_8)
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_1;
+ }
+ else if (self->s.frame == FRAME_attak2_11)
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_2;
+ }
+ else
+ {
+ flash_number = MZ2_SUPERTANK_ROCKET_3;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_rocket(self, start, dir, 50, 500, flash_number);
+}
+
+void
+supertankMachineGun(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t vec;
+ vec3_t start;
+ vec3_t forward, right;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1);
+
+ dir[0] = 0;
+ dir[1] = self->s.angles[1];
+ dir[2] = 0;
+
+ AngleVectors(dir, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ VectorCopy(self->enemy->s.origin, vec);
+ VectorMA(vec, 0, self->enemy->velocity, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, forward);
+ VectorNormalize(forward);
+ }
+
+ monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void
+supertank_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range;
+
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ if (range <= 160)
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack1;
+ }
+ else
+ {
+ /* fire rockets more often at distance */
+ if (random() < 0.3)
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack1;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &supertank_move_attack2;
+ }
+ }
+}
+
+void
+supertank_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -60, -60, 0);
+ VectorSet(self->maxs, 60, 60, 72);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+void
+BossExplode(edict_t *self)
+{
+ vec3_t org;
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ self->think = BossExplode;
+ VectorCopy(self->s.origin, org);
+ org[2] += 24 + (rand() & 15);
+
+ switch (self->count++)
+ {
+ case 0:
+ org[0] -= 24;
+ org[1] -= 24;
+ break;
+ case 1:
+ org[0] += 24;
+ org[1] += 24;
+ break;
+ case 2:
+ org[0] += 24;
+ org[1] -= 24;
+ break;
+ case 3:
+ org[0] -= 24;
+ org[1] += 24;
+ break;
+ case 4:
+ org[0] -= 48;
+ org[1] -= 48;
+ break;
+ case 5:
+ org[0] += 48;
+ org[1] += 48;
+ break;
+ case 6:
+ org[0] -= 48;
+ org[1] += 48;
+ break;
+ case 7:
+ org[0] += 48;
+ org[1] -= 48;
+ break;
+ case 8:
+ self->s.sound = 0;
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 8; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ gi.WriteByte(svc_temp_entity);
+ gi.WriteByte(TE_EXPLOSION1);
+ gi.WritePosition(org);
+ gi.multicast(self->s.origin, MULTICAST_PVS);
+
+ self->nextthink = level.time + 0.1;
+}
+
+void
+supertank_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage /* unused */,
+ vec3_t point /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_NO;
+ self->count = 0;
+ self->monsterinfo.currentmove = &supertank_move_death;
+}
+
+/*
+ * QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight Powershield
+ */
+void
+SP_monster_supertank(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ sound_pain1 = gi.soundindex("bosstank/btkpain1.wav");
+ sound_pain2 = gi.soundindex("bosstank/btkpain2.wav");
+ sound_pain3 = gi.soundindex("bosstank/btkpain3.wav");
+ sound_death = gi.soundindex("bosstank/btkdeth1.wav");
+ sound_search1 = gi.soundindex("bosstank/btkunqv1.wav");
+ sound_search2 = gi.soundindex("bosstank/btkunqv2.wav");
+
+ tread_sound = gi.soundindex("bosstank/btkengn1.wav");
+
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+ self->s.modelindex = gi.modelindex("models/monsters/boss1/tris.md2");
+ VectorSet(self->mins, -64, -64, 0);
+ VectorSet(self->maxs, 64, 64, 112);
+
+ self->health = 1500;
+ self->gib_health = -500;
+ self->mass = 800;
+
+ self->pain = supertank_pain;
+ self->die = supertank_die;
+ self->monsterinfo.stand = supertank_stand;
+ self->monsterinfo.walk = supertank_walk;
+ self->monsterinfo.run = supertank_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = supertank_attack;
+ self->monsterinfo.search = supertank_search;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = NULL;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &supertank_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ if (self->spawnflags & 8)
+ {
+ self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+ self->monsterinfo.power_armor_power = 400;
+ }
+
+ walkmonster_start(self);
+}
diff --git a/xatrix/src/monster/supertank/supertank.h b/xatrix/src/monster/supertank/supertank.h
new file mode 100644
index 0000000..bb8c9db
--- /dev/null
+++ b/xatrix/src/monster/supertank/supertank.h
@@ -0,0 +1,263 @@
+/* =======================================================================
+ *
+ * Supertank aka "Boss1" animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_attak1_1 0
+#define FRAME_attak1_2 1
+#define FRAME_attak1_3 2
+#define FRAME_attak1_4 3
+#define FRAME_attak1_5 4
+#define FRAME_attak1_6 5
+#define FRAME_attak1_7 6
+#define FRAME_attak1_8 7
+#define FRAME_attak1_9 8
+#define FRAME_attak1_10 9
+#define FRAME_attak1_11 10
+#define FRAME_attak1_12 11
+#define FRAME_attak1_13 12
+#define FRAME_attak1_14 13
+#define FRAME_attak1_15 14
+#define FRAME_attak1_16 15
+#define FRAME_attak1_17 16
+#define FRAME_attak1_18 17
+#define FRAME_attak1_19 18
+#define FRAME_attak1_20 19
+#define FRAME_attak2_1 20
+#define FRAME_attak2_2 21
+#define FRAME_attak2_3 22
+#define FRAME_attak2_4 23
+#define FRAME_attak2_5 24
+#define FRAME_attak2_6 25
+#define FRAME_attak2_7 26
+#define FRAME_attak2_8 27
+#define FRAME_attak2_9 28
+#define FRAME_attak2_10 29
+#define FRAME_attak2_11 30
+#define FRAME_attak2_12 31
+#define FRAME_attak2_13 32
+#define FRAME_attak2_14 33
+#define FRAME_attak2_15 34
+#define FRAME_attak2_16 35
+#define FRAME_attak2_17 36
+#define FRAME_attak2_18 37
+#define FRAME_attak2_19 38
+#define FRAME_attak2_20 39
+#define FRAME_attak2_21 40
+#define FRAME_attak2_22 41
+#define FRAME_attak2_23 42
+#define FRAME_attak2_24 43
+#define FRAME_attak2_25 44
+#define FRAME_attak2_26 45
+#define FRAME_attak2_27 46
+#define FRAME_attak3_1 47
+#define FRAME_attak3_2 48
+#define FRAME_attak3_3 49
+#define FRAME_attak3_4 50
+#define FRAME_attak3_5 51
+#define FRAME_attak3_6 52
+#define FRAME_attak3_7 53
+#define FRAME_attak3_8 54
+#define FRAME_attak3_9 55
+#define FRAME_attak3_10 56
+#define FRAME_attak3_11 57
+#define FRAME_attak3_12 58
+#define FRAME_attak3_13 59
+#define FRAME_attak3_14 60
+#define FRAME_attak3_15 61
+#define FRAME_attak3_16 62
+#define FRAME_attak3_17 63
+#define FRAME_attak3_18 64
+#define FRAME_attak3_19 65
+#define FRAME_attak3_20 66
+#define FRAME_attak3_21 67
+#define FRAME_attak3_22 68
+#define FRAME_attak3_23 69
+#define FRAME_attak3_24 70
+#define FRAME_attak3_25 71
+#define FRAME_attak3_26 72
+#define FRAME_attak3_27 73
+#define FRAME_attak4_1 74
+#define FRAME_attak4_2 75
+#define FRAME_attak4_3 76
+#define FRAME_attak4_4 77
+#define FRAME_attak4_5 78
+#define FRAME_attak4_6 79
+#define FRAME_backwd_1 80
+#define FRAME_backwd_2 81
+#define FRAME_backwd_3 82
+#define FRAME_backwd_4 83
+#define FRAME_backwd_5 84
+#define FRAME_backwd_6 85
+#define FRAME_backwd_7 86
+#define FRAME_backwd_8 87
+#define FRAME_backwd_9 88
+#define FRAME_backwd_10 89
+#define FRAME_backwd_11 90
+#define FRAME_backwd_12 91
+#define FRAME_backwd_13 92
+#define FRAME_backwd_14 93
+#define FRAME_backwd_15 94
+#define FRAME_backwd_16 95
+#define FRAME_backwd_17 96
+#define FRAME_backwd_18 97
+#define FRAME_death_1 98
+#define FRAME_death_2 99
+#define FRAME_death_3 100
+#define FRAME_death_4 101
+#define FRAME_death_5 102
+#define FRAME_death_6 103
+#define FRAME_death_7 104
+#define FRAME_death_8 105
+#define FRAME_death_9 106
+#define FRAME_death_10 107
+#define FRAME_death_11 108
+#define FRAME_death_12 109
+#define FRAME_death_13 110
+#define FRAME_death_14 111
+#define FRAME_death_15 112
+#define FRAME_death_16 113
+#define FRAME_death_17 114
+#define FRAME_death_18 115
+#define FRAME_death_19 116
+#define FRAME_death_20 117
+#define FRAME_death_21 118
+#define FRAME_death_22 119
+#define FRAME_death_23 120
+#define FRAME_death_24 121
+#define FRAME_death_31 122
+#define FRAME_death_32 123
+#define FRAME_death_33 124
+#define FRAME_death_45 125
+#define FRAME_death_46 126
+#define FRAME_death_47 127
+#define FRAME_forwrd_1 128
+#define FRAME_forwrd_2 129
+#define FRAME_forwrd_3 130
+#define FRAME_forwrd_4 131
+#define FRAME_forwrd_5 132
+#define FRAME_forwrd_6 133
+#define FRAME_forwrd_7 134
+#define FRAME_forwrd_8 135
+#define FRAME_forwrd_9 136
+#define FRAME_forwrd_10 137
+#define FRAME_forwrd_11 138
+#define FRAME_forwrd_12 139
+#define FRAME_forwrd_13 140
+#define FRAME_forwrd_14 141
+#define FRAME_forwrd_15 142
+#define FRAME_forwrd_16 143
+#define FRAME_forwrd_17 144
+#define FRAME_forwrd_18 145
+#define FRAME_left_1 146
+#define FRAME_left_2 147
+#define FRAME_left_3 148
+#define FRAME_left_4 149
+#define FRAME_left_5 150
+#define FRAME_left_6 151
+#define FRAME_left_7 152
+#define FRAME_left_8 153
+#define FRAME_left_9 154
+#define FRAME_left_10 155
+#define FRAME_left_11 156
+#define FRAME_left_12 157
+#define FRAME_left_13 158
+#define FRAME_left_14 159
+#define FRAME_left_15 160
+#define FRAME_left_16 161
+#define FRAME_left_17 162
+#define FRAME_left_18 163
+#define FRAME_pain1_1 164
+#define FRAME_pain1_2 165
+#define FRAME_pain1_3 166
+#define FRAME_pain1_4 167
+#define FRAME_pain2_5 168
+#define FRAME_pain2_6 169
+#define FRAME_pain2_7 170
+#define FRAME_pain2_8 171
+#define FRAME_pain3_9 172
+#define FRAME_pain3_10 173
+#define FRAME_pain3_11 174
+#define FRAME_pain3_12 175
+#define FRAME_right_1 176
+#define FRAME_right_2 177
+#define FRAME_right_3 178
+#define FRAME_right_4 179
+#define FRAME_right_5 180
+#define FRAME_right_6 181
+#define FRAME_right_7 182
+#define FRAME_right_8 183
+#define FRAME_right_9 184
+#define FRAME_right_10 185
+#define FRAME_right_11 186
+#define FRAME_right_12 187
+#define FRAME_right_13 188
+#define FRAME_right_14 189
+#define FRAME_right_15 190
+#define FRAME_right_16 191
+#define FRAME_right_17 192
+#define FRAME_right_18 193
+#define FRAME_stand_1 194
+#define FRAME_stand_2 195
+#define FRAME_stand_3 196
+#define FRAME_stand_4 197
+#define FRAME_stand_5 198
+#define FRAME_stand_6 199
+#define FRAME_stand_7 200
+#define FRAME_stand_8 201
+#define FRAME_stand_9 202
+#define FRAME_stand_10 203
+#define FRAME_stand_11 204
+#define FRAME_stand_12 205
+#define FRAME_stand_13 206
+#define FRAME_stand_14 207
+#define FRAME_stand_15 208
+#define FRAME_stand_16 209
+#define FRAME_stand_17 210
+#define FRAME_stand_18 211
+#define FRAME_stand_19 212
+#define FRAME_stand_20 213
+#define FRAME_stand_21 214
+#define FRAME_stand_22 215
+#define FRAME_stand_23 216
+#define FRAME_stand_24 217
+#define FRAME_stand_25 218
+#define FRAME_stand_26 219
+#define FRAME_stand_27 220
+#define FRAME_stand_28 221
+#define FRAME_stand_29 222
+#define FRAME_stand_30 223
+#define FRAME_stand_31 224
+#define FRAME_stand_32 225
+#define FRAME_stand_33 226
+#define FRAME_stand_34 227
+#define FRAME_stand_35 228
+#define FRAME_stand_36 229
+#define FRAME_stand_37 230
+#define FRAME_stand_38 231
+#define FRAME_stand_39 232
+#define FRAME_stand_40 233
+#define FRAME_stand_41 234
+#define FRAME_stand_42 235
+#define FRAME_stand_43 236
+#define FRAME_stand_44 237
+#define FRAME_stand_45 238
+#define FRAME_stand_46 239
+#define FRAME_stand_47 240
+#define FRAME_stand_48 241
+#define FRAME_stand_49 242
+#define FRAME_stand_50 243
+#define FRAME_stand_51 244
+#define FRAME_stand_52 245
+#define FRAME_stand_53 246
+#define FRAME_stand_54 247
+#define FRAME_stand_55 248
+#define FRAME_stand_56 249
+#define FRAME_stand_57 250
+#define FRAME_stand_58 251
+#define FRAME_stand_59 252
+#define FRAME_stand_60 253
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/monster/tank/tank.c b/xatrix/src/monster/tank/tank.c
new file mode 100644
index 0000000..7db8c49
--- /dev/null
+++ b/xatrix/src/monster/tank/tank.c
@@ -0,0 +1,1097 @@
+/* =======================================================================
+ *
+ * Tank and Tank Commander.
+ *
+ * =======================================================================
+ */
+
+#include "../../header/local.h"
+#include "tank.h"
+
+void tank_refire_rocket(edict_t *self);
+void tank_doattack_rocket(edict_t *self);
+void tank_reattack_blaster(edict_t *self);
+void tank_walk(edict_t *self);
+void tank_run(edict_t *self);
+
+static int sound_thud;
+static int sound_pain;
+static int sound_idle;
+static int sound_die;
+static int sound_step;
+static int sound_sight;
+static int sound_windup;
+static int sound_strike;
+
+void
+tank_sight(edict_t *self, edict_t *other /* unused */)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void
+tank_footstep(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0);
+}
+
+void
+tank_thud(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0);
+}
+
+void
+tank_windup(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
+}
+
+void
+tank_idle(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t tank_frames_stand[] = {
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL},
+ {ai_stand, 0, NULL}
+};
+
+mmove_t tank_move_stand = {
+ FRAME_stand01,
+ FRAME_stand30,
+ tank_frames_stand,
+ NULL
+};
+
+void
+tank_stand(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &tank_move_stand;
+}
+
+mframe_t tank_frames_start_walk[] = {
+ {ai_walk, 0, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 11, tank_footstep}
+};
+
+mmove_t tank_move_start_walk = {
+ FRAME_walk01,
+ FRAME_walk04,
+ tank_frames_start_walk,
+ tank_walk
+};
+
+mframe_t tank_frames_walk[] = {
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 4, tank_footstep},
+ {ai_walk, 3, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 4, NULL},
+ {ai_walk, 5, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 7, NULL},
+ {ai_walk, 6, NULL},
+ {ai_walk, 6, tank_footstep}
+};
+
+mmove_t tank_move_walk = {
+ FRAME_walk05,
+ FRAME_walk20,
+ tank_frames_walk,
+ NULL
+};
+
+mframe_t tank_frames_stop_walk[] = {
+ {ai_walk, 3, NULL},
+ {ai_walk, 3, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 2, NULL},
+ {ai_walk, 4, tank_footstep}
+};
+
+mmove_t tank_move_stop_walk = {
+ FRAME_walk21,
+ FRAME_walk25,
+ tank_frames_stop_walk,
+ tank_stand
+};
+
+void
+tank_walk(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &tank_move_walk;
+}
+
+mframe_t tank_frames_start_run[] = {
+ {ai_run, 0, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 11, tank_footstep}
+};
+
+mmove_t tank_move_start_run = {
+ FRAME_walk01,
+ FRAME_walk04,
+ tank_frames_start_run,
+ tank_run
+};
+
+mframe_t tank_frames_run[] = {
+ {ai_run, 4, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 4, NULL},
+ {ai_run, 4, tank_footstep},
+ {ai_run, 3, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 4, NULL},
+ {ai_run, 5, NULL},
+ {ai_run, 7, NULL},
+ {ai_run, 7, NULL},
+ {ai_run, 6, NULL},
+ {ai_run, 6, tank_footstep}
+};
+
+mmove_t tank_move_run = {
+ FRAME_walk05,
+ FRAME_walk20,
+ tank_frames_run,
+ NULL
+};
+
+mframe_t tank_frames_stop_run[] = {
+ {ai_run, 3, NULL},
+ {ai_run, 3, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 2, NULL},
+ {ai_run, 4, tank_footstep}
+};
+
+mmove_t tank_move_stop_run = {
+ FRAME_walk21,
+ FRAME_walk25,
+ tank_frames_stop_run,
+ tank_walk
+};
+
+void
+tank_run(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy && self->enemy->client)
+ {
+ self->monsterinfo.aiflags |= AI_BRUTAL;
+ }
+ else
+ {
+ self->monsterinfo.aiflags &= ~AI_BRUTAL;
+ }
+
+ if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+ {
+ self->monsterinfo.currentmove = &tank_move_stand;
+ return;
+ }
+
+ if ((self->monsterinfo.currentmove == &tank_move_walk) ||
+ (self->monsterinfo.currentmove == &tank_move_start_run))
+ {
+ self->monsterinfo.currentmove = &tank_move_run;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_start_run;
+ }
+}
+
+mframe_t tank_frames_pain1[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t tank_move_pain1 = {
+ FRAME_pain101,
+ FRAME_pain104,
+ tank_frames_pain1,
+ tank_run
+};
+
+mframe_t tank_frames_pain2[] = {
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t tank_move_pain2 = {
+ FRAME_pain201,
+ FRAME_pain205,
+ tank_frames_pain2,
+ tank_run
+};
+
+mframe_t tank_frames_pain3[] = {
+ {ai_move, -7, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, tank_footstep}
+};
+
+mmove_t tank_move_pain3 = {
+ FRAME_pain301,
+ FRAME_pain316,
+ tank_frames_pain3,
+ tank_run
+};
+
+void
+tank_pain(edict_t *self, edict_t *other /* other */,
+ float kick /* other */, int damage)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < (self->max_health / 2))
+ {
+ self->s.skinnum |= 1;
+ }
+
+ if (damage <= 10)
+ {
+ return;
+ }
+
+ if (level.time < self->pain_debounce_time)
+ {
+ return;
+ }
+
+ if (damage <= 30)
+ {
+ if (random() > 0.2)
+ {
+ return;
+ }
+ }
+
+ /* If hard or nightmare, don't go into pain while attacking */
+ if (skill->value >= SKILL_HARD)
+ {
+ if ((self->s.frame >= FRAME_attak301) &&
+ (self->s.frame <= FRAME_attak330))
+ {
+ return;
+ }
+
+ if ((self->s.frame >= FRAME_attak101) &&
+ (self->s.frame <= FRAME_attak116))
+ {
+ return;
+ }
+ }
+
+ self->pain_debounce_time = level.time + 3;
+ gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+ if (skill->value == SKILL_HARDPLUS)
+ {
+ return; /* no pain anims in nightmare */
+ }
+
+ if (damage <= 30)
+ {
+ self->monsterinfo.currentmove = &tank_move_pain1;
+ }
+ else if (damage <= 60)
+ {
+ self->monsterinfo.currentmove = &tank_move_pain2;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_pain3;
+ }
+}
+
+void
+TankBlaster(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t end;
+ vec3_t dir;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak110)
+ {
+ flash_number = MZ2_TANK_BLASTER_1;
+ }
+ else if (self->s.frame == FRAME_attak113)
+ {
+ flash_number = MZ2_TANK_BLASTER_2;
+ }
+ else
+ {
+ flash_number = MZ2_TANK_BLASTER_3;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, end);
+ end[2] += self->enemy->viewheight;
+ VectorSubtract(end, start, dir);
+
+ monster_fire_blaster(self, start, dir, 30, 800, flash_number, EF_BLASTER);
+}
+
+void
+TankStrike(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ gi.sound(self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0);
+}
+
+void
+TankRocket(edict_t *self)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t dir;
+ vec3_t vec;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->s.frame == FRAME_attak324)
+ {
+ flash_number = MZ2_TANK_ROCKET_1;
+ }
+ else if (self->s.frame == FRAME_attak327)
+ {
+ flash_number = MZ2_TANK_ROCKET_2;
+ }
+ else
+ {
+ flash_number = MZ2_TANK_ROCKET_3;
+ }
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, dir);
+ VectorNormalize(dir);
+
+ monster_fire_rocket(self, start, dir, 50, 550, flash_number);
+}
+
+void
+TankMachineGun(edict_t *self)
+{
+ vec3_t dir;
+ vec3_t vec;
+ vec3_t start;
+ vec3_t forward, right;
+ int flash_number;
+
+ if (!self)
+ {
+ return;
+ }
+
+ flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406);
+
+ AngleVectors(self->s.angles, forward, right, NULL);
+ G_ProjectSource(self->s.origin, monster_flash_offset[flash_number],
+ forward, right, start);
+
+ if (self->enemy)
+ {
+ VectorCopy(self->enemy->s.origin, vec);
+ vec[2] += self->enemy->viewheight;
+ VectorSubtract(vec, start, vec);
+ vectoangles(vec, vec);
+ dir[0] = vec[0];
+ }
+ else
+ {
+ dir[0] = 0;
+ }
+
+ if (self->s.frame <= FRAME_attak415)
+ {
+ dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411);
+ }
+ else
+ {
+ dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419);
+ }
+
+ dir[2] = 0;
+
+ AngleVectors(dir, forward, NULL, NULL);
+
+ monster_fire_bullet(self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+mframe_t tank_frames_attack_blast[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -2, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster}, /* 10 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster} /* 16 */
+};
+
+mmove_t tank_move_attack_blast = {
+ FRAME_attak101,
+ FRAME_attak116,
+ tank_frames_attack_blast,
+ tank_reattack_blaster
+};
+
+mframe_t tank_frames_reattack_blast[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankBlaster} /* 16 */
+};
+
+mmove_t tank_move_reattack_blast = {
+ FRAME_attak111,
+ FRAME_attak116,
+ tank_frames_reattack_blast,
+ tank_reattack_blaster
+};
+
+mframe_t tank_frames_attack_post_blast[] = {
+ {ai_move, 0, NULL}, /* 17 */
+ {ai_move, 0, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, -2, tank_footstep} /* 22 */
+};
+
+mmove_t tank_move_attack_post_blast = {
+ FRAME_attak117,
+ FRAME_attak122,
+ tank_frames_attack_post_blast,
+ tank_run
+};
+
+void
+tank_reattack_blaster(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (skill->value >= SKILL_HARD)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (self->enemy->health > 0)
+ {
+ if (random() <= 0.6)
+ {
+ self->monsterinfo.currentmove = &tank_move_reattack_blast;
+ return;
+ }
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &tank_move_attack_post_blast;
+}
+
+void
+tank_poststrike(edict_t *self)
+{
+ self->enemy = NULL;
+ tank_run(self);
+}
+
+mframe_t tank_frames_attack_strike[] = {
+ {ai_move, 3, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 7, NULL},
+ {ai_move, 9, tank_footstep},
+ {ai_move, 2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 2, tank_footstep},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, tank_windup},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, TankStrike},
+ {ai_move, 0, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -1, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -10, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, -2, tank_footstep}
+};
+
+mmove_t tank_move_attack_strike = {
+ FRAME_attak201,
+ FRAME_attak238,
+ tank_frames_attack_strike,
+ tank_poststrike
+};
+
+mframe_t tank_frames_attack_pre_rocket[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 10 */
+
+ {ai_charge, 0, NULL},
+ {ai_charge, 1, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 7, NULL},
+ {ai_charge, 7, NULL},
+ {ai_charge, 7, tank_footstep},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 20 */
+
+ {ai_charge, -3, NULL}
+};
+
+mmove_t tank_move_attack_pre_rocket = {
+ FRAME_attak301,
+ FRAME_attak321,
+ tank_frames_attack_pre_rocket,
+ tank_doattack_rocket
+};
+
+mframe_t tank_frames_attack_fire_rocket[] = {
+ {ai_charge, -3, NULL}, /* Loop Start 22 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankRocket}, /* 24 */
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, TankRocket},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, -1, TankRocket} /* 30 Loop End */
+};
+
+mmove_t tank_move_attack_fire_rocket = {
+ FRAME_attak322,
+ FRAME_attak330,
+ tank_frames_attack_fire_rocket,
+ tank_refire_rocket
+};
+
+mframe_t tank_frames_attack_post_rocket[] = {
+ {ai_charge, 0, NULL}, /* 31 */
+ {ai_charge, -1, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 3, NULL},
+ {ai_charge, 4, NULL},
+ {ai_charge, 2, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 40 */
+
+ {ai_charge, 0, NULL},
+ {ai_charge, -9, NULL},
+ {ai_charge, -8, NULL},
+ {ai_charge, -7, NULL},
+ {ai_charge, -1, NULL},
+ {ai_charge, -1, tank_footstep},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}, /* 50 */
+
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t tank_move_attack_post_rocket = {
+ FRAME_attak331,
+ FRAME_attak353,
+ tank_frames_attack_post_rocket,
+ tank_run
+};
+
+mframe_t tank_frames_attack_chain[] = {
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {NULL, 0, TankMachineGun},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL},
+ {ai_charge, 0, NULL}
+};
+
+mmove_t tank_move_attack_chain = {
+ FRAME_attak401,
+ FRAME_attak429,
+ tank_frames_attack_chain,
+ tank_run
+};
+
+void
+tank_refire_rocket(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ /* Only on hard or nightmare */
+ if (skill->value >= SKILL_HARD)
+ {
+ if (self->enemy->health > 0)
+ {
+ if (visible(self, self->enemy))
+ {
+ if (random() <= 0.4)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+ return;
+ }
+ }
+ }
+ }
+
+ self->monsterinfo.currentmove = &tank_move_attack_post_rocket;
+}
+
+void
+tank_doattack_rocket(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+}
+
+void
+tank_attack(edict_t *self)
+{
+ vec3_t vec;
+ float range;
+ float r;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->enemy->health < 0)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_strike;
+ self->monsterinfo.aiflags &= ~AI_BRUTAL;
+ return;
+ }
+
+ VectorSubtract(self->enemy->s.origin, self->s.origin, vec);
+ range = VectorLength(vec);
+
+ r = random();
+
+ if (range <= 125)
+ {
+ if (r < 0.4)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_chain;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_blast;
+ }
+ }
+ else if (range <= 250)
+ {
+ if (r < 0.5)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_chain;
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_blast;
+ }
+ }
+ else
+ {
+ if (r < 0.33)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_chain;
+ }
+ else if (r < 0.66)
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_pre_rocket;
+ self->pain_debounce_time = level.time + 5.0; /* no pain for a while */
+ }
+ else
+ {
+ self->monsterinfo.currentmove = &tank_move_attack_blast;
+ }
+ }
+}
+
+void
+tank_dead(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ VectorSet(self->mins, -16, -16, -16);
+ VectorSet(self->maxs, 16, 16, -0);
+ self->movetype = MOVETYPE_TOSS;
+ self->svflags |= SVF_DEADMONSTER;
+ self->nextthink = 0;
+ gi.linkentity(self);
+}
+
+mframe_t tank_frames_death1[] = {
+ {ai_move, -7, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 3, NULL},
+ {ai_move, 6, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 1, NULL},
+ {ai_move, 2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -2, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -3, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -6, NULL},
+ {ai_move, -4, NULL},
+ {ai_move, -5, NULL},
+ {ai_move, -7, NULL},
+ {ai_move, -15, tank_thud},
+ {ai_move, -5, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL},
+ {ai_move, 0, NULL}
+};
+
+mmove_t tank_move_death = {
+ FRAME_death101,
+ FRAME_death132,
+ tank_frames_death1,
+ tank_dead
+};
+
+void
+tank_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ /* check for gib */
+ if (self->health <= self->gib_health)
+ {
+ gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 1 /*4*/; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+ }
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
+ }
+
+ ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+ ThrowHead(self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
+ self->deadflag = DEAD_DEAD;
+ return;
+ }
+
+ if (self->deadflag == DEAD_DEAD)
+ {
+ return;
+ }
+
+ /* regular death */
+ gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+ self->deadflag = DEAD_DEAD;
+ self->takedamage = DAMAGE_YES;
+
+ self->monsterinfo.currentmove = &tank_move_death;
+}
+
+/*
+ * QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+ * QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+ */
+void
+SP_monster_tank(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ self->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
+ VectorSet(self->mins, -32, -32, -16);
+ VectorSet(self->maxs, 32, 32, 72);
+ self->movetype = MOVETYPE_STEP;
+ self->solid = SOLID_BBOX;
+
+ sound_pain = gi.soundindex("tank/tnkpain2.wav");
+ sound_thud = gi.soundindex("tank/tnkdeth2.wav");
+ sound_idle = gi.soundindex("tank/tnkidle1.wav");
+ sound_die = gi.soundindex("tank/death.wav");
+ sound_step = gi.soundindex("tank/step.wav");
+ sound_windup = gi.soundindex("tank/tnkatck4.wav");
+ sound_strike = gi.soundindex("tank/tnkatck5.wav");
+ sound_sight = gi.soundindex("tank/sight1.wav");
+
+ gi.soundindex("tank/tnkatck1.wav");
+ gi.soundindex("tank/tnkatk2a.wav");
+ gi.soundindex("tank/tnkatk2b.wav");
+ gi.soundindex("tank/tnkatk2c.wav");
+ gi.soundindex("tank/tnkatk2d.wav");
+ gi.soundindex("tank/tnkatk2e.wav");
+ gi.soundindex("tank/tnkatck3.wav");
+
+ if (strcmp(self->classname, "monster_tank_commander") == 0)
+ {
+ self->health = 1000;
+ self->gib_health = -225;
+ }
+ else
+ {
+ self->health = 750;
+ self->gib_health = -200;
+ }
+
+ self->mass = 500;
+
+ self->pain = tank_pain;
+ self->die = tank_die;
+ self->monsterinfo.stand = tank_stand;
+ self->monsterinfo.walk = tank_walk;
+ self->monsterinfo.run = tank_run;
+ self->monsterinfo.dodge = NULL;
+ self->monsterinfo.attack = tank_attack;
+ self->monsterinfo.melee = NULL;
+ self->monsterinfo.sight = tank_sight;
+ self->monsterinfo.idle = tank_idle;
+
+ gi.linkentity(self);
+
+ self->monsterinfo.currentmove = &tank_move_stand;
+ self->monsterinfo.scale = MODEL_SCALE;
+
+ walkmonster_start(self);
+
+ if (strcmp(self->classname, "monster_tank_commander") == 0)
+ {
+ self->s.skinnum = 2;
+ }
+}
diff --git a/xatrix/src/monster/tank/tank.h b/xatrix/src/monster/tank/tank.h
new file mode 100644
index 0000000..0ca6a6c
--- /dev/null
+++ b/xatrix/src/monster/tank/tank.h
@@ -0,0 +1,303 @@
+/* =======================================================================
+ *
+ * Tank and Tank Commander animations.
+ *
+ * =======================================================================
+ */
+
+#define FRAME_stand01 0
+#define FRAME_stand02 1
+#define FRAME_stand03 2
+#define FRAME_stand04 3
+#define FRAME_stand05 4
+#define FRAME_stand06 5
+#define FRAME_stand07 6
+#define FRAME_stand08 7
+#define FRAME_stand09 8
+#define FRAME_stand10 9
+#define FRAME_stand11 10
+#define FRAME_stand12 11
+#define FRAME_stand13 12
+#define FRAME_stand14 13
+#define FRAME_stand15 14
+#define FRAME_stand16 15
+#define FRAME_stand17 16
+#define FRAME_stand18 17
+#define FRAME_stand19 18
+#define FRAME_stand20 19
+#define FRAME_stand21 20
+#define FRAME_stand22 21
+#define FRAME_stand23 22
+#define FRAME_stand24 23
+#define FRAME_stand25 24
+#define FRAME_stand26 25
+#define FRAME_stand27 26
+#define FRAME_stand28 27
+#define FRAME_stand29 28
+#define FRAME_stand30 29
+#define FRAME_walk01 30
+#define FRAME_walk02 31
+#define FRAME_walk03 32
+#define FRAME_walk04 33
+#define FRAME_walk05 34
+#define FRAME_walk06 35
+#define FRAME_walk07 36
+#define FRAME_walk08 37
+#define FRAME_walk09 38
+#define FRAME_walk10 39
+#define FRAME_walk11 40
+#define FRAME_walk12 41
+#define FRAME_walk13 42
+#define FRAME_walk14 43
+#define FRAME_walk15 44
+#define FRAME_walk16 45
+#define FRAME_walk17 46
+#define FRAME_walk18 47
+#define FRAME_walk19 48
+#define FRAME_walk20 49
+#define FRAME_walk21 50
+#define FRAME_walk22 51
+#define FRAME_walk23 52
+#define FRAME_walk24 53
+#define FRAME_walk25 54
+#define FRAME_attak101 55
+#define FRAME_attak102 56
+#define FRAME_attak103 57
+#define FRAME_attak104 58
+#define FRAME_attak105 59
+#define FRAME_attak106 60
+#define FRAME_attak107 61
+#define FRAME_attak108 62
+#define FRAME_attak109 63
+#define FRAME_attak110 64
+#define FRAME_attak111 65
+#define FRAME_attak112 66
+#define FRAME_attak113 67
+#define FRAME_attak114 68
+#define FRAME_attak115 69
+#define FRAME_attak116 70
+#define FRAME_attak117 71
+#define FRAME_attak118 72
+#define FRAME_attak119 73
+#define FRAME_attak120 74
+#define FRAME_attak121 75
+#define FRAME_attak122 76
+#define FRAME_attak201 77
+#define FRAME_attak202 78
+#define FRAME_attak203 79
+#define FRAME_attak204 80
+#define FRAME_attak205 81
+#define FRAME_attak206 82
+#define FRAME_attak207 83
+#define FRAME_attak208 84
+#define FRAME_attak209 85
+#define FRAME_attak210 86
+#define FRAME_attak211 87
+#define FRAME_attak212 88
+#define FRAME_attak213 89
+#define FRAME_attak214 90
+#define FRAME_attak215 91
+#define FRAME_attak216 92
+#define FRAME_attak217 93
+#define FRAME_attak218 94
+#define FRAME_attak219 95
+#define FRAME_attak220 96
+#define FRAME_attak221 97
+#define FRAME_attak222 98
+#define FRAME_attak223 99
+#define FRAME_attak224 100
+#define FRAME_attak225 101
+#define FRAME_attak226 102
+#define FRAME_attak227 103
+#define FRAME_attak228 104
+#define FRAME_attak229 105
+#define FRAME_attak230 106
+#define FRAME_attak231 107
+#define FRAME_attak232 108
+#define FRAME_attak233 109
+#define FRAME_attak234 110
+#define FRAME_attak235 111
+#define FRAME_attak236 112
+#define FRAME_attak237 113
+#define FRAME_attak238 114
+#define FRAME_attak301 115
+#define FRAME_attak302 116
+#define FRAME_attak303 117
+#define FRAME_attak304 118
+#define FRAME_attak305 119
+#define FRAME_attak306 120
+#define FRAME_attak307 121
+#define FRAME_attak308 122
+#define FRAME_attak309 123
+#define FRAME_attak310 124
+#define FRAME_attak311 125
+#define FRAME_attak312 126
+#define FRAME_attak313 127
+#define FRAME_attak314 128
+#define FRAME_attak315 129
+#define FRAME_attak316 130
+#define FRAME_attak317 131
+#define FRAME_attak318 132
+#define FRAME_attak319 133
+#define FRAME_attak320 134
+#define FRAME_attak321 135
+#define FRAME_attak322 136
+#define FRAME_attak323 137
+#define FRAME_attak324 138
+#define FRAME_attak325 139
+#define FRAME_attak326 140
+#define FRAME_attak327 141
+#define FRAME_attak328 142
+#define FRAME_attak329 143
+#define FRAME_attak330 144
+#define FRAME_attak331 145
+#define FRAME_attak332 146
+#define FRAME_attak333 147
+#define FRAME_attak334 148
+#define FRAME_attak335 149
+#define FRAME_attak336 150
+#define FRAME_attak337 151
+#define FRAME_attak338 152
+#define FRAME_attak339 153
+#define FRAME_attak340 154
+#define FRAME_attak341 155
+#define FRAME_attak342 156
+#define FRAME_attak343 157
+#define FRAME_attak344 158
+#define FRAME_attak345 159
+#define FRAME_attak346 160
+#define FRAME_attak347 161
+#define FRAME_attak348 162
+#define FRAME_attak349 163
+#define FRAME_attak350 164
+#define FRAME_attak351 165
+#define FRAME_attak352 166
+#define FRAME_attak353 167
+#define FRAME_attak401 168
+#define FRAME_attak402 169
+#define FRAME_attak403 170
+#define FRAME_attak404 171
+#define FRAME_attak405 172
+#define FRAME_attak406 173
+#define FRAME_attak407 174
+#define FRAME_attak408 175
+#define FRAME_attak409 176
+#define FRAME_attak410 177
+#define FRAME_attak411 178
+#define FRAME_attak412 179
+#define FRAME_attak413 180
+#define FRAME_attak414 181
+#define FRAME_attak415 182
+#define FRAME_attak416 183
+#define FRAME_attak417 184
+#define FRAME_attak418 185
+#define FRAME_attak419 186
+#define FRAME_attak420 187
+#define FRAME_attak421 188
+#define FRAME_attak422 189
+#define FRAME_attak423 190
+#define FRAME_attak424 191
+#define FRAME_attak425 192
+#define FRAME_attak426 193
+#define FRAME_attak427 194
+#define FRAME_attak428 195
+#define FRAME_attak429 196
+#define FRAME_pain101 197
+#define FRAME_pain102 198
+#define FRAME_pain103 199
+#define FRAME_pain104 200
+#define FRAME_pain201 201
+#define FRAME_pain202 202
+#define FRAME_pain203 203
+#define FRAME_pain204 204
+#define FRAME_pain205 205
+#define FRAME_pain301 206
+#define FRAME_pain302 207
+#define FRAME_pain303 208
+#define FRAME_pain304 209
+#define FRAME_pain305 210
+#define FRAME_pain306 211
+#define FRAME_pain307 212
+#define FRAME_pain308 213
+#define FRAME_pain309 214
+#define FRAME_pain310 215
+#define FRAME_pain311 216
+#define FRAME_pain312 217
+#define FRAME_pain313 218
+#define FRAME_pain314 219
+#define FRAME_pain315 220
+#define FRAME_pain316 221
+#define FRAME_death101 222
+#define FRAME_death102 223
+#define FRAME_death103 224
+#define FRAME_death104 225
+#define FRAME_death105 226
+#define FRAME_death106 227
+#define FRAME_death107 228
+#define FRAME_death108 229
+#define FRAME_death109 230
+#define FRAME_death110 231
+#define FRAME_death111 232
+#define FRAME_death112 233
+#define FRAME_death113 234
+#define FRAME_death114 235
+#define FRAME_death115 236
+#define FRAME_death116 237
+#define FRAME_death117 238
+#define FRAME_death118 239
+#define FRAME_death119 240
+#define FRAME_death120 241
+#define FRAME_death121 242
+#define FRAME_death122 243
+#define FRAME_death123 244
+#define FRAME_death124 245
+#define FRAME_death125 246
+#define FRAME_death126 247
+#define FRAME_death127 248
+#define FRAME_death128 249
+#define FRAME_death129 250
+#define FRAME_death130 251
+#define FRAME_death131 252
+#define FRAME_death132 253
+#define FRAME_recln101 254
+#define FRAME_recln102 255
+#define FRAME_recln103 256
+#define FRAME_recln104 257
+#define FRAME_recln105 258
+#define FRAME_recln106 259
+#define FRAME_recln107 260
+#define FRAME_recln108 261
+#define FRAME_recln109 262
+#define FRAME_recln110 263
+#define FRAME_recln111 264
+#define FRAME_recln112 265
+#define FRAME_recln113 266
+#define FRAME_recln114 267
+#define FRAME_recln115 268
+#define FRAME_recln116 269
+#define FRAME_recln117 270
+#define FRAME_recln118 271
+#define FRAME_recln119 272
+#define FRAME_recln120 273
+#define FRAME_recln121 274
+#define FRAME_recln122 275
+#define FRAME_recln123 276
+#define FRAME_recln124 277
+#define FRAME_recln125 278
+#define FRAME_recln126 279
+#define FRAME_recln127 280
+#define FRAME_recln128 281
+#define FRAME_recln129 282
+#define FRAME_recln130 283
+#define FRAME_recln131 284
+#define FRAME_recln132 285
+#define FRAME_recln133 286
+#define FRAME_recln134 287
+#define FRAME_recln135 288
+#define FRAME_recln136 289
+#define FRAME_recln137 290
+#define FRAME_recln138 291
+#define FRAME_recln139 292
+#define FRAME_recln140 293
+
+#define MODEL_SCALE 1.000000
diff --git a/xatrix/src/player/client.c b/xatrix/src/player/client.c
new file mode 100644
index 0000000..5339674
--- /dev/null
+++ b/xatrix/src/player/client.c
@@ -0,0 +1,2274 @@
+/* =======================================================================
+ *
+ * Interface between client <-> game and client calculations.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+#include "../monster/misc/player.h"
+
+void ClientUserinfoChanged(edict_t *ent, char *userinfo);
+void SP_misc_teleporter_dest(edict_t *ent);
+void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+void
+SP_FixCoopSpots(edict_t *self)
+{
+ /* Entity number 292 is an unnamed info_player_start
+ next to a named info_player_start. Delete it, if
+ we're in coop since it screws up the spawnpoint
+ selection heuristic in SelectCoopSpawnPoint().
+ This unnamed info_player_start is selected as
+ spawnpoint for player 0, therefor none of the
+ named info_coop_start() matches... */
+ if(Q_stricmp(level.mapname, "xware") == 0)
+ {
+ if (self->s.number == 292)
+ {
+ G_FreeEdict(self);
+ self = NULL;
+ }
+ }
+}
+
+void
+SP_CreateCoopSpots(edict_t *self)
+{
+ /* Necessary for savegame compatiblity */
+}
+
+void
+SP_CreateUnnamedSpawn(edict_t *self)
+{
+ /* Necessary for savegame compatiblity */
+}
+
+/*
+ * QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
+ * The normal starting point for a level.
+ */
+void
+SP_info_player_start(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!coop->value)
+ {
+ return;
+ }
+
+ /* Fix coop spawn points */
+ SP_FixCoopSpots(self);
+}
+
+/*
+ * QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
+ * potential spawning position for deathmatch games
+ */
+void
+SP_info_player_deathmatch(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+
+ SP_misc_teleporter_dest(self);
+}
+
+/*
+ * QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
+ * potential spawning position for coop games
+ */
+void
+SP_info_player_coop(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (!coop->value)
+ {
+ G_FreeEdict(self);
+ return;
+ }
+}
+
+/*
+ * QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+ * The deathmatch intermission point will be at one of these
+ * Use 'angles' instead of 'angle', so you can set pitch or
+ * roll as well as yaw. 'pitch yaw roll'
+ */
+void
+SP_info_player_intermission(edict_t *ent)
+{
+ /* This function cannot be removed
+ * since the info_player_intermission
+ * needs a callback function. Like
+ * every entity. */
+}
+
+/* ======================================================================= */
+
+void
+player_pain(edict_t *self /* unsued */, edict_t *other /* unused */,
+ float kick /* unused */, int damage /* unused */)
+{
+ /* Player pain is handled at the end
+ * of the frame in P_DamageFeedback.
+ * This function is still here since
+ * the player is an entity and needs
+ * a pain callback */
+}
+
+qboolean
+IsFemale(edict_t *ent)
+{
+ char *info;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!ent->client)
+ {
+ return false;
+ }
+
+ info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
+
+ if ((info[0] == 'f') || (info[0] == 'F'))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+qboolean
+IsNeutral(edict_t *ent)
+{
+ char *info;
+
+ if (!ent)
+ {
+ return false;
+ }
+
+ if (!ent->client)
+ {
+ return false;
+ }
+
+ info = Info_ValueForKey(ent->client->pers.userinfo, "gender");
+
+ if ((info[0] != 'f') && (info[0] != 'F') &&
+ (info[0] != 'm') && (info[0] != 'M'))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void
+ClientObituary(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker)
+{
+ int mod;
+ char *message;
+ char *message2;
+ qboolean ff;
+
+ if (!self || !inflictor)
+ {
+ return;
+ }
+
+ if (coop->value && attacker->client)
+ {
+ meansOfDeath |= MOD_FRIENDLY_FIRE;
+ }
+
+ if (deathmatch->value || coop->value)
+ {
+ ff = meansOfDeath & MOD_FRIENDLY_FIRE;
+ mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+ message = NULL;
+ message2 = "";
+
+ switch (mod)
+ {
+ case MOD_SUICIDE:
+ message = "suicides";
+ break;
+ case MOD_FALLING:
+ message = "cratered";
+ break;
+ case MOD_CRUSH:
+ message = "was squished";
+ break;
+ case MOD_WATER:
+ message = "sank like a rock";
+ break;
+ case MOD_SLIME:
+ message = "melted";
+ break;
+ case MOD_LAVA:
+ message = "does a back flip into the lava";
+ break;
+ case MOD_EXPLOSIVE:
+ case MOD_BARREL:
+ message = "blew up";
+ break;
+ case MOD_EXIT:
+ message = "found a way out";
+ break;
+ case MOD_TARGET_LASER:
+ message = "saw the light";
+ break;
+ case MOD_TARGET_BLASTER:
+ message = "got blasted";
+ break;
+ case MOD_BOMB:
+ case MOD_SPLASH:
+ case MOD_TRIGGER_HURT:
+ message = "was in the wrong place";
+ break;
+ case MOD_GEKK:
+ case MOD_BRAINTENTACLE:
+ message = "that's gotta hurt";
+ break;
+ }
+
+ if (attacker == self)
+ {
+ switch (mod)
+ {
+ case MOD_HELD_GRENADE:
+ message = "tried to put the pin back in";
+ break;
+ case MOD_HG_SPLASH:
+ case MOD_G_SPLASH:
+
+ if (IsNeutral(self))
+ {
+ message = "tripped on its own grenade";
+ }
+ else if (IsFemale(self))
+ {
+ message = "tripped on her own grenade";
+ }
+ else
+ {
+ message = "tripped on his own grenade";
+ }
+
+ break;
+ case MOD_R_SPLASH:
+
+ if (IsNeutral(self))
+ {
+ message = "blew itself up";
+ }
+ else if (IsFemale(self))
+ {
+ message = "blew herself up";
+ }
+ else
+ {
+ message = "blew himself up";
+ }
+
+ break;
+ case MOD_BFG_BLAST:
+ message = "should have used a smaller gun";
+ break;
+ case MOD_TRAP:
+ message = "sucked into his own trap";
+ break;
+ default:
+
+ if (IsNeutral(self))
+ {
+ message = "killed itself";
+ }
+ else if (IsFemale(self))
+ {
+ message = "killed herself";
+ }
+ else
+ {
+ message = "killed himself";
+ }
+
+ break;
+ }
+ }
+
+ if (message)
+ {
+ gi.bprintf(PRINT_MEDIUM, "%s %s.\n",
+ self->client->pers.netname,
+ message);
+
+ if (deathmatch->value)
+ {
+ self->client->resp.score--;
+ }
+
+ self->enemy = NULL;
+ return;
+ }
+
+ self->enemy = attacker;
+
+ if (attacker && attacker->client)
+ {
+ switch (mod)
+ {
+ case MOD_BLASTER:
+ message = "was blasted by";
+ break;
+ case MOD_SHOTGUN:
+ message = "was gunned down by";
+ break;
+ case MOD_SSHOTGUN:
+ message = "was blown away by";
+ message2 = "'s super shotgun";
+ break;
+ case MOD_MACHINEGUN:
+ message = "was machinegunned by";
+ break;
+ case MOD_CHAINGUN:
+ message = "was cut in half by";
+ message2 = "'s chaingun";
+ break;
+ case MOD_GRENADE:
+ message = "was popped by";
+ message2 = "'s grenade";
+ break;
+ case MOD_G_SPLASH:
+ message = "was shredded by";
+ message2 = "'s shrapnel";
+ break;
+ case MOD_ROCKET:
+ message = "ate";
+ message2 = "'s rocket";
+ break;
+ case MOD_R_SPLASH:
+ message = "almost dodged";
+ message2 = "'s rocket";
+ break;
+ case MOD_HYPERBLASTER:
+ message = "was melted by";
+ message2 = "'s hyperblaster";
+ break;
+ case MOD_RAILGUN:
+ message = "was railed by";
+ break;
+ case MOD_BFG_LASER:
+ message = "saw the pretty lights from";
+ message2 = "'s BFG";
+ break;
+ case MOD_BFG_BLAST:
+ message = "was disintegrated by";
+ message2 = "'s BFG blast";
+ break;
+ case MOD_BFG_EFFECT:
+ message = "couldn't hide from";
+ message2 = "'s BFG";
+ break;
+ case MOD_HANDGRENADE:
+ message = "caught";
+ message2 = "'s handgrenade";
+ break;
+ case MOD_HG_SPLASH:
+ message = "didn't see";
+ message2 = "'s handgrenade";
+ break;
+ case MOD_HELD_GRENADE:
+ message = "feels";
+ message2 = "'s pain";
+ break;
+ case MOD_TELEFRAG:
+ message = "tried to invade";
+ message2 = "'s personal space";
+ break;
+ case MOD_RIPPER:
+ message = "ripped to shreds by";
+ message2 = "'s ripper gun";
+ break;
+ case MOD_PHALANX:
+ message = "was evaporated by";
+ break;
+ case MOD_TRAP:
+ message = "caught in trap by";
+ break;
+ }
+
+ if (message)
+ {
+ gi.bprintf(PRINT_MEDIUM, "%s %s %s%s\n",
+ self->client->pers.netname,
+ message, attacker->client->pers.netname,
+ message2);
+
+ if (deathmatch->value)
+ {
+ if (ff)
+ {
+ attacker->client->resp.score--;
+ }
+ else
+ {
+ attacker->client->resp.score++;
+ }
+ }
+
+ return;
+ }
+ }
+ }
+
+ gi.bprintf(PRINT_MEDIUM, "%s died.\n", self->client->pers.netname);
+
+ if (deathmatch->value)
+ {
+ self->client->resp.score--;
+ }
+}
+
+void
+TossClientWeapon(edict_t *self)
+{
+ gitem_t *item;
+ edict_t *drop;
+ qboolean quad;
+ qboolean quadfire;
+ float spread;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (!deathmatch->value)
+ {
+ return;
+ }
+
+ item = self->client->pers.weapon;
+
+ if (!self->client->pers.inventory[self->client->ammo_index])
+ {
+ item = NULL;
+ }
+
+ if (item && (strcmp(item->pickup_name, "Blaster") == 0))
+ {
+ item = NULL;
+ }
+
+ if (!((int)(dmflags->value) & DF_QUAD_DROP))
+ {
+ quad = false;
+ }
+ else
+ {
+ quad = (self->client->quad_framenum > (level.framenum + 10));
+ }
+
+ if (!((int)(dmflags->value) & DF_QUADFIRE_DROP))
+ {
+ quadfire = false;
+ }
+ else
+ {
+ quadfire = (self->client->quadfire_framenum > (level.framenum + 10));
+ }
+
+ if (item && quad)
+ {
+ spread = 22.5;
+ }
+ else if (item && quadfire)
+ {
+ spread = 12.5;
+ }
+ else
+ {
+ spread = 0.0;
+ }
+
+ if (item)
+ {
+ self->client->v_angle[YAW] -= spread;
+ drop = Drop_Item(self, item);
+ self->client->v_angle[YAW] += spread;
+ drop->spawnflags = DROPPED_PLAYER_ITEM;
+ }
+
+ if (quad)
+ {
+ self->client->v_angle[YAW] += spread;
+ drop = Drop_Item(self, FindItemByClassname("item_quad"));
+ self->client->v_angle[YAW] -= spread;
+ drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+ drop->touch = Touch_Item;
+ drop->nextthink = level.time + (self->client->quad_framenum -
+ level.framenum) * FRAMETIME;
+ drop->think = G_FreeEdict;
+ }
+
+ if (quadfire)
+ {
+ self->client->v_angle[YAW] += spread;
+ drop = Drop_Item(self, FindItemByClassname("item_quadfire"));
+ self->client->v_angle[YAW] -= spread;
+ drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+ drop->touch = Touch_Item;
+ drop->nextthink = level.time + (self->client->quadfire_framenum -
+ level.framenum) * FRAMETIME;
+ drop->think = G_FreeEdict;
+ }
+}
+
+void
+LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker)
+{
+ vec3_t dir;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (attacker && (attacker != world) && (attacker != self))
+ {
+ VectorSubtract(attacker->s.origin, self->s.origin, dir);
+ }
+ else if (inflictor && (inflictor != world) && (inflictor != self))
+ {
+ VectorSubtract(inflictor->s.origin, self->s.origin, dir);
+ }
+ else
+ {
+ self->client->killer_yaw = self->s.angles[YAW];
+ return;
+ }
+
+ if (dir[0])
+ {
+ self->client->killer_yaw = 180 / M_PI *atan2(dir[1], dir[0]);
+ }
+ else
+ {
+ self->client->killer_yaw = 0;
+
+ if (dir[1] > 0)
+ {
+ self->client->killer_yaw = 90;
+ }
+ else if (dir[1] < 0)
+ {
+ self->client->killer_yaw = -90;
+ }
+ }
+
+ if (self->client->killer_yaw < 0)
+ {
+ self->client->killer_yaw += 360;
+ }
+}
+
+void
+player_die(edict_t *self, edict_t *inflictor, edict_t *attacker,
+ int damage, vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self || !inflictor || !attacker)
+ {
+ return;
+ }
+
+ VectorClear(self->avelocity);
+
+ self->takedamage = DAMAGE_YES;
+ self->movetype = MOVETYPE_TOSS;
+
+ self->s.modelindex2 = 0; /* remove linked weapon model */
+
+ self->s.angles[0] = 0;
+ self->s.angles[2] = 0;
+
+ self->s.sound = 0;
+ self->client->weapon_sound = 0;
+
+ self->maxs[2] = -8;
+
+ self->svflags |= SVF_DEADMONSTER;
+
+ if (!self->deadflag)
+ {
+ self->client->respawn_time = level.time + 1.0;
+ LookAtKiller(self, inflictor, attacker);
+ self->client->ps.pmove.pm_type = PM_DEAD;
+ ClientObituary(self, inflictor, attacker);
+ TossClientWeapon(self);
+
+ if (deathmatch->value)
+ {
+ Cmd_Help_f(self); /* show scores */
+ }
+
+ /* clear inventory: this is kind of ugly, but
+ it's how we want to handle keys in coop */
+ for (n = 0; n < game.num_items; n++)
+ {
+ if (coop->value && itemlist[n].flags & IT_KEY)
+ {
+ self->client->resp.coop_respawn.inventory[n] =
+ self->client->pers.inventory[n];
+ }
+
+ self->client->pers.inventory[n] = 0;
+ }
+ }
+
+ /* remove powerups */
+ self->client->quad_framenum = 0;
+ self->client->invincible_framenum = 0;
+ self->client->breather_framenum = 0;
+ self->client->enviro_framenum = 0;
+ self->flags &= ~FL_POWER_ARMOR;
+
+ self->client->quadfire_framenum = 0;
+
+ if (self->health < -40)
+ {
+ /* gib (sound played at end of server frame) */
+ self->sounds = gi.soundindex("misc/udeath.wav");
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ ThrowClientHead(self, damage);
+
+ self->takedamage = DAMAGE_NO;
+ }
+ else
+ {
+ /* normal death */
+ if (!self->deadflag)
+ {
+ static int i;
+
+ i = (i + 1) % 3;
+
+ /* start a death animation */
+ self->client->anim_priority = ANIM_DEATH;
+
+ if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ self->s.frame = FRAME_crdeath1 - 1;
+ self->client->anim_end = FRAME_crdeath5;
+ }
+ else
+ {
+ switch (i)
+ {
+ case 0:
+ self->s.frame = FRAME_death101 - 1;
+ self->client->anim_end = FRAME_death106;
+ break;
+ case 1:
+ self->s.frame = FRAME_death201 - 1;
+ self->client->anim_end = FRAME_death206;
+ break;
+ case 2:
+ self->s.frame = FRAME_death301 - 1;
+ self->client->anim_end = FRAME_death308;
+ break;
+ }
+ }
+
+ /* sound played at end of server frame */
+ if (!self->sounds)
+ {
+ self->sounds = gi.soundindex(va("*death%i.wav",
+ (rand() % 4) + 1));
+ }
+ }
+ }
+
+ self->deadflag = DEAD_DEAD;
+
+ gi.linkentity(self);
+}
+
+/* ======================================================================= */
+
+/*
+ * This is only called when the game first
+ * initializes in single player, but is called
+ * after each death and level change in deathmatch
+ */
+void
+InitClientPersistant(gclient_t *client)
+{
+ gitem_t *item;
+
+ if (!client)
+ {
+ return;
+ }
+
+ memset(&client->pers, 0, sizeof(client->pers));
+
+ item = FindItem("Blaster");
+ client->pers.selected_item = ITEM_INDEX(item);
+ client->pers.inventory[client->pers.selected_item] = 1;
+
+ client->pers.weapon = item;
+
+ client->pers.health = 100;
+ client->pers.max_health = 100;
+
+ client->pers.max_bullets = 200;
+ client->pers.max_shells = 100;
+ client->pers.max_rockets = 50;
+ client->pers.max_grenades = 50;
+ client->pers.max_cells = 200;
+ client->pers.max_slugs = 50;
+
+ client->pers.max_magslug = 50;
+ client->pers.max_trap = 5;
+
+ client->pers.connected = true;
+}
+
+void
+InitClientResp(gclient_t *client)
+{
+ if (!client)
+ {
+ return;
+ }
+
+ memset(&client->resp, 0, sizeof(client->resp));
+ client->resp.enterframe = level.framenum;
+ client->resp.coop_respawn = client->pers;
+}
+
+/*
+ * Some information that should be persistant, like health,
+ * is still stored in the edict structure, so it needs to
+ * be mirrored out to the client structure before all the
+ * edicts are wiped.
+ */
+void
+SaveClientData(void)
+{
+ int i;
+ edict_t *ent;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ent = &g_edicts[1 + i];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ game.clients[i].pers.health = ent->health;
+ game.clients[i].pers.max_health = ent->max_health;
+ game.clients[i].pers.savedFlags =
+ (ent->flags & (FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR));
+
+ if (coop->value)
+ {
+ game.clients[i].pers.score = ent->client->resp.score;
+ }
+ }
+}
+
+void
+FetchClientEntData(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->health = ent->client->pers.health;
+ ent->max_health = ent->client->pers.max_health;
+ ent->flags |= ent->client->pers.savedFlags;
+
+ if (coop->value)
+ {
+ ent->client->resp.score = ent->client->pers.score;
+ }
+}
+
+/* ======================================================================= */
+
+/*
+ * Returns the distance to the nearest
+ * player from the given spot
+ */
+float
+PlayersRangeFromSpot(edict_t *spot)
+{
+ edict_t *player;
+ float bestplayerdistance;
+ vec3_t v;
+ int n;
+ float playerdistance;
+
+ if (!spot)
+ {
+ return 0;
+ }
+
+ bestplayerdistance = 9999999;
+
+ for (n = 1; n <= maxclients->value; n++)
+ {
+ player = &g_edicts[n];
+
+ if (!player->inuse)
+ {
+ continue;
+ }
+
+ if (player->health <= 0)
+ {
+ continue;
+ }
+
+ VectorSubtract(spot->s.origin, player->s.origin, v);
+ playerdistance = VectorLength(v);
+
+ if (playerdistance < bestplayerdistance)
+ {
+ bestplayerdistance = playerdistance;
+ }
+ }
+
+ return bestplayerdistance;
+}
+
+/*
+ * go to a random point, but NOT the two
+ * points closest to other players
+ */
+edict_t *
+SelectRandomDeathmatchSpawnPoint(void)
+{
+ edict_t *spot, *spot1, *spot2;
+ int count = 0;
+ int selection;
+ float range, range1, range2;
+
+ spot = NULL;
+ range1 = range2 = 99999;
+ spot1 = spot2 = NULL;
+
+ while ((spot = G_Find(spot, FOFS(classname),
+ "info_player_deathmatch")) != NULL)
+ {
+ count++;
+ range = PlayersRangeFromSpot(spot);
+
+ if (range < range1)
+ {
+ range1 = range;
+ spot1 = spot;
+ }
+ else if (range < range2)
+ {
+ range2 = range;
+ spot2 = spot;
+ }
+ }
+
+ if (!count)
+ {
+ return NULL;
+ }
+
+ if (count <= 2)
+ {
+ spot1 = spot2 = NULL;
+ }
+ else
+ {
+ count -= 2;
+ }
+
+ selection = rand() % count;
+
+ spot = NULL;
+
+ do
+ {
+ spot = G_Find(spot, FOFS(classname), "info_player_deathmatch");
+
+ if ((spot == spot1) || (spot == spot2))
+ {
+ selection++;
+ }
+ }
+ while (selection--);
+
+ return spot;
+}
+
+edict_t *
+SelectFarthestDeathmatchSpawnPoint(void)
+{
+ edict_t *bestspot;
+ float bestdistance, bestplayerdistance;
+ edict_t *spot;
+
+ spot = NULL;
+ bestspot = NULL;
+ bestdistance = 0;
+
+ while ((spot = G_Find(spot, FOFS(classname),
+ "info_player_deathmatch")) != NULL)
+ {
+ bestplayerdistance = PlayersRangeFromSpot(spot);
+
+ if (bestplayerdistance > bestdistance)
+ {
+ bestspot = spot;
+ bestdistance = bestplayerdistance;
+ }
+ }
+
+ if (bestspot)
+ {
+ return bestspot;
+ }
+
+ /* if there is a player just spawned on each and every start spot
+ we have no choice to turn one into a telefrag meltdown */
+ spot = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
+
+ return spot;
+}
+
+edict_t *
+SelectDeathmatchSpawnPoint(void)
+{
+ if ((int)(dmflags->value) & DF_SPAWN_FARTHEST)
+ {
+ return SelectFarthestDeathmatchSpawnPoint();
+ }
+ else
+ {
+ return SelectRandomDeathmatchSpawnPoint();
+ }
+}
+
+edict_t *
+SelectCoopSpawnPoint(edict_t *ent)
+{
+ int index;
+ edict_t *spot = NULL;
+ char *target;
+
+ if (!ent)
+ {
+ return NULL;
+ }
+
+ index = ent->client - game.clients;
+
+ /* player 0 starts in normal player spawn point */
+ if (!index)
+ {
+ return NULL;
+ }
+
+ spot = NULL;
+
+ /* assume there are four coop spots at each spawnpoint */
+ while (1)
+ {
+ spot = G_Find(spot, FOFS(classname), "info_player_coop");
+
+ if (!spot)
+ {
+ return NULL; /* we didn't have enough... */
+ }
+
+ target = spot->targetname;
+
+ if (!target)
+ {
+ target = "";
+ }
+
+ if (Q_stricmp(game.spawnpoint, target) == 0)
+ {
+ /* this is a coop spawn point for
+ one of the clients here */
+ index--;
+
+ if (!index)
+ {
+ return spot; /* this is it */
+ }
+ }
+ }
+
+ return spot;
+}
+
+/*
+ * Chooses a player start, deathmatch start, coop start, etc
+ */
+void
+SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles)
+{
+ edict_t *spot = NULL;
+ edict_t *coopspot = NULL;
+ int dist;
+ int index;
+ int counter = 0;
+ vec3_t d;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ spot = SelectDeathmatchSpawnPoint();
+ }
+ else if (coop->value)
+ {
+ spot = SelectCoopSpawnPoint(ent);
+ }
+
+ /* find a single player start spot */
+ if (!spot)
+ {
+ while ((spot = G_Find(spot, FOFS(classname), "info_player_start")) != NULL)
+ {
+ if (!game.spawnpoint[0] && !spot->targetname)
+ {
+ break;
+ }
+
+ if (!game.spawnpoint[0] || !spot->targetname)
+ {
+ continue;
+ }
+
+ if (Q_stricmp(game.spawnpoint, spot->targetname) == 0)
+ {
+ break;
+ }
+ }
+
+ if (!spot)
+ {
+ if (!game.spawnpoint[0])
+ {
+ /* there wasn't a spawnpoint without a target, so use any */
+ spot = G_Find(spot, FOFS(classname), "info_player_start");
+ }
+
+ if (!spot)
+ {
+ gi.error("Couldn't find spawn point %s\n", game.spawnpoint);
+ }
+ }
+ }
+
+ /* If we are in coop and we didn't find a coop
+ spawnpoint due to map bugs (not correctly
+ connected or the map was loaded via console
+ and thus no previously map is known to the
+ client) use one in 550 units radius. */
+ if (coop->value)
+ {
+ index = ent->client - game.clients;
+
+ if (Q_stricmp(spot->classname, "info_player_start") == 0 && index != 0)
+ {
+ while(counter < 3)
+ {
+ coopspot = G_Find(coopspot, FOFS(classname), "info_player_coop");
+
+ if (!coopspot)
+ {
+ break;
+ }
+
+ VectorSubtract(coopspot->s.origin, spot->s.origin, d);
+
+ /* In xship the coop spawnpoints are farther
+ away than in other maps. Quirk around this.
+ Oh well... */
+ if (Q_stricmp(level.mapname, "xship") == 0)
+ {
+ dist = 2500;
+ }
+ else
+ {
+ dist = 550;
+ }
+
+ if ((VectorLength(d) < dist))
+ {
+ if (index == counter)
+ {
+ spot = coopspot;
+ break;
+ }
+ else
+ {
+ counter++;
+ }
+ }
+ }
+ }
+ }
+
+ VectorCopy(spot->s.origin, origin);
+ origin[2] += 9;
+ VectorCopy(spot->s.angles, angles);
+}
+
+/* ====================================================================== */
+
+void
+InitBodyQue(void)
+{
+ int i;
+ edict_t *ent;
+
+ level.body_que = 0;
+
+ for (i = 0; i < BODY_QUEUE_SIZE; i++)
+ {
+ ent = G_Spawn();
+ ent->classname = "bodyque";
+ }
+}
+
+void
+body_die(edict_t *self, edict_t *inflictor /* unused */,
+ edict_t *attacker /* unused */, int damage,
+ vec3_t point /* unused */)
+{
+ int n;
+
+ if (!self)
+ {
+ return;
+ }
+
+ if (self->health < -40)
+ {
+ gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"),
+ 1, ATTN_NORM, 0);
+
+ for (n = 0; n < 4; n++)
+ {
+ ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2",
+ damage, GIB_ORGANIC);
+ }
+
+ self->s.origin[2] -= 48;
+ ThrowClientHead(self, damage);
+ self->takedamage = DAMAGE_NO;
+ }
+}
+
+void
+CopyToBodyQue(edict_t *ent)
+{
+ edict_t *body;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* grab a body que and cycle to the next one */
+ body = &g_edicts[(int)maxclients->value + level.body_que + 1];
+ level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
+
+ gi.unlinkentity(ent);
+ gi.unlinkentity(body);
+
+ body->s = ent->s;
+ body->s.number = body - g_edicts;
+
+ body->svflags = ent->svflags;
+ VectorCopy(ent->mins, body->mins);
+ VectorCopy(ent->maxs, body->maxs);
+ VectorCopy(ent->absmin, body->absmin);
+ VectorCopy(ent->absmax, body->absmax);
+ VectorCopy(ent->size, body->size);
+ body->solid = ent->solid;
+ body->clipmask = ent->clipmask;
+ body->owner = ent->owner;
+ body->movetype = ent->movetype;
+
+ body->die = body_die;
+ body->takedamage = DAMAGE_YES;
+
+ gi.linkentity(body);
+}
+
+void
+respawn(edict_t *self)
+{
+ if (!self)
+ {
+ return;
+ }
+
+ if (deathmatch->value || coop->value)
+ {
+ /* spectator's don't leave bodies */
+ if (self->movetype != MOVETYPE_NOCLIP)
+ {
+ CopyToBodyQue(self);
+ }
+
+ self->svflags &= ~SVF_NOCLIENT;
+ PutClientInServer(self);
+
+ /* add a teleportation effect */
+ self->s.event = EV_PLAYER_TELEPORT;
+
+ /* hold in place briefly */
+ self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+ self->client->ps.pmove.pm_time = 14;
+
+ self->client->respawn_time = level.time;
+
+ return;
+ }
+
+ /* restart the entire server */
+ gi.AddCommandString("menu_loadgame\n");
+}
+
+/*
+ * only called when pers.spectator changes
+ * note that resp.spectator should be the
+ * opposite of pers.spectator here
+ */
+void
+spectator_respawn(edict_t *ent)
+{
+ int i, numspec;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* if the user wants to become a spectator,
+ make sure he doesn't exceed max_spectators */
+ if (ent->client->pers.spectator)
+ {
+ char *value = Info_ValueForKey(ent->client->pers.userinfo, "spectator");
+
+ if (*spectator_password->string &&
+ strcmp(spectator_password->string, "none") &&
+ strcmp(spectator_password->string, value))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n");
+ ent->client->pers.spectator = false;
+ gi.WriteByte(svc_stufftext);
+ gi.WriteString("spectator 0\n");
+ gi.unicast(ent, true);
+ return;
+ }
+
+ /* count spectators */
+ for (i = 1, numspec = 0; i <= maxclients->value; i++)
+ {
+ if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
+ {
+ numspec++;
+ }
+ }
+
+ if (numspec >= maxspectators->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full.");
+ ent->client->pers.spectator = false;
+
+ /* reset his spectator var */
+ gi.WriteByte(svc_stufftext);
+ gi.WriteString("spectator 0\n");
+ gi.unicast(ent, true);
+ return;
+ }
+ }
+ else
+ {
+ /* he was a spectator and wants to join the
+ game he must have the right password */
+ char *value = Info_ValueForKey(ent->client->pers.userinfo, "password");
+
+ if (*password->string && strcmp(password->string, "none") &&
+ strcmp(password->string, value))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n");
+ ent->client->pers.spectator = true;
+ gi.WriteByte(svc_stufftext);
+ gi.WriteString("spectator 1\n");
+ gi.unicast(ent, true);
+ return;
+ }
+ }
+
+ /* clear score on respawn */
+ ent->client->pers.score = ent->client->resp.score = 0;
+
+ ent->svflags &= ~SVF_NOCLIENT;
+ PutClientInServer(ent);
+
+ /* add a teleportation effect */
+ if (!ent->client->pers.spectator)
+ {
+ /* send effect */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGIN);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ /* hold in place briefly */
+ ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+ ent->client->ps.pmove.pm_time = 14;
+ }
+
+ ent->client->respawn_time = level.time;
+
+ if (ent->client->pers.spectator)
+ {
+ gi.bprintf(PRINT_HIGH, "%s has moved to the sidelines\n",
+ ent->client->pers.netname);
+ }
+ else
+ {
+ gi.bprintf(PRINT_HIGH, "%s joined the game\n",
+ ent->client->pers.netname);
+ }
+}
+
+/* ============================================================== */
+
+/*
+ * Called when a player connects to
+ * a server or respawns in a deathmatch.
+ */
+void
+PutClientInServer(edict_t *ent)
+{
+ vec3_t mins = {-16, -16, -24};
+ vec3_t maxs = {16, 16, 32};
+ int index;
+ vec3_t spawn_origin, spawn_angles;
+ gclient_t *client;
+ int i;
+ client_persistant_t saved;
+ client_respawn_t resp;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* find a spawn point do it before setting
+ health back up, so farthest ranging
+ doesn't count this client */
+ SelectSpawnPoint(ent, spawn_origin, spawn_angles);
+
+ index = ent - g_edicts - 1;
+ client = ent->client;
+
+ /* deathmatch wipes most client data every spawn */
+ if (deathmatch->value)
+ {
+ char userinfo[MAX_INFO_STRING];
+
+ resp = client->resp;
+ memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
+ InitClientPersistant(client);
+ ClientUserinfoChanged(ent, userinfo);
+ }
+ else if (coop->value)
+ {
+ char userinfo[MAX_INFO_STRING];
+
+ resp = client->resp;
+ memcpy(userinfo, client->pers.userinfo, sizeof(userinfo));
+ resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged;
+ resp.coop_respawn.helpchanged = client->pers.helpchanged;
+ client->pers = resp.coop_respawn;
+ ClientUserinfoChanged(ent, userinfo);
+
+ if (resp.score > client->pers.score)
+ {
+ client->pers.score = resp.score;
+ }
+ }
+ else
+ {
+ memset(&resp, 0, sizeof(resp));
+ }
+
+ /* clear everything but the persistant data */
+ saved = client->pers;
+ memset(client, 0, sizeof(*client));
+ client->pers = saved;
+
+ if (client->pers.health <= 0)
+ {
+ InitClientPersistant(client);
+ }
+
+ client->resp = resp;
+
+ /* copy some data from the client to the entity */
+ FetchClientEntData(ent);
+
+ /* clear entity values */
+ ent->groundentity = NULL;
+ ent->client = &game.clients[index];
+ ent->takedamage = DAMAGE_AIM;
+ ent->movetype = MOVETYPE_WALK;
+ ent->viewheight = 22;
+ ent->inuse = true;
+ ent->classname = "player";
+ ent->mass = 200;
+ ent->solid = SOLID_BBOX;
+ ent->deadflag = DEAD_NO;
+ ent->air_finished = level.time + 12;
+ ent->clipmask = MASK_PLAYERSOLID;
+ ent->model = "players/male/tris.md2";
+ ent->pain = player_pain;
+ ent->die = player_die;
+ ent->waterlevel = 0;
+ ent->watertype = 0;
+ ent->flags &= ~FL_NO_KNOCKBACK;
+ ent->svflags = 0;
+
+ VectorCopy(mins, ent->mins);
+ VectorCopy(maxs, ent->maxs);
+ VectorClear(ent->velocity);
+
+ /* clear playerstate values */
+ memset(&ent->client->ps, 0, sizeof(client->ps));
+
+ client->ps.pmove.origin[0] = spawn_origin[0] * 8;
+ client->ps.pmove.origin[1] = spawn_origin[1] * 8;
+ client->ps.pmove.origin[2] = spawn_origin[2] * 8;
+
+ if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+ {
+ client->ps.fov = 90;
+ }
+ else
+ {
+ client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
+
+ if (client->ps.fov < 1)
+ {
+ client->ps.fov = 90;
+ }
+ else if (client->ps.fov > 160)
+ {
+ client->ps.fov = 160;
+ }
+ }
+
+ client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
+
+ /* clear entity state values */
+ ent->s.effects = 0;
+ ent->s.modelindex = 255; /* will use the skin specified model */
+ ent->s.modelindex2 = 255; /* custom gun model */
+
+ /* sknum is player num and weapon number
+ weapon number will be added in changeweapon */
+ ent->s.skinnum = ent - g_edicts - 1;
+
+ ent->s.frame = 0;
+ VectorCopy(spawn_origin, ent->s.origin);
+ ent->s.origin[2] += 1; /* make sure off ground */
+ VectorCopy(ent->s.origin, ent->s.old_origin);
+
+ /* set the delta angle */
+ for (i = 0; i < 3; i++)
+ {
+ client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
+ spawn_angles[i] - client->resp.cmd_angles[i]);
+ }
+
+ ent->s.angles[PITCH] = 0;
+ ent->s.angles[YAW] = spawn_angles[YAW];
+ ent->s.angles[ROLL] = 0;
+ VectorCopy(ent->s.angles, client->ps.viewangles);
+ VectorCopy(ent->s.angles, client->v_angle);
+
+ /* spawn a spectator */
+ if (client->pers.spectator)
+ {
+ client->chase_target = NULL;
+
+ client->resp.spectator = true;
+
+ ent->movetype = MOVETYPE_NOCLIP;
+ ent->solid = SOLID_NOT;
+ ent->svflags |= SVF_NOCLIENT;
+ ent->client->ps.gunindex = 0;
+ gi.linkentity(ent);
+ return;
+ }
+ else
+ {
+ client->resp.spectator = false;
+ }
+
+ if (!KillBox(ent))
+ {
+ /* could't spawn in? */
+ }
+
+ gi.linkentity(ent);
+
+ /* force the current weapon up */
+ client->newweapon = client->pers.weapon;
+ ChangeWeapon(ent);
+}
+
+/*
+ * A client has just connected to the server in
+ * deathmatch mode, so clear everything out before
+ * starting them.
+ */
+void
+ClientBeginDeathmatch(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ G_InitEdict(ent);
+
+ InitClientResp(ent->client);
+
+ /* locate ent at a spawn point */
+ PutClientInServer(ent);
+
+ if (level.intermissiontime)
+ {
+ MoveClientToIntermission(ent);
+ }
+ else
+ {
+ /* send effect */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGIN);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+ }
+
+ gi.bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+
+ /* make sure all view stuff is valid */
+ ClientEndServerFrame(ent);
+}
+
+/*
+ * called when a client has finished connecting, and is ready
+ * to be placed into the game. This will happen every level load.
+ */
+void
+ClientBegin(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client = game.clients + (ent - g_edicts - 1);
+
+ if (deathmatch->value)
+ {
+ ClientBeginDeathmatch(ent);
+ return;
+ }
+
+ /* if there is already a body waiting for us (a loadgame),
+ just take it, otherwise spawn one from scratch */
+ if (ent->inuse == true)
+ {
+ /* the client has cleared the client side viewangles upon
+ connecting to the server, which is different than the
+ state when the game is saved, so we need to compensate
+ with deltaangles */
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(
+ ent->client->ps.viewangles[i]);
+ }
+ }
+ else
+ {
+ /* a spawn point will completely reinitialize the entity
+ except for the persistant data that was initialized at
+ ClientConnect() time */
+ G_InitEdict(ent);
+ ent->classname = "player";
+ InitClientResp(ent->client);
+ PutClientInServer(ent);
+ }
+
+ if (level.intermissiontime)
+ {
+ MoveClientToIntermission(ent);
+ }
+ else
+ {
+ /* send effect if in a multiplayer game */
+ if (game.maxclients > 1)
+ {
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGIN);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ gi.bprintf(PRINT_HIGH, "%s entered the game\n",
+ ent->client->pers.netname);
+ }
+ }
+
+ /* make sure all view stuff is valid */
+ ClientEndServerFrame(ent);
+}
+
+/*
+ * Called whenever the player updates a userinfo variable.
+ * The game can override any of the settings in place
+ * (forcing skins or names, etc) before copying it off.
+ */
+void
+ClientUserinfoChanged(edict_t *ent, char *userinfo)
+{
+ char *s;
+ int playernum;
+
+ if (!ent || !userinfo)
+ {
+ return;
+ }
+
+ /* check for malformed or illegal info strings */
+ if (!Info_Validate(userinfo))
+ {
+ strcpy(userinfo, "\\name\\badinfo\\skin\\male/grunt");
+ }
+
+ /* set name */
+ s = Info_ValueForKey(userinfo, "name");
+ strncpy(ent->client->pers.netname, s, sizeof(ent->client->pers.netname) - 1);
+
+ /* set spectator */
+ s = Info_ValueForKey(userinfo, "spectator");
+
+ /* spectators are only supported in deathmatch */
+ if (deathmatch->value && *s && strcmp(s, "0"))
+ {
+ ent->client->pers.spectator = true;
+ }
+ else
+ {
+ ent->client->pers.spectator = false;
+ }
+
+ /* set skin */
+ s = Info_ValueForKey(userinfo, "skin");
+
+ playernum = ent - g_edicts - 1;
+
+ /* combine name and skin into a configstring */
+ gi.configstring(CS_PLAYERSKINS + playernum,
+ va("%s\\%s", ent->client->pers.netname, s));
+
+ /* fov */
+ if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+ {
+ ent->client->ps.fov = 90;
+ }
+ else
+ {
+ ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
+
+ if (ent->client->ps.fov < 1)
+ {
+ ent->client->ps.fov = 90;
+ }
+ else if (ent->client->ps.fov > 160)
+ {
+ ent->client->ps.fov = 160;
+ }
+ }
+
+ /* handedness */
+ s = Info_ValueForKey(userinfo, "hand");
+
+ if (strlen(s))
+ {
+ ent->client->pers.hand = atoi(s);
+ }
+
+ /* save off the userinfo in case we want to check something later */
+ strncpy(ent->client->pers.userinfo, userinfo,
+ sizeof(ent->client->pers.userinfo) - 1);
+}
+
+/*
+ * Called when a player begins connecting to the server.
+ * The game can refuse entrance to a client by returning false.
+ * If the client is allowed, the connection process will continue
+ * and eventually get to ClientBegin(). Changing levels will NOT
+ * cause this to be called again, but loadgames will.
+ */
+qboolean
+ClientConnect(edict_t *ent, char *userinfo)
+{
+ char *value;
+
+ if (!ent || !userinfo)
+ {
+ return false;
+ }
+
+ /* check to see if they are on the banned IP list */
+ value = Info_ValueForKey(userinfo, "ip");
+
+ if (SV_FilterPacket(value))
+ {
+ Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
+ return false;
+ }
+
+ /* check for a spectator */
+ value = Info_ValueForKey(userinfo, "spectator");
+
+ if (deathmatch->value && *value && strcmp(value, "0"))
+ {
+ int i, numspec;
+
+ if (*spectator_password->string &&
+ strcmp(spectator_password->string, "none") &&
+ strcmp(spectator_password->string, value))
+ {
+ Info_SetValueForKey(userinfo, "rejmsg",
+ "Spectator password required or incorrect.");
+ return false;
+ }
+
+ /* count spectators */
+ for (i = numspec = 0; i < maxclients->value; i++)
+ {
+ if (g_edicts[i + 1].inuse && g_edicts[i + 1].client->pers.spectator)
+ {
+ numspec++;
+ }
+ }
+
+ if (numspec >= maxspectators->value)
+ {
+ Info_SetValueForKey(userinfo, "rejmsg",
+ "Server spectator limit is full.");
+ return false;
+ }
+ }
+ else
+ {
+ /* check for a password */
+ value = Info_ValueForKey(userinfo, "password");
+
+ if (*password->string && strcmp(password->string, "none") &&
+ strcmp(password->string, value))
+ {
+ Info_SetValueForKey(userinfo, "rejmsg",
+ "Password required or incorrect.");
+ return false;
+ }
+ }
+
+ /* they can connect */
+ ent->client = game.clients + (ent - g_edicts - 1);
+
+ /* if there is already a body waiting for us (a loadgame),
+ just take it, otherwise spawn one from scratch */
+ if (ent->inuse == false)
+ {
+ /* clear the respawning variables */
+ InitClientResp(ent->client);
+
+ if (!game.autosaved || !ent->client->pers.weapon)
+ {
+ InitClientPersistant(ent->client);
+ }
+ }
+
+ ClientUserinfoChanged(ent, userinfo);
+
+ if (game.maxclients > 1)
+ {
+ gi.dprintf("%s connected\n", ent->client->pers.netname);
+ }
+
+ ent->svflags = 0; /* make sure we start with known default */
+ ent->client->pers.connected = true;
+ return true;
+}
+
+/*
+ * Called when a player drops from the server.
+ * Will not be called between levels.
+ */
+void
+ClientDisconnect(edict_t *ent)
+{
+ int playernum;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!ent->client)
+ {
+ return;
+ }
+
+ gi.bprintf(PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
+
+ /* send effect */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_LOGOUT);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ gi.unlinkentity(ent);
+ ent->s.modelindex = 0;
+ ent->solid = SOLID_NOT;
+ ent->inuse = false;
+ ent->classname = "disconnected";
+ ent->client->pers.connected = false;
+
+ playernum = ent - g_edicts - 1;
+ gi.configstring(CS_PLAYERSKINS + playernum, "");
+}
+
+/* ============================================================== */
+
+edict_t *pm_passent;
+
+/* pmove doesn't need to know
+ about passent and contentmask */
+trace_t
+PM_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
+{
+ if (pm_passent->health > 0)
+ {
+ return gi.trace(start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
+ }
+ else
+ {
+ return gi.trace(start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
+ }
+}
+
+unsigned
+CheckBlock(void *b, int c)
+{
+ int v, i;
+
+ if (!b)
+ {
+ return 0;
+ }
+
+ v = 0;
+
+ for (i = 0; i < c; i++)
+ {
+ v += ((byte *)b)[i];
+ }
+
+ return v;
+}
+
+void
+PrintPmove(pmove_t *pm)
+{
+ unsigned c1, c2;
+
+ if (!pm)
+ {
+ return;
+ }
+
+ c1 = CheckBlock(&pm->s, sizeof(pm->s));
+ c2 = CheckBlock(&pm->cmd, sizeof(pm->cmd));
+ Com_Printf("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
+}
+
+/*
+ * This will be called once for each client frame, which will
+ * usually be a couple times for each server frame.
+ */
+void
+ClientThink(edict_t *ent, usercmd_t *ucmd)
+{
+ gclient_t *client;
+ edict_t *other;
+ int i, j;
+ pmove_t pm;
+
+ if (!ent || !ucmd)
+ {
+ return;
+ }
+
+ level.current_entity = ent;
+ client = ent->client;
+
+ if (level.intermissiontime)
+ {
+ client->ps.pmove.pm_type = PM_FREEZE;
+
+ /* can exit intermission after five seconds */
+ if ((level.time > level.intermissiontime + 5.0) &&
+ (ucmd->buttons & BUTTON_ANY))
+ {
+ level.exitintermission = true;
+ }
+
+ return;
+ }
+
+ pm_passent = ent;
+
+ if (ent->client->chase_target)
+ {
+ client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+ client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+ client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+ }
+ else
+ {
+ /* set up for pmove */
+ memset(&pm, 0, sizeof(pm));
+
+ if (ent->movetype == MOVETYPE_NOCLIP)
+ {
+ client->ps.pmove.pm_type = PM_SPECTATOR;
+ }
+ else if (ent->s.modelindex != 255)
+ {
+ client->ps.pmove.pm_type = PM_GIB;
+ }
+ else if (ent->deadflag)
+ {
+ client->ps.pmove.pm_type = PM_DEAD;
+ }
+ else
+ {
+ client->ps.pmove.pm_type = PM_NORMAL;
+ }
+
+ client->ps.pmove.gravity = sv_gravity->value;
+ pm.s = client->ps.pmove;
+
+ for (i = 0; i < 3; i++)
+ {
+ pm.s.origin[i] = ent->s.origin[i] * 8;
+ /* save to an int first, in case the short overflows
+ * so we get defined behavior (at least with -fwrapv) */
+ int tmpVel = ent->velocity[i] * 8;
+ pm.s.velocity[i] = tmpVel;
+ }
+
+ if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
+ {
+ pm.snapinitial = true;
+ }
+
+ pm.cmd = *ucmd;
+
+ pm.trace = PM_trace; /* adds default parms */
+ pm.pointcontents = gi.pointcontents;
+
+ /* perform a pmove */
+ gi.Pmove(&pm);
+
+ /* save results of pmove */
+ client->ps.pmove = pm.s;
+ client->old_pmove = pm.s;
+
+ for (i = 0; i < 3; i++)
+ {
+ ent->s.origin[i] = pm.s.origin[i] * 0.125;
+ ent->velocity[i] = pm.s.velocity[i] * 0.125;
+ }
+
+ VectorCopy(pm.mins, ent->mins);
+ VectorCopy(pm.maxs, ent->maxs);
+
+ client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+ client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+ client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+
+ if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) &&
+ (pm.waterlevel == 0))
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
+ PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
+ }
+
+ ent->viewheight = pm.viewheight;
+ ent->waterlevel = pm.waterlevel;
+ ent->watertype = pm.watertype;
+ ent->groundentity = pm.groundentity;
+
+ if (pm.groundentity)
+ {
+ ent->groundentity_linkcount = pm.groundentity->linkcount;
+ }
+
+ if (ent->deadflag)
+ {
+ client->ps.viewangles[ROLL] = 40;
+ client->ps.viewangles[PITCH] = -15;
+ client->ps.viewangles[YAW] = client->killer_yaw;
+ }
+ else
+ {
+ VectorCopy(pm.viewangles, client->v_angle);
+ VectorCopy(pm.viewangles, client->ps.viewangles);
+ }
+
+ gi.linkentity(ent);
+
+ if (ent->movetype != MOVETYPE_NOCLIP)
+ {
+ G_TouchTriggers(ent);
+ }
+
+ /* touch other objects */
+ for (i = 0; i < pm.numtouch; i++)
+ {
+ other = pm.touchents[i];
+
+ for (j = 0; j < i; j++)
+ {
+ if (pm.touchents[j] == other)
+ {
+ break;
+ }
+ }
+
+ if (j != i)
+ {
+ continue; /* duplicated */
+ }
+
+ if (!other->touch)
+ {
+ continue;
+ }
+
+ other->touch(other, ent, NULL, NULL);
+ }
+ }
+
+ client->oldbuttons = client->buttons;
+ client->buttons = ucmd->buttons;
+ client->latched_buttons |= client->buttons & ~client->oldbuttons;
+
+ /* save light level the player is standing
+ on for monster sighting AI */
+ ent->light_level = ucmd->lightlevel;
+
+ /* fire weapon from final position if needed */
+ if (client->latched_buttons & BUTTON_ATTACK)
+ {
+ if (client->resp.spectator)
+ {
+ client->latched_buttons = 0;
+
+ if (client->chase_target)
+ {
+ client->chase_target = NULL;
+ client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+ }
+ else
+ {
+ GetChaseTarget(ent);
+ }
+ }
+ else if (!client->weapon_thunk)
+ {
+ client->weapon_thunk = true;
+ Think_Weapon(ent);
+ }
+ }
+
+ if (client->resp.spectator)
+ {
+ if (ucmd->upmove >= 10)
+ {
+ if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD))
+ {
+ client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
+
+ if (client->chase_target)
+ {
+ ChaseNext(ent);
+ }
+ else
+ {
+ GetChaseTarget(ent);
+ }
+ }
+ }
+ else
+ {
+ client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
+ }
+ }
+
+ /* update chase cam if being followed */
+ for (i = 1; i <= maxclients->value; i++)
+ {
+ other = g_edicts + i;
+
+ if (other->inuse && (other->client->chase_target == ent))
+ {
+ UpdateChaseCam(other);
+ }
+ }
+}
+
+/*
+ * This will be called once for each server frame,
+ * before running any other entities in the world.
+ */
+void
+ClientBeginServerFrame(edict_t *ent)
+{
+ gclient_t *client;
+ int buttonMask;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return;
+ }
+
+ client = ent->client;
+
+ if (deathmatch->value &&
+ (client->pers.spectator != client->resp.spectator) &&
+ ((level.time - client->respawn_time) >= 5))
+ {
+ spectator_respawn(ent);
+ return;
+ }
+
+ /* run weapon animations if it hasn't been done by a ucmd_t */
+ if (!client->weapon_thunk && !client->resp.spectator)
+ {
+ Think_Weapon(ent);
+ }
+ else
+ {
+ client->weapon_thunk = false;
+ }
+
+ if (ent->deadflag)
+ {
+ /* wait for any button just going down */
+ if (level.time > client->respawn_time)
+ {
+ /* in deathmatch, only wait for attack button */
+ if (deathmatch->value)
+ {
+ buttonMask = BUTTON_ATTACK;
+ }
+ else
+ {
+ buttonMask = -1;
+ }
+
+ if ((client->latched_buttons & buttonMask) ||
+ (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN)))
+ {
+ respawn(ent);
+ client->latched_buttons = 0;
+ }
+ }
+
+ return;
+ }
+
+ /* add player trail so monsters can follow */
+ if (!deathmatch->value)
+ {
+ if (!visible(ent, PlayerTrail_LastSpot()))
+ {
+ PlayerTrail_Add(ent->s.old_origin);
+ }
+ }
+
+ client->latched_buttons = 0;
+}
diff --git a/xatrix/src/player/hud.c b/xatrix/src/player/hud.c
new file mode 100644
index 0000000..8703d7a
--- /dev/null
+++ b/xatrix/src/player/hud.c
@@ -0,0 +1,625 @@
+/* =======================================================================
+ *
+ * HUD, deathmatch scoreboard, help computer and intermission stuff.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+
+void
+MoveClientToIntermission(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value || coop->value)
+ {
+ ent->client->showscores = true;
+ }
+
+ VectorCopy(level.intermission_origin, ent->s.origin);
+ ent->client->ps.pmove.origin[0] = level.intermission_origin[0] * 8;
+ ent->client->ps.pmove.origin[1] = level.intermission_origin[1] * 8;
+ ent->client->ps.pmove.origin[2] = level.intermission_origin[2] * 8;
+ VectorCopy(level.intermission_angle, ent->client->ps.viewangles);
+ ent->client->ps.pmove.pm_type = PM_FREEZE;
+ ent->client->ps.gunindex = 0;
+ ent->client->ps.blend[3] = 0;
+ ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+ /* clean up powerup info */
+ ent->client->quad_framenum = 0;
+ ent->client->invincible_framenum = 0;
+ ent->client->breather_framenum = 0;
+ ent->client->enviro_framenum = 0;
+ ent->client->grenade_blew_up = false;
+ ent->client->grenade_time = 0;
+ ent->client->quadfire_framenum = 0;
+ ent->client->trap_blew_up = false;
+ ent->client->trap_time = 0;
+
+ ent->viewheight = 0;
+ ent->s.modelindex = 0;
+ ent->s.modelindex2 = 0;
+ ent->s.modelindex3 = 0;
+ ent->s.modelindex = 0;
+ ent->s.effects = 0;
+ ent->s.sound = 0;
+ ent->solid = SOLID_NOT;
+
+ gi.linkentity(ent);
+
+ /* add the layout */
+
+ if (deathmatch->value || coop->value)
+ {
+ DeathmatchScoreboardMessage(ent, NULL);
+ gi.unicast(ent, true);
+ }
+}
+
+void
+BeginIntermission(edict_t *targ)
+{
+ int i, n;
+ edict_t *ent, *client;
+
+ if (!targ)
+ {
+ return;
+ }
+
+ if (level.intermissiontime)
+ {
+ return; /* already activated */
+ }
+
+ game.autosaved = false;
+
+ /* respawn any dead clients */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ client = g_edicts + 1 + i;
+
+ if (!client->inuse)
+ {
+ continue;
+ }
+
+ if (client->health <= 0)
+ {
+ respawn(client);
+ }
+ }
+
+ level.intermissiontime = level.time;
+ level.changemap = targ->map;
+
+ if (strstr(level.changemap, "*"))
+ {
+ if (coop->value)
+ {
+ for (i = 0; i < maxclients->value; i++)
+ {
+ client = g_edicts + 1 + i;
+
+ if (!client->inuse)
+ {
+ continue;
+ }
+
+ /* strip players of all keys between units */
+ for (n = 0; n < game.num_items; n++)
+ {
+ if (itemlist[n].flags & IT_KEY)
+ {
+ client->client->pers.inventory[n] = 0;
+ }
+ }
+
+ client->client->pers.power_cubes = 0;
+ }
+ }
+ }
+ else
+ {
+ if (!deathmatch->value)
+ {
+ level.exitintermission = 1; /* go immediately to the next level */
+ return;
+ }
+ }
+
+ level.exitintermission = 0;
+
+ /* find an intermission spot */
+ ent = G_Find(NULL, FOFS(classname), "info_player_intermission");
+
+ if (!ent)
+ {
+ /* the map creator forgot to put in an intermission point... */
+ ent = G_Find(NULL, FOFS(classname), "info_player_start");
+
+ if (!ent)
+ {
+ ent = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
+ }
+ }
+ else
+ {
+ /* chose one of four spots */
+ i = rand() & 3;
+
+ while (i--)
+ {
+ ent = G_Find(ent, FOFS(classname), "info_player_intermission");
+
+ if (!ent) /* wrap around the list */
+ {
+ ent = G_Find(ent, FOFS(classname), "info_player_intermission");
+ }
+ }
+ }
+
+ VectorCopy(ent->s.origin, level.intermission_origin);
+ VectorCopy(ent->s.angles, level.intermission_angle);
+
+ /* move all clients to the intermission point */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ client = g_edicts + 1 + i;
+
+ if (!client->inuse)
+ {
+ continue;
+ }
+
+ MoveClientToIntermission(client);
+ }
+}
+
+void
+DeathmatchScoreboardMessage(edict_t *ent, edict_t *killer /* can be NULL */)
+{
+ char entry[1024];
+ char string[1400];
+ int stringlength;
+ int i, j, k;
+ int sorted[MAX_CLIENTS];
+ int sortedscores[MAX_CLIENTS];
+ int score, total;
+ int x, y;
+ gclient_t *cl;
+ edict_t *cl_ent;
+ char *tag;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* sort the clients by score */
+ total = 0;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ cl_ent = g_edicts + 1 + i;
+
+ if (!cl_ent->inuse || game.clients[i].resp.spectator)
+ {
+ continue;
+ }
+
+ score = game.clients[i].resp.score;
+
+ for (j = 0; j < total; j++)
+ {
+ if (score > sortedscores[j])
+ {
+ break;
+ }
+ }
+
+ for (k = total; k > j; k--)
+ {
+ sorted[k] = sorted[k - 1];
+ sortedscores[k] = sortedscores[k - 1];
+ }
+
+ sorted[j] = i;
+ sortedscores[j] = score;
+ total++;
+ }
+
+ /* print level name and exit rules */
+ string[0] = 0;
+ stringlength = strlen(string);
+
+ /* add the clients in sorted order */
+ if (total > 12)
+ {
+ total = 12;
+ }
+
+ for (i = 0; i < total; i++)
+ {
+ cl = &game.clients[sorted[i]];
+ cl_ent = g_edicts + 1 + sorted[i];
+
+ x = (i >= 6) ? 160 : 0;
+ y = 32 + 32 * (i % 6);
+
+ /* add a dogtag */
+ if (cl_ent == ent)
+ {
+ tag = "tag1";
+ }
+ else if (cl_ent == killer)
+ {
+ tag = "tag2";
+ }
+ else
+ {
+ tag = NULL;
+ }
+
+ if (tag)
+ {
+ Com_sprintf(entry, sizeof(entry),
+ "xv %i yv %i picn %s ", x + 32, y, tag);
+ j = strlen(entry);
+
+ if (stringlength + j > 1024)
+ {
+ break;
+ }
+
+ strcpy(string + stringlength, entry);
+ stringlength += j;
+ }
+
+ /* send the layout */
+ Com_sprintf(entry, sizeof(entry), "client %i %i %i %i %i %i ",
+ x, y, sorted[i], cl->resp.score, cl->ping,
+ (level.framenum - cl->resp.enterframe) / 600);
+ j = strlen(entry);
+
+ if (stringlength + j > 1024)
+ {
+ break;
+ }
+
+ strcpy(string + stringlength, entry);
+ stringlength += j;
+ }
+
+ gi.WriteByte(svc_layout);
+ gi.WriteString(string);
+}
+
+void
+HelpComputerMessage(edict_t *ent)
+{
+ char string[1024];
+ char *sk;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (skill->value == SKILL_EASY)
+ {
+ sk = "easy";
+ }
+ else if (skill->value == SKILL_MEDIUM)
+ {
+ sk = "medium";
+ }
+ else if (skill->value == SKILL_HARD)
+ {
+ sk = "hard";
+ }
+ else
+ {
+ sk = "hard+";
+ }
+
+ /* send the layout */
+ Com_sprintf(string, sizeof(string),
+ "xv 32 yv 8 picn help " /* background */
+ "xv 202 yv 12 string2 \"%s\" " /* skill */
+ "xv 0 yv 24 cstring2 \"%s\" " /* level name */
+ "xv 0 yv 54 cstring2 \"%s\" " /* help 1 */
+ "xv 0 yv 110 cstring2 \"%s\" " /* help 2 */
+ "xv 50 yv 164 string2 \" kills goals secrets\" "
+ "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
+ sk,
+ level.level_name,
+ game.helpmessage1,
+ game.helpmessage2,
+ level.killed_monsters, level.total_monsters,
+ level.found_goals, level.total_goals,
+ level.found_secrets, level.total_secrets);
+
+ gi.WriteByte(svc_layout);
+ gi.WriteString(string);
+}
+
+void
+InventoryMessage(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ gi.WriteByte(svc_inventory);
+
+ for (i = 0; i < MAX_ITEMS; i++)
+ {
+ gi.WriteShort(ent->client->pers.inventory[i]);
+ }
+}
+
+/* ======================================================================= */
+
+void
+G_SetStats(edict_t *ent)
+{
+ gitem_t *item;
+ int index, cells = 0;
+ int power_armor_type;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* health */
+ ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
+ ent->client->ps.stats[STAT_HEALTH] = (ent->health < -99) ? -99 : ent->health;
+
+ /* ammo */
+ if (!ent->client->ammo_index)
+ {
+ ent->client->ps.stats[STAT_AMMO_ICON] = 0;
+ ent->client->ps.stats[STAT_AMMO] = 0;
+ }
+ else
+ {
+ item = &itemlist[ent->client->ammo_index];
+ ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex(item->icon);
+ ent->client->ps.stats[STAT_AMMO] =
+ ent->client->pers.inventory[ent->client->ammo_index];
+ }
+
+ cells = 0;
+
+ /* armor */
+ power_armor_type = PowerArmorType(ent);
+
+ if (power_armor_type)
+ {
+ cells = ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))];
+
+ if (cells == 0)
+ {
+ /* ran out of cells for power armor */
+ ent->flags &= ~FL_POWER_ARMOR;
+ gi.sound(ent, CHAN_ITEM, gi.soundindex( "misc/power2.wav"),
+ 1, ATTN_NORM, 0);
+ power_armor_type = 0;
+ }
+ }
+
+ index = ArmorIndex(ent);
+
+ if (power_armor_type && (!index || (level.framenum & 8)))
+ {
+ /* flash between power armor and other armor icon */
+ ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex("i_powershield");
+ ent->client->ps.stats[STAT_ARMOR] = cells;
+ }
+ else if (index)
+ {
+ item = GetItemByIndex(index);
+ ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex(item->icon);
+ ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
+ ent->client->ps.stats[STAT_ARMOR] = 0;
+ }
+
+ /* pickup message */
+ if (level.time > ent->client->pickup_msg_time)
+ {
+ ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
+ ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
+ }
+
+ /* timers */
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_quad");
+ ent->client->ps.stats[STAT_TIMER] =
+ (ent->client->quad_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->quadfire_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_quadfire");
+ ent->client->ps.stats[STAT_TIMER] = (ent->client->quadfire_framenum
+ - level.framenum) / 10;
+ }
+ else if (ent->client->invincible_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex(
+ "p_invulnerability");
+ ent->client->ps.stats[STAT_TIMER] =
+ (ent->client->invincible_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->enviro_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_envirosuit");
+ ent->client->ps.stats[STAT_TIMER] =
+ (ent->client->enviro_framenum - level.framenum) / 10;
+ }
+ else if (ent->client->breather_framenum > level.framenum)
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_rebreather");
+ ent->client->ps.stats[STAT_TIMER] =
+ (ent->client->breather_framenum - level.framenum) / 10;
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_TIMER_ICON] = 0;
+ ent->client->ps.stats[STAT_TIMER] = 0;
+ }
+
+ /* selected item */
+ if (ent->client->pers.selected_item == -1)
+ {
+ ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_SELECTED_ICON] =
+ gi.imageindex(itemlist[ent->client->pers.selected_item].icon);
+ }
+
+ ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
+
+ /* layouts */
+ ent->client->ps.stats[STAT_LAYOUTS] = 0;
+
+ if (deathmatch->value)
+ {
+ if ((ent->client->pers.health <= 0) || level.intermissiontime ||
+ ent->client->showscores)
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+ }
+
+ if (ent->client->showinventory && (ent->client->pers.health > 0))
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+ }
+ }
+ else
+ {
+ if (ent->client->showscores || ent->client->showhelp)
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+ }
+
+ if (ent->client->showinventory && (ent->client->pers.health > 0))
+ {
+ ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+ }
+ }
+
+ /* frags */
+ ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
+
+ /* help icon / current weapon if not shown */
+ if (ent->client->pers.helpchanged && (level.framenum & 8))
+ {
+ ent->client->ps.stats[STAT_HELPICON] = gi.imageindex("i_help");
+ }
+ else if (((ent->client->pers.hand == CENTER_HANDED) ||
+ (ent->client->ps.fov > 91)) &&
+ ent->client->pers.weapon)
+ {
+ cvar_t *gun;
+ gun = gi.cvar("cl_gun", "2", 0);
+
+ if (gun->value != 2)
+ {
+ ent->client->ps.stats[STAT_HELPICON] = gi.imageindex(
+ ent->client->pers.weapon->icon);
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_HELPICON] = 0;
+ }
+ }
+ else
+ {
+ ent->client->ps.stats[STAT_HELPICON] = 0;
+ }
+
+ ent->client->ps.stats[STAT_SPECTATOR] = 0;
+}
+
+void
+G_CheckChaseStats(edict_t *ent)
+{
+ int i;
+ gclient_t *cl;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ for (i = 1; i <= maxclients->value; i++)
+ {
+ cl = g_edicts[i].client;
+
+ if (!g_edicts[i].inuse || (cl->chase_target != ent))
+ {
+ continue;
+ }
+
+ memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats));
+ G_SetSpectatorStats(g_edicts + i);
+ }
+}
+
+void
+G_SetSpectatorStats(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ gclient_t *cl = ent->client;
+
+ if (!cl->chase_target)
+ {
+ G_SetStats(ent);
+ }
+
+ cl->ps.stats[STAT_SPECTATOR] = 1;
+
+ /* layouts are independant in spectator */
+ cl->ps.stats[STAT_LAYOUTS] = 0;
+
+ if ((cl->pers.health <= 0) || level.intermissiontime || cl->showscores)
+ {
+ cl->ps.stats[STAT_LAYOUTS] |= 1;
+ }
+
+ if (cl->showinventory && (cl->pers.health > 0))
+ {
+ cl->ps.stats[STAT_LAYOUTS] |= 2;
+ }
+
+ if (cl->chase_target && cl->chase_target->inuse)
+ {
+ cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS +
+ (cl->chase_target - g_edicts) - 1;
+ }
+ else
+ {
+ cl->ps.stats[STAT_CHASE] = 0;
+ }
+}
diff --git a/xatrix/src/player/trail.c b/xatrix/src/player/trail.c
new file mode 100644
index 0000000..13db2c6
--- /dev/null
+++ b/xatrix/src/player/trail.c
@@ -0,0 +1,156 @@
+/* =======================================================================
+ *
+ * The player trail, used by monsters to locate the player.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+
+/*
+ * This is a circular list containing the a list of points of where
+ * the player has been recently. It is used by monsters for pursuit.
+ *
+ * .origin the spot
+ * .owner forward link
+ * .aiment backward link
+ */
+
+#define TRAIL_LENGTH 8
+
+#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1))
+#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1))
+
+edict_t *trail[TRAIL_LENGTH];
+int trail_head;
+qboolean trail_active = false;
+
+void
+PlayerTrail_Init(void)
+{
+ int n;
+
+ if (deathmatch->value)
+ {
+ return;
+ }
+
+ for (n = 0; n < TRAIL_LENGTH; n++)
+ {
+ trail[n] = G_Spawn();
+ trail[n]->classname = "player_trail";
+ }
+
+ trail_head = 0;
+ trail_active = true;
+}
+
+void
+PlayerTrail_Add(vec3_t spot)
+{
+ vec3_t temp;
+
+ if (!trail_active)
+ {
+ return;
+ }
+
+ VectorCopy(spot, trail[trail_head]->s.origin);
+
+ trail[trail_head]->timestamp = level.time;
+
+ VectorSubtract(spot, trail[PREV(trail_head)]->s.origin, temp);
+ trail[trail_head]->s.angles[1] = vectoyaw(temp);
+
+ trail_head = NEXT(trail_head);
+}
+
+void
+PlayerTrail_New(vec3_t spot)
+{
+ if (!trail_active)
+ {
+ return;
+ }
+
+ PlayerTrail_Init();
+ PlayerTrail_Add(spot);
+}
+
+edict_t *
+PlayerTrail_PickFirst(edict_t *self)
+{
+ int marker;
+ int n;
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ if (!trail_active)
+ {
+ return NULL;
+ }
+
+ for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+ {
+ if (trail[marker]->timestamp <= self->monsterinfo.trail_time)
+ {
+ marker = NEXT(marker);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (visible(self, trail[marker]))
+ {
+ return trail[marker];
+ }
+
+ if (visible(self, trail[PREV(marker)]))
+ {
+ return trail[PREV(marker)];
+ }
+
+ return trail[marker];
+}
+
+edict_t *
+PlayerTrail_PickNext(edict_t *self)
+{
+ int marker;
+ int n;
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ if (!trail_active)
+ {
+ return NULL;
+ }
+
+ for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+ {
+ if (trail[marker]->timestamp <= self->monsterinfo.trail_time)
+ {
+ marker = NEXT(marker);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return trail[marker];
+}
+
+edict_t *
+PlayerTrail_LastSpot(void)
+{
+ return trail[PREV(trail_head)];
+}
diff --git a/xatrix/src/player/view.c b/xatrix/src/player/view.c
new file mode 100644
index 0000000..b2eeadd
--- /dev/null
+++ b/xatrix/src/player/view.c
@@ -0,0 +1,1431 @@
+/* =======================================================================
+ *
+ * The "camera" through which the player looks into the game.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+#include "../monster/misc/player.h"
+
+static edict_t *current_player;
+static gclient_t *current_client;
+
+static vec3_t forward, right, up;
+float xyspeed;
+
+float bobmove;
+int bobcycle; /* odd cycles are right foot going forward */
+float bobfracsin; /* sin(bobfrac*M_PI) */
+
+float
+SV_CalcRoll(vec3_t angles, vec3_t velocity)
+{
+ float sign;
+ float side;
+ float value;
+
+ side = DotProduct(velocity, right);
+ sign = side < 0 ? -1 : 1;
+ side = fabs(side);
+
+ value = sv_rollangle->value;
+
+ if (side < sv_rollspeed->value)
+ {
+ side = side * value / sv_rollspeed->value;
+ }
+ else
+ {
+ side = value;
+ }
+
+ return side * sign;
+}
+
+/*
+ * Handles color blends and view kicks
+ */
+void
+P_DamageFeedback(edict_t *player)
+{
+ gclient_t *client;
+ float side;
+ float realcount, count, kick;
+ vec3_t v;
+ int r, l;
+ static vec3_t power_color = {0.0, 1.0, 0.0};
+ static vec3_t acolor = {1.0, 1.0, 1.0};
+ static vec3_t bcolor = {1.0, 0.0, 0.0};
+
+ if (!player)
+ {
+ return;
+ }
+
+ /* death/gib sound is aggregated and played here */
+ if (player->sounds)
+ {
+ gi.sound (player, CHAN_VOICE, player->sounds, 1, ATTN_NORM, 0);
+ player->sounds = 0;
+ }
+
+ client = player->client;
+
+ /* flash the backgrounds behind the status numbers */
+ client->ps.stats[STAT_FLASHES] = 0;
+
+ if (client->damage_blood)
+ {
+ client->ps.stats[STAT_FLASHES] |= 1;
+ }
+
+ if (client->damage_armor && !(player->flags & FL_GODMODE) &&
+ (client->invincible_framenum <= level.framenum))
+ {
+ client->ps.stats[STAT_FLASHES] |= 2;
+ }
+
+ /* total points of damage shot at the player this frame */
+ count =
+ (client->damage_blood + client->damage_armor + client->damage_parmor);
+
+ if (count == 0)
+ {
+ return; /* didn't take any damage */
+ }
+
+ /* start a pain animation if still in the player model */
+ if ((client->anim_priority < ANIM_PAIN) && (player->s.modelindex == 255))
+ {
+ static int i;
+
+ client->anim_priority = ANIM_PAIN;
+
+ if (client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ player->s.frame = FRAME_crpain1 - 1;
+ client->anim_end = FRAME_crpain4;
+ }
+ else
+ {
+ i = (i + 1) % 3;
+
+ switch (i)
+ {
+ case 0:
+ player->s.frame = FRAME_pain101 - 1;
+ client->anim_end = FRAME_pain104;
+ break;
+ case 1:
+ player->s.frame = FRAME_pain201 - 1;
+ client->anim_end = FRAME_pain204;
+ break;
+ case 2:
+ player->s.frame = FRAME_pain301 - 1;
+ client->anim_end = FRAME_pain304;
+ break;
+ }
+ }
+ }
+
+ realcount = count;
+
+ if (count < 10)
+ {
+ count = 10; /* always make a visible effect */
+ }
+
+ /* play an apropriate pain sound */
+ if ((level.time > player->pain_debounce_time) &&
+ !(player->flags & FL_GODMODE) &&
+ (client->invincible_framenum <= level.framenum) &&
+ player->health > 0)
+ {
+ r = 1 + (rand() & 1);
+ player->pain_debounce_time = level.time + 0.7;
+
+ if (player->health < 25)
+ {
+ l = 25;
+ }
+ else if (player->health < 50)
+ {
+ l = 50;
+ }
+ else if (player->health < 75)
+ {
+ l = 75;
+ }
+ else
+ {
+ l = 100;
+ }
+
+ gi.sound(player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav",
+ l, r)), 1, ATTN_NORM, 0);
+ }
+
+ /* the total alpha of the blend is always proportional to count */
+ if (client->damage_alpha < 0)
+ {
+ client->damage_alpha = 0;
+ }
+
+ client->damage_alpha += count * 0.01;
+
+ if (client->damage_alpha < 0.2)
+ {
+ client->damage_alpha = 0.2;
+ }
+
+ if (client->damage_alpha > 0.6)
+ {
+ client->damage_alpha = 0.6; /* don't go too saturated */
+ }
+
+ /* the color of the blend will vary based
+ on how much was absorbed by different
+ armors */
+ VectorClear(v);
+
+ if (client->damage_parmor)
+ {
+ VectorMA(v, (float)client->damage_parmor / realcount, power_color, v);
+ }
+
+ if (client->damage_armor)
+ {
+ VectorMA(v, (float)client->damage_armor / realcount, acolor, v);
+ }
+
+ if (client->damage_blood)
+ {
+ VectorMA(v, (float)client->damage_blood / realcount, bcolor, v);
+ }
+
+ VectorCopy(v, client->damage_blend);
+
+ /* calculate view angle kicks */
+ kick = abs(client->damage_knockback);
+
+ if (kick && (player->health > 0)) /* kick of 0 means no view adjust at all */
+ {
+ kick = kick * 100 / player->health;
+
+ if (kick < count * 0.5)
+ {
+ kick = count * 0.5;
+ }
+
+ if (kick > 50)
+ {
+ kick = 50;
+ }
+
+ VectorSubtract(client->damage_from, player->s.origin, v);
+ VectorNormalize(v);
+
+ side = DotProduct(v, right);
+ client->v_dmg_roll = kick * side * 0.3;
+
+ side = -DotProduct(v, forward);
+ client->v_dmg_pitch = kick * side * 0.3;
+
+ client->v_dmg_time = level.time + DAMAGE_TIME;
+ }
+
+ /* clear totals */
+ client->damage_blood = 0;
+ client->damage_armor = 0;
+ client->damage_parmor = 0;
+ client->damage_knockback = 0;
+}
+
+/*
+ * Auto pitching on slopes?
+ *
+ * fall from 128: 400 = 160000
+ * fall from 256: 580 = 336400
+ * fall from 384: 720 = 518400
+ * fall from 512: 800 = 640000
+ * fall from 640: 960 =
+ *
+ * damage = deltavelocity*deltavelocity * 0.0001
+ */
+void
+SV_CalcViewOffset(edict_t *ent)
+{
+ float *angles;
+ float bob;
+ float ratio;
+ float delta;
+ vec3_t v;
+
+ /* base angles */
+ angles = ent->client->ps.kick_angles;
+
+ /* if dead, fix the angle and don't add any kick */
+ if (ent->deadflag)
+ {
+ VectorClear(angles);
+
+ ent->client->ps.viewangles[ROLL] = 40;
+ ent->client->ps.viewangles[PITCH] = -15;
+ ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;
+ }
+ else
+ {
+ /* add angles based on weapon kick */
+ VectorCopy(ent->client->kick_angles, angles);
+
+ /* add angles based on damage kick */
+ ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME;
+
+ if (ratio < 0)
+ {
+ ratio = 0;
+ ent->client->v_dmg_pitch = 0;
+ ent->client->v_dmg_roll = 0;
+ }
+
+ angles[PITCH] += ratio * ent->client->v_dmg_pitch;
+ angles[ROLL] += ratio * ent->client->v_dmg_roll;
+
+ /* add pitch based on fall kick */
+ ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+
+ if (ratio < 0)
+ {
+ ratio = 0;
+ }
+
+ angles[PITCH] += ratio * ent->client->fall_value;
+
+ /* add angles based on velocity */
+ delta = DotProduct(ent->velocity, forward);
+ angles[PITCH] += delta * run_pitch->value;
+
+ delta = DotProduct(ent->velocity, right);
+ angles[ROLL] += delta * run_roll->value;
+
+ /* add angles based on bob */
+ delta = bobfracsin * bob_pitch->value * xyspeed;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ delta *= 6; /* crouching */
+ }
+
+ angles[PITCH] += delta;
+ delta = bobfracsin * bob_roll->value * xyspeed;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ delta *= 6; /* crouching */
+ }
+
+ if (bobcycle & 1)
+ {
+ delta = -delta;
+ }
+
+ angles[ROLL] += delta;
+ }
+
+ /* base origin */
+ VectorClear(v);
+
+ /* add view height */
+ v[2] += ent->viewheight;
+
+ /* add fall height */
+ ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+
+ if (ratio < 0)
+ {
+ ratio = 0;
+ }
+
+ v[2] -= ratio * ent->client->fall_value * 0.4;
+
+ /* add bob height */
+ bob = bobfracsin * xyspeed * bob_up->value;
+
+ if (bob > 6)
+ {
+ bob = 6;
+ }
+
+ v[2] += bob;
+
+ /* add kick offset */
+ VectorAdd(v, ent->client->kick_origin, v);
+
+ /* absolutely bound offsets so the view can
+ never be outside the player box */
+
+ if (v[0] < -14)
+ {
+ v[0] = -14;
+ }
+ else if (v[0] > 14)
+ {
+ v[0] = 14;
+ }
+
+ if (v[1] < -14)
+ {
+ v[1] = -14;
+ }
+ else if (v[1] > 14)
+ {
+ v[1] = 14;
+ }
+
+ if (v[2] < -22)
+ {
+ v[2] = -22;
+ }
+ else if (v[2] > 30)
+ {
+ v[2] = 30;
+ }
+
+ VectorCopy(v, ent->client->ps.viewoffset);
+}
+
+void
+SV_CalcGunOffset(edict_t *ent)
+{
+ int i;
+ float delta;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ /* gun angles from bobbing */
+ ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005;
+ ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01;
+
+ if (bobcycle & 1)
+ {
+ ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL];
+ ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW];
+ }
+
+ ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005;
+
+ /* gun angles from delta movement */
+ for (i = 0; i < 3; i++)
+ {
+ delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i];
+
+ if (delta > 180)
+ {
+ delta -= 360;
+ }
+
+ if (delta < -180)
+ {
+ delta += 360;
+ }
+
+ if (delta > 45)
+ {
+ delta = 45;
+ }
+
+ if (delta < -45)
+ {
+ delta = -45;
+ }
+
+ if (i == YAW)
+ {
+ ent->client->ps.gunangles[ROLL] += 0.1 * delta;
+ }
+
+ ent->client->ps.gunangles[i] += 0.2 * delta;
+ }
+
+ /* gun height */
+ VectorClear(ent->client->ps.gunoffset);
+
+ /* gun_x / gun_y / gun_z are development tools */
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->ps.gunoffset[i] += forward[i] * (gun_y->value);
+ ent->client->ps.gunoffset[i] += right[i] * gun_x->value;
+ ent->client->ps.gunoffset[i] += up[i] * (-gun_z->value);
+ }
+}
+
+void
+SV_AddBlend(float r, float g, float b, float a, float *v_blend)
+{
+ float a2, a3;
+
+ if (!v_blend)
+ {
+ return;
+ }
+
+ if (a <= 0)
+ {
+ return;
+ }
+
+ a2 = v_blend[3] + (1 - v_blend[3]) * a; /* new total alpha */
+ a3 = v_blend[3] / a2; /* fraction of color from old */
+
+ v_blend[0] = v_blend[0] * a3 + r * (1 - a3);
+ v_blend[1] = v_blend[1] * a3 + g * (1 - a3);
+ v_blend[2] = v_blend[2] * a3 + b * (1 - a3);
+ v_blend[3] = a2;
+}
+
+void
+SV_CalcBlend(edict_t *ent)
+{
+ int contents;
+ vec3_t vieworg;
+ int remaining;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->ps.blend[0] = ent->client->ps.blend[1] =
+ ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0;
+
+ /* add for contents */
+ VectorAdd(ent->s.origin, ent->client->ps.viewoffset, vieworg);
+ contents = gi.pointcontents(vieworg);
+
+ if (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER))
+ {
+ ent->client->ps.rdflags |= RDF_UNDERWATER;
+ }
+ else
+ {
+ ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+ }
+
+ if (contents & (CONTENTS_SOLID | CONTENTS_LAVA))
+ {
+ SV_AddBlend(1.0, 0.3, 0.0, 0.6, ent->client->ps.blend);
+ }
+ else if (contents & CONTENTS_SLIME)
+ {
+ SV_AddBlend(0.0, 0.1, 0.05, 0.6, ent->client->ps.blend);
+ }
+ else if (contents & CONTENTS_WATER)
+ {
+ SV_AddBlend(0.5, 0.3, 0.2, 0.4, ent->client->ps.blend);
+ }
+
+ /* add for powerups */
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ remaining = ent->client->quad_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(0, 0, 1, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->quadfire_framenum > level.framenum)
+ {
+ remaining = ent->client->quadfire_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(1, 0.2, 0.5, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->invincible_framenum > level.framenum)
+ {
+ remaining = ent->client->invincible_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(1, 1, 0, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->enviro_framenum > level.framenum)
+ {
+ remaining = ent->client->enviro_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(0, 1, 0, 0.08, ent->client->ps.blend);
+ }
+ }
+ else if (ent->client->breather_framenum > level.framenum)
+ {
+ remaining = ent->client->breather_framenum - level.framenum;
+
+ if (remaining == 30) /* beginning to fade */
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ SV_AddBlend(0.4, 1, 0.4, 0.04, ent->client->ps.blend);
+ }
+ }
+
+ /* add for damage */
+ if (ent->client->damage_alpha > 0)
+ {
+ SV_AddBlend(ent->client->damage_blend[0],
+ ent->client->damage_blend[1],
+ ent->client->damage_blend[2],
+ ent->client->damage_alpha,
+ ent->client->ps.blend);
+ }
+
+ if (ent->client->bonus_alpha > 0)
+ {
+ SV_AddBlend(0.85, 0.7, 0.3, ent->client->bonus_alpha,
+ ent->client->ps.blend);
+ }
+
+ /* drop the damage value */
+ ent->client->damage_alpha -= 0.06;
+
+ if (ent->client->damage_alpha < 0)
+ {
+ ent->client->damage_alpha = 0;
+ }
+
+ /* drop the bonus value */
+ ent->client->bonus_alpha -= 0.1;
+
+ if (ent->client->bonus_alpha < 0)
+ {
+ ent->client->bonus_alpha = 0;
+ }
+}
+
+void
+P_FallingDamage(edict_t *ent)
+{
+ float delta;
+ int damage;
+ vec3_t dir;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.modelindex != 255)
+ {
+ return; /* not in the player model */
+ }
+
+ if (ent->movetype == MOVETYPE_NOCLIP)
+ {
+ return;
+ }
+
+ if ((ent->client->oldvelocity[2] < 0) &&
+ (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity))
+ {
+ delta = ent->client->oldvelocity[2];
+ }
+ else
+ {
+ if (!ent->groundentity)
+ {
+ return;
+ }
+
+ delta = ent->velocity[2] - ent->client->oldvelocity[2];
+ }
+
+ delta = delta * delta * 0.0001;
+
+ /* never take falling damage if completely underwater */
+ if (ent->waterlevel == 3)
+ {
+ return;
+ }
+
+ if (ent->waterlevel == 2)
+ {
+ delta *= 0.25;
+ }
+
+ if (ent->waterlevel == 1)
+ {
+ delta *= 0.5;
+ }
+
+ if (delta < 1)
+ {
+ return;
+ }
+
+ if (delta < 15)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ return;
+ }
+
+ ent->client->fall_value = delta * 0.5;
+
+ if (ent->client->fall_value > 40)
+ {
+ ent->client->fall_value = 40;
+ }
+
+ ent->client->fall_time = level.time + FALL_TIME;
+
+ if (delta > 30)
+ {
+ if (ent->health > 0)
+ {
+ if (delta >= 55)
+ {
+ ent->s.event = EV_FALLFAR;
+ }
+ else
+ {
+ ent->s.event = EV_FALL;
+ }
+ }
+
+ ent->pain_debounce_time = level.time; /* no normal pain sound */
+ damage = (delta - 30) / 2;
+
+ if (damage < 1)
+ {
+ damage = 1;
+ }
+
+ VectorSet(dir, 0, 0, 1);
+
+ if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING))
+ {
+ T_Damage(ent, world, world, dir, ent->s.origin, vec3_origin,
+ damage, 0, 0, MOD_FALLING);
+ }
+ }
+ else
+ {
+ ent->s.event = EV_FALLSHORT;
+ return;
+ }
+}
+
+void
+P_WorldEffects(void)
+{
+ qboolean breather;
+ qboolean envirosuit;
+ int waterlevel, old_waterlevel;
+
+ if (current_player->movetype == MOVETYPE_NOCLIP)
+ {
+ current_player->air_finished = level.time + 12; /* don't need air */
+ return;
+ }
+
+ waterlevel = current_player->waterlevel;
+ old_waterlevel = current_client->old_waterlevel;
+ current_client->old_waterlevel = waterlevel;
+
+ breather = current_client->breather_framenum > level.framenum;
+ envirosuit = current_client->enviro_framenum > level.framenum;
+
+ /* if just entered a water volume, play a sound */
+ if (!old_waterlevel && waterlevel)
+ {
+ PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+
+ if (current_player->watertype & CONTENTS_LAVA)
+ {
+ gi.sound(current_player, CHAN_BODY,
+ gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (current_player->watertype & CONTENTS_SLIME)
+ {
+ gi.sound(current_player, CHAN_BODY,
+ gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (current_player->watertype & CONTENTS_WATER)
+ {
+ gi.sound(current_player, CHAN_BODY,
+ gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+ }
+
+ current_player->flags |= FL_INWATER;
+
+ /* clear damage_debounce, so the pain sound will play immediately */
+ current_player->damage_debounce_time = level.time - 1;
+ }
+
+ /* if just completely exited a water volume, play a sound */
+ if (old_waterlevel && !waterlevel)
+ {
+ PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+ gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"),
+ 1, ATTN_NORM, 0);
+ current_player->flags &= ~FL_INWATER;
+ }
+
+ /* check for head just going under water */
+ if ((old_waterlevel != 3) && (waterlevel == 3))
+ {
+ gi.sound(current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ /* check for head just coming out of water */
+ if ((old_waterlevel == 3) && (waterlevel != 3))
+ {
+ if (current_player->air_finished < level.time)
+ {
+ /* gasp for air */
+ gi.sound(current_player, CHAN_VOICE,
+ gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0);
+ PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+ }
+ else if (current_player->air_finished < level.time + 11)
+ {
+ /* just break surface */
+ gi.sound(current_player, CHAN_VOICE,
+ gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0);
+ }
+ }
+
+ /* check for drowning */
+ if (waterlevel == 3)
+ {
+ /* breather or envirosuit give air */
+ if (breather || envirosuit)
+ {
+ current_player->air_finished = level.time + 10;
+
+ if (((int)(current_client->breather_framenum -
+ level.framenum) % 25) == 0)
+ {
+ if (!current_client->breather_sound)
+ {
+ gi.sound(current_player, CHAN_AUTO,
+ gi.soundindex("player/u_breath1.wav"),
+ 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(current_player, CHAN_AUTO,
+ gi.soundindex("player/u_breath2.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ current_client->breather_sound ^= 1;
+ PlayerNoise(current_player, current_player->s.origin,
+ PNOISE_SELF);
+ }
+ }
+
+ /* if out of air, start drowning */
+ if (current_player->air_finished < level.time)
+ {
+ /* drown! */
+ if ((current_player->client->next_drown_time < level.time) &&
+ (current_player->health > 0))
+ {
+ current_player->client->next_drown_time = level.time + 1;
+
+ /* take more damage the longer underwater */
+ current_player->dmg += 2;
+
+ if (current_player->dmg > 15)
+ {
+ current_player->dmg = 15;
+ }
+
+ /* play a gurp sound instead of a normal pain sound */
+ if (current_player->health <= current_player->dmg)
+ {
+ gi.sound(current_player, CHAN_VOICE,
+ gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
+ }
+ else if (rand() & 1)
+ {
+ gi.sound(current_player, CHAN_VOICE,
+ gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(current_player, CHAN_VOICE,
+ gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ current_player->pain_debounce_time = level.time;
+
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin,
+ vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+ }
+ }
+ }
+ else
+ {
+ current_player->air_finished = level.time + 12;
+ current_player->dmg = 2;
+ }
+
+ /* check for sizzle damage */
+ if (waterlevel &&
+ (current_player->watertype & (CONTENTS_LAVA | CONTENTS_SLIME)))
+ {
+ if (current_player->watertype & CONTENTS_LAVA)
+ {
+ if ((current_player->health > 0) &&
+ (current_player->pain_debounce_time <= level.time) &&
+ (current_client->invincible_framenum < level.framenum) &&
+ !(current_player->flags & FL_GODMODE))
+ {
+ if (rand() & 1)
+ {
+ gi.sound(current_player, CHAN_VOICE,
+ gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0);
+ }
+ else
+ {
+ gi.sound(current_player, CHAN_VOICE,
+ gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0);
+ }
+
+ current_player->pain_debounce_time = level.time + 1;
+ }
+
+ if (envirosuit) /* take 1/3 damage with envirosuit */
+ {
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin,
+ vec3_origin, 1 * waterlevel, 0, 0, MOD_LAVA);
+ }
+ else
+ {
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin,
+ vec3_origin, 3 * waterlevel, 0, 0, MOD_LAVA);
+ }
+ }
+
+ if (current_player->watertype & CONTENTS_SLIME)
+ {
+ if (!envirosuit)
+ {
+ /* no damage from slime with envirosuit */
+ T_Damage(current_player, world, world, vec3_origin, current_player->s.origin,
+ vec3_origin, 1 * waterlevel, 0, 0, MOD_SLIME);
+ }
+ }
+ }
+}
+
+void
+G_SetClientEffects(edict_t *ent)
+{
+ int pa_type;
+ int remaining;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->s.effects = 0;
+ ent->s.renderfx = 0;
+
+ if ((ent->health <= 0) || level.intermissiontime)
+ {
+ return;
+ }
+
+ if (ent->powerarmor_time > level.time)
+ {
+ pa_type = PowerArmorType(ent);
+
+ if (pa_type == POWER_ARMOR_SCREEN)
+ {
+ ent->s.effects |= EF_POWERSCREEN;
+ }
+ else if (pa_type == POWER_ARMOR_SHIELD)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= RF_SHELL_GREEN;
+ }
+ }
+
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ remaining = ent->client->quad_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_QUAD;
+ }
+ }
+
+ if (ent->client->quadfire_framenum > level.framenum)
+ {
+ remaining = ent->client->quadfire_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_QUAD;
+ }
+ }
+
+ if (ent->client->invincible_framenum > level.framenum)
+ {
+ remaining = ent->client->invincible_framenum - level.framenum;
+
+ if ((remaining > 30) || (remaining & 4))
+ {
+ ent->s.effects |= EF_PENT;
+ }
+ }
+
+ /* show cheaters!!! */
+ if (ent->flags & FL_GODMODE)
+ {
+ ent->s.effects |= EF_COLOR_SHELL;
+ ent->s.renderfx |= (RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE);
+ }
+}
+
+void
+G_SetClientEvent(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.event)
+ {
+ return;
+ }
+
+ if (ent->health <= 0)
+ {
+ return;
+ }
+
+ if (g_footsteps->value == 1)
+ {
+ if (ent->groundentity && (xyspeed > 225))
+ {
+ if ((int)(current_client->bobtime + bobmove) != bobcycle)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ }
+ }
+ }
+ else if (g_footsteps->value == 2)
+ {
+ if (ent->groundentity)
+ {
+ if ((int)(current_client->bobtime + bobmove) != bobcycle)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ }
+ }
+ }
+ else if (g_footsteps->value >= 3)
+ {
+ if ((int)(current_client->bobtime + bobmove) != bobcycle)
+ {
+ ent->s.event = EV_FOOTSTEP;
+ }
+ }
+}
+
+void
+G_SetClientSound(edict_t *ent)
+{
+ char *weap;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->pers.game_helpchanged != game.helpchanged)
+ {
+ ent->client->pers.game_helpchanged = game.helpchanged;
+ ent->client->pers.helpchanged = 1;
+ }
+
+ /* help beep (no more than three times) */
+ if (ent->client->pers.helpchanged &&
+ (ent->client->pers.helpchanged <= 3) && !(level.framenum & 63))
+ {
+ ent->client->pers.helpchanged++;
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("misc/pc_up.wav"),
+ 1, ATTN_STATIC, 0);
+ }
+
+ if (ent->client->pers.weapon)
+ {
+ weap = ent->client->pers.weapon->classname;
+ }
+ else
+ {
+ weap = "";
+ }
+
+ if (ent->waterlevel && (ent->watertype & (CONTENTS_LAVA | CONTENTS_SLIME)))
+ {
+ ent->s.sound = snd_fry;
+ }
+ else if (strcmp(weap, "weapon_railgun") == 0)
+ {
+ ent->s.sound = gi.soundindex("weapons/rg_hum.wav");
+ }
+ else if (strcmp(weap, "weapon_bfg") == 0)
+ {
+ ent->s.sound = gi.soundindex("weapons/bfg_hum.wav");
+ }
+ else if (strcmp(weap, "weapon_phalanx") == 0)
+ {
+ ent->s.sound = gi.soundindex("weapons/phaloop.wav");
+ }
+ else if (ent->client->weapon_sound)
+ {
+ ent->s.sound = ent->client->weapon_sound;
+ }
+ else
+ {
+ ent->s.sound = 0;
+ }
+}
+
+void
+G_SetClientFrame(edict_t *ent)
+{
+ gclient_t *client;
+ qboolean duck, run;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->s.modelindex != 255)
+ {
+ return; /* not in the player model */
+ }
+
+ client = ent->client;
+
+ if (client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ duck = true;
+ }
+ else
+ {
+ duck = false;
+ }
+
+ if (xyspeed)
+ {
+ run = true;
+ }
+ else
+ {
+ run = false;
+ }
+
+ /* check for stand/duck and stop/go transitions */
+ if ((duck != client->anim_duck) && (client->anim_priority < ANIM_DEATH))
+ {
+ goto newanim;
+ }
+
+ if ((run != client->anim_run) && (client->anim_priority == ANIM_BASIC))
+ {
+ goto newanim;
+ }
+
+ if (!ent->groundentity && (client->anim_priority <= ANIM_WAVE))
+ {
+ goto newanim;
+ }
+
+ if (client->anim_priority == ANIM_REVERSE)
+ {
+ if (ent->s.frame > client->anim_end)
+ {
+ ent->s.frame--;
+ return;
+ }
+ }
+ else if (ent->s.frame < client->anim_end)
+ {
+ /* continue an animation */
+ ent->s.frame++;
+ return;
+ }
+
+ if (client->anim_priority == ANIM_DEATH)
+ {
+ return; /* stay there */
+ }
+
+ if (client->anim_priority == ANIM_JUMP)
+ {
+ if (!ent->groundentity)
+ {
+ return; /* stay there */
+ }
+
+ ent->client->anim_priority = ANIM_WAVE;
+ ent->s.frame = FRAME_jump3;
+ ent->client->anim_end = FRAME_jump6;
+ return;
+ }
+
+newanim:
+ /* return to either a running or standing frame */
+ client->anim_priority = ANIM_BASIC;
+ client->anim_duck = duck;
+ client->anim_run = run;
+
+ if (!ent->groundentity)
+ {
+ client->anim_priority = ANIM_JUMP;
+
+ if (ent->s.frame != FRAME_jump2)
+ {
+ ent->s.frame = FRAME_jump1;
+ }
+
+ client->anim_end = FRAME_jump2;
+ }
+ else if (run)
+ {
+ /* running */
+ if (duck)
+ {
+ ent->s.frame = FRAME_crwalk1;
+ client->anim_end = FRAME_crwalk6;
+ }
+ else
+ {
+ ent->s.frame = FRAME_run1;
+ client->anim_end = FRAME_run6;
+ }
+ }
+ else
+ {
+ /* standing */
+ if (duck)
+ {
+ ent->s.frame = FRAME_crstnd01;
+ client->anim_end = FRAME_crstnd19;
+ }
+ else
+ {
+ ent->s.frame = FRAME_stand01;
+ client->anim_end = FRAME_stand40;
+ }
+ }
+}
+
+/*
+ * Called for each player at the end of
+ * the server frame and right after spawning
+ */
+void
+ClientEndServerFrame(edict_t *ent)
+{
+ float bobtime;
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ current_player = ent;
+ current_client = ent->client;
+
+ /* If the origin or velocity have changed since ClientThink(),
+ update the pmove values. This will happen when the client
+ is pushed by a bmodel or kicked by an explosion.
+ If it wasn't updated here, the view position would lag a frame
+ behind the body position when pushed -- "sinking into plats" */
+ for (i = 0; i < 3; i++)
+ {
+ current_client->ps.pmove.origin[i] = ent->s.origin[i] * 8.0;
+ current_client->ps.pmove.velocity[i] = ent->velocity[i] * 8.0;
+ }
+
+ /* If the end of unit layout is displayed, don't give
+ the player any normal movement attributes */
+ if (level.intermissiontime)
+ {
+ current_client->ps.blend[3] = 0;
+ current_client->ps.fov = 90;
+ G_SetStats(ent);
+ return;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, up);
+
+ /* burn from lava, etc */
+ P_WorldEffects();
+
+ /* set model angles from view angles so other things in
+ the world can tell which direction you are looking */
+ if (ent->client->v_angle[PITCH] > 180)
+ {
+ ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH]) / 3;
+ }
+ else
+ {
+ ent->s.angles[PITCH] = ent->client->v_angle[PITCH] / 3;
+ }
+
+ ent->s.angles[YAW] = ent->client->v_angle[YAW];
+ ent->s.angles[ROLL] = 0;
+ ent->s.angles[ROLL] = SV_CalcRoll(ent->s.angles, ent->velocity) * 4;
+
+ /* calculate speed and cycle to be used for
+ all cyclic walking effects */
+ xyspeed = sqrt(ent->velocity[0] * ent->velocity[0] +
+ ent->velocity[1] * ent->velocity[1]);
+
+ if (xyspeed < 5)
+ {
+ bobmove = 0;
+ current_client->bobtime = 0; /* start at beginning of cycle again */
+ }
+ else if (ent->groundentity)
+ {
+ /* so bobbing only cycles when on ground */
+ if (xyspeed > 210)
+ {
+ bobmove = 0.25;
+ }
+ else if (xyspeed > 100)
+ {
+ bobmove = 0.125;
+ }
+ else
+ {
+ bobmove = 0.0625;
+ }
+ }
+
+ bobtime = (current_client->bobtime += bobmove);
+
+ if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ bobtime *= 4;
+ }
+
+ bobcycle = (int)bobtime;
+ bobfracsin = fabs(sin(bobtime * M_PI));
+
+ /* detect hitting the floor */
+ P_FallingDamage(ent);
+
+ /* apply all the damage taken this frame */
+ P_DamageFeedback(ent);
+
+ /* determine the view offsets */
+ SV_CalcViewOffset(ent);
+
+ /* determine the gun offsets */
+ SV_CalcGunOffset(ent);
+
+ /* determine the full screen color blend
+ must be after viewoffset, so eye contents
+ can be accurately determined */
+ SV_CalcBlend(ent);
+
+ /* chase cam stuff */
+ if (ent->client->resp.spectator)
+ {
+ G_SetSpectatorStats(ent);
+ }
+ else
+ {
+ G_SetStats(ent);
+ }
+
+ G_CheckChaseStats(ent);
+ G_SetClientEvent(ent);
+ G_SetClientEffects(ent);
+ G_SetClientSound(ent);
+ G_SetClientFrame(ent);
+
+ VectorCopy(ent->velocity, ent->client->oldvelocity);
+ VectorCopy(ent->client->ps.viewangles, ent->client->oldviewangles);
+
+ /* clear weapon kicks */
+ VectorClear(ent->client->kick_origin);
+ VectorClear(ent->client->kick_angles);
+
+ if (!(level.framenum & 31))
+ {
+ /* if the scoreboard is up, update it */
+ if (ent->client->showscores)
+ {
+ DeathmatchScoreboardMessage(ent, ent->enemy);
+ gi.unicast(ent, false);
+ }
+
+ /* if the help computer is up, update it */
+ if (ent->client->showhelp)
+ {
+ ent->client->pers.helpchanged = 0;
+ HelpComputerMessage(ent);
+ gi.unicast(ent, false);
+ }
+ }
+
+ /* if the inventory is up, update it */
+ if (ent->client->showinventory)
+ {
+ InventoryMessage(ent);
+ gi.unicast(ent, false);
+ }
+}
diff --git a/xatrix/src/player/weapon.c b/xatrix/src/player/weapon.c
new file mode 100644
index 0000000..d2e398d
--- /dev/null
+++ b/xatrix/src/player/weapon.c
@@ -0,0 +1,2426 @@
+/* =======================================================================
+ *
+ * Player weapons.
+ *
+ * =======================================================================
+ */
+
+#include "../header/local.h"
+#include "../monster/misc/player.h"
+#include <limits.h>
+
+#define PLAYER_NOISE_SELF 0
+#define PLAYER_NOISE_IMPACT 1
+
+#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1)
+#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1)
+#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1)
+
+#define GRENADE_TIMER 3.0
+#define GRENADE_MINSPEED 400
+#define GRENADE_MAXSPEED 800
+
+#define TRAP_TIMER 5.0
+#define TRAP_MINSPEED 300
+#define TRAP_MAXSPEED 700
+
+static qboolean is_quad;
+static qboolean is_quadfire;
+static byte is_silenced;
+
+void weapon_grenade_fire(edict_t *ent, qboolean held);
+void weapon_trap_fire(edict_t *ent, qboolean held);
+
+void
+P_ProjectSource(edict_t *ent, vec3_t distance,
+ vec3_t forward, vec3_t right, vec3_t result)
+{
+ gclient_t *client = ent->client;
+ float *point = ent->s.origin;
+ vec3_t _distance;
+
+ if (!client)
+ {
+ return;
+ }
+
+ VectorCopy(distance, _distance);
+
+ if (client->pers.hand == LEFT_HANDED)
+ {
+ _distance[1] *= -1;
+ }
+ else if (client->pers.hand == CENTER_HANDED)
+ {
+ _distance[1] = 0;
+ }
+
+ G_ProjectSource(point, _distance, forward, right, result);
+
+ // Berserker: fix - now the projectile hits exactly where the scope is pointing.
+ if (aimfix->value)
+ {
+ vec3_t start, end;
+ VectorSet(start, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] + ent->viewheight);
+ VectorMA(start, 8192, forward, end);
+
+ trace_t tr = gi.trace(start, NULL, NULL, end, ent, MASK_SHOT);
+ if (tr.fraction < 1)
+ {
+ VectorSubtract(tr.endpos, result, forward);
+ VectorNormalize(forward);
+ }
+ }
+}
+
+/*
+ * Each player can have two noise objects associated with it:
+ * a personal noise (jumping, pain, weapon firing), and a weapon
+ * target noise (bullet wall impacts)
+ *
+ * Monsters that don't directly see the player can move
+ * to a noise in hopes of seeing the player from there.
+ */
+static edict_t *
+PlayerNoise_Spawn(edict_t *who, int type)
+{
+ edict_t *noise;
+
+ if (!who)
+ {
+ return NULL;
+ }
+
+ noise = G_SpawnOptional();
+ if (!noise)
+ {
+ return NULL;
+ }
+
+ noise->classname = "player_noise";
+ noise->spawnflags = type;
+ VectorSet (noise->mins, -8, -8, -8);
+ VectorSet (noise->maxs, 8, 8, 8);
+ noise->owner = who;
+ noise->svflags = SVF_NOCLIENT;
+
+ return noise;
+}
+
+static void
+PlayerNoise_Verify(edict_t *who)
+{
+ edict_t *e;
+ edict_t *n1;
+ edict_t *n2;
+
+ if (!who)
+ {
+ return;
+ }
+
+ n1 = who->mynoise;
+ n2 = who->mynoise2;
+
+ if (n1 && !n1->inuse)
+ {
+ n1 = NULL;
+ }
+
+ if (n2 && !n2->inuse)
+ {
+ n2 = NULL;
+ }
+
+ if (n1 && n2)
+ {
+ return;
+ }
+
+ for (e = g_edicts + 1 + game.maxclients; e < &g_edicts[globals.num_edicts]; e++)
+ {
+ if (!e->inuse || strcmp(e->classname, "player_noise") != 0)
+ {
+ continue;
+ }
+
+ if (e->owner && e->owner != who)
+ {
+ continue;
+ }
+
+ e->owner = who;
+
+ if (!n2 && (e->spawnflags == PLAYER_NOISE_IMPACT || n1))
+ {
+ n2 = e;
+ }
+ else
+ {
+ n1 = e;
+ }
+
+ if (n1 && n2)
+ {
+ break;
+ }
+ }
+
+ if (!n1)
+ {
+ n1 = PlayerNoise_Spawn(who, PLAYER_NOISE_SELF);
+ }
+
+ if (!n2)
+ {
+ n2 = PlayerNoise_Spawn(who, PLAYER_NOISE_IMPACT);
+ }
+
+ who->mynoise = n1;
+ who->mynoise2 = n2;
+}
+
+void
+PlayerNoise(edict_t *who, vec3_t where, int type)
+{
+ edict_t *noise;
+
+ if (!who || !who->client)
+ {
+ return;
+ }
+
+ if (type == PNOISE_WEAPON)
+ {
+ if (who->client->silencer_shots)
+ {
+ who->client->silencer_shots--;
+ return;
+ }
+ }
+
+ if (deathmatch->value)
+ {
+ return;
+ }
+
+ if (who->flags & FL_NOTARGET)
+ {
+ return;
+ }
+
+ PlayerNoise_Verify(who);
+
+ if ((type == PNOISE_SELF) || (type == PNOISE_WEAPON))
+ {
+ if (level.framenum <= (level.sound_entity_framenum + 3))
+ {
+ return;
+ }
+
+ if (!who->mynoise)
+ {
+ return;
+ }
+
+ noise = who->mynoise;
+ level.sound_entity = noise;
+ level.sound_entity_framenum = level.framenum;
+ }
+ else
+ {
+ if (level.framenum <= (level.sound2_entity_framenum + 3))
+ {
+ return;
+ }
+
+ if (!who->mynoise2)
+ {
+ return;
+ }
+
+ noise = who->mynoise2;
+ level.sound2_entity = noise;
+ level.sound2_entity_framenum = level.framenum;
+ }
+
+ VectorCopy(where, noise->s.origin);
+ VectorSubtract(where, noise->maxs, noise->absmin);
+ VectorAdd(where, noise->maxs, noise->absmax);
+ noise->last_sound_time = level.time;
+ gi.linkentity(noise);
+}
+
+qboolean
+Pickup_Weapon(edict_t *ent, edict_t *other)
+{
+ int index;
+ gitem_t *ammo;
+
+ if (!ent || !other)
+ {
+ return false;
+ }
+
+ index = ITEM_INDEX(ent->item);
+
+ if ((((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) &&
+ other->client->pers.inventory[index])
+ {
+ if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) &&
+ (!coop_pickup_weapons->value || (ent->flags & FL_COOP_TAKEN)))
+ {
+ return false; /* leave the weapon for others to pickup */
+ }
+ }
+
+ other->client->pers.inventory[index]++;
+
+ if (!(ent->spawnflags & DROPPED_ITEM))
+ {
+ /* give them some ammo with it */
+ ammo = FindItem(ent->item->ammo);
+
+ /* Don't get infinite ammo with trap */
+ if (((int)dmflags->value & DF_INFINITE_AMMO) &&
+ Q_stricmp(ent->item->pickup_name, "ammo_trap"))
+ {
+ Add_Ammo(other, ammo, 1000);
+ }
+ else
+ {
+ Add_Ammo(other, ammo, ammo->quantity);
+ }
+
+ if (!(ent->spawnflags & DROPPED_PLAYER_ITEM))
+ {
+ if (deathmatch->value)
+ {
+ if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+ {
+ ent->flags |= FL_RESPAWN;
+ }
+ else
+ {
+ SetRespawn(ent, 30);
+ }
+ }
+
+ if (coop->value)
+ {
+ ent->flags |= FL_RESPAWN;
+ ent->flags |= FL_COOP_TAKEN;
+ }
+ }
+ }
+
+ if ((other->client->pers.weapon != ent->item) &&
+ (other->client->pers.inventory[index] == 1) &&
+ (!deathmatch->value || (other->client->pers.weapon == FindItem("blaster"))))
+ {
+ other->client->newweapon = ent->item;
+ }
+
+ return true;
+}
+
+/*
+ * The old weapon has been dropped all the
+ * way, so make the new one current
+ */
+void
+ChangeWeapon(edict_t *ent)
+{
+ int i;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->grenade_time)
+ {
+ ent->client->grenade_time = level.time;
+ ent->client->weapon_sound = 0;
+ weapon_grenade_fire(ent, false);
+ ent->client->grenade_time = 0;
+ }
+
+ ent->client->pers.lastweapon = ent->client->pers.weapon;
+ ent->client->pers.weapon = ent->client->newweapon;
+ ent->client->newweapon = NULL;
+ ent->client->machinegun_shots = 0;
+
+ /* set visible model */
+ if (ent->s.modelindex == 255)
+ {
+ if (ent->client->pers.weapon)
+ {
+ i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8);
+ }
+ else
+ {
+ i = 0;
+ }
+
+ ent->s.skinnum = (ent - g_edicts - 1) | i;
+ }
+
+ if (ent->client->pers.weapon && ent->client->pers.weapon->ammo)
+ {
+ ent->client->ammo_index =
+ ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo));
+ }
+ else
+ {
+ ent->client->ammo_index = 0;
+ }
+
+ if (!ent->client->pers.weapon)
+ {
+ /* dead */
+ ent->client->ps.gunindex = 0;
+ return;
+ }
+
+ ent->client->weaponstate = WEAPON_ACTIVATING;
+ ent->client->ps.gunframe = 0;
+ ent->client->ps.gunindex = gi.modelindex(
+ ent->client->pers.weapon->view_model);
+
+ ent->client->anim_priority = ANIM_PAIN;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crpain1;
+ ent->client->anim_end = FRAME_crpain4;
+ }
+ else
+ {
+ ent->s.frame = FRAME_pain301;
+ ent->client->anim_end = FRAME_pain304;
+ }
+}
+
+void
+NoAmmoWeaponChange(edict_t *ent)
+{
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))])
+ {
+ ent->client->newweapon = FindItem("railgun");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] > 1 &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("ionripper"))])
+ {
+ ent->client->newweapon = FindItem("ionripper");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))])
+ {
+ ent->client->newweapon = FindItem("hyperblaster");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))])
+ {
+ ent->client->newweapon = FindItem("chaingun");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))])
+ {
+ ent->client->newweapon = FindItem("machinegun");
+ return;
+ }
+
+ if ((ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1) &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))])
+ {
+ ent->client->newweapon = FindItem("super shotgun");
+ return;
+ }
+
+ if (ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] &&
+ ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))])
+ {
+ ent->client->newweapon = FindItem("shotgun");
+ return;
+ }
+
+ ent->client->newweapon = FindItem("blaster");
+}
+
+/*
+ * Called by ClientBeginServerFrame and ClientThink
+ */
+void
+Think_Weapon(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ /* if just died, put the weapon away */
+ if (ent->health < 1)
+ {
+ ent->client->newweapon = NULL;
+ ChangeWeapon(ent);
+ }
+
+ /* call active weapon think routine */
+ if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink)
+ {
+ is_quad = (ent->client->quad_framenum > level.framenum);
+ is_quadfire = (ent->client->quadfire_framenum > level.framenum);
+
+ if (ent->client->silencer_shots)
+ {
+ is_silenced = MZ_SILENCED;
+ }
+ else
+ {
+ is_silenced = 0;
+ }
+
+ ent->client->pers.weapon->weaponthink(ent);
+ }
+}
+
+/*
+ * Client (player) animation for changing weapon
+ */
+static void
+Change_Weap_Animation(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->anim_priority = ANIM_REVERSE;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crpain4 + 1;
+ ent->client->anim_end = FRAME_crpain1;
+ }
+ else
+ {
+ ent->s.frame = FRAME_pain304 + 1;
+ ent->client->anim_end = FRAME_pain301;
+ }
+}
+
+/*
+ * Make the weapon ready if there is ammo
+ */
+void
+Use_Weapon(edict_t *ent, gitem_t *item)
+{
+ int ammo_index;
+ gitem_t *ammo_item;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ /* see if we're already using it */
+ if (item == ent->client->pers.weapon)
+ {
+ return;
+ }
+
+ if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO))
+ {
+ ammo_item = FindItem(item->ammo);
+ ammo_index = ITEM_INDEX(ammo_item);
+
+ if (!ent->client->pers.inventory[ammo_index])
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No %s for %s.\n",
+ ammo_item->pickup_name, item->pickup_name);
+ return;
+ }
+
+ if (ent->client->pers.inventory[ammo_index] < item->quantity)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Not enough %s for %s.\n",
+ ammo_item->pickup_name, item->pickup_name);
+ return;
+ }
+ }
+
+ /* change to this weapon when down */
+ ent->client->newweapon = item;
+}
+
+void
+Use_Weapon2(edict_t *ent, gitem_t *item)
+{
+ int ammo_index;
+ gitem_t *ammo_item;
+ gitem_t *nextitem;
+ int index;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if (strcmp(item->pickup_name, "HyperBlaster") == 0)
+ {
+ if (item == ent->client->pers.weapon)
+ {
+ item = FindItem("Ionripper");
+ index = ITEM_INDEX(item);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ item = FindItem("HyperBlaster");
+ }
+ }
+ }
+
+ else if (strcmp(item->pickup_name, "Railgun") == 0)
+ {
+ ammo_item = FindItem(item->ammo);
+ ammo_index = ITEM_INDEX(ammo_item);
+
+ if (!ent->client->pers.inventory[ammo_index])
+ {
+ nextitem = FindItem("Phalanx");
+ ammo_item = FindItem(nextitem->ammo);
+ ammo_index = ITEM_INDEX(ammo_item);
+
+ if (ent->client->pers.inventory[ammo_index])
+ {
+ item = FindItem("Phalanx");
+ index = ITEM_INDEX(item);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ item = FindItem("Railgun");
+ }
+ }
+ }
+ else if (item == ent->client->pers.weapon)
+ {
+ item = FindItem("Phalanx");
+ index = ITEM_INDEX(item);
+
+ if (!ent->client->pers.inventory[index])
+ {
+ item = FindItem("Railgun");
+ }
+ }
+ }
+
+ /* see if we're already using it */
+ if (item == ent->client->pers.weapon)
+ {
+ return;
+ }
+
+ if (item->ammo)
+ {
+ ammo_item = FindItem(item->ammo);
+ ammo_index = ITEM_INDEX(ammo_item);
+
+ if (!ent->client->pers.inventory[ammo_index] && !g_select_empty->value)
+ {
+ gi.cprintf(ent, PRINT_HIGH, "No %s for %s.\n",
+ ammo_item->pickup_name, item->pickup_name);
+ return;
+ }
+ }
+
+ /* change to this weapon when down */
+ ent->client->newweapon = item;
+}
+
+void
+Drop_Weapon(edict_t *ent, gitem_t *item)
+{
+ int index;
+
+ if (!ent || !item)
+ {
+ return;
+ }
+
+ if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+ {
+ return;
+ }
+
+ index = ITEM_INDEX(item);
+
+ /* see if we're already using it */
+ if (((item == ent->client->pers.weapon) ||
+ (item == ent->client->newweapon)) &&
+ (ent->client->pers.inventory[index] == 1))
+ {
+ gi.cprintf(ent, PRINT_HIGH, "Can't drop current weapon\n");
+ return;
+ }
+
+ Drop_Item(ent, item);
+ ent->client->pers.inventory[index]--;
+}
+
+/*
+ * A generic function to handle the basics of weapon thinking
+ */
+void
+Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST,
+ int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames,
+ int *fire_frames, void (*fire)(edict_t *ent))
+{
+ int n;
+ const unsigned short int change_speed = (g_swap_speed->value > 1)?
+ (g_swap_speed->value < USHRT_MAX)? (unsigned short int)g_swap_speed->value : 1
+ : 1;
+
+ if (!ent || !fire_frames || !fire)
+ {
+ return;
+ }
+
+ if (ent->deadflag || (ent->s.modelindex != 255)) /* VWep animations screw up corpses */
+ {
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_DROPPING)
+ {
+ if (ent->client->ps.gunframe >= FRAME_DEACTIVATE_LAST - change_speed + 1)
+ {
+ ChangeWeapon(ent);
+ return;
+ }
+ else if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) >= (4 * change_speed) )
+ {
+ unsigned short int remainder = FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe;
+ // "if (remainder == 4)" at change_speed == 1
+ if ( ( remainder <= (4 * change_speed) )
+ && ( remainder > (3 * change_speed) ) )
+ {
+ Change_Weap_Animation(ent);
+ }
+ }
+
+ ent->client->ps.gunframe += change_speed;
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_ACTIVATING)
+ {
+ if (ent->client->ps.gunframe >= FRAME_ACTIVATE_LAST - change_speed + 1)
+ {
+ ent->client->weaponstate = WEAPON_READY;
+ ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+ return;
+ }
+
+ ent->client->ps.gunframe += change_speed;
+ return;
+ }
+
+ if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
+ {
+ ent->client->weaponstate = WEAPON_DROPPING;
+ ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
+
+ if ( (FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < (4 * change_speed) )
+ {
+ Change_Weap_Animation(ent);
+ }
+
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_READY)
+ {
+ if (((ent->client->latched_buttons |
+ ent->client->buttons) & BUTTON_ATTACK))
+ {
+ ent->client->latched_buttons &= ~BUTTON_ATTACK;
+
+ if ((!ent->client->ammo_index) ||
+ (ent->client->pers.inventory[ent->client->ammo_index] >=
+ ent->client->pers.weapon->quantity))
+ {
+ ent->client->ps.gunframe = FRAME_FIRE_FIRST;
+ ent->client->weaponstate = WEAPON_FIRING;
+
+ /* start the animation */
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - 1;
+ ent->client->anim_end = FRAME_attack8;
+ }
+ }
+ else
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE,
+ gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ }
+ }
+ else
+ {
+ if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
+ {
+ ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+ return;
+ }
+
+ if (pause_frames)
+ {
+ for (n = 0; pause_frames[n]; n++)
+ {
+ if (ent->client->ps.gunframe == pause_frames[n])
+ {
+ if (rand() & 15)
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ ent->client->ps.gunframe++;
+ return;
+ }
+ }
+
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ for (n = 0; fire_frames[n]; n++)
+ {
+ if (ent->client->ps.gunframe == fire_frames[n])
+ {
+ if (ent->client->quad_framenum > level.framenum)
+ {
+ gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ fire(ent);
+ break;
+ }
+ }
+
+ if (!fire_frames[n])
+ {
+ ent->client->ps.gunframe++;
+ }
+
+ if (ent->client->ps.gunframe == FRAME_IDLE_FIRST + 1)
+ {
+ ent->client->weaponstate = WEAPON_READY;
+ }
+ }
+}
+
+void
+weapon_grenade_fire(edict_t *ent, qboolean held)
+{
+ vec3_t offset;
+ vec3_t forward, right;
+ vec3_t start;
+ int damage = 125;
+ float timer;
+ int speed;
+ float radius;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ radius = damage + 40;
+
+ if (is_quad)
+ {
+ damage *= 4;
+
+ gi.sound(ent, CHAN_ITEM, gi.soundindex(
+ "items/damage3.wav"), 1, ATTN_NORM, 0);
+ }
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ timer = ent->client->grenade_time - level.time;
+ speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) *
+ ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER);
+ fire_grenade2(ent, start, forward, damage, speed, timer, radius, held);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+
+ ent->client->grenade_time = level.time + 1.0;
+
+ if (ent->deadflag || (ent->s.modelindex != 255)) /* VWep animations screw up corpses */
+ {
+ return;
+ }
+
+ if (ent->health <= 0)
+ {
+ return;
+ }
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->client->anim_priority = ANIM_ATTACK;
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak3;
+ }
+ else
+ {
+ ent->client->anim_priority = ANIM_REVERSE;
+ ent->s.frame = FRAME_wave08;
+ ent->client->anim_end = FRAME_wave01;
+ }
+}
+
+void
+Weapon_Grenade(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+ {
+ ChangeWeapon(ent);
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_ACTIVATING)
+ {
+ ent->client->weaponstate = WEAPON_READY;
+ ent->client->ps.gunframe = 16;
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_READY)
+ {
+ if (((ent->client->latched_buttons |
+ ent->client->buttons) & BUTTON_ATTACK))
+ {
+ ent->client->latched_buttons &= ~BUTTON_ATTACK;
+
+ if (ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ ent->client->ps.gunframe = 1;
+ ent->client->weaponstate = WEAPON_FIRING;
+ ent->client->grenade_time = 0;
+ }
+ else
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),
+ 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ }
+
+ return;
+ }
+
+ if ((ent->client->ps.gunframe == 29) ||
+ (ent->client->ps.gunframe == 34) ||
+ (ent->client->ps.gunframe == 39) ||
+ (ent->client->ps.gunframe == 48))
+ {
+ if (rand() & 15)
+ {
+ return;
+ }
+ }
+
+ if (++ent->client->ps.gunframe > 48)
+ {
+ ent->client->ps.gunframe = 16;
+ }
+
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ if (ent->client->ps.gunframe == 5)
+ {
+ gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ if (ent->client->ps.gunframe == 11)
+ {
+ if (!ent->client->grenade_time)
+ {
+ ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+ ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
+ }
+
+ /* they waited too long, detonate it in their hand */
+ if (!ent->client->grenade_blew_up &&
+ (level.time >= ent->client->grenade_time))
+ {
+ ent->client->weapon_sound = 0;
+ weapon_grenade_fire(ent, true);
+ ent->client->grenade_blew_up = true;
+ }
+
+ if (ent->client->buttons & BUTTON_ATTACK)
+ {
+ return;
+ }
+
+ if (ent->client->grenade_blew_up)
+ {
+ if (level.time >= ent->client->grenade_time)
+ {
+ ent->client->ps.gunframe = 15;
+ ent->client->grenade_blew_up = false;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+ if (ent->client->ps.gunframe == 12)
+ {
+ ent->client->weapon_sound = 0;
+ weapon_grenade_fire(ent, false);
+ }
+
+ if ((ent->client->ps.gunframe == 15) &&
+ (level.time < ent->client->grenade_time))
+ {
+ return;
+ }
+
+ ent->client->ps.gunframe++;
+
+ if (ent->client->ps.gunframe == 16)
+ {
+ ent->client->grenade_time = 0;
+ ent->client->weaponstate = WEAPON_READY;
+ }
+ }
+}
+
+/* GRENADE LAUNCHER */
+
+void
+weapon_grenadelauncher_fire(edict_t *ent)
+{
+ vec3_t offset;
+ vec3_t forward, right;
+ vec3_t start;
+ int damage = 120;
+ float radius;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ radius = damage + 40;
+
+ if (is_quad)
+ {
+ damage *= 4;
+ }
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -1;
+
+ fire_grenade(ent, start, forward, damage, 600, 2.5, radius);
+
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_GRENADE | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_GrenadeLauncher(edict_t *ent)
+{
+ static int pause_frames[] = {34, 51, 59, 0};
+ static int fire_frames[] = {6, 0};
+
+ Weapon_Generic(ent, 5, 16, 59, 64, pause_frames,
+ fire_frames, weapon_grenadelauncher_fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 5, 16, 59, 64, pause_frames,
+ fire_frames, weapon_grenadelauncher_fire);
+ }
+}
+
+/* ROCKET */
+
+void
+Weapon_RocketLauncher_Fire(edict_t *ent)
+{
+ vec3_t offset, start;
+ vec3_t forward, right;
+ int damage;
+ float damage_radius;
+ int radius_damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ damage = 100 + (int)(random() * 20.0);
+ radius_damage = 120;
+ damage_radius = 120;
+
+ if (is_quad)
+ {
+ damage *= 4;
+ radius_damage *= 4;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -1;
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_rocket(ent, start, forward, damage, 650, damage_radius, radius_damage);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_ROCKET | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_RocketLauncher(edict_t *ent)
+{
+ static int pause_frames[] = {25, 33, 42, 50, 0};
+ static int fire_frames[] = {5, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 4, 12, 50, 54, pause_frames,
+ fire_frames, Weapon_RocketLauncher_Fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 4, 12, 50, 54, pause_frames,
+ fire_frames, Weapon_RocketLauncher_Fire);
+ }
+}
+
+/* BLASTER / HYPERBLASTER */
+
+void
+Blaster_Fire(edict_t *ent, vec3_t g_offset, int damage,
+ qboolean hyper, int effect)
+{
+ vec3_t forward, right;
+ vec3_t start;
+ vec3_t offset;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= 4;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ VectorSet(offset, 24, 8, ent->viewheight - 8);
+ VectorAdd(offset, g_offset, offset);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -1;
+
+ fire_blaster(ent, start, forward, damage, 1000, effect, hyper);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+
+ if (hyper)
+ {
+ gi.WriteByte(MZ_HYPERBLASTER | is_silenced);
+ }
+ else
+ {
+ gi.WriteByte(MZ_BLASTER | is_silenced);
+ }
+
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+}
+
+void
+Weapon_Blaster_Fire(edict_t *ent)
+{
+ int damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 15;
+ }
+ else
+ {
+ damage = 10;
+ }
+
+ Blaster_Fire(ent, vec3_origin, damage, false, EF_BLASTER);
+ ent->client->ps.gunframe++;
+}
+
+void
+Weapon_Blaster(edict_t *ent)
+{
+ static int pause_frames[] = {19, 32, 0};
+ static int fire_frames[] = {5, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 4, 8, 52, 55, pause_frames,
+ fire_frames, Weapon_Blaster_Fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 4, 8, 52, 55, pause_frames,
+ fire_frames, Weapon_Blaster_Fire);
+ }
+}
+
+void
+Weapon_HyperBlaster_Fire(edict_t *ent)
+{
+ float rotation;
+ vec3_t offset;
+ int effect;
+ int damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav");
+
+ if (!(ent->client->buttons & BUTTON_ATTACK))
+ {
+ ent->client->ps.gunframe++;
+ }
+ else
+ {
+ if (!ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),
+ 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ }
+ else
+ {
+ rotation = (ent->client->ps.gunframe - 5) * 2 * M_PI / 6;
+ offset[0] = -4 * sin(rotation);
+ offset[1] = 0;
+ offset[2] = 4 * cos(rotation);
+
+ if ((ent->client->ps.gunframe == 6) ||
+ (ent->client->ps.gunframe == 9))
+ {
+ effect = EF_HYPERBLASTER;
+ }
+ else
+ {
+ effect = 0;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 15;
+ }
+ else
+ {
+ damage = 20;
+ }
+
+ Blaster_Fire(ent, offset, damage, true, effect);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - 1;
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - 1;
+ ent->client->anim_end = FRAME_attack8;
+ }
+ }
+
+ ent->client->ps.gunframe++;
+
+ if ((ent->client->ps.gunframe == 12) &&
+ ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ ent->client->ps.gunframe = 6;
+ }
+ }
+
+ if (ent->client->ps.gunframe == 12)
+ {
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"),
+ 1, ATTN_NORM, 0);
+ ent->client->weapon_sound = 0;
+ }
+}
+
+void
+Weapon_HyperBlaster(edict_t *ent)
+{
+ static int pause_frames[] = {0};
+ static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 5, 20, 49, 53, pause_frames,
+ fire_frames, Weapon_HyperBlaster_Fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 5, 20, 49, 53, pause_frames,
+ fire_frames, Weapon_HyperBlaster_Fire);
+ }
+}
+
+/* MACHINEGUN / CHAINGUN */
+
+void
+Machinegun_Fire(edict_t *ent)
+{
+ int i;
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t angles;
+ int damage = 8;
+ int kick = 2;
+ vec3_t offset;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (!(ent->client->buttons & BUTTON_ATTACK))
+ {
+ ent->client->machinegun_shots = 0;
+ ent->client->ps.gunframe++;
+ return;
+ }
+
+ if (ent->client->ps.gunframe == 5)
+ {
+ ent->client->ps.gunframe = 4;
+ }
+ else
+ {
+ ent->client->ps.gunframe = 5;
+ }
+
+ if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
+ {
+ ent->client->ps.gunframe = 6;
+
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),
+ 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= 4;
+ kick *= 4;
+ }
+
+ for (i = 1; i < 3; i++)
+ {
+ ent->client->kick_origin[i] = crandom() * 0.35;
+ ent->client->kick_angles[i] = crandom() * 0.7;
+ }
+
+ ent->client->kick_origin[0] = crandom() * 0.35;
+ ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5;
+
+ /* raise the gun as it is firing */
+ if (!(deathmatch->value || g_machinegun_norecoil->value))
+ {
+ ent->client->machinegun_shots++;
+
+ if (ent->client->machinegun_shots > 9)
+ {
+ ent->client->machinegun_shots = 9;
+ }
+ }
+
+ /* get start / end positions */
+ VectorAdd(ent->client->v_angle, ent->client->kick_angles, angles);
+ AngleVectors(angles, forward, right, NULL);
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_bullet(ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
+
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_MACHINEGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - (int)(random() + 0.25);
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - (int)(random() + 0.25);
+ ent->client->anim_end = FRAME_attack8;
+ }
+}
+
+void
+Weapon_Machinegun(edict_t *ent)
+{
+ static int pause_frames[] = {23, 45, 0};
+ static int fire_frames[] = {4, 5, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 3, 5, 45, 49, pause_frames,
+ fire_frames, Machinegun_Fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 3, 5, 45, 49, pause_frames,
+ fire_frames, Machinegun_Fire);
+ }
+}
+
+void
+Chaingun_Fire(edict_t *ent)
+{
+ int i;
+ int shots;
+ vec3_t start;
+ vec3_t forward, right, up;
+ float r, u;
+ vec3_t offset;
+ int damage;
+ int kick = 2;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 6;
+ }
+ else
+ {
+ damage = 8;
+ }
+
+ if (ent->client->ps.gunframe == 5)
+ {
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"),
+ 1, ATTN_IDLE, 0);
+ }
+
+ if ((ent->client->ps.gunframe == 14) &&
+ !(ent->client->buttons & BUTTON_ATTACK))
+ {
+ ent->client->ps.gunframe = 32;
+ ent->client->weapon_sound = 0;
+ return;
+ }
+ else if ((ent->client->ps.gunframe == 21) &&
+ (ent->client->buttons & BUTTON_ATTACK) &&
+ ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ ent->client->ps.gunframe = 15;
+ }
+ else
+ {
+ ent->client->ps.gunframe++;
+ }
+
+ if (ent->client->ps.gunframe == 22)
+ {
+ ent->client->weapon_sound = 0;
+ gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"),
+ 1, ATTN_IDLE, 0);
+ }
+ else
+ {
+ ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav");
+ }
+
+ ent->client->anim_priority = ANIM_ATTACK;
+
+ if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+ {
+ ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1);
+ ent->client->anim_end = FRAME_crattak9;
+ }
+ else
+ {
+ ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1);
+ ent->client->anim_end = FRAME_attack8;
+ }
+
+ if (ent->client->ps.gunframe <= 9)
+ {
+ shots = 1;
+ }
+ else if (ent->client->ps.gunframe <= 14)
+ {
+ if (ent->client->buttons & BUTTON_ATTACK)
+ {
+ shots = 2;
+ }
+ else
+ {
+ shots = 1;
+ }
+ }
+ else
+ {
+ shots = 3;
+ }
+
+ if (ent->client->pers.inventory[ent->client->ammo_index] < shots)
+ {
+ shots = ent->client->pers.inventory[ent->client->ammo_index];
+ }
+
+ if (!shots)
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),
+ 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= 4;
+ kick *= 4;
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ ent->client->kick_origin[i] = crandom() * 0.35;
+ ent->client->kick_angles[i] = crandom() * 0.7;
+ }
+
+ for (i = 0; i < shots; i++)
+ {
+ /* get start / end positions */
+ AngleVectors(ent->client->v_angle, forward, right, up);
+ r = 7 + crandom() * 4;
+ u = crandom() * 4;
+ VectorSet(offset, 0, r, u + ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward,
+ right, start);
+
+ fire_bullet(ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD,
+ DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
+ }
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte((MZ_CHAINGUN1 + shots - 1) | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= shots;
+ }
+}
+
+void
+Weapon_Chaingun(edict_t *ent)
+{
+ static int pause_frames[] = {38, 43, 51, 61, 0};
+ static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 4, 31, 61, 64, pause_frames,
+ fire_frames, Chaingun_Fire);
+ }
+}
+
+/* SHOTGUN / SUPERSHOTGUN */
+
+void
+weapon_shotgun_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t offset;
+ int damage = 4;
+ int kick = 8;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (ent->client->ps.gunframe == 9)
+ {
+ ent->client->ps.gunframe++;
+ return;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ if (is_quad)
+ {
+ damage *= 4;
+ kick *= 4;
+ }
+
+ fire_shotgun(ent, start, forward, damage, kick, 500,
+ 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_SHOTGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_Shotgun(edict_t *ent)
+{
+ static int pause_frames[] = {22, 28, 34, 0};
+ static int fire_frames[] = {8, 9, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 7, 18, 36, 39, pause_frames,
+ fire_frames, weapon_shotgun_fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 7, 18, 36, 39, pause_frames,
+ fire_frames, weapon_shotgun_fire);
+ }
+}
+
+void
+weapon_supershotgun_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t offset;
+ vec3_t v;
+ int damage = 6;
+ int kick = 12;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ if (is_quad)
+ {
+ damage *= 4;
+ kick *= 4;
+ }
+
+ v[PITCH] = ent->client->v_angle[PITCH];
+ v[YAW] = ent->client->v_angle[YAW] - 5;
+ v[ROLL] = ent->client->v_angle[ROLL];
+ AngleVectors(v, forward, NULL, NULL);
+
+ if (aimfix->value)
+ {
+ AngleVectors(v, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ }
+
+ fire_shotgun(ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD,
+ DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN);
+
+ v[YAW] = ent->client->v_angle[YAW] + 5;
+ AngleVectors(v, forward, NULL, NULL);
+
+ if (aimfix->value)
+ {
+ AngleVectors(v, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ }
+
+ fire_shotgun(ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD,
+ DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT / 2, MOD_SSHOTGUN);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_SSHOTGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= 2;
+ }
+}
+
+void
+Weapon_SuperShotgun(edict_t *ent)
+{
+ static int pause_frames[] = {29, 42, 57, 0};
+ static int fire_frames[] = {7, 0};
+
+ Weapon_Generic(ent, 6, 17, 57, 61, pause_frames,
+ fire_frames, weapon_supershotgun_fire);
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 6, 17, 57, 61, pause_frames,
+ fire_frames, weapon_supershotgun_fire);
+ }
+}
+
+/* RAILGUN */
+
+void
+weapon_railgun_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t offset;
+ int damage;
+ int kick;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* normal damage is too extreme in dm */
+ damage = 100;
+ kick = 200;
+ }
+ else
+ {
+ damage = 150;
+ kick = 250;
+ }
+
+ if (is_quad)
+ {
+ damage *= 4;
+ kick *= 4;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -3, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -3;
+
+ VectorSet(offset, 0, 7, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_rail(ent, start, forward, damage, kick);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_RAILGUN | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+}
+
+void
+Weapon_Railgun(edict_t *ent)
+{
+ static int pause_frames[] = {56, 0};
+ static int fire_frames[] = {4, 0};
+
+ Weapon_Generic(ent, 3, 18, 56, 61, pause_frames,
+ fire_frames, weapon_railgun_fire);
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 3, 18, 56, 61, pause_frames,
+ fire_frames, weapon_railgun_fire);
+ }
+}
+
+/* BFG10K */
+
+void
+weapon_bfg_fire(edict_t *ent)
+{
+ vec3_t offset, start;
+ vec3_t forward, right;
+ int damage;
+ float damage_radius = 1000;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ damage = 200;
+ }
+ else
+ {
+ damage = 500;
+ }
+
+ if (ent->client->ps.gunframe == 9)
+ {
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_BFG | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+ return;
+ }
+
+ /* cells can go down during windup (from power armor hits), so
+ check again and abort firing if we don't have enough now */
+ if (ent->client->pers.inventory[ent->client->ammo_index] < 50)
+ {
+ ent->client->ps.gunframe++;
+ return;
+ }
+
+ if (is_quad)
+ {
+ damage *= 4;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+
+ /* make a big pitch kick with an inverse fall */
+ ent->client->v_dmg_pitch = -40;
+ ent->client->v_dmg_roll = crandom() * 8;
+ ent->client->v_dmg_time = level.time + DAMAGE_TIME;
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ fire_bfg(ent, start, forward, damage, 400, damage_radius);
+
+ ent->client->ps.gunframe++;
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -= 50;
+ }
+}
+
+void
+Weapon_BFG(edict_t *ent)
+{
+ static int pause_frames[] = {39, 45, 50, 55, 0};
+ static int fire_frames[] = {9, 17, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 8, 32, 55, 58, pause_frames,
+ fire_frames, weapon_bfg_fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 8, 32, 55, 58, pause_frames,
+ fire_frames, weapon_bfg_fire);
+ }
+}
+
+/* ====================================================================== */
+
+/* RipperGun */
+
+void
+weapon_ionripper_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right;
+ vec3_t offset;
+ vec3_t tempang;
+ int damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ if (deathmatch->value)
+ {
+ /* tone down for deathmatch */
+ damage = 30;
+ }
+ else
+ {
+ damage = 50;
+ }
+
+ if (is_quad)
+ {
+ damage *= 4;
+ }
+
+ VectorCopy(ent->client->v_angle, tempang);
+ tempang[YAW] += crandom();
+
+ AngleVectors(tempang, forward, right, NULL);
+
+ VectorScale(forward, -3, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -3;
+
+ VectorSet(offset, 16, 7, ent->viewheight - 8);
+
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ fire_ionripper(ent, start, forward, damage, 500, EF_IONRIPPER);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_IONRIPPER | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ ent->client->ps.gunframe++;
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] -=
+ ent->client->pers.weapon->quantity;
+ }
+
+ if (ent->client->pers.inventory[ent->client->ammo_index] < 0)
+ {
+ ent->client->pers.inventory[ent->client->ammo_index] = 0;
+ }
+}
+
+void
+Weapon_Ionripper(edict_t *ent)
+{
+ static int pause_frames[] = {36, 0};
+ static int fire_frames[] = {5, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 4, 6, 36, 39, pause_frames,
+ fire_frames, weapon_ionripper_fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 4, 6, 36, 39, pause_frames,
+ fire_frames, weapon_ionripper_fire);
+ }
+}
+
+/* Phalanx */
+
+void
+weapon_phalanx_fire(edict_t *ent)
+{
+ vec3_t start;
+ vec3_t forward, right, up;
+ vec3_t offset;
+ vec3_t v;
+ int damage;
+ float damage_radius;
+ int radius_damage;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ damage = 70 + (int)(random() * 10.0);
+ radius_damage = 120;
+ damage_radius = 120;
+
+ if (is_quad)
+ {
+ damage *= 4;
+ radius_damage *= 4;
+ }
+
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ if (ent->client->ps.gunframe == 8)
+ {
+ v[PITCH] = ent->client->v_angle[PITCH];
+ v[YAW] = ent->client->v_angle[YAW] - 1.5;
+ v[ROLL] = ent->client->v_angle[ROLL];
+ AngleVectors(v, forward, right, up);
+
+ if (aimfix->value)
+ {
+ AngleVectors(v, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ }
+
+ radius_damage = 30;
+ damage_radius = 120;
+
+ fire_plasma(ent, start, forward, damage, 725,
+ damage_radius, radius_damage);
+
+ if (!((int)dmflags->value & DF_INFINITE_AMMO))
+ {
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ }
+ }
+ else
+ {
+ v[PITCH] = ent->client->v_angle[PITCH];
+ v[YAW] = ent->client->v_angle[YAW] + 1.5;
+ v[ROLL] = ent->client->v_angle[ROLL];
+ AngleVectors(v, forward, right, up);
+
+ if (aimfix->value)
+ {
+ AngleVectors(v, forward, right, NULL);
+
+ VectorScale(forward, -2, ent->client->kick_origin);
+ ent->client->kick_angles[0] = -2;
+
+ VectorSet(offset, 0, 8, ent->viewheight - 8);
+ P_ProjectSource(ent, offset, forward, right, start);
+ }
+
+ fire_plasma(ent, start, forward, damage, 725,
+ damage_radius, radius_damage);
+
+ /* send muzzle flash */
+ gi.WriteByte(svc_muzzleflash);
+ gi.WriteShort(ent - g_edicts);
+ gi.WriteByte(MZ_PHALANX | is_silenced);
+ gi.multicast(ent->s.origin, MULTICAST_PVS);
+
+ PlayerNoise(ent, start, PNOISE_WEAPON);
+ }
+
+ ent->client->ps.gunframe++;
+}
+
+void
+Weapon_Phalanx(edict_t *ent)
+{
+ static int pause_frames[] = {29, 42, 55, 0};
+ static int fire_frames[] = {7, 8, 0};
+
+ if (!ent)
+ {
+ return;
+ }
+
+ Weapon_Generic(ent, 5, 20, 58, 63, pause_frames,
+ fire_frames, weapon_phalanx_fire);
+
+ if (is_quadfire)
+ {
+ Weapon_Generic(ent, 5, 20, 58, 63, pause_frames,
+ fire_frames, weapon_phalanx_fire);
+ }
+}
+
+/* TRAP */
+
+void
+weapon_trap_fire(edict_t *ent, qboolean held)
+{
+ vec3_t offset;
+ vec3_t forward, right;
+ vec3_t start;
+ int damage = 125;
+ float timer;
+ int speed;
+ float radius;
+
+ if (!ent)
+ {
+ return;
+ }
+
+ radius = damage + 40;
+
+ if (is_quad)
+ {
+ damage *= 4;
+ }
+
+ VectorSet(offset, 8, 8, ent->viewheight - 8);
+ AngleVectors(ent->client->v_angle, forward, right, NULL);
+ P_ProjectSource(ent, offset, forward, right, start);
+
+ timer = ent->client->grenade_time - level.time;
+ speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) *
+ ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER);
+ fire_trap(ent, start, forward, damage, speed, timer, radius, held);
+
+ ent->client->pers.inventory[ent->client->ammo_index]--;
+ ent->client->grenade_time = level.time + 1.0;
+}
+
+void
+Weapon_Trap(edict_t *ent)
+{
+ if (!ent)
+ {
+ return;
+ }
+
+ if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+ {
+ ChangeWeapon(ent);
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_ACTIVATING)
+ {
+ ent->client->weaponstate = WEAPON_READY;
+ ent->client->ps.gunframe = 16;
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_READY)
+ {
+ if (((ent->client->latched_buttons |
+ ent->client->buttons) & BUTTON_ATTACK))
+ {
+ ent->client->latched_buttons &= ~BUTTON_ATTACK;
+
+ if (ent->client->pers.inventory[ent->client->ammo_index])
+ {
+ ent->client->ps.gunframe = 1;
+ ent->client->weaponstate = WEAPON_FIRING;
+ ent->client->grenade_time = 0;
+ }
+ else
+ {
+ if (level.time >= ent->pain_debounce_time)
+ {
+ gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"),
+ 1, ATTN_NORM, 0);
+ ent->pain_debounce_time = level.time + 1;
+ }
+
+ NoAmmoWeaponChange(ent);
+ }
+
+ return;
+ }
+
+ if ((ent->client->ps.gunframe == 29) ||
+ (ent->client->ps.gunframe == 34) ||
+ (ent->client->ps.gunframe == 39) ||
+ (ent->client->ps.gunframe == 48))
+ {
+ if (rand() & 15)
+ {
+ return;
+ }
+ }
+
+ if (++ent->client->ps.gunframe > 48)
+ {
+ ent->client->ps.gunframe = 16;
+ }
+
+ return;
+ }
+
+ if (ent->client->weaponstate == WEAPON_FIRING)
+ {
+ if (ent->client->ps.gunframe == 5)
+ {
+ gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/trapcock.wav"),
+ 1, ATTN_NORM, 0);
+ }
+
+ if (ent->client->ps.gunframe == 11)
+ {
+ if (!ent->client->grenade_time)
+ {
+ ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+ ent->client->weapon_sound = gi.soundindex("weapons/traploop.wav");
+ }
+
+ /* they waited too long, detonate it in their hand */
+ if (!ent->client->grenade_blew_up &&
+ (level.time >= ent->client->grenade_time))
+ {
+ ent->client->weapon_sound = 0;
+ weapon_trap_fire(ent, true);
+ ent->client->grenade_blew_up = true;
+ }
+
+ if (ent->client->buttons & BUTTON_ATTACK)
+ {
+ return;
+ }
+
+ if (ent->client->grenade_blew_up)
+ {
+ if (level.time >= ent->client->grenade_time)
+ {
+ ent->client->ps.gunframe = 15;
+ ent->client->grenade_blew_up = false;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+
+ if (ent->client->ps.gunframe == 12)
+ {
+ ent->client->weapon_sound = 0;
+ weapon_trap_fire(ent, false);
+
+ if (ent->client->pers.inventory[ent->client->ammo_index] == 0)
+ {
+ NoAmmoWeaponChange(ent);
+ }
+ }
+
+ if ((ent->client->ps.gunframe == 15) &&
+ (level.time < ent->client->grenade_time))
+ {
+ return;
+ }
+
+ ent->client->ps.gunframe++;
+
+ if (ent->client->ps.gunframe == 16)
+ {
+ ent->client->grenade_time = 0;
+ ent->client->weaponstate = WEAPON_READY;
+ }
+ }
+}
diff --git a/xatrix/src/savegame/savegame.c b/xatrix/src/savegame/savegame.c
new file mode 100644
index 0000000..465a46b
--- /dev/null
+++ b/xatrix/src/savegame/savegame.c
@@ -0,0 +1,1174 @@
+/*
+ * =======================================================================
+ *
+ * The savegame system.
+ *
+ * =======================================================================
+ */
+
+/*
+ * This is the Quake 2 savegame system, fixed by Yamagi
+ * based on an idea by Knightmare of kmquake2. This major
+ * rewrite of the original g_save.c is much more robust
+ * and portable since it doesn't use any function pointers.
+ *
+ * Inner workings:
+ * When the game is saved all function pointers are
+ * translated into human readable function definition strings.
+ * The same way all mmove_t pointers are translated. This
+ * human readable strings are then written into the file.
+ * At game load the human readable strings are retranslated
+ * into the actual function pointers and struct pointers. The
+ * pointers are generated at each compilation / start of the
+ * client, thus the pointers are always correct.
+ *
+ * Limitations:
+ * While savegames survive recompilations of the game source
+ * and bigger changes in the source, there are some limitation
+ * which a nearly impossible to fix without a object orientated
+ * rewrite of the game.
+ * - If functions or mmove_t structs that a referencenced
+ * inside savegames are added or removed (e.g. the files
+ * in tables/ are altered) the load functions cannot
+ * reconnect all pointers and thus not restore the game.
+ * - If the operating system is changed internal structures
+ * may change in an unrepairable way.
+ * - If the architecture is changed pointer length and
+ * other internal datastructures change in an
+ * incompatible way.
+ * - If the edict_t struct is changed, savegames
+ * will break.
+ * This is not so bad as it looks since functions and
+ * struct won't be added and edict_t won't be changed
+ * if no big, sweeping changes are done. The operating
+ * system and architecture are in the hands of the user.
+ */
+
+#include "../header/local.h"
+
+/*
+* When ever the savegame version is changed, q2 will refuse to
+* load older savegames. This should be bumped if the files
+* in tables/ are changed, otherwise strange things may happen.
+*/
+#define SAVEGAMEVER "YQ2-4"
+
+
+/*
+ * This macros are used to prohibit loading of savegames
+ * created on other systems or architectures. This will
+ * crash q2 in spectacular ways
+ */
+#ifndef YQ2OSTYPE
+#error YQ2OSTYPE should be defined by the build system
+#endif
+
+#ifndef YQ2ARCH
+#error YQ2ARCH should be defined by the build system
+#endif
+
+/*
+ * Older operating system and architecture detection
+ * macros, implemented by savegame version YQ2-1.
+ */
+#if defined(__APPLE__)
+#define YQ2OSTYPE_1 "MacOS X"
+#elif defined(__FreeBSD__)
+#define YQ2OSTYPE_1 "FreeBSD"
+#elif defined(__OpenBSD__)
+#define YQ2OSTYPE_1 "OpenBSD"
+#elif defined(__linux__)
+ #define YQ2OSTYPE_1 "Linux"
+#elif defined(_WIN32)
+ #define YQ2OSTYPE_1 "Windows"
+#else
+ #define YQ2OSTYPE_1 "Unknown"
+#endif
+
+#if defined(__i386__)
+#define YQ2ARCH_1 "i386"
+#elif defined(__x86_64__)
+#define YQ2ARCH_1 "amd64"
+#elif defined(__sparc__)
+#define YQ2ARCH_1 "sparc64"
+#elif defined(__ia64__)
+ #define YQ2ARCH_1 "ia64"
+#else
+ #define YQ2ARCH_1 "unknown"
+#endif
+
+/*
+ * Connects a human readable
+ * function signature with
+ * the corresponding pointer
+ */
+typedef struct
+{
+ char *funcStr;
+ byte *funcPtr;
+} functionList_t;
+
+/*
+ * Connects a human readable
+ * mmove_t string with the
+ * correspondig pointer
+ * */
+typedef struct
+{
+ char *mmoveStr;
+ mmove_t *mmovePtr;
+} mmoveList_t;
+
+typedef struct
+{
+ char ver[32];
+ char game[32];
+ char os[32];
+ char arch[32];
+} savegameHeader_t;
+
+
+/* ========================================================= */
+
+/*
+ * Prototypes for forward
+ * declaration for all game
+ * functions.
+ */
+#include "tables/gamefunc_decs.h"
+
+/*
+ * List with function pointer
+ * to each of the functions
+ * prototyped above.
+ */
+functionList_t functionList[] = {
+ #include "tables/gamefunc_list.h"
+};
+
+/*
+ * Prtotypes for forward
+ * declaration for all game
+ * mmove_t functions.
+ */
+#include "tables/gamemmove_decs.h"
+
+/*
+ * List with pointers to
+ * each of the mmove_t
+ * functions prototyped
+ * above.
+ */
+mmoveList_t mmoveList[] = {
+ #include "tables/gamemmove_list.h"
+};
+
+/*
+ * Fields to be saved
+ */
+field_t fields[] = {
+ #include "tables/fields.h"
+};
+
+/*
+ * Level fields to
+ * be saved
+ */
+field_t levelfields[] = {
+ #include "tables/levelfields.h"
+};
+
+/*
+ * Client fields to
+ * be saved
+ */
+field_t clientfields[] = {
+ #include "tables/clientfields.h"
+};
+
+/* ========================================================= */
+
+/*
+ * This will be called when the dll is first loaded,
+ * which only happens when a new game is started or
+ * a save game is loaded.
+ */
+void
+InitGame(void)
+{
+ gi.dprintf("Game is starting up.\n");
+ gi.dprintf("Game is %s built on %s.\n", GAMEVERSION, __DATE__);
+
+ gun_x = gi.cvar ("gun_x", "0", 0);
+ gun_y = gi.cvar ("gun_y", "0", 0);
+ gun_z = gi.cvar ("gun_z", "0", 0);
+ sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
+ sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
+ sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
+ sv_gravity = gi.cvar ("sv_gravity", "800", 0);
+
+ /* noset vars */
+ dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
+
+ /* latched vars */
+ sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
+ gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
+ gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH);
+ maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
+ maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO);
+ deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
+ coop = gi.cvar ("coop", "0", CVAR_LATCH);
+ coop_elevator_delay = gi.cvar("coop_elevator_delay", "1.0", CVAR_ARCHIVE);
+ coop_pickup_weapons = gi.cvar("coop_pickup_weapons", "0", CVAR_ARCHIVE);
+ skill = gi.cvar ("skill", "1", CVAR_LATCH);
+ maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
+ g_footsteps = gi.cvar ("g_footsteps", "1", CVAR_ARCHIVE);
+ g_fix_triggered = gi.cvar ("g_fix_triggered", "0", 0);
+
+ /* change anytime vars */
+ dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
+ fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
+ timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
+ password = gi.cvar ("password", "", CVAR_USERINFO);
+ spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO);
+ needpass = gi.cvar ("needpass", "0", CVAR_SERVERINFO);
+ filterban = gi.cvar ("filterban", "1", 0);
+ g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
+ run_pitch = gi.cvar ("run_pitch", "0.002", 0);
+ run_roll = gi.cvar ("run_roll", "0.005", 0);
+ bob_up = gi.cvar ("bob_up", "0.005", 0);
+ bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
+ bob_roll = gi.cvar ("bob_roll", "0.002", 0);
+
+ /* flood control */
+ flood_msgs = gi.cvar ("flood_msgs", "4", 0);
+ flood_persecond = gi.cvar ("flood_persecond", "4", 0);
+ flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
+
+ /* dm map list */
+ sv_maplist = gi.cvar ("sv_maplist", "", 0);
+
+ /* others */
+ aimfix = gi.cvar("aimfix", "0", CVAR_ARCHIVE);
+ g_machinegun_norecoil = gi.cvar("g_machinegun_norecoil", "0", CVAR_ARCHIVE);
+ g_swap_speed = gi.cvar("g_swap_speed", "1", 0);
+
+ /* items */
+ InitItems ();
+
+ game.helpmessage1[0] = 0;
+ game.helpmessage2[0] = 0;
+
+ /* initialize all entities for this game */
+ game.maxentities = maxentities->value;
+ g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+ globals.edicts = g_edicts;
+ globals.max_edicts = game.maxentities;
+
+ /* initialize all clients for this game */
+ game.maxclients = maxclients->value;
+ game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+ globals.num_edicts = game.maxclients+1;
+}
+
+/* ========================================================= */
+
+/*
+ * Helper function to get
+ * the human readable function
+ * definition by an address.
+ * Called by WriteField1 and
+ * WriteField2.
+ */
+functionList_t *
+GetFunctionByAddress(byte *adr)
+{
+ int i;
+
+ for (i = 0; functionList[i].funcStr; i++)
+ {
+ if (functionList[i].funcPtr == adr)
+ {
+ return &functionList[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper function to get the
+ * pointer to a function by
+ * it's human readable name.
+ * Called by WriteField1 and
+ * WriteField2.
+ */
+byte *
+FindFunctionByName(char *name)
+{
+ int i;
+
+ for (i = 0; functionList[i].funcStr; i++)
+ {
+ if (!strcmp(name, functionList[i].funcStr))
+ {
+ return functionList[i].funcPtr;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper function to get the
+ * human readable definition of
+ * a mmove_t struct by a pointer.
+ */
+mmoveList_t *
+GetMmoveByAddress(mmove_t *adr)
+{
+ int i;
+
+ for (i = 0; mmoveList[i].mmoveStr; i++)
+ {
+ if (mmoveList[i].mmovePtr == adr)
+ {
+ return &mmoveList[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Helper function to get the
+ * pointer to a mmove_t struct
+ * by a human readable definition.
+ */
+mmove_t *
+FindMmoveByName(char *name)
+{
+ int i;
+
+ for (i = 0; mmoveList[i].mmoveStr; i++)
+ {
+ if (!strcmp(name, mmoveList[i].mmoveStr))
+ {
+ return mmoveList[i].mmovePtr;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* ========================================================= */
+
+/*
+ * The following two functions are
+ * doing the dirty work to write the
+ * data generated by the functions
+ * below this block into files.
+ */
+void
+WriteField1(FILE *f, field_t *field, byte *base)
+{
+ void *p;
+ int len;
+ int index;
+ functionList_t *func;
+ mmoveList_t *mmove;
+
+ if (field->flags & FFL_SPAWNTEMP)
+ {
+ return;
+ }
+
+ p = (void *)(base + field->ofs);
+
+ switch (field->type)
+ {
+ case F_INT:
+ case F_FLOAT:
+ case F_ANGLEHACK:
+ case F_VECTOR:
+ case F_IGNORE:
+ break;
+
+ case F_LSTRING:
+ case F_GSTRING:
+
+ if (*(char **)p)
+ {
+ len = strlen(*(char **)p) + 1;
+ }
+ else
+ {
+ len = 0;
+ }
+
+ *(int *)p = len;
+ break;
+ case F_EDICT:
+
+ if (*(edict_t **)p == NULL)
+ {
+ index = -1;
+ }
+ else
+ {
+ index = *(edict_t **)p - g_edicts;
+ }
+
+ *(int *)p = index;
+ break;
+ case F_CLIENT:
+
+ if (*(gclient_t **)p == NULL)
+ {
+ index = -1;
+ }
+ else
+ {
+ index = *(gclient_t **)p - game.clients;
+ }
+
+ *(int *)p = index;
+ break;
+ case F_ITEM:
+
+ if (*(edict_t **)p == NULL)
+ {
+ index = -1;
+ }
+ else
+ {
+ index = *(gitem_t **)p - itemlist;
+ }
+
+ *(int *)p = index;
+ break;
+ case F_FUNCTION:
+
+ if (*(byte **)p == NULL)
+ {
+ len = 0;
+ }
+ else
+ {
+ func = GetFunctionByAddress (*(byte **)p);
+
+ if (!func)
+ {
+ gi.error ("WriteField1: function not in list, can't save game");
+ }
+
+ len = strlen(func->funcStr)+1;
+ }
+
+ *(int *)p = len;
+ break;
+ case F_MMOVE:
+
+ if (*(byte **)p == NULL)
+ {
+ len = 0;
+ }
+ else
+ {
+ mmove = GetMmoveByAddress (*(mmove_t **)p);
+
+ if (!mmove)
+ {
+ gi.error ("WriteField1: mmove not in list, can't save game");
+ }
+
+ len = strlen(mmove->mmoveStr)+1;
+ }
+
+ *(int *)p = len;
+ break;
+ default:
+ gi.error("WriteEdict: unknown field type");
+ }
+}
+
+void
+WriteField2(FILE *f, field_t *field, byte *base)
+{
+ int len;
+ void *p;
+ functionList_t *func;
+ mmoveList_t *mmove;
+
+ if (field->flags & FFL_SPAWNTEMP)
+ {
+ return;
+ }
+
+ p = (void *)(base + field->ofs);
+
+ switch (field->type)
+ {
+ case F_LSTRING:
+
+ if (*(char **)p)
+ {
+ len = strlen(*(char **)p) + 1;
+ fwrite(*(char **)p, len, 1, f);
+ }
+
+ break;
+ case F_FUNCTION:
+
+ if (*(byte **)p)
+ {
+ func = GetFunctionByAddress (*(byte **)p);
+
+ if (!func)
+ {
+ gi.error ("WriteField2: function not in list, can't save game");
+ }
+
+ len = strlen(func->funcStr)+1;
+ fwrite (func->funcStr, len, 1, f);
+ }
+
+ break;
+ case F_MMOVE:
+
+ if (*(byte **)p)
+ {
+ mmove = GetMmoveByAddress (*(mmove_t **)p);
+
+ if (!mmove)
+ {
+ gi.error ("WriteField2: mmove not in list, can't save game");
+ }
+
+ len = strlen(mmove->mmoveStr)+1;
+ fwrite (mmove->mmoveStr, len, 1, f);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+/* ========================================================= */
+
+/*
+ * This function does the dirty
+ * work to read the data from a
+ * file. The processing of the
+ * data is done in the functions
+ * below
+ */
+void
+ReadField(FILE *f, field_t *field, byte *base)
+{
+ void *p;
+ int len;
+ int index;
+ char funcStr[2048];
+
+ if (field->flags & FFL_SPAWNTEMP)
+ {
+ return;
+ }
+
+ p = (void *)(base + field->ofs);
+
+ switch (field->type)
+ {
+ case F_INT:
+ case F_FLOAT:
+ case F_ANGLEHACK:
+ case F_VECTOR:
+ case F_IGNORE:
+ break;
+
+ case F_LSTRING:
+ len = *(int *)p;
+
+ if (!len)
+ {
+ *(char **)p = NULL;
+ }
+ else
+ {
+ *(char **)p = gi.TagMalloc(32 + len, TAG_LEVEL);
+ fread(*(char **)p, len, 1, f);
+ }
+
+ break;
+ case F_EDICT:
+ index = *(int *)p;
+
+ if (index == -1)
+ {
+ *(edict_t **)p = NULL;
+ }
+ else
+ {
+ *(edict_t **)p = &g_edicts[index];
+ }
+
+ break;
+ case F_CLIENT:
+ index = *(int *)p;
+
+ if (index == -1)
+ {
+ *(gclient_t **)p = NULL;
+ }
+ else
+ {
+ *(gclient_t **)p = &game.clients[index];
+ }
+
+ break;
+ case F_ITEM:
+ index = *(int *)p;
+
+ if (index == -1)
+ {
+ *(gitem_t **)p = NULL;
+ }
+ else
+ {
+ *(gitem_t **)p = &itemlist[index];
+ }
+
+ break;
+ case F_FUNCTION:
+ len = *(int *)p;
+
+ if (!len)
+ {
+ *(byte **)p = NULL;
+ }
+ else
+ {
+ if (len > sizeof(funcStr))
+ {
+ gi.error ("ReadField: function name is longer than buffer (%i chars)",
+ (int)sizeof(funcStr));
+ }
+
+ fread (funcStr, len, 1, f);
+
+ if ( !(*(byte **)p = FindFunctionByName (funcStr)) )
+ {
+ gi.error ("ReadField: function %s not found in table, can't load game", funcStr);
+ }
+
+ }
+ break;
+ case F_MMOVE:
+ len = *(int *)p;
+
+ if (!len)
+ {
+ *(byte **)p = NULL;
+ }
+ else
+ {
+ if (len > sizeof(funcStr))
+ {
+ gi.error ("ReadField: mmove name is longer than buffer (%i chars)",
+ (int)sizeof(funcStr));
+ }
+
+ fread (funcStr, len, 1, f);
+
+ if ( !(*(mmove_t **)p = FindMmoveByName (funcStr)) )
+ {
+ gi.error ("ReadField: mmove %s not found in table, can't load game", funcStr);
+ }
+ }
+ break;
+
+ default:
+ gi.error("ReadEdict: unknown field type");
+ }
+}
+
+/* ========================================================= */
+
+/*
+ * Write the client struct into a file.
+ */
+void
+WriteClient(FILE *f, gclient_t *client)
+{
+ field_t *field;
+ gclient_t temp;
+
+ /* all of the ints, floats, and vectors stay as they are */
+ temp = *client;
+
+ /* change the pointers to indexes */
+ for (field = clientfields; field->name; field++)
+ {
+ WriteField1(f, field, (byte *)&temp);
+ }
+
+ /* write the block */
+ fwrite(&temp, sizeof(temp), 1, f);
+
+ /* now write any allocated data following the edict */
+ for (field = clientfields; field->name; field++)
+ {
+ WriteField2(f, field, (byte *)client);
+ }
+}
+
+/*
+ * Read the client struct from a file
+ */
+void
+ReadClient(FILE *f, gclient_t *client, short save_ver)
+{
+ field_t *field;
+
+ fread(client, sizeof(*client), 1, f);
+
+ for (field = clientfields; field->name; field++)
+ {
+ if (field->save_ver <= save_ver)
+ {
+ ReadField(f, field, (byte *)client);
+ }
+ }
+
+ if (save_ver < 3)
+ {
+ InitClientResp(client);
+ }
+}
+
+/* ========================================================= */
+
+/*
+ * Writes the game struct into
+ * a file. This is called when
+ * ever the games goes to e new
+ * level or the user saves the
+ * game. Saved informations are:
+ * - cross level data
+ * - client states
+ * - help computer info
+ */
+void
+WriteGame(const char *filename, qboolean autosave)
+{
+ savegameHeader_t sv;
+ FILE *f;
+ int i;
+
+ if (!autosave)
+ {
+ SaveClientData();
+ }
+
+ f = fopen(filename, "wb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* Savegame identification */
+ memset(&sv, 0, sizeof(sv));
+
+ Q_strlcpy(sv.ver, SAVEGAMEVER, sizeof(sv.ver) - 1);
+ Q_strlcpy(sv.game, GAMEVERSION, sizeof(sv.game) - 1);
+ Q_strlcpy(sv.os, YQ2OSTYPE, sizeof(sv.os) - 1);
+ Q_strlcpy(sv.arch, YQ2ARCH, sizeof(sv.arch) - 1);
+
+ fwrite(&sv, sizeof(sv), 1, f);
+
+ game.autosaved = autosave;
+ fwrite(&game, sizeof(game), 1, f);
+ game.autosaved = false;
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ WriteClient(f, &game.clients[i]);
+ }
+
+ fclose(f);
+}
+
+/*
+ * Read the game structs from
+ * a file. Called when ever a
+ * savegames is loaded.
+ */
+void
+ReadGame(const char *filename)
+{
+ savegameHeader_t sv;
+ FILE *f;
+ int i;
+
+ short save_ver = 0;
+
+ gi.FreeTags(TAG_GAME);
+
+ f = fopen(filename, "rb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* Sanity checks */
+ fread(&sv, sizeof(sv), 1, f);
+
+ static const struct {
+ const char* verstr;
+ int vernum;
+ } version_mappings[] = {
+ {"YQ2-1", 1},
+ {"YQ2-2", 2},
+ {"YQ2-3", 3},
+ {"YQ2-4", 4},
+ };
+
+ for (i=0; i < sizeof(version_mappings)/sizeof(version_mappings[0]); ++i)
+ {
+ if (strcmp(version_mappings[i].verstr, sv.ver) == 0)
+ {
+ save_ver = version_mappings[i].vernum;
+ break;
+ }
+ }
+
+ if (save_ver == 0) // not found in mappings table
+ {
+ fclose(f);
+ gi.error("Savegame from an incompatible version.\n");
+ }
+ else if (save_ver == 1)
+ {
+ if (strcmp(sv.game, GAMEVERSION) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from an other game.so.\n");
+ }
+ else if (strcmp(sv.os, YQ2OSTYPE_1) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from an other os.\n");
+ }
+
+#ifdef _WIN32
+ /* Windows was forced to i386 */
+ if (strcmp(sv.arch, "i386") != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another architecture.\n");
+ }
+#else
+ if (strcmp(sv.arch, YQ2ARCH_1) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another architecture.\n");
+ }
+#endif
+ }
+ else // all newer savegame versions
+ {
+ if (strcmp(sv.game, GAMEVERSION) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another game.so.\n");
+ }
+ else if (strcmp(sv.os, YQ2OSTYPE) != 0)
+ {
+ fclose(f);
+ gi.error("Savegame from another os.\n");
+ }
+ else if (strcmp(sv.arch, YQ2ARCH) != 0)
+ {
+#if defined(_WIN32) && (defined(__i386__) || defined(_M_IX86))
+ // before savegame version "YQ2-4" (and after version 1),
+ // the official Win32 binaries accidentally had the YQ2ARCH "AMD64"
+ // instead of "i386" set due to a bug in the Makefile.
+ // This quirk allows loading those savegames anyway
+ if (save_ver >= 4 || strcmp(sv.arch, "AMD64") != 0)
+#endif
+ {
+ fclose(f);
+ gi.error("Savegame from another architecture.\n");
+ }
+ }
+ }
+
+ g_edicts = gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+ globals.edicts = g_edicts;
+
+ fread(&game, sizeof(game), 1, f);
+ game.clients = gi.TagMalloc(game.maxclients * sizeof(game.clients[0]),
+ TAG_GAME);
+
+ for (i = 0; i < game.maxclients; i++)
+ {
+ ReadClient(f, &game.clients[i], save_ver);
+ }
+
+ fclose(f);
+}
+
+/* ========================================================== */
+
+/*
+ * Helper function to write the
+ * edict into a file. Called by
+ * WriteLevel.
+ */
+void
+WriteEdict(FILE *f, edict_t *ent)
+{
+ field_t *field;
+ edict_t temp;
+
+ /* all of the ints, floats, and vectors stay as they are */
+ temp = *ent;
+
+ /* change the pointers to lengths or indexes */
+ for (field = fields; field->name; field++)
+ {
+ WriteField1(f, field, (byte *)&temp);
+ }
+
+ /* write the block */
+ fwrite(&temp, sizeof(temp), 1, f);
+
+ /* now write any allocated data following the edict */
+ for (field = fields; field->name; field++)
+ {
+ WriteField2(f, field, (byte *)ent);
+ }
+}
+
+/*
+ * Helper fcuntion to write the
+ * level local data into a file.
+ * Called by WriteLevel.
+ */
+void
+WriteLevelLocals(FILE *f)
+{
+ field_t *field;
+ level_locals_t temp;
+
+ /* all of the ints, floats, and vectors stay as they are */
+ temp = level;
+
+ /* change the pointers to lengths or indexes */
+ for (field = levelfields; field->name; field++)
+ {
+ WriteField1(f, field, (byte *)&temp);
+ }
+
+ /* write the block */
+ fwrite(&temp, sizeof(temp), 1, f);
+
+ /* now write any allocated data following the edict */
+ for (field = levelfields; field->name; field++)
+ {
+ WriteField2(f, field, (byte *)&level);
+ }
+}
+
+/*
+ * Writes the current level
+ * into a file.
+ */
+void
+WriteLevel(const char *filename)
+{
+ int i;
+ edict_t *ent;
+ FILE *f;
+
+ f = fopen(filename, "wb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* write out edict size for checking */
+ i = sizeof(edict_t);
+ fwrite(&i, sizeof(i), 1, f);
+
+ /* write out level_locals_t */
+ WriteLevelLocals(f);
+
+ /* write out all the entities */
+ for (i = 0; i < globals.num_edicts; i++)
+ {
+ ent = &g_edicts[i];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ fwrite(&i, sizeof(i), 1, f);
+ WriteEdict(f, ent);
+ }
+
+ i = -1;
+ fwrite(&i, sizeof(i), 1, f);
+
+ fclose(f);
+}
+
+/* ========================================================== */
+
+/*
+ * A helper function to
+ * read the edict back
+ * into the memory. Called
+ * by ReadLevel.
+ */
+void
+ReadEdict(FILE *f, edict_t *ent)
+{
+ field_t *field;
+
+ fread(ent, sizeof(*ent), 1, f);
+
+ for (field = fields; field->name; field++)
+ {
+ ReadField(f, field, (byte *)ent);
+ }
+}
+
+/*
+ * A helper function to
+ * read the level local
+ * data from a file.
+ * Called by ReadLevel.
+ */
+void
+ReadLevelLocals(FILE *f)
+{
+ field_t *field;
+
+ fread(&level, sizeof(level), 1, f);
+
+ for (field = levelfields; field->name; field++)
+ {
+ ReadField(f, field, (byte *)&level);
+ }
+}
+
+/*
+ * Reads a level back into the memory.
+ * SpawnEntities were allready called
+ * in the same way when the level was
+ * saved. All world links were cleared
+ * befor this function was called. When
+ * this function is called, no clients
+ * are connected to the server.
+ */
+void
+ReadLevel(const char *filename)
+{
+ int entnum;
+ FILE *f;
+ int i;
+ edict_t *ent;
+
+ f = fopen(filename, "rb");
+
+ if (!f)
+ {
+ gi.error("Couldn't open %s", filename);
+ }
+
+ /* free any dynamic memory allocated by
+ loading the level base state */
+ gi.FreeTags(TAG_LEVEL);
+
+ /* wipe all the entities */
+ memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0]));
+ globals.num_edicts = maxclients->value + 1;
+
+ /* check edict size */
+ fread(&i, sizeof(i), 1, f);
+
+ if (i != sizeof(edict_t))
+ {
+ fclose(f);
+ gi.error("ReadLevel: mismatched edict size");
+ }
+
+ /* load the level locals */
+ ReadLevelLocals(f);
+
+ /* load all the entities */
+ while (1)
+ {
+ if (fread(&entnum, sizeof(entnum), 1, f) != 1)
+ {
+ fclose(f);
+ gi.error("ReadLevel: failed to read entnum");
+ }
+
+ if (entnum == -1)
+ {
+ break;
+ }
+
+ if (entnum >= globals.num_edicts)
+ {
+ globals.num_edicts = entnum + 1;
+ }
+
+ ent = &g_edicts[entnum];
+ ReadEdict(f, ent);
+
+ /* let the server rebuild world links for this ent */
+ memset(&ent->area, 0, sizeof(ent->area));
+ gi.linkentity(ent);
+ }
+
+ fclose(f);
+
+ /* mark all clients as unconnected */
+ for (i = 0; i < maxclients->value; i++)
+ {
+ ent = &g_edicts[i + 1];
+ ent->client = game.clients + i;
+ ent->client->pers.connected = false;
+ }
+
+ /* do any load time things at this point */
+ for (i = 0; i < globals.num_edicts; i++)
+ {
+ ent = &g_edicts[i];
+
+ if (!ent->inuse)
+ {
+ continue;
+ }
+
+ /* fire any cross-level triggers */
+ if (ent->classname)
+ {
+ if (strcmp(ent->classname, "target_crosslevel_target") == 0)
+ {
+ ent->nextthink = level.time + ent->delay;
+ }
+ }
+ }
+}
+
diff --git a/xatrix/src/savegame/tables/clientfields.h b/xatrix/src/savegame/tables/clientfields.h
new file mode 100644
index 0000000..fdef3e5
--- /dev/null
+++ b/xatrix/src/savegame/tables/clientfields.h
@@ -0,0 +1,14 @@
+/*
+ * =======================================================================
+ *
+ * Fields of the client to be saved.
+ *
+ * =======================================================================
+ */
+
+{"pers.weapon", CLOFS(pers.weapon), F_ITEM},
+{"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM},
+{"newweapon", CLOFS(newweapon), F_ITEM},
+{"resp.coop_respawn.weapon", CLOFS(resp.coop_respawn.weapon), F_ITEM, 0, 3},
+{"resp.coop_respawn.lastweapon", CLOFS(resp.coop_respawn.lastweapon), F_ITEM, 0, 3},
+{NULL, 0, F_INT}
diff --git a/xatrix/src/savegame/tables/fields.h b/xatrix/src/savegame/tables/fields.h
new file mode 100644
index 0000000..007a2b8
--- /dev/null
+++ b/xatrix/src/savegame/tables/fields.h
@@ -0,0 +1,89 @@
+/*
+ * =======================================================================
+ *
+ * Game fields to be saved.
+ *
+ * =======================================================================
+ */
+
+{"classname", FOFS(classname), F_LSTRING},
+{"model", FOFS(model), F_LSTRING},
+{"spawnflags", FOFS(spawnflags), F_INT},
+{"speed", FOFS(speed), F_FLOAT},
+{"accel", FOFS(accel), F_FLOAT},
+{"decel", FOFS(decel), F_FLOAT},
+{"target", FOFS(target), F_LSTRING},
+{"targetname", FOFS(targetname), F_LSTRING},
+{"pathtarget", FOFS(pathtarget), F_LSTRING},
+{"deathtarget", FOFS(deathtarget), F_LSTRING},
+{"killtarget", FOFS(killtarget), F_LSTRING},
+{"combattarget", FOFS(combattarget), F_LSTRING},
+{"message", FOFS(message), F_LSTRING},
+{"team", FOFS(team), F_LSTRING},
+{"wait", FOFS(wait), F_FLOAT},
+{"delay", FOFS(delay), F_FLOAT},
+{"random", FOFS(random), F_FLOAT},
+{"move_origin", FOFS(move_origin), F_VECTOR},
+{"move_angles", FOFS(move_angles), F_VECTOR},
+{"style", FOFS(style), F_INT},
+{"count", FOFS(count), F_INT},
+{"health", FOFS(health), F_INT},
+{"sounds", FOFS(sounds), F_INT},
+{"light", 0, F_IGNORE},
+{"dmg", FOFS(dmg), F_INT},
+{"mass", FOFS(mass), F_INT},
+{"volume", FOFS(volume), F_FLOAT},
+{"attenuation", FOFS(attenuation), F_FLOAT},
+{"map", FOFS(map), F_LSTRING},
+{"origin", FOFS(s.origin), F_VECTOR},
+{"angles", FOFS(s.angles), F_VECTOR},
+{"angle", FOFS(s.angles), F_ANGLEHACK},
+{"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN},
+{"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN},
+{"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN},
+{"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN},
+{"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN},
+{"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN},
+{"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN},
+{"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN},
+{"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN},
+{"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN},
+{"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN},
+{"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN},
+{"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN},
+{"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN},
+{"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN},
+{"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN},
+{"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN},
+{"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN},
+{"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN},
+{"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN},
+{"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN},
+{"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN},
+{"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN},
+{"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN},
+{"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN},
+{"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN},
+{"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN},
+{"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN},
+{"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN},
+{"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN},
+{"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN},
+{"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN},
+{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
+{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
+{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
+{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
+{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
+{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
+{"item", FOFS(item), F_ITEM},
+{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
+{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
+{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
+{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
+{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
+{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
+{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
+{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
+{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP},
+{0, 0, 0, 0}
diff --git a/xatrix/src/savegame/tables/gamefunc_decs.h b/xatrix/src/savegame/tables/gamefunc_decs.h
new file mode 100644
index 0000000..e5a210e
--- /dev/null
+++ b/xatrix/src/savegame/tables/gamefunc_decs.h
@@ -0,0 +1,1231 @@
+/*
+ * =======================================================================
+ *
+ * Prototypes for every function in the game.so.
+ *
+ * =======================================================================
+ */
+
+extern void ReadLevel ( const char * filename ) ;
+extern void ReadLevelLocals ( FILE * f ) ;
+extern void ReadEdict ( FILE * f , edict_t * ent ) ;
+extern void WriteLevel ( const char * filename ) ;
+extern void WriteLevelLocals ( FILE * f ) ;
+extern void WriteEdict ( FILE * f , edict_t * ent ) ;
+extern void ReadGame ( const char * filename ) ;
+extern void WriteGame ( const char * filename , qboolean autosave ) ;
+extern void ReadClient ( FILE * f , gclient_t * client , short save_ver ) ;
+extern void WriteClient ( FILE * f , gclient_t * client ) ;
+extern void ReadField ( FILE * f , field_t * field , byte * base ) ;
+extern void WriteField2 ( FILE * f , field_t * field , byte * base ) ;
+extern void WriteField1 ( FILE * f , field_t * field , byte * base ) ;
+extern mmove_t * FindMmoveByName ( char * name ) ;
+extern mmoveList_t * GetMmoveByAddress ( mmove_t * adr ) ;
+extern byte * FindFunctionByName ( char * name ) ;
+extern functionList_t * GetFunctionByAddress ( byte * adr ) ;
+extern void InitGame ( void ) ;
+extern void Info_SetValueForKey ( char * s , char * key , char * value ) ;
+extern qboolean Info_Validate ( char * s ) ;
+extern void Info_RemoveKey ( char * s , char * key ) ;
+extern char * Info_ValueForKey ( char * s , char * key ) ;
+extern void Com_sprintf ( char * dest , int size , char * fmt , ... ) ;
+extern int Q_strcasecmp ( char * s1 , char * s2 ) ;
+extern int Q_strncasecmp ( char * s1 , char * s2 , int n ) ;
+extern int Q_stricmp ( const char * s1 , const char * s2 ) ;
+extern void Com_PageInMemory ( byte * buffer , int size ) ;
+extern char * COM_Parse ( char * * data_p ) ;
+extern char * va ( const char * format , ... ) ;
+extern void Swap_Init ( void ) ;
+extern float FloatNoSwap ( float f ) ;
+extern float FloatSwap ( float f ) ;
+extern int LongNoSwap ( int l ) ;
+extern int LongSwap ( int l ) ;
+extern short ShortNoSwap ( short l ) ;
+extern short ShortSwap ( short l ) ;
+extern float LittleFloat ( float l ) ;
+extern float BigFloat ( float l ) ;
+extern int LittleLong ( int l ) ;
+extern int BigLong ( int l ) ;
+extern short LittleShort ( short l ) ;
+extern short BigShort ( short l ) ;
+extern void COM_DefaultExtension ( char * path , const char * extension ) ;
+extern void COM_FilePath ( const char * in , char * out ) ;
+extern void COM_FileBase ( char * in , char * out ) ;
+extern const char * COM_FileExtension ( const char * in ) ;
+extern void COM_StripExtension ( char * in , char * out ) ;
+extern char * COM_SkipPath ( char * pathname ) ;
+extern int Q_log2 ( int val ) ;
+extern void VectorScale ( vec3_t in , vec_t scale , vec3_t out ) ;
+extern void VectorInverse ( vec3_t v ) ;
+extern vec_t VectorLength ( vec3_t v ) ;
+extern void CrossProduct ( vec3_t v1 , vec3_t v2 , vec3_t cross ) ;
+extern void _VectorCopy ( vec3_t in , vec3_t out ) ;
+extern void _VectorAdd ( vec3_t veca , vec3_t vecb , vec3_t out ) ;
+extern void _VectorSubtract ( vec3_t veca , vec3_t vecb , vec3_t out ) ;
+extern vec_t _DotProduct ( vec3_t v1 , vec3_t v2 ) ;
+extern void VectorMA ( vec3_t veca , float scale , vec3_t vecb , vec3_t vecc ) ;
+extern vec_t VectorNormalize2 ( vec3_t v , vec3_t out ) ;
+extern vec_t VectorNormalize ( vec3_t v ) ;
+extern int VectorCompare ( vec3_t v1 , vec3_t v2 ) ;
+extern void AddPointToBounds ( vec3_t v , vec3_t mins , vec3_t maxs ) ;
+extern void ClearBounds ( vec3_t mins , vec3_t maxs ) ;
+extern int BoxOnPlaneSide2 ( vec3_t emins , vec3_t emaxs , struct cplane_s * p ) ;
+extern float anglemod ( float a ) ;
+extern float LerpAngle ( float a2 , float a1 , float frac ) ;
+extern float Q_fabs ( float f ) ;
+extern void R_ConcatTransforms ( float in1 [ 3 ] [ 4 ] , float in2 [ 3 ] [ 4 ] , float out [ 3 ] [ 4 ] ) ;
+extern void R_ConcatRotations ( float in1 [ 3 ] [ 3 ] , float in2 [ 3 ] [ 3 ] , float out [ 3 ] [ 3 ] ) ;
+extern void PerpendicularVector ( vec3_t dst , const vec3_t src ) ;
+extern void ProjectPointOnPlane ( vec3_t dst , const vec3_t p , const vec3_t normal ) ;
+extern void AngleVectors ( vec3_t angles , vec3_t forward , vec3_t right , vec3_t up ) ;
+extern void RotatePointAroundVector ( vec3_t dst , const vec3_t dir , const vec3_t point , float degrees ) ;
+extern void Weapon_Trap ( edict_t * ent ) ;
+extern void weapon_trap_fire ( edict_t * ent , qboolean held ) ;
+extern void Weapon_Phalanx ( edict_t * ent ) ;
+extern void weapon_phalanx_fire ( edict_t * ent ) ;
+extern void Weapon_Ionripper ( edict_t * ent ) ;
+extern void weapon_ionripper_fire ( edict_t * ent ) ;
+extern void Weapon_BFG ( edict_t * ent ) ;
+extern void weapon_bfg_fire ( edict_t * ent ) ;
+extern void Weapon_Railgun ( edict_t * ent ) ;
+extern void weapon_railgun_fire ( edict_t * ent ) ;
+extern void Weapon_SuperShotgun ( edict_t * ent ) ;
+extern void weapon_supershotgun_fire ( edict_t * ent ) ;
+extern void Weapon_Shotgun ( edict_t * ent ) ;
+extern void weapon_shotgun_fire ( edict_t * ent ) ;
+extern void Weapon_Chaingun ( edict_t * ent ) ;
+extern void Chaingun_Fire ( edict_t * ent ) ;
+extern void Weapon_Machinegun ( edict_t * ent ) ;
+extern void Machinegun_Fire ( edict_t * ent ) ;
+extern void Weapon_HyperBlaster ( edict_t * ent ) ;
+extern void Weapon_HyperBlaster_Fire ( edict_t * ent ) ;
+extern void Weapon_Blaster ( edict_t * ent ) ;
+extern void Weapon_Blaster_Fire ( edict_t * ent ) ;
+extern void Blaster_Fire ( edict_t * ent , vec3_t g_offset , int damage , qboolean hyper , int effect ) ;
+extern void Weapon_RocketLauncher ( edict_t * ent ) ;
+extern void Weapon_RocketLauncher_Fire ( edict_t * ent ) ;
+extern void Weapon_GrenadeLauncher ( edict_t * ent ) ;
+extern void weapon_grenadelauncher_fire ( edict_t * ent ) ;
+extern void Weapon_Grenade ( edict_t * ent ) ;
+extern void weapon_grenade_fire ( edict_t * ent , qboolean held ) ;
+extern void Weapon_Generic ( edict_t * ent , int FRAME_ACTIVATE_LAST , int FRAME_FIRE_LAST , int FRAME_IDLE_LAST , int FRAME_DEACTIVATE_LAST , int * pause_frames , int * fire_frames , void ( * fire ) ( edict_t * ent ) ) ;
+extern void Drop_Weapon ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Weapon2 ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Weapon ( edict_t * ent , gitem_t * item ) ;
+extern void Think_Weapon ( edict_t * ent ) ;
+extern void NoAmmoWeaponChange ( edict_t * ent ) ;
+extern void ChangeWeapon ( edict_t * ent ) ;
+extern qboolean Pickup_Weapon ( edict_t * ent , edict_t * other ) ;
+extern void PlayerNoise ( edict_t * who , vec3_t where , int type ) ;
+extern void ClientEndServerFrame ( edict_t * ent ) ;
+extern void P_ProjectSource(edict_t *ent, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
+extern void G_SetClientFrame ( edict_t * ent ) ;
+extern void G_SetClientSound ( edict_t * ent ) ;
+extern void G_SetClientEvent ( edict_t * ent ) ;
+extern void G_SetClientEffects ( edict_t * ent ) ;
+extern void P_WorldEffects ( void ) ;
+extern void P_FallingDamage ( edict_t * ent ) ;
+extern void SV_CalcBlend ( edict_t * ent ) ;
+extern void SV_AddBlend ( float r , float g , float b , float a , float * v_blend ) ;
+extern void SV_CalcGunOffset ( edict_t * ent ) ;
+extern void SV_CalcViewOffset ( edict_t * ent ) ;
+extern void P_DamageFeedback ( edict_t * player ) ;
+extern float SV_CalcRoll ( vec3_t angles , vec3_t velocity ) ;
+extern edict_t * PlayerTrail_LastSpot ( void ) ;
+extern edict_t * PlayerTrail_PickNext ( edict_t * self ) ;
+extern edict_t * PlayerTrail_PickFirst ( edict_t * self ) ;
+extern void PlayerTrail_New ( vec3_t spot ) ;
+extern void PlayerTrail_Add ( vec3_t spot ) ;
+extern void PlayerTrail_Init ( void ) ;
+extern void G_SetSpectatorStats ( edict_t * ent ) ;
+extern void G_CheckChaseStats ( edict_t * ent ) ;
+extern void G_SetStats ( edict_t * ent ) ;
+extern void InventoryMessage ( edict_t * ent ) ;
+extern void HelpComputerMessage ( edict_t * ent ) ;
+extern void DeathmatchScoreboardMessage ( edict_t * ent , edict_t * killer ) ;
+extern void BeginIntermission ( edict_t * targ ) ;
+extern void MoveClientToIntermission ( edict_t * ent ) ;
+extern void ClientBeginServerFrame ( edict_t * ent ) ;
+extern void ClientThink ( edict_t * ent , usercmd_t * ucmd ) ;
+extern void PrintPmove ( pmove_t * pm ) ;
+extern unsigned CheckBlock ( void * b , int c ) ;
+extern trace_t PM_trace ( vec3_t start , vec3_t mins , vec3_t maxs , vec3_t end ) ;
+extern void ClientDisconnect ( edict_t * ent ) ;
+extern qboolean ClientConnect ( edict_t * ent , char * userinfo ) ;
+extern void ClientUserinfoChanged ( edict_t * ent , char * userinfo ) ;
+extern void ClientBegin ( edict_t * ent ) ;
+extern void ClientBeginDeathmatch ( edict_t * ent ) ;
+extern void PutClientInServer ( edict_t * ent ) ;
+extern void spectator_respawn ( edict_t * ent ) ;
+extern void respawn ( edict_t * self ) ;
+extern void CopyToBodyQue ( edict_t * ent ) ;
+extern void body_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void InitBodyQue ( void ) ;
+extern void SelectSpawnPoint ( edict_t * ent , vec3_t origin , vec3_t angles ) ;
+extern edict_t * SelectCoopSpawnPoint ( edict_t * ent ) ;
+extern edict_t * SelectDeathmatchSpawnPoint ( void ) ;
+extern edict_t * SelectFarthestDeathmatchSpawnPoint ( void ) ;
+extern edict_t * SelectRandomDeathmatchSpawnPoint ( void ) ;
+extern float PlayersRangeFromSpot ( edict_t * spot ) ;
+extern void FetchClientEntData ( edict_t * ent ) ;
+extern void SaveClientData ( void ) ;
+extern void InitClientResp ( gclient_t * client ) ;
+extern void InitClientPersistant ( gclient_t * client ) ;
+extern void player_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void LookAtKiller ( edict_t * self , edict_t * inflictor , edict_t * attacker ) ;
+extern void TossClientWeapon ( edict_t * self ) ;
+extern void ClientObituary ( edict_t * self , edict_t * inflictor , edict_t * attacker ) ;
+extern qboolean IsNeutral ( edict_t * ent ) ;
+extern qboolean IsFemale ( edict_t * ent ) ;
+extern void player_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void SP_info_player_intermission ( edict_t *ent );
+extern void SP_info_player_coop ( edict_t * self ) ;
+extern void SP_info_player_deathmatch ( edict_t * self ) ;
+extern void SP_info_player_start ( edict_t * self ) ;
+extern void SP_CreateCoopSpots ( edict_t * self ) ;
+extern void SP_FixCoopSpots ( edict_t * self ) ;
+extern void SP_monster_tank ( edict_t * self ) ;
+extern void tank_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void tank_dead ( edict_t * self ) ;
+extern void tank_attack ( edict_t * self ) ;
+extern void tank_doattack_rocket ( edict_t * self ) ;
+extern void tank_refire_rocket ( edict_t * self ) ;
+extern void tank_poststrike ( edict_t * self ) ;
+extern void tank_reattack_blaster ( edict_t * self ) ;
+extern void TankMachineGun ( edict_t * self ) ;
+extern void TankRocket ( edict_t * self ) ;
+extern void TankStrike ( edict_t * self ) ;
+extern void TankBlaster ( edict_t * self ) ;
+extern void tank_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void tank_run ( edict_t * self ) ;
+extern void tank_walk ( edict_t * self ) ;
+extern void tank_stand ( edict_t * self ) ;
+extern void tank_idle ( edict_t * self ) ;
+extern void tank_windup ( edict_t * self ) ;
+extern void tank_thud ( edict_t * self ) ;
+extern void tank_footstep ( edict_t * self ) ;
+extern void tank_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_supertank ( edict_t * self ) ;
+extern void supertank_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void BossExplode ( edict_t * self ) ;
+extern void supertank_dead ( edict_t * self ) ;
+extern void supertank_attack ( edict_t * self ) ;
+extern void supertankMachineGun ( edict_t * self ) ;
+extern void supertankRocket ( edict_t * self ) ;
+extern void supertank_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void supertank_reattack1 ( edict_t * self ) ;
+extern void supertank_run ( edict_t * self ) ;
+extern void supertank_walk ( edict_t * self ) ;
+extern void supertank_forward ( edict_t * self ) ;
+extern void supertank_stand ( edict_t * self ) ;
+extern void supertank_search ( edict_t * self ) ;
+extern void TreadSound ( edict_t * self ) ;
+extern void SP_monster_soldier_lasergun ( edict_t * self ) ;
+extern void SP_monster_soldier_hypergun ( edict_t * self ) ;
+extern void SP_monster_soldier_ripper ( edict_t * self ) ;
+extern void SP_monster_soldier_h ( edict_t * self ) ;
+extern void soldierh_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void soldierh_dead ( edict_t * self ) ;
+extern void soldierh_fire7 ( edict_t * self ) ;
+extern void soldierh_fire6 ( edict_t * self ) ;
+extern void soldierh_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void soldierh_duck_hold ( edict_t * self ) ;
+extern void soldierh_sight ( edict_t * self , edict_t * other ) ;
+extern void soldierh_attack ( edict_t * self ) ;
+extern void soldierh_attack6_refire ( edict_t * self ) ;
+extern void soldierh_fire8 ( edict_t * self ) ;
+extern void soldierh_fire4 ( edict_t * self ) ;
+extern void soldierh_attack3_refire ( edict_t * self ) ;
+extern void soldierh_fire3 ( edict_t * self ) ;
+extern void soldierh_duck_up ( edict_t * self ) ;
+extern void soldierh_duck_down ( edict_t * self ) ;
+extern void soldierh_attack2_refire2 ( edict_t * self ) ;
+extern void soldierh_attack2_refire1 ( edict_t * self ) ;
+extern void soldierh_fire2 ( edict_t * self ) ;
+extern void soldierh_ripper2 ( edict_t * self ) ;
+extern void soldierh_hyper_refire2 ( edict_t * self ) ;
+extern void soldierh_hyper_sound ( edict_t * self ) ;
+extern void soldierh_attack1_refire2 ( edict_t * self ) ;
+extern void soldierh_attack1_refire1 ( edict_t * self ) ;
+extern void soldierh_fire1 ( edict_t * self ) ;
+extern void soldierh_ripper1 ( edict_t * self ) ;
+extern void soldierh_hyper_refire1 ( edict_t * self ) ;
+extern void soldierh_fire ( edict_t * self , int flash_number ) ;
+extern void soldierh_laserbeam ( edict_t * self , int flash_index ) ;
+extern void soldierh_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void soldierh_run ( edict_t * self ) ;
+extern void soldierh_walk ( edict_t * self ) ;
+extern void soldierh_walk1_random ( edict_t * self ) ;
+extern void soldierh_stand ( edict_t * self ) ;
+extern void soldierh_cock ( edict_t * self ) ;
+extern void soldierh_idle ( edict_t * self ) ;
+extern void SP_monster_soldier_ss ( edict_t * self ) ;
+extern void SP_monster_soldier ( edict_t * self ) ;
+extern void SP_monster_soldier_light ( edict_t * self ) ;
+extern void SP_monster_soldier_x ( edict_t * self ) ;
+extern void soldier_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void soldier_dead ( edict_t * self ) ;
+extern void soldier_fire7 ( edict_t * self ) ;
+extern void soldier_fire6 ( edict_t * self ) ;
+extern void soldier_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void soldier_duck_hold ( edict_t * self ) ;
+extern void soldier_sight ( edict_t * self , edict_t * other ) ;
+extern void soldier_attack ( edict_t * self ) ;
+extern void soldier_attack6_refire ( edict_t * self ) ;
+extern void soldier_fire8 ( edict_t * self ) ;
+extern void soldier_fire4 ( edict_t * self ) ;
+extern void soldier_attack3_refire ( edict_t * self ) ;
+extern void soldier_fire3 ( edict_t * self ) ;
+extern void soldier_duck_up ( edict_t * self ) ;
+extern void soldier_duck_down ( edict_t * self ) ;
+extern void soldier_attack2_refire2 ( edict_t * self ) ;
+extern void soldier_attack2_refire1 ( edict_t * self ) ;
+extern void soldier_fire2 ( edict_t * self ) ;
+extern void soldier_attack1_refire2 ( edict_t * self ) ;
+extern void soldier_attack1_refire1 ( edict_t * self ) ;
+extern void soldier_fire1 ( edict_t * self ) ;
+extern void soldier_fire ( edict_t * self , int flash_number ) ;
+extern void soldier_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void soldier_run ( edict_t * self ) ;
+extern void soldier_walk ( edict_t * self ) ;
+extern void soldier_walk1_random ( edict_t * self ) ;
+extern void soldier_stand ( edict_t * self ) ;
+extern void soldier_cock ( edict_t * self ) ;
+extern void soldier_idle ( edict_t * self ) ;
+extern void SP_monster_parasite ( edict_t * self ) ;
+extern void parasite_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void parasite_dead ( edict_t * self ) ;
+extern void parasite_attack ( edict_t * self ) ;
+extern void parasite_drain_attack ( edict_t * self ) ;
+extern qboolean parasite_drain_attack_ok ( vec3_t start , vec3_t end ) ;
+extern void parasite_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void parasite_walk ( edict_t * self ) ;
+extern void parasite_start_walk ( edict_t * self ) ;
+extern void parasite_run ( edict_t * self ) ;
+extern void parasite_start_run ( edict_t * self ) ;
+extern void parasite_stand ( edict_t * self ) ;
+extern void parasite_idle ( edict_t * self ) ;
+extern void parasite_refidget ( edict_t * self ) ;
+extern void parasite_do_fidget ( edict_t * self ) ;
+extern void parasite_end_fidget ( edict_t * self ) ;
+extern void parasite_search ( edict_t * self ) ;
+extern void parasite_scratch ( edict_t * self ) ;
+extern void parasite_tap ( edict_t * self ) ;
+extern void parasite_sight ( edict_t * self , edict_t * other ) ;
+extern void parasite_reel_in ( edict_t * self ) ;
+extern void parasite_launch ( edict_t * self ) ;
+extern void SP_monster_mutant ( edict_t * self ) ;
+extern void mutant_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void mutant_dead ( edict_t * self ) ;
+extern void mutant_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern qboolean mutant_checkattack ( edict_t * self ) ;
+extern qboolean mutant_check_jump ( edict_t * self ) ;
+extern qboolean mutant_check_melee ( edict_t * self ) ;
+extern void mutant_jump ( edict_t * self ) ;
+extern void mutant_check_landing ( edict_t * self ) ;
+extern void mutant_jump_takeoff ( edict_t * self ) ;
+extern void mutant_jump_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void mutant_melee ( edict_t * self ) ;
+extern void mutant_check_refire ( edict_t * self ) ;
+extern void mutant_hit_right ( edict_t * self ) ;
+extern void mutant_hit_left ( edict_t * self ) ;
+extern void mutant_run ( edict_t * self ) ;
+extern void mutant_walk ( edict_t * self ) ;
+extern void mutant_walk_loop ( edict_t * self ) ;
+extern void mutant_idle ( edict_t * self ) ;
+extern void mutant_idle_loop ( edict_t * self ) ;
+extern void mutant_stand ( edict_t * self ) ;
+extern void mutant_swing ( edict_t * self ) ;
+extern void mutant_search ( edict_t * self ) ;
+extern void mutant_sight ( edict_t * self , edict_t * other ) ;
+extern void mutant_step ( edict_t * self ) ;
+extern qboolean M_walkmove ( edict_t * ent , float yaw , float dist ) ;
+extern void M_MoveToGoal ( edict_t * ent , float dist ) ;
+extern qboolean SV_CloseEnough ( edict_t * ent , edict_t * goal , float dist ) ;
+extern void SV_NewChaseDir ( edict_t * actor , edict_t * enemy , float dist ) ;
+extern void SV_FixCheckBottom ( edict_t * ent ) ;
+extern qboolean SV_StepDirection ( edict_t * ent , float yaw , float dist ) ;
+extern void M_ChangeYaw ( edict_t * ent ) ;
+extern qboolean SV_movestep ( edict_t * ent , vec3_t move , qboolean relink ) ;
+extern qboolean M_CheckBottom ( edict_t * ent ) ;
+extern void SP_monster_medic ( edict_t * self ) ;
+extern qboolean medic_checkattack ( edict_t * self ) ;
+extern void medic_attack ( edict_t * self ) ;
+extern void medic_hook_retract ( edict_t * self ) ;
+extern void medic_cable_attack ( edict_t * self ) ;
+extern void medic_hook_launch ( edict_t * self ) ;
+extern void medic_continue ( edict_t * self ) ;
+extern void medic_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void medic_duck_up ( edict_t * self ) ;
+extern void medic_duck_hold ( edict_t * self ) ;
+extern void medic_duck_down ( edict_t * self ) ;
+extern void medic_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void medic_dead ( edict_t * self ) ;
+extern void medic_fire_blaster ( edict_t * self ) ;
+extern void medic_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void medic_run ( edict_t * self ) ;
+extern void medic_walk ( edict_t * self ) ;
+extern void medic_stand ( edict_t * self ) ;
+extern void medic_sight ( edict_t * self , edict_t * other ) ;
+extern void medic_search ( edict_t * self ) ;
+extern void medic_idle ( edict_t * self ) ;
+extern edict_t * medic_FindDeadMonster ( edict_t * self ) ;
+extern void SP_misc_insane ( edict_t * self ) ;
+extern void insane_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void insane_dead ( edict_t * self ) ;
+extern void insane_stand ( edict_t * self ) ;
+extern void insane_checkup ( edict_t * self ) ;
+extern void insane_checkdown ( edict_t * self ) ;
+extern void insane_onground ( edict_t * self ) ;
+extern void insane_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void insane_run ( edict_t * self ) ;
+extern void insane_walk ( edict_t * self ) ;
+extern void insane_cross ( edict_t * self ) ;
+extern void insane_scream ( edict_t * self ) ;
+extern void insane_moan ( edict_t * self ) ;
+extern void insane_shake ( edict_t * self ) ;
+extern void insane_fist ( edict_t * self ) ;
+extern void SP_monster_infantry ( edict_t * self ) ;
+extern void infantry_attack ( edict_t * self ) ;
+extern void infantry_smack ( edict_t * self ) ;
+extern void infantry_swing ( edict_t * self ) ;
+extern void infantry_fire ( edict_t * self ) ;
+extern void infantry_cock_gun ( edict_t * self ) ;
+extern void infantry_set_firetime ( edict_t * self ) ;
+extern void infantry_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void infantry_duck_up ( edict_t * self ) ;
+extern void infantry_duck_hold ( edict_t * self ) ;
+extern void infantry_duck_down ( edict_t * self ) ;
+extern void infantry_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void infantry_dead ( edict_t * self ) ;
+extern void infantry_sight ( edict_t * self , edict_t * other ) ;
+extern void InfantryMachineGun ( edict_t * self ) ;
+extern void infantry_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void infantry_run ( edict_t * self ) ;
+extern void infantry_walk ( edict_t * self ) ;
+extern void infantry_fidget ( edict_t * self ) ;
+extern void infantry_stand ( edict_t * self ) ;
+extern void SP_monster_hover ( edict_t * self ) ;
+extern void hover_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void hover_dead ( edict_t * self ) ;
+extern void hover_deadthink ( edict_t * self ) ;
+extern void hover_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void hover_attack ( edict_t * self ) ;
+extern void hover_start_attack ( edict_t * self ) ;
+extern void hover_walk ( edict_t * self ) ;
+extern void hover_run ( edict_t * self ) ;
+extern void hover_stand ( edict_t * self ) ;
+extern void hover_fire_blaster ( edict_t * self ) ;
+extern void hover_reattack ( edict_t * self ) ;
+extern void hover_search ( edict_t * self ) ;
+extern void hover_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_gunner ( edict_t * self ) ;
+extern void gunner_refire_chain ( edict_t * self ) ;
+extern void gunner_fire_chain ( edict_t * self ) ;
+extern void gunner_attack ( edict_t * self ) ;
+extern void GunnerGrenade ( edict_t * self ) ;
+extern void GunnerFire ( edict_t * self ) ;
+extern void gunner_opengun ( edict_t * self ) ;
+extern void gunner_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void gunner_duck_up ( edict_t * self ) ;
+extern void gunner_duck_hold ( edict_t * self ) ;
+extern void gunner_duck_down ( edict_t * self ) ;
+extern void gunner_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void gunner_dead ( edict_t * self ) ;
+extern void gunner_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void gunner_runandshoot ( edict_t * self ) ;
+extern void gunner_run ( edict_t * self ) ;
+extern void gunner_walk ( edict_t * self ) ;
+extern void gunner_stand ( edict_t * self ) ;
+extern void gunner_fidget ( edict_t * self ) ;
+extern void gunner_search ( edict_t * self ) ;
+extern void gunner_sight ( edict_t * self , edict_t * other ) ;
+extern void gunner_idlesound ( edict_t * self ) ;
+extern void SP_monster_gladiator ( edict_t * self ) ;
+extern void gladiator_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void gladiator_dead ( edict_t * self ) ;
+extern void gladiator_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void gladiator_attack ( edict_t * self ) ;
+extern void GladiatorGun ( edict_t * self ) ;
+extern void gladiator_melee ( edict_t * self ) ;
+extern void GaldiatorMelee ( edict_t * self ) ;
+extern void gladiator_run ( edict_t * self ) ;
+extern void gladiator_walk ( edict_t * self ) ;
+extern void gladiator_stand ( edict_t * self ) ;
+extern void gladiator_cleaver_swing ( edict_t * self ) ;
+extern void gladiator_search ( edict_t * self ) ;
+extern void gladiator_sight ( edict_t * self , edict_t * other ) ;
+extern void gladiator_idle ( edict_t * self ) ;
+extern void SP_monster_gladb ( edict_t * self ) ;
+extern void gladb_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void gladb_dead ( edict_t * self ) ;
+extern void gladb_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void gladb_attack ( edict_t * self ) ;
+extern void gladbGun_check ( edict_t * self ) ;
+extern void gladbGun ( edict_t * self ) ;
+extern void gladb_melee ( edict_t * self ) ;
+extern void GladbMelee ( edict_t * self ) ;
+extern void gladb_run ( edict_t * self ) ;
+extern void gladb_walk ( edict_t * self ) ;
+extern void gladb_stand ( edict_t * self ) ;
+extern void gladb_cleaver_swing ( edict_t * self ) ;
+extern void gladb_search ( edict_t * self ) ;
+extern void gladb_sight ( edict_t * self , edict_t * other ) ;
+extern void gladb_idle ( edict_t * self ) ;
+extern void land_to_water ( edict_t * self ) ;
+extern void water_to_land ( edict_t * self ) ;
+extern void SP_monster_gekk ( edict_t * self ) ;
+extern void gekk_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void gekk_duck_hold ( edict_t * self ) ;
+extern void gekk_duck_up ( edict_t * self ) ;
+extern void gekk_duck_down ( edict_t * self ) ;
+extern void gekk_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void isgibfest ( edict_t * self ) ;
+extern void gekk_gibfest ( edict_t * self ) ;
+extern void gekk_dead ( edict_t * self ) ;
+extern void gekk_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void gekk_jump ( edict_t * self ) ;
+extern void gekk_check_landing ( edict_t * self ) ;
+extern void gekk_stop_skid ( edict_t * self ) ;
+extern void gekk_jump_takeoff2 ( edict_t * self ) ;
+extern void gekk_jump_takeoff ( edict_t * self ) ;
+extern void gekk_jump_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void gekk_melee ( edict_t * self ) ;
+extern void gekk_preattack ( edict_t * self ) ;
+extern void gekk_bite ( edict_t * self ) ;
+extern void gekk_check_underwater ( edict_t * self ) ;
+extern void reloogie ( edict_t * self ) ;
+extern void loogie ( edict_t * self ) ;
+extern void fire_loogie ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed ) ;
+extern void loogie_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void gekk_check_refire ( edict_t * self ) ;
+extern void gekk_hit_right ( edict_t * self ) ;
+extern void gekk_hit_left ( edict_t * self ) ;
+extern void gekk_run ( edict_t * self ) ;
+extern void gekk_run_start ( edict_t * self ) ;
+extern void gekk_walk ( edict_t * self ) ;
+extern void gekk_idle ( edict_t * self ) ;
+extern void gekk_idle_loop ( edict_t * self ) ;
+extern void gekk_stand ( edict_t * self ) ;
+extern void gekk_swim ( edict_t * self ) ;
+extern void gekk_swim_loop ( edict_t * self ) ;
+extern void ai_stand2 ( edict_t * self , float dist ) ;
+extern void gekk_face ( edict_t * self ) ;
+extern void gekk_swing ( edict_t * self ) ;
+extern void gekk_search ( edict_t * self ) ;
+extern void gekk_sight ( edict_t * self , edict_t * other ) ;
+extern void gekk_step ( edict_t * self ) ;
+extern qboolean gekk_checkattack ( edict_t * self ) ;
+extern qboolean gekk_check_jump_close ( edict_t * self ) ;
+extern qboolean gekk_check_jump ( edict_t * self ) ;
+extern qboolean gekk_check_melee ( edict_t * self ) ;
+extern void SP_monster_flyer ( edict_t * self ) ;
+extern void flyer_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void flyer_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void flyer_check_melee ( edict_t * self ) ;
+extern void flyer_melee ( edict_t * self ) ;
+extern void flyer_nextmove ( edict_t * self ) ;
+extern void flyer_setstart ( edict_t * self ) ;
+extern void flyer_attack ( edict_t * self ) ;
+extern void flyer_loop_melee ( edict_t * self ) ;
+extern void flyer_slash_right ( edict_t * self ) ;
+extern void flyer_slash_left ( edict_t * self ) ;
+extern void flyer_fireright ( edict_t * self ) ;
+extern void flyer_fireleft ( edict_t * self ) ;
+extern void flyer_fire ( edict_t * self , int flash_number ) ;
+extern void flyer_start ( edict_t * self ) ;
+extern void flyer_stop ( edict_t * self ) ;
+extern void flyer_stand ( edict_t * self ) ;
+extern void flyer_walk ( edict_t * self ) ;
+extern void flyer_run ( edict_t * self ) ;
+extern void flyer_pop_blades ( edict_t * self ) ;
+extern void flyer_idle ( edict_t * self ) ;
+extern void flyer_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_floater ( edict_t * self ) ;
+extern void floater_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void floater_dead ( edict_t * self ) ;
+extern void floater_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void floater_melee ( edict_t * self ) ;
+extern void floater_attack ( edict_t * self ) ;
+extern void floater_zap ( edict_t * self ) ;
+extern void floater_wham ( edict_t * self ) ;
+extern void floater_walk ( edict_t * self ) ;
+extern void floater_run ( edict_t * self ) ;
+extern void floater_stand ( edict_t * self ) ;
+extern void floater_fire_blaster ( edict_t * self ) ;
+extern void floater_idle ( edict_t * self ) ;
+extern void floater_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_flipper ( edict_t * self ) ;
+extern void flipper_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void flipper_sight ( edict_t * self , edict_t * other ) ;
+extern void flipper_dead ( edict_t * self ) ;
+extern void flipper_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void flipper_melee ( edict_t * self ) ;
+extern void flipper_preattack ( edict_t * self ) ;
+extern void flipper_bite ( edict_t * self ) ;
+extern void flipper_start_run ( edict_t * self ) ;
+extern void flipper_walk ( edict_t * self ) ;
+extern void flipper_run ( edict_t * self ) ;
+extern void flipper_run_loop ( edict_t * self ) ;
+extern void flipper_stand ( edict_t * self ) ;
+extern void SP_monster_fixbot ( edict_t * self ) ;
+extern void bot_goal_think ( edict_t *self ) ;
+extern void fixbot_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void fixbot_dead ( edict_t * self ) ;
+extern void fixbot_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void fixbot_attack ( edict_t * self ) ;
+extern void fixbot_start_attack ( edict_t * self ) ;
+extern void fixbot_walk ( edict_t * self ) ;
+extern void fixbot_run ( edict_t * self ) ;
+extern void fixbot_stand ( edict_t * self ) ;
+extern void fixbot_fire_blaster ( edict_t * self ) ;
+extern void fixbot_fire_welder ( edict_t * self ) ;
+extern void ai_move2 ( edict_t * self , float dist ) ;
+extern void weldstate ( edict_t * self ) ;
+extern void fixbot_fire_laser ( edict_t * self ) ;
+extern int check_telefrag ( edict_t * self ) ;
+extern void ai_movetogoal ( edict_t * self , float dist ) ;
+extern void go_roam ( edict_t * self ) ;
+extern void ai_facing ( edict_t * self , float dist ) ;
+extern void fly_vertical2 ( edict_t * self ) ;
+extern void fly_vertical ( edict_t * self ) ;
+extern void blastoff ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int te_impact , int hspread , int vspread ) ;
+extern void use_scanner ( edict_t * self ) ;
+extern void roam_goal ( edict_t * self ) ;
+extern void change_to_roam ( edict_t * self ) ;
+extern void takeoff_goal ( edict_t * self ) ;
+extern void landing_goal ( edict_t * self ) ;
+extern int fixbot_search ( edict_t * self ) ;
+extern edict_t * fixbot_FindDeadMonster ( edict_t * self ) ;
+extern float crand ( void ) ;
+extern void SP_monster_chick_heat ( edict_t * self ) ;
+extern void SP_monster_chick ( edict_t * self ) ;
+extern void chick_sight ( edict_t * self , edict_t * other ) ;
+extern void chick_attack ( edict_t * self ) ;
+extern void chick_melee ( edict_t * self ) ;
+extern void chick_slash ( edict_t * self ) ;
+extern void chick_reslash ( edict_t * self ) ;
+extern void chick_attack1 ( edict_t * self ) ;
+extern void chick_rerocket ( edict_t * self ) ;
+extern void ChickReload ( edict_t * self ) ;
+extern void Chick_PreAttack1 ( edict_t * self ) ;
+extern void ChickRocket ( edict_t * self ) ;
+extern void ChickSlash ( edict_t * self ) ;
+extern void chick_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void chick_duck_up ( edict_t * self ) ;
+extern void chick_duck_hold ( edict_t * self ) ;
+extern void chick_duck_down ( edict_t * self ) ;
+extern void chick_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void chick_dead ( edict_t * self ) ;
+extern void chick_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void chick_run ( edict_t * self ) ;
+extern void chick_walk ( edict_t * self ) ;
+extern void chick_stand ( edict_t * self ) ;
+extern void chick_fidget ( edict_t * self ) ;
+extern void ChickMoan ( edict_t * self ) ;
+extern void SP_monster_brain ( edict_t * self ) ;
+extern void brain_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void brain_dead ( edict_t * self ) ;
+extern void brain_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void brain_run ( edict_t * self ) ;
+extern void brain_attack ( edict_t * self ) ;
+extern void brain_laserbeam_reattack ( edict_t * self ) ;
+extern void brain_laserbeam ( edict_t * self ) ;
+extern void brain_tounge_attack ( edict_t * self ) ;
+extern qboolean brain_tounge_attack_ok ( vec3_t start , vec3_t end ) ;
+extern void brain_melee ( edict_t * self ) ;
+extern void brain_chest_closed ( edict_t * self ) ;
+extern void brain_tentacle_attack ( edict_t * self ) ;
+extern void brain_chest_open ( edict_t * self ) ;
+extern void brain_hit_left ( edict_t * self ) ;
+extern void brain_swing_left ( edict_t * self ) ;
+extern void brain_hit_right ( edict_t * self ) ;
+extern void brain_swing_right ( edict_t * self ) ;
+extern void brain_dodge ( edict_t * self , edict_t * attacker , float eta ) ;
+extern void brain_duck_up ( edict_t * self ) ;
+extern void brain_duck_hold ( edict_t * self ) ;
+extern void brain_duck_down ( edict_t * self ) ;
+extern void brain_walk ( edict_t * self ) ;
+extern void brain_idle ( edict_t * self ) ;
+extern void brain_stand ( edict_t * self ) ;
+extern void brain_search ( edict_t * self ) ;
+extern void brain_sight ( edict_t * self , edict_t * other ) ;
+extern void SP_monster_boss5 ( edict_t * self ) ;
+extern void boss5_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void BossExplode2 ( edict_t * self ) ;
+extern void boss5_dead ( edict_t * self ) ;
+extern void boss5_attack ( edict_t * self ) ;
+extern void boss5MachineGun ( edict_t * self ) ;
+extern void boss5Rocket ( edict_t * self ) ;
+extern void boss5_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void boss5_reattack1 ( edict_t * self ) ;
+extern void boss5_run ( edict_t * self ) ;
+extern void boss5_walk ( edict_t * self ) ;
+extern void boss5_forward ( edict_t * self ) ;
+extern void boss5_stand ( edict_t * self ) ;
+extern void boss5_search ( edict_t * self ) ;
+extern void TreadSound2 ( edict_t * self ) ;
+extern void MakronToss ( edict_t * self ) ;
+extern void MakronSpawn ( edict_t * self ) ;
+extern void SP_monster_makron ( edict_t * self ) ;
+extern void MakronPrecache ( void ) ;
+extern qboolean Makron_CheckAttack ( edict_t * self ) ;
+extern void makron_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void makron_torso_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void makron_dead ( edict_t * self ) ;
+extern void makron_torso ( edict_t * ent ) ;
+extern void makron_torso_think ( edict_t * self ) ;
+extern void makron_attack ( edict_t * self ) ;
+extern void makron_sight ( edict_t * self , edict_t * other ) ;
+extern void makron_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void MakronHyperblaster ( edict_t * self ) ;
+extern void MakronRailgun ( edict_t * self ) ;
+extern void MakronSaveloc ( edict_t * self ) ;
+extern void makronBFG ( edict_t * self ) ;
+extern void makron_run ( edict_t * self ) ;
+extern void makron_walk ( edict_t * self ) ;
+extern void makron_prerailgun ( edict_t * self ) ;
+extern void makron_brainsplorch ( edict_t * self ) ;
+extern void makron_step_right ( edict_t * self ) ;
+extern void makron_step_left ( edict_t * self ) ;
+extern void makron_popup ( edict_t * self ) ;
+extern void makron_hit ( edict_t * self ) ;
+extern void makron_stand ( edict_t * self ) ;
+extern void makron_taunt ( edict_t * self ) ;
+extern void SP_monster_jorg ( edict_t * self ) ;
+extern qboolean Jorg_CheckAttack ( edict_t * self ) ;
+extern void jorg_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void jorg_dead ( edict_t * self ) ;
+extern void jorg_attack ( edict_t * self ) ;
+extern void jorg_firebullet ( edict_t * self ) ;
+extern void jorg_firebullet_left ( edict_t * self ) ;
+extern void jorg_firebullet_right ( edict_t * self ) ;
+extern void jorgBFG ( edict_t * self ) ;
+extern void jorg_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void jorg_attack1 ( edict_t * self ) ;
+extern void jorg_reattack1 ( edict_t * self ) ;
+extern void jorg_run ( edict_t * self ) ;
+extern void jorg_walk ( edict_t * self ) ;
+extern void jorg_stand ( edict_t * self ) ;
+extern void jorg_step_right ( edict_t * self ) ;
+extern void jorg_step_left ( edict_t * self ) ;
+extern void jorg_death_hit ( edict_t * self ) ;
+extern void jorg_idle ( edict_t * self ) ;
+extern void jorg_search ( edict_t * self ) ;
+extern void SP_monster_boss3_stand ( edict_t * self ) ;
+extern void Think_Boss3Stand ( edict_t * ent ) ;
+extern void Use_Boss3 ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_monster_boss2 ( edict_t * self ) ;
+extern qboolean Boss2_CheckAttack ( edict_t * self ) ;
+extern void boss2_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void boss2_dead ( edict_t * self ) ;
+extern void boss2_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void boss2_reattack_mg ( edict_t * self ) ;
+extern void boss2_attack_mg ( edict_t * self ) ;
+extern void boss2_attack ( edict_t * self ) ;
+extern void boss2_walk ( edict_t * self ) ;
+extern void boss2_run ( edict_t * self ) ;
+extern void boss2_stand ( edict_t * self ) ;
+extern void Boss2MachineGun ( edict_t * self ) ;
+extern void boss2_firebullet_left ( edict_t * self ) ;
+extern void boss2_firebullet_right ( edict_t * self ) ;
+extern void Boss2Rocket ( edict_t * self ) ;
+extern void boss2_search ( edict_t * self ) ;
+extern void SP_monster_berserk ( edict_t * self ) ;
+extern void berserk_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void berserk_dead ( edict_t * self ) ;
+extern void berserk_pain ( edict_t * self , edict_t * other , float kick , int damage ) ;
+extern void berserk_melee ( edict_t * self ) ;
+extern void berserk_strike ( edict_t * self ) ;
+extern void berserk_attack_club ( edict_t * self ) ;
+extern void berserk_swing ( edict_t * self ) ;
+extern void berserk_attack_spike ( edict_t * self ) ;
+extern void berserk_run ( edict_t * self ) ;
+extern void berserk_walk ( edict_t * self ) ;
+extern void berserk_fidget ( edict_t * self ) ;
+extern void berserk_stand ( edict_t * self ) ;
+extern void berserk_search ( edict_t * self ) ;
+extern void berserk_sight ( edict_t * self , edict_t * other ) ;
+extern void fire_trap ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , float timer , float damage_radius , qboolean held ) ;
+extern void Trap_Think ( edict_t * ent ) ;
+extern void fire_plasma ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , float damage_radius , int radius_damage ) ;
+extern void plasma_touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void fire_heat ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , float damage_radius , int radius_damage ) ;
+extern void heat_think ( edict_t * self ) ;
+extern void fire_ionripper ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int effect ) ;
+extern void ionripper_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void ionripper_sparks ( edict_t * self ) ;
+extern void fire_bfg ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , float damage_radius ) ;
+extern void bfg_think ( edict_t * self ) ;
+extern void bfg_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void bfg_explode ( edict_t * self ) ;
+extern void fire_rail ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick ) ;
+extern void fire_rocket ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , float damage_radius , int radius_damage ) ;
+extern void rocket_touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void fire_grenade2 ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , float timer , float damage_radius , qboolean held ) ;
+extern void fire_grenade ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , float timer , float damage_radius ) ;
+extern void Grenade_Touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Grenade_Explode ( edict_t * ent ) ;
+extern void fire_blueblaster ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int effect ) ;
+extern void fire_blaster ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int effect , qboolean hyper ) ;
+extern void blaster_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void fire_shotgun ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int hspread , int vspread , int count , int mod ) ;
+extern void fire_bullet ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int hspread , int vspread , int mod ) ;
+extern void fire_lead ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int te_impact , int hspread , int vspread , int mod ) ;
+extern qboolean fire_hit ( edict_t * self , vec3_t aim , int damage , int kick ) ;
+extern void check_dodge ( edict_t * self , vec3_t start , vec3_t dir , int speed ) ;
+extern qboolean KillBox ( edict_t * ent ) ;
+extern void G_TouchSolids ( edict_t * ent ) ;
+extern void G_TouchTriggers ( edict_t * ent ) ;
+extern void G_FreeEdict ( edict_t * ed ) ;
+extern edict_t * G_Spawn ( void ) ;
+extern void G_InitEdict ( edict_t * e ) ;
+extern char * G_CopyString ( char * in ) ;
+extern void vectoangles ( vec3_t value1 , vec3_t angles ) ;
+extern float vectoyaw ( vec3_t vec ) ;
+extern void G_SetMovedir ( vec3_t angles , vec3_t movedir ) ;
+extern char * vtos ( vec3_t v ) ;
+extern float * tv ( float x , float y , float z ) ;
+extern void G_UseTargets ( edict_t * ent , edict_t * activator ) ;
+extern void Think_Delay ( edict_t * ent ) ;
+extern edict_t * G_PickTarget ( char * targetname ) ;
+extern edict_t * findradius ( edict_t * from , vec3_t org , float rad ) ;
+extern edict_t * G_Find ( edict_t * from , int fieldofs , char * match ) ;
+extern void G_ProjectSource ( vec3_t point , vec3_t distance , vec3_t forward , vec3_t right , vec3_t result ) ;
+extern void SP_turret_driver ( edict_t * self ) ;
+extern void turret_driver_link ( edict_t * self ) ;
+extern void turret_driver_think ( edict_t * self ) ;
+extern void turret_driver_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void SP_turret_base ( edict_t * self ) ;
+extern void SP_turret_breach ( edict_t * self ) ;
+extern void turret_breach_finish_init ( edict_t * self ) ;
+extern void turret_breach_think ( edict_t * self ) ;
+extern void turret_breach_fire ( edict_t * self ) ;
+extern void turret_blocked ( edict_t * self , edict_t * other ) ;
+extern float SnapToEights ( float x ) ;
+extern void AnglesNormalize ( vec3_t vec ) ;
+extern void SP_trigger_monsterjump ( edict_t * self ) ;
+extern void trigger_monsterjump_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_trigger_gravity ( edict_t * self ) ;
+extern void trigger_gravity_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_trigger_hurt ( edict_t * self ) ;
+extern void hurt_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void hurt_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_push ( edict_t * self ) ;
+extern void trigger_push_active ( edict_t * self ) ;
+extern void trigger_push_inactive ( edict_t * self ) ;
+extern void trigger_effect ( edict_t * self ) ;
+extern void trigger_push_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_trigger_always ( edict_t * ent ) ;
+extern void SP_trigger_counter ( edict_t * self ) ;
+extern void trigger_counter_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_key ( edict_t * self ) ;
+extern void trigger_key_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_relay ( edict_t * self ) ;
+extern void trigger_relay_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_trigger_once ( edict_t * ent ) ;
+extern void SP_trigger_multiple ( edict_t * ent ) ;
+extern void trigger_enable ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void Touch_Multi ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Use_Multi ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void multi_trigger ( edict_t * ent ) ;
+extern void multi_wait ( edict_t * ent ) ;
+extern void InitTrigger ( edict_t * self ) ;
+extern void SP_target_earthquake ( edict_t * self ) ;
+extern void target_earthquake_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_earthquake_think ( edict_t * self ) ;
+extern void SP_target_lightramp ( edict_t * self ) ;
+extern void target_lightramp_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_lightramp_think ( edict_t * self ) ;
+extern void SP_target_mal_laser ( edict_t * self ) ;
+extern void mal_laser_think ( edict_t * self ) ;
+extern void target_mal_laser_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_mal_laser_off ( edict_t * self ) ;
+extern void target_mal_laser_on ( edict_t * self ) ;
+extern void SP_target_laser ( edict_t * self ) ;
+extern void target_laser_start ( edict_t * self ) ;
+extern void target_laser_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_laser_off ( edict_t * self ) ;
+extern void target_laser_on ( edict_t * self ) ;
+extern void target_laser_think ( edict_t * self ) ;
+extern void SP_target_crosslevel_target ( edict_t * self ) ;
+extern void target_crosslevel_target_think ( edict_t * self ) ;
+extern void SP_target_crosslevel_trigger ( edict_t * self ) ;
+extern void trigger_crosslevel_trigger_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_blaster ( edict_t * self ) ;
+extern void use_target_blaster ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_spawner ( edict_t * self ) ;
+extern void use_target_spawner ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_splash ( edict_t * self ) ;
+extern void use_target_splash ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_changelevel ( edict_t * ent ) ;
+extern void use_target_changelevel ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_explosion ( edict_t * ent ) ;
+extern void use_target_explosion ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void target_explosion_explode ( edict_t * self ) ;
+extern void SP_target_goal ( edict_t * ent ) ;
+extern void use_target_goal ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_secret ( edict_t * ent ) ;
+extern void use_target_secret ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_help ( edict_t * ent ) ;
+extern void Use_Target_Help ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_speaker ( edict_t * ent ) ;
+extern void Use_Target_Speaker ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_target_temp_entity ( edict_t * ent ) ;
+extern void Use_Target_Tent ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void ServerCommand ( void ) ;
+extern void SVCmd_WriteIP_f ( void ) ;
+extern void SVCmd_ListIP_f ( void ) ;
+extern void SVCmd_RemoveIP_f ( void ) ;
+extern void SVCmd_AddIP_f ( void ) ;
+extern qboolean SV_FilterPacket ( char * from ) ;
+extern void Svcmd_Test_f ( void ) ;
+extern void SP_worldspawn ( edict_t * ent ) ;
+extern void SpawnEntities ( const char * mapname , char * entities , const char * spawnpoint ) ;
+extern void G_FindTeams ( void ) ;
+extern char * ED_ParseEdict ( char * data , edict_t * ent ) ;
+extern void ED_ParseField ( const char * key , const char * value , edict_t * ent ) ;
+extern char * ED_NewString ( const char * string ) ;
+extern void ED_CallSpawn ( edict_t * ent ) ;
+extern void G_RunEntity ( edict_t * ent ) ;
+extern void SV_Physics_Step ( edict_t * ent ) ;
+extern void SV_AddRotationalFriction ( edict_t * ent ) ;
+extern void SV_Physics_Toss ( edict_t * ent ) ;
+extern void SV_Physics_Noclip ( edict_t * ent ) ;
+extern void SV_Physics_None ( edict_t * ent ) ;
+extern void SV_Physics_Pusher ( edict_t * ent ) ;
+extern qboolean SV_Push ( edict_t * pusher , vec3_t move , vec3_t amove ) ;
+extern trace_t SV_PushEntity ( edict_t * ent , vec3_t push ) ;
+extern void RealBoundingBox ( edict_t * ent , vec3_t mins , vec3_t maxs ) ;
+extern void SV_AddGravity ( edict_t * ent ) ;
+extern int SV_FlyMove ( edict_t * ent , float time , int mask ) ;
+extern int ClipVelocity ( vec3_t in , vec3_t normal , vec3_t out , float overbounce ) ;
+extern void SV_Impact ( edict_t * e1 , trace_t * trace ) ;
+extern qboolean SV_RunThink ( edict_t * ent ) ;
+extern void SV_CheckVelocity ( edict_t * ent ) ;
+extern edict_t * SV_TestEntityPosition ( edict_t * ent ) ;
+extern void swimmonster_start ( edict_t * self ) ;
+extern void swimmonster_start_go ( edict_t * self ) ;
+extern void flymonster_start ( edict_t * self ) ;
+extern void flymonster_start_go ( edict_t * self ) ;
+extern void walkmonster_start ( edict_t * self ) ;
+extern void walkmonster_start_go ( edict_t * self ) ;
+extern void monster_start_go ( edict_t * self ) ;
+extern qboolean monster_start ( edict_t * self ) ;
+extern void monster_death_use ( edict_t * self ) ;
+extern void monster_triggered_start ( edict_t * self ) ;
+extern void monster_triggered_spawn_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void monster_triggered_spawn ( edict_t * self ) ;
+extern void monster_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void monster_think ( edict_t * self ) ;
+extern void M_MoveFrame ( edict_t * self ) ;
+extern void M_SetEffects ( edict_t * ent ) ;
+extern void M_droptofloor ( edict_t * ent ) ;
+extern void M_WorldEffects ( edict_t * ent ) ;
+extern void M_CatagorizePosition ( edict_t * ent ) ;
+extern void M_CheckGround ( edict_t * ent ) ;
+extern void AttackFinished ( edict_t * self , float time ) ;
+extern void M_FlyCheck ( edict_t * self ) ;
+extern void M_FliesOn ( edict_t * self ) ;
+extern void M_FliesOff ( edict_t * self ) ;
+extern void monster_fire_bfg ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , int kick , float damage_radius , int flashtype ) ;
+extern void monster_fire_railgun ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int flashtype ) ;
+extern void monster_fire_rocket ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype ) ;
+extern void monster_fire_grenade ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int speed , int flashtype ) ;
+extern void monster_dabeam ( edict_t * self ) ;
+extern void dabeam_hit ( edict_t * self ) ;
+extern void monster_fire_heat ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype ) ;
+extern void monster_fire_ionripper ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype , int effect ) ;
+extern void monster_fire_blueblaster ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype , int effect ) ;
+extern void monster_fire_blaster ( edict_t * self , vec3_t start , vec3_t dir , int damage , int speed , int flashtype , int effect ) ;
+extern void monster_fire_shotgun ( edict_t * self , vec3_t start , vec3_t aimdir , int damage , int kick , int hspread , int vspread , int count , int flashtype ) ;
+extern void monster_fire_bullet ( edict_t * self , vec3_t start , vec3_t dir , int damage , int kick , int hspread , int vspread , int flashtype ) ;
+extern void SP_misc_nuke ( edict_t * ent ) ;
+extern void use_nuke ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_amb4 ( edict_t * ent ) ;
+extern void amb4_think ( edict_t * ent ) ;
+extern void SP_misc_teleporter_dest ( edict_t * ent ) ;
+extern void SP_misc_teleporter ( edict_t * ent ) ;
+extern void teleporter_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_func_clock ( edict_t * self ) ;
+extern void func_clock_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_clock_think ( edict_t * self ) ;
+extern void func_clock_format_countdown ( edict_t * self ) ;
+extern void func_clock_reset ( edict_t * self ) ;
+extern void SP_target_string ( edict_t * self ) ;
+extern void target_string_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_target_character ( edict_t * self ) ;
+extern void SP_misc_gib_head ( edict_t * ent ) ;
+extern void SP_misc_gib_leg ( edict_t * ent ) ;
+extern void SP_misc_gib_arm ( edict_t * ent ) ;
+extern void SP_light_mine2 ( edict_t * ent ) ;
+extern void SP_light_mine1 ( edict_t * ent ) ;
+extern void SP_misc_satellite_dish ( edict_t * ent ) ;
+extern void misc_satellite_dish_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void misc_satellite_dish_think ( edict_t * self ) ;
+extern void SP_misc_transport ( edict_t * ent ) ;
+extern void SP_misc_strogg_ship ( edict_t * ent ) ;
+extern void misc_strogg_ship_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_viper_missile ( edict_t * self ) ;
+extern void misc_viper_missile_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_viper_bomb ( edict_t * self ) ;
+extern void misc_viper_bomb_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void misc_viper_bomb_prethink ( edict_t * self ) ;
+extern void misc_viper_bomb_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_misc_bigviper ( edict_t * ent ) ;
+extern void SP_misc_crashviper ( edict_t * ent ) ;
+extern void SP_misc_viper ( edict_t * ent ) ;
+extern void misc_viper_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_deadsoldier ( edict_t * ent ) ;
+extern void misc_deadsoldier_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void SP_misc_banner ( edict_t * ent ) ;
+extern void misc_banner_think ( edict_t * ent ) ;
+extern void SP_monster_commander_body ( edict_t * self ) ;
+extern void commander_body_drop ( edict_t * self ) ;
+extern void commander_body_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void commander_body_think ( edict_t * self ) ;
+extern void SP_misc_easterchick2 ( edict_t * ent ) ;
+extern void misc_easterchick2_think ( edict_t * self ) ;
+extern void SP_misc_easterchick ( edict_t * ent ) ;
+extern void misc_easterchick_think ( edict_t * self ) ;
+extern void SP_misc_eastertank ( edict_t * ent ) ;
+extern void misc_eastertank_think ( edict_t * self ) ;
+extern void SP_misc_blackhole ( edict_t * ent ) ;
+extern void misc_blackhole_think ( edict_t * self ) ;
+extern void misc_blackhole_use ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void SP_misc_explobox ( edict_t * self ) ;
+extern void barrel_delay ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void barrel_explode ( edict_t * self ) ;
+extern void barrel_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_func_explosive ( edict_t * self ) ;
+extern void func_explosive_spawn ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_explosive_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_explosive_explode ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void SP_func_object ( edict_t * self ) ;
+extern void func_object_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_object_release ( edict_t * self ) ;
+extern void func_object_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_func_wall ( edict_t * self ) ;
+extern void func_wall_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_light ( edict_t * self ) ;
+extern void light_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_info_notnull ( edict_t * self ) ;
+extern void SP_info_null ( edict_t * self ) ;
+extern void SP_viewthing ( edict_t * ent ) ;
+extern void TH_viewthing ( edict_t * ent ) ;
+extern void SP_point_combat ( edict_t * self ) ;
+extern void point_combat_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void SP_path_corner ( edict_t * self ) ;
+extern void path_corner_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void BecomeExplosion2 ( edict_t * self ) ;
+extern void BecomeExplosion1 ( edict_t * self ) ;
+extern void ThrowDebris ( edict_t * self , char * modelname , float speed , vec3_t origin ) ;
+extern void debris_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void ThrowClientHead ( edict_t * self , int damage ) ;
+extern void ThrowHeadACID ( edict_t * self , char * gibname , int damage , int type ) ;
+extern void ThrowGibACID ( edict_t * self , char * gibname , int damage , int type ) ;
+extern void ThrowHead ( edict_t * self , char * gibname , int damage , int type ) ;
+extern void ThrowGib ( edict_t * self , char * gibname , int damage , int type ) ;
+extern void gib_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void gib_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void gib_think ( edict_t * self ) ;
+extern void ClipGibVelocity ( edict_t * ent ) ;
+extern void VelocityForDamage ( int damage , vec3_t v ) ;
+extern void SP_func_areaportal ( edict_t * ent ) ;
+extern void Use_Areaportal ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void G_RunFrame ( void ) ;
+extern void ExitLevel ( void ) ;
+extern void CheckDMRules ( void ) ;
+extern void CheckNeedPass ( void ) ;
+extern void EndDMLevel ( void ) ;
+extern edict_t * CreateTargetChangeLevel ( char * map ) ;
+extern void ClientEndServerFrames ( void ) ;
+extern void Com_Printf ( char * msg , ... ) ;
+extern void Sys_Error ( char * error , ... ) ;
+extern game_export_t * GetGameAPI ( game_import_t * import ) ;
+extern void ShutdownGame ( void ) ;
+extern void SetItemNames ( void ) ;
+extern void InitItems ( void ) ;
+extern void SP_item_foodcube ( edict_t * self ) ;
+extern void SP_item_health_mega ( edict_t * self ) ;
+extern void SP_item_health_large ( edict_t * self ) ;
+extern void SP_item_health_small ( edict_t * self ) ;
+extern void SP_item_health ( edict_t * self ) ;
+extern void SpawnItem ( edict_t * ent , gitem_t * item ) ;
+extern void PrecacheItem ( gitem_t * it ) ;
+extern void droptofloor ( edict_t * ent ) ;
+extern void Use_Item ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern edict_t * Drop_Item ( edict_t * ent , gitem_t * item ) ;
+extern void drop_make_touchable ( edict_t * ent ) ;
+extern void drop_temp_touch ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Touch_Item ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Drop_PowerArmor ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_PowerArmor ( edict_t * ent , edict_t * other ) ;
+extern void Use_PowerArmor ( edict_t * ent , gitem_t * item ) ;
+extern int PowerArmorType ( edict_t * ent ) ;
+extern qboolean Pickup_Armor ( edict_t * ent , edict_t * other ) ;
+extern int ArmorIndex ( edict_t * ent ) ;
+extern qboolean Pickup_Health ( edict_t * ent , edict_t * other ) ;
+extern void MegaHealth_think ( edict_t * self ) ;
+extern void Drop_Ammo ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_Ammo ( edict_t * ent , edict_t * other ) ;
+extern qboolean Add_Ammo ( edict_t * ent , gitem_t * item , int count ) ;
+extern qboolean Pickup_Key ( edict_t * ent , edict_t * other ) ;
+extern void Use_Silencer ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Invulnerability ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Envirosuit ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Breather ( edict_t * ent , gitem_t * item ) ;
+extern void Use_QuadFire ( edict_t * ent , gitem_t * item ) ;
+extern void Use_Quad ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_Pack ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_Bandolier ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_AncientHead ( edict_t * ent , edict_t * other ) ;
+extern qboolean Pickup_Adrenaline ( edict_t * ent , edict_t * other ) ;
+extern void Drop_General ( edict_t * ent , gitem_t * item ) ;
+extern qboolean Pickup_Powerup ( edict_t * ent , edict_t * other ) ;
+extern void SetRespawn ( edict_t * ent , float delay ) ;
+extern void DoRespawn ( edict_t * ent ) ;
+extern gitem_t * FindItem ( char * pickup_name ) ;
+extern gitem_t * FindItemByClassname ( char * classname ) ;
+extern gitem_t * GetItemByIndex ( int index ) ;
+extern void SP_object_repair ( edict_t * ent ) ;
+extern void object_repair_sparks ( edict_t * ent ) ;
+extern void object_repair_dead ( edict_t * ent ) ;
+extern void object_repair_fx ( edict_t * ent ) ;
+extern void SP_rotating_light ( edict_t * self ) ;
+extern void rotating_light_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void rotating_light_killed ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void rotating_light_alarm ( edict_t * self ) ;
+extern void SP_func_killbox ( edict_t * ent ) ;
+extern void use_killbox ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_door_secret ( edict_t * ent ) ;
+extern void door_secret_die ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void door_secret_blocked ( edict_t * self , edict_t * other ) ;
+extern void door_secret_done ( edict_t * self ) ;
+extern void door_secret_move6 ( edict_t * self ) ;
+extern void door_secret_move5 ( edict_t * self ) ;
+extern void door_secret_move4 ( edict_t * self ) ;
+extern void door_secret_move3 ( edict_t * self ) ;
+extern void door_secret_move2 ( edict_t * self ) ;
+extern void door_secret_move1 ( edict_t * self ) ;
+extern void door_secret_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_conveyor ( edict_t * self ) ;
+extern void func_conveyor_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_timer ( edict_t * self ) ;
+extern void func_timer_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_timer_think ( edict_t * self ) ;
+extern void SP_trigger_elevator ( edict_t * self ) ;
+extern void trigger_elevator_init ( edict_t * self ) ;
+extern void trigger_elevator_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void SP_func_train ( edict_t * self ) ;
+extern void train_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void func_train_find ( edict_t * self ) ;
+extern void train_resume ( edict_t * self ) ;
+extern void train_next ( edict_t * self ) ;
+extern void train_wait ( edict_t * self ) ;
+extern void train_blocked ( edict_t * self , edict_t * other ) ;
+extern void SP_func_water ( edict_t * self ) ;
+extern void SP_func_door_rotating ( edict_t * ent ) ;
+extern void SP_func_door ( edict_t * ent ) ;
+extern void door_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void door_killed ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void door_blocked ( edict_t * self , edict_t * other ) ;
+extern void Think_SpawnDoorTrigger ( edict_t * ent ) ;
+extern void Think_CalcMoveSpeed ( edict_t * self ) ;
+extern void Touch_DoorTrigger ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void door_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void door_go_up ( edict_t * self , edict_t * activator ) ;
+extern void door_go_down ( edict_t * self ) ;
+extern void door_hit_bottom ( edict_t * self ) ;
+extern void door_hit_top ( edict_t * self ) ;
+extern void door_use_areaportals ( edict_t * self , qboolean open ) ;
+extern void SP_func_button ( edict_t * ent ) ;
+extern void button_killed ( edict_t * self , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern void button_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void button_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void button_fire ( edict_t * self ) ;
+extern void button_wait ( edict_t * self ) ;
+extern void button_return ( edict_t * self ) ;
+extern void button_done ( edict_t * self ) ;
+extern void SP_func_rotating ( edict_t * ent ) ;
+extern void rotating_use ( edict_t * self , edict_t * other , edict_t * activator ) ;
+extern void rotating_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void rotating_blocked ( edict_t * self , edict_t * other ) ;
+extern void SP_func_plat ( edict_t * ent ) ;
+extern void plat_spawn_inside_trigger ( edict_t * ent ) ;
+extern void Touch_Plat_Center ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ;
+extern void Use_Plat ( edict_t * ent , edict_t * other , edict_t * activator ) ;
+extern void plat_blocked ( edict_t * self , edict_t * other ) ;
+extern void plat_go_up ( edict_t * ent ) ;
+extern void plat_go_down ( edict_t * ent ) ;
+extern void plat_hit_bottom ( edict_t * ent ) ;
+extern void plat_hit_top ( edict_t * ent ) ;
+extern void Think_AccelMove ( edict_t * ent ) ;
+extern void plat_Accelerate ( moveinfo_t * moveinfo ) ;
+extern void plat_CalcAcceleratedMove ( moveinfo_t * moveinfo ) ;
+extern void AngleMove_Calc ( edict_t * ent , void ( * func ) ( edict_t * ) ) ;
+extern void AngleMove_Begin ( edict_t * ent ) ;
+extern void AngleMove_Final ( edict_t * ent ) ;
+extern void AngleMove_Done ( edict_t * ent ) ;
+extern void Move_Calc ( edict_t * ent , vec3_t dest , void ( * func ) ( edict_t * ) ) ;
+extern void Move_Begin ( edict_t * ent ) ;
+extern void Move_Final ( edict_t * ent ) ;
+extern void Move_Done ( edict_t * ent ) ;
+extern void T_RadiusDamage ( edict_t * inflictor , edict_t * attacker , float damage , edict_t * ignore , float radius , int mod ) ;
+extern void T_Damage ( edict_t * targ , edict_t * inflictor , edict_t * attacker , vec3_t dir , vec3_t point , vec3_t normal , int damage , int knockback , int dflags , int mod ) ;
+extern void M_ReactToDamage ( edict_t * targ , edict_t * attacker ) ;
+extern int CheckArmor ( edict_t * ent , vec3_t point , vec3_t normal , int damage , int te_sparks , int dflags ) ;
+extern int CheckPowerArmor ( edict_t * ent , vec3_t point , vec3_t normal , int damage , int dflags ) ;
+extern void SpawnDamage ( int type , vec3_t origin , vec3_t normal , int damage ) ;
+extern void Killed ( edict_t * targ , edict_t * inflictor , edict_t * attacker , int damage , vec3_t point ) ;
+extern qboolean CanDamage ( edict_t * targ , edict_t * inflictor ) ;
+extern void ClientCommand ( edict_t * ent ) ;
+extern void Cmd_PlayerList_f ( edict_t * ent ) ;
+extern void Cmd_Say_f ( edict_t * ent , qboolean team , qboolean arg0 ) ;
+extern void Cmd_Wave_f ( edict_t * ent ) ;
+extern void Cmd_Players_f ( edict_t * ent ) ;
+extern int PlayerSort ( void const * a , void const * b ) ;
+extern void Cmd_PutAway_f ( edict_t * ent ) ;
+extern void Cmd_Kill_f ( edict_t * ent ) ;
+extern void Cmd_InvDrop_f ( edict_t * ent ) ;
+extern void Cmd_WeapLast_f ( edict_t * ent ) ;
+extern void Cmd_WeapNext_f ( edict_t * ent ) ;
+extern void Cmd_WeapPrev_f ( edict_t * ent ) ;
+extern void Cmd_InvUse_f ( edict_t * ent ) ;
+extern void Cmd_Inven_f ( edict_t * ent ) ;
+extern void Cmd_Help_f ( edict_t * ent ) ;
+extern void Cmd_Score_f ( edict_t * ent ) ;
+extern void Cmd_Drop_f ( edict_t * ent ) ;
+extern void Cmd_Use_f ( edict_t * ent ) ;
+extern void Cmd_Noclip_f ( edict_t * ent ) ;
+extern void Cmd_Notarget_f ( edict_t * ent ) ;
+extern void Cmd_God_f ( edict_t * ent ) ;
+extern void Cmd_Give_f ( edict_t * ent ) ;
+extern void ValidateSelectedItem ( edict_t * ent ) ;
+extern void SelectPrevItem ( edict_t * ent , int itflags ) ;
+extern void SelectNextItem ( edict_t * ent , int itflags ) ;
+extern qboolean OnSameTeam ( edict_t * ent1 , edict_t * ent2 ) ;
+extern void GetChaseTarget ( edict_t * ent ) ;
+extern void ChasePrev ( edict_t * ent ) ;
+extern void ChaseNext ( edict_t * ent ) ;
+extern void UpdateChaseCam ( edict_t * ent ) ;
+extern void ai_run ( edict_t * self , float dist ) ;
+extern qboolean ai_checkattack ( edict_t * self , float dist ) ;
+extern void ai_run_slide ( edict_t * self , float distance ) ;
+extern void ai_run_missile ( edict_t * self ) ;
+extern void ai_run_melee ( edict_t * self ) ;
+extern qboolean M_CheckAttack ( edict_t * self ) ;
+extern qboolean FacingIdeal ( edict_t * self ) ;
+extern qboolean FindTarget ( edict_t * self ) ;
+extern void FoundTarget ( edict_t * self ) ;
+extern void HuntTarget ( edict_t * self ) ;
+extern qboolean infront ( edict_t * self , edict_t * other ) ;
+extern qboolean visible ( edict_t * self , edict_t * other ) ;
+extern int range ( edict_t * self , edict_t * other ) ;
+extern void ai_turn ( edict_t * self , float dist ) ;
+extern void ai_charge ( edict_t * self , float dist ) ;
+extern void ai_walk ( edict_t * self , float dist ) ;
+extern void ai_stand ( edict_t * self , float dist ) ;
+extern void ai_move ( edict_t * self , float dist ) ;
+extern void AI_SetSightClient ( void ) ;
+extern void wait_and_change_think(edict_t* ent);
diff --git a/xatrix/src/savegame/tables/gamefunc_list.h b/xatrix/src/savegame/tables/gamefunc_list.h
new file mode 100644
index 0000000..717510d
--- /dev/null
+++ b/xatrix/src/savegame/tables/gamefunc_list.h
@@ -0,0 +1,1232 @@
+/*
+ * =======================================================================
+ *
+ * Functionpointers to every function in the game.so.
+ *
+ * =======================================================================
+ */
+
+{"ReadLevel", (byte *)ReadLevel},
+{"ReadLevelLocals", (byte *)ReadLevelLocals},
+{"ReadEdict", (byte *)ReadEdict},
+{"WriteLevel", (byte *)WriteLevel},
+{"WriteLevelLocals", (byte *)WriteLevelLocals},
+{"WriteEdict", (byte *)WriteEdict},
+{"ReadGame", (byte *)ReadGame},
+{"WriteGame", (byte *)WriteGame},
+{"ReadClient", (byte *)ReadClient},
+{"WriteClient", (byte *)WriteClient},
+{"ReadField", (byte *)ReadField},
+{"WriteField2", (byte *)WriteField2},
+{"WriteField1", (byte *)WriteField1},
+{"FindMmoveByName", (byte *)FindMmoveByName},
+{"GetMmoveByAddress", (byte *)GetMmoveByAddress},
+{"FindFunctionByName", (byte *)FindFunctionByName},
+{"GetFunctionByAddress", (byte *)GetFunctionByAddress},
+{"InitGame", (byte *)InitGame},
+{"Info_SetValueForKey", (byte *)Info_SetValueForKey},
+{"Info_Validate", (byte *)Info_Validate},
+{"Info_RemoveKey", (byte *)Info_RemoveKey},
+{"Info_ValueForKey", (byte *)Info_ValueForKey},
+{"Com_sprintf", (byte *)Com_sprintf},
+{"Q_strcasecmp", (byte *)Q_strcasecmp},
+{"Q_strncasecmp", (byte *)Q_strncasecmp},
+{"Q_stricmp", (byte *)Q_stricmp},
+{"Com_PageInMemory", (byte *)Com_PageInMemory},
+{"COM_Parse", (byte *)COM_Parse},
+{"va", (byte *)va},
+{"Swap_Init", (byte *)Swap_Init},
+{"FloatNoSwap", (byte *)FloatNoSwap},
+{"FloatSwap", (byte *)FloatSwap},
+{"LongNoSwap", (byte *)LongNoSwap},
+{"LongSwap", (byte *)LongSwap},
+{"ShortNoSwap", (byte *)ShortNoSwap},
+{"ShortSwap", (byte *)ShortSwap},
+{"LittleFloat", (byte *)LittleFloat},
+{"BigFloat", (byte *)BigFloat},
+{"LittleLong", (byte *)LittleLong},
+{"BigLong", (byte *)BigLong},
+{"LittleShort", (byte *)LittleShort},
+{"BigShort", (byte *)BigShort},
+{"COM_DefaultExtension", (byte *)COM_DefaultExtension},
+{"COM_FilePath", (byte *)COM_FilePath},
+{"COM_FileBase", (byte *)COM_FileBase},
+{"COM_FileExtension", (byte *)COM_FileExtension},
+{"COM_StripExtension", (byte *)COM_StripExtension},
+{"COM_SkipPath", (byte *)COM_SkipPath},
+{"Q_log2", (byte *)Q_log2},
+{"VectorScale", (byte *)VectorScale},
+{"VectorInverse", (byte *)VectorInverse},
+{"VectorLength", (byte *)VectorLength},
+{"CrossProduct", (byte *)CrossProduct},
+{"_VectorCopy", (byte *)_VectorCopy},
+{"_VectorAdd", (byte *)_VectorAdd},
+{"_VectorSubtract", (byte *)_VectorSubtract},
+{"_DotProduct", (byte *)_DotProduct},
+{"VectorMA", (byte *)VectorMA},
+{"VectorNormalize2", (byte *)VectorNormalize2},
+{"VectorNormalize", (byte *)VectorNormalize},
+{"VectorCompare", (byte *)VectorCompare},
+{"AddPointToBounds", (byte *)AddPointToBounds},
+{"ClearBounds", (byte *)ClearBounds},
+{"BoxOnPlaneSide2", (byte *)BoxOnPlaneSide2},
+{"anglemod", (byte *)anglemod},
+{"LerpAngle", (byte *)LerpAngle},
+{"Q_fabs", (byte *)Q_fabs},
+{"R_ConcatTransforms", (byte *)R_ConcatTransforms},
+{"R_ConcatRotations", (byte *)R_ConcatRotations},
+{"PerpendicularVector", (byte *)PerpendicularVector},
+{"ProjectPointOnPlane", (byte *)ProjectPointOnPlane},
+{"AngleVectors", (byte *)AngleVectors},
+{"RotatePointAroundVector", (byte *)RotatePointAroundVector},
+{"Weapon_Trap", (byte *)Weapon_Trap},
+{"weapon_trap_fire", (byte *)weapon_trap_fire},
+{"Weapon_Phalanx", (byte *)Weapon_Phalanx},
+{"weapon_phalanx_fire", (byte *)weapon_phalanx_fire},
+{"Weapon_Ionripper", (byte *)Weapon_Ionripper},
+{"weapon_ionripper_fire", (byte *)weapon_ionripper_fire},
+{"Weapon_BFG", (byte *)Weapon_BFG},
+{"weapon_bfg_fire", (byte *)weapon_bfg_fire},
+{"Weapon_Railgun", (byte *)Weapon_Railgun},
+{"weapon_railgun_fire", (byte *)weapon_railgun_fire},
+{"Weapon_SuperShotgun", (byte *)Weapon_SuperShotgun},
+{"weapon_supershotgun_fire", (byte *)weapon_supershotgun_fire},
+{"Weapon_Shotgun", (byte *)Weapon_Shotgun},
+{"weapon_shotgun_fire", (byte *)weapon_shotgun_fire},
+{"Weapon_Chaingun", (byte *)Weapon_Chaingun},
+{"Chaingun_Fire", (byte *)Chaingun_Fire},
+{"Weapon_Machinegun", (byte *)Weapon_Machinegun},
+{"Machinegun_Fire", (byte *)Machinegun_Fire},
+{"Weapon_HyperBlaster", (byte *)Weapon_HyperBlaster},
+{"Weapon_HyperBlaster_Fire", (byte *)Weapon_HyperBlaster_Fire},
+{"Weapon_Blaster", (byte *)Weapon_Blaster},
+{"Weapon_Blaster_Fire", (byte *)Weapon_Blaster_Fire},
+{"Blaster_Fire", (byte *)Blaster_Fire},
+{"Weapon_RocketLauncher", (byte *)Weapon_RocketLauncher},
+{"Weapon_RocketLauncher_Fire", (byte *)Weapon_RocketLauncher_Fire},
+{"Weapon_GrenadeLauncher", (byte *)Weapon_GrenadeLauncher},
+{"weapon_grenadelauncher_fire", (byte *)weapon_grenadelauncher_fire},
+{"Weapon_Grenade", (byte *)Weapon_Grenade},
+{"weapon_grenade_fire", (byte *)weapon_grenade_fire},
+{"Weapon_Generic", (byte *)Weapon_Generic},
+{"Drop_Weapon", (byte *)Drop_Weapon},
+{"Use_Weapon2", (byte *)Use_Weapon2},
+{"Use_Weapon", (byte *)Use_Weapon},
+{"Think_Weapon", (byte *)Think_Weapon},
+{"NoAmmoWeaponChange", (byte *)NoAmmoWeaponChange},
+{"ChangeWeapon", (byte *)ChangeWeapon},
+{"Pickup_Weapon", (byte *)Pickup_Weapon},
+{"PlayerNoise", (byte *)PlayerNoise},
+{"P_ProjectSource", (byte *)P_ProjectSource},
+{"ClientEndServerFrame", (byte *)ClientEndServerFrame},
+{"G_SetClientFrame", (byte *)G_SetClientFrame},
+{"G_SetClientSound", (byte *)G_SetClientSound},
+{"G_SetClientEvent", (byte *)G_SetClientEvent},
+{"G_SetClientEffects", (byte *)G_SetClientEffects},
+{"P_WorldEffects", (byte *)P_WorldEffects},
+{"P_FallingDamage", (byte *)P_FallingDamage},
+{"SV_CalcBlend", (byte *)SV_CalcBlend},
+{"SV_AddBlend", (byte *)SV_AddBlend},
+{"SV_CalcGunOffset", (byte *)SV_CalcGunOffset},
+{"SV_CalcViewOffset", (byte *)SV_CalcViewOffset},
+{"P_DamageFeedback", (byte *)P_DamageFeedback},
+{"SV_CalcRoll", (byte *)SV_CalcRoll},
+{"PlayerTrail_LastSpot", (byte *)PlayerTrail_LastSpot},
+{"PlayerTrail_PickNext", (byte *)PlayerTrail_PickNext},
+{"PlayerTrail_PickFirst", (byte *)PlayerTrail_PickFirst},
+{"PlayerTrail_New", (byte *)PlayerTrail_New},
+{"PlayerTrail_Add", (byte *)PlayerTrail_Add},
+{"PlayerTrail_Init", (byte *)PlayerTrail_Init},
+{"G_SetSpectatorStats", (byte *)G_SetSpectatorStats},
+{"G_CheckChaseStats", (byte *)G_CheckChaseStats},
+{"G_SetStats", (byte *)G_SetStats},
+{"InventoryMessage", (byte *)InventoryMessage},
+{"HelpComputerMessage", (byte *)HelpComputerMessage},
+{"DeathmatchScoreboardMessage", (byte *)DeathmatchScoreboardMessage},
+{"BeginIntermission", (byte *)BeginIntermission},
+{"MoveClientToIntermission", (byte *)MoveClientToIntermission},
+{"ClientBeginServerFrame", (byte *)ClientBeginServerFrame},
+{"ClientThink", (byte *)ClientThink},
+{"PrintPmove", (byte *)PrintPmove},
+{"CheckBlock", (byte *)CheckBlock},
+{"PM_trace", (byte *)PM_trace},
+{"ClientDisconnect", (byte *)ClientDisconnect},
+{"ClientConnect", (byte *)ClientConnect},
+{"ClientUserinfoChanged", (byte *)ClientUserinfoChanged},
+{"ClientBegin", (byte *)ClientBegin},
+{"ClientBeginDeathmatch", (byte *)ClientBeginDeathmatch},
+{"PutClientInServer", (byte *)PutClientInServer},
+{"spectator_respawn", (byte *)spectator_respawn},
+{"respawn", (byte *)respawn},
+{"CopyToBodyQue", (byte *)CopyToBodyQue},
+{"body_die", (byte *)body_die},
+{"InitBodyQue", (byte *)InitBodyQue},
+{"SelectSpawnPoint", (byte *)SelectSpawnPoint},
+{"SelectCoopSpawnPoint", (byte *)SelectCoopSpawnPoint},
+{"SelectDeathmatchSpawnPoint", (byte *)SelectDeathmatchSpawnPoint},
+{"SelectFarthestDeathmatchSpawnPoint", (byte *)SelectFarthestDeathmatchSpawnPoint},
+{"SelectRandomDeathmatchSpawnPoint", (byte *)SelectRandomDeathmatchSpawnPoint},
+{"PlayersRangeFromSpot", (byte *)PlayersRangeFromSpot},
+{"FetchClientEntData", (byte *)FetchClientEntData},
+{"SaveClientData", (byte *)SaveClientData},
+{"InitClientResp", (byte *)InitClientResp},
+{"InitClientPersistant", (byte *)InitClientPersistant},
+{"player_die", (byte *)player_die},
+{"LookAtKiller", (byte *)LookAtKiller},
+{"TossClientWeapon", (byte *)TossClientWeapon},
+{"ClientObituary", (byte *)ClientObituary},
+{"IsNeutral", (byte *)IsNeutral},
+{"IsFemale", (byte *)IsFemale},
+{"player_pain", (byte *)player_pain},
+{"SP_info_player_intermission", (byte *)SP_info_player_intermission},
+{"SP_info_player_coop", (byte *)SP_info_player_coop},
+{"SP_info_player_deathmatch", (byte *)SP_info_player_deathmatch},
+{"SP_info_player_start", (byte *)SP_info_player_start},
+{"SP_CreateCoopSpots", (byte *)SP_CreateCoopSpots},
+{"SP_FixCoopSpots", (byte *)SP_FixCoopSpots},
+{"SP_monster_tank", (byte *)SP_monster_tank},
+{"tank_die", (byte *)tank_die},
+{"tank_dead", (byte *)tank_dead},
+{"tank_attack", (byte *)tank_attack},
+{"tank_doattack_rocket", (byte *)tank_doattack_rocket},
+{"tank_refire_rocket", (byte *)tank_refire_rocket},
+{"tank_poststrike", (byte *)tank_poststrike},
+{"tank_reattack_blaster", (byte *)tank_reattack_blaster},
+{"TankMachineGun", (byte *)TankMachineGun},
+{"TankRocket", (byte *)TankRocket},
+{"TankStrike", (byte *)TankStrike},
+{"TankBlaster", (byte *)TankBlaster},
+{"tank_pain", (byte *)tank_pain},
+{"tank_run", (byte *)tank_run},
+{"tank_walk", (byte *)tank_walk},
+{"tank_stand", (byte *)tank_stand},
+{"tank_idle", (byte *)tank_idle},
+{"tank_windup", (byte *)tank_windup},
+{"tank_thud", (byte *)tank_thud},
+{"tank_footstep", (byte *)tank_footstep},
+{"tank_sight", (byte *)tank_sight},
+{"SP_monster_supertank", (byte *)SP_monster_supertank},
+{"supertank_die", (byte *)supertank_die},
+{"BossExplode", (byte *)BossExplode},
+{"supertank_dead", (byte *)supertank_dead},
+{"supertank_attack", (byte *)supertank_attack},
+{"supertankMachineGun", (byte *)supertankMachineGun},
+{"supertankRocket", (byte *)supertankRocket},
+{"supertank_pain", (byte *)supertank_pain},
+{"supertank_reattack1", (byte *)supertank_reattack1},
+{"supertank_run", (byte *)supertank_run},
+{"supertank_walk", (byte *)supertank_walk},
+{"supertank_forward", (byte *)supertank_forward},
+{"supertank_stand", (byte *)supertank_stand},
+{"supertank_search", (byte *)supertank_search},
+{"TreadSound", (byte *)TreadSound},
+{"SP_monster_soldier_lasergun", (byte *)SP_monster_soldier_lasergun},
+{"SP_monster_soldier_hypergun", (byte *)SP_monster_soldier_hypergun},
+{"SP_monster_soldier_ripper", (byte *)SP_monster_soldier_ripper},
+{"SP_monster_soldier_h", (byte *)SP_monster_soldier_h},
+{"soldierh_die", (byte *)soldierh_die},
+{"soldierh_dead", (byte *)soldierh_dead},
+{"soldierh_fire7", (byte *)soldierh_fire7},
+{"soldierh_fire6", (byte *)soldierh_fire6},
+{"soldierh_dodge", (byte *)soldierh_dodge},
+{"soldierh_duck_hold", (byte *)soldierh_duck_hold},
+{"soldierh_sight", (byte *)soldierh_sight},
+{"soldierh_attack", (byte *)soldierh_attack},
+{"soldierh_attack6_refire", (byte *)soldierh_attack6_refire},
+{"soldierh_fire8", (byte *)soldierh_fire8},
+{"soldierh_fire4", (byte *)soldierh_fire4},
+{"soldierh_attack3_refire", (byte *)soldierh_attack3_refire},
+{"soldierh_fire3", (byte *)soldierh_fire3},
+{"soldierh_duck_up", (byte *)soldierh_duck_up},
+{"soldierh_duck_down", (byte *)soldierh_duck_down},
+{"soldierh_attack2_refire2", (byte *)soldierh_attack2_refire2},
+{"soldierh_attack2_refire1", (byte *)soldierh_attack2_refire1},
+{"soldierh_fire2", (byte *)soldierh_fire2},
+{"soldierh_ripper2", (byte *)soldierh_ripper2},
+{"soldierh_hyper_refire2", (byte *)soldierh_hyper_refire2},
+{"soldierh_hyper_sound", (byte *)soldierh_hyper_sound},
+{"soldierh_attack1_refire2", (byte *)soldierh_attack1_refire2},
+{"soldierh_attack1_refire1", (byte *)soldierh_attack1_refire1},
+{"soldierh_fire1", (byte *)soldierh_fire1},
+{"soldierh_ripper1", (byte *)soldierh_ripper1},
+{"soldierh_hyper_refire1", (byte *)soldierh_hyper_refire1},
+{"soldierh_fire", (byte *)soldierh_fire},
+{"soldierh_laserbeam", (byte *)soldierh_laserbeam},
+{"soldierh_pain", (byte *)soldierh_pain},
+{"soldierh_run", (byte *)soldierh_run},
+{"soldierh_walk", (byte *)soldierh_walk},
+{"soldierh_walk1_random", (byte *)soldierh_walk1_random},
+{"soldierh_stand", (byte *)soldierh_stand},
+{"soldierh_cock", (byte *)soldierh_cock},
+{"soldierh_idle", (byte *)soldierh_idle},
+{"SP_monster_soldier_ss", (byte *)SP_monster_soldier_ss},
+{"SP_monster_soldier", (byte *)SP_monster_soldier},
+{"SP_monster_soldier_light", (byte *)SP_monster_soldier_light},
+{"SP_monster_soldier_x", (byte *)SP_monster_soldier_x},
+{"soldier_die", (byte *)soldier_die},
+{"soldier_dead", (byte *)soldier_dead},
+{"soldier_fire7", (byte *)soldier_fire7},
+{"soldier_fire6", (byte *)soldier_fire6},
+{"soldier_dodge", (byte *)soldier_dodge},
+{"soldier_duck_hold", (byte *)soldier_duck_hold},
+{"soldier_sight", (byte *)soldier_sight},
+{"soldier_attack", (byte *)soldier_attack},
+{"soldier_attack6_refire", (byte *)soldier_attack6_refire},
+{"soldier_fire8", (byte *)soldier_fire8},
+{"soldier_fire4", (byte *)soldier_fire4},
+{"soldier_attack3_refire", (byte *)soldier_attack3_refire},
+{"soldier_fire3", (byte *)soldier_fire3},
+{"soldier_duck_up", (byte *)soldier_duck_up},
+{"soldier_duck_down", (byte *)soldier_duck_down},
+{"soldier_attack2_refire2", (byte *)soldier_attack2_refire2},
+{"soldier_attack2_refire1", (byte *)soldier_attack2_refire1},
+{"soldier_fire2", (byte *)soldier_fire2},
+{"soldier_attack1_refire2", (byte *)soldier_attack1_refire2},
+{"soldier_attack1_refire1", (byte *)soldier_attack1_refire1},
+{"soldier_fire1", (byte *)soldier_fire1},
+{"soldier_fire", (byte *)soldier_fire},
+{"soldier_pain", (byte *)soldier_pain},
+{"soldier_run", (byte *)soldier_run},
+{"soldier_walk", (byte *)soldier_walk},
+{"soldier_walk1_random", (byte *)soldier_walk1_random},
+{"soldier_stand", (byte *)soldier_stand},
+{"soldier_cock", (byte *)soldier_cock},
+{"soldier_idle", (byte *)soldier_idle},
+{"SP_monster_parasite", (byte *)SP_monster_parasite},
+{"parasite_die", (byte *)parasite_die},
+{"parasite_dead", (byte *)parasite_dead},
+{"parasite_attack", (byte *)parasite_attack},
+{"parasite_drain_attack", (byte *)parasite_drain_attack},
+{"parasite_drain_attack_ok", (byte *)parasite_drain_attack_ok},
+{"parasite_pain", (byte *)parasite_pain},
+{"parasite_walk", (byte *)parasite_walk},
+{"parasite_start_walk", (byte *)parasite_start_walk},
+{"parasite_run", (byte *)parasite_run},
+{"parasite_start_run", (byte *)parasite_start_run},
+{"parasite_stand", (byte *)parasite_stand},
+{"parasite_idle", (byte *)parasite_idle},
+{"parasite_refidget", (byte *)parasite_refidget},
+{"parasite_do_fidget", (byte *)parasite_do_fidget},
+{"parasite_end_fidget", (byte *)parasite_end_fidget},
+{"parasite_search", (byte *)parasite_search},
+{"parasite_scratch", (byte *)parasite_scratch},
+{"parasite_tap", (byte *)parasite_tap},
+{"parasite_sight", (byte *)parasite_sight},
+{"parasite_reel_in", (byte *)parasite_reel_in},
+{"parasite_launch", (byte *)parasite_launch},
+{"SP_monster_mutant", (byte *)SP_monster_mutant},
+{"mutant_die", (byte *)mutant_die},
+{"mutant_dead", (byte *)mutant_dead},
+{"mutant_pain", (byte *)mutant_pain},
+{"mutant_checkattack", (byte *)mutant_checkattack},
+{"mutant_check_jump", (byte *)mutant_check_jump},
+{"mutant_check_melee", (byte *)mutant_check_melee},
+{"mutant_jump", (byte *)mutant_jump},
+{"mutant_check_landing", (byte *)mutant_check_landing},
+{"mutant_jump_takeoff", (byte *)mutant_jump_takeoff},
+{"mutant_jump_touch", (byte *)mutant_jump_touch},
+{"mutant_melee", (byte *)mutant_melee},
+{"mutant_check_refire", (byte *)mutant_check_refire},
+{"mutant_hit_right", (byte *)mutant_hit_right},
+{"mutant_hit_left", (byte *)mutant_hit_left},
+{"mutant_run", (byte *)mutant_run},
+{"mutant_walk", (byte *)mutant_walk},
+{"mutant_walk_loop", (byte *)mutant_walk_loop},
+{"mutant_idle", (byte *)mutant_idle},
+{"mutant_idle_loop", (byte *)mutant_idle_loop},
+{"mutant_stand", (byte *)mutant_stand},
+{"mutant_swing", (byte *)mutant_swing},
+{"mutant_search", (byte *)mutant_search},
+{"mutant_sight", (byte *)mutant_sight},
+{"mutant_step", (byte *)mutant_step},
+{"M_walkmove", (byte *)M_walkmove},
+{"M_MoveToGoal", (byte *)M_MoveToGoal},
+{"SV_CloseEnough", (byte *)SV_CloseEnough},
+{"SV_NewChaseDir", (byte *)SV_NewChaseDir},
+{"SV_FixCheckBottom", (byte *)SV_FixCheckBottom},
+{"SV_StepDirection", (byte *)SV_StepDirection},
+{"M_ChangeYaw", (byte *)M_ChangeYaw},
+{"SV_movestep", (byte *)SV_movestep},
+{"M_CheckBottom", (byte *)M_CheckBottom},
+{"SP_monster_medic", (byte *)SP_monster_medic},
+{"medic_checkattack", (byte *)medic_checkattack},
+{"medic_attack", (byte *)medic_attack},
+{"medic_hook_retract", (byte *)medic_hook_retract},
+{"medic_cable_attack", (byte *)medic_cable_attack},
+{"medic_hook_launch", (byte *)medic_hook_launch},
+{"medic_continue", (byte *)medic_continue},
+{"medic_dodge", (byte *)medic_dodge},
+{"medic_duck_up", (byte *)medic_duck_up},
+{"medic_duck_hold", (byte *)medic_duck_hold},
+{"medic_duck_down", (byte *)medic_duck_down},
+{"medic_die", (byte *)medic_die},
+{"medic_dead", (byte *)medic_dead},
+{"medic_fire_blaster", (byte *)medic_fire_blaster},
+{"medic_pain", (byte *)medic_pain},
+{"medic_run", (byte *)medic_run},
+{"medic_walk", (byte *)medic_walk},
+{"medic_stand", (byte *)medic_stand},
+{"medic_sight", (byte *)medic_sight},
+{"medic_search", (byte *)medic_search},
+{"medic_idle", (byte *)medic_idle},
+{"medic_FindDeadMonster", (byte *)medic_FindDeadMonster},
+{"SP_misc_insane", (byte *)SP_misc_insane},
+{"insane_die", (byte *)insane_die},
+{"insane_dead", (byte *)insane_dead},
+{"insane_stand", (byte *)insane_stand},
+{"insane_checkup", (byte *)insane_checkup},
+{"insane_checkdown", (byte *)insane_checkdown},
+{"insane_onground", (byte *)insane_onground},
+{"insane_pain", (byte *)insane_pain},
+{"insane_run", (byte *)insane_run},
+{"insane_walk", (byte *)insane_walk},
+{"insane_cross", (byte *)insane_cross},
+{"insane_scream", (byte *)insane_scream},
+{"insane_moan", (byte *)insane_moan},
+{"insane_shake", (byte *)insane_shake},
+{"insane_fist", (byte *)insane_fist},
+{"SP_monster_infantry", (byte *)SP_monster_infantry},
+{"infantry_attack", (byte *)infantry_attack},
+{"infantry_smack", (byte *)infantry_smack},
+{"infantry_swing", (byte *)infantry_swing},
+{"infantry_fire", (byte *)infantry_fire},
+{"infantry_cock_gun", (byte *)infantry_cock_gun},
+{"infantry_set_firetime", (byte *)infantry_set_firetime},
+{"infantry_dodge", (byte *)infantry_dodge},
+{"infantry_duck_up", (byte *)infantry_duck_up},
+{"infantry_duck_hold", (byte *)infantry_duck_hold},
+{"infantry_duck_down", (byte *)infantry_duck_down},
+{"infantry_die", (byte *)infantry_die},
+{"infantry_dead", (byte *)infantry_dead},
+{"infantry_sight", (byte *)infantry_sight},
+{"InfantryMachineGun", (byte *)InfantryMachineGun},
+{"infantry_pain", (byte *)infantry_pain},
+{"infantry_run", (byte *)infantry_run},
+{"infantry_walk", (byte *)infantry_walk},
+{"infantry_fidget", (byte *)infantry_fidget},
+{"infantry_stand", (byte *)infantry_stand},
+{"SP_monster_hover", (byte *)SP_monster_hover},
+{"hover_die", (byte *)hover_die},
+{"hover_dead", (byte *)hover_dead},
+{"hover_deadthink", (byte *)hover_deadthink},
+{"hover_pain", (byte *)hover_pain},
+{"hover_attack", (byte *)hover_attack},
+{"hover_start_attack", (byte *)hover_start_attack},
+{"hover_walk", (byte *)hover_walk},
+{"hover_run", (byte *)hover_run},
+{"hover_stand", (byte *)hover_stand},
+{"hover_fire_blaster", (byte *)hover_fire_blaster},
+{"hover_reattack", (byte *)hover_reattack},
+{"hover_search", (byte *)hover_search},
+{"hover_sight", (byte *)hover_sight},
+{"SP_monster_gunner", (byte *)SP_monster_gunner},
+{"gunner_refire_chain", (byte *)gunner_refire_chain},
+{"gunner_fire_chain", (byte *)gunner_fire_chain},
+{"gunner_attack", (byte *)gunner_attack},
+{"GunnerGrenade", (byte *)GunnerGrenade},
+{"GunnerFire", (byte *)GunnerFire},
+{"gunner_opengun", (byte *)gunner_opengun},
+{"gunner_dodge", (byte *)gunner_dodge},
+{"gunner_duck_up", (byte *)gunner_duck_up},
+{"gunner_duck_hold", (byte *)gunner_duck_hold},
+{"gunner_duck_down", (byte *)gunner_duck_down},
+{"gunner_die", (byte *)gunner_die},
+{"gunner_dead", (byte *)gunner_dead},
+{"gunner_pain", (byte *)gunner_pain},
+{"gunner_runandshoot", (byte *)gunner_runandshoot},
+{"gunner_run", (byte *)gunner_run},
+{"gunner_walk", (byte *)gunner_walk},
+{"gunner_stand", (byte *)gunner_stand},
+{"gunner_fidget", (byte *)gunner_fidget},
+{"gunner_search", (byte *)gunner_search},
+{"gunner_sight", (byte *)gunner_sight},
+{"gunner_idlesound", (byte *)gunner_idlesound},
+{"SP_monster_gladiator", (byte *)SP_monster_gladiator},
+{"gladiator_die", (byte *)gladiator_die},
+{"gladiator_dead", (byte *)gladiator_dead},
+{"gladiator_pain", (byte *)gladiator_pain},
+{"gladiator_attack", (byte *)gladiator_attack},
+{"GladiatorGun", (byte *)GladiatorGun},
+{"gladiator_melee", (byte *)gladiator_melee},
+{"GaldiatorMelee", (byte *)GaldiatorMelee},
+{"gladiator_run", (byte *)gladiator_run},
+{"gladiator_walk", (byte *)gladiator_walk},
+{"gladiator_stand", (byte *)gladiator_stand},
+{"gladiator_cleaver_swing", (byte *)gladiator_cleaver_swing},
+{"gladiator_search", (byte *)gladiator_search},
+{"gladiator_sight", (byte *)gladiator_sight},
+{"gladiator_idle", (byte *)gladiator_idle},
+{"SP_monster_gladb", (byte *)SP_monster_gladb},
+{"gladb_die", (byte *)gladb_die},
+{"gladb_dead", (byte *)gladb_dead},
+{"gladb_pain", (byte *)gladb_pain},
+{"gladb_attack", (byte *)gladb_attack},
+{"gladbGun_check", (byte *)gladbGun_check},
+{"gladbGun", (byte *)gladbGun},
+{"gladb_melee", (byte *)gladb_melee},
+{"GladbMelee", (byte *)GladbMelee},
+{"gladb_run", (byte *)gladb_run},
+{"gladb_walk", (byte *)gladb_walk},
+{"gladb_stand", (byte *)gladb_stand},
+{"gladb_cleaver_swing", (byte *)gladb_cleaver_swing},
+{"gladb_search", (byte *)gladb_search},
+{"gladb_sight", (byte *)gladb_sight},
+{"gladb_idle", (byte *)gladb_idle},
+{"land_to_water", (byte *)land_to_water},
+{"water_to_land", (byte *)water_to_land},
+{"SP_monster_gekk", (byte *)SP_monster_gekk},
+{"gekk_dodge", (byte *)gekk_dodge},
+{"gekk_duck_hold", (byte *)gekk_duck_hold},
+{"gekk_duck_up", (byte *)gekk_duck_up},
+{"gekk_duck_down", (byte *)gekk_duck_down},
+{"gekk_die", (byte *)gekk_die},
+{"isgibfest", (byte *)isgibfest},
+{"gekk_gibfest", (byte *)gekk_gibfest},
+{"gekk_dead", (byte *)gekk_dead},
+{"gekk_pain", (byte *)gekk_pain},
+{"gekk_jump", (byte *)gekk_jump},
+{"gekk_check_landing", (byte *)gekk_check_landing},
+{"gekk_stop_skid", (byte *)gekk_stop_skid},
+{"gekk_jump_takeoff2", (byte *)gekk_jump_takeoff2},
+{"gekk_jump_takeoff", (byte *)gekk_jump_takeoff},
+{"gekk_jump_touch", (byte *)gekk_jump_touch},
+{"gekk_melee", (byte *)gekk_melee},
+{"gekk_preattack", (byte *)gekk_preattack},
+{"gekk_bite", (byte *)gekk_bite},
+{"gekk_check_underwater", (byte *)gekk_check_underwater},
+{"reloogie", (byte *)reloogie},
+{"loogie", (byte *)loogie},
+{"fire_loogie", (byte *)fire_loogie},
+{"loogie_touch", (byte *)loogie_touch},
+{"gekk_check_refire", (byte *)gekk_check_refire},
+{"gekk_hit_right", (byte *)gekk_hit_right},
+{"gekk_hit_left", (byte *)gekk_hit_left},
+{"gekk_run", (byte *)gekk_run},
+{"gekk_run_start", (byte *)gekk_run_start},
+{"gekk_walk", (byte *)gekk_walk},
+{"gekk_idle", (byte *)gekk_idle},
+{"gekk_idle_loop", (byte *)gekk_idle_loop},
+{"gekk_stand", (byte *)gekk_stand},
+{"gekk_swim", (byte *)gekk_swim},
+{"gekk_swim_loop", (byte *)gekk_swim_loop},
+{"ai_stand2", (byte *)ai_stand2},
+{"gekk_face", (byte *)gekk_face},
+{"gekk_swing", (byte *)gekk_swing},
+{"gekk_search", (byte *)gekk_search},
+{"gekk_sight", (byte *)gekk_sight},
+{"gekk_step", (byte *)gekk_step},
+{"gekk_checkattack", (byte *)gekk_checkattack},
+{"gekk_check_jump_close", (byte *)gekk_check_jump_close},
+{"gekk_check_jump", (byte *)gekk_check_jump},
+{"gekk_check_melee", (byte *)gekk_check_melee},
+{"SP_monster_flyer", (byte *)SP_monster_flyer},
+{"flyer_die", (byte *)flyer_die},
+{"flyer_pain", (byte *)flyer_pain},
+{"flyer_check_melee", (byte *)flyer_check_melee},
+{"flyer_melee", (byte *)flyer_melee},
+{"flyer_nextmove", (byte *)flyer_nextmove},
+{"flyer_setstart", (byte *)flyer_setstart},
+{"flyer_attack", (byte *)flyer_attack},
+{"flyer_loop_melee", (byte *)flyer_loop_melee},
+{"flyer_slash_right", (byte *)flyer_slash_right},
+{"flyer_slash_left", (byte *)flyer_slash_left},
+{"flyer_fireright", (byte *)flyer_fireright},
+{"flyer_fireleft", (byte *)flyer_fireleft},
+{"flyer_fire", (byte *)flyer_fire},
+{"flyer_start", (byte *)flyer_start},
+{"flyer_stop", (byte *)flyer_stop},
+{"flyer_stand", (byte *)flyer_stand},
+{"flyer_walk", (byte *)flyer_walk},
+{"flyer_run", (byte *)flyer_run},
+{"flyer_pop_blades", (byte *)flyer_pop_blades},
+{"flyer_idle", (byte *)flyer_idle},
+{"flyer_sight", (byte *)flyer_sight},
+{"SP_monster_floater", (byte *)SP_monster_floater},
+{"floater_die", (byte *)floater_die},
+{"floater_dead", (byte *)floater_dead},
+{"floater_pain", (byte *)floater_pain},
+{"floater_melee", (byte *)floater_melee},
+{"floater_attack", (byte *)floater_attack},
+{"floater_zap", (byte *)floater_zap},
+{"floater_wham", (byte *)floater_wham},
+{"floater_walk", (byte *)floater_walk},
+{"floater_run", (byte *)floater_run},
+{"floater_stand", (byte *)floater_stand},
+{"floater_fire_blaster", (byte *)floater_fire_blaster},
+{"floater_idle", (byte *)floater_idle},
+{"floater_sight", (byte *)floater_sight},
+{"SP_monster_flipper", (byte *)SP_monster_flipper},
+{"flipper_die", (byte *)flipper_die},
+{"flipper_sight", (byte *)flipper_sight},
+{"flipper_dead", (byte *)flipper_dead},
+{"flipper_pain", (byte *)flipper_pain},
+{"flipper_melee", (byte *)flipper_melee},
+{"flipper_preattack", (byte *)flipper_preattack},
+{"flipper_bite", (byte *)flipper_bite},
+{"flipper_start_run", (byte *)flipper_start_run},
+{"flipper_walk", (byte *)flipper_walk},
+{"flipper_run", (byte *)flipper_run},
+{"flipper_run_loop", (byte *)flipper_run_loop},
+{"flipper_stand", (byte *)flipper_stand},
+{"SP_monster_fixbot", (byte *)SP_monster_fixbot},
+{"bot_goal_think", (byte *)bot_goal_think},
+{"fixbot_die", (byte *)fixbot_die},
+{"fixbot_dead", (byte *)fixbot_dead},
+{"fixbot_pain", (byte *)fixbot_pain},
+{"fixbot_attack", (byte *)fixbot_attack},
+{"fixbot_start_attack", (byte *)fixbot_start_attack},
+{"fixbot_walk", (byte *)fixbot_walk},
+{"fixbot_run", (byte *)fixbot_run},
+{"fixbot_stand", (byte *)fixbot_stand},
+{"fixbot_fire_blaster", (byte *)fixbot_fire_blaster},
+{"fixbot_fire_welder", (byte *)fixbot_fire_welder},
+{"ai_move2", (byte *)ai_move2},
+{"weldstate", (byte *)weldstate},
+{"fixbot_fire_laser", (byte *)fixbot_fire_laser},
+{"check_telefrag", (byte *)check_telefrag},
+{"ai_movetogoal", (byte *)ai_movetogoal},
+{"go_roam", (byte *)go_roam},
+{"ai_facing", (byte *)ai_facing},
+{"fly_vertical2", (byte *)fly_vertical2},
+{"fly_vertical", (byte *)fly_vertical},
+{"blastoff", (byte *)blastoff},
+{"use_scanner", (byte *)use_scanner},
+{"roam_goal", (byte *)roam_goal},
+{"change_to_roam", (byte *)change_to_roam},
+{"takeoff_goal", (byte *)takeoff_goal},
+{"landing_goal", (byte *)landing_goal},
+{"fixbot_search", (byte *)fixbot_search},
+{"fixbot_FindDeadMonster", (byte *)fixbot_FindDeadMonster},
+{"crand", (byte *)crand},
+{"SP_monster_chick_heat", (byte *)SP_monster_chick_heat},
+{"SP_monster_chick", (byte *)SP_monster_chick},
+{"chick_sight", (byte *)chick_sight},
+{"chick_attack", (byte *)chick_attack},
+{"chick_melee", (byte *)chick_melee},
+{"chick_slash", (byte *)chick_slash},
+{"chick_reslash", (byte *)chick_reslash},
+{"chick_attack1", (byte *)chick_attack1},
+{"chick_rerocket", (byte *)chick_rerocket},
+{"ChickReload", (byte *)ChickReload},
+{"Chick_PreAttack1", (byte *)Chick_PreAttack1},
+{"ChickRocket", (byte *)ChickRocket},
+{"ChickSlash", (byte *)ChickSlash},
+{"chick_dodge", (byte *)chick_dodge},
+{"chick_duck_up", (byte *)chick_duck_up},
+{"chick_duck_hold", (byte *)chick_duck_hold},
+{"chick_duck_down", (byte *)chick_duck_down},
+{"chick_die", (byte *)chick_die},
+{"chick_dead", (byte *)chick_dead},
+{"chick_pain", (byte *)chick_pain},
+{"chick_run", (byte *)chick_run},
+{"chick_walk", (byte *)chick_walk},
+{"chick_stand", (byte *)chick_stand},
+{"chick_fidget", (byte *)chick_fidget},
+{"ChickMoan", (byte *)ChickMoan},
+{"SP_monster_brain", (byte *)SP_monster_brain},
+{"brain_die", (byte *)brain_die},
+{"brain_dead", (byte *)brain_dead},
+{"brain_pain", (byte *)brain_pain},
+{"brain_run", (byte *)brain_run},
+{"brain_attack", (byte *)brain_attack},
+{"brain_laserbeam_reattack", (byte *)brain_laserbeam_reattack},
+{"brain_laserbeam", (byte *)brain_laserbeam},
+{"brain_tounge_attack", (byte *)brain_tounge_attack},
+{"brain_tounge_attack_ok", (byte *)brain_tounge_attack_ok},
+{"brain_melee", (byte *)brain_melee},
+{"brain_chest_closed", (byte *)brain_chest_closed},
+{"brain_tentacle_attack", (byte *)brain_tentacle_attack},
+{"brain_chest_open", (byte *)brain_chest_open},
+{"brain_hit_left", (byte *)brain_hit_left},
+{"brain_swing_left", (byte *)brain_swing_left},
+{"brain_hit_right", (byte *)brain_hit_right},
+{"brain_swing_right", (byte *)brain_swing_right},
+{"brain_dodge", (byte *)brain_dodge},
+{"brain_duck_up", (byte *)brain_duck_up},
+{"brain_duck_hold", (byte *)brain_duck_hold},
+{"brain_duck_down", (byte *)brain_duck_down},
+{"brain_walk", (byte *)brain_walk},
+{"brain_idle", (byte *)brain_idle},
+{"brain_stand", (byte *)brain_stand},
+{"brain_search", (byte *)brain_search},
+{"brain_sight", (byte *)brain_sight},
+{"SP_monster_boss5", (byte *)SP_monster_boss5},
+{"boss5_die", (byte *)boss5_die},
+{"BossExplode2", (byte *)BossExplode2},
+{"boss5_dead", (byte *)boss5_dead},
+{"boss5_attack", (byte *)boss5_attack},
+{"boss5MachineGun", (byte *)boss5MachineGun},
+{"boss5Rocket", (byte *)boss5Rocket},
+{"boss5_pain", (byte *)boss5_pain},
+{"boss5_reattack1", (byte *)boss5_reattack1},
+{"boss5_run", (byte *)boss5_run},
+{"boss5_walk", (byte *)boss5_walk},
+{"boss5_forward", (byte *)boss5_forward},
+{"boss5_stand", (byte *)boss5_stand},
+{"boss5_search", (byte *)boss5_search},
+{"TreadSound2", (byte *)TreadSound2},
+{"MakronToss", (byte *)MakronToss},
+{"MakronSpawn", (byte *)MakronSpawn},
+{"SP_monster_makron", (byte *)SP_monster_makron},
+{"MakronPrecache", (byte *)MakronPrecache},
+{"Makron_CheckAttack", (byte *)Makron_CheckAttack},
+{"makron_die", (byte *)makron_die},
+{"makron_torso_die", (byte *)makron_torso_die},
+{"makron_dead", (byte *)makron_dead},
+{"makron_torso", (byte *)makron_torso},
+{"makron_torso_think", (byte *)makron_torso_think},
+{"makron_attack", (byte *)makron_attack},
+{"makron_sight", (byte *)makron_sight},
+{"makron_pain", (byte *)makron_pain},
+{"MakronHyperblaster", (byte *)MakronHyperblaster},
+{"MakronRailgun", (byte *)MakronRailgun},
+{"MakronSaveloc", (byte *)MakronSaveloc},
+{"makronBFG", (byte *)makronBFG},
+{"makron_run", (byte *)makron_run},
+{"makron_walk", (byte *)makron_walk},
+{"makron_prerailgun", (byte *)makron_prerailgun},
+{"makron_brainsplorch", (byte *)makron_brainsplorch},
+{"makron_step_right", (byte *)makron_step_right},
+{"makron_step_left", (byte *)makron_step_left},
+{"makron_popup", (byte *)makron_popup},
+{"makron_hit", (byte *)makron_hit},
+{"makron_stand", (byte *)makron_stand},
+{"makron_taunt", (byte *)makron_taunt},
+{"SP_monster_jorg", (byte *)SP_monster_jorg},
+{"Jorg_CheckAttack", (byte *)Jorg_CheckAttack},
+{"jorg_die", (byte *)jorg_die},
+{"jorg_dead", (byte *)jorg_dead},
+{"jorg_attack", (byte *)jorg_attack},
+{"jorg_firebullet", (byte *)jorg_firebullet},
+{"jorg_firebullet_left", (byte *)jorg_firebullet_left},
+{"jorg_firebullet_right", (byte *)jorg_firebullet_right},
+{"jorgBFG", (byte *)jorgBFG},
+{"jorg_pain", (byte *)jorg_pain},
+{"jorg_attack1", (byte *)jorg_attack1},
+{"jorg_reattack1", (byte *)jorg_reattack1},
+{"jorg_run", (byte *)jorg_run},
+{"jorg_walk", (byte *)jorg_walk},
+{"jorg_stand", (byte *)jorg_stand},
+{"jorg_step_right", (byte *)jorg_step_right},
+{"jorg_step_left", (byte *)jorg_step_left},
+{"jorg_death_hit", (byte *)jorg_death_hit},
+{"jorg_idle", (byte *)jorg_idle},
+{"jorg_search", (byte *)jorg_search},
+{"SP_monster_boss3_stand", (byte *)SP_monster_boss3_stand},
+{"Think_Boss3Stand", (byte *)Think_Boss3Stand},
+{"Use_Boss3", (byte *)Use_Boss3},
+{"SP_monster_boss2", (byte *)SP_monster_boss2},
+{"Boss2_CheckAttack", (byte *)Boss2_CheckAttack},
+{"boss2_die", (byte *)boss2_die},
+{"boss2_dead", (byte *)boss2_dead},
+{"boss2_pain", (byte *)boss2_pain},
+{"boss2_reattack_mg", (byte *)boss2_reattack_mg},
+{"boss2_attack_mg", (byte *)boss2_attack_mg},
+{"boss2_attack", (byte *)boss2_attack},
+{"boss2_walk", (byte *)boss2_walk},
+{"boss2_run", (byte *)boss2_run},
+{"boss2_stand", (byte *)boss2_stand},
+{"Boss2MachineGun", (byte *)Boss2MachineGun},
+{"boss2_firebullet_left", (byte *)boss2_firebullet_left},
+{"boss2_firebullet_right", (byte *)boss2_firebullet_right},
+{"Boss2Rocket", (byte *)Boss2Rocket},
+{"boss2_search", (byte *)boss2_search},
+{"SP_monster_berserk", (byte *)SP_monster_berserk},
+{"berserk_die", (byte *)berserk_die},
+{"berserk_dead", (byte *)berserk_dead},
+{"berserk_pain", (byte *)berserk_pain},
+{"berserk_melee", (byte *)berserk_melee},
+{"berserk_strike", (byte *)berserk_strike},
+{"berserk_attack_club", (byte *)berserk_attack_club},
+{"berserk_swing", (byte *)berserk_swing},
+{"berserk_attack_spike", (byte *)berserk_attack_spike},
+{"berserk_run", (byte *)berserk_run},
+{"berserk_walk", (byte *)berserk_walk},
+{"berserk_fidget", (byte *)berserk_fidget},
+{"berserk_stand", (byte *)berserk_stand},
+{"berserk_search", (byte *)berserk_search},
+{"berserk_sight", (byte *)berserk_sight},
+{"fire_trap", (byte *)fire_trap},
+{"Trap_Think", (byte *)Trap_Think},
+{"fire_plasma", (byte *)fire_plasma},
+{"plasma_touch", (byte *)plasma_touch},
+{"fire_heat", (byte *)fire_heat},
+{"heat_think", (byte *)heat_think},
+{"fire_ionripper", (byte *)fire_ionripper},
+{"ionripper_touch", (byte *)ionripper_touch},
+{"ionripper_sparks", (byte *)ionripper_sparks},
+{"fire_bfg", (byte *)fire_bfg},
+{"bfg_think", (byte *)bfg_think},
+{"bfg_touch", (byte *)bfg_touch},
+{"bfg_explode", (byte *)bfg_explode},
+{"fire_rail", (byte *)fire_rail},
+{"fire_rocket", (byte *)fire_rocket},
+{"rocket_touch", (byte *)rocket_touch},
+{"fire_grenade2", (byte *)fire_grenade2},
+{"fire_grenade", (byte *)fire_grenade},
+{"Grenade_Touch", (byte *)Grenade_Touch},
+{"Grenade_Explode", (byte *)Grenade_Explode},
+{"fire_blueblaster", (byte *)fire_blueblaster},
+{"fire_blaster", (byte *)fire_blaster},
+{"blaster_touch", (byte *)blaster_touch},
+{"fire_shotgun", (byte *)fire_shotgun},
+{"fire_bullet", (byte *)fire_bullet},
+{"fire_lead", (byte *)fire_lead},
+{"fire_hit", (byte *)fire_hit},
+{"check_dodge", (byte *)check_dodge},
+{"KillBox", (byte *)KillBox},
+{"G_TouchSolids", (byte *)G_TouchSolids},
+{"G_TouchTriggers", (byte *)G_TouchTriggers},
+{"G_FreeEdict", (byte *)G_FreeEdict},
+{"G_Spawn", (byte *)G_Spawn},
+{"G_InitEdict", (byte *)G_InitEdict},
+{"G_CopyString", (byte *)G_CopyString},
+{"vectoangles", (byte *)vectoangles},
+{"vectoyaw", (byte *)vectoyaw},
+{"G_SetMovedir", (byte *)G_SetMovedir},
+{"vtos", (byte *)vtos},
+{"tv", (byte *)tv},
+{"G_UseTargets", (byte *)G_UseTargets},
+{"Think_Delay", (byte *)Think_Delay},
+{"G_PickTarget", (byte *)G_PickTarget},
+{"findradius", (byte *)findradius},
+{"G_Find", (byte *)G_Find},
+{"G_ProjectSource", (byte *)G_ProjectSource},
+{"SP_turret_driver", (byte *)SP_turret_driver},
+{"turret_driver_link", (byte *)turret_driver_link},
+{"turret_driver_think", (byte *)turret_driver_think},
+{"turret_driver_die", (byte *)turret_driver_die},
+{"SP_turret_base", (byte *)SP_turret_base},
+{"SP_turret_breach", (byte *)SP_turret_breach},
+{"turret_breach_finish_init", (byte *)turret_breach_finish_init},
+{"turret_breach_think", (byte *)turret_breach_think},
+{"turret_breach_fire", (byte *)turret_breach_fire},
+{"turret_blocked", (byte *)turret_blocked},
+{"SnapToEights", (byte *)SnapToEights},
+{"AnglesNormalize", (byte *)AnglesNormalize},
+{"SP_trigger_monsterjump", (byte *)SP_trigger_monsterjump},
+{"trigger_monsterjump_touch", (byte *)trigger_monsterjump_touch},
+{"SP_trigger_gravity", (byte *)SP_trigger_gravity},
+{"trigger_gravity_touch", (byte *)trigger_gravity_touch},
+{"SP_trigger_hurt", (byte *)SP_trigger_hurt},
+{"hurt_touch", (byte *)hurt_touch},
+{"hurt_use", (byte *)hurt_use},
+{"SP_trigger_push", (byte *)SP_trigger_push},
+{"trigger_push_active", (byte *)trigger_push_active},
+{"trigger_push_inactive", (byte *)trigger_push_inactive},
+{"trigger_effect", (byte *)trigger_effect},
+{"trigger_push_touch", (byte *)trigger_push_touch},
+{"SP_trigger_always", (byte *)SP_trigger_always},
+{"SP_trigger_counter", (byte *)SP_trigger_counter},
+{"trigger_counter_use", (byte *)trigger_counter_use},
+{"SP_trigger_key", (byte *)SP_trigger_key},
+{"trigger_key_use", (byte *)trigger_key_use},
+{"SP_trigger_relay", (byte *)SP_trigger_relay},
+{"trigger_relay_use", (byte *)trigger_relay_use},
+{"SP_trigger_once", (byte *)SP_trigger_once},
+{"SP_trigger_multiple", (byte *)SP_trigger_multiple},
+{"trigger_enable", (byte *)trigger_enable},
+{"Touch_Multi", (byte *)Touch_Multi},
+{"Use_Multi", (byte *)Use_Multi},
+{"multi_trigger", (byte *)multi_trigger},
+{"multi_wait", (byte *)multi_wait},
+{"InitTrigger", (byte *)InitTrigger},
+{"SP_target_earthquake", (byte *)SP_target_earthquake},
+{"target_earthquake_use", (byte *)target_earthquake_use},
+{"target_earthquake_think", (byte *)target_earthquake_think},
+{"SP_target_lightramp", (byte *)SP_target_lightramp},
+{"target_lightramp_use", (byte *)target_lightramp_use},
+{"target_lightramp_think", (byte *)target_lightramp_think},
+{"SP_target_mal_laser", (byte *)SP_target_mal_laser},
+{"mal_laser_think", (byte *)mal_laser_think},
+{"target_mal_laser_use", (byte *)target_mal_laser_use},
+{"target_mal_laser_off", (byte *)target_mal_laser_off},
+{"target_mal_laser_on", (byte *)target_mal_laser_on},
+{"SP_target_laser", (byte *)SP_target_laser},
+{"target_laser_start", (byte *)target_laser_start},
+{"target_laser_use", (byte *)target_laser_use},
+{"target_laser_off", (byte *)target_laser_off},
+{"target_laser_on", (byte *)target_laser_on},
+{"target_laser_think", (byte *)target_laser_think},
+{"SP_target_crosslevel_target", (byte *)SP_target_crosslevel_target},
+{"target_crosslevel_target_think", (byte *)target_crosslevel_target_think},
+{"SP_target_crosslevel_trigger", (byte *)SP_target_crosslevel_trigger},
+{"trigger_crosslevel_trigger_use", (byte *)trigger_crosslevel_trigger_use},
+{"SP_target_blaster", (byte *)SP_target_blaster},
+{"use_target_blaster", (byte *)use_target_blaster},
+{"SP_target_spawner", (byte *)SP_target_spawner},
+{"use_target_spawner", (byte *)use_target_spawner},
+{"SP_target_splash", (byte *)SP_target_splash},
+{"use_target_splash", (byte *)use_target_splash},
+{"SP_target_changelevel", (byte *)SP_target_changelevel},
+{"use_target_changelevel", (byte *)use_target_changelevel},
+{"SP_target_explosion", (byte *)SP_target_explosion},
+{"use_target_explosion", (byte *)use_target_explosion},
+{"target_explosion_explode", (byte *)target_explosion_explode},
+{"SP_target_goal", (byte *)SP_target_goal},
+{"use_target_goal", (byte *)use_target_goal},
+{"SP_target_secret", (byte *)SP_target_secret},
+{"use_target_secret", (byte *)use_target_secret},
+{"SP_target_help", (byte *)SP_target_help},
+{"Use_Target_Help", (byte *)Use_Target_Help},
+{"SP_target_speaker", (byte *)SP_target_speaker},
+{"Use_Target_Speaker", (byte *)Use_Target_Speaker},
+{"SP_target_temp_entity", (byte *)SP_target_temp_entity},
+{"Use_Target_Tent", (byte *)Use_Target_Tent},
+{"ServerCommand", (byte *)ServerCommand},
+{"SVCmd_WriteIP_f", (byte *)SVCmd_WriteIP_f},
+{"SVCmd_ListIP_f", (byte *)SVCmd_ListIP_f},
+{"SVCmd_RemoveIP_f", (byte *)SVCmd_RemoveIP_f},
+{"SVCmd_AddIP_f", (byte *)SVCmd_AddIP_f},
+{"SV_FilterPacket", (byte *)SV_FilterPacket},
+{"Svcmd_Test_f", (byte *)Svcmd_Test_f},
+{"SP_worldspawn", (byte *)SP_worldspawn},
+{"SpawnEntities", (byte *)SpawnEntities},
+{"G_FindTeams", (byte *)G_FindTeams},
+{"ED_ParseEdict", (byte *)ED_ParseEdict},
+{"ED_ParseField", (byte *)ED_ParseField},
+{"ED_NewString", (byte *)ED_NewString},
+{"ED_CallSpawn", (byte *)ED_CallSpawn},
+{"G_RunEntity", (byte *)G_RunEntity},
+{"SV_Physics_Step", (byte *)SV_Physics_Step},
+{"SV_AddRotationalFriction", (byte *)SV_AddRotationalFriction},
+{"SV_Physics_Toss", (byte *)SV_Physics_Toss},
+{"SV_Physics_Noclip", (byte *)SV_Physics_Noclip},
+{"SV_Physics_None", (byte *)SV_Physics_None},
+{"SV_Physics_Pusher", (byte *)SV_Physics_Pusher},
+{"SV_Push", (byte *)SV_Push},
+{"SV_PushEntity", (byte *)SV_PushEntity},
+{"RealBoundingBox", (byte *)RealBoundingBox},
+{"SV_AddGravity", (byte *)SV_AddGravity},
+{"SV_FlyMove", (byte *)SV_FlyMove},
+{"ClipVelocity", (byte *)ClipVelocity},
+{"SV_Impact", (byte *)SV_Impact},
+{"SV_RunThink", (byte *)SV_RunThink},
+{"SV_CheckVelocity", (byte *)SV_CheckVelocity},
+{"SV_TestEntityPosition", (byte *)SV_TestEntityPosition},
+{"swimmonster_start", (byte *)swimmonster_start},
+{"swimmonster_start_go", (byte *)swimmonster_start_go},
+{"flymonster_start", (byte *)flymonster_start},
+{"flymonster_start_go", (byte *)flymonster_start_go},
+{"walkmonster_start", (byte *)walkmonster_start},
+{"walkmonster_start_go", (byte *)walkmonster_start_go},
+{"monster_start_go", (byte *)monster_start_go},
+{"monster_start", (byte *)monster_start},
+{"monster_death_use", (byte *)monster_death_use},
+{"monster_triggered_start", (byte *)monster_triggered_start},
+{"monster_triggered_spawn_use", (byte *)monster_triggered_spawn_use},
+{"monster_triggered_spawn", (byte *)monster_triggered_spawn},
+{"monster_use", (byte *)monster_use},
+{"monster_think", (byte *)monster_think},
+{"M_MoveFrame", (byte *)M_MoveFrame},
+{"M_SetEffects", (byte *)M_SetEffects},
+{"M_droptofloor", (byte *)M_droptofloor},
+{"M_WorldEffects", (byte *)M_WorldEffects},
+{"M_CatagorizePosition", (byte *)M_CatagorizePosition},
+{"M_CheckGround", (byte *)M_CheckGround},
+{"AttackFinished", (byte *)AttackFinished},
+{"M_FlyCheck", (byte *)M_FlyCheck},
+{"M_FliesOn", (byte *)M_FliesOn},
+{"M_FliesOff", (byte *)M_FliesOff},
+{"monster_fire_bfg", (byte *)monster_fire_bfg},
+{"monster_fire_railgun", (byte *)monster_fire_railgun},
+{"monster_fire_rocket", (byte *)monster_fire_rocket},
+{"monster_fire_grenade", (byte *)monster_fire_grenade},
+{"monster_dabeam", (byte *)monster_dabeam},
+{"dabeam_hit", (byte *)dabeam_hit},
+{"monster_fire_heat", (byte *)monster_fire_heat},
+{"monster_fire_ionripper", (byte *)monster_fire_ionripper},
+{"monster_fire_blueblaster", (byte *)monster_fire_blueblaster},
+{"monster_fire_blaster", (byte *)monster_fire_blaster},
+{"monster_fire_shotgun", (byte *)monster_fire_shotgun},
+{"monster_fire_bullet", (byte *)monster_fire_bullet},
+{"SP_misc_nuke", (byte *)SP_misc_nuke},
+{"use_nuke", (byte *)use_nuke},
+{"SP_misc_amb4", (byte *)SP_misc_amb4},
+{"amb4_think", (byte *)amb4_think},
+{"SP_misc_teleporter_dest", (byte *)SP_misc_teleporter_dest},
+{"SP_misc_teleporter", (byte *)SP_misc_teleporter},
+{"teleporter_touch", (byte *)teleporter_touch},
+{"SP_func_clock", (byte *)SP_func_clock},
+{"func_clock_use", (byte *)func_clock_use},
+{"func_clock_think", (byte *)func_clock_think},
+{"func_clock_format_countdown", (byte *)func_clock_format_countdown},
+{"func_clock_reset", (byte *)func_clock_reset},
+{"SP_target_string", (byte *)SP_target_string},
+{"target_string_use", (byte *)target_string_use},
+{"SP_target_character", (byte *)SP_target_character},
+{"SP_misc_gib_head", (byte *)SP_misc_gib_head},
+{"SP_misc_gib_leg", (byte *)SP_misc_gib_leg},
+{"SP_misc_gib_arm", (byte *)SP_misc_gib_arm},
+{"SP_light_mine2", (byte *)SP_light_mine2},
+{"SP_light_mine1", (byte *)SP_light_mine1},
+{"SP_misc_satellite_dish", (byte *)SP_misc_satellite_dish},
+{"misc_satellite_dish_use", (byte *)misc_satellite_dish_use},
+{"misc_satellite_dish_think", (byte *)misc_satellite_dish_think},
+{"SP_misc_transport", (byte *)SP_misc_transport},
+{"SP_misc_strogg_ship", (byte *)SP_misc_strogg_ship},
+{"misc_strogg_ship_use", (byte *)misc_strogg_ship_use},
+{"SP_misc_viper_missile", (byte *)SP_misc_viper_missile},
+{"misc_viper_missile_use", (byte *)misc_viper_missile_use},
+{"SP_misc_viper_bomb", (byte *)SP_misc_viper_bomb},
+{"misc_viper_bomb_use", (byte *)misc_viper_bomb_use},
+{"misc_viper_bomb_prethink", (byte *)misc_viper_bomb_prethink},
+{"misc_viper_bomb_touch", (byte *)misc_viper_bomb_touch},
+{"SP_misc_bigviper", (byte *)SP_misc_bigviper},
+{"SP_misc_crashviper", (byte *)SP_misc_crashviper},
+{"SP_misc_viper", (byte *)SP_misc_viper},
+{"misc_viper_use", (byte *)misc_viper_use},
+{"SP_misc_deadsoldier", (byte *)SP_misc_deadsoldier},
+{"misc_deadsoldier_die", (byte *)misc_deadsoldier_die},
+{"SP_misc_banner", (byte *)SP_misc_banner},
+{"misc_banner_think", (byte *)misc_banner_think},
+{"SP_monster_commander_body", (byte *)SP_monster_commander_body},
+{"commander_body_drop", (byte *)commander_body_drop},
+{"commander_body_use", (byte *)commander_body_use},
+{"commander_body_think", (byte *)commander_body_think},
+{"SP_misc_easterchick2", (byte *)SP_misc_easterchick2},
+{"misc_easterchick2_think", (byte *)misc_easterchick2_think},
+{"SP_misc_easterchick", (byte *)SP_misc_easterchick},
+{"misc_easterchick_think", (byte *)misc_easterchick_think},
+{"SP_misc_eastertank", (byte *)SP_misc_eastertank},
+{"misc_eastertank_think", (byte *)misc_eastertank_think},
+{"SP_misc_blackhole", (byte *)SP_misc_blackhole},
+{"misc_blackhole_think", (byte *)misc_blackhole_think},
+{"misc_blackhole_use", (byte *)misc_blackhole_use},
+{"SP_misc_explobox", (byte *)SP_misc_explobox},
+{"barrel_delay", (byte *)barrel_delay},
+{"barrel_explode", (byte *)barrel_explode},
+{"barrel_touch", (byte *)barrel_touch},
+{"SP_func_explosive", (byte *)SP_func_explosive},
+{"func_explosive_spawn", (byte *)func_explosive_spawn},
+{"func_explosive_use", (byte *)func_explosive_use},
+{"func_explosive_explode", (byte *)func_explosive_explode},
+{"SP_func_object", (byte *)SP_func_object},
+{"func_object_use", (byte *)func_object_use},
+{"func_object_release", (byte *)func_object_release},
+{"func_object_touch", (byte *)func_object_touch},
+{"SP_func_wall", (byte *)SP_func_wall},
+{"func_wall_use", (byte *)func_wall_use},
+{"SP_light", (byte *)SP_light},
+{"light_use", (byte *)light_use},
+{"SP_info_notnull", (byte *)SP_info_notnull},
+{"SP_info_null", (byte *)SP_info_null},
+{"SP_viewthing", (byte *)SP_viewthing},
+{"TH_viewthing", (byte *)TH_viewthing},
+{"SP_point_combat", (byte *)SP_point_combat},
+{"point_combat_touch", (byte *)point_combat_touch},
+{"SP_path_corner", (byte *)SP_path_corner},
+{"path_corner_touch", (byte *)path_corner_touch},
+{"BecomeExplosion2", (byte *)BecomeExplosion2},
+{"BecomeExplosion1", (byte *)BecomeExplosion1},
+{"ThrowDebris", (byte *)ThrowDebris},
+{"debris_die", (byte *)debris_die},
+{"ThrowClientHead", (byte *)ThrowClientHead},
+{"ThrowHeadACID", (byte *)ThrowHeadACID},
+{"ThrowGibACID", (byte *)ThrowGibACID},
+{"ThrowHead", (byte *)ThrowHead},
+{"ThrowGib", (byte *)ThrowGib},
+{"gib_die", (byte *)gib_die},
+{"gib_touch", (byte *)gib_touch},
+{"gib_think", (byte *)gib_think},
+{"ClipGibVelocity", (byte *)ClipGibVelocity},
+{"VelocityForDamage", (byte *)VelocityForDamage},
+{"SP_func_areaportal", (byte *)SP_func_areaportal},
+{"Use_Areaportal", (byte *)Use_Areaportal},
+{"G_RunFrame", (byte *)G_RunFrame},
+{"ExitLevel", (byte *)ExitLevel},
+{"CheckDMRules", (byte *)CheckDMRules},
+{"CheckNeedPass", (byte *)CheckNeedPass},
+{"EndDMLevel", (byte *)EndDMLevel},
+{"CreateTargetChangeLevel", (byte *)CreateTargetChangeLevel},
+{"ClientEndServerFrames", (byte *)ClientEndServerFrames},
+{"Com_Printf", (byte *)Com_Printf},
+{"Sys_Error", (byte *)Sys_Error},
+{"GetGameAPI", (byte *)GetGameAPI},
+{"ShutdownGame", (byte *)ShutdownGame},
+{"SetItemNames", (byte *)SetItemNames},
+{"InitItems", (byte *)InitItems},
+{"SP_item_foodcube", (byte *)SP_item_foodcube},
+{"SP_item_health_mega", (byte *)SP_item_health_mega},
+{"SP_item_health_large", (byte *)SP_item_health_large},
+{"SP_item_health_small", (byte *)SP_item_health_small},
+{"SP_item_health", (byte *)SP_item_health},
+{"SpawnItem", (byte *)SpawnItem},
+{"PrecacheItem", (byte *)PrecacheItem},
+{"droptofloor", (byte *)droptofloor},
+{"Use_Item", (byte *)Use_Item},
+{"Drop_Item", (byte *)Drop_Item},
+{"drop_make_touchable", (byte *)drop_make_touchable},
+{"drop_temp_touch", (byte *)drop_temp_touch},
+{"Touch_Item", (byte *)Touch_Item},
+{"Drop_PowerArmor", (byte *)Drop_PowerArmor},
+{"Pickup_PowerArmor", (byte *)Pickup_PowerArmor},
+{"Use_PowerArmor", (byte *)Use_PowerArmor},
+{"PowerArmorType", (byte *)PowerArmorType},
+{"Pickup_Armor", (byte *)Pickup_Armor},
+{"ArmorIndex", (byte *)ArmorIndex},
+{"Pickup_Health", (byte *)Pickup_Health},
+{"MegaHealth_think", (byte *)MegaHealth_think},
+{"Drop_Ammo", (byte *)Drop_Ammo},
+{"Pickup_Ammo", (byte *)Pickup_Ammo},
+{"Add_Ammo", (byte *)Add_Ammo},
+{"Pickup_Key", (byte *)Pickup_Key},
+{"Use_Silencer", (byte *)Use_Silencer},
+{"Use_Invulnerability", (byte *)Use_Invulnerability},
+{"Use_Envirosuit", (byte *)Use_Envirosuit},
+{"Use_Breather", (byte *)Use_Breather},
+{"Use_QuadFire", (byte *)Use_QuadFire},
+{"Use_Quad", (byte *)Use_Quad},
+{"Pickup_Pack", (byte *)Pickup_Pack},
+{"Pickup_Bandolier", (byte *)Pickup_Bandolier},
+{"Pickup_AncientHead", (byte *)Pickup_AncientHead},
+{"Pickup_Adrenaline", (byte *)Pickup_Adrenaline},
+{"Drop_General", (byte *)Drop_General},
+{"Pickup_Powerup", (byte *)Pickup_Powerup},
+{"SetRespawn", (byte *)SetRespawn},
+{"DoRespawn", (byte *)DoRespawn},
+{"FindItem", (byte *)FindItem},
+{"FindItemByClassname", (byte *)FindItemByClassname},
+{"GetItemByIndex", (byte *)GetItemByIndex},
+{"SP_object_repair", (byte *)SP_object_repair},
+{"object_repair_sparks", (byte *)object_repair_sparks},
+{"object_repair_dead", (byte *)object_repair_dead},
+{"object_repair_fx", (byte *)object_repair_fx},
+{"SP_rotating_light", (byte *)SP_rotating_light},
+{"rotating_light_use", (byte *)rotating_light_use},
+{"rotating_light_killed", (byte *)rotating_light_killed},
+{"rotating_light_alarm", (byte *)rotating_light_alarm},
+{"SP_func_killbox", (byte *)SP_func_killbox},
+{"use_killbox", (byte *)use_killbox},
+{"SP_func_door_secret", (byte *)SP_func_door_secret},
+{"door_secret_die", (byte *)door_secret_die},
+{"door_secret_blocked", (byte *)door_secret_blocked},
+{"door_secret_done", (byte *)door_secret_done},
+{"door_secret_move6", (byte *)door_secret_move6},
+{"door_secret_move5", (byte *)door_secret_move5},
+{"door_secret_move4", (byte *)door_secret_move4},
+{"door_secret_move3", (byte *)door_secret_move3},
+{"door_secret_move2", (byte *)door_secret_move2},
+{"door_secret_move1", (byte *)door_secret_move1},
+{"door_secret_use", (byte *)door_secret_use},
+{"SP_func_conveyor", (byte *)SP_func_conveyor},
+{"func_conveyor_use", (byte *)func_conveyor_use},
+{"SP_func_timer", (byte *)SP_func_timer},
+{"func_timer_use", (byte *)func_timer_use},
+{"func_timer_think", (byte *)func_timer_think},
+{"SP_trigger_elevator", (byte *)SP_trigger_elevator},
+{"trigger_elevator_init", (byte *)trigger_elevator_init},
+{"trigger_elevator_use", (byte *)trigger_elevator_use},
+{"SP_func_train", (byte *)SP_func_train},
+{"train_use", (byte *)train_use},
+{"func_train_find", (byte *)func_train_find},
+{"train_resume", (byte *)train_resume},
+{"train_next", (byte *)train_next},
+{"train_wait", (byte *)train_wait},
+{"train_blocked", (byte *)train_blocked},
+{"SP_func_water", (byte *)SP_func_water},
+{"SP_func_door_rotating", (byte *)SP_func_door_rotating},
+{"SP_func_door", (byte *)SP_func_door},
+{"door_touch", (byte *)door_touch},
+{"door_killed", (byte *)door_killed},
+{"door_blocked", (byte *)door_blocked},
+{"Think_SpawnDoorTrigger", (byte *)Think_SpawnDoorTrigger},
+{"Think_CalcMoveSpeed", (byte *)Think_CalcMoveSpeed},
+{"Touch_DoorTrigger", (byte *)Touch_DoorTrigger},
+{"door_use", (byte *)door_use},
+{"door_go_up", (byte *)door_go_up},
+{"door_go_down", (byte *)door_go_down},
+{"door_hit_bottom", (byte *)door_hit_bottom},
+{"door_hit_top", (byte *)door_hit_top},
+{"door_use_areaportals", (byte *)door_use_areaportals},
+{"SP_func_button", (byte *)SP_func_button},
+{"button_killed", (byte *)button_killed},
+{"button_touch", (byte *)button_touch},
+{"button_use", (byte *)button_use},
+{"button_fire", (byte *)button_fire},
+{"button_wait", (byte *)button_wait},
+{"button_return", (byte *)button_return},
+{"button_done", (byte *)button_done},
+{"SP_func_rotating", (byte *)SP_func_rotating},
+{"rotating_use", (byte *)rotating_use},
+{"rotating_touch", (byte *)rotating_touch},
+{"rotating_blocked", (byte *)rotating_blocked},
+{"SP_func_plat", (byte *)SP_func_plat},
+{"plat_spawn_inside_trigger", (byte *)plat_spawn_inside_trigger},
+{"Touch_Plat_Center", (byte *)Touch_Plat_Center},
+{"Use_Plat", (byte *)Use_Plat},
+{"plat_blocked", (byte *)plat_blocked},
+{"plat_go_up", (byte *)plat_go_up},
+{"plat_go_down", (byte *)plat_go_down},
+{"plat_hit_bottom", (byte *)plat_hit_bottom},
+{"plat_hit_top", (byte *)plat_hit_top},
+{"Think_AccelMove", (byte *)Think_AccelMove},
+{"plat_Accelerate", (byte *)plat_Accelerate},
+{"plat_CalcAcceleratedMove", (byte *)plat_CalcAcceleratedMove},
+{"AngleMove_Calc", (byte *)AngleMove_Calc},
+{"AngleMove_Begin", (byte *)AngleMove_Begin},
+{"AngleMove_Final", (byte *)AngleMove_Final},
+{"AngleMove_Done", (byte *)AngleMove_Done},
+{"Move_Calc", (byte *)Move_Calc},
+{"Move_Begin", (byte *)Move_Begin},
+{"Move_Final", (byte *)Move_Final},
+{"Move_Done", (byte *)Move_Done},
+{"T_RadiusDamage", (byte *)T_RadiusDamage},
+{"T_Damage", (byte *)T_Damage},
+{"M_ReactToDamage", (byte *)M_ReactToDamage},
+{"CheckArmor", (byte *)CheckArmor},
+{"CheckPowerArmor", (byte *)CheckPowerArmor},
+{"SpawnDamage", (byte *)SpawnDamage},
+{"Killed", (byte *)Killed},
+{"CanDamage", (byte *)CanDamage},
+{"ClientCommand", (byte *)ClientCommand},
+{"Cmd_PlayerList_f", (byte *)Cmd_PlayerList_f},
+{"Cmd_Say_f", (byte *)Cmd_Say_f},
+{"Cmd_Wave_f", (byte *)Cmd_Wave_f},
+{"Cmd_Players_f", (byte *)Cmd_Players_f},
+{"PlayerSort", (byte *)PlayerSort},
+{"Cmd_PutAway_f", (byte *)Cmd_PutAway_f},
+{"Cmd_Kill_f", (byte *)Cmd_Kill_f},
+{"Cmd_InvDrop_f", (byte *)Cmd_InvDrop_f},
+{"Cmd_WeapLast_f", (byte *)Cmd_WeapLast_f},
+{"Cmd_WeapNext_f", (byte *)Cmd_WeapNext_f},
+{"Cmd_WeapPrev_f", (byte *)Cmd_WeapPrev_f},
+{"Cmd_InvUse_f", (byte *)Cmd_InvUse_f},
+{"Cmd_Inven_f", (byte *)Cmd_Inven_f},
+{"Cmd_Help_f", (byte *)Cmd_Help_f},
+{"Cmd_Score_f", (byte *)Cmd_Score_f},
+{"Cmd_Drop_f", (byte *)Cmd_Drop_f},
+{"Cmd_Use_f", (byte *)Cmd_Use_f},
+{"Cmd_Noclip_f", (byte *)Cmd_Noclip_f},
+{"Cmd_Notarget_f", (byte *)Cmd_Notarget_f},
+{"Cmd_God_f", (byte *)Cmd_God_f},
+{"Cmd_Give_f", (byte *)Cmd_Give_f},
+{"ValidateSelectedItem", (byte *)ValidateSelectedItem},
+{"SelectPrevItem", (byte *)SelectPrevItem},
+{"SelectNextItem", (byte *)SelectNextItem},
+{"OnSameTeam", (byte *)OnSameTeam},
+{"GetChaseTarget", (byte *)GetChaseTarget},
+{"ChasePrev", (byte *)ChasePrev},
+{"ChaseNext", (byte *)ChaseNext},
+{"UpdateChaseCam", (byte *)UpdateChaseCam},
+{"ai_run", (byte *)ai_run},
+{"ai_checkattack", (byte *)ai_checkattack},
+{"ai_run_slide", (byte *)ai_run_slide},
+{"ai_run_missile", (byte *)ai_run_missile},
+{"ai_run_melee", (byte *)ai_run_melee},
+{"M_CheckAttack", (byte *)M_CheckAttack},
+{"FacingIdeal", (byte *)FacingIdeal},
+{"FindTarget", (byte *)FindTarget},
+{"FoundTarget", (byte *)FoundTarget},
+{"HuntTarget", (byte *)HuntTarget},
+{"infront", (byte *)infront},
+{"visible", (byte *)visible},
+{"range", (byte *)range},
+{"ai_turn", (byte *)ai_turn},
+{"ai_charge", (byte *)ai_charge},
+{"ai_walk", (byte *)ai_walk},
+{"ai_stand", (byte *)ai_stand},
+{"ai_move", (byte *)ai_move},
+{"AI_SetSightClient", (byte *)AI_SetSightClient},
+{"wait_and_change_think", (byte *)wait_and_change_think},
+{0, 0}
diff --git a/xatrix/src/savegame/tables/gamemmove_decs.h b/xatrix/src/savegame/tables/gamemmove_decs.h
new file mode 100644
index 0000000..e51848c
--- /dev/null
+++ b/xatrix/src/savegame/tables/gamemmove_decs.h
@@ -0,0 +1,377 @@
+/*
+ * =======================================================================
+ *
+ * Pointers to every mmove_t in the game.so.
+ *
+ * =======================================================================
+ */
+
+extern mmove_t tank_move_death ;
+extern mmove_t tank_move_attack_chain ;
+extern mmove_t tank_move_attack_post_rocket ;
+extern mmove_t tank_move_attack_fire_rocket ;
+extern mmove_t tank_move_attack_pre_rocket ;
+extern mmove_t tank_move_attack_strike ;
+extern mmove_t tank_move_attack_post_blast ;
+extern mmove_t tank_move_reattack_blast ;
+extern mmove_t tank_move_attack_blast ;
+extern mmove_t tank_move_pain3 ;
+extern mmove_t tank_move_pain2 ;
+extern mmove_t tank_move_pain1 ;
+extern mmove_t tank_move_stop_run ;
+extern mmove_t tank_move_run ;
+extern mmove_t tank_move_start_run ;
+extern mmove_t tank_move_stop_walk ;
+extern mmove_t tank_move_walk ;
+extern mmove_t tank_move_start_walk ;
+extern mmove_t tank_move_stand ;
+extern mmove_t supertank_move_end_attack1 ;
+extern mmove_t supertank_move_attack1 ;
+extern mmove_t supertank_move_attack2 ;
+extern mmove_t supertank_move_attack3 ;
+extern mmove_t supertank_move_attack4 ;
+extern mmove_t supertank_move_backward ;
+extern mmove_t supertank_move_death ;
+extern mmove_t supertank_move_pain1 ;
+extern mmove_t supertank_move_pain2 ;
+extern mmove_t supertank_move_pain3 ;
+extern mmove_t supertank_move_turn_left ;
+extern mmove_t supertank_move_turn_right ;
+extern mmove_t supertank_move_forward ;
+extern mmove_t supertank_move_run ;
+extern mmove_t supertank_move_stand ;
+extern mmove_t soldierh_move_death6 ;
+extern mmove_t soldierh_move_death5 ;
+extern mmove_t soldierh_move_death4 ;
+extern mmove_t soldierh_move_death3 ;
+extern mmove_t soldierh_move_death2 ;
+extern mmove_t soldierh_move_death1 ;
+extern mmove_t soldierh_move_duck ;
+extern mmove_t soldierh_move_attack6 ;
+extern mmove_t soldierh_move_attack4 ;
+extern mmove_t soldierh_move_attack3 ;
+extern mmove_t soldierh_move_attack2 ;
+extern mmove_t soldierh_move_attack1 ;
+extern mmove_t soldierh_move_pain4 ;
+extern mmove_t soldierh_move_pain3 ;
+extern mmove_t soldierh_move_pain2 ;
+extern mmove_t soldierh_move_pain1 ;
+extern mmove_t soldierh_move_run ;
+extern mmove_t soldierh_move_start_run ;
+extern mmove_t soldierh_move_walk2 ;
+extern mmove_t soldierh_move_walk1 ;
+extern mmove_t soldierh_move_stand3 ;
+extern mmove_t soldierh_move_stand1 ;
+extern mmove_t soldier_move_death6 ;
+extern mmove_t soldier_move_death5 ;
+extern mmove_t soldier_move_death4 ;
+extern mmove_t soldier_move_death3 ;
+extern mmove_t soldier_move_death2 ;
+extern mmove_t soldier_move_death1 ;
+extern mmove_t soldier_move_duck ;
+extern mmove_t soldier_move_attack6 ;
+extern mmove_t soldier_move_attack4 ;
+extern mmove_t soldier_move_attack3 ;
+extern mmove_t soldier_move_attack2 ;
+extern mmove_t soldier_move_attack1 ;
+extern mmove_t soldier_move_pain4 ;
+extern mmove_t soldier_move_pain3 ;
+extern mmove_t soldier_move_pain2 ;
+extern mmove_t soldier_move_pain1 ;
+extern mmove_t soldier_move_run ;
+extern mmove_t soldier_move_start_run ;
+extern mmove_t soldier_move_walk2 ;
+extern mmove_t soldier_move_walk1 ;
+extern mmove_t soldier_move_stand3 ;
+extern mmove_t soldier_move_stand1 ;
+extern mmove_t parasite_move_death ;
+extern mmove_t parasite_move_break ;
+extern mmove_t parasite_move_drain ;
+extern mmove_t parasite_move_pain1 ;
+extern mmove_t parasite_move_stop_walk ;
+extern mmove_t parasite_move_start_walk ;
+extern mmove_t parasite_move_walk ;
+extern mmove_t parasite_move_stop_run ;
+extern mmove_t parasite_move_start_run ;
+extern mmove_t parasite_move_run ;
+extern mmove_t parasite_move_stand ;
+extern mmove_t parasite_move_end_fidget ;
+extern mmove_t parasite_move_fidget ;
+extern mmove_t parasite_move_start_fidget ;
+extern mmove_t mutant_move_death2 ;
+extern mmove_t mutant_move_death1 ;
+extern mmove_t mutant_move_pain3 ;
+extern mmove_t mutant_move_pain2 ;
+extern mmove_t mutant_move_pain1 ;
+extern mmove_t mutant_move_jump ;
+extern mmove_t mutant_move_attack ;
+extern mmove_t mutant_move_run ;
+extern mmove_t mutant_move_start_walk ;
+extern mmove_t mutant_move_walk ;
+extern mmove_t mutant_move_idle ;
+extern mmove_t mutant_move_stand ;
+extern mmove_t medic_move_attackCable ;
+extern mmove_t medic_move_attackBlaster ;
+extern mmove_t medic_move_attackHyperBlaster ;
+extern mmove_t medic_move_duck ;
+extern mmove_t medic_move_death ;
+extern mmove_t medic_move_pain2 ;
+extern mmove_t medic_move_pain1 ;
+extern mmove_t medic_move_run ;
+extern mmove_t medic_move_walk ;
+extern mmove_t medic_move_stand ;
+extern mmove_t insane_move_struggle_cross ;
+extern mmove_t insane_move_cross ;
+extern mmove_t insane_move_crawl_death ;
+extern mmove_t insane_move_crawl_pain ;
+extern mmove_t insane_move_runcrawl ;
+extern mmove_t insane_move_crawl ;
+extern mmove_t insane_move_stand_death ;
+extern mmove_t insane_move_stand_pain ;
+extern mmove_t insane_move_run_insane ;
+extern mmove_t insane_move_walk_insane ;
+extern mmove_t insane_move_run_normal ;
+extern mmove_t insane_move_walk_normal ;
+extern mmove_t insane_move_down ;
+extern mmove_t insane_move_jumpdown ;
+extern mmove_t insane_move_downtoup ;
+extern mmove_t insane_move_uptodown ;
+extern mmove_t insane_move_stand_insane ;
+extern mmove_t insane_move_stand_normal ;
+extern mmove_t infantry_move_attack2 ;
+extern mmove_t infantry_move_attack1 ;
+extern mmove_t infantry_move_duck ;
+extern mmove_t infantry_move_death3 ;
+extern mmove_t infantry_move_death2 ;
+extern mmove_t infantry_move_death1 ;
+extern mmove_t infantry_move_pain2 ;
+extern mmove_t infantry_move_pain1 ;
+extern mmove_t infantry_move_run ;
+extern mmove_t infantry_move_walk ;
+extern mmove_t infantry_move_fidget ;
+extern mmove_t infantry_move_stand ;
+extern mmove_t hover_move_end_attack ;
+extern mmove_t hover_move_attack1 ;
+extern mmove_t hover_move_start_attack ;
+extern mmove_t hover_move_backward ;
+extern mmove_t hover_move_death1 ;
+extern mmove_t hover_move_run ;
+extern mmove_t hover_move_walk ;
+extern mmove_t hover_move_forward ;
+extern mmove_t hover_move_land ;
+extern mmove_t hover_move_pain1 ;
+extern mmove_t hover_move_pain2 ;
+extern mmove_t hover_move_pain3 ;
+extern mmove_t hover_move_takeoff ;
+extern mmove_t hover_move_stop2 ;
+extern mmove_t hover_move_stop1 ;
+extern mmove_t hover_move_stand ;
+extern mmove_t gunner_move_attack_grenade ;
+extern mmove_t gunner_move_endfire_chain ;
+extern mmove_t gunner_move_fire_chain ;
+extern mmove_t gunner_move_attack_chain ;
+extern mmove_t gunner_move_duck ;
+extern mmove_t gunner_move_death ;
+extern mmove_t gunner_move_pain1 ;
+extern mmove_t gunner_move_pain2 ;
+extern mmove_t gunner_move_pain3 ;
+extern mmove_t gunner_move_runandshoot ;
+extern mmove_t gunner_move_run ;
+extern mmove_t gunner_move_walk ;
+extern mmove_t gunner_move_stand ;
+extern mmove_t gunner_move_fidget ;
+extern mmove_t gladiator_move_death ;
+extern mmove_t gladiator_move_pain_air ;
+extern mmove_t gladiator_move_pain ;
+extern mmove_t gladiator_move_attack_gun ;
+extern mmove_t gladiator_move_attack_melee ;
+extern mmove_t gladiator_move_run ;
+extern mmove_t gladiator_move_walk ;
+extern mmove_t gladiator_move_stand ;
+extern mmove_t gladb_move_death ;
+extern mmove_t gladb_move_pain_air ;
+extern mmove_t gladb_move_pain ;
+extern mmove_t gladb_move_attack_gun ;
+extern mmove_t gladb_move_attack_melee ;
+extern mmove_t gladb_move_run ;
+extern mmove_t gladb_move_walk ;
+extern mmove_t gladb_move_stand ;
+extern mmove_t gekk_move_rduck ;
+extern mmove_t gekk_move_lduck ;
+extern mmove_t gekk_move_wdeath ;
+extern mmove_t gekk_move_death4 ;
+extern mmove_t gekk_move_death3 ;
+extern mmove_t gekk_move_death1 ;
+extern mmove_t gekk_move_pain2 ;
+extern mmove_t gekk_move_pain1 ;
+extern mmove_t gekk_move_pain ;
+extern mmove_t gekk_move_attack ;
+extern mmove_t gekk_move_leapatk2 ;
+extern mmove_t gekk_move_leapatk ;
+extern mmove_t gekk_move_attack2 ;
+extern mmove_t gekk_move_attack1 ;
+extern mmove_t gekk_move_spit ;
+extern mmove_t gekk_move_run_start ;
+extern mmove_t gekk_move_run ;
+extern mmove_t gekk_move_walk ;
+extern mmove_t gekk_move_idle2 ;
+extern mmove_t gekk_move_idle ;
+extern mmove_t gekk_move_swim_start ;
+extern mmove_t gekk_move_swim_loop ;
+extern mmove_t gekk_move_standunderwater ;
+extern mmove_t gekk_move_stand ;
+extern mmove_t flyer_move_loop_melee ;
+extern mmove_t flyer_move_end_melee ;
+extern mmove_t flyer_move_start_melee ;
+extern mmove_t flyer_move_attack2 ;
+extern mmove_t flyer_move_bankleft ;
+extern mmove_t flyer_move_bankright ;
+extern mmove_t flyer_move_defense ;
+extern mmove_t flyer_move_pain1 ;
+extern mmove_t flyer_move_pain2 ;
+extern mmove_t flyer_move_pain3 ;
+extern mmove_t flyer_move_rollleft ;
+extern mmove_t flyer_move_rollright ;
+extern mmove_t flyer_move_stop ;
+extern mmove_t flyer_move_start ;
+extern mmove_t flyer_move_run ;
+extern mmove_t flyer_move_walk ;
+extern mmove_t flyer_move_stand ;
+extern mmove_t floater_move_run ;
+extern mmove_t floater_move_walk ;
+extern mmove_t floater_move_pain3 ;
+extern mmove_t floater_move_pain2 ;
+extern mmove_t floater_move_pain1 ;
+extern mmove_t floater_move_death ;
+extern mmove_t floater_move_attack3 ;
+extern mmove_t floater_move_attack2 ;
+extern mmove_t floater_move_attack1 ;
+extern mmove_t floater_move_activate ;
+extern mmove_t floater_move_stand2 ;
+extern mmove_t floater_move_stand1 ;
+extern mmove_t flipper_move_death ;
+extern mmove_t flipper_move_attack ;
+extern mmove_t flipper_move_pain1 ;
+extern mmove_t flipper_move_pain2 ;
+extern mmove_t flipper_move_start_run ;
+extern mmove_t flipper_move_walk ;
+extern mmove_t flipper_move_run_start ;
+extern mmove_t flipper_move_run_loop ;
+extern mmove_t flipper_move_stand ;
+extern mmove_t fixbot_move_weld_end ;
+extern mmove_t fixbot_move_weld ;
+extern mmove_t fixbot_move_weld_start ;
+extern mmove_t fixbot_move_attack2 ;
+extern mmove_t fixbot_move_laserattack ;
+extern mmove_t fixbot_move_attack1 ;
+extern mmove_t fixbot_move_start_attack ;
+extern mmove_t fixbot_move_backward ;
+extern mmove_t fixbot_move_death1 ;
+extern mmove_t fixbot_move_run ;
+extern mmove_t fixbot_move_walk ;
+extern mmove_t fixbot_move_forward ;
+extern mmove_t fixbot_move_land ;
+extern mmove_t fixbot_move_pain3 ;
+extern mmove_t fixbot_move_painb ;
+extern mmove_t fixbot_move_paina ;
+extern mmove_t fixbot_move_takeoff ;
+extern mmove_t fixbot_move_turn ;
+extern mmove_t fixbot_move_roamgoal ;
+extern mmove_t fixbot_move_pickup ;
+extern mmove_t fixbot_move_stand2 ;
+extern mmove_t fixbot_move_stand ;
+extern mmove_t fixbot_move_landing ;
+extern mmove_t chick_move_start_slash ;
+extern mmove_t chick_move_end_slash ;
+extern mmove_t chick_move_slash ;
+extern mmove_t chick_move_end_attack1 ;
+extern mmove_t chick_move_attack1 ;
+extern mmove_t chick_move_start_attack1 ;
+extern mmove_t chick_move_duck ;
+extern mmove_t chick_move_death1 ;
+extern mmove_t chick_move_death2 ;
+extern mmove_t chick_move_pain3 ;
+extern mmove_t chick_move_pain2 ;
+extern mmove_t chick_move_pain1 ;
+extern mmove_t chick_move_walk ;
+extern mmove_t chick_move_run ;
+extern mmove_t chick_move_start_run ;
+extern mmove_t chick_move_stand ;
+extern mmove_t chick_move_fidget ;
+extern mmove_t brain_move_run ;
+extern mmove_t brain_move_attack4 ;
+extern mmove_t brain_move_attack3 ;
+extern mmove_t brain_move_attack2 ;
+extern mmove_t brain_move_attack1 ;
+extern mmove_t brain_move_death1 ;
+extern mmove_t brain_move_death2 ;
+extern mmove_t brain_move_duck ;
+extern mmove_t brain_move_pain1 ;
+extern mmove_t brain_move_pain2 ;
+extern mmove_t brain_move_pain3 ;
+extern mmove_t brain_move_defense ;
+extern mmove_t brain_move_walk1 ;
+extern mmove_t brain_move_idle ;
+extern mmove_t brain_move_stand ;
+extern mmove_t boss5_move_end_attack1 ;
+extern mmove_t boss5_move_attack1 ;
+extern mmove_t boss5_move_attack2 ;
+extern mmove_t boss5_move_attack3 ;
+extern mmove_t boss5_move_attack4 ;
+extern mmove_t boss5_move_backward ;
+extern mmove_t boss5_move_death ;
+extern mmove_t boss5_move_pain1 ;
+extern mmove_t boss5_move_pain2 ;
+extern mmove_t boss5_move_pain3 ;
+extern mmove_t boss5_move_turn_left ;
+extern mmove_t boss5_move_turn_right ;
+extern mmove_t boss5_move_forward ;
+extern mmove_t boss5_move_run ;
+extern mmove_t boss5_move_stand ;
+extern mmove_t makron_move_attack5 ;
+extern mmove_t makron_move_attack4 ;
+extern mmove_t makron_move_attack3 ;
+extern mmove_t makron_move_sight ;
+extern mmove_t makron_move_death3 ;
+extern mmove_t makron_move_death2 ;
+extern mmove_t makron_move_pain4 ;
+extern mmove_t makron_move_pain5 ;
+extern mmove_t makron_move_pain6 ;
+extern mmove_t makron_move_walk ;
+extern mmove_t makron_move_run ;
+extern mmove_t makron_move_stand ;
+extern mmove_t jorg_move_end_attack1 ;
+extern mmove_t jorg_move_attack1 ;
+extern mmove_t jorg_move_start_attack1 ;
+extern mmove_t jorg_move_attack2 ;
+extern mmove_t jorg_move_death ;
+extern mmove_t jorg_move_pain1 ;
+extern mmove_t jorg_move_pain2 ;
+extern mmove_t jorg_move_pain3 ;
+extern mmove_t jorg_move_end_walk ;
+extern mmove_t jorg_move_walk ;
+extern mmove_t jorg_move_start_walk ;
+extern mmove_t jorg_move_run ;
+extern mmove_t jorg_move_stand ;
+extern mmove_t boss2_move_death ;
+extern mmove_t boss2_move_pain_light ;
+extern mmove_t boss2_move_pain_heavy ;
+extern mmove_t boss2_move_attack_rocket ;
+extern mmove_t boss2_move_attack_post_mg ;
+extern mmove_t boss2_move_attack_mg ;
+extern mmove_t boss2_move_attack_pre_mg ;
+extern mmove_t boss2_move_run ;
+extern mmove_t boss2_move_walk ;
+extern mmove_t boss2_move_fidget ;
+extern mmove_t boss2_move_stand ;
+extern mmove_t berserk_move_death2 ;
+extern mmove_t berserk_move_death1 ;
+extern mmove_t berserk_move_pain2 ;
+extern mmove_t berserk_move_pain1 ;
+extern mmove_t berserk_move_attack_strike ;
+extern mmove_t berserk_move_attack_club ;
+extern mmove_t berserk_move_attack_spike ;
+extern mmove_t berserk_move_run1 ;
+extern mmove_t berserk_move_walk ;
+extern mmove_t berserk_move_stand_fidget ;
+extern mmove_t berserk_move_stand ;
diff --git a/xatrix/src/savegame/tables/gamemmove_list.h b/xatrix/src/savegame/tables/gamemmove_list.h
new file mode 100644
index 0000000..70624ee
--- /dev/null
+++ b/xatrix/src/savegame/tables/gamemmove_list.h
@@ -0,0 +1,378 @@
+/*
+ * =======================================================================
+ *
+ * Prototypes for every mmove_t in the game.so.
+ *
+ * =======================================================================
+ */
+
+{"tank_move_death", &tank_move_death},
+{"tank_move_attack_chain", &tank_move_attack_chain},
+{"tank_move_attack_post_rocket", &tank_move_attack_post_rocket},
+{"tank_move_attack_fire_rocket", &tank_move_attack_fire_rocket},
+{"tank_move_attack_pre_rocket", &tank_move_attack_pre_rocket},
+{"tank_move_attack_strike", &tank_move_attack_strike},
+{"tank_move_attack_post_blast", &tank_move_attack_post_blast},
+{"tank_move_reattack_blast", &tank_move_reattack_blast},
+{"tank_move_attack_blast", &tank_move_attack_blast},
+{"tank_move_pain3", &tank_move_pain3},
+{"tank_move_pain2", &tank_move_pain2},
+{"tank_move_pain1", &tank_move_pain1},
+{"tank_move_stop_run", &tank_move_stop_run},
+{"tank_move_run", &tank_move_run},
+{"tank_move_start_run", &tank_move_start_run},
+{"tank_move_stop_walk", &tank_move_stop_walk},
+{"tank_move_walk", &tank_move_walk},
+{"tank_move_start_walk", &tank_move_start_walk},
+{"tank_move_stand", &tank_move_stand},
+{"supertank_move_end_attack1", &supertank_move_end_attack1},
+{"supertank_move_attack1", &supertank_move_attack1},
+{"supertank_move_attack2", &supertank_move_attack2},
+{"supertank_move_attack3", &supertank_move_attack3},
+{"supertank_move_attack4", &supertank_move_attack4},
+{"supertank_move_backward", &supertank_move_backward},
+{"supertank_move_death", &supertank_move_death},
+{"supertank_move_pain1", &supertank_move_pain1},
+{"supertank_move_pain2", &supertank_move_pain2},
+{"supertank_move_pain3", &supertank_move_pain3},
+{"supertank_move_turn_left", &supertank_move_turn_left},
+{"supertank_move_turn_right", &supertank_move_turn_right},
+{"supertank_move_forward", &supertank_move_forward},
+{"supertank_move_run", &supertank_move_run},
+{"supertank_move_stand", &supertank_move_stand},
+{"soldierh_move_death6", &soldierh_move_death6},
+{"soldierh_move_death5", &soldierh_move_death5},
+{"soldierh_move_death4", &soldierh_move_death4},
+{"soldierh_move_death3", &soldierh_move_death3},
+{"soldierh_move_death2", &soldierh_move_death2},
+{"soldierh_move_death1", &soldierh_move_death1},
+{"soldierh_move_duck", &soldierh_move_duck},
+{"soldierh_move_attack6", &soldierh_move_attack6},
+{"soldierh_move_attack4", &soldierh_move_attack4},
+{"soldierh_move_attack3", &soldierh_move_attack3},
+{"soldierh_move_attack2", &soldierh_move_attack2},
+{"soldierh_move_attack1", &soldierh_move_attack1},
+{"soldierh_move_pain4", &soldierh_move_pain4},
+{"soldierh_move_pain3", &soldierh_move_pain3},
+{"soldierh_move_pain2", &soldierh_move_pain2},
+{"soldierh_move_pain1", &soldierh_move_pain1},
+{"soldierh_move_run", &soldierh_move_run},
+{"soldierh_move_start_run", &soldierh_move_start_run},
+{"soldierh_move_walk2", &soldierh_move_walk2},
+{"soldierh_move_walk1", &soldierh_move_walk1},
+{"soldierh_move_stand3", &soldierh_move_stand3},
+{"soldierh_move_stand1", &soldierh_move_stand1},
+{"soldier_move_death6", &soldier_move_death6},
+{"soldier_move_death5", &soldier_move_death5},
+{"soldier_move_death4", &soldier_move_death4},
+{"soldier_move_death3", &soldier_move_death3},
+{"soldier_move_death2", &soldier_move_death2},
+{"soldier_move_death1", &soldier_move_death1},
+{"soldier_move_duck", &soldier_move_duck},
+{"soldier_move_attack6", &soldier_move_attack6},
+{"soldier_move_attack4", &soldier_move_attack4},
+{"soldier_move_attack3", &soldier_move_attack3},
+{"soldier_move_attack2", &soldier_move_attack2},
+{"soldier_move_attack1", &soldier_move_attack1},
+{"soldier_move_pain4", &soldier_move_pain4},
+{"soldier_move_pain3", &soldier_move_pain3},
+{"soldier_move_pain2", &soldier_move_pain2},
+{"soldier_move_pain1", &soldier_move_pain1},
+{"soldier_move_run", &soldier_move_run},
+{"soldier_move_start_run", &soldier_move_start_run},
+{"soldier_move_walk2", &soldier_move_walk2},
+{"soldier_move_walk1", &soldier_move_walk1},
+{"soldier_move_stand3", &soldier_move_stand3},
+{"soldier_move_stand1", &soldier_move_stand1},
+{"parasite_move_death", &parasite_move_death},
+{"parasite_move_break", &parasite_move_break},
+{"parasite_move_drain", &parasite_move_drain},
+{"parasite_move_pain1", &parasite_move_pain1},
+{"parasite_move_stop_walk", &parasite_move_stop_walk},
+{"parasite_move_start_walk", &parasite_move_start_walk},
+{"parasite_move_walk", &parasite_move_walk},
+{"parasite_move_stop_run", &parasite_move_stop_run},
+{"parasite_move_start_run", &parasite_move_start_run},
+{"parasite_move_run", &parasite_move_run},
+{"parasite_move_stand", &parasite_move_stand},
+{"parasite_move_end_fidget", &parasite_move_end_fidget},
+{"parasite_move_fidget", &parasite_move_fidget},
+{"parasite_move_start_fidget", &parasite_move_start_fidget},
+{"mutant_move_death2", &mutant_move_death2},
+{"mutant_move_death1", &mutant_move_death1},
+{"mutant_move_pain3", &mutant_move_pain3},
+{"mutant_move_pain2", &mutant_move_pain2},
+{"mutant_move_pain1", &mutant_move_pain1},
+{"mutant_move_jump", &mutant_move_jump},
+{"mutant_move_attack", &mutant_move_attack},
+{"mutant_move_run", &mutant_move_run},
+{"mutant_move_start_walk", &mutant_move_start_walk},
+{"mutant_move_walk", &mutant_move_walk},
+{"mutant_move_idle", &mutant_move_idle},
+{"mutant_move_stand", &mutant_move_stand},
+{"medic_move_attackCable", &medic_move_attackCable},
+{"medic_move_attackBlaster", &medic_move_attackBlaster},
+{"medic_move_attackHyperBlaster", &medic_move_attackHyperBlaster},
+{"medic_move_duck", &medic_move_duck},
+{"medic_move_death", &medic_move_death},
+{"medic_move_pain2", &medic_move_pain2},
+{"medic_move_pain1", &medic_move_pain1},
+{"medic_move_run", &medic_move_run},
+{"medic_move_walk", &medic_move_walk},
+{"medic_move_stand", &medic_move_stand},
+{"insane_move_struggle_cross", &insane_move_struggle_cross},
+{"insane_move_cross", &insane_move_cross},
+{"insane_move_crawl_death", &insane_move_crawl_death},
+{"insane_move_crawl_pain", &insane_move_crawl_pain},
+{"insane_move_runcrawl", &insane_move_runcrawl},
+{"insane_move_crawl", &insane_move_crawl},
+{"insane_move_stand_death", &insane_move_stand_death},
+{"insane_move_stand_pain", &insane_move_stand_pain},
+{"insane_move_run_insane", &insane_move_run_insane},
+{"insane_move_walk_insane", &insane_move_walk_insane},
+{"insane_move_run_normal", &insane_move_run_normal},
+{"insane_move_walk_normal", &insane_move_walk_normal},
+{"insane_move_down", &insane_move_down},
+{"insane_move_jumpdown", &insane_move_jumpdown},
+{"insane_move_downtoup", &insane_move_downtoup},
+{"insane_move_uptodown", &insane_move_uptodown},
+{"insane_move_stand_insane", &insane_move_stand_insane},
+{"insane_move_stand_normal", &insane_move_stand_normal},
+{"infantry_move_attack2", &infantry_move_attack2},
+{"infantry_move_attack1", &infantry_move_attack1},
+{"infantry_move_duck", &infantry_move_duck},
+{"infantry_move_death3", &infantry_move_death3},
+{"infantry_move_death2", &infantry_move_death2},
+{"infantry_move_death1", &infantry_move_death1},
+{"infantry_move_pain2", &infantry_move_pain2},
+{"infantry_move_pain1", &infantry_move_pain1},
+{"infantry_move_run", &infantry_move_run},
+{"infantry_move_walk", &infantry_move_walk},
+{"infantry_move_fidget", &infantry_move_fidget},
+{"infantry_move_stand", &infantry_move_stand},
+{"hover_move_end_attack", &hover_move_end_attack},
+{"hover_move_attack1", &hover_move_attack1},
+{"hover_move_start_attack", &hover_move_start_attack},
+{"hover_move_backward", &hover_move_backward},
+{"hover_move_death1", &hover_move_death1},
+{"hover_move_run", &hover_move_run},
+{"hover_move_walk", &hover_move_walk},
+{"hover_move_forward", &hover_move_forward},
+{"hover_move_land", &hover_move_land},
+{"hover_move_pain1", &hover_move_pain1},
+{"hover_move_pain2", &hover_move_pain2},
+{"hover_move_pain3", &hover_move_pain3},
+{"hover_move_takeoff", &hover_move_takeoff},
+{"hover_move_stop2", &hover_move_stop2},
+{"hover_move_stop1", &hover_move_stop1},
+{"hover_move_stand", &hover_move_stand},
+{"gunner_move_attack_grenade", &gunner_move_attack_grenade},
+{"gunner_move_endfire_chain", &gunner_move_endfire_chain},
+{"gunner_move_fire_chain", &gunner_move_fire_chain},
+{"gunner_move_attack_chain", &gunner_move_attack_chain},
+{"gunner_move_duck", &gunner_move_duck},
+{"gunner_move_death", &gunner_move_death},
+{"gunner_move_pain1", &gunner_move_pain1},
+{"gunner_move_pain2", &gunner_move_pain2},
+{"gunner_move_pain3", &gunner_move_pain3},
+{"gunner_move_runandshoot", &gunner_move_runandshoot},
+{"gunner_move_run", &gunner_move_run},
+{"gunner_move_walk", &gunner_move_walk},
+{"gunner_move_stand", &gunner_move_stand},
+{"gunner_move_fidget", &gunner_move_fidget},
+{"gladiator_move_death", &gladiator_move_death},
+{"gladiator_move_pain_air", &gladiator_move_pain_air},
+{"gladiator_move_pain", &gladiator_move_pain},
+{"gladiator_move_attack_gun", &gladiator_move_attack_gun},
+{"gladiator_move_attack_melee", &gladiator_move_attack_melee},
+{"gladiator_move_run", &gladiator_move_run},
+{"gladiator_move_walk", &gladiator_move_walk},
+{"gladiator_move_stand", &gladiator_move_stand},
+{"gladb_move_death", &gladb_move_death},
+{"gladb_move_pain_air", &gladb_move_pain_air},
+{"gladb_move_pain", &gladb_move_pain},
+{"gladb_move_attack_gun", &gladb_move_attack_gun},
+{"gladb_move_attack_melee", &gladb_move_attack_melee},
+{"gladb_move_run", &gladb_move_run},
+{"gladb_move_walk", &gladb_move_walk},
+{"gladb_move_stand", &gladb_move_stand},
+{"gekk_move_rduck", &gekk_move_rduck},
+{"gekk_move_lduck", &gekk_move_lduck},
+{"gekk_move_wdeath", &gekk_move_wdeath},
+{"gekk_move_death4", &gekk_move_death4},
+{"gekk_move_death3", &gekk_move_death3},
+{"gekk_move_death1", &gekk_move_death1},
+{"gekk_move_pain2", &gekk_move_pain2},
+{"gekk_move_pain1", &gekk_move_pain1},
+{"gekk_move_pain", &gekk_move_pain},
+{"gekk_move_attack", &gekk_move_attack},
+{"gekk_move_leapatk2", &gekk_move_leapatk2},
+{"gekk_move_leapatk", &gekk_move_leapatk},
+{"gekk_move_attack2", &gekk_move_attack2},
+{"gekk_move_attack1", &gekk_move_attack1},
+{"gekk_move_spit", &gekk_move_spit},
+{"gekk_move_run_start", &gekk_move_run_start},
+{"gekk_move_run", &gekk_move_run},
+{"gekk_move_walk", &gekk_move_walk},
+{"gekk_move_idle2", &gekk_move_idle2},
+{"gekk_move_idle", &gekk_move_idle},
+{"gekk_move_swim_start", &gekk_move_swim_start},
+{"gekk_move_swim_loop", &gekk_move_swim_loop},
+{"gekk_move_standunderwater", &gekk_move_standunderwater},
+{"gekk_move_stand", &gekk_move_stand},
+{"flyer_move_loop_melee", &flyer_move_loop_melee},
+{"flyer_move_end_melee", &flyer_move_end_melee},
+{"flyer_move_start_melee", &flyer_move_start_melee},
+{"flyer_move_attack2", &flyer_move_attack2},
+{"flyer_move_bankleft", &flyer_move_bankleft},
+{"flyer_move_bankright", &flyer_move_bankright},
+{"flyer_move_defense", &flyer_move_defense},
+{"flyer_move_pain1", &flyer_move_pain1},
+{"flyer_move_pain2", &flyer_move_pain2},
+{"flyer_move_pain3", &flyer_move_pain3},
+{"flyer_move_rollleft", &flyer_move_rollleft},
+{"flyer_move_rollright", &flyer_move_rollright},
+{"flyer_move_stop", &flyer_move_stop},
+{"flyer_move_start", &flyer_move_start},
+{"flyer_move_run", &flyer_move_run},
+{"flyer_move_walk", &flyer_move_walk},
+{"flyer_move_stand", &flyer_move_stand},
+{"floater_move_run", &floater_move_run},
+{"floater_move_walk", &floater_move_walk},
+{"floater_move_pain3", &floater_move_pain3},
+{"floater_move_pain2", &floater_move_pain2},
+{"floater_move_pain1", &floater_move_pain1},
+{"floater_move_death", &floater_move_death},
+{"floater_move_attack3", &floater_move_attack3},
+{"floater_move_attack2", &floater_move_attack2},
+{"floater_move_attack1", &floater_move_attack1},
+{"floater_move_activate", &floater_move_activate},
+{"floater_move_stand2", &floater_move_stand2},
+{"floater_move_stand1", &floater_move_stand1},
+{"flipper_move_death", &flipper_move_death},
+{"flipper_move_attack", &flipper_move_attack},
+{"flipper_move_pain1", &flipper_move_pain1},
+{"flipper_move_pain2", &flipper_move_pain2},
+{"flipper_move_start_run", &flipper_move_start_run},
+{"flipper_move_walk", &flipper_move_walk},
+{"flipper_move_run_start", &flipper_move_run_start},
+{"flipper_move_run_loop", &flipper_move_run_loop},
+{"flipper_move_stand", &flipper_move_stand},
+{"fixbot_move_weld_end", &fixbot_move_weld_end},
+{"fixbot_move_weld", &fixbot_move_weld},
+{"fixbot_move_weld_start", &fixbot_move_weld_start},
+{"fixbot_move_attack2", &fixbot_move_attack2},
+{"fixbot_move_laserattack", &fixbot_move_laserattack},
+{"fixbot_move_attack1", &fixbot_move_attack1},
+{"fixbot_move_start_attack", &fixbot_move_start_attack},
+{"fixbot_move_backward", &fixbot_move_backward},
+{"fixbot_move_death1", &fixbot_move_death1},
+{"fixbot_move_run", &fixbot_move_run},
+{"fixbot_move_walk", &fixbot_move_walk},
+{"fixbot_move_forward", &fixbot_move_forward},
+{"fixbot_move_land", &fixbot_move_land},
+{"fixbot_move_pain3", &fixbot_move_pain3},
+{"fixbot_move_painb", &fixbot_move_painb},
+{"fixbot_move_paina", &fixbot_move_paina},
+{"fixbot_move_takeoff", &fixbot_move_takeoff},
+{"fixbot_move_turn", &fixbot_move_turn},
+{"fixbot_move_roamgoal", &fixbot_move_roamgoal},
+{"fixbot_move_pickup", &fixbot_move_pickup},
+{"fixbot_move_stand2", &fixbot_move_stand2},
+{"fixbot_move_stand", &fixbot_move_stand},
+{"fixbot_move_landing", &fixbot_move_landing},
+{"chick_move_start_slash", &chick_move_start_slash},
+{"chick_move_end_slash", &chick_move_end_slash},
+{"chick_move_slash", &chick_move_slash},
+{"chick_move_end_attack1", &chick_move_end_attack1},
+{"chick_move_attack1", &chick_move_attack1},
+{"chick_move_start_attack1", &chick_move_start_attack1},
+{"chick_move_duck", &chick_move_duck},
+{"chick_move_death1", &chick_move_death1},
+{"chick_move_death2", &chick_move_death2},
+{"chick_move_pain3", &chick_move_pain3},
+{"chick_move_pain2", &chick_move_pain2},
+{"chick_move_pain1", &chick_move_pain1},
+{"chick_move_walk", &chick_move_walk},
+{"chick_move_run", &chick_move_run},
+{"chick_move_start_run", &chick_move_start_run},
+{"chick_move_stand", &chick_move_stand},
+{"chick_move_fidget", &chick_move_fidget},
+{"brain_move_run", &brain_move_run},
+{"brain_move_attack4", &brain_move_attack4},
+{"brain_move_attack3", &brain_move_attack3},
+{"brain_move_attack2", &brain_move_attack2},
+{"brain_move_attack1", &brain_move_attack1},
+{"brain_move_death1", &brain_move_death1},
+{"brain_move_death2", &brain_move_death2},
+{"brain_move_duck", &brain_move_duck},
+{"brain_move_pain1", &brain_move_pain1},
+{"brain_move_pain2", &brain_move_pain2},
+{"brain_move_pain3", &brain_move_pain3},
+{"brain_move_defense", &brain_move_defense},
+{"brain_move_walk1", &brain_move_walk1},
+{"brain_move_idle", &brain_move_idle},
+{"brain_move_stand", &brain_move_stand},
+{"boss5_move_end_attack1", &boss5_move_end_attack1},
+{"boss5_move_attack1", &boss5_move_attack1},
+{"boss5_move_attack2", &boss5_move_attack2},
+{"boss5_move_attack3", &boss5_move_attack3},
+{"boss5_move_attack4", &boss5_move_attack4},
+{"boss5_move_backward", &boss5_move_backward},
+{"boss5_move_death", &boss5_move_death},
+{"boss5_move_pain1", &boss5_move_pain1},
+{"boss5_move_pain2", &boss5_move_pain2},
+{"boss5_move_pain3", &boss5_move_pain3},
+{"boss5_move_turn_left", &boss5_move_turn_left},
+{"boss5_move_turn_right", &boss5_move_turn_right},
+{"boss5_move_forward", &boss5_move_forward},
+{"boss5_move_run", &boss5_move_run},
+{"boss5_move_stand", &boss5_move_stand},
+{"makron_move_attack5", &makron_move_attack5},
+{"makron_move_attack4", &makron_move_attack4},
+{"makron_move_attack3", &makron_move_attack3},
+{"makron_move_sight", &makron_move_sight},
+{"makron_move_death3", &makron_move_death3},
+{"makron_move_death2", &makron_move_death2},
+{"makron_move_pain4", &makron_move_pain4},
+{"makron_move_pain5", &makron_move_pain5},
+{"makron_move_pain6", &makron_move_pain6},
+{"makron_move_walk", &makron_move_walk},
+{"makron_move_run", &makron_move_run},
+{"makron_move_stand", &makron_move_stand},
+{"jorg_move_end_attack1", &jorg_move_end_attack1},
+{"jorg_move_attack1", &jorg_move_attack1},
+{"jorg_move_start_attack1", &jorg_move_start_attack1},
+{"jorg_move_attack2", &jorg_move_attack2},
+{"jorg_move_death", &jorg_move_death},
+{"jorg_move_pain1", &jorg_move_pain1},
+{"jorg_move_pain2", &jorg_move_pain2},
+{"jorg_move_pain3", &jorg_move_pain3},
+{"jorg_move_end_walk", &jorg_move_end_walk},
+{"jorg_move_walk", &jorg_move_walk},
+{"jorg_move_start_walk", &jorg_move_start_walk},
+{"jorg_move_run", &jorg_move_run},
+{"jorg_move_stand", &jorg_move_stand},
+{"boss2_move_death", &boss2_move_death},
+{"boss2_move_pain_light", &boss2_move_pain_light},
+{"boss2_move_pain_heavy", &boss2_move_pain_heavy},
+{"boss2_move_attack_rocket", &boss2_move_attack_rocket},
+{"boss2_move_attack_post_mg", &boss2_move_attack_post_mg},
+{"boss2_move_attack_mg", &boss2_move_attack_mg},
+{"boss2_move_attack_pre_mg", &boss2_move_attack_pre_mg},
+{"boss2_move_run", &boss2_move_run},
+{"boss2_move_walk", &boss2_move_walk},
+{"boss2_move_fidget", &boss2_move_fidget},
+{"boss2_move_stand", &boss2_move_stand},
+{"berserk_move_death2", &berserk_move_death2},
+{"berserk_move_death1", &berserk_move_death1},
+{"berserk_move_pain2", &berserk_move_pain2},
+{"berserk_move_pain1", &berserk_move_pain1},
+{"berserk_move_attack_strike", &berserk_move_attack_strike},
+{"berserk_move_attack_club", &berserk_move_attack_club},
+{"berserk_move_attack_spike", &berserk_move_attack_spike},
+{"berserk_move_run1", &berserk_move_run1},
+{"berserk_move_walk", &berserk_move_walk},
+{"berserk_move_stand_fidget", &berserk_move_stand_fidget},
+{"berserk_move_stand", &berserk_move_stand},
+{0, 0}
diff --git a/xatrix/src/savegame/tables/levelfields.h b/xatrix/src/savegame/tables/levelfields.h
new file mode 100644
index 0000000..fa547e2
--- /dev/null
+++ b/xatrix/src/savegame/tables/levelfields.h
@@ -0,0 +1,14 @@
+/*
+ * =======================================================================
+ *
+ * Fields inside a level to be saved.
+ *
+ * =======================================================================
+ */
+
+{"changemap", LLOFS(changemap), F_LSTRING},
+{"sight_client", LLOFS(sight_client), F_EDICT},
+{"sight_entity", LLOFS(sight_entity), F_EDICT},
+{"sound_entity", LLOFS(sound_entity), F_EDICT},
+{"sound2_entity", LLOFS(sound2_entity), F_EDICT},
+{NULL, 0, F_INT}
diff --git a/xatrix/src/shared/flash.c b/xatrix/src/shared/flash.c
new file mode 100644
index 0000000..943f591
--- /dev/null
+++ b/xatrix/src/shared/flash.c
@@ -0,0 +1,463 @@
+/*
+ * =======================================================================
+ *
+ * Muzzle flash posititions.
+ *
+ * =======================================================================
+ */
+
+#include "../header/shared.h"
+
+vec3_t monster_flash_offset[] = {
+ /* flash 0 is not used */
+ {0.0, 0.0, 0.0},
+
+ /* MZ2_TANK_BLASTER_1 1 */
+ {20.7, -18.5, 28.7},
+ /* MZ2_TANK_BLASTER_2 2 */
+ {16.6, -21.5, 30.1},
+ /* MZ2_TANK_BLASTER_3 3 */
+ {11.8, -23.9, 32.1},
+ /* MZ2_TANK_MACHINEGUN_1 4 */
+ {22.9, -0.7, 25.3},
+ /* MZ2_TANK_MACHINEGUN_2 5 */
+ {22.2, 6.2, 22.3},
+ /* MZ2_TANK_MACHINEGUN_3 6 */
+ {19.4, 13.1, 18.6},
+ /* MZ2_TANK_MACHINEGUN_4 7 */
+ {19.4, 18.8, 18.6},
+ /* MZ2_TANK_MACHINEGUN_5 8 */
+ {17.9, 25.0, 18.6},
+ /* MZ2_TANK_MACHINEGUN_6 9 */
+ {14.1, 30.5, 20.6},
+ /* MZ2_TANK_MACHINEGUN_7 10 */
+ {9.3, 35.3, 22.1},
+ /* MZ2_TANK_MACHINEGUN_8 11 */
+ {4.7, 38.4, 22.1},
+ /* MZ2_TANK_MACHINEGUN_9 12 */
+ {-1.1, 40.4, 24.1},
+ /* MZ2_TANK_MACHINEGUN_10 13 */
+ {-6.5, 41.2, 24.1},
+ /* MZ2_TANK_MACHINEGUN_11 14 */
+ {3.2, 40.1, 24.7},
+ /* MZ2_TANK_MACHINEGUN_12 15 */
+ {11.7, 36.7, 26.0},
+ /* MZ2_TANK_MACHINEGUN_13 16 */
+ {18.9, 31.3, 26.0},
+ /* MZ2_TANK_MACHINEGUN_14 17 */
+ {24.4, 24.4, 26.4},
+ /* MZ2_TANK_MACHINEGUN_15 18 */
+ {27.1, 17.1, 27.2},
+ /* MZ2_TANK_MACHINEGUN_16 19 */
+ {28.5, 9.1, 28.0},
+ /* MZ2_TANK_MACHINEGUN_17 20 */
+ {27.1, 2.2, 28.0},
+ /* MZ2_TANK_MACHINEGUN_18 21 */
+ {24.9, -2.8, 28.0},
+ /* MZ2_TANK_MACHINEGUN_19 22 */
+ {21.6, -7.0, 26.4},
+ /* MZ2_TANK_ROCKET_1 23 */
+ {6.2, 29.1, 49.1},
+ /* MZ2_TANK_ROCKET_2 24 */
+ {6.9, 23.8, 49.1},
+ /* MZ2_TANK_ROCKET_3 25 */
+ {8.3, 17.8, 49.5},
+
+ /* MZ2_INFANTRY_MACHINEGUN_1 26 */
+ {26.6, 7.1, 13.1},
+ /* MZ2_INFANTRY_MACHINEGUN_2 27 */
+ {18.2, 7.5, 15.4},
+ /* MZ2_INFANTRY_MACHINEGUN_3 28 */
+ {17.2, 10.3, 17.9},
+ /* MZ2_INFANTRY_MACHINEGUN_4 29 */
+ {17.0, 12.8, 20.1},
+ /* MZ2_INFANTRY_MACHINEGUN_5 30 */
+ {15.1, 14.1, 21.8},
+ /* MZ2_INFANTRY_MACHINEGUN_6 31 */
+ {11.8, 17.2, 23.1},
+ /* MZ2_INFANTRY_MACHINEGUN_7 32 */
+ {11.4, 20.2, 21.0},
+ /* MZ2_INFANTRY_MACHINEGUN_8 33 */
+ {9.0, 23.0, 18.9},
+ /* MZ2_INFANTRY_MACHINEGUN_9 34 */
+ {13.9, 18.6, 17.7},
+ /* MZ2_INFANTRY_MACHINEGUN_10 35 */
+ {15.4, 15.6, 15.8},
+ /* MZ2_INFANTRY_MACHINEGUN_11 36 */
+ {10.2, 15.2, 25.1},
+ /* MZ2_INFANTRY_MACHINEGUN_12 37 */
+ {-1.9, 15.1, 28.2},
+ /* MZ2_INFANTRY_MACHINEGUN_13 38 */
+ {-12.4, 13.0, 20.2},
+
+ /* MZ2_SOLDIER_BLASTER_1 39 */
+ {10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_2 40 */
+ {21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_1 41 */
+ {10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_2 42 */
+ {21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_1 43 */
+ {10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_2 44 */
+ {21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2},
+
+ /* MZ2_GUNNER_MACHINEGUN_1 45 */
+ {30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_2 46 */
+ {29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_3 47 */
+ {28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_4 48 */
+ {28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_5 49 */
+ {26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_6 50 */
+ {26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_7 51 */
+ {26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15},
+ /* MZ2_GUNNER_MACHINEGUN_8 52 */
+ {29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15},
+ /* MZ2_GUNNER_GRENADE_1 53 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+ /* MZ2_GUNNER_GRENADE_2 54 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+ /* MZ2_GUNNER_GRENADE_3 55 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+ /* MZ2_GUNNER_GRENADE_4 56 */
+ {4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15},
+
+ /* MZ2_CHICK_ROCKET_1 57 */
+ {24.8, -9.0, 39.0},
+
+ /* MZ2_FLYER_BLASTER_1 58 */
+ {12.1, 13.4, -14.5},
+ /* MZ2_FLYER_BLASTER_2 59 */
+ {12.1, -7.4, -14.5},
+
+ /* MZ2_MEDIC_BLASTER_1 60 */
+ {12.1, 5.4, 16.5},
+
+ /* MZ2_GLADIATOR_RAILGUN_1 61 */
+ {30.0, 18.0, 28.0},
+
+ /* MZ2_HOVER_BLASTER_1 62 */
+ {32.5, -0.8, 10.0},
+
+ /* MZ2_ACTOR_MACHINEGUN_1 63 */
+ {18.4, 7.4, 9.6},
+
+ /* MZ2_SUPERTANK_MACHINEGUN_1 64 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_2 65 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_3 66 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_4 67 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_5 68 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_MACHINEGUN_6 69 */
+ {30.0, 30.0, 88.5},
+ /* MZ2_SUPERTANK_ROCKET_1 70 */
+ {16.0, -22.5, 91.2},
+ /* MZ2_SUPERTANK_ROCKET_2 71 */
+ {16.0, -33.4, 86.7},
+ /* MZ2_SUPERTANK_ROCKET_3 72 */
+ {16.0, -42.8, 83.3},
+
+ /* MZ2_BOSS2_MACHINEGUN_L1 73 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L2 74 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L3 75 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L4 76 */
+ {32, -40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_L5 77 */
+ {32, -40, 70},
+
+ /* MZ2_BOSS2_ROCKET_1 78 */
+ {22.0, 16.0, 10.0},
+ /* MZ2_BOSS2_ROCKET_2 79 */
+ {22.0, 8.0, 10.0},
+ /* MZ2_BOSS2_ROCKET_3 80 */
+ {22.0, -8.0, 10.0},
+ /* MZ2_BOSS2_ROCKET_4 81 */
+ {22.0, -16.0, 10.0},
+
+ /* MZ2_FLOAT_BLASTER_1 82 */
+ {32.5, -0.8, 10},
+
+ /* MZ2_SOLDIER_BLASTER_3 83 */
+ {20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_3 84 */
+ {20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_3 85 */
+ {20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_4 86 */
+ {7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_4 87 */
+ {7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_4 88 */
+ {7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_5 89 */
+ {30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_5 90 */
+ {30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_5 91 */
+ {30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_6 92 */
+ {27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_6 93 */
+ {27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_6 94 */
+ {27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_7 95 */
+ {28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_7 96 */
+ {28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_7 97 */
+ {28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2},
+ /* MZ2_SOLDIER_BLASTER_8 98 */
+ {31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2},
+ /* MZ2_SOLDIER_SHOTGUN_8 99 */
+ {34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2},
+ /* MZ2_SOLDIER_MACHINEGUN_8 100 */
+ {34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2},
+
+ /* MZ2_MAKRON_BFG 101 */
+ {17, -19.5, 62.9},
+ /* MZ2_MAKRON_BLASTER_1 102 */
+ {-3.6, -24.1, 59.5},
+ /* MZ2_MAKRON_BLASTER_2 103 */
+ {-1.6, -19.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_3 104 */
+ {-0.1, -14.4, 59.5},
+ /* MZ2_MAKRON_BLASTER_4 105 */
+ {2.0, -7.6, 59.5},
+ /* MZ2_MAKRON_BLASTER_5 106 */
+ {3.4, 1.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_6 107 */
+ {3.7, 11.1, 59.5},
+ /* MZ2_MAKRON_BLASTER_7 108 */
+ {-0.3, 22.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_8 109 */
+ {-6, 33, 59.5},
+ /* MZ2_MAKRON_BLASTER_9 110 */
+ {-9.3, 36.4, 59.5},
+ /* MZ2_MAKRON_BLASTER_10 111 */
+ {-7, 35, 59.5},
+ /* MZ2_MAKRON_BLASTER_11 112 */
+ {-2.1, 29, 59.5},
+ /* MZ2_MAKRON_BLASTER_12 113 */
+ {3.9, 17.3, 59.5},
+ /* MZ2_MAKRON_BLASTER_13 114 */
+ {6.1, 5.8, 59.5},
+ /* MZ2_MAKRON_BLASTER_14 115 */
+ {5.9, -4.4, 59.5},
+ /* MZ2_MAKRON_BLASTER_15 116 */
+ {4.2, -14.1, 59.5},
+ /* MZ2_MAKRON_BLASTER_16 117 */
+ {2.4, -18.8, 59.5},
+ /* MZ2_MAKRON_BLASTER_17 118 */
+ {-1.8, -25.5, 59.5},
+ /* MZ2_MAKRON_RAILGUN_1 119 */
+ {-17.3, 7.8, 72.4},
+
+ /* MZ2_JORG_MACHINEGUN_L1 120 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L2 121 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L3 122 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L4 123 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L5 124 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_L6 125 */
+ {78.5, -47.1, 96},
+ /* MZ2_JORG_MACHINEGUN_R1 126 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R2 127 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R3 128 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R4 129 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R5 130 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_MACHINEGUN_R6 131 */
+ {78.5, 46.7, 96},
+ /* MZ2_JORG_BFG_1 132 */
+ {6.3, -9, 111.2},
+
+ /* MZ2_BOSS2_MACHINEGUN_R1 73 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R2 74 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R3 75 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R4 76 */
+ {32, 40, 70},
+ /* MZ2_BOSS2_MACHINEGUN_R5 77 */
+ {32, 40, 70},
+
+
+ /* MZ2_CARRIER_MACHINEGUN_L1 */
+ {56, -32, 32},
+ /* MZ2_CARRIER_MACHINEGUN_R1 */
+ {56, 32, 32},
+ /* MZ2_CARRIER_GRENADE */
+ {42, 24, 50},
+ /* MZ2_TURRET_MACHINEGUN 141 */
+ {16, 0, 0},
+ /* MZ2_TURRET_ROCKET 142 */
+ {16, 0, 0},
+ /* MZ2_TURRET_BLASTER 143 */
+ {16, 0, 0},
+ /* MZ2_STALKER_BLASTER 144 */
+ {24, 0, 6},
+ /* MZ2_DAEDALUS_BLASTER 145 */
+ {32.5, -0.8, 10.0},
+ /* MZ2_MEDIC_BLASTER_2 146 */
+ {12.1, 5.4, 16.5},
+ /* MZ2_CARRIER_RAILGUN 147 */
+ {32, 0, 6},
+ /* MZ2_WIDOW_DISRUPTOR 148 */
+ {57.72, 14.50, 88.81},
+ /* MZ2_WIDOW_BLASTER 149 */
+ {56, 32, 32},
+ /* MZ2_WIDOW_RAIL 150 */
+ {62, -20, 84},
+ /* MZ2_WIDOW_PLASMABEAM 151 */
+ {32, 0, 6},
+ /* MZ2_CARRIER_MACHINEGUN_L2 152 */
+ {61, -32, 12},
+ /* MZ2_CARRIER_MACHINEGUN_R2 153 */
+ {61, 32, 12},
+ /* MZ2_WIDOW_RAIL_LEFT 154 */
+ {17, -62, 91},
+ /* MZ2_WIDOW_RAIL_RIGHT 155 */
+ {68, 12, 86},
+ /* MZ2_WIDOW_BLASTER_SWEEP1 156 */
+ {47.5, 56, 89},
+ /* MZ2_WIDOW_BLASTER_SWEEP2 157 */
+ {54, 52, 91},
+ /* MZ2_WIDOW_BLASTER_SWEEP3 158 */
+ {58, 40, 91},
+ /* MZ2_WIDOW_BLASTER_SWEEP4 159 */
+ {68, 30, 88},
+ /* MZ2_WIDOW_BLASTER_SWEEP5 160 */
+ {74, 20, 88},
+ /* MZ2_WIDOW_BLASTER_SWEEP6 161 */
+ {73, 11, 87},
+ /* MZ2_WIDOW_BLASTER_SWEEP7 162 */
+ {73, 3, 87},
+ /* MZ2_WIDOW_BLASTER_SWEEP8 163 */
+ {70, -12, 87},
+ /* MZ2_WIDOW_BLASTER_SWEEP9 164 */
+ {67, -20, 90},
+ /* MZ2_WIDOW_BLASTER_100 165 */
+ {-20, 76, 90},
+ /* MZ2_WIDOW_BLASTER_90 166 */
+ {-8, 74, 90},
+ /* MZ2_WIDOW_BLASTER_80 167 */
+ {0, 72, 90},
+ /* MZ2_WIDOW_BLASTER_70 168 d06 */
+ {10, 71, 89},
+ /* MZ2_WIDOW_BLASTER_60 169 d07 */
+ {23, 70, 87},
+ /* MZ2_WIDOW_BLASTER_50 170 d08 */
+ {32, 64, 85},
+ /* MZ2_WIDOW_BLASTER_40 171 */
+ {40, 58, 84},
+ /* MZ2_WIDOW_BLASTER_30 172 d10 */
+ {48, 50, 83},
+ /* MZ2_WIDOW_BLASTER_20 173 */
+ {54, 42, 82},
+ /* MZ2_WIDOW_BLASTER_10 174 d12 */
+ {56, 34, 82},
+ /* MZ2_WIDOW_BLASTER_0 175 */
+ {58, 26, 82},
+ /* MZ2_WIDOW_BLASTER_10L 176 d14 */
+ {60, 16, 82},
+ /* MZ2_WIDOW_BLASTER_20L 177 */
+ {59, 6, 81},
+ /* MZ2_WIDOW_BLASTER_30L 178 d16 */
+ {58, -2, 80},
+ /* MZ2_WIDOW_BLASTER_40L 179 */
+ {57, -10, 79},
+ /* MZ2_WIDOW_BLASTER_50L 180 d18 */
+ {54, -18, 78},
+ /* MZ2_WIDOW_BLASTER_60L 181 */
+ {42, -32, 80},
+ /* MZ2_WIDOW_BLASTER_70L 182 d20 */
+ {36, -40, 78},
+ /* MZ2_WIDOW_RUN_1 183 */
+ {68.4, 10.88, 82.08},
+ /* MZ2_WIDOW_RUN_2 184 */
+ {68.51, 8.64, 85.14},
+ /* MZ2_WIDOW_RUN_3 185 */
+ {68.66, 6.38, 88.78},
+ /* MZ2_WIDOW_RUN_4 186 */
+ {68.73, 5.1, 84.47},
+ /* MZ2_WIDOW_RUN_5 187 */
+ {68.82, 4.79, 80.52},
+ /* MZ2_WIDOW_RUN_6 188 */
+ {68.77, 6.11, 85.37},
+ /* MZ2_WIDOW_RUN_7 189 */
+ {68.67, 7.99, 90.24},
+ /* MZ2_WIDOW_RUN_8 190 */
+ {68.55, 9.54, 87.36},
+ /* MZ2_CARRIER_ROCKET_1 191 */
+ {0, 0, -5},
+ /* MZ2_CARRIER_ROCKET_2 192 */
+ {0, 0, -5},
+ /* MZ2_CARRIER_ROCKET_3 193 */
+ {0, 0, -5},
+ /* MZ2_CARRIER_ROCKET_4 194 */
+ {0, 0, -5},
+ /* MZ2_WIDOW2_BEAMER_1 195 */
+ /* { 72.13, -17.63, 93.77 }, */
+ {69.00, -17.63, 93.77},
+ /* MZ2_WIDOW2_BEAMER_2 196 */
+ /* { 71.46, -17.08, 89.82 }, */
+ {69.00, -17.08, 89.82},
+ /* MZ2_WIDOW2_BEAMER_3 197 */
+ /* { 71.47, -18.40, 90.70 }, */
+ {69.00, -18.40, 90.70},
+ /* MZ2_WIDOW2_BEAMER_4 198 */
+ /* { 71.96, -18.34, 94.32 }, */
+ {69.00, -18.34, 94.32},
+ /* MZ2_WIDOW2_BEAMER_5 199 */
+ /* { 72.25, -18.30, 97.98 }, */
+ {69.00, -18.30, 97.98},
+ /* MZ2_WIDOW2_BEAM_SWEEP_1 200 */
+ {45.04, -59.02, 92.24},
+ /* MZ2_WIDOW2_BEAM_SWEEP_2 201 */
+ {50.68, -54.70, 91.96},
+ /* MZ2_WIDOW2_BEAM_SWEEP_3 202 */
+ {56.57, -47.72, 91.65},
+ /* MZ2_WIDOW2_BEAM_SWEEP_4 203 */
+ {61.75, -38.75, 91.38},
+ /* MZ2_WIDOW2_BEAM_SWEEP_5 204 */
+ {65.55, -28.76, 91.24},
+ /* MZ2_WIDOW2_BEAM_SWEEP_6 205 */
+ {67.79, -18.90, 91.22},
+ /* MZ2_WIDOW2_BEAM_SWEEP_7 206 */
+ {68.60, -9.52, 91.23},
+ /* MZ2_WIDOW2_BEAM_SWEEP_8 207 */
+ {68.08, 0.18, 91.32},
+ /* MZ2_WIDOW2_BEAM_SWEEP_9 208 */
+ {66.14, 9.79, 91.44},
+ /* MZ2_WIDOW2_BEAM_SWEEP_10 209 */
+ {62.77, 18.91, 91.65},
+ /* MZ2_WIDOW2_BEAM_SWEEP_11 210 */
+ {58.29, 27.11, 92.00},
+
+ /* end of table */
+ {0.0, 0.0, 0.0}
+};
+
diff --git a/xatrix/src/shared/rand.c b/xatrix/src/shared/rand.c
new file mode 100644
index 0000000..22b4a71
--- /dev/null
+++ b/xatrix/src/shared/rand.c
@@ -0,0 +1,97 @@
+/*
+ * KISS PRNG (c) 2011 Shinobu
+ *
+ * This file was optained from zuttobenkyou.wordpress.com
+ * and modified by the Yamagi Quake II developers.
+ *
+ * LICENSE: Public domain
+ *
+ * =======================================================================
+ *
+ * KISS PRNG, as devised by Dr. George Marsaglia
+ *
+ * =======================================================================
+ */
+
+#include <stdint.h>
+
+#define QSIZE 0x200000
+#define CNG (cng = 6906969069ULL * cng + 13579)
+#define XS (xs ^= (xs << 13), xs ^= (xs >> 17), xs ^= (xs << 43))
+#define KISS (B64MWC() + CNG + XS)
+
+static uint64_t QARY[QSIZE];
+static int j;
+static uint64_t carry;
+static uint64_t xs;
+static uint64_t cng;
+
+uint64_t
+B64MWC(void)
+{
+ uint64_t t, x;
+
+ j = (j + 1) & (QSIZE - 1);
+ x = QARY[j];
+ t = (x << 28) + carry;
+ carry = (x >> 36) - (t < x);
+ return QARY[j] = t - x;
+}
+
+/*
+ * Generate a pseudorandom
+ * integer >0.
+ */
+int
+randk(void)
+{
+ int r;
+
+ r = (int)KISS;
+ r = (r < 0) ? (r * -1) : r;
+
+ return r;
+}
+
+/*
+ * Generate a pseudorandom
+ * signed float between
+ * 0 and 1.
+ */
+float
+frandk(void)
+{
+ return (randk()&32767)* (1.0/32767);
+}
+
+/* Generate a pseudorandom
+ * float between -1 and 1.
+ */
+float
+crandk(void)
+{
+ return (randk()&32767)* (2.0/32767) - 1;
+}
+
+/*
+ * Seeds the PRNG
+ */
+void
+randk_seed(void)
+{
+ uint64_t i;
+
+ /* Seed QARY[] with CNG+XS: */
+ for (i = 0; i < QSIZE; i++)
+ {
+ QARY[i] = CNG + XS;
+ }
+
+ /* Run through several rounds
+ to warm up the state */
+ for (i = 0; i < 256; i++)
+ {
+ randk();
+ }
+}
+
diff --git a/xatrix/src/shared/shared.c b/xatrix/src/shared/shared.c
new file mode 100644
index 0000000..1419ff8
--- /dev/null
+++ b/xatrix/src/shared/shared.c
@@ -0,0 +1,1347 @@
+/*
+ * =======================================================================
+ *
+ * Support functions, linked into client, server, renderer and game.
+ *
+ * =======================================================================
+ */
+
+#include <ctype.h>
+
+#include "../header/shared.h"
+
+#define DEG2RAD(a) (a * M_PI) / 180.0F
+
+vec3_t vec3_origin = {0, 0, 0};
+
+/* ============================================================================ */
+
+void
+RotatePointAroundVector(vec3_t dst, const vec3_t dir,
+ const vec3_t point, float degrees)
+{
+ float m[3][3];
+ float im[3][3];
+ float zrot[3][3];
+ float tmpmat[3][3];
+ float rot[3][3];
+ int i;
+ vec3_t vr, vup, vf;
+
+ vf[0] = dir[0];
+ vf[1] = dir[1];
+ vf[2] = dir[2];
+
+ PerpendicularVector(vr, dir);
+ CrossProduct(vr, vf, vup);
+
+ m[0][0] = vr[0];
+ m[1][0] = vr[1];
+ m[2][0] = vr[2];
+
+ m[0][1] = vup[0];
+ m[1][1] = vup[1];
+ m[2][1] = vup[2];
+
+ m[0][2] = vf[0];
+ m[1][2] = vf[1];
+ m[2][2] = vf[2];
+
+ memcpy(im, m, sizeof(im));
+
+ im[0][1] = m[1][0];
+ im[0][2] = m[2][0];
+ im[1][0] = m[0][1];
+ im[1][2] = m[2][1];
+ im[2][0] = m[0][2];
+ im[2][1] = m[1][2];
+
+ memset(zrot, 0, sizeof(zrot));
+ zrot[1][1] = zrot[2][2] = 1.0F;
+
+ zrot[0][0] = (float)cos(DEG2RAD(degrees));
+ zrot[0][1] = (float)sin(DEG2RAD(degrees));
+ zrot[1][0] = (float)-sin(DEG2RAD(degrees));
+ zrot[1][1] = (float)cos(DEG2RAD(degrees));
+
+ R_ConcatRotations(m, zrot, tmpmat);
+ R_ConcatRotations(tmpmat, im, rot);
+
+ for (i = 0; i < 3; i++)
+ {
+ dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] *
+ point[2];
+ }
+}
+
+void
+AngleVectors(vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
+{
+ float angle;
+ static float sr, sp, sy, cr, cp, cy;
+
+ angle = angles[YAW] * (M_PI * 2 / 360);
+ sy = (float)sin(angle);
+ cy = (float)cos(angle);
+ angle = angles[PITCH] * (M_PI * 2 / 360);
+ sp = (float)sin(angle);
+ cp = (float)cos(angle);
+ angle = angles[ROLL] * (M_PI * 2 / 360);
+ sr = (float)sin(angle);
+ cr = (float)cos(angle);
+
+ if (forward)
+ {
+ forward[0] = cp * cy;
+ forward[1] = cp * sy;
+ forward[2] = -sp;
+ }
+
+ if (right)
+ {
+ right[0] = (-1 * sr * sp * cy + - 1 * cr * -sy);
+ right[1] = (-1 * sr * sp * sy + - 1 * cr * cy);
+ right[2] = -1 * sr * cp;
+ }
+
+ if (up)
+ {
+ up[0] = (cr * sp * cy + - sr * -sy);
+ up[1] = (cr * sp * sy + - sr * cy);
+ up[2] = cr * cp;
+ }
+}
+
+void
+AngleVectors2(vec3_t value1, vec3_t angles)
+{
+ float forward;
+ float yaw, pitch;
+
+ if ((value1[1] == 0) && (value1[0] == 0))
+ {
+ yaw = 0;
+
+ if (value1[2] > 0)
+ {
+ pitch = 90;
+ }
+
+ else
+ {
+ pitch = 270;
+ }
+ }
+ else
+ {
+ if (value1[0])
+ {
+ yaw = ((float)atan2(value1[1], value1[0]) * 180 / M_PI);
+ }
+
+ else if (value1[1] > 0)
+ {
+ yaw = 90;
+ }
+
+ else
+ {
+ yaw = 270;
+ }
+
+ if (yaw < 0)
+ {
+ yaw += 360;
+ }
+
+ forward = (float)sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
+ pitch = ((float)atan2(value1[2], forward) * 180 / M_PI);
+
+ if (pitch < 0)
+ {
+ pitch += 360;
+ }
+ }
+
+ angles[PITCH] = -pitch;
+ angles[YAW] = yaw;
+ angles[ROLL] = 0;
+}
+
+void
+ProjectPointOnPlane(vec3_t dst, const vec3_t p, const vec3_t normal)
+{
+ float d;
+ vec3_t n;
+ float inv_denom;
+
+ inv_denom = 1.0F / DotProduct(normal, normal);
+
+ d = DotProduct(normal, p) * inv_denom;
+
+ n[0] = normal[0] * inv_denom;
+ n[1] = normal[1] * inv_denom;
+ n[2] = normal[2] * inv_denom;
+
+ dst[0] = p[0] - d * n[0];
+ dst[1] = p[1] - d * n[1];
+ dst[2] = p[2] - d * n[2];
+}
+
+/* assumes "src" is normalized */
+void
+PerpendicularVector(vec3_t dst, const vec3_t src)
+{
+ int pos;
+ int i;
+ float minelem = 1.0F;
+ vec3_t tempvec;
+
+ /* find the smallest magnitude axially aligned vector */
+ for (pos = 0, i = 0; i < 3; i++)
+ {
+ if (fabs(src[i]) < minelem)
+ {
+ pos = i;
+ minelem = (float)fabs(src[i]);
+ }
+ }
+
+ tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
+ tempvec[pos] = 1.0F;
+
+ /* project the point onto the plane defined by src */
+ ProjectPointOnPlane(dst, tempvec, src);
+
+ /* normalize the result */
+ VectorNormalize(dst);
+}
+
+void
+R_ConcatRotations(float in1[3][3], float in2[3][3], float out[3][3])
+{
+ out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+ in1[0][2] * in2[2][0];
+ out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+ in1[0][2] * in2[2][1];
+ out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+ in1[0][2] * in2[2][2];
+ out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+ in1[1][2] * in2[2][0];
+ out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+ in1[1][2] * in2[2][1];
+ out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+ in1[1][2] * in2[2][2];
+ out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+ in1[2][2] * in2[2][0];
+ out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+ in1[2][2] * in2[2][1];
+ out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+ in1[2][2] * in2[2][2];
+}
+
+void
+R_ConcatTransforms(float in1[3][4], float in2[3][4], float out[3][4])
+{
+ out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+ in1[0][2] * in2[2][0];
+ out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+ in1[0][2] * in2[2][1];
+ out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+ in1[0][2] * in2[2][2];
+ out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
+ in1[0][2] * in2[2][3] + in1[0][3];
+ out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+ in1[1][2] * in2[2][0];
+ out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+ in1[1][2] * in2[2][1];
+ out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+ in1[1][2] * in2[2][2];
+ out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
+ in1[1][2] * in2[2][3] + in1[1][3];
+ out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+ in1[2][2] * in2[2][0];
+ out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+ in1[2][2] * in2[2][1];
+ out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+ in1[2][2] * in2[2][2];
+ out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
+ in1[2][2] * in2[2][3] + in1[2][3];
+}
+
+/* ============================================================================ */
+
+float
+Q_fabs(float f)
+{
+ int tmp = *(int *)&f;
+
+ tmp &= 0x7FFFFFFF;
+ return *(float *)&tmp;
+}
+
+float
+LerpAngle(float a2, float a1, float frac)
+{
+ if (a1 - a2 > 180)
+ {
+ a1 -= 360;
+ }
+
+ if (a1 - a2 < -180)
+ {
+ a1 += 360;
+ }
+
+ return a2 + frac * (a1 - a2);
+}
+
+float
+anglemod(float a)
+{
+ a = (360.0 / 65536) * ((int)(a * (65536 / 360.0)) & 65535);
+ return a;
+}
+
+/*
+ * This is the slow, general version
+ */
+int
+BoxOnPlaneSide2(vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ int i;
+ float dist1, dist2;
+ int sides;
+ vec3_t corners[2];
+
+ for (i = 0; i < 3; i++)
+ {
+ if (p->normal[i] < 0)
+ {
+ corners[0][i] = emins[i];
+ corners[1][i] = emaxs[i];
+ }
+ else
+ {
+ corners[1][i] = emins[i];
+ corners[0][i] = emaxs[i];
+ }
+ }
+
+ dist1 = DotProduct(p->normal, corners[0]) - p->dist;
+ dist2 = DotProduct(p->normal, corners[1]) - p->dist;
+ sides = 0;
+
+ if (dist1 >= 0)
+ {
+ sides = 1;
+ }
+
+ if (dist2 < 0)
+ {
+ sides |= 2;
+ }
+
+ return sides;
+}
+
+/*
+ * Returns 1, 2, or 1 + 2
+ */
+int
+BoxOnPlaneSide(vec3_t emins, vec3_t emaxs, struct cplane_s *p)
+{
+ float dist1, dist2;
+ int sides;
+
+ /* fast axial cases */
+ if (p->type < 3)
+ {
+ if (p->dist <= emins[p->type])
+ {
+ return 1;
+ }
+
+ if (p->dist >= emaxs[p->type])
+ {
+ return 2;
+ }
+
+ return 3;
+ }
+
+ /* general case */
+ switch (p->signbits)
+ {
+ case 0:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 1:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 2:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 3:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ break;
+ case 4:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ case 5:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ case 6:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ case 7:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
+ p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
+ p->normal[2] * emaxs[2];
+ break;
+ default:
+ dist1 = dist2 = 0;
+ break;
+ }
+
+ sides = 0;
+
+ if (dist1 >= p->dist)
+ {
+ sides = 1;
+ }
+
+ if (dist2 < p->dist)
+ {
+ sides |= 2;
+ }
+
+ return sides;
+}
+
+void
+ClearBounds(vec3_t mins, vec3_t maxs)
+{
+ mins[0] = mins[1] = mins[2] = 99999;
+ maxs[0] = maxs[1] = maxs[2] = -99999;
+}
+
+void
+AddPointToBounds(vec3_t v, vec3_t mins, vec3_t maxs)
+{
+ int i;
+ vec_t val;
+
+ for (i = 0; i < 3; i++)
+ {
+ val = v[i];
+
+ if (val < mins[i])
+ {
+ mins[i] = val;
+ }
+
+ if (val > maxs[i])
+ {
+ maxs[i] = val;
+ }
+ }
+}
+
+int
+VectorCompare(vec3_t v1, vec3_t v2)
+{
+ if ((v1[0] != v2[0]) || (v1[1] != v2[1]) || (v1[2] != v2[2]))
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+vec_t
+VectorNormalize(vec3_t v)
+{
+ float length, ilength;
+
+ length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
+ length = (float)sqrt(length);
+
+ if (length)
+ {
+ ilength = 1 / length;
+ v[0] *= ilength;
+ v[1] *= ilength;
+ v[2] *= ilength;
+ }
+
+ return length;
+}
+
+vec_t
+VectorNormalize2(vec3_t v, vec3_t out)
+{
+ VectorCopy(v, out);
+
+ return VectorNormalize(out);
+}
+
+void
+VectorMA(vec3_t veca, float scale, vec3_t vecb, vec3_t vecc)
+{
+ vecc[0] = veca[0] + scale * vecb[0];
+ vecc[1] = veca[1] + scale * vecb[1];
+ vecc[2] = veca[2] + scale * vecb[2];
+}
+
+vec_t
+_DotProduct(vec3_t v1, vec3_t v2)
+{
+ return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+}
+
+void
+_VectorSubtract(vec3_t veca, vec3_t vecb, vec3_t out)
+{
+ out[0] = veca[0] - vecb[0];
+ out[1] = veca[1] - vecb[1];
+ out[2] = veca[2] - vecb[2];
+}
+
+void
+_VectorAdd(vec3_t veca, vec3_t vecb, vec3_t out)
+{
+ out[0] = veca[0] + vecb[0];
+ out[1] = veca[1] + vecb[1];
+ out[2] = veca[2] + vecb[2];
+}
+
+void
+_VectorCopy(vec3_t in, vec3_t out)
+{
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+}
+
+void
+CrossProduct(vec3_t v1, vec3_t v2, vec3_t cross)
+{
+ cross[0] = v1[1] * v2[2] - v1[2] * v2[1];
+ cross[1] = v1[2] * v2[0] - v1[0] * v2[2];
+ cross[2] = v1[0] * v2[1] - v1[1] * v2[0];
+}
+
+double sqrt(double x);
+
+vec_t
+VectorLength(vec3_t v)
+{
+ int i;
+ float length;
+
+ length = 0;
+
+ for (i = 0; i < 3; i++)
+ {
+ length += v[i] * v[i];
+ }
+
+ length = (float)sqrt(length);
+
+ return length;
+}
+
+void
+VectorInverse(vec3_t v)
+{
+ v[0] = -v[0];
+ v[1] = -v[1];
+ v[2] = -v[2];
+}
+
+void
+VectorScale(vec3_t in, vec_t scale, vec3_t out)
+{
+ out[0] = in[0] * scale;
+ out[1] = in[1] * scale;
+ out[2] = in[2] * scale;
+}
+
+int
+Q_log2(int val)
+{
+ int answer = 0;
+
+ while (val >>= 1)
+ {
+ answer++;
+ }
+
+ return answer;
+}
+
+/* ==================================================================================== */
+
+char *
+COM_SkipPath(char *pathname)
+{
+ char *last;
+
+ last = pathname;
+
+ while (*pathname)
+ {
+ if (*pathname == '/')
+ {
+ last = pathname + 1;
+ }
+
+ pathname++;
+ }
+
+ return last;
+}
+
+void
+COM_StripExtension(char *in, char *out)
+{
+ while (*in && *in != '.')
+ {
+ *out++ = *in++;
+ }
+
+ *out = 0;
+}
+
+const char *
+COM_FileExtension(const char *in)
+{
+ const char *ext = strrchr(in, '.');
+
+ if (!ext || ext == in)
+ {
+ return "";
+ }
+
+ return ext + 1;
+}
+
+void
+COM_FileBase(char *in, char *out)
+{
+ char *s, *s2;
+
+ s = in + strlen(in) - 1;
+
+ while (s != in && *s != '.')
+ {
+ s--;
+ }
+
+ for (s2 = s; s2 != in && *s2 != '/'; s2--)
+ {
+ }
+
+ if (s - s2 < 2)
+ {
+ out[0] = 0;
+ }
+ else
+ {
+ s--;
+ strncpy(out, s2 + 1, s - s2);
+ out[s - s2] = 0;
+ }
+}
+
+/*
+ * Returns the path up to, but not including the last /
+ */
+void
+COM_FilePath(const char *in, char *out)
+{
+ const char *s;
+
+ s = in + strlen(in) - 1;
+
+ while (s != in && *s != '/')
+ {
+ s--;
+ }
+
+ strncpy(out, in, s - in);
+ out[s - in] = 0;
+}
+
+void
+COM_DefaultExtension(char *path, const char *extension)
+{
+ char *src;
+
+ /* */
+ /* if path doesn't have a .EXT, append extension */
+ /* (extension should include the .) */
+ /* */
+ src = path + strlen(path) - 1;
+
+ while (*src != '/' && src != path)
+ {
+ if (*src == '.')
+ {
+ return; /* it has an extension */
+ }
+
+ src--;
+ }
+
+ strcat(path, extension);
+}
+
+/*
+ * ============================================================================
+ *
+ * BYTE ORDER FUNCTIONS
+ *
+ * ============================================================================
+ */
+
+qboolean bigendien;
+
+/* can't just use function pointers, or dll linkage can
+ mess up when qcommon is included in multiple places */
+short (*_BigShort)(short l);
+short (*_LittleShort)(short l);
+int (*_BigLong)(int l);
+int (*_LittleLong)(int l);
+float (*_BigFloat)(float l);
+float (*_LittleFloat)(float l);
+
+short
+BigShort(short l)
+{
+ return _BigShort(l);
+}
+
+short
+LittleShort(short l)
+{
+ return _LittleShort(l);
+}
+
+int
+BigLong(int l)
+{
+ return _BigLong(l);
+}
+
+int
+LittleLong(int l)
+{
+ return _LittleLong(l);
+}
+
+float
+BigFloat(float l)
+{
+ return _BigFloat(l);
+}
+
+float
+LittleFloat(float l)
+{
+ return _LittleFloat(l);
+}
+
+short
+ShortSwap(short l)
+{
+ byte b1, b2;
+
+ b1 = l & 255;
+ b2 = (l >> 8) & 255;
+
+ return (b1 << 8) + b2;
+}
+
+short
+ShortNoSwap(short l)
+{
+ return l;
+}
+
+int
+LongSwap(int l)
+{
+ byte b1, b2, b3, b4;
+
+ b1 = l & 255;
+ b2 = (l >> 8) & 255;
+ b3 = (l >> 16) & 255;
+ b4 = (l >> 24) & 255;
+
+ return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4;
+}
+
+int
+LongNoSwap(int l)
+{
+ return l;
+}
+
+float
+FloatSwap(float f)
+{
+ union
+ {
+ float f;
+ byte b[4];
+ } dat1, dat2;
+
+ dat1.f = f;
+ dat2.b[0] = dat1.b[3];
+ dat2.b[1] = dat1.b[2];
+ dat2.b[2] = dat1.b[1];
+ dat2.b[3] = dat1.b[0];
+ return dat2.f;
+}
+
+float
+FloatNoSwap(float f)
+{
+ return f;
+}
+
+void
+Swap_Init(void)
+{
+ byte swaptest[2] = {1, 0};
+
+ /* set the byte swapping variables in a portable manner */
+ /* PVS NOTE: maybe use memcpy here? */
+ if (*(short *)swaptest == 1)
+ {
+ bigendien = false;
+ _BigShort = ShortSwap;
+ _LittleShort = ShortNoSwap;
+ _BigLong = LongSwap;
+ _LittleLong = LongNoSwap;
+ _BigFloat = FloatSwap;
+ _LittleFloat = FloatNoSwap;
+ Com_Printf("Byte ordering: little endian\n\n");
+ }
+ else
+ {
+ bigendien = true;
+ _BigShort = ShortNoSwap;
+ _LittleShort = ShortSwap;
+ _BigLong = LongNoSwap;
+ _LittleLong = LongSwap;
+ _BigFloat = FloatNoSwap;
+ _LittleFloat = FloatSwap;
+ Com_Printf("Byte ordering: big endian\n\n");
+ }
+
+ if (LittleShort(*(short *)swaptest) != 1)
+ assert("Error in the endian conversion!");
+}
+
+/*
+ * does a varargs printf into a temp buffer, so I don't
+ * need to have varargs versions of all text functions.
+ */
+char *
+va(const char *format, ...)
+{
+ va_list argptr;
+ static char string[1024];
+
+ va_start(argptr, format);
+ vsnprintf(string, 1024, format, argptr);
+ va_end(argptr);
+
+ return string;
+}
+
+char com_token[MAX_TOKEN_CHARS];
+
+/*
+ * Parse a token out of a string
+ */
+char *
+COM_Parse(char **data_p)
+{
+ int c;
+ int len;
+ char *data;
+
+ data = *data_p;
+ len = 0;
+ com_token[0] = 0;
+
+ if (!data)
+ {
+ *data_p = NULL;
+ return "";
+ }
+
+skipwhite:
+
+ while ((c = *data) <= ' ')
+ {
+ if (c == 0)
+ {
+ *data_p = NULL;
+ return "";
+ }
+
+ data++;
+ }
+
+ /* skip // comments */
+ if ((c == '/') && (data[1] == '/'))
+ {
+ while (*data && *data != '\n')
+ {
+ data++;
+ }
+
+ goto skipwhite;
+ }
+
+ /* handle quoted strings specially */
+ if (c == '\"')
+ {
+ data++;
+
+ while (1)
+ {
+ c = *data++;
+
+ if ((c == '\"') || !c)
+ {
+ com_token[len] = 0;
+ *data_p = data;
+ return com_token;
+ }
+
+ if (len < MAX_TOKEN_CHARS)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ }
+ }
+
+ /* parse a regular word */
+ do
+ {
+ if (len < MAX_TOKEN_CHARS)
+ {
+ com_token[len] = c;
+ len++;
+ }
+
+ data++;
+ c = *data;
+ }
+ while (c > 32);
+
+ if (len == MAX_TOKEN_CHARS)
+ {
+ len = 0;
+ }
+
+ com_token[len] = 0;
+
+ *data_p = data;
+ return com_token;
+}
+
+int paged_total;
+
+void
+Com_PageInMemory(byte *buffer, int size)
+{
+ int i;
+
+ for (i = size - 1; i > 0; i -= 4096)
+ {
+ paged_total += buffer[i];
+ }
+}
+
+/*
+ * ============================================================================
+ *
+ * LIBRARY REPLACEMENT FUNCTIONS
+ *
+ * ============================================================================
+ */
+
+int
+Q_stricmp(const char *s1, const char *s2)
+{
+ return strcasecmp(s1, s2);
+}
+
+int
+Q_strncasecmp(char *s1, char *s2, int n)
+{
+ int c1, c2;
+
+ do
+ {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (!n--)
+ {
+ return 0; /* strings are equal until end point */
+ }
+
+ if (c1 != c2)
+ {
+ if ((c1 >= 'a') && (c1 <= 'z'))
+ {
+ c1 -= ('a' - 'A');
+ }
+
+ if ((c2 >= 'a') && (c2 <= 'z'))
+ {
+ c2 -= ('a' - 'A');
+ }
+
+ if (c1 != c2)
+ {
+ return -1; /* strings not equal */
+ }
+ }
+ }
+ while (c1);
+
+ return 0; /* strings are equal */
+}
+
+int
+Q_strcasecmp(char *s1, char *s2)
+{
+ return Q_strncasecmp(s1, s2, 99999);
+}
+
+void
+Com_sprintf(char *dest, int size, char *fmt, ...)
+{
+ int len;
+ va_list argptr;
+ static char bigbuffer[0x10000];
+
+ va_start(argptr, fmt);
+ len = vsnprintf(bigbuffer, 0x10000, fmt, argptr);
+ va_end(argptr);
+
+ if (len >= size)
+ {
+ Com_Printf("Com_sprintf: overflow\n");
+
+ dest = NULL;
+ return;
+ }
+
+ bigbuffer[size - 1] = '\0';
+ strcpy(dest, bigbuffer);
+}
+
+char *
+strlwr ( char *s )
+{
+ char *p = s;
+
+ while ( *s )
+ {
+ *s = tolower( *s );
+ s++;
+ }
+
+ return ( p );
+}
+
+int
+Q_strlcpy(char *dst, const char *src, int size)
+{
+ const char *s = src;
+
+ while (*s)
+ {
+ if (size > 1)
+ {
+ *dst++ = *s;
+ size--;
+ }
+ s++;
+ }
+ if (size > 0)
+ {
+ *dst = '\0';
+ }
+
+ return s - src;
+}
+
+int
+Q_strlcat(char *dst, const char *src, int size)
+{
+ char *d = dst;
+
+ while (size > 0 && *d)
+ {
+ size--;
+ d++;
+ }
+
+ return (d - dst) + Q_strlcpy(d, src, size);
+}
+
+/*
+ * =====================================================================
+ *
+ * INFO STRINGS
+ *
+ * =====================================================================
+ */
+
+/*
+ * Searches the string for the given
+ * key and returns the associated value,
+ * or an empty string.
+ */
+char *
+Info_ValueForKey(char *s, char *key)
+{
+ char pkey[512];
+ static char value[2][512]; /* use two buffers so compares
+ work without stomping on each other */
+ static int valueindex;
+ char *o;
+
+ valueindex ^= 1;
+
+ if (*s == '\\')
+ {
+ s++;
+ }
+
+ while (1)
+ {
+ o = pkey;
+
+ while (*s != '\\')
+ {
+ if (!*s)
+ {
+ return "";
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+ s++;
+
+ o = value[valueindex];
+
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ {
+ return "";
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+
+ if (!strcmp(key, pkey))
+ {
+ return value[valueindex];
+ }
+
+ if (!*s)
+ {
+ return "";
+ }
+
+ s++;
+ }
+}
+
+void
+Info_RemoveKey(char *s, char *key)
+{
+ char *start;
+ char pkey[512];
+ char value[512];
+ char *o;
+
+ if (strstr(key, "\\"))
+ {
+ return;
+ }
+
+ while (1)
+ {
+ start = s;
+
+ if (*s == '\\')
+ {
+ s++;
+ }
+
+ o = pkey;
+
+ while (*s != '\\')
+ {
+ if (!*s)
+ {
+ return;
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+ s++;
+
+ o = value;
+
+ while (*s != '\\' && *s)
+ {
+ if (!*s)
+ {
+ return;
+ }
+
+ *o++ = *s++;
+ }
+
+ *o = 0;
+
+ if (!strcmp(key, pkey))
+ {
+ memmove(start, s, strlen(s) + 1); /* remove this part */
+ return;
+ }
+
+ if (!*s)
+ {
+ return;
+ }
+ }
+}
+
+/*
+ * Some characters are illegal in info strings
+ * because they can mess up the server's parsing
+ */
+qboolean
+Info_Validate(char *s)
+{
+ if (strstr(s, "\""))
+ {
+ return false;
+ }
+
+ if (strstr(s, ";"))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void
+Info_SetValueForKey(char *s, char *key, char *value)
+{
+ char newi[MAX_INFO_STRING], *v;
+ int c;
+ int maxsize = MAX_INFO_STRING;
+
+ if (strstr(key, "\\") || strstr(value, "\\"))
+ {
+ Com_Printf("Can't use keys or values with a \\\n");
+ return;
+ }
+
+ if (strstr(key, ";"))
+ {
+ Com_Printf("Can't use keys or values with a semicolon\n");
+ return;
+ }
+
+ if (strstr(key, "\"") || strstr(value, "\""))
+ {
+ Com_Printf("Can't use keys or values with a \"\n");
+ return;
+ }
+
+ if ((strlen(key) > MAX_INFO_KEY - 1) || (strlen(value) > MAX_INFO_KEY - 1))
+ {
+ Com_Printf("Keys and values must be < 64 characters.\n");
+ return;
+ }
+
+ Info_RemoveKey(s, key);
+
+ if (*value == '\0')
+ {
+ return;
+ }
+
+ Com_sprintf(newi, sizeof(newi), "\\%s\\%s", key, value);
+
+ if (strlen(newi) + strlen(s) >= maxsize)
+ {
+ Com_Printf("Info string length exceeded\n");
+ return;
+ }
+
+ /* only copy ascii values */
+ s += strlen(s);
+ v = newi;
+
+ while (*v)
+ {
+ c = *v++;
+ c &= 127; /* strip high bits */
+
+ if ((c >= 32) && (c < 127))
+ {
+ *s++ = c;
+ }
+ }
+
+ *s = 0;
+}
diff --git a/xatrix/stuff/mapfixes/industry.ent b/xatrix/stuff/mapfixes/industry.ent
new file mode 100644
index 0000000..edfce64
--- /dev/null
+++ b/xatrix/stuff/mapfixes/industry.ent
@@ -0,0 +1,6042 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Fixed an unreachable monster_gunner (1238)
+//
+// This gunner stands in front of the elevator when you return
+// after picking up the green key. His targetname, t238, is never
+// targeted. I set it to t301 instead, which is targeted by the
+// green key.
+{
+"sounds" "8"
+"nextmap" "outbase"
+"message" "Industrial Facility"
+"spawnflags" "1796"
+"sky" "x1u3"
+"classname" "worldspawn"
+"angle" "180"
+}
+{
+"origin" "320 -1216 248"
+"angle" "90"
+"targetname" "w_treat"
+"classname" "info_player_coop"
+}
+{
+"classname" "misc_teleporter"
+"spawnflags" "1792"
+"target" "t315"
+"origin" "-96 -1768 152"
+}
+{
+"origin" "-1360 -2040 144"
+"classname" "ammo_trap"
+}
+{
+"origin" "-1088 -936 352"
+"angles" "5 145 0"
+"classname" "info_player_intermission"
+}
+{
+"classname" "target_goal"
+"targetname" "t327"
+"origin" "344 -1232 232"
+}
+{
+"model" "*1"
+"classname" "trigger_once"
+"target" "t327"
+}
+{
+"classname" "target_goal"
+"targetname" "t326"
+"origin" "1512 632 176"
+}
+{
+"model" "*2"
+"classname" "trigger_once"
+"target" "t326"
+}
+{
+"origin" "-1048 -896 16"
+"spawnflags" "2048"
+"classname" "ammo_trap"
+}
+{
+"model" "*3"
+"spawnflags" "1798"
+"classname" "func_wall"
+}
+{
+"spawnflags" "2048"
+"classname" "ammo_bullets"
+"origin" "-808 1960 416"
+}
+{
+"classname" "ammo_bullets"
+"spawnflags" "2048"
+"origin" "-760 1960 416"
+}
+{
+"origin" "-608 -1920 144"
+"classname" "ammo_bullets"
+}
+{
+"spawnflags" "2048"
+"classname" "ammo_shells"
+"origin" "-808 1768 144"
+}
+{
+"classname" "ammo_shells"
+"spawnflags" "2048"
+"origin" "-808 1728 144"
+}
+{
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+"origin" "-2265 544 296"
+}
+{
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+"origin" "-1248 -256 32"
+}
+{
+"origin" "-1360 -2088 144"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1400 -2088 144"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1320 -2088 144"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1360 -1960 152"
+"targetname" "t324"
+"spawnflags" "2"
+"angle" "90"
+"classname" "monster_parasite"
+}
+{
+"spawnflags" "2048"
+"origin" "-1392 -1672 160"
+"target" "t322"
+"targetname" "t270"
+"classname" "trigger_relay"
+}
+{
+"model" "*4"
+"target" "t323"
+"count" "7"
+"spawnflags" "1"
+"targetname" "t322"
+"classname" "trigger_counter"
+}
+{
+"origin" "-1360 -2048 176"
+"classname" "light"
+"light" "70"
+"_color" "1.000000 0.000000 0.000000"
+}
+{
+"origin" "-1360 -1944 176"
+"_color" "1.000000 0.000000 0.000000"
+"light" "70"
+"classname" "light"
+}
+{
+"model" "*5"
+"spawnflags" "2048"
+"target" "t324"
+"targetname" "t323"
+"wait" "-1"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-224 -512 304"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-480 -512 304"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-480 -288 304"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-480 -160 304"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-480 -32 304"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-480 192 176"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-256 192 176"
+}
+{
+"_color" "0.678431 0.882353 1.000000"
+"light" "65"
+"classname" "light"
+"origin" "-24 192 176"
+}
+{
+"classname" "light"
+"light" "65"
+"_color" "0.678431 0.882353 1.000000"
+"origin" "352 192 304"
+}
+{
+"classname" "ammo_rockets"
+"spawnflags" "1792"
+"origin" "1974 856 144"
+}
+{
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+"origin" "1974 896 144"
+}
+{
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+"origin" "1974 936 144"
+}
+{
+"origin" "-184 -1776 152"
+"angle" "180"
+"classname" "info_player_start"
+}
+{
+"origin" "528 1632 264"
+"noise" "world/chatter3"
+"volume" "1"
+"attenuation" "-1"
+"spawnflags" "4"
+"targetname" "t321"
+"classname" "target_speaker"
+}
+{
+"origin" "520 1696 264"
+"delay" "3"
+"target" "t320"
+"targetname" "t275"
+"classname" "trigger_relay"
+}
+{
+"origin" "520 1656 264"
+"target" "t321"
+"targetname" "t275"
+"classname" "trigger_relay"
+}
+{
+"origin" "1512 312 200"
+"noise" "world/chatter2"
+"volume" "1"
+"attenuation" "-1"
+"spawnflags" "4"
+"targetname" "t319"
+"classname" "target_speaker"
+}
+{
+"origin" "1496 344 200"
+"target" "t318"
+"targetname" "t274"
+"classname" "trigger_relay"
+}
+{
+"origin" "1496 328 200"
+"delay" ".5"
+"target" "t319"
+"targetname" "t274"
+"classname" "trigger_relay"
+}
+{
+"origin" "1136 920 -176"
+"spawnflags" "1792"
+"classname" "item_armor_combat"
+}
+{
+"target" "t315"
+"spawnflags" "1792"
+"origin" "1544 440 -160"
+"classname" "misc_teleporter"
+}
+{
+"origin" "-1472 2024 424"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"model" "*6"
+"spawnflags" "1798"
+"classname" "func_wall"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t296"
+"target" "t317"
+"origin" "1456 784 -184"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t314"
+"target" "t316"
+"message" "Blue forcefield deactivated."
+"origin" "-408 536 240"
+}
+{
+"origin" "-2512 -1168 24"
+"targetname" "t315"
+"angle" "45"
+"classname" "misc_teleporter_dest"
+"spawnflags" "1792"
+}
+{
+"origin" "-472 -568 -104"
+"target" "t315"
+"spawnflags" "1792"
+"classname" "misc_teleporter"
+}
+{
+"model" "*7"
+"spawnflags" "1798"
+"classname" "func_wall"
+}
+{
+"origin" "328 -160 16"
+"spawnflags" "1792"
+"classname" "item_silencer"
+}
+{
+"origin" "224 224 208"
+"spawnflags" "1792"
+"classname" "ammo_slugs"
+}
+{
+"origin" "224 288 240"
+"spawnflags" "1792"
+"classname" "weapon_railgun"
+}
+{
+"origin" "-520 -536 144"
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+}
+{
+"origin" "-520 -568 144"
+"classname" "weapon_grenadelauncher"
+"spawnflags" "1792"
+}
+{
+"origin" "-520 -608 144"
+"spawnflags" "1792"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-536 1008 144"
+"classname" "weapon_shotgun"
+"spawnflags" "1792"
+}
+{
+"origin" "-496 1008 144"
+"spawnflags" "1792"
+"classname" "ammo_shells"
+}
+{
+"classname" "ammo_bullets"
+"origin" "-448 -1800 144"
+"spawnflags" "1792"
+}
+{
+"spawnflags" "1792"
+"origin" "-448 -1768 144"
+"classname" "weapon_chaingun"
+}
+{
+"classname" "ammo_bullets"
+"origin" "-608 -1968 144"
+}
+{
+"origin" "-544 -1576 152"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-2672 48 208"
+"classname" "ammo_cells"
+"spawnflags" "1792"
+}
+{
+"origin" "-2656 0 208"
+"classname" "weapon_bfg"
+"spawnflags" "1792"
+}
+{
+"origin" "-2672 -40 208"
+"spawnflags" "1792"
+"classname" "ammo_cells"
+}
+{
+"origin" "-1920 -152 208"
+"target" "t143"
+"spawnflags" "1792"
+"classname" "trigger_always"
+}
+{
+"origin" "1536 1816 144"
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+}
+{
+"origin" "1576 1816 144"
+"spawnflags" "1792"
+"classname" "ammo_grenades"
+}
+{
+"origin" "1120 1656 200"
+"target" "t214"
+"spawnflags" "1792"
+"classname" "trigger_always"
+}
+{
+"model" "*8"
+"spawnflags" "2054"
+"classname" "func_wall"
+}
+{
+"model" "*9"
+"spawnflags" "2054"
+"classname" "func_wall"
+}
+{
+"origin" "-824 1504 336"
+"spawnflags" "1792"
+"classname" "item_bandolier"
+}
+{
+"classname" "ammo_slugs"
+"spawnflags" "1792"
+"origin" "-216 1960 416"
+}
+{
+"classname" "weapon_railgun"
+"spawnflags" "1792"
+"origin" "-184 1960 416"
+}
+{
+"origin" "-256 480 144"
+"spawnflags" "1792"
+"classname" "item_quadfire"
+}
+{
+"model" "*10"
+"target" "t270"
+"classname" "func_button"
+"angle" "0"
+"lip" "4"
+}
+{
+"classname" "monster_gladiator"
+"angle" "180"
+"spawnflags" "3"
+"targetname" "t273"
+"target" "t314"
+"origin" "-392 480 152"
+}
+{
+"classname" "light"
+"light" "125"
+"_color" "1.000000 1.000000 0.835294"
+"origin" "-396 480 208"
+}
+{
+"model" "*11"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "t316"
+}
+{
+"targetname" "xintell"
+"angle" "180"
+"classname" "info_player_coop"
+"origin" "-128 -1856 152"
+}
+{
+"targetname" "xintell"
+"angle" "180"
+"classname" "info_player_coop"
+"origin" "-128 -1680 152"
+}
+{
+"targetname" "xintell"
+"classname" "info_player_coop"
+"angle" "180"
+"origin" "-96 -1768 152"
+}
+{
+"targetname" "outbase"
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "-1472 2288 424"
+}
+{
+"targetname" "outbase"
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "-1472 2208 424"
+}
+{
+"classname" "info_player_coop"
+"angle" "270"
+"targetname" "outbase"
+"origin" "-1472 2368 424"
+}
+{
+"angle" "90"
+"classname" "info_player_coop"
+"origin" "1544 320 152"
+"targetname" "refinery"
+}
+{
+"angle" "90"
+"classname" "info_player_coop"
+"origin" "1544 384 152"
+"targetname" "refinery"
+}
+{
+"classname" "info_player_coop"
+"angle" "90"
+"targetname" "refinery"
+"origin" "1544 256 152"
+}
+{
+"spawnflags" "2048"
+"classname" "ammo_magslug"
+"origin" "1240 1272 -176"
+}
+{
+"classname" "ammo_magslug"
+"spawnflags" "2048"
+"origin" "1176 1272 -176"
+}
+{
+"classname" "weapon_phalanx"
+"spawnflags" "2048"
+"origin" "1208 1272 -176"
+}
+{
+"spawnflags" "2048"
+"classname" "item_health_large"
+"origin" "-496 1008 144"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "2048"
+"origin" "-536 1008 144"
+}
+{
+"origin" "1536 1816 144"
+"spawnflags" "2048"
+"classname" "ammo_cells"
+}
+{
+"origin" "864 1320 144"
+"classname" "ammo_cells"
+"spawnflags" "2048"
+}
+{
+"origin" "864 1240 144"
+"spawnflags" "2048"
+"classname" "ammo_cells"
+}
+{
+"origin" "1224 408 144"
+"classname" "item_health_large"
+"spawnflags" "0"
+}
+{
+"origin" "1264 408 144"
+"classname" "item_health_large"
+"spawnflags" "0"
+}
+{
+"origin" "1184 408 144"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"origin" "-176 1632 424"
+"targetname" "t296"
+"spawnflags" "3"
+"angle" "180"
+"classname" "monster_infantry"
+}
+{
+"origin" "-232 1176 424"
+"targetname" "t296"
+"spawnflags" "3"
+"angle" "90"
+"classname" "monster_gunner"
+}
+{
+"origin" "472 1312 160"
+"target" "t221"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_gunner"
+}
+{
+"origin" "1112 640 -176"
+"spawnflags" "2048"
+"classname" "ammo_rockets"
+}
+{
+"origin" "920 1600 -176"
+"target" "t309"
+"targetname" "t308"
+"classname" "path_corner"
+}
+{
+"origin" "912 1744 -176"
+"target" "t308"
+"targetname" "t307"
+"classname" "path_corner"
+}
+{
+"origin" "1480 1616 -176"
+"targetname" "t310"
+"target" "t307"
+"classname" "path_corner"
+}
+{
+"origin" "1128 1528 -176"
+"target" "t310"
+"targetname" "t309"
+"classname" "path_corner"
+}
+{
+"origin" "1536 1616 -160"
+"target" "t310"
+"angle" "180"
+"spawnflags" "1"
+"classname" "monster_gladiator"
+}
+{
+"origin" "-2088 -536 648"
+"targetname" "t306"
+"classname" "point_combat"
+}
+{
+"origin" "-2088 -216 728"
+"targetname" "t287"
+"target" "t306"
+"spawnflags" "770"
+"angle" "270"
+"classname" "monster_hover"
+}
+{
+"target" "t305"
+"targetname" "t304"
+"origin" "-1456 -1776 136"
+"classname" "point_combat"
+}
+{
+"targetname" "t277"
+"target" "t304"
+"spawnflags" "1"
+"angle" "270"
+"origin" "-1456 -1576 152"
+"classname" "monster_gladiator"
+}
+{
+"origin" "1960 1296 136"
+"targetname" "t303"
+"classname" "point_combat"
+}
+{
+"classname" "target_help"
+"targetname" "t301"
+"message" "Proceed to Outer Base."
+"origin" "1552 368 -120"
+}
+{
+"origin" "464 48 232"
+"message" "You found a secret."
+"targetname" "t300"
+"classname" "target_secret"
+}
+{
+"origin" "-544 -880 -88"
+"light" "60"
+"classname" "light"
+}
+{
+"origin" "-760 1912 496"
+"target" "t299"
+"targetname" "t298"
+"classname" "trigger_relay"
+}
+{
+"origin" "-792 1912 496"
+"target" "t298"
+"targetname" "t297"
+"item" "key_green_key"
+"classname" "trigger_key"
+}
+{
+"model" "*12"
+"target" "t297"
+"spawnflags" "2048"
+"classname" "trigger_multiple"
+}
+{
+"spawnflags" "2048"
+"origin" "1544 440 -168"
+"classname" "key_green_key"
+"target" "t301"
+}
+{
+"model" "*13"
+"spawnflags" "2054"
+"classname" "func_wall"
+"targetname" "t317"
+}
+{
+"classname" "item_health_large"
+"origin" "-328 -1576 144"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1223 -1767 208"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-863 -1775 208"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-583 -1759 208"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-359 -1759 208"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1383 185 264"
+}
+{
+"targetname" "t305"
+"classname" "point_combat"
+"origin" "-1280 -1776 136"
+}
+{
+"target" "t303"
+"angle" "135"
+"origin" "1920 1096 248"
+"targetname" "t290"
+"classname" "monster_hover"
+"item" "ammo_cells"
+}
+{
+"origin" "1576 432 200"
+"target" "t288"
+"targetname" "t272"
+"classname" "trigger_relay"
+}
+{
+"origin" "1840 904 456"
+"targetname" "t288"
+"classname" "monster_hover"
+"angle" "180"
+"spawnflags" "3"
+}
+{
+"origin" "1736 504 456"
+"targetname" "t288"
+"classname" "monster_hover"
+"angle" "90"
+"spawnflags" "3"
+}
+{
+"origin" "1544 504 456"
+"targetname" "t288"
+"classname" "monster_hover"
+"angle" "90"
+"spawnflags" "771"
+}
+{
+"origin" "1336 504 456"
+"targetname" "t288"
+"classname" "monster_hover"
+"angle" "90"
+"spawnflags" "771"
+}
+{
+"targetname" "t288"
+"origin" "1816 1064 456"
+"spawnflags" "3"
+"angle" "135"
+"classname" "monster_hover"
+}
+{
+"targetname" "t50"
+"target" "t296"
+"origin" "1448 912 -192"
+"spawnflags" "1"
+"angle" "45"
+"classname" "monster_boss5"
+}
+{
+"origin" "-1512 2176 432"
+"target" "t269"
+"spawnflags" "1"
+"classname" "target_crosslevel_target"
+}
+{
+"origin" "-1856 -456 648"
+"targetname" "t285"
+"classname" "point_combat"
+}
+{
+"origin" "-1600 -784 648"
+"targetname" "t286"
+"classname" "point_combat"
+}
+{
+"origin" "-2112 -792 648"
+"targetname" "t284"
+"classname" "point_combat"
+}
+{
+"origin" "-1864 -280 664"
+"targetname" "t287"
+"target" "t285"
+"classname" "monster_hover"
+"angle" "270"
+"spawnflags" "2"
+}
+{
+"origin" "-1608 -1056 744"
+"targetname" "t287"
+"target" "t286"
+"classname" "monster_hover"
+"angle" "90"
+"spawnflags" "2"
+}
+{
+"origin" "-2112 -1064 744"
+"targetname" "t287"
+"target" "t284"
+"spawnflags" "258"
+"angle" "90"
+"classname" "monster_hover"
+}
+{
+"origin" "-1624 -192 136"
+"target" "t279"
+"targetname" "t278"
+"classname" "point_combat"
+}
+{
+"origin" "-1632 -256 136"
+"target" "t280"
+"targetname" "t279"
+"classname" "point_combat"
+}
+{
+"target" "t278"
+"origin" "-1313 -207 152"
+"spawnflags" "769"
+"angle" "180"
+"classname" "monster_soldier_lasergun"
+"targetname" "t1"
+}
+{
+"origin" "-2080 -256 136"
+"target" "t283"
+"targetname" "t282"
+"classname" "point_combat"
+}
+{
+"origin" "-1760 -192 136"
+"target" "t282"
+"targetname" "t281"
+"classname" "point_combat"
+}
+{
+"origin" "-1632 -560 136"
+"targetname" "t280"
+"classname" "point_combat"
+"spawnflags" "1"
+}
+{
+"origin" "-2080 -560 136"
+"targetname" "t283"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"spawnflags" "1792"
+"origin" "-448 -1736 144"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-448 -1568 144"
+"classname" "ammo_rockets"
+}
+{
+"model" "*14"
+"spawnflags" "2048"
+"target" "t276"
+"classname" "trigger_once"
+}
+{
+"spawnflags" "2048"
+"origin" "-280 -1632 144"
+"classname" "item_health_large"
+}
+{
+"origin" "-560 -1960 152"
+"item" "ammo_cells"
+"targetname" "t276"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "-544 -1568 152"
+"item" "item_armor_shard"
+"target" "t277"
+"targetname" "t276"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_gunner"
+}
+{
+"targetname" "t320"
+"origin" "552 1672 280"
+"message" "Pursue airstrike marker to\n Water Treatment Plant."
+"classname" "target_help"
+}
+{
+"model" "*15"
+"target" "t275"
+"targetname" "t273"
+"spawnflags" "4"
+"classname" "trigger_once"
+}
+{
+"targetname" "t318"
+"origin" "1496 448 216"
+"message" "Locate stolen\n airstrike marker."
+"classname" "target_help"
+}
+{
+"origin" "1592 416 200"
+"target" "t273"
+"targetname" "t272"
+"classname" "trigger_relay"
+}
+{
+"origin" "1592 448 200"
+"target" "t272"
+"spawnflags" "2"
+"classname" "target_crosslevel_target"
+}
+{
+"model" "*16"
+"target" "t274"
+"targetname" "t273"
+"spawnflags" "4"
+"classname" "trigger_once"
+}
+{
+"origin" "1544 456 152"
+"targetname" "refinery"
+"angle" "90"
+"classname" "info_player_start"
+}
+{
+"origin" "1504 256 200"
+"map" "refinery$industry"
+"targetname" "t271"
+"classname" "target_changelevel"
+}
+{
+"origin" "-1024 -544 8"
+"targetname" "t1"
+"spawnflags" "2"
+"angle" "180"
+"classname" "monster_boss5"
+}
+{
+"model" "*17"
+"spawnflags" "2048"
+"target" "t1"
+"classname" "trigger_once"
+}
+{
+"origin" "-1376 -1632 160"
+"light" "50"
+"classname" "light"
+}
+{
+"model" "*18"
+"target" "t270"
+"lip" "4"
+"angle" "270"
+"classname" "func_button"
+}
+{
+"origin" "-1456 -1768 272"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-1200 -1768 272"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-976 -1768 272"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-720 -1768 272"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-512 -1696 312"
+"light" "65"
+"classname" "light"
+}
+{
+"origin" "-112 -1704 224"
+"classname" "light"
+"light" "65"
+}
+{
+"origin" "-512 -1840 312"
+"classname" "light"
+"light" "65"
+}
+{
+"origin" "-352 -1840 312"
+"classname" "light"
+"light" "65"
+}
+{
+"origin" "-352 -1696 312"
+"classname" "light"
+"light" "65"
+}
+{
+"origin" "-1456 -1576 272"
+"classname" "light"
+"light" "65"
+}
+{
+"origin" "-112 -1832 224"
+"light" "65"
+"classname" "light"
+}
+{
+"model" "*19"
+"lip" "38"
+"team" "ed1"
+"angle" "270"
+"classname" "func_door"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"model" "*20"
+"lip" "38"
+"team" "ed1"
+"angle" "90"
+"classname" "func_door"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"origin" "120 -1000 360"
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.868526 0.721116"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "96 -1024 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "72 -1000 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "96 -976 360"
+}
+{
+"light" "50"
+"_color" "1.000000 0.000000 0.000000"
+"classname" "light"
+"origin" "1140 1560 180"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.000000 0.000000"
+"light" "50"
+"origin" "1140 1736 180"
+}
+{
+"origin" "-200 -1776 152"
+"target" "t113"
+"angle" "180"
+"classname" "info_player_start"
+"targetname" "xintell"
+}
+{
+"origin" "-1424 2120 424"
+"message" "Proceed to Refinery."
+"targetname" "t268"
+"classname" "target_help"
+}
+{
+"model" "*21"
+"targetname" "t269"
+"target" "t268"
+"spawnflags" "4"
+"classname" "trigger_once"
+}
+{
+"origin" "1119 680 -176"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"target" "t290"
+"origin" "1736 1624 -128"
+"targetname" "t50"
+"classname" "trigger_relay"
+}
+{
+"origin" "144 96 -16"
+"classname" "item_health_large"
+}
+{
+"origin" "-426 105 -24"
+"spawnflags" "2048"
+"delay" "2"
+"target" "t248"
+"targetname" "t247"
+"classname" "trigger_relay"
+}
+{
+"model" "*22"
+"spawnflags" "2048"
+"target" "t247"
+"classname" "trigger_once"
+}
+{
+"origin" "144 96 -8"
+"targetname" "t246"
+"spawnflags" "2050"
+"angle" "180"
+"classname" "monster_soldier_lasergun"
+}
+{
+"model" "*23"
+"spawnflags" "0"
+"targetname" "t248"
+"target" "t246"
+"wait" "-1"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"model" "*24"
+"target" "t271"
+"angle" "270"
+"classname" "trigger_multiple"
+}
+{
+"model" "*25"
+"message" "This door is opened nearby."
+"targetname" "t328"
+"spawnflags" "2048"
+"wait" "-1"
+"_minlight" ".1"
+"angle" "-1"
+"classname" "func_door"
+}
+{
+"classname" "item_armor_combat"
+"spawnflags" "2048"
+"origin" "-1472 192 280"
+}
+{
+"classname" "monster_gunner"
+"angle" "0"
+"spawnflags" "257"
+"origin" "-1328 1857 424"
+"item" "ammo_bullets"
+}
+{
+"spawnflags" "2049"
+"angle" "270"
+"classname" "monster_soldier_lasergun"
+"origin" "-1506 2023 424"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "270"
+"spawnflags" "2049"
+"origin" "-1442 2023 424"
+}
+{
+"classname" "monster_gunner"
+"origin" "464 2009 160"
+"angle" "90"
+"spawnflags" "259"
+"targetname" "t301"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t242"
+"killtarget" "rocketboy"
+"origin" "1976 512 440"
+}
+{
+"model" "*26"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"message" "No prize for you Rocketman"
+"target" "t242"
+}
+{
+"classname" "item_invulnerability"
+"spawnflags" "2048"
+"targetname" "rocketboy"
+"origin" "2063 433 400"
+}
+{
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+"origin" "1231 1464 -168"
+}
+{
+"model" "*27"
+"classname" "trigger_once"
+"target" "t228"
+}
+{
+"classname" "target_secret"
+"targetname" "t226"
+"message" "You've found a secret."
+"origin" "-2582 41 296"
+}
+{
+"model" "*28"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t226"
+}
+{
+"classname" "weapon_phalanx"
+"spawnflags" "2048"
+"origin" "-2652 1 208"
+}
+{
+"classname" "ammo_magslug"
+"spawnflags" "2048"
+"origin" "-2671 48 208"
+}
+{
+"classname" "ammo_magslug"
+"spawnflags" "2048"
+"origin" "-2671 -44 208"
+}
+{
+"spawnflags" "0"
+"classname" "item_health"
+"origin" "-2392 473 16"
+}
+{
+"spawnflags" "0"
+"classname" "item_health"
+"origin" "-2392 545 16"
+}
+{
+"classname" "item_health"
+"spawnflags" "0"
+"origin" "-2392 409 16"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t225"
+"target" "t214"
+"origin" "1114 1681 200"
+}
+{
+"classname" "monster_soldier_hypergun"
+"angle" "45"
+"spawnflags" "2049"
+"origin" "-545 -488 -104"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "45"
+"spawnflags" "2305"
+"origin" "-457 -96 -104"
+}
+{
+"classname" "path_corner"
+"targetname" "t221"
+"origin" "471 1343 144"
+"target" "t222"
+}
+{
+"classname" "path_corner"
+"targetname" "t220"
+"target" "t221"
+"origin" "463 1855 144"
+}
+{
+"classname" "path_corner"
+"targetname" "t219"
+"target" "t220"
+"origin" "-97 1855 144"
+}
+{
+"classname" "path_corner"
+"targetname" "t218"
+"target" "t219"
+"origin" "463 1855 144"
+}
+{
+"classname" "path_corner"
+"targetname" "t217"
+"target" "t218"
+"origin" "463 1647 144"
+}
+{
+"classname" "path_corner"
+"target" "t217"
+"origin" "943 1647 144"
+"targetname" "t223"
+}
+{
+"classname" "path_corner"
+"origin" "463 1647 144"
+"targetname" "t222"
+"target" "t223"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"item" "ammo_bullets"
+"spawnflags" "2049"
+"origin" "1040 1647 160"
+"target" "t223"
+}
+{
+"classname" "monster_gunner"
+"angle" "90"
+"targetname" "t216"
+"origin" "320 -1064 248"
+"spawnflags" "1"
+}
+{
+"classname" "monster_soldier_hypergun"
+"angle" "180"
+"spawnflags" "1"
+"target" "t216"
+"origin" "-265 -1001 248"
+}
+{
+"classname" "monster_soldier_ripper"
+"angle" "315"
+"item" "ammo_cells"
+"origin" "-544 40 -104"
+}
+{
+"classname" "ammo_bullets"
+"spawnflags" "2048"
+"origin" "-1248 -144 144"
+}
+{
+"origin" "-1249 -192 144"
+"classname" "item_health"
+"spawnflags" "2048"
+}
+{
+"classname" "item_health_large"
+"origin" "1406 1480 -168"
+}
+{
+"classname" "trigger_key"
+"targetname" "t213"
+"item" "key_red_key"
+"origin" "1081 1668 200"
+"target" "t225"
+}
+{
+"model" "*29"
+"target" "t244"
+"spawnflags" "4"
+"classname" "trigger_multiple"
+"targetname" "t214"
+}
+{
+"model" "*30"
+"spawnflags" "2048"
+"classname" "trigger_multiple"
+"target" "t213"
+}
+{
+"model" "*31"
+"target" "t244"
+"classname" "trigger_multiple"
+"spawnflags" "4"
+"targetname" "t214"
+}
+{
+"classname" "monster_soldier_ripper"
+"spawnflags" "769"
+"origin" "-1093 1345 408"
+"item" "ammo_cells"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "2048"
+"origin" "-2416 33 16"
+}
+{
+"classname" "item_health"
+"spawnflags" "2048"
+"origin" "-1249 -96 144"
+}
+{
+"target" "t281"
+"classname" "monster_soldier_lasergun"
+"angle" "180"
+"spawnflags" "257"
+"origin" "-1313 -135 152"
+"targetname" "t1"
+}
+{
+"origin" "447 -308 136"
+"targetname" "t207"
+"classname" "point_combat"
+}
+{
+"origin" "281 -11 136"
+"targetname" "t206"
+"classname" "point_combat"
+}
+{
+"origin" "416 -61 136"
+"targetname" "t205"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "239 -51 136"
+"target" "t204"
+"targetname" "t203"
+"classname" "point_combat"
+}
+{
+"origin" "127 -51 136"
+"targetname" "t204"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "301 99 152"
+"target" "t206"
+"spawnflags" "1"
+"angle" "225"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-819 1641 392"
+"targetname" "t202"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"targetname" "t148"
+"spawnflags" "769"
+"target" "t202"
+"angle" "270"
+"origin" "-830 1708 408"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-682 1814 152"
+"angle" "45"
+"spawnflags" "2049"
+"classname" "monster_infantry"
+}
+{
+"origin" "-429 1452 136"
+"target" "t201"
+"targetname" "t200"
+"classname" "point_combat"
+}
+{
+"origin" "-557 1428 136"
+"spawnflags" "1"
+"targetname" "t201"
+"classname" "point_combat"
+}
+{
+"origin" "-439 1566 152"
+"targetname" "t148"
+"target" "t200"
+"spawnflags" "2049"
+"angle" "270"
+"classname" "monster_infantry"
+"item" "ammo_bullets"
+}
+{
+"origin" "-608 1666 136"
+"targetname" "t199"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-608 1760 152"
+"targetname" "t148"
+"spawnflags" "2305"
+"target" "t199"
+"angle" "270"
+"classname" "monster_soldier_hypergun"
+}
+{
+"origin" "-385 1240 136"
+"targetname" "t198"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-246 1240 152"
+"targetname" "t148"
+"spawnflags" "2817"
+"target" "t198"
+"angle" "180"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-1550 -1444 144"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"classname" "point_combat"
+"targetname" "t177"
+"origin" "263 -610 136"
+}
+{
+"model" "*32"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t176"
+}
+{
+"target" "t203"
+"angle" "225"
+"classname" "monster_infantry"
+"origin" "238 22 152"
+"spawnflags" "1"
+"item" "ammo_bullets"
+}
+{
+"target" "t205"
+"classname" "monster_infantry"
+"angle" "225"
+"origin" "350 14 152"
+"spawnflags" "769"
+}
+{
+"target" "t207"
+"classname" "point_combat"
+"targetname" "t175"
+"spawnflags" "2049"
+"origin" "442 -169 136"
+}
+{
+"classname" "monster_infantry"
+"spawnflags" "2049"
+"angle" "0"
+"target" "t175"
+"targetname" "t176"
+"origin" "119 -167 24"
+}
+{
+"classname" "ammo_grenades"
+"spawnflags" "2048"
+"origin" "289 -455 -112"
+}
+{
+"classname" "ammo_grenades"
+"spawnflags" "2048"
+"origin" "241 -455 -112"
+}
+{
+"spawnflags" "2048"
+"classname" "ammo_shells"
+"origin" "311 -160 16"
+}
+{
+"classname" "ammo_shells"
+"spawnflags" "2048"
+"origin" "351 -160 16"
+}
+{
+"classname" "monster_parasite"
+"targetname" "t172"
+"spawnflags" "1"
+"origin" "30 97 -8"
+}
+{
+"classname" "ammo_rockets"
+"spawnflags" "0"
+"origin" "143 -744 152"
+}
+{
+"origin" "420 -391 -112"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "420 -359 -112"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "420 -423 -112"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"model" "*33"
+"target" "t174"
+"classname" "trigger_once"
+"spawnflags" "2048"
+}
+{
+"model" "*34"
+"speed" "150"
+"wait" "-1"
+"angle" "-2"
+"classname" "func_door"
+"lip" "4"
+"targetname" "t174"
+"spawnflags" "2048"
+}
+{
+"origin" "-984 692 400"
+"spawnflags" "2048"
+"classname" "weapon_rocketlauncher"
+}
+{
+"targetname" "t174"
+"origin" "-2277 402 56"
+"target" "t173"
+"classname" "trigger_relay"
+}
+{
+"targetname" "t173"
+"classname" "monster_parasite"
+"angle" "0"
+"spawnflags" "2306"
+"origin" "-2391 545 24"
+}
+{
+"targetname" "t173"
+"classname" "monster_parasite"
+"angle" "0"
+"spawnflags" "2050"
+"origin" "-2391 473 24"
+}
+{
+"spawnflags" "257"
+"origin" "-811 1807 152"
+"angle" "270"
+"classname" "monster_parasite"
+}
+{
+"origin" "440 288 152"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_infantry"
+}
+{
+"origin" "-560 304 -16"
+"spawnflags" "2048"
+"classname" "item_health"
+}
+{
+"origin" "-272 167 -16"
+"spawnflags" "0"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-312 167 -16"
+"spawnflags" "0"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-352 167 -16"
+"spawnflags" "0"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-232 167 -16"
+"spawnflags" "0"
+"classname" "item_armor_shard"
+}
+{
+"model" "*35"
+"spawnflags" "2048"
+"target" "t172"
+"classname" "trigger_once"
+}
+{
+"origin" "-462 268 -8"
+"classname" "monster_infantry"
+"angle" "270"
+"spawnflags" "2305"
+}
+{
+"targetname" "t172"
+"origin" "-414 248 -8"
+"spawnflags" "2049"
+"angle" "270"
+"classname" "monster_infantry"
+}
+{
+"origin" "-896 1504 328"
+"light" "75"
+"classname" "light"
+}
+{
+"spawnflags" "2048"
+"delay" "1.3"
+"target" "t169"
+"targetname" "t168"
+"origin" "-816 1536 408"
+"classname" "trigger_relay"
+}
+{
+"spawnflags" "2048"
+"message" "You've found a secret."
+"targetname" "t168"
+"origin" "-839 1555 408"
+"classname" "target_secret"
+}
+{
+"origin" "-953 1504 288"
+"spawnflags" "2048"
+"classname" "item_health_mega"
+}
+{
+"model" "*36"
+"lip" "10"
+"targetname" "t169"
+"wait" "-1"
+"spawnflags" "0"
+"angle" "-1"
+"classname" "func_door"
+}
+{
+"model" "*37"
+"spawnflags" "2048"
+"lip" "10"
+"target" "t168"
+"health" "1"
+"wait" "-1"
+"angle" "180"
+"classname" "func_door"
+}
+{
+"targetname" "t173"
+"origin" "-2391 409 24"
+"spawnflags" "2818"
+"angle" "0"
+"classname" "monster_parasite"
+}
+{
+"origin" "-1832 345 152"
+"spawnflags" "2305"
+"angle" "90"
+"classname" "monster_infantry"
+"item" "ammo_bullets"
+}
+{
+"origin" "-1385 152 152"
+"spawnflags" "2049"
+"angle" "315"
+"classname" "monster_infantry"
+"item" "ammo_bullets"
+}
+{
+"origin" "255 -820 152"
+"spawnflags" "2305"
+"angle" "90"
+"classname" "monster_gunner"
+"target" "t177"
+"targetname" "t176"
+}
+{
+"target" "t300"
+"origin" "476 96 240"
+"spawnflags" "2048"
+"classname" "item_armor_body"
+}
+{
+"origin" "222 240 208"
+"classname" "ammo_bullets"
+"spawnflags" "2048"
+}
+{
+"origin" "222 208 208"
+"spawnflags" "2048"
+"classname" "ammo_bullets"
+}
+{
+"origin" "222 293 240"
+"spawnflags" "2048"
+"classname" "weapon_chaingun"
+}
+{
+"origin" "93 -874 144"
+"classname" "item_health_large"
+"spawnflags" "2048"
+}
+{
+"origin" "93 -834 144"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"origin" "-373 -487 -104"
+"spawnflags" "2305"
+"angle" "135"
+"classname" "monster_parasite"
+}
+{
+"origin" "-472 -383 136"
+"targetname" "t161"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-470 -568 152"
+"spawnflags" "2048"
+"target" "t161"
+"angle" "90"
+"classname" "monster_gunner"
+"item" "ammo_grenades"
+}
+{
+"origin" "-2240 688 16"
+"spawnflags" "2048"
+"classname" "item_silencer"
+}
+{
+"origin" "-2256 408 160"
+"classname" "item_health"
+"spawnflags" "3584"
+}
+{
+"origin" "-2288 408 160"
+"spawnflags" "2048"
+"classname" "item_health"
+}
+{
+"origin" "-481 1664 336"
+"spawnflags" "0"
+"classname" "item_armor_body"
+}
+{
+"origin" "-353 1272 432"
+"spawnflags" "257"
+"target" "t160"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"target" "t160"
+"targetname" "t159"
+"classname" "path_corner"
+"origin" "-667 1274 416"
+}
+{
+"target" "t159"
+"targetname" "t158"
+"classname" "path_corner"
+"origin" "-571 1370 416"
+}
+{
+"target" "t158"
+"targetname" "t157"
+"origin" "-459 1466 416"
+"classname" "path_corner"
+}
+{
+"targetname" "t160"
+"target" "t157"
+"origin" "-459 1274 416"
+"classname" "path_corner"
+}
+{
+"origin" "508 1343 432"
+"spawnflags" "1"
+"target" "t156"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"origin" "-305 1344 416"
+"target" "t155"
+"targetname" "t154"
+"classname" "path_corner"
+}
+{
+"origin" "-305 1752 416"
+"target" "t154"
+"targetname" "t153"
+"classname" "path_corner"
+}
+{
+"origin" "-625 1752 416"
+"target" "t153"
+"targetname" "t152"
+"classname" "path_corner"
+}
+{
+"origin" "-321 1752 416"
+"target" "t152"
+"targetname" "t151"
+"classname" "path_corner"
+}
+{
+"origin" "-321 1344 416"
+"target" "t151"
+"targetname" "t150"
+"classname" "path_corner"
+}
+{
+"origin" "-113 1344 416"
+"target" "t150"
+"targetname" "t149"
+"classname" "path_corner"
+}
+{
+"origin" "463 1344 416"
+"targetname" "t156"
+"target" "t149"
+"classname" "path_corner"
+}
+{
+"origin" "-97 1344 416"
+"target" "t156"
+"targetname" "t155"
+"classname" "path_corner"
+}
+{
+"model" "*38"
+"spawnflags" "2048"
+"target" "t148"
+"classname" "trigger_once"
+}
+{
+"origin" "-985 727 408"
+"targetname" "t148"
+"target" "t147"
+"angle" "90"
+"spawnflags" "2049"
+"classname" "monster_gunner"
+"item" "ammo_grenades"
+}
+{
+"origin" "-985 815 392"
+"targetname" "t147"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-2217 544 296"
+"spawnflags" "2048"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-2255 464 160"
+"classname" "item_armor_shard"
+"spawnflags" "2048"
+}
+{
+"origin" "-2287 464 160"
+"classname" "item_armor_shard"
+"spawnflags" "2048"
+}
+{
+"origin" "-2223 464 160"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1772 527 152"
+"targetname" "t146"
+"angle" "0"
+"classname" "monster_parasite"
+"spawnflags" "2050"
+}
+{
+"origin" "-1836 519 152"
+"spawnflags" "2818"
+"targetname" "t146"
+"classname" "monster_parasite"
+"angle" "0"
+}
+{
+"origin" "-1804 471 152"
+"spawnflags" "2306"
+"targetname" "t146"
+"angle" "0"
+"classname" "monster_parasite"
+}
+{
+"origin" "-2031 239 144"
+"spawnflags" "2048"
+"classname" "ammo_grenades"
+}
+{
+"spawnflags" "2048"
+"classname" "item_health"
+"origin" "-942 -526 24"
+}
+{
+"spawnflags" "2048"
+"classname" "item_health"
+"origin" "-942 -486 24"
+}
+{
+"classname" "item_health"
+"spawnflags" "2048"
+"origin" "-942 -566 24"
+}
+{
+"model" "*39"
+"spawnflags" "5"
+"classname" "trigger_multiple"
+"targetname" "t143"
+"target" "t144"
+}
+{
+"model" "*40"
+"classname" "trigger_multiple"
+"spawnflags" "5"
+"targetname" "t143"
+"target" "t144"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t139"
+"target" "t143"
+"origin" "-1934 -109 208"
+}
+{
+"model" "*41"
+"classname" "trigger_multiple"
+"spawnflags" "2048"
+"target" "t141"
+}
+{
+"target" "t287"
+"classname" "key_pass"
+"spawnflags" "2048"
+"origin" "-1053 -544 24"
+}
+{
+"classname" "trigger_key"
+"target" "t139"
+"item" "key_pass"
+"origin" "-1931 -134 208"
+"targetname" "t141"
+"spawnflags" "2048"
+}
+{
+"model" "*42"
+"spawnflags" "2048"
+"targetname" "t299"
+"classname" "func_door"
+"angle" "-1"
+"wait" "-1"
+}
+{
+"classname" "target_help"
+"targetname" "t113"
+"origin" "-148 -1790 216"
+"message" "Gain entrance\nto Refinery."
+"spawnflags" "2048"
+}
+{
+"classname" "target_help"
+"targetname" "t113"
+"spawnflags" "2049"
+"message" "Destroy Strogg fuel\n production facility."
+"origin" "-177 -1745 216"
+}
+{
+"model" "*43"
+"classname" "trigger_once"
+"target" "t113"
+"spawnflags" "2048"
+}
+{
+"classname" "target_changelevel"
+"targetname" "t112"
+"map" "outbase$industry"
+"origin" "-1424 2400 464"
+}
+{
+"model" "*44"
+"target" "t112"
+"angle" "90"
+"classname" "trigger_multiple"
+}
+{
+"origin" "-1473 2119 424"
+"targetname" "outbase"
+"angle" "270"
+"classname" "info_player_start"
+}
+{
+"origin" "-1271 1865 472"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "-967 1865 472"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "-967 1865 512"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1271 1865 512"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-1272 1832 576"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1240 1864 576"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1272 1896 576"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1304 1864 576"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1471 1993 472"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-968 1832 576"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-936 1864 576"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-968 1896 576"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1000 1864 576"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-672 1792 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-640 1760 624"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-672 1728 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-704 1760 624"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-679 1761 560"
+}
+{
+"model" "*45"
+"angle" "-1"
+"classname" "func_door"
+"spawnflags" "2056"
+}
+{
+"origin" "1134 1719 144"
+"spawnflags" "1792"
+"classname" "ammo_magslug"
+}
+{
+"origin" "-1730 -1248 16"
+"spawnflags" "1792"
+"classname" "ammo_magslug"
+}
+{
+"origin" "-1648 -1247 16"
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+}
+{
+"origin" "-1688 -1247 16"
+"spawnflags" "1792"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-8 -617 144"
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+}
+{
+"origin" "-8 -585 144"
+"spawnflags" "1792"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-809 1768 144"
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+}
+{
+"origin" "-809 1688 144"
+"spawnflags" "1792"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-809 1727 144"
+"spawnflags" "1792"
+"classname" "weapon_grenadelauncher"
+}
+{
+"origin" "-248 1960 416"
+"spawnflags" "1792"
+"classname" "ammo_slugs"
+}
+{
+"origin" "2057 512 400"
+"classname" "ammo_cells"
+"spawnflags" "1792"
+}
+{
+"origin" "2017 550 400"
+"spawnflags" "1792"
+"classname" "ammo_cells"
+}
+{
+"origin" "2056 551 400"
+"spawnflags" "1792"
+"classname" "item_power_shield"
+}
+{
+"origin" "1111 640 -176"
+"classname" "ammo_cells"
+"spawnflags" "1792"
+}
+{
+"origin" "1127 584 -176"
+"spawnflags" "1792"
+"classname" "ammo_cells"
+}
+{
+"origin" "1111 680 -176"
+"spawnflags" "1792"
+"classname" "weapon_hyperblaster"
+}
+{
+"origin" "1888 402 144"
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+}
+{
+"origin" "1928 402 144"
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+}
+{
+"origin" "1848 402 144"
+"spawnflags" "2048"
+"classname" "ammo_rockets"
+}
+{
+"origin" "1951 1071 144"
+"spawnflags" "1792"
+"classname" "weapon_rocketlauncher"
+}
+{
+"origin" "1447 1704 -168"
+"spawnflags" "1792"
+"classname" "item_health_large"
+}
+{
+"origin" "1175 1465 -168"
+"classname" "item_health_small"
+"spawnflags" "1792"
+}
+{
+"origin" "1143 1465 -168"
+"classname" "item_health_small"
+"spawnflags" "1792"
+}
+{
+"origin" "1207 1465 -168"
+"spawnflags" "1792"
+"classname" "item_health_small"
+}
+{
+"origin" "616 1752 -168"
+"spawnflags" "1792"
+"classname" "ammo_bullets"
+}
+{
+"origin" "616 1720 -168"
+"classname" "ammo_bullets"
+"spawnflags" "1792"
+}
+{
+"origin" "616 1784 -168"
+"spawnflags" "1792"
+"classname" "ammo_bullets"
+}
+{
+"origin" "664 1752 -168"
+"spawnflags" "1792"
+"classname" "weapon_chaingun"
+}
+{
+"origin" "351 -864 208"
+"spawnflags" "1792"
+"classname" "item_adrenaline"
+}
+{
+"origin" "-193 1711 144"
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+}
+{
+"origin" "104 1856 144"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "64 1856 144"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "24 1856 144"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "144 1856 144"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-817 1504 144"
+"spawnflags" "1792"
+"classname" "ammo_magslug"
+}
+{
+"origin" "-944 696 400"
+"classname" "ammo_magslug"
+"spawnflags" "1792"
+}
+{
+"origin" "-1024 696 400"
+"spawnflags" "1792"
+"classname" "ammo_magslug"
+}
+{
+"origin" "-984 696 400"
+"spawnflags" "1792"
+"classname" "weapon_phalanx"
+}
+{
+"origin" "-686 1845 144"
+"classname" "ammo_shells"
+"spawnflags" "1792"
+}
+{
+"origin" "-652 1845 144"
+"spawnflags" "1792"
+"classname" "ammo_shells"
+}
+{
+"origin" "-685 1810 144"
+"spawnflags" "1792"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-298 40 -112"
+"spawnflags" "1792"
+"classname" "ammo_cells"
+}
+{
+"origin" "-256 41 -112"
+"spawnflags" "1792"
+"classname" "weapon_hyperblaster"
+}
+{
+"origin" "366 -415 -112"
+"classname" "ammo_cells"
+"spawnflags" "1792"
+}
+{
+"origin" "366 -367 -112"
+"spawnflags" "1792"
+"classname" "ammo_cells"
+}
+{
+"origin" "417 -389 -112"
+"spawnflags" "1792"
+"classname" "item_power_shield"
+}
+{
+"origin" "278 303 144"
+"spawnflags" "1792"
+"classname" "ammo_slugs"
+}
+{
+"origin" "472 93 240"
+"spawnflags" "1792"
+"classname" "item_health_mega"
+}
+{
+"origin" "94 -873 144"
+"classname" "ammo_slugs"
+"spawnflags" "1792"
+}
+{
+"origin" "175 -720 152"
+"spawnflags" "0"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-523 -570 144"
+"classname" "item_health_large"
+"spawnflags" "2048"
+}
+{
+"origin" "-523 -610 144"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"origin" "-945 479 144"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "-985 479 144"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1025 479 144"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-601 479 144"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "-641 479 144"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "-561 479 144"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-2240 256 16"
+"spawnflags" "2048"
+"classname" "item_bandolier"
+}
+{
+"origin" "-2218 255 16"
+"classname" "ammo_magslug"
+"spawnflags" "1792"
+}
+{
+"origin" "-2266 255 16"
+"spawnflags" "1792"
+"classname" "ammo_magslug"
+}
+{
+"origin" "-2240 704 16"
+"spawnflags" "1792"
+"classname" "weapon_phalanx"
+}
+{
+"origin" "-1841 584 144"
+"classname" "item_health_small"
+"spawnflags" "2048"
+}
+{
+"origin" "-1809 584 144"
+"classname" "item_health_small"
+"spawnflags" "2048"
+}
+{
+"origin" "-1873 584 144"
+"spawnflags" "2048"
+"classname" "item_health_small"
+}
+{
+"origin" "-2288 409 160"
+"classname" "ammo_shells"
+"spawnflags" "1792"
+}
+{
+"origin" "-2256 409 160"
+"spawnflags" "1792"
+"classname" "ammo_shells"
+}
+{
+"origin" "-2240 544 296"
+"spawnflags" "1792"
+"classname" "item_armor_combat"
+}
+{
+"origin" "-1472 232 280"
+"classname" "ammo_cells"
+"spawnflags" "1792"
+}
+{
+"origin" "-1472 152 280"
+"spawnflags" "1792"
+"classname" "ammo_cells"
+}
+{
+"origin" "-1473 192 280"
+"spawnflags" "1792"
+"classname" "weapon_boomer"
+}
+{
+"origin" "-2029 -88 144"
+"spawnflags" "1792"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-1745 -87 144"
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+}
+{
+"origin" "-1697 -87 144"
+"spawnflags" "1792"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-1281 -88 144"
+"spawnflags" "1792"
+"classname" "item_health"
+}
+{
+"origin" "-1241 -88 144"
+"spawnflags" "1792"
+"classname" "item_health"
+}
+{
+"origin" "-1321 -88 144"
+"spawnflags" "1792"
+"classname" "item_health"
+}
+{
+"origin" "-2369 -1312 208"
+"spawnflags" "1792"
+"classname" "item_health_mega"
+}
+{
+"classname" "ammo_shells"
+"spawnflags" "0"
+"origin" "-2252 -248 16"
+}
+{
+"origin" "-2216 -248 16"
+"classname" "ammo_shells"
+"spawnflags" "0"
+}
+{
+"origin" "-2288 -248 16"
+"spawnflags" "1792"
+"classname" "ammo_shells"
+}
+{
+"origin" "-2251 -284 16"
+"spawnflags" "0"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-954 -487 24"
+"classname" "ammo_rockets"
+"spawnflags" "1792"
+}
+{
+"origin" "-954 -599 24"
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-955 -542 24"
+"spawnflags" "1792"
+"classname" "weapon_rocketlauncher"
+}
+{
+"classname" "info_player_start"
+"angle" "90"
+"targetname" "w_treat"
+"origin" "316 -1143 248"
+}
+{
+"classname" "target_changelevel"
+"targetname" "t109"
+"map" "w_treat$industry"
+"origin" "293 -1383 272"
+}
+{
+"model" "*46"
+"classname" "trigger_multiple"
+"angle" "270"
+"target" "t109"
+}
+{
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1313 529 368"
+}
+{
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2089 513 496"
+}
+{
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1881 513 368"
+}
+{
+"origin" "2025 1305 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1897 1249 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1857 1097 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1841 897 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1833 713 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "2209 425 496"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1697 513 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1505 529 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1073 569 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1033 729 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1217 1289 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1041 1217 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1001 1065 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1073 905 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+}
+{
+"origin" "1169 945 384"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "961 889 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "969 1121 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "985 1337 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1185 1225 232"
+}
+{
+"origin" "1601 1529 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1441 1569 304"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1337 1585 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1489 1265 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1521 857 232"
+}
+{
+"origin" "1537 649 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1849 657 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1817 873 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1217 649 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1169 945 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1409 1089 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "961 681 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1593 1777 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1713 1105 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1857 1153 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1825 1345 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2025 1409 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2017 1665 232"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1793 1609 232"
+}
+{
+"origin" "2113 1161 232"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1521 857 72"
+}
+{
+"origin" "1849 657 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1817 873 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1217 649 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1169 945 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1409 1089 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1185 1225 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1489 1265 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1713 1105 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1857 1153 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1825 1345 72"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2025 1409 72"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2017 1665 72"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1793 1609 72"
+}
+{
+"origin" "2113 1161 72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1521 857 -80"
+}
+{
+"origin" "1537 649 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1849 657 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1817 873 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1217 649 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1409 1089 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1185 1225 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1489 1265 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1713 1105 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1857 1153 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1825 1345 -80"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2025 1409 -80"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2017 1665 -80"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1793 1609 -80"
+}
+{
+"origin" "2113 1161 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1409 1089 384"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1713 1105 384"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1817 873 384"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1849 657 384"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1537 649 384"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1217 649 384"
+}
+{
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "2105 1169 424"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1857 1153 384"
+}
+{
+"origin" "1521 857 384"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1825 1345 384"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "2025 1409 384"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "2017 1665 384"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1793 1609 384"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "1529 1609 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "1169 1617 -48"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "817 1665 -48"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "465 1977 -48"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "465 1969 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "1297 1577 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "1057 1609 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "841 1681 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "705 1825 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1425 1617 -48"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "465 2185 -80"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "465 2177 200"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "457 1929 200"
+}
+{
+"origin" "881 1649 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "673 1649 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "-55 1857 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "209 1857 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "673 2033 -80"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "465 1649 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "465 1377 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "465 1121 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "465 1121 328"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "465 1121 480"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "465 1345 480"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-47 1345 528"
+}
+{
+"origin" "241 1345 528"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "465 1345 528"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "241 1345 480"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1057 1649 200"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "465 1345 264"
+}
+{
+"origin" "1049 1649 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "673 1649 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-47 1345 480"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "465 1649 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "465 1857 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "209 1857 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-55 1857 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-367 1849 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-631 1289 496"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-807 1689 496"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-295 1369 496"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-263 1641 496"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-663 1849 496"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-367 1849 496"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "865 1649 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-663 1841 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-623 1537 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-335 1505 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-391 1273 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-671 1281 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-991 1281 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-991 1025 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-991 777 264"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-983 481 264"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "97 -999 296"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-95 -999 296"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-287 -999 296"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-447 -999 296"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-447 -1007 280"
+}
+{
+"origin" "-47 -999 280"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "-247 -999 280"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "321 -999 296"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-447 -1007 8"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "-447 -863 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "-447 -703 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+}
+{
+"origin" "-455 -551 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-479 -215 200"
+}
+{
+"origin" "65 -191 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "65 -319 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "65 -447 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "65 -559 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "321 -543 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "321 -415 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "321 -287 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "321 -159 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "313 -31 288"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "273 -31 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-479 -287 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "209 -999 280"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-247 -263 -72"
+}
+{
+"origin" "-487 -31 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-47 -239 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-55 -407 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "89 -359 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "289 -351 -72"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "257 -311 88"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "257 -311 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "329 113 224"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "65 -79 288"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "265 -127 224"
+}
+{
+"origin" "441 -127 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "41 -127 224"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-247 -55 -72"
+}
+{
+"origin" "433 -311 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "33 -311 224"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "257 -511 224"
+}
+{
+"origin" "433 -511 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "33 -511 224"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "449 -31 224"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "225 -631 224"
+}
+{
+"origin" "369 281 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-239 -511 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-487 -479 224"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-239 -175 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-479 -31 152"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-479 193 104"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-255 193 104"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "1 185 104"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-23 513 120"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-135 737 168"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-335 849 200"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-599 937 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-599 481 320"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-599 713 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-599 481 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "217 -855 224"
+}
+{
+"origin" "-2223 465 40"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-2199 465 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"style" "1"
+"targetname" "t105"
+"classname" "func_areaportal"
+}
+{
+"origin" "-423 1761 560"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-671 1281 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-311 1601 304"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-391 1345 560"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-671 1281 560"
+}
+{
+"origin" "-983 1281 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-983 1281 560"
+}
+{
+"origin" "-991 961 368"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-991 961 560"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-983 481 320"
+}
+{
+"origin" "-831 1273 496"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-655 1585 368"
+}
+{
+"origin" "-1471 1993 512"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1471 465 264"
+}
+{
+"origin" "-1471 465 320"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-1831 465 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-255 481 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-1463 -1655 208"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-1687 177 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-1951 177 264"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb23"
+}
+{
+"origin" "-1439 177 320"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"classname" "info_player_deathmatch"
+"origin" "1830 411 152"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "1216 1273 -168"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "687 2089 -160"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "135"
+"origin" "-191 1244 152"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "-481 25 -104"
+}
+{
+"classname" "info_player_deathmatch"
+"origin" "480 297 152"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "135"
+"origin" "37 86 -8"
+}
+{
+"classname" "info_player_deathmatch"
+"origin" "-601 1008 152"
+}
+{
+"angle" "135"
+"classname" "info_player_deathmatch"
+"origin" "-1282 1 152"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "270"
+"origin" "-2415 33 24"
+}
+{
+"classname" "info_player_deathmatch"
+"origin" "-1249 -152 152"
+}
+{
+"classname" "info_player_deathmatch"
+"origin" "-1050 -961 24"
+}
+{
+"origin" "-1800 464 368"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1831 465 320"
+}
+{
+"origin" "-1832 432 368"
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1864 464 368"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1832 496 368"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1440 464 368"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1472 432 368"
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1504 464 368"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1472 496 368"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-599 849 320"
+}
+{
+"origin" "-1615 -239 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1359 -479 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1623 -511 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-2111 -455 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-2175 -759 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1927 -807 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1663 -807 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1383 -807 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1879 -647 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1735 -1023 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-1983 -1023 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-2367 -639 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-2367 -127 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"origin" "-2367 -383 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"classname" "target_secret"
+"message" "You found another secret."
+"spawnflags" "2048"
+"targetname" "t104"
+"origin" "-2020 573 72"
+}
+{
+"target" "t104"
+"spawnflags" "0"
+"classname" "item_quad"
+"origin" "-1954 464 16"
+}
+{
+"classname" "target_secret"
+"message" "You've found a secret."
+"targetname" "t103"
+"spawnflags" "2048"
+"origin" "-1834 388 296"
+}
+{
+"classname" "path_corner"
+"wait" "-1"
+"origin" "-1984 328 96"
+"target" "t101"
+"targetname" "t102"
+}
+{
+"classname" "path_corner"
+"wait" "-1"
+"origin" "-1984 328 80"
+"targetname" "t101"
+"target" "t102"
+}
+{
+"classname" "target_speaker"
+"noise" "world/dr_short"
+"attenuation" "3"
+"volume" "1"
+"origin" "-1992 456 256"
+"targetname" "t99"
+}
+{
+"model" "*47"
+"classname" "func_explosive"
+"health" "25"
+"dmg" "50"
+"target" "t103"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t83"
+"target" "t99"
+"origin" "-1968 448 256"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t83"
+"target" "t100"
+"origin" "-1960 424 256"
+}
+{
+"model" "*48"
+"classname" "func_train"
+"spawnflags" "2"
+"target" "t86"
+"targetname" "t100"
+"team" "secretx"
+}
+{
+"model" "*49"
+"classname" "func_train"
+"spawnflags" "2"
+"target" "t88"
+"targetname" "t100"
+"team" "secretx"
+}
+{
+"model" "*50"
+"classname" "func_train"
+"spawnflags" "2"
+"target" "t90"
+"targetname" "t100"
+"team" "secretx"
+}
+{
+"model" "*51"
+"classname" "func_train"
+"spawnflags" "2"
+"target" "t92"
+"targetname" "t100"
+"team" "secretx"
+}
+{
+"model" "*52"
+"classname" "func_train"
+"spawnflags" "2"
+"target" "t94"
+"targetname" "t100"
+"team" "secretx"
+}
+{
+"model" "*53"
+"classname" "func_train"
+"spawnflags" "2"
+"targetname" "t100"
+"team" "secretx"
+"target" "t102"
+}
+{
+"classname" "path_corner"
+"targetname" "t93"
+"target" "t94"
+"origin" "-2016 328 64"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"targetname" "t91"
+"target" "t92"
+"origin" "-2048 328 48"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"targetname" "t89"
+"target" "t90"
+"origin" "-2080 328 32"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"targetname" "t87"
+"target" "t88"
+"origin" "-2112 328 16"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"targetname" "t85"
+"target" "t86"
+"origin" "-2144 328 0"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"target" "t85"
+"targetname" "t86"
+"origin" "-2144 328 96"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"target" "t87"
+"targetname" "t88"
+"origin" "-2112 328 96"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"target" "t89"
+"targetname" "t90"
+"origin" "-2080 328 96"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"target" "t91"
+"targetname" "t92"
+"origin" "-2048 328 96"
+"wait" "-1"
+}
+{
+"classname" "path_corner"
+"target" "t93"
+"targetname" "t94"
+"origin" "-2016 328 96"
+"wait" "-1"
+}
+{
+"model" "*54"
+"classname" "func_door"
+"angle" "180"
+"lip" "148"
+"targetname" "t99"
+"speed" "100"
+"spawnflags" "32"
+"wait" "-1"
+"_minlight" ".1"
+}
+{
+"model" "*55"
+"classname" "trigger_once"
+"target" "t84"
+"spawnflags" "2048"
+}
+{
+"model" "*56"
+"target" "t146"
+"classname" "func_door"
+"angle" "-1"
+"wait" "-1"
+"targetname" "t84"
+"speed" "200"
+"spawnflags" "2048"
+}
+{
+"model" "*57"
+"target" "t83"
+"health" "1"
+"lip" "8"
+"angle" "0"
+"classname" "func_button"
+}
+{
+"model" "*58"
+"target" "t83"
+"health" "1"
+"lip" "8"
+"angle" "0"
+"classname" "func_button"
+}
+{
+"noise" "world/amb23"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1935 465 40"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1695 177 320"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1951 177 320"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1887 -159 320"
+}
+{
+"origin" "-2199 465 320"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"origin" "-1463 -1439 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-2367 -895 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-2239 -639 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-2239 -1023 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1471 -1023 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-2031 -239 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1895 -383 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1415 -383 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1343 -639 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1215 -1023 256"
+}
+{
+"noise" "world/amb10"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1471 -1215 256"
+}
+{
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "-1311 -159 320"
+}
+{
+"origin" "-1455 -1575 256"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1455 -1767 256"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1199 -1767 256"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-975 -1767 256"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-719 -1767 256"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2383 1 256"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb10"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"origin" "-600 480 264"
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*59"
+"spawnflags" "8"
+"classname" "func_door"
+"angle" "-1"
+"target" "t50"
+"_minlight" ".1"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*60"
+"classname" "func_door"
+"angle" "-2"
+"spawnflags" "1"
+"targetname" "t58"
+"lip" "32"
+}
+{
+"model" "*61"
+"classname" "func_button"
+"angle" "270"
+"target" "t58"
+"delay" "1.5"
+"lip" "4"
+}
+{
+"model" "*62"
+"classname" "func_door"
+"angle" "-1"
+"_minlight" ".1"
+"spawnflags" "2056"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*63"
+"target" "t105"
+"classname" "func_door"
+"angle" "-1"
+"_minlight" ".1"
+"targetname" "t144"
+}
+{
+"targetname" "t178"
+"origin" "408 -360 -104"
+"spawnflags" "2050"
+"classname" "monster_parasite"
+"angle" "180"
+}
+{
+"targetname" "t178"
+"origin" "408 -432 -104"
+"spawnflags" "2306"
+"angle" "90"
+"classname" "monster_parasite"
+}
+{
+"style" "2"
+"classname" "func_areaportal"
+"targetname" "t50"
+}
+{
+"origin" "432 1976 -24"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "464 1944 -24"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "496 1976 -24"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "464 2008 -24"
+}
+{
+"origin" "840 1752 -104"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "912 1664 -104"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "125"
+"classname" "light"
+"origin" "1248 1616 -104"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "150"
+"classname" "light"
+"origin" "624 1996 -104"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "150"
+"classname" "light"
+"origin" "464 2144 -104"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "125"
+"classname" "light"
+"origin" "464 2016 200"
+}
+{
+"model" "*64"
+"targetname" "t244"
+"_minlight" ".1"
+"spawnflags" "8"
+"target" "t49"
+"angle" "-1"
+"classname" "func_door"
+}
+{
+"style" "3"
+"targetname" "t49"
+"classname" "func_areaportal"
+}
+{
+"origin" "464 1120 200"
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1280 200"
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1152 560"
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "520 1248 176"
+"delay" "2"
+"target" "t46"
+"targetname" "t45"
+"classname" "trigger_relay"
+}
+{
+"model" "*65"
+"target" "t45"
+"classname" "func_button"
+"angle" "270"
+"lip" "4"
+}
+{
+"model" "*66"
+"lip" "24"
+"spawnflags" "0"
+"targetname" "t46"
+"angle" "-1"
+"classname" "func_door"
+"_minlight" ".05"
+}
+{
+"origin" "1428 1616 -160"
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "-48 1312 576"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "-80 1344 576"
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "-16 1344 576"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "-48 1376 576"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "-48 1344 416"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1312 576"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "432 1344 576"
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "496 1344 576"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1376 576"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1344 416"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1648 144"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "432 1648 304"
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "464 1680 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "496 1648 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1616 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 2212 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "496 2180 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 2148 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "432 2180 304"
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "464 1888 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"delay" "2"
+"origin" "520 2064 -168"
+"target" "t42"
+"targetname" "t44"
+"classname" "trigger_relay"
+}
+{
+"model" "*67"
+"target" "t44"
+"angle" "90"
+"lip" "4"
+"classname" "func_button"
+}
+{
+"model" "*68"
+"lip" "8"
+"spawnflags" "1"
+"targetname" "t42"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "208 1888 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "240 1856 304"
+}
+{
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "176 1856 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "208 1824 304"
+}
+{
+"origin" "208 1856 144"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "125"
+"classname" "light"
+"origin" "464 1120 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "496 1856 304"
+}
+{
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "432 1856 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "464 1824 304"
+}
+{
+"origin" "464 1856 144"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "-48 1824 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "-80 1856 304"
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "-48 1888 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "-16 1856 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "240 1312 576"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "272 1344 576"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "240 1376 576"
+}
+{
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "208 1344 576"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "125"
+"classname" "light"
+"origin" "240 1344 416"
+}
+{
+"origin" "464 1456 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "496 1424 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "464 1392 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "432 1424 304"
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "464 1424 144"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "672 1680 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "704 1648 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "640 1648 304"
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "672 1616 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "672 1648 144"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "864 1648 144"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "1056 1648 144"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"origin" "1024 1648 304"
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+}
+{
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "832 1648 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "864 1616 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "896 1648 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "85"
+"classname" "light"
+"origin" "864 1680 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "125"
+"classname" "light"
+"origin" "-48 1856 144"
+}
+{
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "1056 1616 304"
+}
+{
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "1088 1648 304"
+}
+{
+"classname" "light"
+"light" "85"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "1056 1680 304"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "75"
+"classname" "light"
+"origin" "496 1120 560"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "75"
+"classname" "light"
+"origin" "464 1088 560"
+}
+{
+"_color" "1.000000 0.866667 0.717647"
+"light" "125"
+"classname" "light"
+"origin" "464 1120 384"
+}
+{
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.866667 0.717647"
+"origin" "432 1120 560"
+}
+{
+"origin" "1544 336 184"
+"light" "125"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "100"
+"origin" "1544 264 184"
+}
+{
+"style" "4"
+"classname" "func_areaportal"
+"targetname" "t41"
+}
+{
+"model" "*69"
+"_minlight" ".1"
+"classname" "func_door"
+"angle" "180"
+"team" "exit1"
+}
+{
+"model" "*70"
+"_minlight" ".1"
+"classname" "func_door"
+"angle" "0"
+"team" "exit1"
+"target" "t41"
+}
+{
+"origin" "-312 -1000 360"
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.868526 0.721116"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "344 -1000 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-96 -976 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-120 -1000 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-72 -1000 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-96 -1024 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-288 -976 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-448 -1000 312"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-264 -1000 360"
+}
+{
+"_color" "1.000000 0.868526 0.721116"
+"light" "50"
+"classname" "light"
+"origin" "-288 -1024 360"
+}
+{
+"origin" "296 -1000 360"
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.868526 0.721116"
+}
+{
+"origin" "320 -976 360"
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.868526 0.721116"
+}
+{
+"origin" "320 -1024 360"
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.868526 0.721116"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-2200 496 368"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-2168 464 368"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-2200 432 368"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-2232 464 368"
+}
+{
+"origin" "-1696 208 424"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1664 176 424"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1696 144 424"
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1728 176 424"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1440 208 424"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1408 176 424"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1440 144 424"
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1472 176 424"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1472 2024 576"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1440 1992 576"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-1472 1960 576"
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+}
+{
+"origin" "-1504 1992 576"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-984 1248 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-952 1280 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-984 1312 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1016 1280 624"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-672 1248 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-640 1280 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-672 1312 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-704 1280 624"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-384 1312 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-352 1344 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-384 1376 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-416 1344 624"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-416 1728 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-384 1760 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-416 1792 624"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-448 1760 624"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+"origin" "-1952 144 424"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1920 176 424"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1952 208 424"
+}
+{
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+"origin" "-1984 176 424"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-416 1304 236"
+}
+{
+"origin" "-312 1440 164"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-1016 960 624"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-984 992 624"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-952 960 624"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.877953 0.799213"
+}
+{
+"origin" "-984 928 624"
+"_color" "1.000000 0.877953 0.799213"
+"light" "100"
+"classname" "light"
+}
+{
+"light" "125"
+"classname" "light"
+"origin" "-312 1384 236"
+}
+{
+"origin" "-984 1088 264"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-984 904 264"
+"classname" "light"
+"light" "170"
+}
+{
+"origin" "-984 1280 264"
+"light" "150"
+"classname" "light"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-256 1440 228"
+}
+{
+"_color" "0.515748 0.818898 1.000000"
+"origin" "-224 1304 236"
+"classname" "light"
+"light" "125"
+}
+{
+"classname" "light"
+"light" "125"
+"origin" "-312 1440 292"
+}
+{
+"origin" "-312 1880 228"
+"classname" "light"
+"light" "150"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-224 1856 196"
+}
+{
+"origin" "-608 1632 416"
+"classname" "light"
+"light" "265"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-552 1880 228"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-312 1632 164"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-312 1824 164"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "-552 1824 164"
+}
+{
+"origin" "-576 1248 292"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-312 1632 292"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-312 1824 292"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-552 1824 292"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-728 1824 292"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-256 1632 228"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-728 1880 228"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "-728 1824 164"
+"classname" "light"
+"light" "125"
+}
+{
+"origin" "-576 1248 164"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "-984 568 216"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "-472 -440 -80"
+"classname" "light"
+"_color" "1.000000 0.654902 0.309804"
+"light" "150"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "115"
+"classname" "light"
+"origin" "-448 -972 -72"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "125"
+"classname" "light"
+"origin" "64 -192 296"
+}
+{
+"light" "125"
+"_color" "1.000000 0.654902 0.309804"
+"classname" "light"
+"origin" "-424 -480 -80"
+}
+{
+"light" "150"
+"_color" "1.000000 0.654902 0.309804"
+"classname" "light"
+"origin" "-472 -248 -80"
+}
+{
+"classname" "light"
+"_color" "0.541176 0.772549 1.000000"
+"light" "100"
+"origin" "248 -800 168"
+}
+{
+"origin" "64 -448 296"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "64 -320 296"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "-448 -1000 96"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "64 -80 296"
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "-600 568 240"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"model" "*71"
+"classname" "trigger_once"
+"target" "t37"
+"spawnflags" "2048"
+}
+{
+"model" "*72"
+"target" "t178"
+"classname" "func_door"
+"angle" "-2"
+"lip" "2"
+"targetname" "t37"
+"wait" "-1"
+"spawnflags" "2048"
+}
+{
+"_color" "1.000000 1.000000 0.866667"
+"light" "100"
+"classname" "light"
+"origin" "-356 224 56"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 1.000000 0.866667"
+"origin" "-156 224 56"
+}
+{
+"origin" "-472 -104 -80"
+"classname" "light"
+"_color" "1.000000 0.654902 0.309804"
+"light" "150"
+}
+{
+"origin" "-456 24 -80"
+"light" "125"
+"_color" "1.000000 0.654902 0.309804"
+"classname" "light"
+}
+{
+"light" "150"
+"_color" "1.000000 0.858824 0.717647"
+"classname" "light"
+"origin" "-240 -104 -80"
+}
+{
+"classname" "light"
+"_color" "1.000000 0.858824 0.717647"
+"light" "125"
+"origin" "-240 -248 -80"
+}
+{
+"origin" "-16 528 248"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "-88 664 248"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "-192 792 248"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "-336 856 248"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"origin" "-344 856 120"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "100"
+"classname" "light"
+"origin" "-16 528 32"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "100"
+"classname" "light"
+"origin" "-88 664 64"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "100"
+"classname" "light"
+"origin" "-184 792 96"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "100"
+"classname" "light"
+"origin" "0 352 248"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "125"
+"classname" "light"
+"origin" "64 -560 296"
+}
+{
+"origin" "0 352 0"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"_color" "1.000000 0.772549 0.650980"
+"light" "115"
+"classname" "light"
+"origin" "-600 852 264"
+}
+{
+"origin" "-984 480 264"
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.772549 0.650980"
+}
+{
+"_color" "1.000000 1.000000 0.835294"
+"light" "100"
+"classname" "light"
+"origin" "-1176 480 208"
+}
+{
+"origin" "-792 480 208"
+"classname" "light"
+"light" "100"
+"_color" "1.000000 1.000000 0.835294"
+}
+{
+"origin" "-256 480 208"
+"_color" "0.149020 0.596078 1.000000"
+"light" "100"
+"classname" "light"
+}
+{
+"model" "*73"
+"wait" "2"
+"targetname" "t270"
+"classname" "func_door"
+"angle" "-1"
+"target" "t34"
+"_minlight" ".1"
+"spawnflags" "0"
+}
+{
+"style" "5"
+"classname" "func_areaportal"
+"targetname" "t34"
+}
+{
+"origin" "160 -736 160"
+"light" "75"
+"classname" "light"
+}
+{
+"origin" "-920 -896 24"
+"_color" "1.000000 0.793522 0.655870"
+"light" "150"
+"classname" "light"
+}
+{
+"model" "*74"
+"spawnflags" "2048"
+"target" "t249"
+"speed" "50"
+"targetname" "t1"
+"lip" "-112"
+"angle" "-1"
+"classname" "func_door"
+"wait" "-1"
+"team" "bigdoor1"
+}
+{
+"model" "*75"
+"spawnflags" "2048"
+"wait" "-1"
+"speed" "50"
+"targetname" "t1"
+"lip" "-52"
+"angle" "-1"
+"classname" "func_door"
+"team" "bigdoor1"
+}
+{
+"model" "*76"
+"spawnflags" "2048"
+"wait" "-1"
+"speed" "50"
+"targetname" "t1"
+"angle" "-1"
+"classname" "func_door"
+"team" "bigdoor1"
+}
+{
+"model" "*77"
+"angle" "180"
+"classname" "func_door"
+"team" "baddoor"
+"spawnflags" "8"
+}
+{
+"model" "*78"
+"angle" "0"
+"classname" "func_door"
+"team" "baddoor"
+"spawnflags" "8"
+}
+{
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.586614 0.338583"
+"origin" "320 -1376 256"
+}
+{
+"classname" "light"
+"light" "125"
+"_color" "1.000000 0.586614 0.338583"
+"origin" "320 -1216 256"
+}
+{
+"classname" "info_player_coop"
+"targetname" "w_treat"
+"angle" "90"
+"origin" "320 -1280 248"
+}
+{
+"origin" "320 -1352 248"
+"angle" "90"
+"targetname" "w_treat"
+"classname" "info_player_coop"
+}
+{
+"model" "*79"
+"message" "A door has opened."
+"wait" "-1"
+"target" "t328"
+"spawnflags" "2048"
+"lip" "12"
+"angle" "0"
+"classname" "func_button"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "0.149020 0.596078 1.000000"
+"origin" "-224 480 168"
+} \ No newline at end of file
diff --git a/xatrix/stuff/mapfixes/w_treat.ent b/xatrix/stuff/mapfixes/w_treat.ent
new file mode 100644
index 0000000..98813fe
--- /dev/null
+++ b/xatrix/stuff/mapfixes/w_treat.ent
@@ -0,0 +1,4170 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Fixed an inaccessible monster_soldier_hypergun (861)
+//
+// He is part of a 3-man hypergun soldier group. The other two guys
+// both have a targetname t70 while the 3rd guy has none. It was most
+// likely intended for him to have t70 as well so I did just that.
+{
+"sounds" "2"
+"nextmap" "badlands"
+"sky" "x1u4"
+"Message" "Water Treatment Plant"
+"classname" "worldspawn"
+}
+{
+"killtarget" "rocketwall"
+"classname" "trigger_relay"
+"targetname" "t70"
+"origin" "-1328 -1304 312"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t57"
+"killtarget" "rocketwall"
+"origin" "-768 -2416 -88"
+}
+{
+"model" "*1"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "t5"
+}
+{
+"origin" "24 -1448 -16"
+"spawnflags" "2048"
+"classname" "misc_explobox"
+}
+{
+"origin" "-520 -1792 -16"
+"spawnflags" "2048"
+"classname" "misc_explobox"
+}
+{
+"origin" "-3200 -360 304"
+"noise" "world/chatter4"
+"volume" "1"
+"attenuation" "-1"
+"spawnflags" "2052"
+"targetname" "t95"
+"classname" "target_speaker"
+}
+{
+"origin" "-3200 -312 304"
+"target" "t94"
+"targetname" "t16"
+"classname" "trigger_relay"
+}
+{
+"origin" "-3200 -336 304"
+"delay" ".5"
+"target" "t95"
+"targetname" "t16"
+"classname" "trigger_relay"
+}
+{
+"origin" "-1324 -2092 -12"
+"targetname" "t59"
+"classname" "info_notnull"
+}
+{
+"origin" "-3208 -320 248"
+"angle" "0"
+"classname" "info_player_start"
+}
+{
+"angle" "0"
+"classname" "info_player_coop"
+"origin" "-3344 -320 248"
+"targetname" "industry"
+}
+{
+"angle" "0"
+"classname" "info_player_coop"
+"origin" "-3280 -320 248"
+"targetname" "industry"
+}
+{
+"classname" "info_player_coop"
+"targetname" "industry"
+"angle" "0"
+"origin" "-3416 -320 248"
+}
+{
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "1792 24 -120"
+"targetname" "badlands"
+}
+{
+"angle" "270"
+"classname" "info_player_coop"
+"origin" "1792 112 -120"
+"targetname" "badlands"
+}
+{
+"classname" "info_player_coop"
+"angle" "270"
+"targetname" "badlands"
+"origin" "1792 192 -120"
+}
+{
+"origin" "1456 -920 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "60"
+}
+{
+"origin" "1312 -968 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1120 -968 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "976 -888 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "976 -200 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1120 -136 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1312 -136 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1456 -184 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1568 -296 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1680 -184 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1792 -136 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1904 -248 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1904 -856 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1680 -920 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1792 -968 16"
+"classname" "light"
+"_color" "1.000000 0.482353 0.019608"
+"light" "70"
+}
+{
+"origin" "1568 -808 16"
+"light" "70"
+"_color" "1.000000 0.482353 0.019608"
+"classname" "light"
+}
+{
+"model" "*2"
+"classname" "func_wall"
+"spawnflags" "1792"
+}
+{
+"model" "*3"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"model" "*4"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"model" "*5"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "-472 -1324 16"
+"_color" "1.000000 0.482353 0.019608"
+"light" "85"
+"classname" "light"
+}
+{
+"origin" "-720 -592 24"
+"message" "Maintenance hatch\n override enabled."
+"target" "t3"
+"targetname" "t93"
+"classname" "trigger_relay"
+}
+{
+"model" "*6"
+"health" "1"
+"lip" "4"
+"wait" "-1"
+"angle" "-1"
+"spawnflags" "2056"
+"classname" "func_door"
+}
+{
+"model" "*7"
+"spawnflags" "2048"
+"target" "t93"
+"health" "1"
+"classname" "func_explosive"
+}
+{
+"classname" "misc_explobox"
+"spawnflags" "2048"
+"origin" "24 -1408 -16"
+}
+{
+"model" "*8"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"model" "*9"
+"targetname" "t92"
+"spawnflags" "6"
+"classname" "func_wall"
+}
+{
+"model" "*10"
+"targetname" "t92"
+"spawnflags" "6"
+"classname" "func_wall"
+}
+{
+"model" "*11"
+"targetname" "t92"
+"spawnflags" "2049"
+"classname" "func_object"
+}
+{
+"model" "*12"
+"targetname" "t92"
+"spawnflags" "2049"
+"classname" "func_object"
+}
+{
+"model" "*13"
+"targetname" "t92"
+"classname" "func_explosive"
+}
+{
+"model" "*14"
+"targetname" "t92"
+"spawnflags" "2049"
+"classname" "func_object"
+}
+{
+"model" "*15"
+"targetname" "t92"
+"spawnflags" "2054"
+"classname" "func_wall"
+}
+{
+"origin" "-864 -1088 232"
+"spawnflags" "2048"
+"classname" "weapon_railgun"
+}
+{
+"model" "*16"
+"health" "20"
+"target" "t92"
+"mass" "250"
+"dmg" "100"
+"classname" "func_explosive"
+}
+{
+"origin" "1792 -56 -120"
+"targetname" "badlands"
+"angle" "270"
+"classname" "info_player_start"
+}
+{
+"origin" "1808 176 -80"
+"map" "badlands$w_treat"
+"targetname" "t91"
+"classname" "target_changelevel"
+}
+{
+"classname" "monster_soldier_lasergun"
+"spawnflags" "769"
+"target" "t73"
+"origin" "1136 -880 -120"
+}
+{
+"classname" "monster_soldier_lasergun"
+"target" "t75"
+"spawnflags" "257"
+"origin" "1112 -240 -120"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "0"
+"spawnflags" "1024"
+"origin" "-2328 -3200 88"
+}
+{
+"classname" "ammo_shells"
+"origin" "-2920 -1072 -128"
+}
+{
+"classname" "monster_soldier_hypergun"
+"angle" "0"
+"spawnflags" "2"
+"targetname" "t88"
+"origin" "-2888 -1112 -120"
+}
+{
+"classname" "monster_gunner"
+"angle" "90"
+"spawnflags" "1"
+"origin" "1792 -888 -120"
+}
+{
+"classname" "target_goal"
+"targetname" "t89"
+"origin" "1832 -24 -72"
+}
+{
+"model" "*17"
+"target" "t91"
+"angle" "90"
+"classname" "trigger_multiple"
+}
+{
+"classname" "item_armor_shard"
+"origin" "560 -1816 -200"
+}
+{
+"classname" "item_armor_shard"
+"origin" "528 -1864 -200"
+}
+{
+"classname" "item_armor_shard"
+"origin" "608 -1808 -200"
+}
+{
+"classname" "item_health_small"
+"origin" "568 -2224 -200"
+}
+{
+"classname" "item_health_small"
+"origin" "584 -2280 -200"
+}
+{
+"classname" "item_health_small"
+"origin" "520 -2184 -200"
+}
+{
+"classname" "item_health_small"
+"origin" "568 -2328 -200"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "270"
+"spawnflags" "257"
+"origin" "-1984 -1296 280"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "315"
+"spawnflags" "257"
+"origin" "-2792 -1344 280"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "180"
+"spawnflags" "1"
+"origin" "-2280 -1368 8"
+"target" "t87"
+}
+{
+"origin" "-2720 -1088 -48"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"model" "*18"
+"classname" "func_door"
+"angle" "-2"
+"lip" "-16"
+"speed" "200"
+"wait" "-1"
+"targetname" "t87"
+"sounds" "1"
+"target" "t88"
+"spawnflags" "2048"
+}
+{
+"classname" "monster_berserk"
+"angle" "0"
+"spawnflags" "769"
+"origin" "-864 -1456 8"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "0"
+"spawnflags" "1"
+"origin" "-992 -1376 8"
+}
+{
+"classname" "monster_soldier_ripper"
+"angle" "315"
+"spawnflags" "769"
+"origin" "-968 -792 8"
+}
+{
+"spawnflags" "0"
+"classname" "item_health_small"
+"origin" "-320 -88 64"
+}
+{
+"spawnflags" "0"
+"classname" "item_health_small"
+"origin" "-360 -88 64"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "0"
+"origin" "-280 -88 64"
+}
+{
+"spawnflags" "2"
+"angle" "270"
+"classname" "monster_berserk"
+"targetname" "t86"
+"origin" "-352 -104 72"
+}
+{
+"classname" "monster_berserk"
+"angle" "270"
+"spawnflags" "770"
+"targetname" "t86"
+"origin" "-288 -104 72"
+}
+{
+"model" "*19"
+"classname" "func_door"
+"angle" "-2"
+"wait" "-1"
+"speed" "200"
+"sounds" "1"
+"lip" "-16"
+"targetname" "t47"
+"target" "t86"
+"spawnflags" "2048"
+}
+{
+"origin" "-416 -1304 64"
+"spawnflags" "2048"
+"classname" "item_health"
+}
+{
+"origin" "-800 -3096 -176"
+"message" "You've found a secret."
+"targetname" "t85"
+"classname" "target_secret"
+}
+{
+"origin" "-808 -3104 -256"
+"target" "t85"
+"spawnflags" "2048"
+"classname" "item_adrenaline"
+}
+{
+"origin" "-2840 -2248 -384"
+"classname" "item_health_large"
+}
+{
+"origin" "-2176 -1296 0"
+"spawnflags" "2048"
+"classname" "ammo_shells"
+}
+{
+"origin" "-2016 -1304 0"
+"classname" "ammo_magslug"
+"spawnflags" "2048"
+}
+{
+"origin" "-1952 -1304 0"
+"spawnflags" "2048"
+"classname" "ammo_magslug"
+}
+{
+"origin" "-1480 -1760 264"
+"targetname" "t84"
+"classname" "point_combat"
+}
+{
+"origin" "-1136 -1800 280"
+"target" "t84"
+"spawnflags" "2"
+"targetname" "t83"
+"angle" "180"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-1184 -1720 288"
+"target" "t83"
+"targetname" "t63"
+"classname" "trigger_relay"
+}
+{
+"model" "*20"
+"angle" "180"
+"classname" "trigger_monsterjump"
+}
+{
+"origin" "-1480 -1760 264"
+"targetname" "t82"
+"classname" "point_combat"
+}
+{
+"origin" "-1136 -1760 280"
+"targetname" "t63"
+"spawnflags" "2"
+"target" "t82"
+"angle" "180"
+"classname" "monster_soldier_hypergun"
+}
+{
+"origin" "-1352 -1632 8"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_berserk"
+}
+{
+"spawnflags" "1"
+"origin" "-472 -1248 200"
+"angle" "180"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-992 -1024 0"
+"spawnflags" "2048"
+"classname" "item_health"
+}
+{
+"origin" "-1136 -552 64"
+"classname" "item_health_large"
+"spawnflags" "2048"
+}
+{
+"origin" "-1136 -600 64"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"origin" "-256 -848 72"
+"spawnflags" "1"
+"target" "t42"
+"angle" "90"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "-1344 -816 72"
+"spawnflags" "769"
+"target" "t39"
+"angle" "90"
+"classname" "monster_soldier_hypergun"
+}
+{
+"origin" "-1440 -816 56"
+"targetname" "t80"
+"classname" "point_combat"
+"spawnflags" "1"
+}
+{
+"origin" "-1248 -816 56"
+"targetname" "t79"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-1248 -864 72"
+"target" "t79"
+"classname" "monster_soldier_lasergun"
+"angle" "90"
+"spawnflags" "257"
+}
+{
+"origin" "-1440 -864 72"
+"target" "t80"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-1344 -880 56"
+"spawnflags" "1"
+"targetname" "t78"
+"classname" "point_combat"
+}
+{
+"model" "*21"
+"classname" "func_wall"
+"spawnflags" "2054"
+"targetname" "rocketwall"
+}
+{
+"classname" "item_armor_body"
+"spawnflags" "2048"
+"origin" "2432 -744 -144"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "2048"
+"origin" "1728 -936 -128"
+}
+{
+"spawnflags" "0"
+"classname" "ammo_rockets"
+"origin" "1856 -935 -128"
+}
+{
+"classname" "monster_gunner"
+"angle" "270"
+"spawnflags" "769"
+"origin" "1792 -192 -120"
+}
+{
+"classname" "monster_soldier_ripper"
+"angle" "0"
+"spawnflags" "513"
+"target" "t76"
+"origin" "1224 -240 -120"
+}
+{
+"classname" "path_corner"
+"target" "t75"
+"targetname" "t76"
+"origin" "1368 -240 -136"
+}
+{
+"classname" "path_corner"
+"targetname" "t75"
+"target" "t76"
+"origin" "1024 -240 -136"
+}
+{
+"classname" "path_corner"
+"target" "t73"
+"targetname" "t74"
+"origin" "1376 -872 -136"
+}
+{
+"classname" "path_corner"
+"targetname" "t73"
+"target" "t74"
+"origin" "1048 -872 -136"
+}
+{
+"classname" "monster_soldier_ripper"
+"target" "t74"
+"spawnflags" "1"
+"origin" "1424 -872 -120"
+}
+{
+"origin" "704 -2432 264"
+"spawnflags" "3"
+"angle" "180"
+"classname" "monster_gekk"
+"targetname" "t72"
+}
+{
+"spawnflags" "3"
+"angle" "135"
+"classname" "monster_gekk"
+"origin" "120 -2744 392"
+"targetname" "t72"
+}
+{
+"spawnflags" "3"
+"angle" "135"
+"classname" "monster_gekk"
+"origin" "24 -2800 392"
+"targetname" "t72"
+}
+{
+"spawnflags" "3"
+"angle" "90"
+"classname" "monster_gekk"
+"origin" "-472 -2984 392"
+"targetname" "t72"
+}
+{
+"classname" "monster_gekk"
+"angle" "180"
+"spawnflags" "3"
+"origin" "712 -2360 264"
+"targetname" "t72"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t10"
+"origin" "-2336 -3160 112"
+"target" "t72"
+}
+{
+"classname" "monster_gunner"
+"angle" "315"
+"origin" "-2328 -3200 88"
+"spawnflags" "768"
+"targetname" "t90"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "0"
+"spawnflags" "1"
+"origin" "-2336 -3064 -120"
+}
+{
+"classname" "target_secret"
+"targetname" "t71"
+"message" "You've found a secret."
+"origin" "-2080 -1760 -264"
+}
+{
+"classname" "item_invulnerability"
+"spawnflags" "2048"
+"target" "t71"
+"origin" "-2144 -1728 -256"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "1"
+"origin" "-2464 -1336 -120"
+}
+{
+"classname" "monster_soldier_hypergun"
+"angle" "180"
+"spawnflags" "1"
+"origin" "-2336 -1368 -120"
+}
+{
+"spawnflags" "2"
+"angle" "180"
+"classname" "monster_soldier_hypergun"
+"targetname" "t70"
+"origin" "-1192 -1448 280"
+}
+{
+"classname" "monster_soldier_hypergun"
+"angle" "180"
+"spawnflags" "770"
+"targetname" "t70"
+"origin" "-1200 -1512 280"
+}
+{
+"classname" "monster_soldier_hypergun"
+"spawnflags" "2"
+"angle" "180"
+"targetname" "t70"
+"origin" "-1216 -1232 280"
+}
+{
+"model" "*22"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t70"
+}
+{
+"model" "*23"
+"classname" "trigger_once"
+"spawnflags" "2048"
+"target" "t69"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "180"
+"spawnflags" "2"
+"target" "t67"
+"targetname" "t69"
+"origin" "-1168 -1624 280"
+}
+{
+"classname" "monster_soldier_lasergun"
+"angle" "135"
+"spawnflags" "770"
+"target" "t68"
+"targetname" "t69"
+"origin" "-1136 -2008 280"
+}
+{
+"classname" "point_combat"
+"spawnflags" "1"
+"targetname" "t68"
+"origin" "-1488 -1952 264"
+}
+{
+"classname" "item_adrenaline"
+"spawnflags" "2048"
+"origin" "-2848 -1328 272"
+}
+{
+"classname" "point_combat"
+"spawnflags" "1"
+"origin" "-1616 -1608 264"
+"targetname" "t67"
+}
+{
+"classname" "point_combat"
+"targetname" "t66"
+"origin" "-1608 -1472 264"
+}
+{
+"classname" "monster_soldier_hypergun"
+"angle" "180"
+"spawnflags" "2"
+"target" "t66"
+"targetname" "t70"
+"origin" "-1224 -1472 280"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t63"
+"delay" "2"
+"origin" "-1752 -1776 160"
+}
+{
+"classname" "point_combat"
+"targetname" "t65"
+"origin" "-1608 -2072 264"
+}
+{
+"classname" "monster_soldier_ripper"
+"angle" "180"
+"target" "t65"
+"spawnflags" "2"
+"targetname" "t63"
+"origin" "-1488 -2072 280"
+}
+{
+"model" "*24"
+"classname" "trigger_monsterjump"
+"angle" "180"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t63"
+"target" "t64"
+"origin" "-1752 -1744 160"
+}
+{
+"model" "*25"
+"classname" "trigger_once"
+"target" "t63"
+}
+{
+"classname" "point_combat"
+"targetname" "t62"
+"origin" "-1608 -1472 264"
+}
+{
+"model" "*26"
+"classname" "trigger_monsterjump"
+"angle" "180"
+}
+{
+"classname" "monster_soldier_hypergun"
+"angle" "180"
+"spawnflags" "2"
+"origin" "-1272 -1472 280"
+"target" "t62"
+"targetname" "t64"
+}
+{
+"classname" "path_corner"
+"target" "t60"
+"targetname" "t61"
+"origin" "-1696 -1616 -8"
+}
+{
+"classname" "path_corner"
+"targetname" "t60"
+"target" "t61"
+"origin" "-1696 -1976 -8"
+}
+{
+"classname" "monster_gladiator"
+"angle" "180"
+"spawnflags" "1"
+"target" "t61"
+"origin" "-1696 -1792 8"
+}
+{
+"classname" "item_health_large"
+"spawnflags" "2048"
+"origin" "-1408 -1648 96"
+}
+{
+"classname" "point_combat"
+"spawnflags" "1"
+"targetname" "t58"
+"origin" "-1344 -2128 -8"
+}
+{
+"classname" "monster_gunner"
+"angle" "270"
+"spawnflags" "257"
+"target" "t58"
+"targetname" "t59"
+"origin" "-1328 -1912 8"
+}
+{
+"origin" "-1368 -2096 8"
+"targetname" "t57"
+"spawnflags" "1"
+"classname" "monster_berserk"
+"angle" "315"
+"target" "t59"
+}
+{
+"origin" "-1328 -2048 8"
+"targetname" "t57"
+"spawnflags" "1"
+"angle" "315"
+"classname" "monster_berserk"
+"target" "t59"
+}
+{
+"model" "*27"
+"target" "t57"
+"classname" "trigger_once"
+}
+{
+"spawnflags" "0"
+"classname" "ammo_bullets"
+"origin" "-537 -2464 160"
+}
+{
+"spawnflags" "0"
+"classname" "ammo_bullets"
+"origin" "-537 -2504 160"
+}
+{
+"origin" "-792 -2480 168"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_gunner"
+}
+{
+"origin" "-528 -2320 152"
+"targetname" "t55"
+"classname" "point_combat"
+}
+{
+"targetname" "t56"
+"target" "t55"
+"origin" "-648 -2320 168"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"target" "t56"
+"origin" "-200 -2232 88"
+"item" "ammo_cells"
+"spawnflags" "257"
+"angle" "45"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "80 -1176 8"
+"spawnflags" "2817"
+"angle" "270"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "-400 -1952 64"
+"classname" "item_armor_shard"
+"spawnflags" "2048"
+}
+{
+"origin" "-368 -1952 64"
+"classname" "item_armor_shard"
+"spawnflags" "2048"
+}
+{
+"origin" "-336 -1952 64"
+"classname" "item_armor_shard"
+"spawnflags" "2048"
+}
+{
+"origin" "-432 -1952 64"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-32 -1200 64"
+"classname" "item_health_small"
+"spawnflags" "2048"
+}
+{
+"origin" "-32 -1232 64"
+"classname" "item_health_small"
+"spawnflags" "2048"
+}
+{
+"origin" "-32 -1264 64"
+"classname" "item_health_small"
+"spawnflags" "2048"
+}
+{
+"origin" "-32 -1168 64"
+"spawnflags" "2048"
+"classname" "item_health_small"
+}
+{
+"origin" "-520 -1832 -16"
+"classname" "misc_explobox"
+"spawnflags" "2048"
+}
+{
+"origin" "-216 -1936 -16"
+"spawnflags" "2048"
+"classname" "misc_explobox"
+}
+{
+"origin" "-488 -1904 0"
+"spawnflags" "2048"
+"classname" "item_health"
+}
+{
+"origin" "24 -1744 8"
+"spawnflags" "1"
+"target" "t54"
+"angle" "180"
+"classname" "monster_gladiator"
+}
+{
+"origin" "-16 -1744 -8"
+"targetname" "t54"
+"target" "t53"
+"classname" "path_corner"
+}
+{
+"origin" "-472 -1744 -8"
+"target" "t54"
+"targetname" "t53"
+"classname" "path_corner"
+}
+{
+"origin" "-704 -1504 -8"
+"target" "t52"
+"targetname" "t51"
+"classname" "point_combat"
+}
+{
+"origin" "-704 -1232 -8"
+"targetname" "t52"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-544 -1504 8"
+"targetname" "t50"
+"target" "t51"
+"spawnflags" "257"
+"angle" "180"
+"classname" "monster_gunner"
+}
+{
+"origin" "-912 -1184 8"
+"spawnflags" "257"
+"angle" "0"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "-992 -1448 0"
+"spawnflags" "2048"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-992 -1216 0"
+"classname" "item_health_small"
+"spawnflags" "2048"
+}
+{
+"origin" "-992 -1176 0"
+"classname" "item_health_small"
+"spawnflags" "2048"
+}
+{
+"origin" "-992 -1256 0"
+"spawnflags" "2048"
+"classname" "item_health_small"
+}
+{
+"origin" "-544 -1384 0"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"origin" "-560 -1192 -8"
+"targetname" "t49"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "-528 -1192 8"
+"targetname" "t50"
+"target" "t49"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-992 -680 8"
+"target" "t50"
+"targetname" "t47"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_soldier_hypergun"
+}
+{
+"origin" "-480 -1368 248"
+"message" "You located a secret."
+"targetname" "t48"
+"classname" "target_secret"
+}
+{
+"origin" "-480 -1408 256"
+"target" "t48"
+"spawnflags" "2048"
+"classname" "item_quad"
+}
+{
+"origin" "-424 -1344 72"
+"targetname" "t47"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_gunner"
+}
+{
+"origin" "-96 -1000 72"
+"target" "t47"
+"spawnflags" "1"
+"angle" "180"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "-904 -96 328"
+"classname" "monster_soldier_lasergun"
+"angle" "270"
+"spawnflags" "1"
+}
+{
+"origin" "-904 -416 328"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_soldier_lasergun"
+}
+{
+"origin" "-2008 -384 72"
+"item" "ammo_cells"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_soldier_ripper"
+}
+{
+"target" "t78"
+"origin" "-1344 -1000 72"
+"classname" "monster_gunner"
+"angle" "90"
+"spawnflags" "257"
+}
+{
+"origin" "-1792 -1120 72"
+"item" "item_armor_shard"
+"target" "t46"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_soldier_hypergun"
+}
+{
+"target" "t41"
+"targetname" "t40"
+"origin" "-1344 -224 56"
+"classname" "path_corner"
+}
+{
+"target" "t44"
+"targetname" "t43"
+"origin" "-256 -248 56"
+"classname" "path_corner"
+}
+{
+"target" "t43"
+"targetname" "t42"
+"origin" "-256 -800 56"
+"classname" "path_corner"
+}
+{
+"target" "t42"
+"targetname" "t41"
+"origin" "-256 -248 56"
+"classname" "path_corner"
+}
+{
+"target" "t45"
+"targetname" "t44"
+"origin" "-1344 -224 56"
+"classname" "path_corner"
+}
+{
+"wait" "5"
+"target" "t40"
+"targetname" "t39"
+"origin" "-1344 -768 56"
+"classname" "path_corner"
+}
+{
+"target" "t39"
+"targetname" "t38"
+"origin" "-1344 -224 56"
+"classname" "path_corner"
+}
+{
+"target" "t38"
+"targetname" "t37"
+"origin" "-1792 -224 56"
+"classname" "path_corner"
+}
+{
+"targetname" "t46"
+"target" "t37"
+"origin" "-1792 -1088 56"
+"classname" "path_corner"
+}
+{
+"target" "t46"
+"targetname" "t45"
+"origin" "-1792 -224 56"
+"classname" "path_corner"
+}
+{
+"origin" "-2136 -880 64"
+"classname" "item_health"
+"spawnflags" "2048"
+}
+{
+"origin" "-2176 -880 64"
+"spawnflags" "2048"
+"classname" "item_health"
+}
+{
+"origin" "-2240 -448 56"
+"target" "t35"
+"targetname" "t34"
+"classname" "path_corner"
+}
+{
+"origin" "-2368 -448 56"
+"target" "t34"
+"targetname" "t33"
+"classname" "path_corner"
+}
+{
+"origin" "-2240 -448 56"
+"target" "t33"
+"targetname" "t32"
+"classname" "path_corner"
+}
+{
+"origin" "-2240 -168 56"
+"targetname" "t36"
+"target" "t32"
+"classname" "path_corner"
+}
+{
+"origin" "-2240 -768 56"
+"target" "t36"
+"targetname" "t35"
+"classname" "path_corner"
+}
+{
+"item" "ammo_slugs"
+"origin" "-2192 -160 72"
+"target" "t36"
+"spawnflags" "1"
+"angle" "225"
+"classname" "monster_gladiator"
+}
+{
+"origin" "-2280 -792 312"
+"spawnflags" "1"
+"targetname" "t31"
+"classname" "point_combat"
+}
+{
+"origin" "-2224 -792 328"
+"target" "t31"
+"angle" "180"
+"classname" "monster_soldier_lasergun"
+}
+{
+"item" "ammo_cells"
+"origin" "-2424 184 184"
+"target" "t29"
+"spawnflags" "769"
+"angle" "180"
+"classname" "monster_soldier_ripper"
+}
+{
+"item" "ammo_cells"
+"origin" "-2240 208 184"
+"target" "t30"
+"spawnflags" "1"
+"classname" "monster_soldier_hypergun"
+}
+{
+"origin" "-2880 208 168"
+"target" "t27"
+"targetname" "t26"
+"classname" "path_corner"
+}
+{
+"origin" "-2896 16 168"
+"target" "t28"
+"targetname" "t27"
+"classname" "path_corner"
+}
+{
+"origin" "-2880 208 168"
+"target" "t29"
+"targetname" "t28"
+"classname" "path_corner"
+}
+{
+"origin" "-2520 208 168"
+"target" "t26"
+"targetname" "t25"
+"classname" "path_corner"
+}
+{
+"origin" "-2280 208 168"
+"targetname" "t30"
+"target" "t25"
+"classname" "path_corner"
+}
+{
+"origin" "-2520 208 168"
+"target" "t30"
+"targetname" "t29"
+"classname" "path_corner"
+}
+{
+"classname" "monster_gunner"
+"angle" "180"
+"spawnflags" "257"
+"target" "t24"
+"origin" "-2368 -896 72"
+}
+{
+"classname" "path_corner"
+"targetname" "t17"
+"target" "t18"
+"origin" "-2776 -872 72"
+}
+{
+"classname" "path_corner"
+"targetname" "t18"
+"target" "t19"
+"origin" "-2936 -752 136"
+}
+{
+"classname" "path_corner"
+"targetname" "t19"
+"target" "t20"
+"origin" "-2976 -600 184"
+}
+{
+"classname" "path_corner"
+"targetname" "t21"
+"target" "t22"
+"origin" "-2976 -600 184"
+}
+{
+"classname" "path_corner"
+"targetname" "t22"
+"target" "t23"
+"origin" "-2936 -752 136"
+}
+{
+"classname" "path_corner"
+"targetname" "t23"
+"target" "t24"
+"origin" "-2776 -872 72"
+}
+{
+"classname" "path_corner"
+"target" "t17"
+"targetname" "t24"
+"origin" "-2432 -896 56"
+}
+{
+"classname" "path_corner"
+"targetname" "t20"
+"target" "t21"
+"origin" "-2968 -488 216"
+}
+{
+"targetname" "t94"
+"classname" "target_help"
+"message" "Locate entrance to\n the Badlands."
+"origin" "-3232 -280 320"
+}
+{
+"model" "*28"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t16"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"origin" "-3434 -288 280"
+"map" "industry$w_treat"
+"targetname" "t15"
+"classname" "target_changelevel"
+}
+{
+"model" "*29"
+"target" "t15"
+"angle" "180"
+"classname" "trigger_multiple"
+}
+{
+"spawnflags" "1792"
+"classname" "item_bandolier"
+"origin" "-786 -3106 -256"
+}
+{
+"classname" "weapon_machinegun"
+"spawnflags" "1792"
+"origin" "-1545 -97 64"
+}
+{
+"classname" "item_pack"
+"spawnflags" "0"
+"origin" "-1513 -1472 -312"
+}
+{
+"classname" "weapon_shotgun"
+"spawnflags" "1792"
+"origin" "-1122 -1120 0"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "315"
+"origin" "-1561 -1112 72"
+}
+{
+"classname" "item_quad"
+"spawnflags" "1792"
+"origin" "-2144 -1728 -256"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "0"
+"origin" "-1173 -2043 272"
+}
+{
+"classname" "ammo_bullets"
+"origin" "-1235 -1903 0"
+}
+{
+"classname" "ammo_bullets"
+"origin" "-1235 -1951 0"
+}
+{
+"classname" "weapon_chaingun"
+"origin" "-1401 -1920 160"
+}
+{
+"classname" "item_invulnerability"
+"spawnflags" "1792"
+"origin" "-480 -1408 256"
+}
+{
+"model" "*30"
+"classname" "func_wall"
+"spawnflags" "1792"
+}
+{
+"classname" "weapon_grenadelauncher"
+"spawnflags" "1792"
+"origin" "-705 -544 0"
+}
+{
+"classname" "item_silencer"
+"spawnflags" "0"
+"origin" "-433 -1512 64"
+}
+{
+"classname" "info_player_intermission"
+"spawnflags" "1792"
+"angles" "0 145 0"
+"origin" "592 -2200 96"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+"origin" "1792 -240 -80"
+}
+{
+"origin" "1064 -536 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1072 -200 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1088 -896 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1400 -896 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1400 -208 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1560 -544 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1792 -848 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1792 -552 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1792 64 -80"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1976 -544 -152"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2712 -2048 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2432 -2048 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2112 -2048 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2432 -2264 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2432 -2560 -152"
+}
+{
+"origin" "-2048 -3072 32"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2304 -3072 32"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2136 -2832 -152"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2776 -1872 -152"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2432 -2832 -152"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1728 -2832 -152"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1344 -2832 -152"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-936 -2832 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1344 -2832 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1728 -2832 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2136 -2832 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2432 -2832 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2432 -2560 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1976 -544 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1640 -544 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1336 -392 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1336 -648 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "448 -576 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "768 -560 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "1056 -552 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "448 -960 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "456 -1248 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "632 -1392 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "528 -1600 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "520 -1888 -200"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "448 -2312 -200"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "304 -1432 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "200 -1824 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "216 -2096 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "40 -2360 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "112 -2624 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-240 -2544 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-272 -2784 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-648 -3016 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-520 -2664 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-704 -2832 -216"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "2432 -592 -184"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "2240 -552 -184"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+"origin" "1416 -544 -80"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "1640 -544 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "1336 -648 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "1336 -392 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "1056 -552 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "768 -560 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "448 -576 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "448 -960 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "456 -1248 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "632 -1392 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "304 -1432 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "528 -1600 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "200 -1824 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "520 -1888 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "216 -2096 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "448 -2312 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "40 -2360 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "112 -2624 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-272 -2784 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-240 -2544 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-520 -2664 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-648 -3016 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-704 -2832 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-936 -2832 -152"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+"origin" "-1760 -3072 32"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2432 -2264 -280"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2176 -2120 -280"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "464 -2184 296"
+}
+{
+"origin" "240 -2328 296"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-72 -2696 296"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-616 -2848 296"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-232 -2400 296"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "840 -2464 488"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-40 -2072 296"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "192 -1760 296"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "128 -1456 296"
+}
+{
+"origin" "448 -1448 296"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-664 -2472 376"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "128 -1208 296"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-184 -1760 296"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-456 -1760 296"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-736 -1760 208"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-576 -2432 -56"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-912 -2432 -56"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1168 -2360 64"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1328 -2144 104"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1280 -1792 160"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1720 -1792 72"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1856 -2000 -56"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1904 -1672 -56"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1160 -768 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1800 -1432 -8"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1992 -1432 -8"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2200 -1432 -8"
+}
+{
+"origin" "-1608 -1512 -8"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1824 -1512 -8"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2048 -1512 -8"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2224 -1600 -96"
+}
+{
+"origin" "-2144 -1600 -96"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2224 -1728 -168"
+}
+{
+"origin" "-2144 -1728 -168"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1496 -1432 -8"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2224 -1728 -184"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1736 -1512 -8"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1952 -1512 -8"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2144 -1520 -8"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-1832 -1432 -8"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2048 -1432 -8"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2224 -1432 -8"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2224 -1600 -128"
+}
+{
+"origin" "-2144 -1600 -128"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1440 -1512 -8"
+}
+{
+"origin" "-2144 -1728 -184"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-1536 -1512 -8"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2168 -1800 -280"
+}
+{
+"origin" "-2528 -1864 -280"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2752 -1880 -280"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2752 -2120 -280"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2432 -2120 -280"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "2432 -864 -184"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1536 -1432 -8"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2184 -1800 -320"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-2016 -1808 -280"
+}
+{
+"origin" "-2464 -1808 -352"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2192 -1696 -56"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb12"
+"origin" "-1904 -1472 48"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb12"
+"origin" "-2184 -1472 48"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb12"
+"origin" "-2184 -1672 48"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb12"
+"origin" "-1296 -1512 112"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1968 -1840 -344"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2432 -1664 -56"
+}
+{
+"origin" "-1912 -1496 56"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2184 -1504 56"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2432 -1456 -56"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1688 -1496 56"
+}
+{
+"origin" "-2712 -1456 -56"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2712 -1664 -56"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2720 -1224 -56"
+}
+{
+"origin" "-1160 -1624 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-1416 -1624 360"
+}
+{
+"origin" "-1408 -1944 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1528 -1376 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1816 -1456 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-1808 -1800 360"
+}
+{
+"origin" "-2128 -1800 352"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2432 -1800 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-2712 -1800 352"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-2128 -2048 360"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-2432 -2048 360"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-2432 -1456 360"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-2712 -1456 360"
+}
+{
+"origin" "-2128 -1456 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1744 -2040 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2712 -2048 360"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2"
+"origin" "-1152 -1944 360"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-2720 -1248 -48"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb12"
+"origin" "-1200 -1320 112"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb12"
+"origin" "-1456 -1240 112"
+}
+{
+"origin" "-1392 -1472 -296"
+"noise" "world/water1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1384 -1472 -184"
+"noise" "world/amb12"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1656 -1480 48"
+"noise" "world/amb12"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1344 -1368 176"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1344 -1152 176"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "576 -1768 296"
+"noise" "world/wind2"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1"
+"origin" "-1392 -1472 -160"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2600 -1088 -56"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2336 -1088 8"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-2088 -1088 96"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1856 -1088 144"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1792 -936 144"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1792 -768 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-2872 -1112 -48"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-2560 -1088 -48"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-2368 -1088 16"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-2176 -1088 72"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1968 -1088 144"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1792 -1088 144"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1792 -960 144"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1976 -768 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+"origin" "-376 -424 432"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+"origin" "-512 -576 432"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb14"
+"origin" "-736 -664 432"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-640 -704 256"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-992 -1216 112"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-904 -112 176"
+}
+{
+"origin" "-832 -1024 256"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-832 -704 256"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-552 -256 432"
+"noise" "world/amb14"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-536 -656 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-704 -544 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-896 -544 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-992 -640 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-992 -832 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-992 -1024 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-992 -1408 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-384 -1504 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-192 -1504 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-96 -1408 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-96 -1216 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-96 -1024 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-96 -832 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-256 -680 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-256 -488 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-256 -256 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-680 -256 112"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-904 -400 176"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-904 -256 360"
+"noise" "world/fan1"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1160 -112 176"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1344 -768 176"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1352 -512 176"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1344 -256 176"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1792 -256 176"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-1792 -512 176"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-2480 -784 336"
+}
+{
+"origin" "-1528 -1792 72"
+"noise" "world/amb8"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1160 -576 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-640 -1024 256"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1352 -112 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1544 -112 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1736 -112 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1928 -112 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1976 -384 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-1976 -576 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb8"
+"origin" "-1568 -256 176"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-2176 -672 128"
+}
+{
+"origin" "-1344 -960 152"
+"noise" "world/amb7"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2200 80 -56"
+"noise" "world/water1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2512 152 -56"
+"noise" "world/water1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2776 96 -56"
+"noise" "world/water1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2736 -312 -56"
+"noise" "world/water1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2464 -616 -56"
+"noise" "world/water1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2792 -664 -56"
+"noise" "world/water1.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2816 -768 408"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-2296 -440 536"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-2200 -760 536"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb7"
+"origin" "-2176 -288 128"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/water1.wav"
+"origin" "-2408 -192 -56"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-2920 -544 536"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-2880 -328 536"
+}
+{
+"origin" "-2880 208 536"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2920 -40 536"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"origin" "-2184 -72 536"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"origin" "-2288 184 496"
+}
+{
+"origin" "2160 -544 -120"
+"targetname" "t14"
+"message" "You've found a secret."
+"spawnflags" "2048"
+"classname" "target_secret"
+}
+{
+"model" "*31"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"model" "*32"
+"target" "t14"
+"spawnflags" "2048"
+"health" "25"
+"classname" "func_explosive"
+}
+{
+"origin" "1792 -935 -128"
+"classname" "ammo_rockets"
+"spawnflags" "2048"
+}
+{
+"origin" "1728 -935 -128"
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+}
+{
+"origin" "1792 -935 -128"
+"spawnflags" "1792"
+"classname" "weapon_rocketlauncher"
+}
+{
+"origin" "1247 -903 -128"
+"classname" "item_health_small"
+"spawnflags" "0"
+}
+{
+"origin" "1183 -903 -128"
+"classname" "item_health_small"
+"spawnflags" "0"
+}
+{
+"origin" "1119 -903 -128"
+"classname" "item_health_small"
+"spawnflags" "0"
+}
+{
+"origin" "1311 -903 -128"
+"spawnflags" "0"
+"classname" "item_health_small"
+}
+{
+"origin" "-991 -639 0"
+"spawnflags" "0"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-1125 -419 64"
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-2176 -1297 0"
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+}
+{
+"spawnflags" "0"
+"classname" "ammo_cells"
+"origin" "-2466 -1813 -384"
+}
+{
+"origin" "-1120 -1505 0"
+"classname" "item_health_large"
+}
+{
+"classname" "item_health_large"
+"origin" "-1120 -1457 0"
+}
+{
+"model" "*33"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*34"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"model" "*35"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"model" "*36"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "171 -1234 0"
+"classname" "item_health_large"
+"spawnflags" "1792"
+}
+{
+"origin" "171 -1186 0"
+"classname" "item_health_large"
+"spawnflags" "0"
+}
+{
+"origin" "171 -1282 0"
+"classname" "item_health_large"
+"spawnflags" "1792"
+}
+{
+"origin" "-537 -2424 160"
+"classname" "ammo_bullets"
+"spawnflags" "0"
+}
+{
+"origin" "-538 -2462 160"
+"classname" "weapon_machinegun"
+"spawnflags" "1792"
+}
+{
+"model" "*37"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"spawnflags" "0"
+"classname" "item_health_large"
+"origin" "-1760 -3240 80"
+}
+{
+"model" "*38"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "-2718 -313 -80"
+"spawnflags" "0"
+"classname" "item_armor_jacket"
+}
+{
+"origin" "-2217 -15 16"
+"spawnflags" "0"
+"classname" "item_health_small"
+}
+{
+"origin" "-2185 -15 16"
+"spawnflags" "0"
+"classname" "item_health_small"
+}
+{
+"origin" "-2153 -15 16"
+"spawnflags" "0"
+"classname" "item_health_small"
+}
+{
+"origin" "-2249 -15 16"
+"spawnflags" "0"
+"classname" "item_health_small"
+}
+{
+"origin" "-1120 -1553 0"
+"classname" "item_health_large"
+}
+{
+"angle" "90"
+"origin" "-992 -1445 8"
+"classname" "info_player_deathmatch"
+}
+{
+"targetname" "t13"
+"origin" "830 -2642 264"
+"spawnflags" "1792"
+"angle" "90"
+"classname" "misc_teleporter_dest"
+}
+{
+"target" "t13"
+"origin" "-2321 -3200 88"
+"spawnflags" "1792"
+"classname" "misc_teleporter"
+}
+{
+"origin" "-872 -1946 0"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-992 -832 0"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-1154 -1904 136"
+"classname" "item_armor_combat"
+}
+{
+"model" "*39"
+"_minlight" ".1"
+"team" "xitdoor"
+"angle" "0"
+"classname" "func_door"
+"spawnflags" "8"
+}
+{
+"model" "*40"
+"_minlight" ".1"
+"team" "xitdoor"
+"angle" "180"
+"classname" "func_door"
+"spawnflags" "8"
+"target" "t89"
+}
+{
+"origin" "-1655 -863 64"
+"spawnflags" "0"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-1695 -863 64"
+"spawnflags" "1792"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-2100 -575 64"
+"spawnflags" "1792"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-2086 -428 64"
+"spawnflags" "1792"
+"classname" "ammo_shells"
+}
+{
+"origin" "-2086 -340 64"
+"spawnflags" "1792"
+"classname" "ammo_shells"
+}
+{
+"origin" "-2086 -384 64"
+"spawnflags" "1792"
+"classname" "ammo_shells"
+}
+{
+"origin" "-2746 -1059 -128"
+"classname" "ammo_slugs"
+"spawnflags" "1792"
+}
+{
+"origin" "-2021 -1624 -128"
+"classname" "item_health_large"
+"spawnflags" "0"
+}
+{
+"origin" "-2069 -1624 -128"
+"classname" "item_health_large"
+"spawnflags" "0"
+}
+{
+"origin" "-1672 -2082 136"
+"classname" "item_health_mega"
+"spawnflags" "0"
+}
+{
+"origin" "-1945 -1882 -384"
+"classname" "weapon_hyperblaster"
+"spawnflags" "0"
+}
+{
+"origin" "-2586 -1813 -384"
+"classname" "ammo_cells"
+"spawnflags" "0"
+}
+{
+"origin" "-2338 -1813 -384"
+"classname" "ammo_cells"
+"spawnflags" "0"
+}
+{
+"origin" "-1144 -2084 272"
+"spawnflags" "0"
+"classname" "item_power_shield"
+}
+{
+"origin" "-1631 -1312 0"
+"classname" "item_breather"
+"spawnflags" "0"
+}
+{
+"spawnflags" "0"
+"origin" "-644 -2463 288"
+"classname" "item_armor_jacket"
+}
+{
+"origin" "86 -1761 -256"
+"spawnflags" "1792"
+"classname" "item_adrenaline"
+}
+{
+"spawnflags" "1792"
+"origin" "-1473 -638 64"
+"classname" "item_health_small"
+}
+{
+"spawnflags" "1792"
+"origin" "-1473 -682 64"
+"classname" "item_health_small"
+}
+{
+"spawnflags" "1792"
+"origin" "-1473 -594 64"
+"classname" "item_health_small"
+}
+{
+"origin" "-1498 -1303 0"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1546 -1303 0"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1450 -1303 0"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-1800 -3240 80"
+"classname" "item_health_large"
+"spawnflags" "0"
+}
+{
+"origin" "-1808 -3032 -128"
+"classname" "ammo_bullets"
+"spawnflags" "1792"
+}
+{
+"spawnflags" "1792"
+"origin" "-1752 -3032 -128"
+"classname" "ammo_bullets"
+}
+{
+"origin" "-1864 -3032 -128"
+"classname" "ammo_bullets"
+"spawnflags" "1792"
+}
+{
+"origin" "-1808 -3080 -128"
+"spawnflags" "1792"
+"classname" "weapon_chaingun"
+}
+{
+"spawnflags" "0"
+"origin" "-1808 -2112 -128"
+"classname" "ammo_shells"
+}
+{
+"spawnflags" "0"
+"origin" "-1808 -2064 -128"
+"classname" "ammo_shells"
+}
+{
+"origin" "-1832 -2176 -128"
+"spawnflags" "1792"
+"classname" "weapon_supershotgun"
+}
+{
+"origin" "-2712 -1312 272"
+"classname" "ammo_rockets"
+"spawnflags" "1792"
+}
+{
+"origin" "-2656 -1312 272"
+"spawnflags" "1792"
+"classname" "ammo_rockets"
+}
+{
+"origin" "-2832 -1328 272"
+"spawnflags" "1792"
+"classname" "weapon_rocketlauncher"
+}
+{
+"spawnflags" "1792"
+"origin" "-1648 -416 64"
+"classname" "ammo_grenades"
+}
+{
+"spawnflags" "1792"
+"origin" "-1648 -360 64"
+"classname" "ammo_grenades"
+}
+{
+"origin" "-864 -96 64"
+"classname" "item_armor_shard"
+"spawnflags" "0"
+}
+{
+"origin" "-904 -96 64"
+"classname" "item_armor_shard"
+"spawnflags" "0"
+}
+{
+"origin" "-944 -96 64"
+"classname" "item_armor_shard"
+"spawnflags" "0"
+}
+{
+"origin" "-904 -416 64"
+"spawnflags" "0"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-944 -416 64"
+"spawnflags" "0"
+"classname" "item_armor_shard"
+}
+{
+"origin" "-864 -416 64"
+"spawnflags" "0"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "1792"
+"origin" "-104 -1380 64"
+"classname" "item_health_large"
+}
+{
+"spawnflags" "1792"
+"origin" "-104 -1436 64"
+"classname" "item_health_large"
+}
+{
+"origin" "-672 -1944 0"
+"classname" "ammo_shells"
+"spawnflags" "0"
+}
+{
+"origin" "-712 -1944 0"
+"spawnflags" "0"
+"classname" "ammo_shells"
+}
+{
+"origin" "96 -1200 0"
+"spawnflags" "1792"
+"classname" "weapon_shotgun"
+}
+{
+"origin" "-848 -2536 160"
+"spawnflags" "1792"
+"classname" "item_health_large"
+}
+{
+"origin" "-800 -2536 160"
+"spawnflags" "0"
+"classname" "item_health_large"
+}
+{
+"spawnflags" "0"
+"origin" "1312 -200 -128"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "0"
+"origin" "1248 -200 -128"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "0"
+"origin" "1184 -200 -128"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "0"
+"origin" "1120 -200 -128"
+"classname" "item_armor_shard"
+}
+{
+"spawnflags" "1792"
+"origin" "960 -2408 256"
+"classname" "ammo_slugs"
+}
+{
+"spawnflags" "1792"
+"origin" "760 -2400 256"
+"classname" "weapon_railgun"
+}
+{
+"origin" "-2144 -880 72"
+"angle" "90"
+"classname" "info_player_deathmatch"
+}
+{
+"model" "*41"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "1000 -888 -120"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-2288 -3072 -120"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-192 -1504 72"
+"angle" "90"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "40 -1144 8"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-832 -2280 168"
+"angle" "0"
+"classname" "info_player_deathmatch"
+}
+{
+"model" "*42"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"origin" "-2336 -1304 -120"
+"angle" "225"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-1952 -864 72"
+"angle" "90"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "-2152 -840 320"
+"classname" "ammo_cells"
+}
+{
+"origin" "-2152 -880 320"
+"classname" "ammo_cells"
+}
+{
+"origin" "-2936 352 184"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"model" "*43"
+"target" "t3"
+"spawnflags" "1792"
+"angle" "90"
+"classname" "func_button"
+}
+{
+"model" "*44"
+"spawnflags" "2048"
+"classname" "func_wall"
+}
+{
+"model" "*45"
+"spawnflags" "2048"
+"classname" "func_button"
+"angle" "180"
+"target" "t10"
+"message" "Secondary flood gate opened."
+}
+{
+"model" "*46"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "-1"
+"wait" "-1"
+"targetname" "t7"
+}
+{
+"origin" "-2304 -3056 152"
+"_color" "1.000000 0.673228 0.188976"
+"light" "75"
+"classname" "light"
+}
+{
+"_color" "1.000000 0.603922 0.368627"
+"light" "75"
+"classname" "light"
+"origin" "-1792 -3072 -80"
+}
+{
+"_color" "1.000000 0.673228 0.188976"
+"light" "75"
+"classname" "light"
+"origin" "-1792 -3056 152"
+}
+{
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.673228 0.188976"
+"origin" "-1984 -3056 152"
+}
+{
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.673228 0.188976"
+"origin" "-2112 -3056 152"
+}
+{
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.673228 0.188976"
+"origin" "-2240 -3056 152"
+}
+{
+"classname" "light"
+"light" "50"
+"_color" "1.000000 0.673228 0.188976"
+"origin" "-2352 -3200 112"
+}
+{
+"origin" "-2176 -3056 152"
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.673228 0.188976"
+}
+{
+"origin" "-2048 -3056 152"
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.673228 0.188976"
+}
+{
+"origin" "-1920 -3056 152"
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.673228 0.188976"
+}
+{
+"origin" "-1792 -3072 -24"
+"classname" "light"
+"light" "75"
+"_color" "1.000000 0.603922 0.368627"
+}
+{
+"origin" "-1856 -3056 152"
+"_color" "1.000000 0.673228 0.188976"
+"light" "75"
+"classname" "light"
+}
+{
+"model" "*47"
+"spawnflags" "2048"
+"targetname" "t7"
+"classname" "func_door"
+"angle" "-1"
+"wait" "-1"
+}
+{
+"model" "*48"
+"spawnflags" "2048"
+"target" "t9"
+"classname" "trigger_once"
+}
+{
+"spawnflags" "2048"
+"origin" "-1352 -1560 -200"
+"message" "You've found a secret area."
+"targetname" "t9"
+"classname" "target_secret"
+}
+{
+"model" "*49"
+"spawnflags" "2048"
+"classname" "func_button"
+"target" "t8"
+"angle" "90"
+}
+{
+"model" "*50"
+"message" "Access Denied."
+"wait" "-1"
+"spawnflags" "2048"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "t8"
+}
+{
+"classname" "light"
+"light" "100"
+"_color" "1.000000 0.870079 0.783465"
+"origin" "-1216 -1344 248"
+}
+{
+"_color" "0.768627 1.000000 1.000000"
+"classname" "light"
+"light" "50"
+"origin" "-1552 -1512 -16"
+}
+{
+"_color" "0.768627 1.000000 1.000000"
+"classname" "light"
+"light" "50"
+"origin" "-1552 -1432 -16"
+}
+{
+"_color" "0.768627 1.000000 1.000000"
+"classname" "light"
+"light" "50"
+"origin" "-1648 -1512 -16"
+}
+{
+"_color" "0.768627 1.000000 1.000000"
+"classname" "light"
+"light" "50"
+"origin" "-1648 -1432 -16"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*51"
+"spawnflags" "2048"
+"health" "25"
+"classname" "func_explosive"
+}
+{
+"classname" "func_group"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-1760 -1432 -16"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-1888 -1432 -16"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2016 -1432 -16"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2144 -1432 -16"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2224 -1432 -16"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2224 -1512 -16"
+}
+{
+"origin" "-2144 -1728 -232"
+"light" "70"
+"classname" "light"
+"_color" "0.482353 0.741176 1.000000"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2144 -1728 -128"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2144 -1600 -128"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2144 -1600 -16"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2144 -1512 -16"
+}
+{
+"origin" "-2224 -1728 -128"
+"light" "70"
+"classname" "light"
+"_color" "0.482353 0.741176 1.000000"
+}
+{
+"origin" "-2224 -1600 -128"
+"light" "70"
+"classname" "light"
+"_color" "0.482353 0.741176 1.000000"
+}
+{
+"origin" "-2224 -1600 -16"
+"light" "70"
+"classname" "light"
+"_color" "0.482353 0.741176 1.000000"
+}
+{
+"origin" "-1440 -1432 -88"
+"light" "50"
+"classname" "light"
+"_color" "0.768627 1.000000 1.000000"
+}
+{
+"origin" "-2016 -1512 -16"
+"light" "70"
+"classname" "light"
+"_color" "0.482353 0.741176 1.000000"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-1760 -1512 -16"
+}
+{
+"origin" "-1888 -1512 -16"
+"light" "70"
+"classname" "light"
+"_color" "0.482353 0.741176 1.000000"
+}
+{
+"origin" "-1440 -1512 -88"
+"light" "50"
+"classname" "light"
+"_color" "0.768627 1.000000 1.000000"
+}
+{
+"model" "*52"
+"spawnflags" "2048"
+"target" "t7"
+"wait" "-1"
+"angle" "0"
+"classname" "func_button"
+"message" "Primary flood gates opened."
+}
+{
+"model" "*53"
+"spawnflags" "2048"
+"wait" "-1"
+"angle" "-1"
+"classname" "func_door"
+"targetname" "t10"
+}
+{
+"classname" "func_group"
+}
+{
+"_color" "0.482353 0.741176 1.000000"
+"classname" "light"
+"light" "70"
+"origin" "-2224 -1728 -232"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*54"
+"spawnflags" "8"
+"team" "sliders1"
+"angle" "270"
+"classname" "func_door"
+}
+{
+"spawnflags" "2048"
+"origin" "-736 -720 336"
+"message" "You've found a secret area."
+"targetname" "t5"
+"classname" "target_secret"
+}
+{
+"model" "*55"
+"spawnflags" "2048"
+"target" "t5"
+"classname" "trigger_once"
+}
+{
+"model" "*56"
+"spawnflags" "8"
+"team" "sliders1"
+"lip" "64"
+"angle" "90"
+"classname" "func_door"
+}
+{
+"model" "*57"
+"targetname" "t4"
+"speed" "50"
+"lip" "4"
+"wait" "-1"
+"angle" "-2"
+"classname" "func_door"
+}
+{
+"model" "*58"
+"targetname" "t4"
+"wait" "-1"
+"lip" "-2"
+"angle" "180"
+"classname" "func_door"
+}
+{
+"origin" "-672 -600 24"
+"target" "t4"
+"targetname" "t3"
+"classname" "trigger_relay"
+}
+{
+"model" "*59"
+"target" "t11"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"message" "This button is not functioning."
+}
+{
+"spawnflags" "2048"
+"classname" "light"
+"light" "60"
+"style" "4"
+"origin" "-616 -576 56"
+}
+{
+"model" "*60"
+"spawnflags" "8"
+"angle" "-1"
+"team" "bigdoor1"
+"speed" "25"
+"lip" "-112"
+"classname" "func_door"
+}
+{
+"model" "*61"
+"spawnflags" "8"
+"team" "bigdoor1"
+"lip" "-52"
+"speed" "25"
+"angle" "-1"
+"classname" "func_door"
+}
+{
+"model" "*62"
+"spawnflags" "8"
+"team" "bigdoor1"
+"speed" "25"
+"angle" "-1"
+"classname" "func_door"
+}
+{
+"classname" "item_armor_body"
+"origin" "-1064 -256 320"
+}
+{
+"classname" "light"
+"light" "200"
+"_color" "1.000000 0.796078 0.325490"
+"origin" "-904 -252 392"
+}
+{
+"model" "*63"
+"origin" "-904 -252 560"
+"speed" "200"
+"_minlight" ".25"
+"spawnflags" "1"
+"classname" "func_rotating"
+}
+{
+"classname" "func_group"
+}
+{
+"model" "*64"
+"classname" "func_door"
+"angle" "-1"
+"_minlight" ".1"
+}
+{
+"classname" "weapon_boomer"
+"origin" "-2200 -832 320"
+}
+{
+"spawnflags" "2048"
+"classname" "target_secret"
+"targetname" "t1"
+"message" "You've discovered a secret area."
+"origin" "-2264 -680 448"
+}
+{
+"model" "*65"
+"spawnflags" "2048"
+"classname" "trigger_once"
+"target" "t1"
+}
+{
+"targetname" "industry"
+"origin" "-3216 -320 248"
+"angle" "0"
+"classname" "info_player_start"
+}
+{
+"model" "*66"
+"team" "baddoor2"
+"classname" "func_door"
+"angle" "270"
+"target" "t277"
+}
+{
+"model" "*67"
+"team" "baddoor2"
+"classname" "func_door"
+"angle" "90"
+}
+{
+"origin" "-3416 -320 256"
+"_color" "1.000000 0.586614 0.338583"
+"light" "125"
+"classname" "light"
+}
+{
+"origin" "-3256 -320 256"
+"_color" "1.000000 0.586614 0.338583"
+"light" "125"
+"classname" "light"
+} \ No newline at end of file
diff --git a/xatrix/stuff/mapfixes/xintell.ent b/xatrix/stuff/mapfixes/xintell.ent
new file mode 100644
index 0000000..99e2ed9
--- /dev/null
+++ b/xatrix/stuff/mapfixes/xintell.ent
@@ -0,0 +1,2482 @@
+// FIXED ENTITY STRING (by BjossiAlfreds)
+//
+// 1. Made the item_invulnerability (18) by the exit into a counted secret
+//
+// Given that its presence can be discovered visually and is easy to get
+// with a simple rocket jump, it can be made into a proper secret. So I
+// added a target_secret (2476) that the item links to.
+{
+"nextmap" "industry"
+"message" "Intelligence Center"
+"sky" "x1u2"
+"classname" "worldspawn"
+"sounds" "9"
+}
+{
+"model" "*1"
+"classname" "func_wall"
+}
+{
+"origin" "304 1096 304"
+"classname" "item_invulnerability"
+"target" "invulsec"
+}
+{
+"origin" "768 -480 488"
+"angle" "180"
+"classname" "info_player_deathmatch"
+}
+{
+"attenuation" "-1"
+"classname" "target_speaker"
+"noise" "world/datafiles.wav"
+"spawnflags" "2052"
+"targetname" "t56"
+"origin" "1576 -512 256"
+}
+{
+"classname" "trigger_relay"
+"targetname" "t3"
+"target" "t56"
+"spawnflags" "2048"
+"wait" "0.5"
+"origin" "1560 -512 256"
+}
+{
+"classname" "ammo_trap"
+"spawnflags" "1792"
+"origin" "880 -480 216"
+}
+{
+"classname" "ammo_rockets"
+"origin" "1392 -464 480"
+"angle" "180"
+"spawnflags" "3584"
+}
+{
+"angle" "270"
+"spawnflags" "1792"
+"classname" "ammo_cells"
+"origin" "1632 -1304 272"
+}
+{
+"spawnflags" "1792"
+"classname" "weapon_hyperblaster"
+"origin" "1632 -1344 272"
+}
+{
+"classname" "ammo_cells"
+"spawnflags" "1792"
+"angle" "180"
+"origin" "1672 -1344 272"
+}
+{
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+"origin" "1840 -976 272"
+}
+{
+"classname" "ammo_grenades"
+"spawnflags" "1792"
+"origin" "1840 -944 272"
+}
+{
+"classname" "weapon_grenadelauncher"
+"spawnflags" "1792"
+"origin" "1872 -960 272"
+}
+{
+"origin" "248 672 304"
+"classname" "weapon_grenadelauncher"
+}
+{
+"origin" "264 632 304"
+"classname" "ammo_grenades"
+}
+{
+"origin" "296 632 304"
+"classname" "ammo_grenades"
+}
+{
+"origin" "1320 -480 288"
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.921569 0.654902"
+}
+{
+"classname" "target_goal"
+"targetname" "t42"
+"origin" "1592 -1384 584"
+}
+{
+"origin" "704 992 512"
+"map" "xu2.cin+*industry$xintell"
+"targetname" "t55"
+"classname" "target_changelevel"
+}
+{
+"model" "*2"
+"spawnflags" "0"
+"target" "t55"
+"classname" "trigger_multiple"
+}
+{
+"classname" "info_player_intermission"
+"angles" "-20 315 0"
+"origin" "2192 -816 280"
+}
+{
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+"origin" "2352 -1584 272"
+}
+{
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+"origin" "2352 -1552 272"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "1792"
+"origin" "2352 -1520 272"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "1792"
+"origin" "2352 -1488 272"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "1792"
+"origin" "2064 -1840 272"
+}
+{
+"classname" "item_health_small"
+"spawnflags" "1792"
+"origin" "2096 -1840 272"
+}
+{
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+"origin" "2032 -1840 272"
+}
+{
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+"origin" "2000 -1840 272"
+}
+{
+"classname" "item_armor_shard"
+"origin" "608 -656 64"
+}
+{
+"classname" "item_armor_shard"
+"origin" "608 -624 64"
+}
+{
+"classname" "item_armor_shard"
+"origin" "608 -592 64"
+}
+{
+"classname" "item_armor_shard"
+"origin" "608 -560 64"
+}
+{
+"classname" "info_player_deathmatch"
+"angle" "45"
+"origin" "416 -1216 72"
+}
+{
+"classname" "target_help"
+"targetname" "t54"
+"spawnflags" "2049"
+"message" "Use Data CD to discover\nthe location of the Strogg\ncounter strike fleet."
+"origin" "432 -2256 88"
+}
+{
+"classname" "target_help"
+"targetname" "t54"
+"message" "Locate the Main\nIntelligence computer\nprocessing core."
+"spawnflags" "2048"
+"origin" "464 -2256 88"
+}
+{
+"model" "*3"
+"classname" "trigger_once"
+"target" "t54"
+"spawnflags" "2048"
+}
+{
+"classname" "weapon_supershotgun"
+"spawnflags" "1792"
+"origin" "896 -1064 64"
+}
+{
+"origin" "352 -32 400"
+"target" "t53"
+"spawnflags" "1"
+"classname" "monster_tank"
+"item" "ammo_rockets"
+}
+{
+"origin" "416 -480 424"
+"target" "t51"
+"targetname" "t50"
+"classname" "path_corner"
+}
+{
+"origin" "416 -480 440"
+"target" "t53"
+"targetname" "t52"
+"classname" "path_corner"
+}
+{
+"targetname" "t53"
+"target" "t50"
+"origin" "416 -32 408"
+"classname" "path_corner"
+}
+{
+"target" "t52"
+"targetname" "t51"
+"origin" "416 -1168 392"
+"classname" "path_corner"
+}
+{
+"origin" "864 336 440"
+"item" "ammo_cells"
+"spawnflags" "2"
+"targetname" "t49"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+}
+{
+"origin" "192 368 408"
+"item" "item_armor_shard"
+"spawnflags" "1"
+"target" "t49"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+}
+{
+"origin" "928 336 440"
+"spawnflags" "258"
+"targetname" "t49"
+"angle" "270"
+"classname" "monster_soldier_ripper"
+}
+{
+"item" "ammo_cells"
+"origin" "128 368 408"
+"spawnflags" "1"
+"angle" "270"
+"classname" "monster_soldier_ripper"
+}
+{
+"model" "*4"
+"target" "t48"
+"classname" "trigger_once"
+}
+{
+"origin" "728 -272 488"
+"targetname" "t48"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+"spawnflags" "257"
+}
+{
+"origin" "728 -688 488"
+"targetname" "t48"
+"spawnflags" "1"
+"angle" "90"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "1560 -528 256"
+"target" "t47"
+"targetname" "t3"
+"classname" "trigger_relay"
+"spawnflags" "2048"
+}
+{
+"model" "*5"
+"targetname" "t47"
+"spawnflags" "2055"
+"classname" "func_wall"
+}
+{
+"origin" "1064 -480 560"
+"classname" "item_enviro"
+}
+{
+"origin" "1096 -480 584"
+"targetname" "t47"
+"spawnflags" "2"
+"angle" "0"
+"classname" "monster_floater"
+}
+{
+"origin" "1528 -520 248"
+"classname" "monster_soldier_ripper"
+"spawnflags" "769"
+"angle" "180"
+}
+{
+"origin" "1528 -448 248"
+"item" "ammo_cells"
+"angle" "180"
+"classname" "monster_soldier_ripper"
+"spawnflags" "1"
+}
+{
+"origin" "1592 -480 248"
+"item" "item_armor_shard"
+"angle" "0"
+"spawnflags" "1"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "1280 -856 480"
+"target" "t46"
+"item" "ammo_rockets"
+"spawnflags" "1"
+"classname" "monster_tank"
+}
+{
+"origin" "1280 -768 504"
+"targetname" "t46"
+"target" "t45"
+"classname" "path_corner"
+}
+{
+"origin" "1280 -192 504"
+"target" "t46"
+"targetname" "t45"
+"classname" "path_corner"
+}
+{
+"origin" "1536 -480 240"
+"classname" "weapon_railgun"
+"spawnflags" "1792"
+}
+{
+"origin" "1488 -408 240"
+"spawnflags" "1792"
+"classname" "ammo_slugs"
+}
+{
+"origin" "1392 -480 488"
+"angle" "180"
+"classname" "info_player_deathmatch"
+}
+{
+"classname" "ammo_cells"
+"origin" "1816 -1160 272"
+"angle" "0"
+}
+{
+"model" "*6"
+"target" "t44"
+"spawnflags" "2816"
+"classname" "trigger_once"
+}
+{
+"origin" "2432 -1344 672"
+"targetname" "t44"
+"angle" "180"
+"spawnflags" "769"
+"classname" "monster_floater"
+}
+{
+"origin" "2496 -1344 672"
+"targetname" "t44"
+"angle" "0"
+"spawnflags" "769"
+"classname" "monster_floater"
+}
+{
+"origin" "2296 -640 392"
+"target" "t43"
+"spawnflags" "1792"
+"classname" "trigger_always"
+}
+{
+"origin" "1592 -1336 584"
+"spawnflags" "2048"
+"target" "t43"
+"targetname" "t42"
+"classname" "trigger_relay"
+}
+{
+"origin" "1592 -1360 584"
+"spawnflags" "2048"
+"target" "t40"
+"targetname" "t42"
+"classname" "trigger_relay"
+}
+{
+"model" "*7"
+"target" "t42"
+"count" "2"
+"targetname" "t41"
+"spawnflags" "2049"
+"classname" "trigger_counter"
+}
+{
+"origin" "1168 -1800 376"
+"spawnflags" "1792"
+"target" "t40"
+"classname" "trigger_always"
+}
+{
+"model" "*8"
+"target" "reddr2"
+"classname" "trigger_multiple"
+}
+{
+"model" "*9"
+"targetname" "t40"
+"target" "reddr2"
+"spawnflags" "4"
+"classname" "trigger_multiple"
+}
+{
+"model" "*10"
+"target" "reddr1"
+"classname" "trigger_multiple"
+}
+{
+"model" "*11"
+"targetname" "t43"
+"target" "reddr1"
+"spawnflags" "4"
+"classname" "trigger_multiple"
+}
+{
+"origin" "2328 -1792 256"
+"angle" "90"
+"spawnflags" "2056"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "2408 -1896 272"
+"classname" "ammo_grenades"
+"spawnflags" "2304"
+}
+{
+"origin" "2408 -1840 272"
+"spawnflags" "2048"
+"classname" "ammo_shells"
+}
+{
+"origin" "2408 -1896 304"
+"spawnflags" "3584"
+"classname" "item_armor_jacket"
+}
+{
+"origin" "2352 -1896 272"
+"spawnflags" "2048"
+"classname" "ammo_shells"
+}
+{
+"origin" "2768 -1424 280"
+"angle" "135"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "1936 -2256 280"
+"angle" "135"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "1304 -2248 272"
+"classname" "item_health"
+}
+{
+"origin" "1336 -2280 272"
+"classname" "item_health"
+}
+{
+"origin" "1680 -2320 272"
+"classname" "ammo_bullets"
+}
+{
+"origin" "1584 -2320 272"
+"classname" "ammo_bullets"
+}
+{
+"origin" "1632 -2304 272"
+"classname" "weapon_chaingun"
+}
+{
+"origin" "1304 -1304 272"
+"classname" "ammo_rockets"
+"angle" "270"
+}
+{
+"origin" "1344 -1304 272"
+"classname" "ammo_rockets"
+"angle" "270"
+}
+{
+"target" "t41"
+"classname" "monster_boss5"
+"angle" "0"
+"origin" "1984 -1792 256"
+"spawnflags" "1"
+"targetname" "t44"
+}
+{
+"origin" "2792 -832 272"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2792 -792 272"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2752 -792 272"
+"spawnflags" "1792"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2832 -1152 272"
+"spawnflags" "1792"
+"classname" "ammo_magslug"
+}
+{
+"origin" "2832 -1088 272"
+"spawnflags" "1792"
+"classname" "ammo_magslug"
+}
+{
+"origin" "2856 -1160 272"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2856 -1192 272"
+"spawnflags" "2048"
+"classname" "item_armor_shard"
+}
+{
+"origin" "2856 -1128 272"
+"classname" "item_armor_shard"
+"spawnflags" "2048"
+}
+{
+"origin" "2856 -1088 272"
+"classname" "item_health_large"
+"spawnflags" "3584"
+}
+{
+"origin" "2856 -1048 272"
+"spawnflags" "2048"
+"classname" "item_health_large"
+}
+{
+"spawnflags" "1792"
+"origin" "2800 -1120 272"
+"classname" "weapon_phalanx"
+}
+{
+"spawnflags" "1536"
+"origin" "1816 -1120 272"
+"classname" "ammo_cells"
+"angle" "0"
+}
+{
+"origin" "1856 -792 272"
+"classname" "item_health_small"
+}
+{
+"origin" "1816 -832 272"
+"classname" "item_health_small"
+}
+{
+"origin" "1816 -792 272"
+"classname" "item_health_small"
+}
+{
+"target" "t41"
+"origin" "1552 -1384 256"
+"angle" "270"
+"classname" "monster_boss5"
+}
+{
+"origin" "2272 224 416"
+"spawnflags" "1"
+"target" "t39"
+"classname" "monster_tank"
+"item" "ammo_rockets"
+}
+{
+"origin" "1824 -160 352"
+"target" "t38"
+"targetname" "t37"
+"classname" "path_corner"
+}
+{
+"origin" "1824 160 424"
+"target" "t39"
+"targetname" "t38"
+"classname" "path_corner"
+}
+{
+"origin" "1824 160 408"
+"target" "t35"
+"targetname" "t34"
+"classname" "path_corner"
+}
+{
+"origin" "1824 -160 336"
+"target" "t36"
+"targetname" "t35"
+"classname" "path_corner"
+}
+{
+"origin" "2272 160 408"
+"targetname" "t39"
+"target" "t34"
+"classname" "path_corner"
+}
+{
+"origin" "2272 -160 304"
+"target" "t37"
+"targetname" "t36"
+"classname" "path_corner"
+}
+{
+"origin" "896 480 456"
+"target" "t33"
+"targetname" "t32"
+"classname" "path_corner"
+}
+{
+"origin" "896 480 440"
+"target" "t31"
+"targetname" "t30"
+"classname" "path_corner"
+}
+{
+"origin" "2272 480 456"
+"target" "t32"
+"targetname" "t31"
+"classname" "path_corner"
+}
+{
+"origin" "888 232 440"
+"targetname" "t33"
+"target" "t30"
+"classname" "path_corner"
+}
+{
+"origin" "2080 480 432"
+"target" "t30"
+"spawnflags" "1"
+"classname" "monster_tank"
+"item" "ammo_rockets"
+}
+{
+"origin" "920 -928 472"
+"targetname" "t29"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"angle" "90"
+"origin" "920 -960 488"
+"spawnflags" "1"
+"target" "t29"
+"classname" "monster_soldier_ripper"
+}
+{
+"angle" "90"
+"origin" "1160 -960 488"
+"spawnflags" "1"
+"classname" "monster_soldier_ripper"
+"item" "ammo_cells"
+}
+{
+"model" "*12"
+"target" "t28"
+"spawnflags" "2048"
+"classname" "trigger_once"
+}
+{
+"origin" "1096 -8 472"
+"targetname" "t27"
+"spawnflags" "257"
+"classname" "point_combat"
+}
+{
+"origin" "1152 0 488"
+"angle" "225"
+"spawnflags" "257"
+"target" "t27"
+"classname" "monster_soldier_ripper"
+}
+{
+"angle" "0"
+"origin" "848 24 488"
+"item" "ammo_cells"
+"targetname" "t28"
+"spawnflags" "1"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "1560 136 504"
+"spawnflags" "1536"
+"classname" "item_armor_body"
+}
+{
+"angle" "0"
+"spawnflags" "2"
+"targetname" "t21"
+"classname" "monster_floater"
+"origin" "1384 348 520"
+}
+{
+"classname" "item_health_large"
+"origin" "1336 348 480"
+}
+{
+"origin" "920 -128 224"
+"spawnflags" "2048"
+"classname" "ammo_grenades"
+}
+{
+"origin" "880 -128 208"
+"angle" "270"
+"spawnflags" "1"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "920 -168 208"
+"spawnflags" "4"
+"angle" "90"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "960 -64 232"
+"angle" "270"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "1240 -56 224"
+"classname" "item_health"
+}
+{
+"origin" "1280 -56 224"
+"classname" "item_health"
+}
+{
+"origin" "904 -704 216"
+"spawnflags" "1"
+"targetname" "t25"
+"classname" "point_combat"
+}
+{
+"origin" "904 -776 216"
+"spawnflags" "1"
+"targetname" "t26"
+"classname" "point_combat"
+}
+{
+"item" "item_armor_shard"
+"origin" "920 -704 232"
+"target" "t25"
+"classname" "monster_soldier_ripper"
+"angle" "180"
+"spawnflags" "256"
+}
+{
+"item" "item_armor_shard"
+"origin" "920 -776 232"
+"spawnflags" "768"
+"target" "t26"
+"classname" "monster_soldier_ripper"
+"angle" "180"
+}
+{
+"origin" "432 -1208 56"
+"spawnflags" "1"
+"targetname" "t24"
+"classname" "point_combat"
+}
+{
+"origin" "424 -856 72"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+"spawnflags" "1"
+}
+{
+"origin" "608 -904 72"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+"spawnflags" "257"
+}
+{
+"target" "t24"
+"origin" "408 -1208 72"
+"spawnflags" "1"
+"angle" "0"
+"classname" "monster_soldier_ripper"
+}
+{
+"origin" "448 -2128 48"
+"angle" "90"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "608 -744 56"
+"targetname" "t23"
+"spawnflags" "1"
+"classname" "point_combat"
+}
+{
+"origin" "608 -720 72"
+"target" "t23"
+"spawnflags" "1"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+}
+{
+"origin" "416 -608 72"
+"spawnflags" "1"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+}
+{
+"origin" "584 -1984 64"
+"classname" "weapon_machinegun"
+}
+{
+"origin" "632 -1984 48"
+"angle" "135"
+"spawnflags" "8"
+"classname" "misc_deadsoldier"
+}
+{
+"origin" "552 -2024 64"
+"classname" "ammo_bullets"
+}
+{
+"origin" "296 -2024 64"
+"classname" "ammo_bullets"
+}
+{
+"origin" "344 -2024 64"
+"classname" "ammo_bullets"
+}
+{
+"origin" "528 -1456 64"
+"classname" "ammo_rockets"
+}
+{
+"origin" "528 -1416 64"
+"classname" "ammo_rockets"
+}
+{
+"origin" "904 -1952 64"
+"classname" "item_health"
+}
+{
+"origin" "904 -2000 64"
+"classname" "item_health"
+}
+{
+"origin" "448 -1440 64"
+"spawnflags" "1792"
+"classname" "weapon_rocketlauncher"
+}
+{
+"model" "*13"
+"classname" "func_wall"
+"spawnflags" "1792"
+}
+{
+"origin" "424 -1360 64"
+"classname" "ammo_shells"
+"spawnflags" "2048"
+}
+{
+"origin" "472 -1360 64"
+"classname" "ammo_shells"
+"spawnflags" "2048"
+}
+{
+"origin" "360 -1528 72"
+"classname" "monster_soldier_ripper"
+"angle" "180"
+"spawnflags" "1"
+}
+{
+"origin" "288 -1364 72"
+"classname" "monster_soldier_ripper"
+"angle" "270"
+"spawnflags" "1"
+}
+{
+"origin" "400 -1440 72"
+"spawnflags" "257"
+"angle" "180"
+"classname" "monster_soldier_ripper"
+}
+{
+"spawnflags" "1792"
+"origin" "2368 -1856 280"
+"targetname" "tport2"
+"angle" "135"
+"classname" "misc_teleporter_dest"
+}
+{
+"spawnflags" "1792"
+"origin" "672 896 312"
+"target" "tport2"
+"angle" "180"
+"classname" "misc_teleporter"
+}
+{
+"origin" "416 -656 456"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "416 -312 456"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "416 -32 456"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "1824 -104 456"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"targetname" "t20"
+"origin" "416 256 512"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/force1.wav"
+}
+{
+"targetname" "t20"
+"noise" "world/force1.wav"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"origin" "416 192 512"
+}
+{
+"targetname" "t20"
+"noise" "world/force1.wav"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"origin" "488 256 512"
+}
+{
+"targetname" "t20"
+"origin" "488 192 512"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/force1.wav"
+}
+{
+"targetname" "t20"
+"origin" "192 384 440"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/force1.wav"
+}
+{
+"targetname" "t20"
+"noise" "world/force1.wav"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"origin" "128 384 440"
+}
+{
+"targetname" "t20"
+"noise" "world/force1.wav"
+"classname" "target_speaker"
+"spawnflags" "2049"
+"origin" "192 456 440"
+}
+{
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+"origin" "680 224 512"
+}
+{
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+"origin" "888 232 512"
+}
+{
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+"origin" "960 -176 312"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"origin" "2272 256 480"
+"noise" "world/amb16.wav"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"origin" "1536 32 264"
+"noise" "world/amb16.wav"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb4.wav"
+"classname" "target_speaker"
+"origin" "416 -1168 456"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb4.wav"
+"classname" "target_speaker"
+"origin" "1280 -296 544"
+}
+{
+"origin" "1888 160 456"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "1528 -440 288"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb9.wav"
+"classname" "target_speaker"
+"origin" "736 -304 568"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb9.wav"
+"classname" "target_speaker"
+"origin" "760 -952 568"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb9.wav"
+"classname" "target_speaker"
+"origin" "728 -680 568"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb9.wav"
+"classname" "target_speaker"
+"origin" "512 -840 104"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb16.wav"
+"classname" "target_speaker"
+"origin" "144 256 456"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb16.wav"
+"classname" "target_speaker"
+"origin" "440 520 392"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb16.wav"
+"classname" "target_speaker"
+"origin" "304 -1440 104"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb4.wav"
+"classname" "target_speaker"
+"origin" "160 952 376"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb4.wav"
+"classname" "target_speaker"
+"origin" "288 952 376"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb4.wav"
+"classname" "target_speaker"
+"origin" "800 -1448 104"
+}
+{
+"origin" "448 -1856 72"
+"target" "tport1"
+"spawnflags" "1792"
+"angle" "90"
+"classname" "misc_teleporter"
+}
+{
+"model" "*14"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"origin" "104 672 312"
+"spawnflags" "1792"
+"targetname" "tport1"
+"angle" "0"
+"classname" "misc_teleporter_dest"
+}
+{
+"origin" "448 1088 312"
+"classname" "info_player_deathmatch"
+}
+{
+"origin" "120 976 304"
+"classname" "item_health"
+}
+{
+"origin" "160 976 304"
+"classname" "item_health"
+}
+{
+"origin" "144 880 432"
+"classname" "item_armor_combat"
+}
+{
+"_color" "1.000000 0.921569 0.654902"
+"light" "150"
+"classname" "light"
+"origin" "1488 -480 288"
+}
+{
+"targetname" "t4"
+"origin" "168 224 504"
+"target" "t20"
+"classname" "trigger_relay"
+}
+{
+"classname" "light"
+"light" "160"
+"origin" "160 256 464"
+"_color" "1.000000 0.701961 0.529412"
+}
+{
+"classname" "light"
+"light" "160"
+"origin" "1248 480 520"
+"_color" "1.000000 0.701961 0.529412"
+}
+{
+"origin" "392 224 456"
+"_color" "1.000000 0.866667 0.701961"
+"light" "150"
+"classname" "light"
+}
+{
+"origin" "504 224 456"
+"_color" "1.000000 0.866667 0.701961"
+"light" "150"
+"classname" "light"
+}
+{
+"model" "*15"
+"targetname" "t20"
+"spawnflags" "2055"
+"classname" "func_wall"
+}
+{
+"model" "*16"
+"targetname" "t20"
+"spawnflags" "2055"
+"classname" "func_wall"
+}
+{
+"model" "*17"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"model" "*18"
+"spawnflags" "1792"
+"classname" "func_wall"
+}
+{
+"_color" "1.000000 0.924901 0.656126"
+"light" "115"
+"classname" "light"
+"origin" "248 760 396"
+}
+{
+"_color" "1.000000 0.924901 0.656126"
+"light" "115"
+"classname" "light"
+"origin" "328 760 396"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "448 832 396"
+}
+{
+"origin" "448 960 396"
+"classname" "light"
+"light" "115"
+}
+{
+"_color" "1.000000 0.924901 0.656126"
+"origin" "248 968 396"
+"classname" "light"
+"light" "115"
+}
+{
+"origin" "624 832 396"
+"classname" "light"
+"light" "115"
+}
+{
+"model" "*19"
+"spawnflags" "2048"
+"classname" "func_button"
+"target" "t19"
+}
+{
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.866667 0.701961"
+"origin" "160 472 456"
+}
+{
+"classname" "light"
+"light" "150"
+"_color" "1.000000 0.866667 0.701961"
+"origin" "160 360 456"
+}
+{
+"origin" "656 896 360"
+"classname" "target_speaker"
+"spawnflags" "1"
+"noise" "world/amb9.wav"
+}
+{
+"model" "*20"
+"classname" "func_door"
+"angle" "-2"
+"spawnflags" "2049"
+"lip" "154"
+"targetname" "t19"
+}
+{
+"_color" "1.000000 0.924901 0.656126"
+"light" "115"
+"classname" "light"
+"origin" "328 968 396"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "688 832 396"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "688 960 396"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "624 960 396"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "688 832 524"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "688 960 524"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "656 832 396"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "656 960 396"
+}
+{
+"light" "150"
+"classname" "light"
+"origin" "656 832 716"
+}
+{
+"classname" "light"
+"light" "150"
+"origin" "656 960 716"
+}
+{
+"light" "115"
+"classname" "light"
+"origin" "624 960 524"
+}
+{
+"classname" "light"
+"light" "115"
+"origin" "624 832 524"
+}
+{
+"model" "*21"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "90"
+"team" "ed1"
+"lip" "38"
+}
+{
+"model" "*22"
+"spawnflags" "2048"
+"classname" "func_door"
+"angle" "270"
+"team" "ed1"
+"lip" "38"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.924901 0.656126"
+"origin" "1032 -1760 328"
+}
+{
+"origin" "2272 -520 328"
+"_color" "1.000000 0.924901 0.656126"
+"light" "120"
+"classname" "light"
+}
+{
+"style" "1"
+"classname" "func_areaportal"
+"targetname" "t18"
+}
+{
+"style" "2"
+"classname" "func_areaportal"
+"targetname" "t17"
+}
+{
+"model" "*23"
+"targetname" "reddr2"
+"classname" "func_door"
+"angle" "90"
+"target" "t17"
+"wait" "2"
+"spawnflags" "4"
+}
+{
+"model" "*24"
+"targetname" "reddr2"
+"classname" "func_door"
+"angle" "270"
+"wait" "2"
+"spawnflags" "4"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.924901 0.656126"
+"origin" "2272 -608 328"
+}
+{
+"origin" "2240 -1728 568"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"origin" "2304 -1408 568"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"origin" "2688 -1336 568"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"classname" "target_speaker"
+"origin" "1848 -2168 568"
+}
+{
+"origin" "1416 -2168 568"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"origin" "1580 -1408 568"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"classname" "target_speaker"
+"origin" "1408 -1408 568"
+}
+{
+"origin" "1920 -1056 568"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"classname" "target_speaker"
+"origin" "1920 -896 568"
+}
+{
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"classname" "target_speaker"
+"origin" "1920 -1792 568"
+}
+{
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"classname" "target_speaker"
+"origin" "1128 -880 648"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"noise" "world/amb18.wav"
+"origin" "2304 -1272 416"
+}
+{
+"origin" "2584 -1272 416"
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"noise" "world/amb18.wav"
+"origin" "2584 -968 416"
+}
+{
+"origin" "2304 -968 416"
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"origin" "1768 -1784 416"
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"noise" "world/amb18.wav"
+"origin" "1488 -1784 416"
+}
+{
+"origin" "1488 -2088 416"
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"origin" "904 -480 312"
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb4.wav"
+"classname" "target_speaker"
+"origin" "1280 -664 544"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb4.wav"
+"classname" "target_speaker"
+"origin" "840 -1664 104"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"noise" "world/amb18.wav"
+"origin" "1768 -2088 416"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1880 480 520"
+"noise" "world/amb16.wav"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1520 424 520"
+"noise" "world/amb16.wav"
+}
+{
+"noise" "world/amb16.wav"
+"origin" "1512 184 520"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"classname" "target_speaker"
+"spawnflags" "1"
+"origin" "1496 184 264"
+"noise" "world/amb16.wav"
+}
+{
+"origin" "904 -264 320"
+"spawnflags" "1"
+"random" "1"
+"classname" "func_timer"
+"target" "t16"
+"wait" "3"
+}
+{
+"origin" "920 -264 320"
+"noise" "world/drip1.wav"
+"classname" "target_speaker"
+"targetname" "t16"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1144 -176 312"
+"noise" "world/amb18.wav"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"origin" "1512 -160 264"
+"noise" "world/amb18.wav"
+}
+{
+"spawnflags" "1"
+"noise" "world/amb16.wav"
+"classname" "target_speaker"
+"origin" "552 -1856 104"
+}
+{
+"origin" "760 -1520 120"
+"classname" "func_timer"
+"random" "1"
+"spawnflags" "1"
+"target" "t14"
+"wait" "3"
+}
+{
+"origin" "776 -1520 120"
+"classname" "target_speaker"
+"noise" "world/drip1.wav"
+"targetname" "t14"
+}
+{
+"spawnflags" "1"
+"random" "1"
+"classname" "func_timer"
+"origin" "808 -1384 120"
+"target" "t13"
+"wait" "3"
+}
+{
+"noise" "world/drip1.wav"
+"classname" "target_speaker"
+"origin" "824 -1384 120"
+"targetname" "t13"
+}
+{
+"classname" "func_timer"
+"random" "1"
+"spawnflags" "1"
+"origin" "904 -632 320"
+"target" "t15"
+"wait" "2"
+}
+{
+"classname" "target_speaker"
+"noise" "world/drip1.wav"
+"origin" "920 -632 320"
+"targetname" "t15"
+}
+{
+"classname" "target_secret"
+"targetname" "t8"
+"message" "You have found a secret area."
+"spawnflags" "2048"
+"origin" "1528 -480 144"
+}
+{
+"classname" "item_adrenaline"
+"target" "t8"
+"origin" "1568 -480 144"
+}
+{
+"classname" "item_health_small"
+"origin" "1580 -628 320"
+}
+{
+"classname" "item_health_small"
+"origin" "1548 -628 320"
+}
+{
+"classname" "item_health_small"
+"origin" "1516 -628 320"
+}
+{
+"classname" "item_health_small"
+"origin" "1484 -628 320"
+}
+{
+"classname" "item_health_small"
+"origin" "1548 -332 320"
+}
+{
+"classname" "item_health_small"
+"origin" "1516 -332 320"
+}
+{
+"classname" "item_health_small"
+"origin" "1484 -332 320"
+}
+{
+"classname" "item_health_small"
+"origin" "1580 -332 320"
+}
+{
+"model" "*25"
+"classname" "func_button"
+"angle" "0"
+"wait" "-1"
+"light" "160"
+"message" "Downloading location of Strogg\nstrike fleet."
+"targetname" "t2"
+"target" "t3"
+"spawnflags" "2048"
+}
+{
+"model" "*26"
+"classname" "trigger_multiple"
+"target" "t1"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_key"
+"item" "key_data_cd"
+"origin" "1628 -432 260"
+"targetname" "t1"
+"target" "t2"
+"spawnflags" "2048"
+}
+{
+"classname" "trigger_relay"
+"origin" "1584 -432 260"
+"targetname" "t3"
+"target" "t4"
+"spawnflags" "2048"
+"delay" "2"
+}
+{
+"model" "*27"
+"classname" "func_explosive"
+"health" "25"
+"mass" "200"
+"target" "t5"
+}
+{
+"targetname" "xware"
+"angle" "90"
+"origin" "448 -2160 72"
+"classname" "info_player_coop"
+}
+{
+"targetname" "xware"
+"angle" "90"
+"origin" "448 -2424 72"
+"classname" "info_player_coop"
+}
+{
+"targetname" "xware"
+"angle" "90"
+"origin" "448 -2368 72"
+"classname" "info_player_coop"
+}
+{
+"origin" "416 -1856 104"
+"_color" "1.000000 0.705882 0.529412"
+"light" "120"
+"classname" "light"
+}
+{
+"origin" "520 -1096 120"
+"classname" "target_speaker"
+"noise" "world/drip1.wav"
+"targetname" "t11"
+}
+{
+"origin" "504 -1096 120"
+"classname" "func_timer"
+"random" "1"
+"spawnflags" "1"
+"target" "t11"
+"wait" "2"
+}
+{
+"origin" "824 -1744 120"
+"classname" "target_speaker"
+"noise" "world/drip1.wav"
+"targetname" "t10"
+}
+{
+"origin" "808 -1744 120"
+"classname" "func_timer"
+"random" "1"
+"spawnflags" "1"
+"target" "t10"
+"wait" "3"
+}
+{
+"origin" "344 -1856 120"
+"classname" "target_speaker"
+"noise" "world/drip1.wav"
+"targetname" "t9"
+}
+{
+"origin" "328 -1856 120"
+"classname" "func_timer"
+"random" "1"
+"spawnflags" "1"
+"target" "t9"
+"wait" "3"
+}
+{
+"origin" "456 -1512 120"
+"noise" "world/drip1.wav"
+"classname" "target_speaker"
+"targetname" "t12"
+}
+{
+"origin" "440 -1512 120"
+"spawnflags" "1"
+"random" "1"
+"classname" "func_timer"
+"target" "t12"
+"wait" "3"
+}
+{
+"noise" "world/amb16.wav"
+"origin" "1160 480 520"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"noise" "world/amb16.wav"
+"origin" "2272 -264 480"
+"spawnflags" "1"
+"classname" "target_speaker"
+}
+{
+"noise" "world/amb18.wav"
+"origin" "1144 -784 312"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"origin" "144 64 456"
+"classname" "target_speaker"
+"noise" "world/amb16.wav"
+"spawnflags" "1"
+}
+{
+"origin" "200 -1728 104"
+"classname" "target_speaker"
+"noise" "world/amb16.wav"
+"spawnflags" "1"
+}
+{
+"origin" "1528 -520 288"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "800 -1264 104"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "192 672 376"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "704 -1120 104"
+"classname" "target_speaker"
+"noise" "world/amb4.wav"
+"spawnflags" "1"
+}
+{
+"origin" "760 -8 568"
+"classname" "target_speaker"
+"noise" "world/amb9.wav"
+"spawnflags" "1"
+}
+{
+"origin" "512 -664 104"
+"classname" "target_speaker"
+"noise" "world/amb9.wav"
+"spawnflags" "1"
+}
+{
+"origin" "1120 -80 648"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"origin" "928 -80 648"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"origin" "2688 -904 568"
+"classname" "target_speaker"
+"noise" "world/wind2.wav"
+"spawnflags" "1"
+}
+{
+"origin" "1216 -480 312"
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"origin" "960 -784 312"
+"noise" "world/amb18.wav"
+"classname" "target_speaker"
+"spawnflags" "1"
+}
+{
+"targetname" "t20"
+"origin" "128 456 440"
+"spawnflags" "2049"
+"classname" "target_speaker"
+"noise" "world/force1.wav"
+}
+{
+"origin" "936 -880 648"
+"spawnflags" "1"
+"noise" "world/wind2.wav"
+"classname" "target_speaker"
+}
+{
+"model" "*28"
+"spawnflags" "2048"
+"target" "t21"
+"classname" "trigger_once"
+}
+{
+"origin" "856 -1024 64"
+"classname" "ammo_shells"
+}
+{
+"origin" "896 -1024 64"
+"classname" "ammo_shells"
+}
+{
+"origin" "608 -880 64"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "608 -848 64"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "608 -816 64"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "608 -912 64"
+"classname" "item_armor_shard"
+"spawnflags" "1792"
+}
+{
+"origin" "416 -528 64"
+"classname" "item_health"
+}
+{
+"origin" "416 -568 64"
+"classname" "item_health"
+}
+{
+"origin" "1312 -752 240"
+"classname" "ammo_cells"
+}
+{
+"origin" "1312 -848 240"
+"classname" "ammo_cells"
+}
+{
+"origin" "1288 -800 240"
+"classname" "weapon_boomer"
+}
+{
+"_color" "1.000000 0.901961 0.588235"
+"light" "50"
+"classname" "light"
+"origin" "1528 -160 280"
+}
+{
+"angle" "180"
+"spawnflags" "258"
+"targetname" "t21"
+"origin" "1656 348 520"
+"classname" "monster_floater"
+}
+{
+"origin" "1704 348 480"
+"classname" "item_health_large"
+}
+{
+"spawnflags" "2304"
+"origin" "1560 136 472"
+"classname" "item_armor_combat"
+}
+{
+"origin" "1552 -464 276"
+"message" "Primary mission completed."
+"spawnflags" "1"
+"classname" "target_help"
+"targetname" "t4"
+}
+{
+"origin" "1552 -432 276"
+"message" "Counter strike fleet is\nlocated on Stroggos Moon.\nProceed to Industry Complex."
+"classname" "target_help"
+"targetname" "t4"
+"spawnflags" "2048"
+}
+{
+"origin" "1560 -408 260"
+"classname" "target_goal"
+"targetname" "t4"
+"spawnflags" "2048"
+}
+{
+"_color" "1.000000 0.901961 0.588235"
+"light" "80"
+"classname" "light"
+"origin" "1408 192 280"
+}
+{
+"_color" "1.000000 0.901961 0.588235"
+"light" "100"
+"classname" "light"
+"origin" "1408 192 376"
+}
+{
+"origin" "1336 264 520"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 1.000000 0.800000"
+}
+{
+"_color" "1.000000 1.000000 0.800000"
+"light" "120"
+"classname" "light"
+"origin" "1336 120 520"
+}
+{
+"targetname" "t22"
+"origin" "480 -2440 48"
+"map" "xware$xintell"
+"classname" "target_changelevel"
+}
+{
+"model" "*29"
+"target" "t22"
+"angle" "270"
+"classname" "trigger_multiple"
+}
+{
+"model" "*30"
+"spawnflags" "2048"
+"targetname" "t21"
+"wait" "-1"
+"classname" "func_door"
+"angle" "-1"
+}
+{
+"model" "*31"
+"spawnflags" "2048"
+"targetname" "t21"
+"wait" "-1"
+"classname" "func_door"
+"angle" "-1"
+}
+{
+"targetname" "xware"
+"origin" "448 -2312 72"
+"angle" "90"
+"classname" "info_player_start"
+}
+{
+"origin" "1528 184 280"
+"classname" "light"
+"light" "80"
+"_color" "1.000000 0.901961 0.588235"
+}
+{
+"_color" "1.000000 1.000000 0.800000"
+"light" "120"
+"classname" "light"
+"origin" "1576 264 520"
+}
+{
+"_color" "1.000000 0.901961 0.588235"
+"light" "50"
+"classname" "light"
+"origin" "1536 0 280"
+}
+{
+"origin" "1576 120 520"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 1.000000 0.800000"
+}
+{
+"origin" "1248 -128 320"
+"classname" "light"
+"light" "160"
+"_color" "0.501961 0.501961 1.000000"
+}
+{
+"classname" "light"
+"light" "160"
+"origin" "1792 480 520"
+"_color" "1.000000 0.701961 0.529412"
+}
+{
+"_color" "1.000000 0.701961 0.529412"
+"origin" "160 64 464"
+"light" "160"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "160"
+"origin" "1520 480 520"
+"_color" "1.000000 0.701961 0.529412"
+}
+{
+"_color" "0.792157 0.749020 1.000000"
+"light" "100"
+"classname" "light"
+"origin" "1080 -480 592"
+}
+{
+"_color" "0.501961 0.501961 1.000000"
+"light" "160"
+"classname" "light"
+"origin" "1056 -832 320"
+}
+{
+"_color" "0.501961 0.501961 1.000000"
+"light" "160"
+"classname" "light"
+"origin" "928 -704 320"
+}
+{
+"_color" "0.501961 0.501961 1.000000"
+"light" "160"
+"classname" "light"
+"origin" "928 -480 320"
+}
+{
+"_color" "0.501961 0.501961 1.000000"
+"light" "160"
+"classname" "light"
+"origin" "928 -256 320"
+}
+{
+"_color" "0.501961 0.501961 1.000000"
+"light" "160"
+"classname" "light"
+"origin" "1056 -128 320"
+}
+{
+"classname" "light"
+"light" "160"
+"_color" "0.501961 0.501961 1.000000"
+"origin" "1248 -832 320"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.588235 0.235294"
+"origin" "928 -424 120"
+}
+{
+"_color" "1.000000 0.588235 0.235294"
+"light" "120"
+"classname" "light"
+"origin" "928 -536 120"
+}
+{
+"_color" "1.000000 0.588235 0.235294"
+"light" "120"
+"classname" "light"
+"origin" "848 -424 120"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.588235 0.235294"
+"origin" "848 -536 120"
+}
+{
+"_color" "1.000000 0.705882 0.529412"
+"light" "120"
+"classname" "light"
+"origin" "512 -736 104"
+}
+{
+"_color" "1.000000 0.924901 0.656126"
+"origin" "1592 -480 288"
+"classname" "light"
+"light" "150"
+}
+{
+"origin" "1080 -976 544"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.846154 0.396761"
+}
+{
+"origin" "936 -976 544"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.846154 0.396761"
+}
+{
+"origin" "512 -576 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"origin" "712 -736 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"origin" "512 -928 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"origin" "512 -1120 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"origin" "800 -1120 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"origin" "824 -1280 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"origin" "800 -1472 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"origin" "800 -1664 104"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+"origin" "800 -1856 104"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+"origin" "608 -1856 104"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+"origin" "224 -1856 104"
+}
+{
+"_color" "1.000000 0.705882 0.529412"
+"light" "50"
+"classname" "light"
+"origin" "512 -448 104"
+}
+{
+"_color" "1.000000 0.705882 0.529412"
+"light" "120"
+"classname" "light"
+"origin" "448 -1440 104"
+}
+{
+"_color" "1.000000 0.705882 0.529412"
+"light" "120"
+"classname" "light"
+"origin" "224 -1440 104"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.705882 0.529412"
+"origin" "224 -1664 104"
+}
+{
+"classname" "func_group"
+}
+{
+"classname" "func_group"
+}
+{
+"origin" "1120 -1760 328"
+"_color" "1.000000 0.924901 0.656126"
+"light" "120"
+"classname" "light"
+}
+{
+"model" "*32"
+"targetname" "reddr1"
+"_minlight" "0.1"
+"angle" "180"
+"classname" "func_door"
+"wait" "2"
+"spawnflags" "4"
+}
+{
+"model" "*33"
+"targetname" "reddr1"
+"_minlight" "0.1"
+"angle" "0"
+"classname" "func_door"
+"target" "t18"
+"wait" "2"
+"spawnflags" "4"
+}
+{
+"model" "*34"
+"mass" "200"
+"health" "25"
+"classname" "func_explosive"
+"target" "t6"
+}
+{
+"model" "*35"
+"mass" "200"
+"health" "25"
+"classname" "func_explosive"
+"target" "t7"
+}
+{
+"origin" "1532 -392 284"
+"noise" "world/brkglas.wav"
+"classname" "target_speaker"
+"targetname" "t5"
+}
+{
+"origin" "1532 -568 284"
+"classname" "target_speaker"
+"noise" "world/brkglas.wav"
+"targetname" "t6"
+}
+{
+"origin" "1608 -480 284"
+"classname" "target_speaker"
+"noise" "world/brkglas.wav"
+"targetname" "t7"
+}
+{
+"origin" "2784 -1120 328"
+"_color" "1.000000 0.924901 0.656126"
+"light" "120"
+"classname" "light"
+}
+{
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.924901 0.656126"
+"origin" "1632 -2272 328"
+}
+{
+"_color" "1.000000 0.846154 0.396761"
+"light" "120"
+"classname" "light"
+"origin" "1280 -840 544"
+}
+{
+"_color" "1.000000 0.846154 0.396761"
+"light" "120"
+"classname" "light"
+"origin" "1280 -696 544"
+}
+{
+"_color" "1.000000 0.846154 0.396761"
+"light" "120"
+"classname" "light"
+"origin" "1280 -264 544"
+}
+{
+"_color" "1.000000 0.846154 0.396761"
+"light" "120"
+"classname" "light"
+"origin" "1280 -120 544"
+}
+{
+"origin" "936 16 544"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.846154 0.396761"
+}
+{
+"origin" "1080 16 544"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.846154 0.396761"
+}
+{
+"_color" "1.000000 0.846154 0.396761"
+"light" "120"
+"classname" "light"
+"origin" "728 -120 544"
+}
+{
+"_color" "1.000000 0.846154 0.396761"
+"light" "120"
+"classname" "light"
+"origin" "728 -264 544"
+}
+{
+"origin" "728 -696 544"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.846154 0.396761"
+}
+{
+"origin" "728 -840 544"
+"classname" "light"
+"light" "120"
+"_color" "1.000000 0.846154 0.396761"
+}
+{
+"spawnflags" "1"
+"classname" "target_speaker"
+"noise" "world/steam3.wav"
+"origin" "968 -480 264"
+}
+{
+"model" "*36"
+"origin" "420 -2236 84"
+"distance" "90"
+"_minlight" "0.1"
+"classname" "func_door_rotating"
+}
+{
+"origin" "1392 -496 480"
+"classname" "ammo_rockets"
+"angle" "180"
+"spawnflags" "3584"
+}
+{
+"origin" "1488 -480 168"
+"classname" "light"
+"light" "120"
+"_color" "0.501961 0.501961 1.000000"
+}
+{
+"light" "120"
+"classname" "light"
+"origin" "1592 -480 168"
+"_color" "0.501961 0.501961 1.000000"
+}
+{
+"origin" "1576 -448 256"
+"targetname" "t56"
+"spawnflags" "2052"
+"noise" "world/datafiles.wav"
+"classname" "target_speaker"
+"attenuation" "-1"
+}
+{
+"classname" "item_pack"
+"spawnflags" "1792"
+"origin" "2168 -1656 272"
+}
+{
+"classname" "target_secret"
+"spawnflags" "2048"
+"origin" "304 1096 304"
+"targetname" "invulsec"
+"message" "You found a secret item."
+} \ No newline at end of file