From 4a992dc9a38436d0b73eae380fd65325f7db833d Mon Sep 17 00:00:00 2001 From: Mike Rubits Date: Wed, 6 Apr 2022 14:43:08 -0500 Subject: [PATCH] Initial commit of Update 3 QuakeC --- COPYING.txt | 340 ++++ README.md | 11 + quakec/ai.qc | 713 ++++++++ quakec/bots/bot.qc | 54 + quakec/buttons.qc | 167 ++ quakec/client.qc | 1751 ++++++++++++++++++ quakec/combat.qc | 351 ++++ quakec/defs.qc | 806 ++++++++ quakec/doors.qc | 835 +++++++++ quakec/fight.qc | 426 +++++ quakec/itemnames.qc | 84 + quakec/items.qc | 1483 +++++++++++++++ quakec/misc.qc | 707 +++++++ quakec/monsters.qc | 267 +++ quakec/monsters/chthon.qc | 430 +++++ quakec/monsters/enforcer.qc | 392 ++++ quakec/monsters/fiend.qc | 388 ++++ quakec/monsters/grunt.qc | 333 ++++ quakec/monsters/hellknight.qc | 485 +++++ quakec/monsters/knight.qc | 293 +++ quakec/monsters/ogre.qc | 540 ++++++ quakec/monsters/rotfish.qc | 218 +++ quakec/monsters/rottweiler.qc | 388 ++++ quakec/monsters/scrag.qc | 456 +++++ quakec/monsters/shambler.qc | 435 +++++ quakec/monsters/shub.qc | 349 ++++ quakec/monsters/spawn.qc | 243 +++ quakec/monsters/vore.qc | 278 +++ quakec/monsters/zombie.qc | 547 ++++++ quakec/plats.qc | 396 ++++ quakec/player.qc | 714 ++++++++ quakec/progs.src | 38 + quakec/subs.qc | 332 ++++ quakec/triggers.qc | 697 +++++++ quakec/weapons.qc | 1362 ++++++++++++++ quakec/world.qc | 421 +++++ quakec_hipnotic/ai.qc | 1210 ++++++++++++ quakec_hipnotic/amtest.qc | 104 ++ quakec_hipnotic/bots/bot.qc | 52 + quakec_hipnotic/buttons.qc | 161 ++ quakec_hipnotic/client.qc | 2061 +++++++++++++++++++++ quakec_hipnotic/combat.qc | 366 ++++ quakec_hipnotic/defs.qc | 781 ++++++++ quakec_hipnotic/doors.qc | 798 ++++++++ quakec_hipnotic/fight.qc | 428 +++++ quakec_hipnotic/flag.qc | 27 + quakec_hipnotic/hip_brk.qc | 109 ++ quakec_hipnotic/hip_expl.qc | 221 +++ quakec_hipnotic/hip_part.qc | 324 ++++ quakec_hipnotic/hip_push.qc | 128 ++ quakec_hipnotic/hiparma.qc | 981 ++++++++++ quakec_hipnotic/hipclock.qc | 104 ++ quakec_hipnotic/hipcount.qc | 208 +++ quakec_hipnotic/hipdecoy.qc | 188 ++ quakec_hipnotic/hipdefs.qc | 141 ++ quakec_hipnotic/hipgrem.qc | 1877 +++++++++++++++++++ quakec_hipnotic/hipholes.qc | 129 ++ quakec_hipnotic/hipitems.qc | 899 +++++++++ quakec_hipnotic/hipmisc.qc | 572 ++++++ quakec_hipnotic/hipmodel.qc | 219 +++ quakec_hipnotic/hipquake.qc | 100 + quakec_hipnotic/hiprot.qc | 1139 ++++++++++++ quakec_hipnotic/hiprubbl.qc | 151 ++ quakec_hipnotic/hipscrge.qc | 530 ++++++ quakec_hipnotic/hipspawn.qc | 382 ++++ quakec_hipnotic/hipspike.qc | 85 + quakec_hipnotic/hipspr.qc | 34 + quakec_hipnotic/hipsubs.qc | 230 +++ quakec_hipnotic/hiptrain.qc | 221 +++ quakec_hipnotic/hiptrig.qc | 298 +++ quakec_hipnotic/hipwater.qc | 69 + quakec_hipnotic/items.qc | 1485 +++++++++++++++ quakec_hipnotic/jctest.qc | 34 + quakec_hipnotic/misc.qc | 764 ++++++++ quakec_hipnotic/models.qc | 604 ++++++ quakec_hipnotic/monsters.qc | 260 +++ quakec_hipnotic/monsters/chthon.qc | 404 ++++ quakec_hipnotic/monsters/enforcer.qc | 375 ++++ quakec_hipnotic/monsters/fiend.qc | 382 ++++ quakec_hipnotic/monsters/grunt.qc | 303 +++ quakec_hipnotic/monsters/hellknight.qc | 460 +++++ quakec_hipnotic/monsters/knight.qc | 289 +++ quakec_hipnotic/monsters/ogre.qc | 481 +++++ quakec_hipnotic/monsters/rotfish.qc | 228 +++ quakec_hipnotic/monsters/rottweiler.qc | 382 ++++ quakec_hipnotic/monsters/scrag.qc | 429 +++++ quakec_hipnotic/monsters/shambler.qc | 403 ++++ quakec_hipnotic/monsters/shub.qc | 327 ++++ quakec_hipnotic/monsters/spawn.qc | 241 +++ quakec_hipnotic/monsters/vore.qc | 256 +++ quakec_hipnotic/monsters/zombie.qc | 564 ++++++ quakec_hipnotic/plats.qc | 383 ++++ quakec_hipnotic/player.qc | 810 ++++++++ quakec_hipnotic/progs.src | 64 + quakec_hipnotic/sprites.qc | 45 + quakec_hipnotic/subs.qc | 335 ++++ quakec_hipnotic/triggers.qc | 734 ++++++++ quakec_hipnotic/weapons.qc | 2331 ++++++++++++++++++++++++ quakec_hipnotic/world.qc | 481 +++++ quakec_mg1/ai.qc | 897 +++++++++ quakec_mg1/buttons.qc | 266 +++ quakec_mg1/client.qc | 2064 +++++++++++++++++++++ quakec_mg1/combat.qc | 458 +++++ quakec_mg1/defs.qc | 890 +++++++++ quakec_mg1/doors.qc | 808 ++++++++ quakec_mg1/fight.qc | 426 +++++ quakec_mg1/fog.qc | 296 +++ quakec_mg1/frametick.qc | 71 + quakec_mg1/func_bob.qc | 134 ++ quakec_mg1/func_fade.qc | 121 ++ quakec_mg1/func_toss.qc | 265 +++ quakec_mg1/horde.qc | 2074 +++++++++++++++++++++ quakec_mg1/items.qc | 1426 +++++++++++++++ quakec_mg1/items_runes.qc | 219 +++ quakec_mg1/lights.qc | 408 +++++ quakec_mg1/map_specific/hub.qc | 36 + quakec_mg1/map_specific/mge2m2.qc | 112 ++ quakec_mg1/math.qc | 86 + quakec_mg1/misc.qc | 721 ++++++++ quakec_mg1/misc_corpses.qc | 106 ++ quakec_mg1/misc_fx.qc | 555 ++++++ quakec_mg1/models.qc | 604 ++++++ quakec_mg1/monsters.qc | 301 +++ quakec_mg1/monsters/boss.qc | 502 +++++ quakec_mg1/monsters/demon.qc | 373 ++++ quakec_mg1/monsters/dog.qc | 380 ++++ quakec_mg1/monsters/enforcer.qc | 365 ++++ quakec_mg1/monsters/fish.qc | 199 ++ quakec_mg1/monsters/hknight.qc | 465 +++++ quakec_mg1/monsters/knight.qc | 294 +++ quakec_mg1/monsters/ogre.qc | 430 +++++ quakec_mg1/monsters/oldone.qc | 302 +++ quakec_mg1/monsters/shalrath.qc | 252 +++ quakec_mg1/monsters/shambler.qc | 400 ++++ quakec_mg1/monsters/soldier.qc | 301 +++ quakec_mg1/monsters/tarbaby.qc | 234 +++ quakec_mg1/monsters/wizard.qc | 427 +++++ quakec_mg1/monsters/zombie.qc | 524 ++++++ quakec_mg1/plats.qc | 393 ++++ quakec_mg1/player.qc | 692 +++++++ quakec_mg1/progs.src | 78 + quakec_mg1/rotate.qc | 198 ++ quakec_mg1/subs.qc | 458 +++++ quakec_mg1/triggers.qc | 1060 +++++++++++ quakec_mg1/weapons.qc | 1503 +++++++++++++++ quakec_mg1/world.qc | 407 +++++ quakec_rogue/ai.qc | 782 ++++++++ quakec_rogue/boss.qc | 404 ++++ quakec_rogue/bots/bot.qc | 52 + quakec_rogue/buttons.qc | 161 ++ quakec_rogue/buzzsaw.qc | 164 ++ quakec_rogue/client.qc | 2117 +++++++++++++++++++++ quakec_rogue/combat.qc | 412 +++++ quakec_rogue/defs.qc | 882 +++++++++ quakec_rogue/demon.qc | 382 ++++ quakec_rogue/dmatch.qc | 200 ++ quakec_rogue/dog.qc | 382 ++++ quakec_rogue/doors.qc | 813 +++++++++ quakec_rogue/dragon.qc | 860 +++++++++ quakec_rogue/earthq.qc | 181 ++ quakec_rogue/eel.qc | 242 +++ quakec_rogue/elevatr.qc | 175 ++ quakec_rogue/ending.qc | 416 +++++ quakec_rogue/enforcer.qc | 376 ++++ quakec_rogue/fight.qc | 427 +++++ quakec_rogue/fish.qc | 206 +++ quakec_rogue/grapple.qc | 328 ++++ quakec_rogue/hknight.qc | 563 ++++++ quakec_rogue/invis_sw.qc | 203 +++ quakec_rogue/items.qc | 1753 ++++++++++++++++++ quakec_rogue/knight.qc | 396 ++++ quakec_rogue/lava_wpn.qc | 224 +++ quakec_rogue/lavaman.qc | 502 +++++ quakec_rogue/lightnin.qc | 202 ++ quakec_rogue/misc.qc | 746 ++++++++ quakec_rogue/monsters.qc | 265 +++ quakec_rogue/morph.qc | 600 ++++++ quakec_rogue/motd.qc | 89 + quakec_rogue/mult_wpn.qc | 570 ++++++ quakec_rogue/mummy.qc | 526 ++++++ quakec_rogue/new_ai.qc | 171 ++ quakec_rogue/newitems.qc | 150 ++ quakec_rogue/newmisc.qc | 163 ++ quakec_rogue/newplats.qc | 621 +++++++ quakec_rogue/ogre.qc | 518 ++++++ quakec_rogue/oldone.qc | 327 ++++ quakec_rogue/pendulum.qc | 410 +++++ quakec_rogue/plats.qc | 383 ++++ quakec_rogue/player.qc | 836 +++++++++ quakec_rogue/progs.src | 68 + quakec_rogue/random.qc | 142 ++ quakec_rogue/runes.qc | 242 +++ quakec_rogue/s_wrath.qc | 580 ++++++ quakec_rogue/shalrath.qc | 255 +++ quakec_rogue/shambler.qc | 403 ++++ quakec_rogue/shield.qc | 122 ++ quakec_rogue/soldier.qc | 303 +++ quakec_rogue/sphere.qc | 177 ++ quakec_rogue/sprites.qc | 45 + quakec_rogue/subs.qc | 335 ++++ quakec_rogue/tarbaby.qc | 367 ++++ quakec_rogue/teamplay.qc | 1886 +++++++++++++++++++ quakec_rogue/timemach.qc | 242 +++ quakec_rogue/triggers.qc | 698 +++++++ quakec_rogue/weapons.qc | 1758 ++++++++++++++++++ quakec_rogue/wizard.qc | 429 +++++ quakec_rogue/world.qc | 458 +++++ quakec_rogue/wrath.qc | 362 ++++ quakec_rogue/zombie.qc | 700 +++++++ 209 files changed, 100244 insertions(+) create mode 100644 COPYING.txt create mode 100644 README.md create mode 100644 quakec/ai.qc create mode 100644 quakec/bots/bot.qc create mode 100644 quakec/buttons.qc create mode 100644 quakec/client.qc create mode 100644 quakec/combat.qc create mode 100644 quakec/defs.qc create mode 100644 quakec/doors.qc create mode 100644 quakec/fight.qc create mode 100644 quakec/itemnames.qc create mode 100644 quakec/items.qc create mode 100644 quakec/misc.qc create mode 100644 quakec/monsters.qc create mode 100644 quakec/monsters/chthon.qc create mode 100644 quakec/monsters/enforcer.qc create mode 100644 quakec/monsters/fiend.qc create mode 100644 quakec/monsters/grunt.qc create mode 100644 quakec/monsters/hellknight.qc create mode 100644 quakec/monsters/knight.qc create mode 100644 quakec/monsters/ogre.qc create mode 100644 quakec/monsters/rotfish.qc create mode 100644 quakec/monsters/rottweiler.qc create mode 100644 quakec/monsters/scrag.qc create mode 100644 quakec/monsters/shambler.qc create mode 100644 quakec/monsters/shub.qc create mode 100644 quakec/monsters/spawn.qc create mode 100644 quakec/monsters/vore.qc create mode 100644 quakec/monsters/zombie.qc create mode 100644 quakec/plats.qc create mode 100644 quakec/player.qc create mode 100644 quakec/progs.src create mode 100644 quakec/subs.qc create mode 100644 quakec/triggers.qc create mode 100644 quakec/weapons.qc create mode 100644 quakec/world.qc create mode 100644 quakec_hipnotic/ai.qc create mode 100644 quakec_hipnotic/amtest.qc create mode 100644 quakec_hipnotic/bots/bot.qc create mode 100644 quakec_hipnotic/buttons.qc create mode 100644 quakec_hipnotic/client.qc create mode 100644 quakec_hipnotic/combat.qc create mode 100644 quakec_hipnotic/defs.qc create mode 100644 quakec_hipnotic/doors.qc create mode 100644 quakec_hipnotic/fight.qc create mode 100644 quakec_hipnotic/flag.qc create mode 100644 quakec_hipnotic/hip_brk.qc create mode 100644 quakec_hipnotic/hip_expl.qc create mode 100644 quakec_hipnotic/hip_part.qc create mode 100644 quakec_hipnotic/hip_push.qc create mode 100644 quakec_hipnotic/hiparma.qc create mode 100644 quakec_hipnotic/hipclock.qc create mode 100644 quakec_hipnotic/hipcount.qc create mode 100644 quakec_hipnotic/hipdecoy.qc create mode 100644 quakec_hipnotic/hipdefs.qc create mode 100644 quakec_hipnotic/hipgrem.qc create mode 100644 quakec_hipnotic/hipholes.qc create mode 100644 quakec_hipnotic/hipitems.qc create mode 100644 quakec_hipnotic/hipmisc.qc create mode 100644 quakec_hipnotic/hipmodel.qc create mode 100644 quakec_hipnotic/hipquake.qc create mode 100644 quakec_hipnotic/hiprot.qc create mode 100644 quakec_hipnotic/hiprubbl.qc create mode 100644 quakec_hipnotic/hipscrge.qc create mode 100644 quakec_hipnotic/hipspawn.qc create mode 100644 quakec_hipnotic/hipspike.qc create mode 100644 quakec_hipnotic/hipspr.qc create mode 100644 quakec_hipnotic/hipsubs.qc create mode 100644 quakec_hipnotic/hiptrain.qc create mode 100644 quakec_hipnotic/hiptrig.qc create mode 100644 quakec_hipnotic/hipwater.qc create mode 100644 quakec_hipnotic/items.qc create mode 100644 quakec_hipnotic/jctest.qc create mode 100644 quakec_hipnotic/misc.qc create mode 100644 quakec_hipnotic/models.qc create mode 100644 quakec_hipnotic/monsters.qc create mode 100644 quakec_hipnotic/monsters/chthon.qc create mode 100644 quakec_hipnotic/monsters/enforcer.qc create mode 100644 quakec_hipnotic/monsters/fiend.qc create mode 100644 quakec_hipnotic/monsters/grunt.qc create mode 100644 quakec_hipnotic/monsters/hellknight.qc create mode 100644 quakec_hipnotic/monsters/knight.qc create mode 100644 quakec_hipnotic/monsters/ogre.qc create mode 100644 quakec_hipnotic/monsters/rotfish.qc create mode 100644 quakec_hipnotic/monsters/rottweiler.qc create mode 100644 quakec_hipnotic/monsters/scrag.qc create mode 100644 quakec_hipnotic/monsters/shambler.qc create mode 100644 quakec_hipnotic/monsters/shub.qc create mode 100644 quakec_hipnotic/monsters/spawn.qc create mode 100644 quakec_hipnotic/monsters/vore.qc create mode 100644 quakec_hipnotic/monsters/zombie.qc create mode 100644 quakec_hipnotic/plats.qc create mode 100644 quakec_hipnotic/player.qc create mode 100644 quakec_hipnotic/progs.src create mode 100644 quakec_hipnotic/sprites.qc create mode 100644 quakec_hipnotic/subs.qc create mode 100644 quakec_hipnotic/triggers.qc create mode 100644 quakec_hipnotic/weapons.qc create mode 100644 quakec_hipnotic/world.qc create mode 100644 quakec_mg1/ai.qc create mode 100644 quakec_mg1/buttons.qc create mode 100644 quakec_mg1/client.qc create mode 100644 quakec_mg1/combat.qc create mode 100644 quakec_mg1/defs.qc create mode 100644 quakec_mg1/doors.qc create mode 100644 quakec_mg1/fight.qc create mode 100644 quakec_mg1/fog.qc create mode 100644 quakec_mg1/frametick.qc create mode 100644 quakec_mg1/func_bob.qc create mode 100644 quakec_mg1/func_fade.qc create mode 100644 quakec_mg1/func_toss.qc create mode 100644 quakec_mg1/horde.qc create mode 100644 quakec_mg1/items.qc create mode 100644 quakec_mg1/items_runes.qc create mode 100644 quakec_mg1/lights.qc create mode 100644 quakec_mg1/map_specific/hub.qc create mode 100644 quakec_mg1/map_specific/mge2m2.qc create mode 100644 quakec_mg1/math.qc create mode 100644 quakec_mg1/misc.qc create mode 100644 quakec_mg1/misc_corpses.qc create mode 100644 quakec_mg1/misc_fx.qc create mode 100644 quakec_mg1/models.qc create mode 100644 quakec_mg1/monsters.qc create mode 100644 quakec_mg1/monsters/boss.qc create mode 100644 quakec_mg1/monsters/demon.qc create mode 100644 quakec_mg1/monsters/dog.qc create mode 100644 quakec_mg1/monsters/enforcer.qc create mode 100644 quakec_mg1/monsters/fish.qc create mode 100644 quakec_mg1/monsters/hknight.qc create mode 100644 quakec_mg1/monsters/knight.qc create mode 100644 quakec_mg1/monsters/ogre.qc create mode 100644 quakec_mg1/monsters/oldone.qc create mode 100644 quakec_mg1/monsters/shalrath.qc create mode 100644 quakec_mg1/monsters/shambler.qc create mode 100644 quakec_mg1/monsters/soldier.qc create mode 100644 quakec_mg1/monsters/tarbaby.qc create mode 100644 quakec_mg1/monsters/wizard.qc create mode 100644 quakec_mg1/monsters/zombie.qc create mode 100644 quakec_mg1/plats.qc create mode 100644 quakec_mg1/player.qc create mode 100644 quakec_mg1/progs.src create mode 100644 quakec_mg1/rotate.qc create mode 100644 quakec_mg1/subs.qc create mode 100644 quakec_mg1/triggers.qc create mode 100644 quakec_mg1/weapons.qc create mode 100644 quakec_mg1/world.qc create mode 100644 quakec_rogue/ai.qc create mode 100644 quakec_rogue/boss.qc create mode 100644 quakec_rogue/bots/bot.qc create mode 100644 quakec_rogue/buttons.qc create mode 100644 quakec_rogue/buzzsaw.qc create mode 100644 quakec_rogue/client.qc create mode 100644 quakec_rogue/combat.qc create mode 100644 quakec_rogue/defs.qc create mode 100644 quakec_rogue/demon.qc create mode 100644 quakec_rogue/dmatch.qc create mode 100644 quakec_rogue/dog.qc create mode 100644 quakec_rogue/doors.qc create mode 100644 quakec_rogue/dragon.qc create mode 100644 quakec_rogue/earthq.qc create mode 100644 quakec_rogue/eel.qc create mode 100644 quakec_rogue/elevatr.qc create mode 100644 quakec_rogue/ending.qc create mode 100644 quakec_rogue/enforcer.qc create mode 100644 quakec_rogue/fight.qc create mode 100644 quakec_rogue/fish.qc create mode 100644 quakec_rogue/grapple.qc create mode 100644 quakec_rogue/hknight.qc create mode 100644 quakec_rogue/invis_sw.qc create mode 100644 quakec_rogue/items.qc create mode 100644 quakec_rogue/knight.qc create mode 100644 quakec_rogue/lava_wpn.qc create mode 100644 quakec_rogue/lavaman.qc create mode 100644 quakec_rogue/lightnin.qc create mode 100644 quakec_rogue/misc.qc create mode 100644 quakec_rogue/monsters.qc create mode 100644 quakec_rogue/morph.qc create mode 100644 quakec_rogue/motd.qc create mode 100644 quakec_rogue/mult_wpn.qc create mode 100644 quakec_rogue/mummy.qc create mode 100644 quakec_rogue/new_ai.qc create mode 100644 quakec_rogue/newitems.qc create mode 100644 quakec_rogue/newmisc.qc create mode 100644 quakec_rogue/newplats.qc create mode 100644 quakec_rogue/ogre.qc create mode 100644 quakec_rogue/oldone.qc create mode 100644 quakec_rogue/pendulum.qc create mode 100644 quakec_rogue/plats.qc create mode 100644 quakec_rogue/player.qc create mode 100644 quakec_rogue/progs.src create mode 100644 quakec_rogue/random.qc create mode 100644 quakec_rogue/runes.qc create mode 100644 quakec_rogue/s_wrath.qc create mode 100644 quakec_rogue/shalrath.qc create mode 100644 quakec_rogue/shambler.qc create mode 100644 quakec_rogue/shield.qc create mode 100644 quakec_rogue/soldier.qc create mode 100644 quakec_rogue/sphere.qc create mode 100644 quakec_rogue/sprites.qc create mode 100644 quakec_rogue/subs.qc create mode 100644 quakec_rogue/tarbaby.qc create mode 100644 quakec_rogue/teamplay.qc create mode 100644 quakec_rogue/timemach.qc create mode 100644 quakec_rogue/triggers.qc create mode 100644 quakec_rogue/weapons.qc create mode 100644 quakec_rogue/wizard.qc create mode 100644 quakec_rogue/world.qc create mode 100644 quakec_rogue/wrath.qc create mode 100644 quakec_rogue/zombie.qc diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/COPYING.txt @@ -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. + + + Copyright (C) + + 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. + + , 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/README.md b/README.md new file mode 100644 index 0000000..8d3ddc5 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Quake Rerelease QuakeC Source Code + +This repository contains the QuakeC source code for the four codebases that were used in the 2021 re-release of Quake: the base campaign, Scourge of Armagon, Dissolution of Eternity, and Dimension of the Machine. Dimension of the Past shares the same codebase as Dimension of the Machine. + +In comparison with the original QuakeC, these codebases have been updated to replace all instances of strings with placeholder strings that can be used for localization. This requires some support on the engine side in order to handle printing messages correctly. There have also been bug fixes and modifications to behaviors from the original game. + +These progs also use some advanced QuakeC features that are only available in modern compilers. We recommend using [FTEQCC](https://www.fteqcc.org) for compiling this release. + +This code is released under the GPLv2 license. See `COPYING.txt` for more details. + +id Software is unable to provide support for this release, however we urge you to take advantage of the depth of community-driven resources already available. \ No newline at end of file diff --git a/quakec/ai.qc b/quakec/ai.qc new file mode 100644 index 0000000..357a66c --- /dev/null +++ b/quakec/ai.qc @@ -0,0 +1,713 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() movetarget_f; +void() t_movetarget; +void() knight_walk1; +void() knight_bow6; +void() knight_bow1; +void(entity etemp, entity stemp, entity stemp, float dmg) T_Damage; +/* + +.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 +*/ + + +// +// globals +// + +// +// when a monster becomes angry at a player, that monster will be used +// as the sight target the next frame so that monsters near that one +// will wake up even if they wouldn't have noticed the player +// +entity sight_entity; +float sight_entity_time; + +void makevectorsfixed(vector ang) { + ang_x *= -1; + makevectors(ang); +}; + +float(float v) anglemod = +{ + while (v >= 360) + v = v - 360; + while (v < 0) + v = v + 360; + return v; +}; + +/* +============================================================================== + +MOVETARGET CODE + +The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target. + +targetname +must be present. The name of this movetarget. + +target +the next spot to move to. If not present, stop here for good. + +pausetime +The number of seconds to spend standing or bowing for path_stand or path_bow + +============================================================================== +*/ + + +void() movetarget_f = +{ + if (!self.targetname) + objerror ("monster_movetarget: no targetname"); + + self.solid = SOLID_TRIGGER; + self.touch = t_movetarget; + setsize (self, '-8 -8 -8', '8 8 8'); + +}; + +/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8) +Monsters will continue walking towards the next target corner. +*/ +void() path_corner = +{ + movetarget_f (); +}; + + +/* +============= +t_movetarget + +Something has bumped into a movetarget. If it is a monster +moving towards it, change the next destination and continue. +============== +*/ +void() t_movetarget = +{ + local entity temp; + + if (other.movetarget != self) + return; + + if (other.enemy) + return; // fighting, not following a path + + temp = self; + self = other; + other = temp; + + if (self.classname == "monster_ogre") + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound + +//dprint ("t_movetarget\n"); + self.goalentity = self.movetarget = find (world, targetname, other.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + if (!self.movetarget) + { + self.pausetime = time + 999999; + self.th_stand (); + return; + } +}; + + + +//============================================================================ + +/* +============= +range + +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 +============= +*/ +float(entity targ) range = +{ + local vector spot1, spot2; + local float r; + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + r = vlen (spot1 - spot2); + if (r < 120) + return RANGE_MELEE; + if (r < 500) + return RANGE_NEAR; + if (r < 1000) + return RANGE_MID; + return RANGE_FAR; +}; + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +float (entity targ) visible = +{ + local vector spot1, spot2; + + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + traceline (spot1, spot2, TRUE, self); // see through other monsters + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_fraction == 1) + return TRUE; + return FALSE; +}; + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +float(entity targ) infront = +{ + local vector vec; + local float dot; + + makevectorsfixed(self.angles); + vec = normalize (targ.origin - self.origin); + dot = vec * v_forward; + + if ( dot > 0.3) + { + return TRUE; + } + return FALSE; +}; + +//============================================================================ + +void() HuntTarget = +{ + self.goalentity = self.enemy; + self.think = self.th_run; + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + self.nextthink = time + 0.1; + SUB_AttackFinished (1); // wait a while before first attack +}; + +void SightSound() { + if ( self.classname == "enforcer" ) { + local float rsnd; + + rsnd = rint( random() * 3 ); + if (rsnd == 1) + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + else if (rsnd == 2) + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + else if (rsnd == 0) + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, self.noise4, 1, ATTN_NORM); + } else { + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } +}; + +void() FoundTarget = +{ + if (self.enemy.classname == "player") + { // let other monsters see this monster for a while + sight_entity = self; + sight_entity_time = time; + } + + self.show_hostile = time + 1; // wake up other monsters + + SightSound (); + HuntTarget (); +}; + +/* +=========== +FindTarget + +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. +============ +*/ +float() FindTarget = +{ + local entity client; + local float r; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry + +// spawnflags & 3 is a big hack, because zombie crucified used the first +// spawn flag prior to the ambush flag, and I forgot about it, so the second +// spawn flag works as well + if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) ) + { + client = sight_entity; + if (client.enemy == self.enemy) + return TRUE; + } + else + { + client = checkclient (); + if (!client) + return FALSE; // current check entity isn't in PVS + } + + if (client == self.enemy) + return FALSE; + + if (client.flags & FL_NOTARGET) + return FALSE; + + if (client.items & IT_INVISIBILITY) + return FALSE; + + r = range (client); + if (r == RANGE_FAR) + return FALSE; + + if (!visible (client)) + return FALSE; + + if (r == RANGE_NEAR) + { + if (client.show_hostile < time && !infront (client)) + return FALSE; + } + + else if (r == RANGE_MID) + { + if ( !infront (client)) + return FALSE; + } + +// +// got one +// + self.enemy = client; + if (self.enemy.classname != "player") + { + self.enemy = self.enemy.enemy; + if (self.enemy.classname != "player") + { + self.enemy = world; + return FALSE; + } + } + + FoundTarget (); + return TRUE; +}; + + +//============================================================================= + +void(float dist) ai_forward = +{ + walkmove (self.angles_y, dist); +}; + +void(float dist) ai_back = +{ + walkmove ( (self.angles_y+180), dist); +}; + + +/* +============= +ai_pain + +stagger back a bit +============= +*/ +void(float dist) ai_pain = +{ + ai_back (dist); +}; + +/* +============= +ai_painforward + +stagger back a bit +============= +*/ +void(float dist) ai_painforward = +{ + walkmove (self.ideal_yaw, dist); +}; + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void(float dist) ai_walk = +{ + movedist = dist; + + // check for noticing a player + if (FindTarget ()) + return; + + movetogoal (dist); +}; + + +/* +============= +ai_stand + +The monster is staying in one place for a while, with slight angle turns +============= +*/ +void() ai_stand = +{ + if (FindTarget ()) + return; + + if (time > self.pausetime) + { + self.th_walk (); + return; + } +}; + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +============= +*/ +void() ai_turn = +{ + if (FindTarget ()) + return; + + ChangeYaw (); +}; + +//============================================================================= + +/* +============= +ChooseTurn +============= +*/ +void(vector dest3) ChooseTurn = +{ + local vector dir, newdir; + + dir = self.origin - dest3; + + newdir_x = trace_plane_normal_y; + newdir_y = 0 - trace_plane_normal_x; + newdir_z = 0; + + if (dir * newdir > 0) + { + dir_x = 0 - trace_plane_normal_y; + dir_y = trace_plane_normal_x; + } + else + { + dir_x = trace_plane_normal_y; + dir_y = 0 - trace_plane_normal_x; + } + + dir_z = 0; + self.ideal_yaw = vectoyaw(dir); +}; + +/* +============ +FacingIdeal + +============ +*/ +float() FacingIdeal = +{ + local float delta; + + delta = anglemod(self.angles_y - self.ideal_yaw); + if (delta > 45 && delta < 315) + return FALSE; + return TRUE; +}; + + +//============================================================================= + +float() WizardCheckAttack; +float() DogCheckAttack; + +float() CheckAnyAttack = +{ + if (!enemy_visible) + return FALSE; + if (self.classname == "monster_army") + return SoldierCheckAttack (); + if (self.classname == "monster_ogre") + return OgreCheckAttack (); + if (self.classname == "monster_shambler") + return ShamCheckAttack (); + if (self.classname == "monster_demon1") + return DemonCheckAttack (); + if (self.classname == "monster_dog") + return DogCheckAttack (); + if (self.classname == "monster_wizard") + return WizardCheckAttack (); + return CheckAttack (); +}; + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void() ai_run_melee = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + + if (FacingIdeal()) + { + self.th_melee (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void() ai_run_missile = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (FacingIdeal()) + { + self.th_missile (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void() ai_run_slide = +{ + local float ofs; + + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (self.lefty) + ofs = 90; + else + ofs = -90; + + if (walkmove (self.ideal_yaw + ofs, movedist)) + return; + + self.lefty = 1 - self.lefty; + + walkmove (self.ideal_yaw - ofs, movedist); +}; + +/* +============= +ai_pathtogoal + +Advanced movement code that use the bots pathfinder if allowed and conditions are right. +Feel free to add any other conditions needed. +============= +*/ +void ai_pathtogoal( float dist ) { + if ( self.allowPathFind == FALSE ) { + movetogoal( dist ); // can't use pathfinding, so use normal Quake movement behavior. + return; + } + + if ( enemy_visible ) { + if ( self.combat_style == CS_RANGED ) { + // do the normal "shoot, walk, shoot" behavior... + movetogoal( dist ); + return; + } else if ( self.combat_style == CS_MELEE ) { + // path pretty close to the enemy, then let normal Quake movement take over. + if ( enemy_range > RANGE_NEAR ) { + if ( walkpathtogoal( dist, self.enemy.origin ) == PATH_IN_PROGRESS ) { + return; + } + } + } else if ( self.combat_style == CS_MIXED ) { + // most mixed combat AI have fairly short range attacks, so try to path within mid range. + if ( enemy_range > RANGE_MID ) { + if ( walkpathtogoal( dist, self.enemy.origin ) == PATH_IN_PROGRESS ) { + return; + } + } + } + } else { + // we can't see our enemy, let's see if we can path to them + if ( walkpathtogoal( dist, self.enemy.origin ) == PATH_IN_PROGRESS ) { + return; + } + } + + movetogoal( dist ); // fall back to normal Quake movement behavior. +} + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void(float dist) ai_run = +{ + + movedist = dist; +// see if the enemy is dead + if (self.enemy.health <= 0) + { + self.enemy = world; + // FIXME: look all around for other targets + if (self.oldenemy.health > 0) + { + self.enemy = self.oldenemy; + HuntTarget (); + } + else + { + if (self.movetarget) + self.th_walk (); + else + self.th_stand (); + return; + } + } + + self.show_hostile = time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_visible = visible(self.enemy); + if (enemy_visible) + self.search_time = time + 5; + +// look for other coop players + if (coop && self.search_time < time) + { + if (FindTarget ()) + return; + } + + enemy_infront = infront(self.enemy); + enemy_range = range(self.enemy); + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + + if (self.attack_state == AS_MISSILE) + { + ai_run_missile (); + return; + } + if (self.attack_state == AS_MELEE) + { + ai_run_melee (); + return; + } + + if (CheckAnyAttack ()) + return; // beginning an attack + + if (self.attack_state == AS_SLIDING) + { + ai_run_slide (); + return; + } + + // mal: added here for demonstration purposes for mods, but disabled by default in id1... +#ifdef USE_ADVANCED_PATHING + ai_pathtogoal( dist ); +#else + movetogoal( dist ); // fall back to normal Quake movement behavior. +#endif +}; diff --git a/quakec/bots/bot.qc b/quakec/bots/bot.qc new file mode 100644 index 0000000..440834e --- /dev/null +++ b/quakec/bots/bot.qc @@ -0,0 +1,54 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +======================== +Bot_PreThink + +Called by the engine every frame before running the bots update in C++ + +NOTE: "self" will be the calling bot + +Example usages of this function: + + 1. Telling bots what to do or where to go ( i.e. setting a high level goal ). + 2. Modifying properties on the bot player ( or other players in-game ) that the bots can see/use doing their update. +======================== +*/ +void Bot_PreThink() { + // add your code here! +}; + +/* +======================== +Bot_PostThink + +Called by the engine every frame after running the bots update in C++ + +NOTE: "self" will be the calling bot + +Example usages of this function: + + 1. Overriding/modifying bot user cmds. + 2. Overriding/modifying bot player properties. +======================== +*/ +void Bot_PostThink() { + // add your code here! +}; \ No newline at end of file diff --git a/quakec/buttons.qc b/quakec/buttons.qc new file mode 100644 index 0000000..962f3f6 --- /dev/null +++ b/quakec/buttons.qc @@ -0,0 +1,167 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// button and multiple button + +void() button_wait; +void() button_return; + +void() button_wait = +{ + self.state = STATE_TOP; + self.nextthink = self.ltime + self.wait; + self.think = button_return; + activator = self.enemy; + SUB_UseTargets(); + self.frame = 1; // use alternate textures +}; + +void() button_done = +{ + self.state = STATE_BOTTOM; +}; + +void() button_return = +{ + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, button_done); + self.frame = 0; // use normal textures + + if (self.health) + self.takedamage = DAMAGE_YES; // can be shot again +}; + + +void() button_blocked = +{ // do nothing, just don't ome all the way back out +}; + + +void() button_fire = +{ + if (self.state == STATE_UP || self.state == STATE_TOP) + return; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, button_wait); +}; + + +void() button_use = +{ + self.enemy = activator; + button_fire (); +}; + +void() button_touch = +{ + if (other.classname != "player") + return; + + self.enemy = other; + button_fire (); +}; + +void() button_killed = +{ + self.enemy = damage_attacker; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + button_fire (); +}; + + +/*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" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +void() func_button = +{ + if (self.sounds == 0) + { + precache_sound ("buttons/airbut1.wav"); + self.noise = "buttons/airbut1.wav"; + } + + if (self.sounds == 1) + { + precache_sound ("buttons/switch21.wav"); + self.noise = "buttons/switch21.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("buttons/switch02.wav"); + self.noise = "buttons/switch02.wav"; + } + + if (self.sounds == 3) + { + precache_sound ("buttons/switch04.wav"); + self.noise = "buttons/switch04.wav"; + } + + SetMovedir (); + + self.classname = "func_button"; + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + setmodel (self, self.model); + + self.blocked = button_blocked; + self.use = button_use; + + if (self.health) + { + self.max_health = self.health; + self.th_die = button_killed; + self.takedamage = DAMAGE_YES; + } + else + self.touch = button_touch; + + if (!self.speed) + self.speed = 40; + + if (!self.wait) + self.wait = 1; + + if (!self.lip) + self.lip = 4; + + self.state = STATE_BOTTOM; + + self.pos1 = self.origin; + vector movedir_fabs = { fabs(self.movedir[0]), fabs(self.movedir[1]), fabs(self.movedir[2]) }; + self.pos2 = self.pos1 + ((movedir_fabs * self.size) - self.lip) * self.movedir; +}; + diff --git a/quakec/client.qc b/quakec/client.qc new file mode 100644 index 0000000..f75d0ff --- /dev/null +++ b/quakec/client.qc @@ -0,0 +1,1751 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// prototypes +void () W_WeaponFrame; +void() W_SetCurrentAmmo; +void(entity attacker, float damage) player_pain; +void() player_stand1; +void (vector org) spawn_tfog; +void (vector org, entity death_owner) spawn_tdeath; + +float modelindex_eyes, modelindex_player; + +/* +============================================================================= + + LEVEL CHANGING / INTERMISSION + +============================================================================= +*/ + +float intermission_running; +float intermission_exittime; + +/*QUAKED info_intermission (1 0.5 0.5) (-16 -16 -16) (16 16 16) +This is the camera point for the intermission. +Use mangle instead of angle, so you can set pitch or roll as well as yaw. 'pitch roll yaw' +*/ +void() info_intermission = +{ +}; + + + +float reset_flag; + +void() SetChangeParms = +{ + if(reset_flag) + { + setspawnparms(self); + return; + } + + if (self.health <= 0 || deathmatch) + { + SetNewParms (); + return; + } + +// remove items + self.items = self.items - (self.items & (IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD) ); + +// cap super health + if (self.health > self.max_health) + self.health = self.max_health; + + if (self.health < self.max_health / 2) + self.health = self.max_health / 2; + + parm1 = self.items; + parm2 = self.health; + parm3 = self.armorvalue; + + if (self.ammo_shells < 25) + parm4 = 25; + else + parm4 = self.ammo_shells; + + parm5 = self.ammo_nails; + parm6 = self.ammo_rockets; + parm7 = self.ammo_cells; + parm8 = self.weapon; + parm9 = self.armortype * 100; +}; + +void() SetNewParms = +{ + parm1 = IT_SHOTGUN | IT_AXE; + if (skill == 3 && !deathmatch) + parm2 = 50; + else + parm2 = 100; + parm3 = 0; + parm4 = 25; + parm5 = 0; + parm6 = 0; + parm7 = 0; + parm8 = 1; + parm9 = 0; +}; + +void() DecodeLevelParms = +{ + if (serverflags) + { + if (world.model == "maps/start.bsp") + SetNewParms (); // take away all stuff on starting new episode + } + + self.items = parm1; + self.health = parm2; + self.armorvalue = parm3; + self.ammo_shells = parm4; + self.ammo_nails = parm5; + self.ammo_rockets = parm6; + self.ammo_cells = parm7; + self.weapon = parm8; + self.armortype = parm9 * 0.01; +}; + +/* +============ +FindIntermission + +Returns the entity to view from +============ +*/ +entity() FindIntermission = +{ + local entity spot; + local float cyc; + + // look for info_intermission first + spot = find (world, classname, "info_intermission"); + if (spot) + { // pick a random one + cyc = random() * 4; + while (cyc > 1) + { + spot = find (spot, classname, "info_intermission"); + + if (!spot) + spot = find (spot, classname, "info_intermission"); + + cyc = cyc - 1; + } + + return spot; + } + + // then look for the start position + spot = find (world, classname, "info_player_start"); + if (spot) + return spot; + + // testinfo_player_start is only found in regioned levels + spot = find (world, classname, "testplayerstart"); + if (spot) + return spot; + + objerror ("FindIntermission: no spot"); + return world; +}; + + +string nextmap; +void() GotoNextMap = +{ + if (cvar("samelevel")) // if samelevel is set, stay on same level + changelevel (mapname); + else + changelevel (nextmap); +}; + + +void() ExitIntermission = +{ + // skip any text in deathmatch + if (deathmatch) + { + GotoNextMap (); + return; + } + + intermission_exittime = time + 1; + intermission_running = intermission_running + 1; + +// +// run some text if at the end of an episode +// + if (intermission_running == 2) + { + if (world.model == "maps/e1m7.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + if (!cvar("registered")) + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e1_shareware"); + } + else + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e1"); + } + + return; + } + else if (world.model == "maps/e2m6.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e2"); + return; + } + else if (world.model == "maps/e3m6.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e3"); + return; + } + else if (world.model == "maps/e4m7.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e4"); + return; + } + + GotoNextMap(); + } + + if (intermission_running == 3) + { + if (!cvar("registered")) + { // shareware episode has been completed, go to sell screen + WriteByte (MSG_ALL, SVC_SELLSCREEN); + return; + } + + if ( (serverflags&15) == 15) + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_all_runes"); + return; + } + + } + + GotoNextMap(); +}; + +/* +============ +IntermissionThink + +When the player presses attack or jump, change to the next level +============ +*/ +void() IntermissionThink = +{ + if (time < intermission_exittime) + return; + + if (!self.button0 && !self.button1 && !self.button2) + return; + + ExitIntermission (); +}; + +void() execute_changelevel = +{ + local entity pos; + + intermission_running = 1; + + // enforce a wait time before allowing changelevel + if (deathmatch) + intermission_exittime = time + 5; + else + intermission_exittime = time + 2; + + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + + pos = FindIntermission (); + + other = find (world, classname, "player"); + + while (other != world) + { + other.view_ofs = '0 0 0'; + other.angles = other.v_angle = pos.mangle; + other.fixangle = TRUE; // turn this way immediately + other.nextthink = time + 0.5; + other.takedamage = DAMAGE_NO; + other.solid = SOLID_NOT; + other.movetype = MOVETYPE_NONE; + other.modelindex = 0; + setorigin (other, pos.origin); + + if (skill == 3) + { + // [NDS] haleyjd: achieve if completed E1M1 Nightmare Tyson style (axe only) + if (other.fired_weapon == 0 && world.model == "maps/e1m1.bsp") + { + msg_entity = other; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_PACIFIST"); // emphasis on "fist", I guess. + } + + // [NDS] haleyjd: achieve if completed E4M6 Nightmare without taking damage + if (other.took_damage == 0 && world.model == "maps/e4m6.bsp") + { + msg_entity = other; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_PAINLESS_MAZE"); // emphasis on "fist", I guess. + } + } + + other = find (other, classname, "player"); + } + + WriteByte (MSG_ALL, SVC_INTERMISSION); + + if (campaign && world.model == "maps/e1m7.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_E1M7"); + } + else if (campaign && world.model == "maps/e2m6.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_E2M6"); + } + else if (campaign && world.model == "maps/e3m6.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_E3M6"); + } + else if (campaign && world.model == "maps/e4m7.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_E4M7"); + } + + if (world.model == "maps/e1m4.bsp" && nextmap == "e1m8") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_E1M8"); + } + else if (world.model == "maps/e2m3.bsp" && nextmap == "e2m7") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_E2M7"); + } + else if (world.model == "maps/e3m4.bsp" && nextmap == "e3m7") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_E3M7"); + } + else if (world.model == "maps/e4m5.bsp" && nextmap == "e4m8") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_E4M8"); + } + +}; + + +void() changelevel_touch = +{ + if (other.classname != "player") + return; + + if ((cvar("noexit") == 1) || ((cvar("noexit") == 2) && (mapname != "start"))) + { + T_Damage (other, self, self, 50000); + return; + } + + if (coop || deathmatch) + { + bprint("$qc_exited", other.netname); + + } + + nextmap = self.map; + + SUB_UseTargets (); + + if ( (self.spawnflags & 1) && (deathmatch == 0) ) + { // NO_INTERMISSION + GotoNextMap(); + return; + } + + self.touch = SUB_Null; + + // we can't move people right now, because touch functions are called + // in the middle of C movement code, so set a think time to do it + self.think = execute_changelevel; + self.nextthink = time + 0.1; +}; + +/* +QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. +Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ +void() trigger_changelevel = +{ + if (!self.map) + objerror ("changelevel trigger doesn't have map"); + + self.netname = "changelevel"; + self.killstring = "$qc_ks_tried_leave"; + + InitTrigger (); + self.touch = changelevel_touch; +}; + + +/* +============================================================================= + + PLAYER GAME EDGE FUNCTIONS + +============================================================================= +*/ + +void() set_suicide_frame; + +// called by ClientKill and DeadThink +void() respawn = +{ + if (coop) + { + // make a copy of the dead body for appearances sake + CopyToBodyQueue (self); + // get the spawn parms as they were at level start + setspawnparms (self); + // respawn + PutClientInServer (); + } + else if (deathmatch) + { + // make a copy of the dead body for appearances sake + CopyToBodyQueue (self); + // set default spawn parms + SetNewParms (); + // respawn + PutClientInServer (); + } + else + { + // restart the entire server +// cvar_set("campaign", ftos(campaign)); +// localcmd ("restart\n"); + + // put serverflags back to map's initial setting + serverflags = startingserverflags; + // request a reset to the parms + reset_flag = TRUE; + // "change" to the same level + localcmd ("changelevel "); + localcmd(mapname); + localcmd("\n"); + } +}; + + +/* +============ +ClientKill + +Player entered the suicide command +============ +*/ +void() ClientKill = +{ + bprint("$qc_suicides", self.netname); + set_suicide_frame (); + self.modelindex = modelindex_player; + self.frags = self.frags - 2; // extra penalty + respawn (); +}; + +/* +============ +PlayerVisibleToSpawnPoint + +Returns true if player can see this point +============ +*/ +float PlayerVisibleToSpawnPoint( entity point ) { + local vector spot1, spot2; + local entity player = find( world, classname, "player" ); + while ( player ) { + if ( player.health > 0 ) { + spot1 = point.origin + player.view_ofs; + spot2 = player.origin + player.view_ofs; + + traceline( spot1, spot2, TRUE, point ); + if ( trace_fraction >= 1.0f ) { + return TRUE; + } + } + + player = find( player, classname, "player" ); + } + + return FALSE; +} + +float IDEAL_DIST_FROM_DM_SPAWN_POINT = 384; +float MIN_DIST_FROM_DM_SPAWN_POINT = 84; + +/* +============ +SelectSpawnPoint + +Returns the entity to spawn at +============ +*/ +entity SelectSpawnPoint(float forceSpawn) { + local entity spot, thing; + local float numspots, totalspots; + local float pcount; + local entity spots; + + numspots = 0; + totalspots = 0; + + // testinfo_player_start is only found in regioned levels + spot = find( world, classname, "testplayerstart" ); + if ( spot ) + return spot; + + // choose a info_player_deathmatch point + if ( coop ) { + lastspawn = find( lastspawn, classname, "info_player_coop" ); + + if ( lastspawn == world ) { + lastspawn = find( lastspawn, classname, "info_player_start" ); + } + + if ( lastspawn != world ) { + return lastspawn; + } + } else if ( deathmatch ) { + + // find all spots that don't have visible players nearby + spots = world; + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + totalspots = totalspots + 1; + + thing = findradius( spot.origin, IDEAL_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { + if ( PlayerVisibleToSpawnPoint( spot ) ) { + pcount = pcount + 1; + } + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + + totalspots = totalspots - 1; + + // on small maps with few spawn points, our "ideal" spawn conditions may not be possible to meet + // so fallback to just trying to pick a point without a player on top of it, so we don't start + // a spawn frag loop + if ( numspots == 0 ) { + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + thing = findradius( spot.origin, MIN_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + } + + // uncomment to force a deferred spawn + // if (forceSpawn == FALSE) return world; + + if ( !numspots ) { + if (forceSpawn == FALSE) { + return world; + } + + // no spots available so just pick one at random + totalspots = rint( ( random() * totalspots ) ); + spot = find( world, classname, "info_player_deathmatch" ); + + while( totalspots > 0 ) { + totalspots = totalspots - 1; + spot = find( spot, classname, "info_player_deathmatch" ); + } + return spot; + } + + // Generate a random number between 1 and numspots + numspots = numspots - 1; + + numspots = rint( ( random() * numspots ) ); + + spot = spots; + while( numspots > 0 ) { + spot = spot.goalentity; + numspots = numspots - 1; + } + + return spot; + } + + if ( serverflags ) { // return with a rune to start + spot = find( world, classname, "info_player_start2" ); + + if ( spot ) { + return spot; + } + } + + spot = find( world, classname, "info_player_start" ); + + if ( !spot ) { + error( "PutClientInServer: no info_player_start on level" ); + } + + return spot; +}; + +/* +=========== +PutClientInServer + +called each time a player is spawned +============ +*/ +void() DecodeLevelParms; +void() PlayerDie; + + +void() PutClientInServer = +{ + local entity spot; + + self.classname = "player"; + if (skill == 3 && !deathmatch) + self.health = 50; + else + self.health = 100; + self.takedamage = DAMAGE_AIM; + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_WALK; + self.show_hostile = 0; + if (skill == 3 && !deathmatch) + self.max_health = 50; + else + self.max_health = 100; + self.flags = FL_CLIENT; + self.air_finished = time + 12; + self.dmg = 2; // initial water damage + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.invisible_finished = 0; + self.invincible_finished = 0; + self.effects = 0; + self.invincible_time = 0; + self.healthrot_nextcheck = 0; + self.fired_weapon = 0; // [NDS] haleyjd + self.took_damage = 0; // [NDS] haleyjd + + if ( coop ) { + self.team = TEAM_HUMANS; + } + + DecodeLevelParms (); + + W_SetCurrentAmmo (); + + self.attack_finished = time; + self.th_pain = player_pain; + self.th_die = PlayerDie; + + self.deadflag = DEAD_NO; + // paustime is set by teleporters to keep the player from moving a while + self.pausetime = 0; + + local float shouldTelefrag; + + if (self.spawn_deferred > 0 && time >= self.spawn_deferred) { + dprint("forcing telefrag on this spawn\n"); + shouldTelefrag = TRUE; + } else { + shouldTelefrag = FALSE; + } + spot = SelectSpawnPoint(shouldTelefrag); + if (spot == world) { + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + self.deadflag = DEAD_DEAD; + setmodel(self, ""); + self.view_ofs = '0 0 1'; // not 0 because PlayerPreThink would return out + self.velocity = '0 0 0'; + + if (self.spawn_deferred == 0) { + dprint("no spawns available! deferring\n"); + self.spawn_deferred = time + 5; + } + + spot = FindIntermission(); + self.angles = self.v_angle = spot.mangle; + self.fixangle = TRUE; + self.origin = spot.origin; + self.weaponmodel = ""; + self.weaponframe = 0; + self.weapon = 0; + + return; + } + + self.spawn_deferred = 0; + + self.origin = spot.origin + '0 0 1'; + self.angles = spot.angles; + self.fixangle = TRUE; // turn this way immediately + + // oh, this is a hack! + setmodel (self, "progs/eyes.mdl"); + modelindex_eyes = self.modelindex; + + setmodel (self, "progs/player.mdl"); + modelindex_player = self.modelindex; + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + + self.view_ofs = '0 0 22'; + self.velocity = '0 0 0'; // 1998-07-21 Player moves after respawn fix by Xian + + player_stand1 (); + + if (deathmatch || coop) + { + makevectorsfixed(self.angles); + spawn_tfog (self.origin + v_forward*20); + } + + spawn_tdeath (self.origin, self); + + stuffcmd(self, "-attack\n"); // prevent shooting after respawning +}; + + +/* +============================================================================= + + QUAKED FUNCTIONS + +============================================================================= +*/ + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 24) +The normal starting point for a level. +*/ +void() info_player_start = +{ +}; + + +/*QUAKED info_player_start2 (1 0 0) (-16 -16 -24) (16 16 24) +Only used on start map for the return point from an episode. +*/ +void() info_player_start2 = +{ +}; + + +/* +saved out by quaked in region mode +*/ +void() testplayerstart = +{ +}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for deathmatch games +*/ +void() info_player_deathmatch = +{ +}; + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for coop games +*/ +void() info_player_coop = +{ +}; + +/* +=============================================================================== + +RULES + +=============================================================================== +*/ + +/* +go to the next level for deathmatch +*/ +void() NextLevel = +{ + local entity o; + + if (nextmap != string_null) + return; // already done + + if (mapname == "start") + { + if (!cvar("registered")) + { + mapname = "e1m1"; + } + else if (!(serverflags & 1)) + { + mapname = "e1m1"; + serverflags = serverflags | 1; + } + else if (!(serverflags & 2)) + { + mapname = "e2m1"; + serverflags = serverflags | 2; + } + else if (!(serverflags & 4)) + { + mapname = "e3m1"; + serverflags = serverflags | 4; + } + else if (!(serverflags & 8)) + { + mapname = "e4m1"; + serverflags = serverflags - 7; + } + + o = spawn(); + o.map = mapname; + } + else + { + // find a trigger changelevel + o = find(world, classname, "trigger_changelevel"); + + if (!o || mapname == "start") + { // go back to same map if no trigger_changelevel + o = spawn(); + o.map = mapname; + } + } + + nextmap = o.map; + gameover = TRUE; + + if (o.nextthink < time) + { + o.think = execute_changelevel; + o.nextthink = time + 0.1; + } +}; + +/* +============ +CheckRules + +Exit deathmatch games upon conditions +============ +*/ +void() CheckRules = +{ + local float timelimit; + local float fraglimit; + + if (gameover) // someone else quit the game already + return; + + timelimit = cvar("timelimit") * 60; + fraglimit = cvar("fraglimit"); + + if (timelimit && time >= timelimit) + { + NextLevel (); + return; + } + + if (fraglimit && self.frags >= fraglimit) + { + NextLevel (); + return; + } +}; + +//============================================================================ + +void() PlayerDeathThink = +{ + local float forward; + + if ((self.flags & FL_ONGROUND)) + { + forward = vlen (self.velocity); + forward = forward - 20; + + if (forward <= 0) + self.velocity = '0 0 0'; + else + self.velocity = forward * normalize(self.velocity); + } + + if (self.spawn_deferred) + { + local entity spot; + + spot = SelectSpawnPoint(FALSE); + //dprint("time {} >= self.spawn_deferred {}\n", ftos(time), ftos(self.spawn_deferred)); + if (spot != world || time >= self.spawn_deferred) { + respawn(); + } + + return; + } + + // wait for all buttons released + if (self.deadflag == DEAD_DEAD) + { + if (self.button2 || self.button1 || self.button0) + return; + + self.deadflag = DEAD_RESPAWNABLE; + return; + } + + // wait for any button down + if (!self.button2 && !self.button1 && !self.button0) + return; + + self.button0 = 0; + self.button1 = 0; + self.button2 = 0; + respawn(); +}; + + +void() PlayerJump = +{ + if (self.flags & FL_WATERJUMP) + return; + + if (self.waterlevel >= 2) + { + if (self.watertype == CONTENT_WATER) + self.velocity_z = 100; + else if (self.watertype == CONTENT_SLIME) + self.velocity_z = 80; + else + self.velocity_z = 50; + + // play swimming sound + if (self.swim_flag < time) + { + self.swim_flag = time + 1; + + if (random() < 0.5) + sound (self, CHAN_BODY, "misc/water1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_BODY, "misc/water2.wav", 1, ATTN_NORM); + } + + return; + } + + if (!(self.flags & FL_ONGROUND)) + return; + + if ( !(self.flags & FL_JUMPRELEASED) ) + return; // don't pogo stick + + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + + self.flags = self.flags - FL_ONGROUND; // don't stairwalk + + self.button2 = 0; + // player jumping sound + sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM); + self.velocity_z = self.velocity_z + 270; +}; + + +/* +=========== +WaterMove + +============ +*/ +.float dmgtime; + +void() WaterMove = +{ + if (self.movetype == MOVETYPE_NOCLIP) + return; + + if (self.health < 0) + return; + + if (self.waterlevel != 3) + { + if (self.air_finished < time) + sound (self, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM); + else if (self.air_finished < time + 9) + sound (self, CHAN_VOICE, "player/gasp1.wav", 1, ATTN_NORM); + + self.air_finished = time + 12; + self.dmg = 2; + } + else if (self.air_finished < time) + { // drown! + if (self.pain_finished < time) + { + self.dmg = self.dmg + 2; + + if (self.dmg > 15) + self.dmg = 10; + + T_Damage (self, world, world, self.dmg); + self.pain_finished = time + 1; + } + } + + if (!self.waterlevel) + { + if (self.flags & FL_INWATER) + { + // play leave water sound + sound (self, CHAN_BODY, "misc/outwater.wav", 1, ATTN_NORM); + self.flags = self.flags - FL_INWATER; + } + + return; + } + + if (self.watertype == CONTENT_LAVA) + { // do damage + if (self.dmgtime < time) + { + if (self.radsuit_finished > time) + self.dmgtime = time + 1; + else + self.dmgtime = time + 0.2; + + T_Damage (self, world, world, 10*self.waterlevel); + } + } + else if (self.watertype == CONTENT_SLIME) + { // do damage + if (self.dmgtime < time && self.radsuit_finished < time) + { + self.dmgtime = time + 1; + T_Damage (self, world, world, 4*self.waterlevel); + } + } + + if ( !(self.flags & FL_INWATER) ) + { + + // player enter water sound + + if (self.watertype == CONTENT_LAVA) + sound (self, CHAN_BODY, "player/inlava.wav", 1, ATTN_NORM); + + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/inh2o.wav", 1, ATTN_NORM); + + if (self.watertype == CONTENT_SLIME) + sound (self, CHAN_BODY, "player/slimbrn2.wav", 1, ATTN_NORM); + + self.flags = self.flags + FL_INWATER; + self.dmgtime = 0; + } + + if (! (self.flags & FL_WATERJUMP) ) + self.velocity = self.velocity - 0.8*self.waterlevel*frametime*self.velocity; +}; + +void() CheckWaterJump = +{ + local vector start, end; + +// check for a jump-out-of-water + makevectorsfixed(self.angles); + start = self.origin; + start_z = start_z + 8; + v_forward_z = 0; + normalize(v_forward); + end = start + v_forward*24; + traceline (start, end, TRUE, self); + + if (trace_fraction < 1) + { // solid at waist + start_z = start_z + self.maxs_z - 8; + end = start + v_forward*24; + self.movedir = trace_plane_normal * -50; + traceline (start, end, TRUE, self); + + if (trace_fraction == 1) + { // open at eye level + self.flags = self.flags | FL_WATERJUMP; + self.velocity_z = 225; + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + self.teleport_time = time + 2; // safety net + return; + } + } +}; + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void() PlayerPreThink = +{ + if (intermission_running) + { + IntermissionThink (); // otherwise a button could be missed between + return; // the think tics + } + + if (self.view_ofs == '0 0 0') + return; // intermission or finale + + makevectors (self.v_angle); // is this still used + + if (deathmatch || coop) + CheckRules (); + + WaterMove (); + + if (self.waterlevel == 2) + CheckWaterJump (); + + if (self.deadflag >= DEAD_DEAD) + { + PlayerDeathThink (); + return; + } + + if (self.deadflag == DEAD_DYING) + return; // dying, so do nothing + + if (self.button2) + { + PlayerJump (); + } + else + self.flags = self.flags | FL_JUMPRELEASED; + + // teleporters can force a non-moving pause time + if (time < self.pausetime) + self.velocity = '0 0 0'; + + if(time > self.attack_finished && self.currentammo == 0 && self.weapon != IT_AXE) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + } +}; + +/* +================ +CheckPowerups + +Check for turning off powerups +================ +*/ +void() CheckPowerups = +{ + if (self.health <= 0) + return; + + // invisibility + if (self.invisible_finished) + { + // sound and screen flash when items starts to run out + if (self.invisible_sound < time) + { + sound (self, CHAN_AUTO, "items/inv3.wav", 0.5, ATTN_IDLE); + self.invisible_sound = time + ((random() * 3) + 1); + } + + + if (self.invisible_finished < time + 3) + { + if (self.invisible_time == 1) + { + sprint(self, "$qc_ring_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/inv2.wav", 1, ATTN_NORM); + self.invisible_time = time + 1; + } + + if (self.invisible_time < time) + { + self.invisible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invisible_finished < time) + { // just stopped + self.items = self.items - IT_INVISIBILITY; + self.invisible_finished = 0; + self.invisible_time = 0; + } + + // use the eyes + self.frame = 0; + self.modelindex = modelindex_eyes; + } + else + self.modelindex = modelindex_player; // don't use eyes + + // invincibility + if (self.invincible_finished) + { + // sound and screen flash when items starts to run out + if (self.invincible_finished < time + 3) + { + if (self.invincible_time == 1) + { + sprint(self, "$qc_protection_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/protect2.wav", 1, ATTN_NORM); + self.invincible_time = time + 1; + } + + if (self.invincible_time < time) + { + self.invincible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invincible_finished < time) + { // just stopped + self.items = self.items - IT_INVULNERABILITY; + self.invincible_time = 0; + self.invincible_finished = 0; + } + if (self.invincible_finished > time) + self.effects = self.effects | EF_PENTALIGHT; + else + self.effects = self.effects - (self.effects & EF_PENTALIGHT); + } + + // super damage + if (self.super_damage_finished) + { + + // sound and screen flash when items starts to run out + if (self.super_damage_finished < time + 3) + { + if (self.super_time == 1) + { + sprint(self, "$qc_quad_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/damage2.wav", 1, ATTN_NORM); + self.super_time = time + 1; + } + + if (self.super_time < time) + { + self.super_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.super_damage_finished < time) + { // just stopped + self.items = self.items - IT_QUAD; + self.super_damage_finished = 0; + self.super_time = 0; + } + if (self.super_damage_finished > time) + self.effects = self.effects | EF_QUADLIGHT; + else + self.effects = self.effects - (self.effects & EF_QUADLIGHT); + } + + // suit + if (self.radsuit_finished) + { + self.air_finished = time + 12; // don't drown + + // sound and screen flash when items starts to run out + if (self.radsuit_finished < time + 3) + { + if (self.rad_time == 1) + { + sprint(self, "$qc_biosuit_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/suit2.wav", 1, ATTN_NORM); + self.rad_time = time + 1; + } + + if (self.rad_time < time) + { + self.rad_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.radsuit_finished < time) + { // just stopped + self.items = self.items - IT_SUIT; + self.rad_time = 0; + self.radsuit_finished = 0; + } + } + +}; + +/* +================ +CheckHealthRot + +Checks for taking off health points from a player +that recently used a Mega-Health. +================ +*/ +void CheckHealthRot() { + if ( !( self.items & IT_SUPERHEALTH ) ) { + return; + } + + if ( self.healthrot_nextcheck > time ) { + return; + } + + if ( self.health > self.max_health ) { + self.health = self.health - 1; + self.healthrot_nextcheck = time + 1; + return; + } + + self.items = self.items - ( self.items & IT_SUPERHEALTH ); + self.healthrot_nextcheck = 0; +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void() PlayerPostThink = +{ + if ( self.view_ofs == '0 0 0') { + return; // intermission or finale + } + + if ( self.deadflag ) { + return; + } + + W_WeaponFrame (); // do weapon stuff + + // check to see if player landed and play landing sound + if ((self.jump_flag < -300) && (self.flags & FL_ONGROUND) && (self.health > 0)) + { + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM); + else if (self.jump_flag < -650) + { + T_Damage (self, world, world, 5); + sound (self, CHAN_VOICE, "player/land2.wav", 1, ATTN_NORM); + if (self.health <= 5) + self.deathtype = "falling"; + } + else + sound (self, CHAN_VOICE, "player/land.wav", 1, ATTN_NORM); + + self.jump_flag = 0; + } + + if (!(self.flags & FL_ONGROUND)) + self.jump_flag = self.velocity_z; + + CheckPowerups(); + CheckHealthRot(); +}; + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +void() ClientConnect = +{ + bprint("$qc_entered", self.netname); + + // a client connecting during an intermission can cause problems + if (intermission_running) + ExitIntermission (); +}; + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server +============ +*/ +void() ClientDisconnect = +{ + if (gameover) + return; + + // if the level end trigger has been activated, just return + // since they aren't *really* leaving + + // let everyone else know + bprint("$qc_left_game", self.netname, ftos(self.frags)); + + sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE); + + self->effects = 0; + set_suicide_frame (); +}; + +/* +=========== +ClientObituary + +called when a player dies +============ +*/ +void(entity targ, entity attacker) ClientObituary = +{ + local float rnum; + + // from GPL QW source + local float attackerteam, targteam; + + attackerteam = attacker.team; + targteam = targ.team; + + rnum = random(); + + if (targ.classname == "player") + { + if (attacker.classname == "teledeath") + { + bprint("$qc_telefragged", targ.netname, attacker.owner.netname); + + attacker.owner.frags = attacker.owner.frags + 1; + return; + } + + if (attacker.classname == "teledeath2") + { + bprint("$qc_satans_power", targ.netname); + + targ.frags = targ.frags - 1; + return; + } + + if (attacker.classname == "player") + { + if (targ == attacker) + { + // killed self + attacker.frags = attacker.frags - 1; + + if (targ.weapon == 64 && targ.waterlevel > 1) + { + if (targ.watertype == CONTENT_SLIME) + bprint("$qc_discharge_slime", targ.netname); + + else if (targ.watertype == CONTENT_LAVA) + bprint("$qc_discharge_lava", targ.netname); + + else + bprint("$qc_discharge_water", targ.netname); + + return; + } + if (targ.weapon == 16) + bprint("$qc_suicide_pin", targ.netname); + else if (rnum) + bprint("$qc_suicide_bored", targ.netname); + else + bprint("$qc_suicide_loaded", targ.netname); + return; + } + else if ( (teamplay == 2) && (targteam == attackerteam) && + (attackerteam != 0) ) + { + if (rnum < 0.25) + bprint("$qc_ff_teammate", attacker.netname); + else if (rnum < 0.50) + bprint("$qc_ff_glasses", attacker.netname); + else if (rnum < 0.75) + bprint("$qc_ff_otherteam", attacker.netname); + else + bprint("$qc_ff_friend", attacker.netname); + + attacker.frags = attacker.frags - 1; + return; + } + else + { + attacker.frags = attacker.frags + 1; + + rnum = attacker.weapon; + + if (rnum == IT_AXE) + { + bprint("$qc_death_ax", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SHOTGUN) + { + bprint("$qc_death_sg", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SUPER_SHOTGUN) + { + bprint("$qc_death_dbl", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_NAILGUN) + { + bprint("$qc_death_nail", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SUPER_NAILGUN) + { + bprint("$qc_death_sng", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_GRENADE_LAUNCHER) + { + if (targ.health < -40) + { + bprint("$qc_death_gl1", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_gl2", targ.netname, attacker.netname); + return; + } + } + + if (rnum == IT_ROCKET_LAUNCHER) + { + if (attacker.super_damage_finished > 0 && targ.health < -40) + { + rnum = random(); + + if (rnum < 0.3) + { + bprint("$qc_death_rl_quad1", targ.netname, attacker.netname); + return; + } + else if (rnum < 0.6) + { + bprint("$qc_death_rl_quad2", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_rl1", targ.netname, attacker.netname); + return; + } + } + else + { + if (targ.health < -40) + { + bprint("$qc_death_rl2", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_rl3", targ.netname, attacker.netname); + return; + } + } + } + + if (rnum == IT_LIGHTNING) + { + if (attacker.waterlevel > 1) + { + bprint("$qc_death_lg1", targ.netname, attacker.netname); + if (attacker.invincible_finished) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SURVIVE_DISCHARGE"); + } + } + else + bprint("$qc_death_lg2", targ.netname, attacker.netname); + + return; + } + } + return; + } + else + { + targ.frags = targ.frags - 1; // killed self + rnum = targ.watertype; + + if (rnum == -3) + { + if (random() < 0.5) + bprint("$qc_death_drown1", targ.netname); + else + bprint("$qc_death_drown2", targ.netname); + + return; + } + else if (rnum == -4) + { + if (random() < 0.5) + bprint("$qc_death_slime1", targ.netname); + else + bprint("$qc_death_slime2", targ.netname); + + return; + } + else if (rnum == -5) + { + if (targ.health < -15) + { + bprint("$qc_death_lava1", targ.netname); + return; + } + + if (random() < 0.5) + bprint("$qc_death_lava2", targ.netname); + else + bprint("$qc_death_lava3", targ.netname); + + return; + } + + if (attacker.solid == SOLID_BSP && attacker != world) + { + bprint("$qc_death_squish", targ.netname); + return; + } + + if(attacker.killstring) + { + bprint(attacker.killstring, targ.netname); + return; + } + + if (targ.deathtype == "falling") + { + targ.deathtype = string_null; + bprint("$qc_death_fall", targ.netname); + return; + } + + bprint("$qc_death_died", targ.netname); + } + } +}; diff --git a/quakec/combat.qc b/quakec/combat.qc new file mode 100644 index 0000000..be5b4ec --- /dev/null +++ b/quakec/combat.qc @@ -0,0 +1,351 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() T_MissileTouch; +void() info_player_start; +void(entity targ, entity attacker) ClientObituary; + +void() monster_death_use; + +//============================================================================ + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +float(entity targ, entity inflictor) CanDamage = +{ + // bmodels need special checking because their origin is 0,0,0 + if (targ.movetype == MOVETYPE_PUSH) + { + traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self); + if (trace_fraction == 1) + return TRUE; + + if (trace_ent == targ) + return TRUE; + + return FALSE; + } + + traceline(inflictor.origin, targ.origin, TRUE, self); + + if (trace_fraction == 1) + return TRUE; + + traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self); + + if (trace_fraction == 1) + return TRUE; + + traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); + + if (trace_fraction == 1) + return TRUE; + + traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); + + if (trace_fraction == 1) + return TRUE; + + traceline(inflictor.origin, targ.origin + '15 -15 0', TRUE, self); + + if (trace_fraction == 1) + return TRUE; + + return FALSE; +}; + + +/* +============ +Killed +============ +*/ +void(entity targ, entity attacker) Killed = +{ + local entity oself; + + oself = self; + self = targ; + + if (self.health < -99) + self.health = -99; // don't let sbar look bad if a player + + if (self.movetype == MOVETYPE_PUSH || self.movetype == MOVETYPE_NONE) + { // doors, triggers, etc + self.th_die (); + self = oself; + return; + } + + self.enemy = attacker; + + // bump the monster counter + if (self.flags & FL_MONSTER) + { + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); + if (attacker.classname == "player") + { + attacker.frags = attacker.frags + 1; + } + + if (attacker != self && attacker.flags & FL_MONSTER) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FRIENDLY_FIRE"); + } + } + + ClientObituary(self, attacker); + + self.takedamage = DAMAGE_NO; + self.touch = SUB_Null; + + monster_death_use(); + self.th_die (); + + self = oself; +}; + + +/* +============ +T_Damage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +============ +*/ +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage= +{ + local vector dir; + local entity oldself; + local float save; + local float take; + + if (!targ.takedamage) + return; + + + // mal: in Coop, don't let bots hurt human teammates - that would be REALLY annoying... + if ( coop && targ != attacker && targ.classname == "player" && attacker.classname == "player" ) { + if ( attacker.flags & FL_ISBOT && !( targ.flags & FL_ISBOT ) ) { + return; + } + } + + // shub kill crash fix. + if (targ.classname == "monster_oldone" && damage < 9999) + return; + + // used by buttons and triggers to set activator for target firing + damage_attacker = attacker; + + // check for quad damage powerup on the attacker + if (attacker.super_damage_finished > time) + damage = damage * 4; + + // save damage based on the target's armor level + save = ceil(targ.armortype*damage); + + if (save >= targ.armorvalue) + { + save = targ.armorvalue; + targ.armortype = 0; // lost all armor + targ.items = targ.items - (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); + } + + targ.armorvalue = targ.armorvalue - save; + take = ceil(damage-save); + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // FIXME: remove after combining shotgun blasts? + if (targ.flags & FL_CLIENT) + { + targ.dmg_take = targ.dmg_take + take; + targ.dmg_save = targ.dmg_save + save; + targ.dmg_inflictor = inflictor; + } + + // figure momentum add + if ( (inflictor != world) && (targ.movetype == MOVETYPE_WALK) ) + { + dir = targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; + dir = normalize(dir); + targ.velocity = targ.velocity + dir*damage*8; + } + + // check for godmode or invincibility + if (targ.flags & FL_GODMODE) + return; + + if (targ.invincible_finished >= time) + { + if (self.invincible_sound < time) + { + sound (targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + self.invincible_sound = time + 2; + } + + return; + } + + // team play damage avoidance + if ( (teamplay == 1 && targ != attacker) && (targ.team > 0)&&(targ.team == attacker.team) ) + return; + + // [NDS] haleyjd: if player and actual health damage is inflicted, flag it here + if (targ.classname == "player" && take != 0) + { + targ.took_damage = 1; + } + + // do the damage + targ.health = targ.health - take; + + if (targ.health <= 0) + { + Killed (targ, attacker); + return; + } + + //react to the damage + oldself = self; + self = targ; + + if ( (self.flags & FL_MONSTER) && attacker != world) + { + // get mad unless of the same class (except for soldiers) + if (self != attacker && attacker != self.enemy) + { + if ( (self.classname != attacker.classname) || (self.classname == "monster_army" ) ) + { + if (self.enemy.classname == "player") + self.oldenemy = self.enemy; + + self.enemy = attacker; + FoundTarget (); + } + } + } + + if (self.th_pain) + { + self.th_pain (attacker, take); + // nightmare mode monsters don't go into pain frames often + if (skill == 3) + self.pain_finished = time + 5; + } + + self = oldself; +}; + +/* +============ +T_RadiusDamage +============ +*/ +void(entity inflictor, entity attacker, float damage, entity ignore) T_RadiusDamage = +{ + local float points; + local entity head; + local vector org; + + head = findradius(inflictor.origin, damage+40); + + while (head) + { + if (head != ignore) + { + if (head.takedamage) + { + org = head.origin + (head.mins + head.maxs)*0.5; + points = 0.5*vlen (inflictor.origin - org); + + if (points < 0) + points = 0; + + points = damage - points; + + if (head == attacker) + points = points * 0.5; + + if (points > 0) + { + if (CanDamage (head, inflictor)) + { // shambler takes half damage from all explosions + + if (head.classname == "monster_shambler") + T_Damage (head, inflictor, attacker, points*0.5); + else + T_Damage (head, inflictor, attacker, points); + } + } + } + } + head = head.chain; + } +}; + +/* +============ +T_BeamDamage +============ +*/ +void(entity attacker, float damage) T_BeamDamage = +{ + local float points; + local entity head; + + head = findradius(attacker.origin, damage+40); + + while (head) + { + if (head.takedamage) + { + points = 0.5*vlen (attacker.origin - head.origin); + if (points < 0) + points = 0; + + points = damage - points; + + if (head == attacker) + points = points * 0.5; + + if (points > 0) + { + if (CanDamage (head, attacker)) + { + if (head.classname == "monster_shambler") + T_Damage (head, attacker, attacker, points*0.5); + else + T_Damage (head, attacker, attacker, points); + } + } + } + head = head.chain; + } +}; diff --git a/quakec/defs.qc b/quakec/defs.qc new file mode 100644 index 0000000..8784379 --- /dev/null +++ b/quakec/defs.qc @@ -0,0 +1,806 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + + SOURCE FOR GLOBALVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system globals +// +entity self; +entity other; +entity world; +float time; +float frametime; + +float force_retouch; // force all entities to touch triggers + // next frame. this is needed because + // non-moving things don't normally scan + // for triggers, and when a trigger is + // created (like a teleport trigger), it + // needs to catch everything. + // decremented each frame, so set to 2 + // to guarantee everything is touched +string mapname; + +float deathmatch; +float coop; +float teamplay; + +float serverflags; // propagated from level to level, used to + // keep track of completed episodes + +float total_secrets; +float total_monsters; + +float found_secrets; // number of secrets found +float killed_monsters; // number of monsters killed + + +// spawnparms are used to encode information about clients across server +// level changes +float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16; + +// +// global variables set by built in functions +// +vector v_forward, v_up, v_right; // set by makevectors() + +// set by traceline / tracebox +float trace_allsolid; +float trace_startsolid; +float trace_fraction; +vector trace_endpos; +vector trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; + +entity msg_entity; // destination of single entity writes + +// +// required prog functions +// +void() main; // only for testing + +void() StartFrame; + +void() PlayerPreThink; +void() PlayerPostThink; + +void() ClientKill; +void() ClientConnect; +void() PutClientInServer; // call after setting the parm1... parms +void() ClientDisconnect; + +void() SetNewParms; // called when a client first connects to + // a server. sets parms so they can be + // saved off for restarts + +void() SetChangeParms; // call to set parms for self so they can + // be saved for a level transition + +//================================================ +void end_sys_globals; // flag for structure dumping +//================================================ + +/* +============================================================================== + + SOURCE FOR ENTVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system fields (*** = do not set in prog code, maintained by C code) +// +.float modelindex; // *** model index in the precached list +.vector absmin, absmax; // *** origin + mins / maxs + +.float ltime; // local time for entity +.float movetype; +.float solid; + +.vector origin; // *** +.vector oldorigin; // *** +.vector velocity; +.vector angles; +.vector avelocity; + +.vector punchangle; // temp angle adjust from damage or recoil + +.string classname; // spawn function +.string model; +.float frame; +.float skin; +.float effects; + +.vector mins, maxs; // bounding box extents reletive to origin +.vector size; // maxs - mins + +.void() touch; +.void() use; +.void() think; +.void() blocked; // for doors or plats, called when can't push other + +.float nextthink; +.entity groundentity; + +// stats +.float health; +.float frags; +.float weapon; // one of the IT_SHOTGUN, etc flags +.string weaponmodel; +.float weaponframe; +.float currentammo; +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; + +.float items; // bit flags + +.float takedamage; +.entity chain; +.float deadflag; + +.vector view_ofs; // add to origin to get eye point + + +.float button0; // fire +.float button1; // use +.float button2; // jump + +.float impulse; // weapon changes + +.float fixangle; +.vector v_angle; // view / targeting angle for players +.float idealpitch; // calculated pitch angle for lookup up slopes + + +.string netname; + +.entity enemy; + +.float flags; + +.float colormap; +.float team; + +.float max_health; // players maximum health is stored here + +.float teleport_time; // don't back up + +.float armortype; // save this fraction of incoming damage +.float armorvalue; + +.float waterlevel; // 0 = not in, 1 = feet, 2 = waist, 3 = eyes +.float watertype; // a contents value + +.float ideal_yaw; +.float yaw_speed; + +.entity aiment; + +.entity goalentity; // a movetarget or an enemy + +.float spawnflags; + +.string target; +.string targetname; + +// damage is accumulated through a frame. and sent as one single +// message, so the super shotgun doesn't generate huge messages +.float dmg_take; +.float dmg_save; +.entity dmg_inflictor; + +.entity owner; // who launched a missile +.vector movedir; // mostly for doors, but also used for waterjump + +.string message; // trigger messages + +.float sounds; // either a cd track number or sound number + +.string noise, noise1, noise2, noise3; // contains names of wavs to play + +//================================================ +void end_sys_fields; // flag for structure dumping +//================================================ + +/* +============================================================================== + + VARS NOT REFERENCED BY C CODE + +============================================================================== +*/ + + +// +// constants +// + +float FALSE = 0; +float TRUE = 1; + +// edict.flags +float FL_FLY = 1; +float FL_SWIM = 2; +float FL_CLIENT = 8; // set for all client edicts +float FL_INWATER = 16; // for enter / leave water splash +float FL_MONSTER = 32; +float FL_GODMODE = 64; // player cheat +float FL_NOTARGET = 128; // player cheat +float FL_ITEM = 256; // extra wide size for bonus items +float FL_ONGROUND = 512; // standing on something +float FL_PARTIALGROUND = 1024; // not all corners are valid +float FL_WATERJUMP = 2048; // player jumping out of water +float FL_JUMPRELEASED = 4096; // for jump debouncing +float FL_ISBOT = 8192; // mal: this will be set on bot players +float FL_NO_PLAYERS = 16384; // mal: used by the spawn point editor +float FL_NO_MONSTERS = 32768; // mal: used by the spawn point editor +float FL_NO_BOTS = 65536; // mal: any item marked with this will be ignored by bots. +float FL_OBJECTIVE = 131072; // mal: any item marked with this is an objective of some kind. + +// edict.movetype values +float MOVETYPE_NONE = 0; // never moves +//float MOVETYPE_ANGLENOCLIP = 1; +//float MOVETYPE_ANGLECLIP = 2; +float MOVETYPE_WALK = 3; // players only +float MOVETYPE_STEP = 4; // discrete, not real time unless fall +float MOVETYPE_FLY = 5; +float MOVETYPE_TOSS = 6; // gravity +float MOVETYPE_PUSH = 7; // no clip to world, push and crush +float MOVETYPE_NOCLIP = 8; +float MOVETYPE_FLYMISSILE = 9; // fly with extra size against monsters +float MOVETYPE_BOUNCE = 10; +float MOVETYPE_GIB = 11; // like MOVETYPE_BOUNCE, but with adjustable gravity + +// edict.solid values +float SOLID_NOT = 0; // no interaction with other objects +float SOLID_TRIGGER = 1; // touch on edge, but not blocking +float SOLID_BBOX = 2; // touch on edge, block +float SOLID_SLIDEBOX = 3; // touch on edge, but not an onground +float SOLID_BSP = 4; // bsp clip, touch on edge, block +float SOLID_CORPSE = 5; // reports touch and can shoot it, but not blocking otherwise. + +// range values +float RANGE_MELEE = 0; +float RANGE_NEAR = 1; +float RANGE_MID = 2; +float RANGE_FAR = 3; + +// deadflag values + +float DEAD_NO = 0; +float DEAD_DYING = 1; +float DEAD_DEAD = 2; +float DEAD_RESPAWNABLE = 3; + +// takedamage values + +float DAMAGE_NO = 0; +float DAMAGE_YES = 1; +float DAMAGE_AIM = 2; + +// items +float IT_AXE = 4096; +float IT_SHOTGUN = 1; +float IT_SUPER_SHOTGUN = 2; +float IT_NAILGUN = 4; +float IT_SUPER_NAILGUN = 8; +float IT_GRENADE_LAUNCHER = 16; +float IT_ROCKET_LAUNCHER = 32; +float IT_LIGHTNING = 64; +float IT_EXTRA_WEAPON = 128; + +float IT_SHELLS = 256; +float IT_NAILS = 512; +float IT_ROCKETS = 1024; +float IT_CELLS = 2048; + +float IT_ARMOR1 = 8192; +float IT_ARMOR2 = 16384; +float IT_ARMOR3 = 32768; +float IT_SUPERHEALTH = 65536; + +float IT_KEY1 = 131072; +float IT_KEY2 = 262144; + +float IT_INVISIBILITY = 524288; +float IT_INVULNERABILITY = 1048576; +float IT_SUIT = 2097152; +float IT_QUAD = 4194304; + +// point content values + +float CONTENT_EMPTY = -1; +float CONTENT_SOLID = -2; +float CONTENT_WATER = -3; +float CONTENT_SLIME = -4; +float CONTENT_LAVA = -5; +float CONTENT_SKY = -6; + +float STATE_TOP = 0; +float STATE_BOTTOM = 1; +float STATE_UP = 2; +float STATE_DOWN = 3; + +vector VEC_ORIGIN = '0 0 0'; +vector VEC_HULL_MIN = '-16 -16 -24'; +vector VEC_HULL_MAX = '16 16 32'; + +vector VEC_HULL2_MIN = '-32 -32 -24'; +vector VEC_HULL2_MAX = '32 32 64'; + +// protocol bytes +float SVC_TEMPENTITY = 23; +float SVC_KILLEDMONSTER = 27; +float SVC_FOUNDSECRET = 28; +float SVC_INTERMISSION = 30; +float SVC_FINALE = 31; +float SVC_CDTRACK = 32; +float SVC_SELLSCREEN = 33; +float SVC_SPAWNEDMONSTER = 39; +float SVC_ACHIEVEMENT = 52; + + +float TE_SPIKE = 0; +float TE_SUPERSPIKE = 1; +float TE_GUNSHOT = 2; +float TE_EXPLOSION = 3; +float TE_TAREXPLOSION = 4; +float TE_LIGHTNING1 = 5; +float TE_LIGHTNING2 = 6; +float TE_WIZSPIKE = 7; +float TE_KNIGHTSPIKE = 8; +float TE_LIGHTNING3 = 9; +float TE_LAVASPLASH = 10; +float TE_TELEPORT = 11; + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +float CHAN_AUTO = 0; +float CHAN_WEAPON = 1; +float CHAN_VOICE = 2; +float CHAN_ITEM = 3; +float CHAN_BODY = 4; + +float ATTN_NONE = 0; +float ATTN_NORM = 1; +float ATTN_IDLE = 2; +float ATTN_STATIC = 3; + +// update types + +float UPDATE_GENERAL = 0; +float UPDATE_STATIC = 1; +float UPDATE_BINARY = 2; +float UPDATE_TEMP = 3; + +// entity effects + +float EF_BRIGHTFIELD = 1; +float EF_MUZZLEFLASH = 2; +float EF_BRIGHTLIGHT = 4; +float EF_DIMLIGHT = 8; +float EF_QUADLIGHT = 16; +float EF_PENTALIGHT = 32; +float EF_CANDLELIGHT = 64; + +// messages +float MSG_BROADCAST = 0; // unreliable to all +float MSG_ONE = 1; // reliable to one (msg_entity) +float MSG_ALL = 2; // reliable to all +float MSG_INIT = 3; // write to the init string + +// team numbers for Coop +float TEAM_MONSTERS = 0; +float TEAM_HUMANS = 1; + +// mal: basic colors - based on the Quake color palette. +float COLOR_RED = 251; +float COLOR_GREEN = 184; +float COLOR_BLUE = 208; +float COLOR_YELLOW = 192; +float COLOR_WHITE = 254; +float COLOR_BLACK = 0; +float COLOR_CYAN = 244; +float COLOR_ORANGE = 95; + +float DRAW_AI_SINGLE_FRAME = .1; // mal: use for AI that think every 10th of a second +float DRAW_GAME_SINGLE_FRAME = -1; // mal: use in game code that updates every frame. + +// player expanded settings flags +float PEF_CHANGEONLYNEW = 1; +float PEF_CHANGENEVER = 2; + +// mal: codes that the QuakeC built-ins "bot_movetopoint" and "bot_followentity" will return each time they're called +float BOT_GOAL_ERROR = 0; // can't do this goal for some reason. +float BOT_GOAL_SUCCESS = 1; // goal as requested is complete. +float BOT_GOAL_IN_PROGRESS = 2; // goal is in progress + +// mal: codes that the QuakeC built-in "walkpathtogoal" will return each time it's called +float PATH_ERROR = 0; // something bad happened ( no nav nodes, no nearby nodes, no path, etc. ). +float PATH_REACHED_GOAL = 1; // reached whatever we were trying to get to. +float PATH_REACHED_PATH_END = 2; // reached the end of the path - can now move directly to target. +float PATH_MOVE_BLOCKED = 3; // something ( or someone ) is in our way. +float PATH_IN_PROGRESS = 4; // path was found, and we're following it. + +//================================================ + +// +// globals +// +float movedist; +float gameover; // set when a rule exits + +string string_null; // null string, nothing should be held here + +entity newmis; // launch_spike sets this after spawning it + +entity activator; // the entity that activated a trigger or brush + +entity damage_attacker; // set by T_Damage +float framecount; + +float skill; +float campaign_valid; +float campaign; + +float cheats_allowed; // mal: let's us run cheat commands in DM/Coop games. + +//================================================ + +// +// world fields (FIXME: make globals) +// +.string wad; +.string map; + +float WORLDTYPE_MEDIEVAL = 0; +float WORLDTYPE_METAL = 1; +float WORLDTYPE_BASE = 2; +.float worldtype; + +//================================================ + +.string killtarget; + +// +// quakeed fields +// +.float light_lev; // not used by game, but parsed by light util +.float style; + + +// +// monster ai +// +.void() th_stand; +.void() th_walk; +.void() th_run; +.void() th_missile; +.void() th_melee; +.void(entity attacker, float damage) th_pain; +.void() th_die; + +.entity oldenemy; // mad at this player before taking damage + +.float speed; + +.float lefty; + +.float search_time; + +// Attack state ( not really used in Vanilla ) +.float attack_state; +float AS_STRAIGHT = 1; +float AS_SLIDING = 2; +float AS_MELEE = 3; +float AS_MISSILE = 4; + +.float allowPathFind; // not used in Vanilla, but added for demonstration purposes... + +// Combat style +.float combat_style; +float CS_NONE = 0; // default - used for bosses or other monsters that don't move +float CS_RANGED = 1; +float CS_MELEE = 2; +float CS_MIXED = 3; // can do ranged or melee equally. + +// +// player only fields +// +.float walkframe; + +.float attack_finished; +.float pain_finished; + +.float invincible_finished; +.float invisible_finished; +.float super_damage_finished; +.float radsuit_finished; + +.float invincible_time, invincible_sound; +.float invisible_time, invisible_sound; +.float super_time, super_sound; +.float rad_time; +.float fly_sound; + +.float healthrot_nextcheck; // if grabbed Mega-Health, the next time we'll rot the players health + +.float axhitme; + +.float show_hostile; // set to time+0.2 whenever a client fires a + // weapon or takes damage. Used to alert + // monsters that otherwise would let the player go +.float jump_flag; // player jump flag +.float swim_flag; // player swimming sound flag +.float air_finished; // when time > air_finished, start drowning +.float bubble_count; // keeps track of the number of bubbles +.string deathtype; // keeps track of how the player died +.float fired_weapon; // [NDS] haleyjd: if player has fired a weapon, for achievement +.float took_damage; // [NDS] haleyjd: if player has taken damage, for achievement + +// +// object stuff +// +.string mdl; +.vector mangle; // angle at start + +.vector oldorigin; // only used by secret door + +.float t_length, t_width; + + +// +// doors, etc +// +.vector dest, dest1, dest2; +.float wait; // time from firing to restarting +.float delay; // time from activation to firing +.entity trigger_field; // door's trigger entity +.string noise4; + +// +// monsters +// +.float pausetime; +.entity movetarget; + + +// +// doors +// +.float aflag; +.float dmg; // damage done by door when hit + +// +// misc +// +.float cnt; // misc flag + +// +// subs +// +.void() think1; +.vector finaldest, finalangle; + +// +// triggers +// +.float count; // for counting triggers + + +// +// plats / doors / buttons +// +.float lip; +.float state; +.vector pos1, pos2; // top and bottom positions +.float height; + +// +// sounds +// +.float waitmin, waitmax; +.float distance; +.float volume; + + +.string killstring; // monster kill message + +.float spawn_deferred; + +void makevectorsfixed(vector ang); + +//=========================================================================== + + +// +// builtin functions +// + +void(vector ang) makevectors = #1; // sets v_forward, etc globals +void(entity e, vector o) setorigin = #2; +void(entity e, string m) setmodel = #3; // set movetype and solid first +void(entity e, vector min, vector max) setsize = #4; +// #5 was removed +void() break = #6; +float() random = #7; // returns 0 - 1 +void(entity e, float chan, string samp, float vol, float atten) sound = #8; +vector(vector v) normalize = #9; +void(string e) error = #10; +void(string e) objerror = #11; +float(vector v) vlen = #12; +float(vector v) vectoyaw = #13; +entity() spawn = #14; +void(entity e) remove = #15; + +// sets trace_* globals +// nomonsters can be: +// An entity will also be ignored for testing if forent == test, +// forent->owner == test, or test->owner == forent +// a forent of world is ignored +void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16; + +entity() checkclient = #17; // returns a client to look for +entity(entity start, .string fld, string match) find = #18; +string(string s) precache_sound = #19; +string(string s) precache_model = #20; +void(entity client, string s)stuffcmd = #21; +entity(vector org, float rad) findradius = #22; +void(string s, ...) bprint = #0:ex_bprint; +void(entity client, string s, ...) sprint = #0:ex_sprint; +void(string s, ...) dprint = #25; +string(float f) ftos = #26; +string(vector v) vtos = #27; +void() coredump = #28; // prints all edicts +void() traceon = #29; // turns statment trace on +void() traceoff = #30; +void(entity e) eprint = #31; // prints an entire edict +float(float yaw, float dist) walkmove = #32; // returns TRUE or FALSE +// #33 was removed +float() droptofloor= #34; // TRUE if landed on floor +void(float style, string value) lightstyle = #35; +float(float v) rint = #36; // round to nearest int +float(float v) floor = #37; // largest integer <= v +float(float v) ceil = #38; // smallest integer >= v +// #39 was removed +float(entity e) checkbottom = #40; // true if self is on ground +float(vector v) pointcontents = #41; // returns a CONTENT_* +// #42 was removed +float(float f) fabs = #43; +vector(entity e, float speed) aim = #44; // returns the shooting vector +float(string s) cvar = #45; // return cvar.value +void(string s) localcmd = #46; // put string into local que +entity(entity e) nextent = #47; // for looping through all ents +void(vector o, vector d, float color, float count) particle = #48;// start a particle effect +void() ChangeYaw = #49; // turn towards self.ideal_yaw + // at self.yaw_speed +// #50 was removed +vector(vector v) vectoangles = #51; + +// +// direct client message generation +// +void(float to, float f) WriteByte = #52; +void(float to, float f) WriteChar = #53; +void(float to, float f) WriteShort = #54; +void(float to, float f) WriteLong = #55; +void(float to, float f) WriteCoord = #56; +void(float to, float f) WriteAngle = #57; +void(float to, string s) WriteString = #58; +void(float to, entity s) WriteEntity = #59; + +// +// broadcast client message generation +// + +// void(float f) bWriteByte = #59; +// void(float f) bWriteChar = #60; +// void(float f) bWriteShort = #61; +// void(float f) bWriteLong = #62; +// void(float f) bWriteCoord = #63; +// void(float f) bWriteAngle = #64; +// void(string s) bWriteString = #65; +// void(entity e) bWriteEntity = #66; + +void(float step) movetogoal = #67; + +string(string s) precache_file = #68; // no effect except for -copy +void(entity e) makestatic = #69; +void(string s) changelevel = #70; + +//#71 was removed + +void(string var, string val) cvar_set = #72; // sets cvar.value + +void(entity client, string s, ...) centerprint = #0:ex_centerprint; // sprint, but in middle + +void(vector pos, string samp, float vol, float atten) ambientsound = #74; + +string(string s) precache_model2 = #75; // registered version only +string(string s) precache_sound2 = #76; // registered version only +string(string s) precache_file2 = #77; // registered version only + +void(entity e) setspawnparms = #78; // set parm1... to the + // values at level start + // for coop respawn + +float() finaleFinished = #0:ex_finaleFinished; + +// Plays a sound that only "client" can hear. Useful for announcers... +void localsound( entity client, string sample ) = #0:ex_localsound; + +// Debug rendering functions. +void draw_point( vector point, float colormap, float lifetime, float depthtest ) = #0:ex_draw_point; +void draw_line( vector start, vector end, float colormap, float lifetime, float depthtest ) = #0:ex_draw_line; +void draw_arrow( vector start, vector end, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_arrow; +void draw_ray( vector start, vector direction, float length, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_ray; +void draw_circle( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_circle; +void draw_bounds( vector min, vector max, float colormap, float lifetime, float depthtest ) = #0:ex_draw_bounds; +void draw_worldtext( string s, vector origin, float size, float lifetime, float depthtest ) = #0:ex_draw_worldtext; +void draw_sphere( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_sphere; +void draw_cylinder( vector origin, float halfHeight, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_cylinder; + +// Bot functions that return a value based on "BOT_GOAL_", depending on the status of the goal. +float bot_movetopoint( entity bot, vector point ) = #0:ex_bot_movetopoint; +float bot_followentity( entity bot, entity goal ) = #0:ex_bot_followentity; + +// Function for checking new player configured flags, such as weapon swap behaviour +float CheckPlayerEXFlags( entity playerEnt ) = #0:ex_CheckPlayerEXFlags; + +// Path monsters to the goal using any available nav nodes on the map +float walkpathtogoal( float movedist, vector goal ) = #0:ex_walkpathtogoal; + +float checkextension( string s ) = #99; + +//============================================================================ + +// +// subs.qc +// +void(vector tdest, float tspeed, void() func) SUB_CalcMove; +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt; +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove; +void() SUB_CalcMoveDone; +void() SUB_CalcAngleMoveDone; +void() SUB_Null; +void() SUB_UseTargets; +void() SUB_Remove; + +// +// combat.qc +// +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage; + + +float (entity e, float healamount, float ignore) T_Heal; // health function + +float(entity targ, entity inflictor) CanDamage; + + diff --git a/quakec/doors.qc b/quakec/doors.qc new file mode 100644 index 0000000..2abb5af --- /dev/null +++ b/quakec/doors.qc @@ -0,0 +1,835 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +float DOOR_START_OPEN = 1; +float DOOR_DONT_LINK = 4; +float DOOR_GOLD_KEY = 8; +float DOOR_SILVER_KEY = 16; +float DOOR_TOGGLE = 32; + +/* +Doors are similar to buttons, but can spawn a fat trigger field around them +to open without a touch, and they link together to form simultanious +double/quad doors. + +Door.owner is the master door. If there is only one door, it points to itself. +If multiple doors, all will point to a single one. + +Door.enemy chains from the master door through all doors linked in the chain. +*/ + +/* +============================================================================= + +THINK FUNCTIONS + +============================================================================= +*/ + +void() door_go_down; +void() door_go_up; + +void() door_blocked = +{ + T_Damage (other, self, self, self.dmg); + + // 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.wait >= 0) + { + if (self.state == STATE_DOWN) + door_go_up (); + else + door_go_down (); + } +}; + + +void() door_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + + if (self.spawnflags & DOOR_TOGGLE) + return; // don't come down automatically + + self.think = door_go_down; + self.nextthink = self.ltime + self.wait; +}; + +void() door_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() door_go_down = +{ + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + + if (self.max_health) + { + self.takedamage = DAMAGE_YES; + self.health = self.max_health; + } + + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, door_hit_bottom); +}; + +void() door_go_up = +{ + if (self.state == STATE_UP) + return; // allready going up + + if (self.state == STATE_TOP) + { // reset top wait time + self.nextthink = self.ltime + self.wait; + return; + } + + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, door_hit_top); + + SUB_UseTargets(); +}; + + +/* +============================================================================= + +ACTIVATION FUNCTIONS + +============================================================================= +*/ + +void() door_fire = +{ + local entity oself; + local entity starte; + + if (self.owner != self) + objerror ("door_fire: self.owner != self"); + + // play use key sound + if (self.items) + sound (self, CHAN_ITEM, self.noise4, 1, ATTN_NORM); // door unlock sound bugfix + + self.message = string_null; // no more message + oself = self; + + if (self.spawnflags & DOOR_TOGGLE) + { + if (self.state == STATE_UP || self.state == STATE_TOP) + { + starte = self; + do + { + door_go_down (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + + self = oself; + return; + } + } + + // trigger all paired doors + starte = self; + do + { + door_go_up (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + + self = oself; +}; + + +void() door_use = +{ + local entity oself; + + self.message = string_null; // door message are for touch only + self.owner.message = string_null; + self.enemy.message = string_null; + oself = self; + self = self.owner; + door_fire (); + self = oself; +}; + + +void() door_trigger_touch = +{ + if (other.health <= 0) + return; + + if (time < self.attack_finished) + return; + + self.attack_finished = time + 1; + + activator = other; + + self = self.owner; + door_use (); +}; + + +void() door_killed = +{ + local entity oself; + + oself = self; + self = self.owner; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + door_use (); + self = oself; +}; + + +/* +================ +door_touch + +Prints messages and opens key doors +================ +*/ +void() door_touch = +{ + if (other.classname != "player") + return; + + if (self.owner.attack_finished > time) + return; + + self.owner.attack_finished = time + 2; + + if (self.owner.message != string_null) + { + centerprint (other, self.owner.message); + sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + + // key door stuff + if (!self.items) + return; + + // FIXME: blink key on player's status bar + if ( (self.items & other.items) != self.items ) + { + if (self.owner.items == IT_KEY1) + { + if (world.worldtype == WORLDTYPE_BASE) + { + centerprint (other, "$qc_need_silver_keycard"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == WORLDTYPE_METAL) + { + centerprint (other, "$qc_need_silver_runekey"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == WORLDTYPE_MEDIEVAL) + { + centerprint (other, "$qc_need_silver_key"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + } + else + { + if (world.worldtype == WORLDTYPE_BASE) + { + centerprint (other, "$qc_need_gold_keycard"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == WORLDTYPE_METAL) + { + centerprint (other, "$qc_need_gold_runekey"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == WORLDTYPE_MEDIEVAL) + { + centerprint (other, "$qc_need_gold_key"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + } + + return; + } + + other.items = other.items - self.items; + self.touch = SUB_Null; + + if (self.enemy) + self.enemy.touch = SUB_Null; // get paired door + + door_use (); +}; + +/* +============================================================================= + +SPAWNING FUNCTIONS + +============================================================================= +*/ + + +entity(vector fmins, vector fmaxs) spawn_field = +{ + local entity trigger; + local vector t1, t2; + + trigger = spawn(); + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.owner = self; + trigger.touch = door_trigger_touch; + + t1 = fmins; + t2 = fmaxs; + setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); + return (trigger); +}; + + +float (entity e1, entity e2) EntitiesTouching = +{ + if (e1.mins_x > e2.maxs_x) + return FALSE; + + if (e1.mins_y > e2.maxs_y) + return FALSE; + + if (e1.mins_z > e2.maxs_z) + return FALSE; + + if (e1.maxs_x < e2.mins_x) + return FALSE; + + if (e1.maxs_y < e2.mins_y) + return FALSE; + + if (e1.maxs_z < e2.mins_z) + return FALSE; + + return TRUE; +}; + + +/* +============= +LinkDoors + + +============= +*/ +void() LinkDoors = +{ + local entity t, starte; + local vector cmins, cmaxs; + + if (self.enemy) + return; // already linked by another door + + if (self.spawnflags & 4) + { + self.owner = self.enemy = self; + return; // don't want to link this door + } + + cmins = self.mins; + cmaxs = self.maxs; + + starte = self; + t = self; + + do + { + self.owner = starte; // master door + + if (self.health) + starte.health = self.health; + + if (self.targetname != string_null) + starte.targetname = self.targetname; + + if (self.message != string_null) + starte.message = self.message; + + t = find (t, classname, self.classname); + if (!t) + { + self.enemy = starte; // make the chain a loop + + // shootable, fired, or key doors just needed the owner/enemy links, + // they don't spawn a field + self = self.owner; + + if (self.health) + return; + + if (self.targetname != string_null) + return; + + if (self.items) + return; + + self.owner.trigger_field = spawn_field(cmins, cmaxs); + + return; + } + + if (EntitiesTouching(self,t)) + { + if (t.enemy) + objerror ("cross connected doors"); + + self.enemy = t; + self = t; + + if (t.mins_x < cmins_x) + cmins_x = t.mins_x; + + if (t.mins_y < cmins_y) + cmins_y = t.mins_y; + + if (t.mins_z < cmins_z) + cmins_z = t.mins_z; + + if (t.maxs_x > cmaxs_x) + cmaxs_x = t.maxs_x; + + if (t.maxs_y > cmaxs_y) + cmaxs_y = t.maxs_y; + + if (t.maxs_z > cmaxs_z) + cmaxs_z = t.maxs_z; + } + } while (1 ); + +}; + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). + +Key doors are allways wait -1. + +"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" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +void() func_door = +{ + if (world.worldtype == WORLDTYPE_MEDIEVAL) + { + precache_sound ("doors/medtry.wav"); + precache_sound ("doors/meduse.wav"); + self.noise3 = "doors/medtry.wav"; + self.noise4 = "doors/meduse.wav"; + } + else if (world.worldtype == WORLDTYPE_METAL) + { + precache_sound ("doors/runetry.wav"); + precache_sound ("doors/runeuse.wav"); + self.noise3 = "doors/runetry.wav"; + self.noise4 = "doors/runeuse.wav"; + } + else if (world.worldtype == WORLDTYPE_BASE) + { + precache_sound ("doors/basetry.wav"); + precache_sound ("doors/baseuse.wav"); + self.noise3 = "doors/basetry.wav"; + self.noise4 = "doors/baseuse.wav"; + } + else + { + dprint ("no worldtype set!\n"); + } + + if (self.sounds == 0) + { + precache_sound ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = "misc/null.wav"; + self.noise2 = "misc/null.wav"; + } + + if (self.sounds == 1) + { + precache_sound ("doors/drclos4.wav"); + precache_sound ("doors/doormv1.wav"); + self.noise1 = "doors/drclos4.wav"; + self.noise2 = "doors/doormv1.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("doors/hydro1.wav"); + precache_sound ("doors/hydro2.wav"); + self.noise2 = "doors/hydro1.wav"; + self.noise1 = "doors/hydro2.wav"; + } + + if (self.sounds == 3) + { + precache_sound ("doors/stndr1.wav"); + precache_sound ("doors/stndr2.wav"); + self.noise2 = "doors/stndr1.wav"; + self.noise1 = "doors/stndr2.wav"; + } + + if (self.sounds == 4) + { + precache_sound ("doors/ddoor1.wav"); + precache_sound ("doors/ddoor2.wav"); + self.noise1 = "doors/ddoor2.wav"; + self.noise2 = "doors/ddoor1.wav"; + } + + SetMovedir (); + + self.max_health = self.health; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + self.classname = "func_door"; + + self.blocked = door_blocked; + self.use = door_use; + + if (self.spawnflags & DOOR_SILVER_KEY) + self.items = IT_KEY1; + + if (self.spawnflags & DOOR_GOLD_KEY) + self.items = IT_KEY2; + + if (!self.speed) + self.speed = 100; + + if (!self.wait) + self.wait = 3; + + if (!self.lip) + self.lip = 8; + + if (!self.dmg) + self.dmg = 2; + + self.pos1 = self.origin; + vector movedir_fabs = { fabs(self.movedir[0]), fabs(self.movedir[1]), fabs(self.movedir[2]) }; + self.pos2 = self.pos1 + ((movedir_fabs * self.size) - self.lip) * self.movedir; + + // DOOR_START_OPEN is to allow an entity to be lighted in the closed position + // but spawn in the open position + if (self.spawnflags & DOOR_START_OPEN) + { + setorigin (self, self.pos2); + self.pos2 = self.pos1; + self.pos1 = self.origin; + } + + self.state = STATE_BOTTOM; + + if (self.health) + { + self.takedamage = DAMAGE_YES; + self.th_die = door_killed; + } + + if (self.items) + self.wait = -1; + + self.touch = door_touch; + + // LinkDoors can't be done until all of the doors have been spawned, so + // the sizes can be detected properly. + self.think = LinkDoors; + self.nextthink = self.ltime + 0.1; +}; + +/* +============================================================================= + +SECRET DOORS + +============================================================================= +*/ + +void() fd_secret_move1; +void() fd_secret_move2; +void() fd_secret_move3; +void() fd_secret_move4; +void() fd_secret_move5; +void() fd_secret_move6; +void() fd_secret_done; + +float SECRET_OPEN_ONCE = 1; // stays open +float SECRET_1ST_LEFT = 2; // 1st move is left of arrow +float SECRET_1ST_DOWN = 4; // 1st move is down from arrow +float SECRET_NO_SHOOT = 8; // only opened by trigger +float SECRET_YES_SHOOT = 16; // shootable even if targeted + + +void () fd_secret_use = +{ + local float temp; + + self.health = 10000; + + // exit if still moving around... + if (self.origin != self.oldorigin) + return; + + self.message = string_null; // no more message + + SUB_UseTargets(); // fire all targets / killtargets + + if (!(self.spawnflags & SECRET_NO_SHOOT)) + { + self.th_pain = SUB_Null; + self.takedamage = DAMAGE_NO; + } + + self.velocity = '0 0 0'; + + // Make a sound, wait a little... + sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.nextthink = self.ltime + 0.1; + + temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1 + makevectors(self.mangle); + + if (!self.t_width) + { + if (self.spawnflags & SECRET_1ST_DOWN) + self. t_width = fabs(v_up * self.size); + else + self. t_width = fabs(v_right * self.size); + } + + if (!self.t_length) + self. t_length = fabs(v_forward * self.size); + + if (self.spawnflags & SECRET_1ST_DOWN) + self.dest1 = self.origin - v_up * self.t_width; + else + self.dest1 = self.origin + v_right * (self.t_width * temp); + + self.dest2 = self.dest1 + v_forward * self.t_length; + SUB_CalcMove(self.dest1, self.speed, fd_secret_move1); + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); +}; + +// Wait after first movement... +void () fd_secret_move1 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move2; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +// Start moving sideways w/sound... +void () fd_secret_move2 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest2, self.speed, fd_secret_move3); +}; + +// Wait here until time to go back... +void () fd_secret_move3 = +{ + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + + if (!(self.spawnflags & SECRET_OPEN_ONCE)) + { + self.nextthink = self.ltime + self.wait; + self.think = fd_secret_move4; + } +}; + +// Move backward... +void () fd_secret_move4 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest1, self.speed, fd_secret_move5); +}; + +// Wait 1 second... +void () fd_secret_move5 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move6; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () fd_secret_move6 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done); +}; + +void () fd_secret_done = +{ + if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use; + self.th_die = fd_secret_use; // secret door kill crash fix + } + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () secret_blocked = +{ + if (time < self.attack_finished) + return; + + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; + +/* +================ +secret_touch + +Prints messages +================ +*/ +void() secret_touch = +{ + if (other.classname != "player") + return; + + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + + if (self.message != string_null) + { + centerprint (other, self.message); + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } +}; + + +/*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot +Basic secret door. Slides back, then to the side. Angle determines direction. +wait = # of seconds before coming back +1st_left = 1st move is left of arrow +1st_down = 1st move is down from arrow +always_shoot = even if targeted, keep shootable +t_width = override WIDTH to move back (or height if going down) +t_length = override LENGTH to move sideways +"dmg" damage to inflict when blocked (2 default) + +If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. +"sounds" +1) medieval +2) metal +3) base +*/ + +void () func_door_secret = +{ + if (self.sounds == 0) + self.sounds = 3; + + if (self.sounds == 1) + { + precache_sound ("doors/latch2.wav"); + precache_sound ("doors/winch2.wav"); + precache_sound ("doors/drclos4.wav"); + self.noise1 = "doors/latch2.wav"; + self.noise2 = "doors/winch2.wav"; + self.noise3 = "doors/drclos4.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("doors/airdoor1.wav"); + precache_sound ("doors/airdoor2.wav"); + self.noise2 = "doors/airdoor1.wav"; + self.noise1 = "doors/airdoor2.wav"; + self.noise3 = "doors/airdoor2.wav"; + } + + if (self.sounds == 3) + { + precache_sound ("doors/basesec1.wav"); + precache_sound ("doors/basesec2.wav"); + self.noise2 = "doors/basesec1.wav"; + self.noise1 = "doors/basesec2.wav"; + self.noise3 = "doors/basesec2.wav"; + } + + if (!self.dmg) + self.dmg = 2; + + // Magic formula... + self.mangle = self.angles; + self.angles = '0 0 0'; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.classname = "func_door_secret"; + setmodel (self, self.model); + setorigin (self, self.origin); + + self.touch = secret_touch; + self.blocked = secret_blocked; + self.speed = 50; + self.use = fd_secret_use; + + if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use; + self.th_die = fd_secret_use; + } + + self.oldorigin = self.origin; + + if (!self.wait) + self.wait = 5; // 5 seconds before closing +}; diff --git a/quakec/fight.qc b/quakec/fight.qc new file mode 100644 index 0000000..2c443c6 --- /dev/null +++ b/quakec/fight.qc @@ -0,0 +1,426 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +A monster is in fight mode if it thinks it can effectively attack its +enemy. + +When it decides it can't attack, it goes into hunt mode. +*/ + +float(float v) anglemod; + +void() knight_atk1; +void() knight_runatk1; +void() ogre_smash1; +void() ogre_swing1; + +void() sham_smash1; +void() sham_swingr1; +void() sham_swingl1; + +float() DemonCheckAttack; +void(float side) Demon_Melee; + +void(vector dest) ChooseTurn; + +void() ai_face; + + +float enemy_visible, enemy_infront, enemy_range; +float enemy_yaw; + + +void() knight_attack = +{ + local float len; + + // decide if now is a good swing time + len = vlen(self.enemy.origin+self.enemy.view_ofs - (self.origin+self.view_ofs)); + + if (len<80) + knight_atk1 (); + else + knight_runatk1 (); +}; + +//============================================================================= + +/* +=========== +CheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() CheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + + // see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != targ) + return FALSE; // don't have a clear shot + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (enemy_range == RANGE_MELEE) + { // melee attack + if (self.th_melee) + { + if (self.classname == "monster_knight") + knight_attack (); + else + self.th_melee (); + + return TRUE; + } + } + +// missile attack + if (!self.th_missile) + return FALSE; + + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + { + chance = 0.9; + self.attack_finished = 0; + } + else if (enemy_range == RANGE_NEAR) + { + if (self.th_melee) + chance = 0.2; + else + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + if (self.th_melee) + chance = 0.05; + else + chance = 0.1; + } + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (2*random()); + return TRUE; + } + + return FALSE; +}; + + +/* +============= +ai_face + +Stay facing the enemy +============= +*/ +void() ai_face = +{ + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); +}; + +/* +============= +ai_charge + +The monster is in a melee attack, so get as close as possible to .enemy +============= +*/ +float (entity targ) visible; +float(entity targ) infront; +float(entity targ) range; + +void(float d) ai_charge = +{ + ai_face (); + movetogoal (d); // done in C code... +}; + +void() ai_charge_side = +{ + local vector dtemp; + local float heading; + + // aim to the left of the enemy for a flyby + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); + + makevectorsfixed(self.angles); + dtemp = self.enemy.origin - 30*v_right; + heading = vectoyaw(dtemp - self.origin); + + walkmove(heading, 20); +}; + + +/* +============= +ai_melee + +============= +*/ +void() ai_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +void() ai_melee_side = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + ai_charge_side(); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + if (!CanDamage (self.enemy, self)) + return; + + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +//============================================================================= + +/* +=========== +SoldierCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() SoldierCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + + // see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + return FALSE; // don't have a clear shot + + +// missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.4; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (1 + random()); + + if (random() < 0.3) + self.lefty = !self.lefty; + + return TRUE; + } + + return FALSE; +}; +//============================================================================= + +/* +=========== +ShamCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() ShamCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_visible) + return FALSE; + + targ = self.enemy; + + // see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + if (vlen(spot1 - spot2) > 600) + return FALSE; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + + // missile attack + if (enemy_range == RANGE_FAR) + return FALSE; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (2 + 2*random()); + return TRUE; +}; + +//============================================================================ + +/* +=========== +OgreCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() OgreCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_visible) + return FALSE; + + targ = self.enemy; + + // see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + + // missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + else if (enemy_range == RANGE_NEAR) + chance = 0.10; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (1 + 2*random()); + return TRUE; +}; + diff --git a/quakec/itemnames.qc b/quakec/itemnames.qc new file mode 100644 index 0000000..5c63410 --- /dev/null +++ b/quakec/itemnames.qc @@ -0,0 +1,84 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +string GetNetName( float item_number ) = { + switch ( item_number ) { + case IT_AXE: + return "Axe"; + case IT_SHOTGUN: + return "Shotgun"; + case IT_SUPER_SHOTGUN: + return "Super Shotgun"; + case IT_NAILGUN: + return "Nailgun"; + case IT_SUPER_NAILGUN: + return "Perforator"; + case IT_GRENADE_LAUNCHER: + return "Grenade Launcher"; + case IT_ROCKET_LAUNCHER: + return "Rocket Launcher"; + case IT_LIGHTNING: + return "Lightning Gun"; + case IT_EXTRA_WEAPON: + return "Extra Weapon"; + case IT_SHELLS: + return "Shells"; + case IT_NAILS: + return "Nails"; + case IT_ROCKETS: + return "Rockets"; + case IT_CELLS: + return "Cells"; + case IT_ARMOR1: + return "Green Armor"; + case IT_ARMOR2: + return "Yellow Armor"; + case IT_ARMOR3: + return "Red Armor"; + case IT_SUPERHEALTH: + return "Mega Health"; + case IT_KEY1: { + if ( world.worldtype == WORLDTYPE_MEDIEVAL ) + return "Silver key"; + else if ( world.worldtype == WORLDTYPE_METAL ) + return "Silver runkey"; + else if ( world.worldtype == WORLDTYPE_BASE ) + return "Silver keycard"; + } + case IT_KEY2: { + if ( world.worldtype == WORLDTYPE_MEDIEVAL ) + return "Gold key"; + else if ( world.worldtype == WORLDTYPE_METAL ) + return "Gold runkey"; + else if ( world.worldtype == WORLDTYPE_BASE ) + return "Gold keycard"; + } + case IT_INVISIBILITY: + return "Ring of Shadows"; + case IT_INVULNERABILITY: + return "Pentagram of Protection"; + case IT_SUIT: + return "Biohazard Suit"; + case IT_QUAD: + return "Quad Damage"; + + default: + return string_null; + } +}; \ No newline at end of file diff --git a/quakec/items.qc b/quakec/items.qc new file mode 100644 index 0000000..d7255e1 --- /dev/null +++ b/quakec/items.qc @@ -0,0 +1,1483 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() W_SetCurrentAmmo; +/* ALL LIGHTS SHOULD BE 0 1 0 IN COLOR ALL OTHER ITEMS SHOULD +BE .8 .3 .4 IN COLOR */ + + +void() SUB_regen = +{ + self.model = self.mdl; // restore original model + self.solid = SOLID_TRIGGER; // allow it to be touched again + sound (self, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); // play respawn sound + setorigin (self, self.origin); +}; + + + +/*QUAKED noclass (0 0 0) (-8 -8 -8) (8 8 8) +prints a warning message when spawned +*/ +void() noclass = +{ + dprint ("noclass spawned at"); + dprint (vtos(self.origin)); + dprint ("\n"); + remove (self); +}; + + + +/* +============ +PlaceItem + +plants the object on the floor +============ +*/ +void() PlaceItem = +{ + local float oldz; + + if( self.netname == string_null ) { + if( self.items ) { + self.netname = GetNetName( self.items ); + } else if( self.weapon ) { + self.netname = GetNetName( self.weapon ); + } + } + + self.mdl = self.model; // so it can be restored on respawn + self.flags = FL_ITEM; // make extra wide + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_TOSS; + self.velocity = '0 0 0'; + self.origin_z = self.origin_z + 6; + oldz = self.origin_z; + + if (!droptofloor()) + { + dprint ("Bonus item fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + return; + } +}; + +/* +============ +StartItem + +Sets the clipping size and plants the object on the floor +============ +*/ +void() StartItem = +{ + self.nextthink = time + 0.2; // items start after other solids + self.think = PlaceItem; +}; + +/* +========================================================================= + +HEALTH BOX + +========================================================================= +*/ +// +// T_Heal: add health to an entity, limiting health to max_health +// "ignore" will ignore max_health limit +// +float (entity e, float healamount, float ignore) T_Heal = +{ + if (e.health <= 0) + return 0; + + if ((!ignore) && (e.health >= e.max_health)) + return 0; + + healamount = ceil(healamount); + + e.health = e.health + healamount; + + if ((!ignore) && (e.health >= e.max_health)) + e.health = e.max_health; + + if (e.health > 250) + e.health = 250; + + return 1; +}; + +/*QUAKED item_health (.3 .3 1) (0 0 0) (32 32 32) rotten megahealth +Health box. Normally gives 25 points. +Rotten box heals 5-10 points, +megahealth will add 100 health, then +rot you down to your maximum health limit, +one point per second. +*/ + +float H_ROTTEN = 1; +float H_MEGA = 2; +.float healamount, healtype; +void() health_touch; + +void() item_health = +{ + self.touch = health_touch; + + if (self.spawnflags & H_ROTTEN) + { + precache_model("maps/b_bh10.bsp"); + + precache_sound("items/r_item1.wav"); + setmodel(self, "maps/b_bh10.bsp"); + self.noise = "items/r_item1.wav"; + self.healamount = 15; + self.healtype = 0; + } + else if (self.spawnflags & H_MEGA) + { + precache_model("maps/b_bh100.bsp"); + precache_sound("items/r_item2.wav"); + setmodel(self, "maps/b_bh100.bsp"); + self.noise = "items/r_item2.wav"; + self.healamount = 100; + self.healtype = 2; + } + else + { + precache_model("maps/b_bh25.bsp"); + precache_sound("items/health1.wav"); + setmodel(self, "maps/b_bh25.bsp"); + self.noise = "items/health1.wav"; + self.healamount = 25; + self.healtype = 1; + } + + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +void() health_touch = +{ + if (other.classname != "player") + return; + + if (self.healtype == 2) // Megahealth? Ignore max_health... + { + if (other.health >= 250) + return; + if (!T_Heal(other, self.healamount, 1)) + return; + } + else + { + if (!T_Heal(other, self.healamount, 0)) + return; + } + + sprint(other, "$qc_item_health", ftos(self.healamount)); + + // health touch sound + sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + + stuffcmd (other, "bf\n"); + + self.model = string_null; + self.solid = SOLID_NOT; + + if ( self.healtype == 2 ) { // If we're a MegaHealth - rot down the player's super health + other.items = other.items | IT_SUPERHEALTH; + other.healthrot_nextcheck = time + 5; + } + + if ( deathmatch && deathmatch != 2 ) { // deathmatch 2 is silly old rules + if ( self.healtype == 2 ) { + self.nextthink = time + 120; + } else { + self.nextthink = time + 20; + } + } + + self.think = SUB_regen; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + +/* +=============================================================================== + +ARMOR + +=============================================================================== +*/ + +void() armor_touch = +{ + local float type, value, bit; + + type = 0.3; + value = 100; + bit = IT_ARMOR1; + + if (other.health <= 0) + return; + + if (other.classname != "player") + return; + + if (self.classname == "item_armor1") + { + type = 0.3; + value = 100; + bit = IT_ARMOR1; + } + + if (self.classname == "item_armor2") + { + type = 0.6; + value = 150; + bit = IT_ARMOR2; + } + + if (self.classname == "item_armorInv") + { + type = 0.8; + value = 200; + bit = IT_ARMOR3; + } + + if (other.armortype*other.armorvalue >= type*value) + return; + + other.armortype = type; + other.armorvalue = value; + other.items = other.items - (other.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + bit; + + self.solid = SOLID_NOT; + self.model = string_null; + + if (deathmatch && deathmatch != 2) + self.nextthink = time + 20; + + self.think = SUB_regen; + + sprint(other, "$qc_item_armor"); + // armor touch sound + sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/* +QUAKED item_armor1 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ +void() item_armor1 = +{ + self.touch = armor_touch; + self.armortype = 0.3; + self.armorvalue = 100; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 0; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/* +QUAKED item_armor2 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ +void() item_armor2 = +{ + self.touch = armor_touch; + self.armortype = 0.6; + self.armorvalue = 150; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 1; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/* +QUAKED item_armorInv (0 .5 .8) (-16 -16 0) (16 16 32) +*/ +void() item_armorInv = +{ + self.touch = armor_touch; + self.armortype = 0.8; + self.armorvalue = 200; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 2; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/* +=============================================================================== + +WEAPONS + +=============================================================================== +*/ + +void() bound_other_ammo = +{ + if (other.ammo_shells > 100) + other.ammo_shells = 100; + + if (other.ammo_nails > 200) + other.ammo_nails = 200; + + if (other.ammo_rockets > 100) + other.ammo_rockets = 100; + + if (other.ammo_cells > 100) + other.ammo_cells = 100; +}; + + +float(float w) RankForWeapon = +{ + if (w == IT_LIGHTNING) + return 1; + + if (w == IT_ROCKET_LAUNCHER) + return 2; + + if (w == IT_SUPER_NAILGUN) + return 3; + + if (w == IT_GRENADE_LAUNCHER) + return 4; + + if (w == IT_SUPER_SHOTGUN) + return 5; + + if (w == IT_NAILGUN) + return 6; + + return 7; +}; + +float (float w) WeaponCode = +{ + if (w == IT_SUPER_SHOTGUN) + return 3; + + if (w == IT_NAILGUN) + return 4; + + if (w == IT_SUPER_NAILGUN) + return 5; + + if (w == IT_GRENADE_LAUNCHER) + return 6; + + if (w == IT_ROCKET_LAUNCHER) + return 7; + + if (w == IT_LIGHTNING) + return 8; + + return 1; +}; + +/* +============= +Deathmatch_Weapon + +Deathmatch weapon change rules for picking up a weapon + +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; +============= +*/ +void(float old, float new) Deathmatch_Weapon = +{ + local float or, nr; + + if ( self.flags & FL_ISBOT ) { + return; + } // mal: let bots pick their own weapon. + + // change self.weapon if desired + or = RankForWeapon (self.weapon); + nr = RankForWeapon (new); + + if ( nr < or ) + self.weapon = new; +}; + +/* +============= +weapon_touch +============= +*/ +float() W_BestWeapon; +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon; + +void() weapon_touch = +{ + local float hadammo, best, new, old; + local entity stemp; + local float leave; + + new = other.items; + + if (!(other.flags & FL_CLIENT)) + return; + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + if (coop || deathmatch == 2 || deathmatch == 3 || deathmatch == 5) + leave = 1; + else + leave = 0; + + if (self.classname == "weapon_nailgun") + { + if (leave && (other.items & IT_NAILGUN) ) + return; + + hadammo = other.ammo_nails; + new = IT_NAILGUN; + other.ammo_nails = other.ammo_nails + 30; + } + else if (self.classname == "weapon_supernailgun") + { + if (leave && (other.items & IT_SUPER_NAILGUN) ) + return; + + hadammo = other.ammo_rockets; + new = IT_SUPER_NAILGUN; + other.ammo_nails = other.ammo_nails + 30; + } + else if (self.classname == "weapon_supershotgun") + { + if (leave && (other.items & IT_SUPER_SHOTGUN) ) + return; + + hadammo = other.ammo_rockets; + new = IT_SUPER_SHOTGUN; + other.ammo_shells = other.ammo_shells + 5; + } + else if (self.classname == "weapon_rocketlauncher") + { + if (leave && (other.items & IT_ROCKET_LAUNCHER) ) + return; + + hadammo = other.ammo_rockets; + new = IT_ROCKET_LAUNCHER; + other.ammo_rockets = other.ammo_rockets + 5; + } + else if (self.classname == "weapon_grenadelauncher") + { + if (leave && (other.items & IT_GRENADE_LAUNCHER) ) + return; + + hadammo = other.ammo_rockets; + new = IT_GRENADE_LAUNCHER; + other.ammo_rockets = other.ammo_rockets + 5; + } + else if (self.classname == "weapon_lightning") + { + if (leave && (other.items & IT_LIGHTNING) ) + return; + + hadammo = other.ammo_rockets; + new = IT_LIGHTNING; + other.ammo_cells = other.ammo_cells + 15; + } + else + objerror ("weapon_touch: unknown classname"); + + sprint(other, "$qc_got_item", self.netname); + + // weapon touch sound + sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + bound_other_ammo (); + + // change to the weapon + old = other.items; + other.items = other.items | new; + + stemp = self; + self = other; + + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (!deathmatch) + self.weapon = new; + else + Deathmatch_Weapon (old, new); + } + + W_SetCurrentAmmo(); + + self = stemp; + + if (leave) + return; + + // remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + + if (deathmatch && deathmatch != 2) + self.nextthink = time + 30; + + self.think = SUB_regen; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/* +QUAKED weapon_supershotgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ +void() weapon_supershotgun = +{ + precache_model ("progs/g_shot.mdl"); + setmodel (self, "progs/g_shot.mdl"); + self.weapon = IT_SUPER_SHOTGUN; + self.netname = "$qc_double_shotgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_nailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_nailgun = +{ + precache_model ("progs/g_nail.mdl"); + setmodel (self, "progs/g_nail.mdl"); + self.weapon = IT_NAILGUN; + self.netname = "$qc_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_supernailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_supernailgun = +{ + precache_model ("progs/g_nail2.mdl"); + setmodel (self, "progs/g_nail2.mdl"); + self.weapon = IT_SUPER_NAILGUN; + self.netname = "$qc_super_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_grenadelauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_grenadelauncher = +{ + precache_model ("progs/g_rock.mdl"); + setmodel (self, "progs/g_rock.mdl"); + self.weapon = IT_GRENADE_LAUNCHER; + self.netname = "$qc_grenade_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_rocketlauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_rocketlauncher = +{ + precache_model ("progs/g_rock2.mdl"); + setmodel (self, "progs/g_rock2.mdl"); + self.weapon = IT_ROCKET_LAUNCHER; + self.netname = "$qc_rocket_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/*QUAKED weapon_lightning (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_lightning = +{ + precache_model ("progs/g_light.mdl"); + setmodel (self, "progs/g_light.mdl"); + self.weapon = IT_LIGHTNING; + self.netname = "$qc_thunderbolt"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/* +=============================================================================== + +AMMO + +=============================================================================== +*/ + +void() ammo_touch = +{ + local entity stemp; + local float best; + + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + +// shotgun + if (self.weapon == 1) + { + if (other.ammo_shells >= 100) + return; + + other.ammo_shells = other.ammo_shells + self.aflag; + } + + // spikes + if (self.weapon == 2) + { + if (other.ammo_nails >= 200) + return; + + other.ammo_nails = other.ammo_nails + self.aflag; + } + + // rockets + if (self.weapon == 3) + { + if (other.ammo_rockets >= 100) + return; + + other.ammo_rockets = other.ammo_rockets + self.aflag; + } + + // cells + if (self.weapon == 4) + { + if (other.ammo_cells >= 100) + return; + other.ammo_cells = other.ammo_cells + self.aflag; + } + + bound_other_ammo (); + + sprint(other, "$qc_got_item", self.netname); + + // ammo touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + // change to a better weapon if appropriate + if ( other.weapon == best && (W_WantsToChangeWeapon(other, 0, 1) == 1)) + { + stemp = self; + self = other; + self.weapon = W_BestWeapon(); + W_SetCurrentAmmo (); + self = stemp; + } + + // if changed current ammo, update it + stemp = self; + self = other; + W_SetCurrentAmmo(); + self = stemp; + + // remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + + if (deathmatch) { + if (deathmatch == 3 || deathmatch == 5) { + self.nextthink = time + 15; + } else if (deathmatch != 2) { + self.nextthink = time + 30; + } + } + + self.think = SUB_regen; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + +float WEAPON_BIG2 = 1; + +/* +QUAKED item_shells (0 .5 .8) (0 0 0) (32 32 32) big +*/ +void() item_shells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_shell1.bsp"); + setmodel (self, "maps/b_shell1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_shell0.bsp"); + setmodel (self, "maps/b_shell0.bsp"); + self.aflag = 20; + } + + self.weapon = 1; + self.netname = "$qc_shells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/* +QUAKED item_spikes (0 .5 .8) (0 0 0) (32 32 32) big +*/ +void() item_spikes = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_nail1.bsp"); + setmodel (self, "maps/b_nail1.bsp"); + self.aflag = 50; + } + else + { + precache_model ("maps/b_nail0.bsp"); + setmodel (self, "maps/b_nail0.bsp"); + self.aflag = 25; + } + + self.weapon = 2; + self.netname = "$qc_nails"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/* +QUAKED item_rockets (0 .5 .8) (0 0 0) (32 32 32) big +*/ +void() item_rockets = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_rock1.bsp"); + setmodel (self, "maps/b_rock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_rock0.bsp"); + setmodel (self, "maps/b_rock0.bsp"); + self.aflag = 5; + } + + self.weapon = 3; + self.netname = "$qc_rockets"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/* +QUAKED item_cells (0 .5 .8) (0 0 0) (32 32 32) big +*/ +void() item_cells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_batt1.bsp"); + setmodel (self, "maps/b_batt1.bsp"); + self.aflag = 12; + } + else + { + precache_model ("maps/b_batt0.bsp"); + setmodel (self, "maps/b_batt0.bsp"); + self.aflag = 6; + } + + self.weapon = 4; + self.netname = "$qc_cells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/* +QUAKED item_weapon (0 .5 .8) (0 0 0) (32 32 32) shotgun rocket spikes big +DO NOT USE THIS!!!! IT WILL BE REMOVED! +*/ + +float WEAPON_SHOTGUN = 1; +float WEAPON_ROCKET = 2; +float WEAPON_SPIKES = 4; +float WEAPON_BIG = 8; +void() item_weapon = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_SHOTGUN) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_shell1.bsp"); + setmodel (self, "maps/b_shell1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_shell0.bsp"); + setmodel (self, "maps/b_shell0.bsp"); + self.aflag = 20; + } + self.weapon = 1; + self.netname = "$qc_shells"; + } + + if (self.spawnflags & WEAPON_SPIKES) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_nail1.bsp"); + setmodel (self, "maps/b_nail1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_nail0.bsp"); + setmodel (self, "maps/b_nail0.bsp"); + self.aflag = 20; + } + self.weapon = 2; + self.netname = "$qc_spikes"; + } + + if (self.spawnflags & WEAPON_ROCKET) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_rock1.bsp"); + setmodel (self, "maps/b_rock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_rock0.bsp"); + setmodel (self, "maps/b_rock0.bsp"); + self.aflag = 5; + } + self.weapon = 3; + self.netname = "$qc_rockets"; + } + + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/* +=============================================================================== + +KEYS + +=============================================================================== +*/ + +void() key_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + if (other.items & self.items) + return; + + sprint(other, "$qc_got_item", self.netname); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + other.items = other.items | self.items; + + if (!coop) + { + self.solid = SOLID_NOT; + self.model = string_null; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void() key_setsounds = +{ + if (world.worldtype == WORLDTYPE_MEDIEVAL) + { + precache_sound ("misc/medkey.wav"); + self.noise = "misc/medkey.wav"; + } + + if (world.worldtype == WORLDTYPE_METAL) + { + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + } + + if (world.worldtype == WORLDTYPE_BASE) + { + precache_sound2 ("misc/basekey.wav"); + self.noise = "misc/basekey.wav"; + } +}; + +/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) +SILVER key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key1 = +{ + if (world.worldtype == WORLDTYPE_MEDIEVAL) + { + precache_model ("progs/w_s_key.mdl"); + setmodel (self, "progs/w_s_key.mdl"); + self.netname = "$qc_silver_key"; + } + else if (world.worldtype == WORLDTYPE_METAL) + { + precache_model ("progs/m_s_key.mdl"); + setmodel (self, "progs/m_s_key.mdl"); + self.netname = "$qc_silver_runekey"; + } + else if (world.worldtype == WORLDTYPE_BASE) + { + precache_model2 ("progs/b_s_key.mdl"); + setmodel (self, "progs/b_s_key.mdl"); + self.netname = "$qc_silver_keycard"; + } + + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY1; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) +GOLD key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key2 = +{ + if (world.worldtype == WORLDTYPE_MEDIEVAL) + { + precache_model ("progs/w_g_key.mdl"); + setmodel (self, "progs/w_g_key.mdl"); + self.netname = "$qc_gold_key"; + } + + if (world.worldtype == WORLDTYPE_METAL) + { + precache_model ("progs/m_g_key.mdl"); + setmodel (self, "progs/m_g_key.mdl"); + self.netname = "$qc_gold_runekey"; + } + + if (world.worldtype == WORLDTYPE_BASE) + { + precache_model2 ("progs/b_g_key.mdl"); + setmodel (self, "progs/b_g_key.mdl"); + self.netname = "$qc_gold_keycard"; + } + + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY2; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +END OF LEVEL RUNES + +=============================================================================== +*/ + +void() sigil_touch = +{ + + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + centerprint(other, "$qc_got_rune"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + self.model = string_null; + serverflags = serverflags | (self.spawnflags & 15); + self.classname = string_null; // so rune doors won't find it + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_sigil (0 .5 .8) (-16 -16 -24) (16 16 32) E1 E2 E3 E4 +End of level sigil, pick up to end episode and return to jrstart. +*/ + +void() item_sigil = +{ + if (!self.spawnflags) + objerror ("no spawnflags"); + + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + + if (self.spawnflags & 1) + { + precache_model ("progs/end1.mdl"); + setmodel (self, "progs/end1.mdl"); + } + + if (self.spawnflags & 2) + { + precache_model2 ("progs/end2.mdl"); + setmodel (self, "progs/end2.mdl"); + } + + if (self.spawnflags & 4) + { + precache_model2 ("progs/end3.mdl"); + setmodel (self, "progs/end3.mdl"); + } + + if (self.spawnflags & 8) + { + precache_model2 ("progs/end4.mdl"); + setmodel (self, "progs/end4.mdl"); + } + + self.touch = sigil_touch; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/* +=============================================================================== + +POWERUPS + +=============================================================================== +*/ + +void() powerup_touch; + + +void() powerup_touch = +{ + + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + sprint(other, "$qc_got_item", self.netname); + + if (deathmatch) + { + self.mdl = self.model; + + if ((self.classname == "item_artifact_invulnerability") || (self.classname == "item_artifact_invisibility")) + self.nextthink = time + 60*5; + else + self.nextthink = time + 60; + + self.think = SUB_regen; + } + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | self.items; + self.model = string_null; + + // do the apropriate action + if (self.classname == "item_artifact_envirosuit") + { + other.rad_time = 1; + other.radsuit_finished = time + 30; + } + + if (self.classname == "item_artifact_invulnerability") + { + other.invincible_time = 1; + other.invincible_finished = time + 30; + } + + if (self.classname == "item_artifact_invisibility") + { + other.invisible_time = 1; + other.invisible_finished = time + 30; + } + + if (self.classname == "item_artifact_super_damage") + { + other.super_time = 1; + other.super_damage_finished = time + 30; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + + +/*QUAKED item_artifact_invulnerability (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invulnerable for 30 seconds +*/ +void() item_artifact_invulnerability = +{ + self.touch = powerup_touch; + + precache_model ("progs/invulner.mdl"); + precache_sound ("items/protect.wav"); + precache_sound ("items/protect2.wav"); + precache_sound ("items/protect3.wav"); + self.noise = "items/protect.wav"; + setmodel (self, "progs/invulner.mdl"); + self.netname = "$qc_pentagram_of_protection"; + self.items = IT_INVULNERABILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_artifact_envirosuit (0 .5 .8) (-16 -16 -24) (16 16 32) +Player takes no damage from water or slime for 30 seconds +*/ +void() item_artifact_envirosuit = +{ + self.touch = powerup_touch; + + precache_model ("progs/suit.mdl"); + precache_sound ("items/suit.wav"); + precache_sound ("items/suit2.wav"); + self.noise = "items/suit.wav"; + setmodel (self, "progs/suit.mdl"); + self.netname = "$qc_biosuit"; + self.items = IT_SUIT; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_invisibility (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invisible for 30 seconds +*/ +void() item_artifact_invisibility = +{ + self.touch = powerup_touch; + + precache_model ("progs/invisibl.mdl"); + precache_sound ("items/inv1.wav"); + precache_sound ("items/inv2.wav"); + precache_sound ("items/inv3.wav"); + self.noise = "items/inv1.wav"; + setmodel (self, "progs/invisibl.mdl"); + self.netname = "$qc_ring_of_shadows"; + self.items = IT_INVISIBILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_super_damage (0 .5 .8) (-16 -16 -24) (16 16 32) +The next attack from the player will do 4x damage +*/ +void() item_artifact_super_damage = +{ + self.touch = powerup_touch; + + precache_model ("progs/quaddama.mdl"); + precache_sound ("items/damage.wav"); + precache_sound ("items/damage2.wav"); + precache_sound ("items/damage3.wav"); + self.noise = "items/damage.wav"; + setmodel (self, "progs/quaddama.mdl"); + self.netname = "$qc_quad_damage"; + self.items = IT_QUAD; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +PLAYER BACKPACKS + +=============================================================================== +*/ + +void() BackpackTouch = +{ + local float best, old, new; + local entity stemp; + local float acount; + + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + acount = 0; + sprint(other, "$qc_backpack_got"); + + if (self.items) + { + if ((other.items & self.items) == 0) + { + acount = 1; + sprint(other, "{}", self.netname); + } + } + + // if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + // change weapons + other.ammo_shells = other.ammo_shells + self.ammo_shells; + other.ammo_nails = other.ammo_nails + self.ammo_nails; + other.ammo_rockets = other.ammo_rockets + self.ammo_rockets; + other.ammo_cells = other.ammo_cells + self.ammo_cells; + + new = self.items; + if (!new) + new = other.weapon; + + old = other.items; + other.items = other.items | self.items; + + bound_other_ammo (); + + if (self.ammo_shells) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_shells", ftos(self.ammo_shells)); + } + + if (self.ammo_nails) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_nails", ftos(self.ammo_nails)); + } + + if (self.ammo_rockets) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_rockets", ftos(self.ammo_rockets)); + } + if (self.ammo_cells) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_cells", ftos(self.ammo_cells)); + } + + sprint (other, "\n"); + + // backpack touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + remove(self); + self = other; + + // change to the weapon + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (self.flags & FL_INWATER) + { + if (new != IT_LIGHTNING) + { + Deathmatch_Weapon (old, new); + } + } + + else + { + Deathmatch_Weapon (old, new); + } + } + + W_SetCurrentAmmo (); +}; + +/* +=============== +DropBackpack +=============== +*/ +void() DropBackpack = +{ + local entity item; + + if (!(self.ammo_shells + self.ammo_nails + self.ammo_rockets + self.ammo_cells)) + return; // nothing in it + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.items = self.weapon; + + item.classname = "item_backpack"; // added for bots. + + if (item.items == IT_AXE) + item.netname = "$qc_axe"; + else if (item.items == IT_SHOTGUN) + item.netname = "$qc_shotgun"; + else if (item.items == IT_SUPER_SHOTGUN) + item.netname = "$qc_double_shotgun"; + else if (item.items == IT_NAILGUN) + item.netname = "$qc_nailgun"; + else if (item.items == IT_SUPER_NAILGUN) + item.netname = "$qc_super_nailgun"; + else if (item.items == IT_GRENADE_LAUNCHER) + item.netname = "$qc_grenade_launcher"; + else if (item.items == IT_ROCKET_LAUNCHER) + item.netname = "$qc_rocket_launcher"; + else if (item.items == IT_LIGHTNING) + item.netname = "$qc_thunderbolt"; + + item.ammo_shells = self.ammo_shells; + item.ammo_nails = self.ammo_nails; + item.ammo_rockets = self.ammo_rockets; + item.ammo_cells = self.ammo_cells; + + // include some ammo in the backpack if the weapon is being given + if (item.ammo_shells < 5 && (item.items == IT_SHOTGUN || item.items == IT_SUPER_SHOTGUN)) { + item.ammo_shells = 5; + } + + if (item.ammo_nails < 20 && (item.items == IT_NAILGUN || item.items == IT_SUPER_NAILGUN)) { + item.ammo_nails = 20; + } + + if (item.ammo_rockets < 5 && (item.items == IT_GRENADE_LAUNCHER || item.items == IT_ROCKET_LAUNCHER)) { + item.ammo_rockets = 5; + } + + if (item.ammo_cells < 15 && item.items == IT_LIGHTNING) { + item.ammo_cells = 15; + } + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + setmodel (item, "progs/backpack.mdl"); + setsize (item, '-16 -16 0', '16 16 56'); + item.touch = BackpackTouch; + + item.nextthink = time + 120; // remove after 2 minutes + item.think = SUB_Remove; +}; diff --git a/quakec/misc.qc b/quakec/misc.qc new file mode 100644 index 0000000..9a5307f --- /dev/null +++ b/quakec/misc.qc @@ -0,0 +1,707 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void() info_null = +{ + remove(self); +}; + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void() info_notnull = +{ +}; + +//============================================================================ + +float START_OFF = 1; + +void() light_use = +{ + if (self.spawnflags & START_OFF) + { + lightstyle(self.style, "m"); + self.spawnflags = self.spawnflags - START_OFF; + } + else + { + lightstyle(self.style, "a"); + self.spawnflags = self.spawnflags + START_OFF; + } +}; + +/*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, it will toggle between on or off. +*/ +void() light = +{ + if (!self.targetname) + { // inert light + remove(self); + return; + } + + if (self.style >= 32) + { + self.use = light_use; + + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } +}; + +/*QUAKED light_fluoro (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, it will toggle between on or off. +Makes steady fluorescent humming sound +*/ +void() light_fluoro = +{ + if (self.style >= 32) + { + self.use = light_use; + + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } + + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_fluorospark (0 1 0) (-8 -8 -8) (8 8 8) +Non-displayed light. +Default light value is 300 +Default style is 10 +Makes sparking, broken fluorescent sound +*/ +void() light_fluorospark = +{ + if (!self.style) + self.style = 10; + + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_globe (0 1 0) (-8 -8 -8) (8 8 8) +Sphere globe light. +Default light value is 300 +Default style is 0 +*/ +void() light_globe = +{ + precache_model ("progs/s_light.spr"); + setmodel (self, "progs/s_light.spr"); + makestatic (self); +}; + +void() FireAmbient = +{ + precache_sound ("ambience/fire1.wav"); + + // attenuate fast + ambientsound (self.origin, "ambience/fire1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_torch_small_walltorch (0 .5 0) (-10 -10 -20) (10 10 20) +Short wall torch +Default light value is 200 +Default style is 0 +*/ +void() light_torch_small_walltorch = +{ + precache_model ("progs/flame.mdl"); + setmodel (self, "progs/flame.mdl"); + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_large_yellow (0 1 0) (-10 -10 -12) (12 12 18) +Large yellow flame ball +*/ +void() light_flame_large_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + self.frame = 1; + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_yellow (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Small yellow flame ball +*/ +void() light_flame_small_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_white (0 1 0) (-10 -10 -40) (10 10 40) START_OFF +Small white flame ball +*/ +void() light_flame_small_white = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + FireAmbient (); + makestatic (self); +}; + +//============================================================================ + + +/*QUAKED misc_fireball (0 .5 .8) (-8 -8 -8) (8 8 8) +Lava Balls +*/ + +void() fire_fly; +void() fire_touch; +void() misc_fireball = +{ + precache_model ("progs/lavaball.mdl"); + self.classname = "fireball"; + self.nextthink = time + (random() * 5); + self.think = fire_fly; + self.netname = "$qc_lava_ball"; + self.killstring = "$qc_ks_lavaball"; + + if (!self.speed) + self.speed = 1000; +}; + +void() fire_fly = +{ + local entity fireball; + + fireball = spawn(); + fireball.solid = SOLID_TRIGGER; + fireball.movetype = MOVETYPE_TOSS; + fireball.velocity = '0 0 1000'; + fireball.velocity_x = (random() * 100) - 50; + fireball.velocity_y = (random() * 100) - 50; + fireball.velocity_z = self.speed + (random() * 200); + fireball.classname = "fireball"; + setmodel (fireball, "progs/lavaball.mdl"); + setsize (fireball, '0 0 0', '0 0 0'); + setorigin (fireball, self.origin); + fireball.nextthink = time + 5; + fireball.think = SUB_Remove; + fireball.touch = fire_touch; + + self.netname = "$qc_lava_ball"; + self.killstring = "$qc_ks_lavaball"; + + self.nextthink = time + (random() * 5) + 3; + self.think = fire_fly; +}; + + +void() fire_touch = +{ + T_Damage (other, self, self, 20); + remove(self); +}; + +//============================================================================ + + +void() barrel_explode = +{ + // did say self.owner + T_RadiusDamage (self, self.enemy, 160, world); + sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM); + particle (self.origin, '0 0 0', 75, 255); + + self.origin_z = self.origin_z + 32; + BecomeExplosion (); +}; + +void() barrel_detonate = +{ + self.classname = "explo_box"; + self.takedamage = DAMAGE_NO; + self.think = barrel_explode; + self.nextthink = self.ltime + 0.3; +} + + +/*QUAKED misc_explobox (0 .5 .8) (0 0 0) (32 32 64) +TESTING THING +*/ + +void() misc_explobox = +{ + local float oldz; + + if(!self.mdl) + self.mdl = "maps/b_explob.bsp"; + + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + precache_model (self.mdl); + setmodel (self, self.mdl); + precache_sound ("weapons/r_exp3.wav"); + self.health = 20; + self.th_die = barrel_detonate; + self.takedamage = DAMAGE_AIM; + self.netname = "$qc_exploding_barrel"; + self.killstring = "$qc_ks_blew_up"; + + self.origin_z = self.origin_z + 2; + oldz = self.origin_z; + droptofloor(); + + if (oldz - self.origin_z > 250) + { + dprint ("explobox fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + } +}; + + + + +/*QUAKED misc_explobox2 (0 .5 .8) (0 0 0) (32 32 64) +Smaller exploding box, REGISTERED ONLY +*/ +void() misc_explobox2 = +{ + self.mdl = "maps/b_exbox2.bsp"; + misc_explobox(); +}; + +//============================================================================ + +float SPAWNFLAG_SUPERSPIKE = 1; +float SPAWNFLAG_LASER = 2; + +void(vector org, vector vec) LaunchLaser; + +void() spikeshooter_use = +{ + if (self.spawnflags & SPAWNFLAG_LASER) + { + sound (self, CHAN_VOICE, "enforcer/enfire.wav", 1, ATTN_NORM); + LaunchLaser (self.origin, self.movedir); + } + else + { + sound (self, CHAN_VOICE, "weapons/spike2.wav", 1, ATTN_NORM); + launch_spike (self.origin, self.movedir); + newmis.velocity = self.movedir * 500; + + if (self.spawnflags & SPAWNFLAG_SUPERSPIKE) + newmis.touch = superspike_touch; + } +}; + +void() shooter_think = +{ + spikeshooter_use (); + self.nextthink = time + self.wait; + newmis.velocity = self.movedir * 500; +}; + + +/*QUAKED trap_spikeshooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser +When triggered, fires a spike in the direction set in QuakeEd. +Laser is only for REGISTERED. +*/ + +void() trap_spikeshooter = +{ + SetMovedir (); + self.use = spikeshooter_use; + self.netname = "$qc_spike_trap"; + self.killstring ="$qc_ks_spiked"; + + if (self.spawnflags & SPAWNFLAG_LASER) + { + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + } + else + precache_sound ("weapons/spike2.wav"); +}; + + +/*QUAKED trap_shooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser +Continuously fires spikes. +"wait" time between spike (1.0 default) +"nextthink" delay before firing first spike, so multiple shooters can be stagered. +*/ +void() trap_shooter = +{ + trap_spikeshooter (); + + if (self.wait == 0) + self.wait = 1; + + self.nextthink = self.nextthink + self.wait + self.ltime; + self.think = shooter_think; +}; + + + +/* +=============================================================================== + + +=============================================================================== +*/ + + +void() make_bubbles; +void() bubble_remove; +void() bubble_bob; + +/*QUAKED air_bubbles (0 .5 .8) (-8 -8 -8) (8 8 8) + +testing air bubbles +*/ + +void() air_bubbles = + +{ + if (deathmatch) + { + remove (self); + return; + } + + precache_model ("progs/s_bubble.spr"); + self.nextthink = time + 1; + self.think = make_bubbles; +}; + +void() make_bubbles = +{ + local entity bubble; + + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + random() + 0.5; + self.think = make_bubbles; +}; + +void() bubble_split = +{ + local entity bubble; + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = self.velocity; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 1; + bubble.cnt = 10; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.frame = 1; + self.cnt = 10; + + if (self.waterlevel != 3) + remove (self); +}; + +void() bubble_remove = +{ + if (other.classname == self.classname) + return; + + remove(self); +}; + +void() bubble_bob = +{ + local float rnd1, rnd2, rnd3; + + self.cnt = self.cnt + 1; + if (self.cnt == 4) + bubble_split(); + + if (self.cnt == 20) + remove(self); + + rnd1 = self.velocity_x + (-10 + (random() * 20)); + rnd2 = self.velocity_y + (-10 + (random() * 20)); + rnd3 = self.velocity_z + 10 + random() * 10; + + if (rnd1 > 10) + rnd1 = 5; + + if (rnd1 < -10) + rnd1 = -5; + + if (rnd2 > 10) + rnd2 = 5; + + if (rnd2 < -10) + rnd2 = -5; + + if (rnd3 < 10) + rnd3 = 15; + + if (rnd3 > 30) + rnd3 = 25; + + self.velocity_x = rnd1; + self.velocity_y = rnd2; + self.velocity_z = rnd3; + + self.nextthink = time + 0.5; + self.think = bubble_bob; +}; + +/*~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~> +~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~*/ + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) + +Just for the debugging level. Don't use +*/ + +void() viewthing = +{ + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + precache_model ("progs/player.mdl"); + setmodel (self, "progs/player.mdl"); +}; + + +/* +============================================================================== + +SIMPLE BMODELS + +============================================================================== +*/ + +void() func_wall_use = +{ // change to alternate textures + self.frame = 1 - self.frame; +}; + +/* +QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibitted +*/ +void() func_wall = +{ + self.angles = '0 0 0'; + self.classname = "func_wall"; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + + +/* +QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +void() func_illusionary = +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + setmodel (self, self.model); + makestatic (self); +}; + +/* +QUAKED func_episodegate (0 .5 .8) ? E1 E2 E3 E4 +This bmodel will appear if the episode has allready been completed, so players can't reenter it. +*/ +void() func_episodegate = +{ + if (!(serverflags & self.spawnflags)) + return; // can still enter episode + + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + +/* +QUAKED func_bossgate (0 .5 .8) ? +This bmodel appears unless players have all of the episode sigils. +*/ +void() func_bossgate = +{ + if ( (serverflags & 15) == 15) + return; // all episodes completed + + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + +//============================================================================ +/* +QUAKED ambient_suck_wind (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_suck_wind = +{ + precache_sound ("ambience/suck1.wav"); + ambientsound (self.origin, "ambience/suck1.wav", 1, ATTN_STATIC); +}; + +/* +QUAKED ambient_drone (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drone = +{ + precache_sound ("ambience/drone6.wav"); + ambientsound (self.origin, "ambience/drone6.wav", 0.5, ATTN_STATIC); +}; + +/* +QUAKED ambient_flouro_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_flouro_buzz = +{ + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 1, ATTN_STATIC); +}; +/* +QUAKED ambient_drip (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drip = +{ + precache_sound ("ambience/drip1.wav"); + ambientsound (self.origin, "ambience/drip1.wav", 0.5, ATTN_STATIC); +}; +/* +QUAKED ambient_comp_hum (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_comp_hum = +{ + precache_sound ("ambience/comp1.wav"); + ambientsound (self.origin, "ambience/comp1.wav", 1, ATTN_STATIC); +}; +/* +QUAKED ambient_thunder (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_thunder = +{ + precache_sound ("ambience/thunder1.wav"); + ambientsound (self.origin, "ambience/thunder1.wav", 0.5, ATTN_STATIC); +}; +/* +QUAKED ambient_light_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_light_buzz = +{ + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); +}; +/* +QUAKED ambient_swamp1 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp1 = +{ + precache_sound ("ambience/swamp1.wav"); + ambientsound (self.origin, "ambience/swamp1.wav", 0.5, ATTN_STATIC); +}; +/* +QUAKED ambient_swamp2 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp2 = +{ + precache_sound ("ambience/swamp2.wav"); + ambientsound (self.origin, "ambience/swamp2.wav", 0.5, ATTN_STATIC); +}; + +//============================================================================ + +void() noise_think = +{ + self.nextthink = time + 0.5; + sound (self, 1, "enforcer/enfire.wav", 1, ATTN_NORM); + sound (self, 2, "enforcer/enfstop.wav", 1, ATTN_NORM); + sound (self, 3, "enforcer/sight1.wav", 1, ATTN_NORM); + sound (self, 4, "enforcer/sight2.wav", 1, ATTN_NORM); + sound (self, 5, "enforcer/sight3.wav", 1, ATTN_NORM); + sound (self, 6, "enforcer/sight4.wav", 1, ATTN_NORM); + sound (self, 7, "enforcer/pain1.wav", 1, ATTN_NORM); +}; + +/*QUAKED misc_noisemaker (1 0.5 0) (-10 -10 -10) (10 10 10) + +For optimzation testing, starts a lot of sounds. +*/ + +void() misc_noisemaker = +{ + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/idle1.wav"); + + self.nextthink = time + 0.1 + random(); + self.think = noise_think; +}; diff --git a/quakec/monsters.qc b/quakec/monsters.qc new file mode 100644 index 0000000..44be791 --- /dev/null +++ b/quakec/monsters.qc @@ -0,0 +1,267 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* ALL MONSTERS SHOULD BE 1 0 0 IN COLOR */ + +// name =[framenum, nexttime, nextthink] {code} +// expands to: +// name () +// { +// self.frame=framenum; +// self.nextthink = time + nexttime; +// self.think = nextthink +// +// }; + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void() monster_use = +{ + if (self.enemy) + return; + + if (self.health <= 0) + return; + + if (activator.items & IT_INVISIBILITY) + return; + + if (activator.flags & FL_NOTARGET) + return; + + if (activator.classname != "player") + return; + + // delay reaction so if the monster is teleported, its sound is still heard + self.enemy = activator; + self.nextthink = time + 0.1; + self.think = FoundTarget; +}; + +/* +================ +monster_death_use + +When a mosnter dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void() monster_death_use = +{ + // fall to ground + if (self.flags & FL_FLY) + self.flags = self.flags - FL_FLY; + + if (self.flags & FL_SWIM) + self.flags = self.flags - FL_SWIM; + + if (!self.target) + return; + + activator = self.enemy; + SUB_UseTargets (); +}; + + +//============================================================================ + +void() walkmonster_start_go = +{ + self.origin_z = self.origin_z + 1; // raise off floor a bit + droptofloor(); + + if (!walkmove(0,0)) + { + dprint ("walkmonster in wall at: "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + + if (!self.yaw_speed) + self.yaw_speed = 20; + + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + self.flags = self.flags | FL_MONSTER; + + if (self.target != string_null) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + // this used to be an objerror + if (self.movetarget.classname == "path_corner") + self.th_walk (); + + else + self.pausetime = 99999999; + self.th_stand (); + } + + else + { + self.pausetime = 99999999; + self.th_stand (); + } + + // spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; +}; + + +void() walkmonster_start = +{ + // delay drop to floor to make sure all doors have been spawned + // spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.think = walkmonster_start_go; + total_monsters = total_monsters + 1; +}; + + + +void() flymonster_start_go = +{ + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + + if (!self.yaw_speed) + self.yaw_speed = 10; + + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + self.flags = self.flags | FL_FLY; + self.flags = self.flags | FL_MONSTER; + + if (!walkmove(0,0)) + { + dprint ("flymonster in wall at: "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + if (self.target != string_null) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + // this used to be an objerror + if (self.movetarget.classname == "path_corner") + self.th_walk (); + + else + self.pausetime = 99999999; + self.th_stand (); + } + + else + { + self.pausetime = 99999999; + self.th_stand (); + } +}; + +void() flymonster_start = +{ + // spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.think = flymonster_start_go; + total_monsters = total_monsters + 1; +}; + + +void() swimmonster_start_go = +{ + if (deathmatch) + { + remove(self); + return; + } + + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + + if (!self.yaw_speed) + self.yaw_speed = 10; + + self.view_ofs = '0 0 10'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + self.flags = self.flags | FL_SWIM; + self.flags = self.flags | FL_MONSTER; + + if (self.target != string_null) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + // this used to be an objerror + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + self.th_walk (); + } + + else + { + self.pausetime = 99999999; + self.th_stand (); + } + + // spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; +}; + +void() swimmonster_start = +{ + // spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random() * 0.5; + self.think = swimmonster_start_go; + total_monsters = total_monsters + 1; +}; + + diff --git a/quakec/monsters/chthon.qc b/quakec/monsters/chthon.qc new file mode 100644 index 0000000..fe1bd2e --- /dev/null +++ b/quakec/monsters/chthon.qc @@ -0,0 +1,430 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BOSS-ONE + +============================================================================== +*/ +$cd id1/models/boss1 +$origin 0 0 -15 +$base base +$skin skin +$scale 5 + +$frame rise1 rise2 rise3 rise4 rise5 rise6 rise7 rise8 rise9 rise10 +$frame rise11 rise12 rise13 rise14 rise15 rise16 rise17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 +$frame walk9 walk10 walk11 walk12 walk13 walk14 walk15 +$frame walk16 walk17 walk18 walk19 walk20 walk21 walk22 +$frame walk23 walk24 walk25 walk26 walk27 walk28 walk29 walk30 walk31 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 attack12 attack13 attack14 attack15 +$frame attack16 attack17 attack18 attack19 attack20 attack21 attack22 +$frame attack23 + +$frame shocka1 shocka2 shocka3 shocka4 shocka5 shocka6 shocka7 shocka8 +$frame shocka9 shocka10 + +$frame shockb1 shockb2 shockb3 shockb4 shockb5 shockb6 + +$frame shockc1 shockc2 shockc3 shockc4 shockc5 shockc6 shockc7 shockc8 +$frame shockc9 shockc10 + + +void(vector p) boss_missile; + +void() boss_face = +{ + local entity target; + local float i = 0; + + // go for another player if multi player + if (self.enemy.health <= 0 || random() < 0.02) + { + target = self.enemy; + + // [QuakeEX] Try a reasonable number of times to find a suitable target. + while (i < 4) + { + target = find(target, classname, "player"); + if (target && target.health > 0) + { + self.enemy = target; + break; + } + i += 1; + } + } + + ai_face(); +}; + +void() boss_rise1 =[ $rise1, boss_rise2 ] { + sound (self, CHAN_WEAPON, "boss1/out1.wav", 1, ATTN_NORM); +}; + +void() boss_rise2 =[ $rise2, boss_rise3 ] { + sound (self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); +}; + +void() boss_rise3 =[ $rise3, boss_rise4 ] {}; +void() boss_rise4 =[ $rise4, boss_rise5 ] {}; +void() boss_rise5 =[ $rise5, boss_rise6 ] {}; +void() boss_rise6 =[ $rise6, boss_rise7 ] {}; +void() boss_rise7 =[ $rise7, boss_rise8 ] {}; +void() boss_rise8 =[ $rise8, boss_rise9 ] {}; +void() boss_rise9 =[ $rise9, boss_rise10 ] {}; +void() boss_rise10 =[ $rise10, boss_rise11 ] {}; +void() boss_rise11 =[ $rise11, boss_rise12 ] {}; +void() boss_rise12 =[ $rise12, boss_rise13 ] {}; +void() boss_rise13 =[ $rise13, boss_rise14 ] {}; +void() boss_rise14 =[ $rise14, boss_rise15 ] {}; +void() boss_rise15 =[ $rise15, boss_rise16 ] {}; +void() boss_rise16 =[ $rise16, boss_rise17 ] {}; +void() boss_rise17 =[ $rise17, boss_missile1 ] {}; + +void() boss_idle1 =[ $walk1, boss_idle2 ] +{ + // [QuakeEX] If boss_face found a new target, start throwing stuff at them. + if(self.enemy.health > 0.0) + boss_missile1(); + else + boss_face(); +}; +void() boss_idle2 =[ $walk2, boss_idle3 ] {boss_face();}; +void() boss_idle3 =[ $walk3, boss_idle4 ] {boss_face();}; +void() boss_idle4 =[ $walk4, boss_idle5 ] {boss_face();}; +void() boss_idle5 =[ $walk5, boss_idle6 ] {boss_face();}; +void() boss_idle6 =[ $walk6, boss_idle7 ] {boss_face();}; +void() boss_idle7 =[ $walk7, boss_idle8 ] {boss_face();}; +void() boss_idle8 =[ $walk8, boss_idle9 ] {boss_face();}; +void() boss_idle9 =[ $walk9, boss_idle10 ] {boss_face();}; +void() boss_idle10 =[ $walk10, boss_idle11 ] {boss_face();}; +void() boss_idle11 =[ $walk11, boss_idle12 ] {boss_face();}; +void() boss_idle12 =[ $walk12, boss_idle13 ] {boss_face();}; +void() boss_idle13 =[ $walk13, boss_idle14 ] {boss_face();}; +void() boss_idle14 =[ $walk14, boss_idle15 ] {boss_face();}; +void() boss_idle15 =[ $walk15, boss_idle16 ] {boss_face();}; +void() boss_idle16 =[ $walk16, boss_idle17 ] {boss_face();}; +void() boss_idle17 =[ $walk17, boss_idle18 ] {boss_face();}; +void() boss_idle18 =[ $walk18, boss_idle19 ] {boss_face();}; +void() boss_idle19 =[ $walk19, boss_idle20 ] {boss_face();}; +void() boss_idle20 =[ $walk20, boss_idle21 ] {boss_face();}; +void() boss_idle21 =[ $walk21, boss_idle22 ] {boss_face();}; +void() boss_idle22 =[ $walk22, boss_idle23 ] {boss_face();}; +void() boss_idle23 =[ $walk23, boss_idle24 ] {boss_face();}; +void() boss_idle24 =[ $walk24, boss_idle25 ] {boss_face();}; +void() boss_idle25 =[ $walk25, boss_idle26 ] {boss_face();}; +void() boss_idle26 =[ $walk26, boss_idle27 ] {boss_face();}; +void() boss_idle27 =[ $walk27, boss_idle28 ] {boss_face();}; +void() boss_idle28 =[ $walk28, boss_idle29 ] {boss_face();}; +void() boss_idle29 =[ $walk29, boss_idle30 ] {boss_face();}; +void() boss_idle30 =[ $walk30, boss_idle31 ] {boss_face();}; +void() boss_idle31 =[ $walk31, boss_idle1 ] {boss_face();}; + +void() boss_missile1 =[ $attack1, boss_missile2 ] {boss_face();}; +void() boss_missile2 =[ $attack2, boss_missile3 ] {boss_face();}; +void() boss_missile3 =[ $attack3, boss_missile4 ] {boss_face();}; +void() boss_missile4 =[ $attack4, boss_missile5 ] {boss_face();}; +void() boss_missile5 =[ $attack5, boss_missile6 ] {boss_face();}; +void() boss_missile6 =[ $attack6, boss_missile7 ] {boss_face();}; +void() boss_missile7 =[ $attack7, boss_missile8 ] {boss_face();}; +void() boss_missile8 =[ $attack8, boss_missile9 ] {boss_face();}; +void() boss_missile9 =[ $attack9, boss_missile10 ] {boss_missile('100 100 200');}; +void() boss_missile10 =[ $attack10, boss_missile11 ] {boss_face();}; +void() boss_missile11 =[ $attack11, boss_missile12 ] {boss_face();}; +void() boss_missile12 =[ $attack12, boss_missile13 ] {boss_face();}; +void() boss_missile13 =[ $attack13, boss_missile14 ] {boss_face();}; +void() boss_missile14 =[ $attack14, boss_missile15 ] {boss_face();}; +void() boss_missile15 =[ $attack15, boss_missile16 ] {boss_face();}; +void() boss_missile16 =[ $attack16, boss_missile17 ] {boss_face();}; +void() boss_missile17 =[ $attack17, boss_missile18 ] {boss_face();}; +void() boss_missile18 =[ $attack18, boss_missile19 ] {boss_face();}; +void() boss_missile19 =[ $attack19, boss_missile20 ] {boss_face();}; +void() boss_missile20 =[ $attack20, boss_missile21 ] {boss_missile('100 -100 200');}; +void() boss_missile21 =[ $attack21, boss_missile22 ] {boss_face();}; +void() boss_missile22 =[ $attack22, boss_missile23 ] {boss_face();}; +void() boss_missile23 =[ $attack23, boss_missile1 ] {boss_face();}; + +void() boss_shocka1 =[ $shocka1, boss_shocka2 ] {}; +void() boss_shocka2 =[ $shocka2, boss_shocka3 ] {}; +void() boss_shocka3 =[ $shocka3, boss_shocka4 ] {}; +void() boss_shocka4 =[ $shocka4, boss_shocka5 ] {}; +void() boss_shocka5 =[ $shocka5, boss_shocka6 ] {}; +void() boss_shocka6 =[ $shocka6, boss_shocka7 ] {}; +void() boss_shocka7 =[ $shocka7, boss_shocka8 ] {}; +void() boss_shocka8 =[ $shocka8, boss_shocka9 ] {}; +void() boss_shocka9 =[ $shocka9, boss_shocka10 ] {}; +void() boss_shocka10 =[ $shocka10, boss_missile1 ] {}; + +void() boss_shockb1 =[ $shockb1, boss_shockb2 ] {}; +void() boss_shockb2 =[ $shockb2, boss_shockb3 ] {}; +void() boss_shockb3 =[ $shockb3, boss_shockb4 ] {}; +void() boss_shockb4 =[ $shockb4, boss_shockb5 ] {}; +void() boss_shockb5 =[ $shockb5, boss_shockb6 ] {}; +void() boss_shockb6 =[ $shockb6, boss_shockb7 ] {}; +void() boss_shockb7 =[ $shockb1, boss_shockb8 ] {}; +void() boss_shockb8 =[ $shockb2, boss_shockb9 ] {}; +void() boss_shockb9 =[ $shockb3, boss_shockb10 ] {}; +void() boss_shockb10 =[ $shockb4, boss_missile1 ] {}; + +void() boss_shockc1 =[ $shockc1, boss_shockc2 ] {}; +void() boss_shockc2 =[ $shockc2, boss_shockc3 ] {}; +void() boss_shockc3 =[ $shockc3, boss_shockc4 ] {}; +void() boss_shockc4 =[ $shockc4, boss_shockc5 ] {}; +void() boss_shockc5 =[ $shockc5, boss_shockc6 ] {}; +void() boss_shockc6 =[ $shockc6, boss_shockc7 ] {}; +void() boss_shockc7 =[ $shockc7, boss_shockc8 ] {}; +void() boss_shockc8 =[ $shockc8, boss_shockc9 ] {}; +void() boss_shockc9 =[ $shockc9, boss_shockc10 ] {}; +void() boss_shockc10 =[ $shockc10, boss_death1 ] {}; + +void() boss_death1 = [$death1, boss_death2] { + sound (self, CHAN_VOICE, "boss1/death.wav", 1, ATTN_NORM); +}; + +void() boss_death2 = [$death2, boss_death3] {}; +void() boss_death3 = [$death3, boss_death4] {}; +void() boss_death4 = [$death4, boss_death5] {}; +void() boss_death5 = [$death5, boss_death6] {}; +void() boss_death6 = [$death6, boss_death7] {}; +void() boss_death7 = [$death7, boss_death8] {}; +void() boss_death8 = [$death8, boss_death9] {}; +void() boss_death9 = [$death9, boss_death10] +{ + sound (self, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); +}; + +void() boss_death10 = [$death9, boss_death10] +{ + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); // FIXME: reliable broadcast + SUB_UseTargets (); + remove (self); +}; + +void(vector p) boss_missile = +{ + local vector offang; + local vector org, vec, d; + local float t; + + offang = vectoangles (self.enemy.origin - self.origin); + makevectors (offang); + + org = self.origin + p_x*v_forward + p_y*v_right + p_z*'0 0 1'; + + // lead the player on hard mode + if (skill > 1) + { + t = vlen(self.enemy.origin - org) / 300; + vec = self.enemy.velocity; + vec_z = 0; + d = self.enemy.origin + t * vec; + } + else + { + d = self.enemy.origin; + } + + vec = normalize (d - org); + + launch_spike (org, vec); + newmis.classname = "chthon_lavaball"; + setmodel (newmis, "progs/lavaball.mdl"); + newmis.avelocity = '200 100 300'; + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.touch = T_MissileTouch; // rocket explosion + sound (self, CHAN_WEAPON, "boss1/throw.wav", 1, ATTN_NORM); + + // check for dead enemy + if (self.enemy.health <= 0) + boss_idle1 (); +}; + + +void() boss_awake = +{ + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + self.takedamage = DAMAGE_NO; + + setmodel (self, "progs/boss.mdl"); + + self.netname = "$qc_chthon"; + self.killstring = "$qc_ks_chthon"; + + setsize (self, '-128 -128 -24', '128 128 256'); + + if (skill == 0) + self.health = 1; + else + self.health = 3; + + self.enemy = activator; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.yaw_speed = 20; + boss_rise1 (); +}; + + +/* +QUAKED monster_boss (1 0 0) (-128 -128 -24) (128 128 256) +*/ +void() monster_boss = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/boss.mdl"); + precache_model ("progs/lavaball.mdl"); + + precache_sound ("weapons/rocket1i.wav"); + precache_sound ("boss1/out1.wav"); + precache_sound ("boss1/sight1.wav"); + precache_sound ("misc/power.wav"); + precache_sound ("boss1/throw.wav"); + precache_sound ("boss1/pain.wav"); + precache_sound ("boss1/death.wav"); + + total_monsters = total_monsters + 1; + + self.use = boss_awake; +}; + +//=========================================================================== + +entity le1, le2; +float lightning_end; + +void() lightning_fire = +{ + local vector p1, p2; + + if (time >= lightning_end) + { // done here, put the terminals back up + self = le1; + door_go_down (); + self = le2; + door_go_down (); + return; + } + + p1 = (le1.mins + le1.maxs) * 0.5; + p1_z = le1.absmin_z - 16; + + p2 = (le2.mins + le2.maxs) * 0.5; + p2_z = le2.absmin_z - 16; + + // compensate for length of bolt + p2 = p2 - normalize(p2-p1)*100; + + self.nextthink = time + 0.1; + self.think = lightning_fire; + + WriteByte (MSG_ALL, SVC_TEMPENTITY); + WriteByte (MSG_ALL, TE_LIGHTNING3); + WriteEntity (MSG_ALL, world); + WriteCoord (MSG_ALL, p1_x); + WriteCoord (MSG_ALL, p1_y); + WriteCoord (MSG_ALL, p1_z); + WriteCoord (MSG_ALL, p2_x); + WriteCoord (MSG_ALL, p2_y); + WriteCoord (MSG_ALL, p2_z); +}; + +void() lightning_use = +{ + if (lightning_end >= time + 1) + return; + + le1 = find( world, target, "lightning"); + le2 = find( le1, target, "lightning"); + if (!le1 || !le2) + { + dprint ("missing lightning targets\n"); + return; + } + + if ((le1.state != STATE_TOP && le1.state != STATE_BOTTOM) || (le2.state != STATE_TOP && le2.state != STATE_BOTTOM) || (le1.state != le2.state) ) + return; + + // don't let the electrodes go back up until the bolt is done + le1.nextthink = -1; + le2.nextthink = -1; + lightning_end = time + 1; + + sound (self, CHAN_VOICE, "misc/power.wav", 1, ATTN_NORM); + lightning_fire (); + + // advance the boss pain if down + self = find (world, classname, "monster_boss"); + + if (!self) + return; + + self.enemy = activator; + + if (le1.state == STATE_TOP && self.health > 0) + { + sound (self, CHAN_VOICE, "boss1/pain.wav", 1, ATTN_NORM); + self.health = self.health - 1; + + if (self.health >= 2) + boss_shocka1(); + else if (self.health == 1) + boss_shockb1(); + else if (self.health == 0) + boss_shockc1(); + } +}; + + +/* +QUAKED event_lightning (0 1 1) (-16 -16 -16) (16 16 16) +Just for boss level. +*/ +void() event_lightning = +{ + self.use = lightning_use; +}; + + diff --git a/quakec/monsters/enforcer.qc b/quakec/monsters/enforcer.qc new file mode 100644 index 0000000..db864bd --- /dev/null +++ b/quakec/monsters/enforcer.qc @@ -0,0 +1,392 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd id1/models/enforcer +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 death12 death13 death14 + +$frame fdeath1 fdeath2 fdeath3 fdeath4 fdeath5 fdeath6 fdeath7 fdeath8 +$frame fdeath9 fdeath10 fdeath11 + +$frame paina1 paina2 paina3 paina4 + +$frame painb1 painb2 painb3 painb4 painb5 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 +$frame paind9 paind10 paind11 paind12 paind13 paind14 paind15 paind16 +$frame paind17 paind18 paind19 + + +void() Laser_Touch = +{ + local vector org; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + sound (self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); + org = self.origin - 8*normalize(self.velocity); + + if (other.health) + { + SpawnBlood (org, self.velocity*0.2, 15); + T_Damage (other, self, self.owner, 15); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } + + remove(self); +}; + +void(vector org, vector vec) LaunchLaser = +{ + if (self.classname == "monster_enforcer") + sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM); + + vec = normalize(vec); + + newmis = spawn(); + newmis.classname = "enforcer_laser"; + newmis.owner = self; + newmis.movetype = MOVETYPE_FLY; + newmis.solid = SOLID_BBOX; + newmis.effects = EF_DIMLIGHT; + + setmodel (newmis, "progs/laser.mdl"); + setsize (newmis, '0 0 0', '0 0 0'); + + setorigin (newmis, org); + + newmis.velocity = vec * 600; + newmis.angles = vectoangles(newmis.velocity); + + newmis.nextthink = time + 5; + newmis.think = SUB_Remove; + newmis.touch = Laser_Touch; +}; + + + +void() enforcer_fire = +{ + local vector org; + + self.effects = self.effects | EF_MUZZLEFLASH; + makevectorsfixed(self.angles); + + org = self.origin + v_forward * 30 + v_right * 8.5 + '0 0 16'; + + LaunchLaser(org, self.enemy.origin - self.origin); +}; + +//============================================================================ + +void() enf_stand1 =[ $stand1, enf_stand2 ] {ai_stand();}; +void() enf_stand2 =[ $stand2, enf_stand3 ] {ai_stand();}; +void() enf_stand3 =[ $stand3, enf_stand4 ] {ai_stand();}; +void() enf_stand4 =[ $stand4, enf_stand5 ] {ai_stand();}; +void() enf_stand5 =[ $stand5, enf_stand6 ] {ai_stand();}; +void() enf_stand6 =[ $stand6, enf_stand7 ] {ai_stand();}; +void() enf_stand7 =[ $stand7, enf_stand1 ] {ai_stand();}; + +void() enf_walk1 =[ $walk1 , enf_walk2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); + + ai_walk(2); +}; + +void() enf_walk2 =[ $walk2 , enf_walk3 ] {ai_walk(4);}; +void() enf_walk3 =[ $walk3 , enf_walk4 ] {ai_walk(4);}; +void() enf_walk4 =[ $walk4 , enf_walk5 ] {ai_walk(3);}; +void() enf_walk5 =[ $walk5 , enf_walk6 ] {ai_walk(1);}; +void() enf_walk6 =[ $walk6 , enf_walk7 ] {ai_walk(2);}; +void() enf_walk7 =[ $walk7 , enf_walk8 ] {ai_walk(2);}; +void() enf_walk8 =[ $walk8 , enf_walk9 ] {ai_walk(1);}; +void() enf_walk9 =[ $walk9 , enf_walk10 ] {ai_walk(2);}; +void() enf_walk10 =[ $walk10, enf_walk11 ] {ai_walk(4);}; +void() enf_walk11 =[ $walk11, enf_walk12 ] {ai_walk(4);}; +void() enf_walk12 =[ $walk12, enf_walk13 ] {ai_walk(1);}; +void() enf_walk13 =[ $walk13, enf_walk14 ] {ai_walk(2);}; +void() enf_walk14 =[ $walk14, enf_walk15 ] {ai_walk(3);}; +void() enf_walk15 =[ $walk15, enf_walk16 ] {ai_walk(4);}; +void() enf_walk16 =[ $walk16, enf_walk1 ] {ai_walk(2);}; + +void() enf_run1 =[ $run1 , enf_run2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); + + ai_run(18); +}; + +void() enf_run2 =[ $run2 , enf_run3 ] {ai_run(14);}; +void() enf_run3 =[ $run3 , enf_run4 ] {ai_run(7);}; +void() enf_run4 =[ $run4 , enf_run5 ] {ai_run(12);}; +void() enf_run5 =[ $run5 , enf_run6 ] {ai_run(14);}; +void() enf_run6 =[ $run6 , enf_run7 ] {ai_run(14);}; +void() enf_run7 =[ $run7 , enf_run8 ] {ai_run(7);}; +void() enf_run8 =[ $run8 , enf_run1 ] {ai_run(11);}; + +void() enf_atk1 =[ $attack1, enf_atk2 ] {ai_face();}; +void() enf_atk2 =[ $attack2, enf_atk3 ] {ai_face();}; +void() enf_atk3 =[ $attack3, enf_atk4 ] {ai_face();}; +void() enf_atk4 =[ $attack4, enf_atk5 ] {ai_face();}; +void() enf_atk5 =[ $attack5, enf_atk6 ] {ai_face();}; +void() enf_atk6 =[ $attack6, enf_atk7 ] {enforcer_fire();}; +void() enf_atk7 =[ $attack7, enf_atk8 ] {ai_face();}; +void() enf_atk8 =[ $attack8, enf_atk9 ] {ai_face();}; +void() enf_atk9 =[ $attack5, enf_atk10 ] {ai_face();}; +void() enf_atk10 =[ $attack6, enf_atk11 ] {enforcer_fire();}; +void() enf_atk11 =[ $attack7, enf_atk12 ] {ai_face();}; +void() enf_atk12 =[ $attack8, enf_atk13 ] {ai_face();}; +void() enf_atk13 =[ $attack9, enf_atk14 ] {ai_face();}; +void() enf_atk14 =[ $attack10, enf_run1 ] { + ai_face(); + SUB_CheckRefire( enf_atk1 ); +}; + +void() enf_paina1 =[ $paina1, enf_paina2 ] {}; +void() enf_paina2 =[ $paina2, enf_paina3 ] {}; +void() enf_paina3 =[ $paina3, enf_paina4 ] {}; +void() enf_paina4 =[ $paina4, enf_run1 ] {}; + +void() enf_painb1 =[ $painb1, enf_painb2 ] {}; +void() enf_painb2 =[ $painb2, enf_painb3 ] {}; +void() enf_painb3 =[ $painb3, enf_painb4 ] {}; +void() enf_painb4 =[ $painb4, enf_painb5 ] {}; +void() enf_painb5 =[ $painb5, enf_run1 ] {}; + +void() enf_painc1 =[ $painc1, enf_painc2 ] {}; +void() enf_painc2 =[ $painc2, enf_painc3 ] {}; +void() enf_painc3 =[ $painc3, enf_painc4 ] {}; +void() enf_painc4 =[ $painc4, enf_painc5 ] {}; +void() enf_painc5 =[ $painc5, enf_painc6 ] {}; +void() enf_painc6 =[ $painc6, enf_painc7 ] {}; +void() enf_painc7 =[ $painc7, enf_painc8 ] {}; +void() enf_painc8 =[ $painc8, enf_run1 ] {}; + +void() enf_paind1 =[ $paind1, enf_paind2 ] {}; +void() enf_paind2 =[ $paind2, enf_paind3 ] {}; +void() enf_paind3 =[ $paind3, enf_paind4 ] {}; +void() enf_paind4 =[ $paind4, enf_paind5 ] {ai_painforward(2);}; +void() enf_paind5 =[ $paind5, enf_paind6 ] {ai_painforward(1);}; +void() enf_paind6 =[ $paind6, enf_paind7 ] {}; +void() enf_paind7 =[ $paind7, enf_paind8 ] {}; +void() enf_paind8 =[ $paind8, enf_paind9 ] {}; +void() enf_paind9 =[ $paind9, enf_paind10 ] {}; +void() enf_paind10 =[ $paind10, enf_paind11 ] {}; +void() enf_paind11 =[ $paind11, enf_paind12 ] {ai_painforward(1);}; +void() enf_paind12 =[ $paind12, enf_paind13 ] {ai_painforward(1);}; +void() enf_paind13 =[ $paind13, enf_paind14 ] {ai_painforward(1);}; +void() enf_paind14 =[ $paind14, enf_paind15 ] {}; +void() enf_paind15 =[ $paind15, enf_paind16 ] {}; +void() enf_paind16 =[ $paind16, enf_paind17 ] {ai_pain(1);}; +void() enf_paind17 =[ $paind17, enf_paind18 ] {ai_pain(1);}; +void() enf_paind18 =[ $paind18, enf_paind19 ] {}; +void() enf_paind19 =[ $paind19, enf_run1 ] {}; + +void(entity attacker, float damage) enf_pain = +{ + local float r; + + r = random (); + if (self.pain_finished > time) + return; + + + if (r < 0.5) + sound (self, CHAN_VOICE, "enforcer/pain1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "enforcer/pain2.wav", 1, ATTN_NORM); + + if (r < 0.2) + { + self.pain_finished = time + 1; + enf_paina1 (); + } + else if (r < 0.4) + { + self.pain_finished = time + 1; + enf_painb1 (); + } + else if (r < 0.7) + { + self.pain_finished = time + 1; + enf_painc1 (); + } + else + { + self.pain_finished = time + 2; + enf_paind1 (); + } +}; + +//============================================================================ + + + + +void() enf_die1 =[ $death1, enf_die2 ] {}; +void() enf_die2 =[ $death2, enf_die3 ] {}; +void() enf_die3 =[ $death3, enf_die4 ] { + self.solid = SOLID_NOT; + self.ammo_cells = 5; + DropBackpack(); +}; + +void() enf_die4 =[ $death4, enf_die5 ] {ai_forward(14);}; +void() enf_die5 =[ $death5, enf_die6 ] {ai_forward(2);}; +void() enf_die6 =[ $death6, enf_die7 ] {}; +void() enf_die7 =[ $death7, enf_die8 ] {}; +void() enf_die8 =[ $death8, enf_die9 ] {}; +void() enf_die9 =[ $death9, enf_die10 ] {ai_forward(3);}; +void() enf_die10 =[ $death10, enf_die11 ] {ai_forward(5);}; +void() enf_die11 =[ $death11, enf_die12 ] {ai_forward(5);}; +void() enf_die12 =[ $death12, enf_die13 ] {ai_forward(5);}; +void() enf_die13 =[ $death13, enf_die14 ] {}; +void() enf_die14 =[ $death14, enf_die14 ] {}; + +void() enf_fdie1 =[ $fdeath1, enf_fdie2 ] {}; +void() enf_fdie2 =[ $fdeath2, enf_fdie3 ] {}; +void() enf_fdie3 =[ $fdeath3, enf_fdie4 ] { + self.solid = SOLID_NOT; + self.ammo_cells = 5; + DropBackpack(); +}; + +void() enf_fdie4 =[ $fdeath4, enf_fdie5 ] {}; +void() enf_fdie5 =[ $fdeath5, enf_fdie6 ] {}; +void() enf_fdie6 =[ $fdeath6, enf_fdie7 ] {}; +void() enf_fdie7 =[ $fdeath7, enf_fdie8 ] {}; +void() enf_fdie8 =[ $fdeath8, enf_fdie9 ] {}; +void() enf_fdie9 =[ $fdeath9, enf_fdie10 ] {}; +void() enf_fdie10 =[ $fdeath10, enf_fdie11 ] {}; +void() enf_fdie11 =[ $fdeath11, enf_fdie11 ] {}; + + +void() enf_die = +{ + // check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_mega.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + // regular death + sound (self, CHAN_VOICE, "enforcer/death1.wav", 1, ATTN_NORM); + + if (random() > 0.5) + enf_die1 (); + else + enf_fdie1 (); +}; + + +/*QUAKED monster_enforcer (1 0 0) (-16 -16 -24) (16 16 40) Ambush*/ +void() monster_enforcer = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/enforcer.mdl"); + precache_model2 ("progs/h_mega.mdl"); + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/idle1.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/enforcer.mdl"); + + self.netname = "$qc_enforcer"; + self.killstring = "$qc_ks_enforcer"; + self.noise = "enforcer/sight1.wav"; + self.noise1 = "enforcer/sight2.wav"; + self.noise2 = "enforcer/sight3.wav"; + self.noise3 = "enforcer/sight4.wav"; + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + self.max_health = 80; + + self.th_stand = enf_stand1; + self.th_walk = enf_walk1; + self.th_run = enf_run1; + self.th_pain = enf_pain; + self.th_die = enf_die; + self.th_missile = enf_atk1; + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + + walkmonster_start(); +}; diff --git a/quakec/monsters/fiend.qc b/quakec/monsters/fiend.qc new file mode 100644 index 0000000..99d31d4 --- /dev/null +++ b/quakec/monsters/fiend.qc @@ -0,0 +1,388 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +$cd id1/models/demon3 +$scale 0.8 +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + +$frame run1 run2 run3 run4 run5 run6 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 leap10 +$frame leap11 leap12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attacka1 attacka2 attacka3 attacka4 attacka5 attacka6 attacka7 attacka8 +$frame attacka9 attacka10 attacka11 attacka12 attacka13 attacka14 attacka15 + +//============================================================================ + +void() Demon_JumpTouch; + +void() demon1_stand1 =[ $stand1, demon1_stand2 ] {ai_stand();}; +void() demon1_stand2 =[ $stand2, demon1_stand3 ] {ai_stand();}; +void() demon1_stand3 =[ $stand3, demon1_stand4 ] {ai_stand();}; +void() demon1_stand4 =[ $stand4, demon1_stand5 ] {ai_stand();}; +void() demon1_stand5 =[ $stand5, demon1_stand6 ] {ai_stand();}; +void() demon1_stand6 =[ $stand6, demon1_stand7 ] {ai_stand();}; +void() demon1_stand7 =[ $stand7, demon1_stand8 ] {ai_stand();}; +void() demon1_stand8 =[ $stand8, demon1_stand9 ] {ai_stand();}; +void() demon1_stand9 =[ $stand9, demon1_stand10 ] {ai_stand();}; +void() demon1_stand10 =[ $stand10, demon1_stand11 ] {ai_stand();}; +void() demon1_stand11 =[ $stand11, demon1_stand12 ] {ai_stand();}; +void() demon1_stand12 =[ $stand12, demon1_stand13 ] {ai_stand();}; +void() demon1_stand13 =[ $stand13, demon1_stand1 ] {ai_stand();}; + +void() demon1_walk1 =[ $walk1, demon1_walk2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); + + ai_walk(8); +}; + +void() demon1_walk2 =[ $walk2, demon1_walk3 ] {ai_walk(6);}; +void() demon1_walk3 =[ $walk3, demon1_walk4 ] {ai_walk(6);}; +void() demon1_walk4 =[ $walk4, demon1_walk5 ] {ai_walk(7);}; +void() demon1_walk5 =[ $walk5, demon1_walk6 ] {ai_walk(4);}; +void() demon1_walk6 =[ $walk6, demon1_walk7 ] {ai_walk(6);}; +void() demon1_walk7 =[ $walk7, demon1_walk8 ] {ai_walk(10);}; +void() demon1_walk8 =[ $walk8, demon1_walk1 ] {ai_walk(10);}; + +void() demon1_run1 =[ $run1, demon1_run2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); + + ai_run(20); +}; + +void() demon1_run2 =[ $run2, demon1_run3 ] {ai_run(15);}; +void() demon1_run3 =[ $run3, demon1_run4 ] {ai_run(36);}; +void() demon1_run4 =[ $run4, demon1_run5 ] {ai_run(20);}; +void() demon1_run5 =[ $run5, demon1_run6 ] {ai_run(15);}; +void() demon1_run6 =[ $run6, demon1_run1 ] {ai_run(36);}; + +void() demon1_jump1 =[ $leap1, demon1_jump2 ] {ai_face();}; +void() demon1_jump2 =[ $leap2, demon1_jump3 ] {ai_face();}; +void() demon1_jump3 =[ $leap3, demon1_jump4 ] {ai_face();}; +void() demon1_jump4 =[ $leap4, demon1_jump5 ] +{ + ai_face(); + + self.touch = Demon_JumpTouch; + makevectorsfixed(self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 250'; + + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; + +void() demon1_jump5 =[ $leap5, demon1_jump6 ] {}; +void() demon1_jump6 =[ $leap6, demon1_jump7 ] {}; +void() demon1_jump7 =[ $leap7, demon1_jump8 ] {}; +void() demon1_jump8 =[ $leap8, demon1_jump9 ] {}; +void() demon1_jump9 =[ $leap9, demon1_jump10 ] {}; +void() demon1_jump10 =[ $leap10, demon1_jump1 ] { + self.nextthink = time + 3; // if three seconds pass, assume demon is stuck and jump again +}; + +void() demon1_jump11 =[ $leap11, demon1_jump12 ] {}; +void() demon1_jump12 =[ $leap12, demon1_run1 ] {}; + + +void() demon1_atta1 =[ $attacka1, demon1_atta2 ] {ai_charge(4);}; +void() demon1_atta2 =[ $attacka2, demon1_atta3 ] {ai_charge(0);}; +void() demon1_atta3 =[ $attacka3, demon1_atta4 ] {ai_charge(0);}; +void() demon1_atta4 =[ $attacka4, demon1_atta5 ] {ai_charge(1);}; +void() demon1_atta5 =[ $attacka5, demon1_atta6 ] {ai_charge(2); Demon_Melee(200);}; +void() demon1_atta6 =[ $attacka6, demon1_atta7 ] {ai_charge(1);}; +void() demon1_atta7 =[ $attacka7, demon1_atta8 ] {ai_charge(6);}; +void() demon1_atta8 =[ $attacka8, demon1_atta9 ] {ai_charge(8);}; +void() demon1_atta9 =[ $attacka9, demon1_atta10] {ai_charge(4);}; +void() demon1_atta10 =[ $attacka10, demon1_atta11] {ai_charge(2);}; +void() demon1_atta11 =[ $attacka11, demon1_atta12] {Demon_Melee(-200);}; +void() demon1_atta12 =[ $attacka12, demon1_atta13] {ai_charge(5);}; +void() demon1_atta13 =[ $attacka13, demon1_atta14] {ai_charge(8);}; +void() demon1_atta14 =[ $attacka14, demon1_atta15] {ai_charge(4);}; +void() demon1_atta15 =[ $attacka15, demon1_run1] {ai_charge(4);}; + +void() demon1_pain1 =[ $pain1, demon1_pain2 ] {}; +void() demon1_pain2 =[ $pain2, demon1_pain3 ] {}; +void() demon1_pain3 =[ $pain3, demon1_pain4 ] {}; +void() demon1_pain4 =[ $pain4, demon1_pain5 ] {}; +void() demon1_pain5 =[ $pain5, demon1_pain6 ] {}; +void() demon1_pain6 =[ $pain6, demon1_run1 ] {}; + +void(entity attacker, float damage) demon1_pain = +{ + if (self.touch == Demon_JumpTouch) + return; + + if (self.pain_finished > time) + return; + + self.pain_finished = time + 1; + sound (self, CHAN_VOICE, "demon/dpain1.wav", 1, ATTN_NORM); + + if (random()*200 > damage) + return; // didn't flinch + + demon1_pain1 (); +}; + +void() demon1_die1 =[ $death1, demon1_die2 ] { + sound (self, CHAN_VOICE, "demon/ddeath.wav", 1, ATTN_NORM); +}; + +void() demon1_die2 =[ $death2, demon1_die3 ] {}; +void() demon1_die3 =[ $death3, demon1_die4 ] {}; +void() demon1_die4 =[ $death4, demon1_die5 ] {}; +void() demon1_die5 =[ $death5, demon1_die6 ] {}; +void() demon1_die6 =[ $death6, demon1_die7 ] { + self.solid = SOLID_NOT; +}; +void() demon1_die7 =[ $death7, demon1_die8 ] {}; +void() demon1_die8 =[ $death8, demon1_die9 ] {}; +void() demon1_die9 =[ $death9, demon1_die9 ] {}; + +void() demon_die = +{ + // check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_demon.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + return; + } + + // regular death + demon1_die1 (); +}; + + +void() Demon_MeleeAttack = +{ + demon1_atta1 (); +}; + + +/*QUAKED monster_demon1 (1 0 0) (-32 -32 -24) (32 32 64) Ambush*/ +void() monster_demon1 = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/demon.mdl"); + precache_model ("progs/h_demon.mdl"); + + precache_sound ("demon/ddeath.wav"); + precache_sound ("demon/dhit2.wav"); + precache_sound ("demon/djump.wav"); + precache_sound ("demon/dpain1.wav"); + precache_sound ("demon/idle1.wav"); + precache_sound ("demon/sight2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/demon.mdl"); + self.noise = "demon/sight2.wav"; + self.killstring = "$qc_ks_fiend"; + + self.netname = "$qc_fiend"; + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 300; + self.max_health = 300; + + self.th_stand = demon1_stand1; + self.th_walk = demon1_walk1; + self.th_run = demon1_run1; + self.th_die = demon_die; + self.th_melee = Demon_MeleeAttack; // one of two attacks + self.th_missile = demon1_jump1; // jump attack + self.th_pain = demon1_pain; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + walkmonster_start(); +}; + + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +/* +============== +CheckDemonMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDemonMelee = +{ + if (enemy_range == RANGE_MELEE) + { // FIXME: check canreach + self.attack_state = AS_MELEE; + return TRUE; + } + + return FALSE; +}; + +/* +============== +CheckDemonJump + +============== +*/ +float() CheckDemonJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 100) + return FALSE; + + if (d > 200) + { + if (random() < 0.9) + return FALSE; + } + + return TRUE; +}; + +float() DemonCheckAttack = +{ + // if close enough for slashing, go for it + if (CheckDemonMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDemonJump ()) + { + self.attack_state = AS_MISSILE; + sound (self, CHAN_VOICE, "demon/djump.wav", 1, ATTN_NORM); + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +void(float side) Demon_Melee = +{ + local float ldmg; + local vector delta; + + ai_face (); + walkmove (self.ideal_yaw, 12); // allow a little closing + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + if (!CanDamage (self.enemy, self)) + return; + + sound (self, CHAN_WEAPON, "demon/dhit2.wav", 1, ATTN_NORM); + ldmg = 10 + 5*random(); + T_Damage (self.enemy, self, self, ldmg); + + makevectorsfixed(self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); +}; + + +void() Demon_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 40 + 10*random(); + T_Damage (other, self, self, ldmg); + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up + self.touch = SUB_Null; + self.think = demon1_jump1; + self.nextthink = time + 0.1; + } + + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = demon1_jump11; + self.nextthink = time + 0.1; +}; + diff --git a/quakec/monsters/grunt.qc b/quakec/monsters/grunt.qc new file mode 100644 index 0000000..b0afe4d --- /dev/null +++ b/quakec/monsters/grunt.qc @@ -0,0 +1,333 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd id1/models/soldier3 +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 + +$frame load1 load2 load3 load4 load5 load6 load7 load8 load9 load10 load11 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 shoot7 shoot8 shoot9 + +$frame prowl_1 prowl_2 prowl_3 prowl_4 prowl_5 prowl_6 prowl_7 prowl_8 +$frame prowl_9 prowl_10 prowl_11 prowl_12 prowl_13 prowl_14 prowl_15 prowl_16 +$frame prowl_17 prowl_18 prowl_19 prowl_20 prowl_21 prowl_22 prowl_23 prowl_24 + +/* +============================================================================== +SOLDIER CODE +============================================================================== +*/ + +void() army_fire; + +void() army_stand1 =[ $stand1, army_stand2 ] {ai_stand();}; +void() army_stand2 =[ $stand2, army_stand3 ] {ai_stand();}; +void() army_stand3 =[ $stand3, army_stand4 ] {ai_stand();}; +void() army_stand4 =[ $stand4, army_stand5 ] {ai_stand();}; +void() army_stand5 =[ $stand5, army_stand6 ] {ai_stand();}; +void() army_stand6 =[ $stand6, army_stand7 ] {ai_stand();}; +void() army_stand7 =[ $stand7, army_stand8 ] {ai_stand();}; +void() army_stand8 =[ $stand8, army_stand1 ] {ai_stand();}; + +void() army_walk1 =[ $prowl_1, army_walk2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); + + ai_walk(1); +}; + +void() army_walk2 =[ $prowl_2, army_walk3 ] {ai_walk(1);}; +void() army_walk3 =[ $prowl_3, army_walk4 ] {ai_walk(1);}; +void() army_walk4 =[ $prowl_4, army_walk5 ] {ai_walk(1);}; +void() army_walk5 =[ $prowl_5, army_walk6 ] {ai_walk(2);}; +void() army_walk6 =[ $prowl_6, army_walk7 ] {ai_walk(3);}; +void() army_walk7 =[ $prowl_7, army_walk8 ] {ai_walk(4);}; +void() army_walk8 =[ $prowl_8, army_walk9 ] {ai_walk(4);}; +void() army_walk9 =[ $prowl_9, army_walk10 ] {ai_walk(2);}; +void() army_walk10 =[ $prowl_10, army_walk11 ] {ai_walk(2);}; +void() army_walk11 =[ $prowl_11, army_walk12 ] {ai_walk(2);}; +void() army_walk12 =[ $prowl_12, army_walk13 ] {ai_walk(1);}; +void() army_walk13 =[ $prowl_13, army_walk14 ] {ai_walk(0);}; +void() army_walk14 =[ $prowl_14, army_walk15 ] {ai_walk(1);}; +void() army_walk15 =[ $prowl_15, army_walk16 ] {ai_walk(1);}; +void() army_walk16 =[ $prowl_16, army_walk17 ] {ai_walk(1);}; +void() army_walk17 =[ $prowl_17, army_walk18 ] {ai_walk(3);}; +void() army_walk18 =[ $prowl_18, army_walk19 ] {ai_walk(3);}; +void() army_walk19 =[ $prowl_19, army_walk20 ] {ai_walk(3);}; +void() army_walk20 =[ $prowl_20, army_walk21 ] {ai_walk(3);}; +void() army_walk21 =[ $prowl_21, army_walk22 ] {ai_walk(2);}; +void() army_walk22 =[ $prowl_22, army_walk23 ] {ai_walk(1);}; +void() army_walk23 =[ $prowl_23, army_walk24 ] {ai_walk(1);}; +void() army_walk24 =[ $prowl_24, army_walk1 ] {ai_walk(1);}; + +void() army_run1 =[ $run1, army_run2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); + + ai_run(11); +}; + +void() army_run2 =[ $run2, army_run3 ] {ai_run(15);}; +void() army_run3 =[ $run3, army_run4 ] {ai_run(10);}; +void() army_run4 =[ $run4, army_run5 ] {ai_run(10);}; +void() army_run5 =[ $run5, army_run6 ] {ai_run(8);}; +void() army_run6 =[ $run6, army_run7 ] {ai_run(15);}; +void() army_run7 =[ $run7, army_run8 ] {ai_run(10);}; +void() army_run8 =[ $run8, army_run1 ] {ai_run(8);}; + +void() army_atk1 =[ $shoot1, army_atk2 ] {ai_face();}; +void() army_atk2 =[ $shoot2, army_atk3 ] {ai_face();}; +void() army_atk3 =[ $shoot3, army_atk4 ] {ai_face();}; +void() army_atk4 =[ $shoot4, army_atk5 ] {ai_face();}; +void() army_atk5 =[ $shoot5, army_atk6 ] { + ai_face(); + army_fire(); + self.effects = self.effects | EF_MUZZLEFLASH; +}; + +void() army_atk6 =[ $shoot6, army_atk7 ] {ai_face();}; +void() army_atk7 =[ $shoot7, army_atk8 ] { + ai_face(); + SUB_CheckRefire + (army_atk1); +}; +void() army_atk8 =[ $shoot8, army_atk9 ] {ai_face();}; +void() army_atk9 =[ $shoot9, army_run1 ] {ai_face();}; + + +void() army_pain1 =[ $pain1, army_pain2 ] {}; +void() army_pain2 =[ $pain2, army_pain3 ] {}; +void() army_pain3 =[ $pain3, army_pain4 ] {}; +void() army_pain4 =[ $pain4, army_pain5 ] {}; +void() army_pain5 =[ $pain5, army_pain6 ] {}; +void() army_pain6 =[ $pain6, army_run1 ] {ai_pain(1);}; + +void() army_painb1 =[ $painb1, army_painb2 ] {}; +void() army_painb2 =[ $painb2, army_painb3 ] {ai_painforward(13);}; +void() army_painb3 =[ $painb3, army_painb4 ] {ai_painforward(9);}; +void() army_painb4 =[ $painb4, army_painb5 ] {}; +void() army_painb5 =[ $painb5, army_painb6 ] {}; +void() army_painb6 =[ $painb6, army_painb7 ] {}; +void() army_painb7 =[ $painb7, army_painb8 ] {}; +void() army_painb8 =[ $painb8, army_painb9 ] {}; +void() army_painb9 =[ $painb9, army_painb10] {}; +void() army_painb10 =[ $painb10, army_painb11] {}; +void() army_painb11 =[ $painb11, army_painb12] {}; +void() army_painb12 =[ $painb12, army_painb13] {ai_pain(2);}; +void() army_painb13 =[ $painb13, army_painb14] {}; +void() army_painb14 =[ $painb14, army_run1 ] {}; + +void() army_painc1 =[ $painc1, army_painc2 ] {}; +void() army_painc2 =[ $painc2, army_painc3 ] {ai_pain(1);}; +void() army_painc3 =[ $painc3, army_painc4 ] {}; +void() army_painc4 =[ $painc4, army_painc5 ] {}; +void() army_painc5 =[ $painc5, army_painc6 ] {ai_painforward(1);}; +void() army_painc6 =[ $painc6, army_painc7 ] {ai_painforward(1);}; +void() army_painc7 =[ $painc7, army_painc8 ] {}; +void() army_painc8 =[ $painc8, army_painc9 ] {ai_pain(1);}; +void() army_painc9 =[ $painc9, army_painc10] {ai_painforward(4);}; +void() army_painc10=[ $painc10, army_painc11] {ai_painforward(3);}; +void() army_painc11=[ $painc11, army_painc12] {ai_painforward(6);}; +void() army_painc12=[ $painc12, army_painc13] {ai_painforward(8);}; +void() army_painc13=[ $painc13, army_run1] {}; + +void(entity attacker, float damage) army_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (r < 0.2) + { + self.pain_finished = time + 0.6; + army_pain1 (); + sound (self, CHAN_VOICE, "soldier/pain1.wav", 1, ATTN_NORM); + } + else if (r < 0.6) + { + self.pain_finished = time + 1.1; + army_painb1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } + else + { + self.pain_finished = time + 1.1; + army_painc1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } +}; + + +void() army_fire = +{ + local vector dir; + local entity en; + + ai_face(); + + sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM); + + // fire somewhat behind the player, so a dodging player is harder to hit + en = self.enemy; + + dir = en.origin - en.velocity*0.2; + dir = normalize (dir - self.origin); + + FireBullets (4, dir, '0.1 0.1 0'); +}; + + + +void() army_die1 =[ $death1, army_die2 ] {}; +void() army_die2 =[ $death2, army_die3 ] {}; +void() army_die3 =[ $death3, army_die4 ] { + self.solid = SOLID_NOT; + self.ammo_shells = 5; + DropBackpack(); +}; + +void() army_die4 =[ $death4, army_die5 ] {}; +void() army_die5 =[ $death5, army_die6 ] {}; +void() army_die6 =[ $death6, army_die7 ] {}; +void() army_die7 =[ $death7, army_die8 ] {}; +void() army_die8 =[ $death8, army_die9 ] {}; +void() army_die9 =[ $death9, army_die10 ] {}; +void() army_die10 =[ $death10, army_die10 ] {}; + +void() army_cdie1 =[ $deathc1, army_cdie2 ] {}; +void() army_cdie2 =[ $deathc2, army_cdie3 ] {ai_back(5);}; +void() army_cdie3 =[ $deathc3, army_cdie4 ] { + self.solid = SOLID_NOT; + self.ammo_shells = 5; + DropBackpack(); + ai_back(4); +}; + +void() army_cdie4 =[ $deathc4, army_cdie5 ] {ai_back(13);}; +void() army_cdie5 =[ $deathc5, army_cdie6 ] {ai_back(3);}; +void() army_cdie6 =[ $deathc6, army_cdie7 ] {ai_back(4);}; +void() army_cdie7 =[ $deathc7, army_cdie8 ] {}; +void() army_cdie8 =[ $deathc8, army_cdie9 ] {}; +void() army_cdie9 =[ $deathc9, army_cdie10 ] {}; +void() army_cdie10 =[ $deathc10, army_cdie11 ] {}; +void() army_cdie11 =[ $deathc11, army_cdie11 ] {}; + + +void() army_die = +{ + // check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_guard.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + // regular death + sound (self, CHAN_VOICE, "soldier/death1.wav", 1, ATTN_NORM); + if (random() < 0.5) + army_die1 (); + else + army_cdie1 (); +}; + + +/*QUAKED monster_army (1 0 0) (-16 -16 -24) (16 16 40) Ambush*/ +void() monster_army = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/soldier.mdl"); + precache_model ("progs/h_guard.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_sound ("soldier/death1.wav"); + precache_sound ("soldier/idle.wav"); + precache_sound ("soldier/pain1.wav"); + precache_sound ("soldier/pain2.wav"); + precache_sound ("soldier/sattck1.wav"); + precache_sound ("soldier/sight1.wav"); + + precache_sound ("player/udeath.wav"); // gib death + + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/soldier.mdl"); + + self.noise = "soldier/sight1.wav"; + self.netname = "$qc_grunt"; + self.killstring = "$qc_ks_grunt"; + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 30; + self.max_health = 30; + + self.th_stand = army_stand1; + self.th_walk = army_walk1; + self.th_run = army_run1; + self.th_missile = army_atk1; + self.th_pain = army_pain; + self.th_die = army_die; + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + + walkmonster_start (); +}; diff --git a/quakec/monsters/hellknight.qc b/quakec/monsters/hellknight.qc new file mode 100644 index 0000000..00bee95 --- /dev/null +++ b/quakec/monsters/hellknight.qc @@ -0,0 +1,485 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd id1/models/knight2 +$origin 0 0 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 walk15 walk16 walk17 +$frame walk18 walk19 walk20 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 death12 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame char_a1 char_a2 char_a3 char_a4 char_a5 char_a6 char_a7 char_a8 +$frame char_a9 char_a10 char_a11 char_a12 char_a13 char_a14 char_a15 char_a16 + +$frame magica1 magica2 magica3 magica4 magica5 magica6 magica7 magica8 +$frame magica9 magica10 magica11 magica12 magica13 magica14 + +$frame magicb1 magicb2 magicb3 magicb4 magicb5 magicb6 magicb7 magicb8 +$frame magicb9 magicb10 magicb11 magicb12 magicb13 + +$frame char_b1 char_b2 char_b3 char_b4 char_b5 char_b6 + +$frame slice1 slice2 slice3 slice4 slice5 slice6 slice7 slice8 slice9 slice10 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 smash8 smash9 smash10 +$frame smash11 + +$frame w_attack1 w_attack2 w_attack3 w_attack4 w_attack5 w_attack6 w_attack7 +$frame w_attack8 w_attack9 w_attack10 w_attack11 w_attack12 w_attack13 w_attack14 +$frame w_attack15 w_attack16 w_attack17 w_attack18 w_attack19 w_attack20 +$frame w_attack21 w_attack22 + +$frame magicc1 magicc2 magicc3 magicc4 magicc5 magicc6 magicc7 magicc8 +$frame magicc9 magicc10 magicc11 + + +void() hknight_char_a1; +void() hknight_run1; +void() hk_idle_sound; + +void(float offset) hknight_shot = +{ + local vector offang; + local vector org, vec; + + offang = vectoangles (self.enemy.origin - self.origin); + offang_y = offang_y + offset * 6; + + makevectors (offang); + + org = self.origin + self.mins + self.size*0.5 + v_forward * 20; + + // set missile speed + vec = normalize (v_forward); + vec_z = 0 - vec_z + (random() - 0.5)*0.1; + + launch_spike (org, vec); + newmis.classname = "knight_spike"; + setmodel (newmis, "progs/k_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.effects = newmis.effects | EF_CANDLELIGHT; // give the projectiles a weak glow + sound (self, CHAN_WEAPON, "hknight/attack1.wav", 1, ATTN_NORM); +}; + +void() CheckForCharge = +{ + // check for mad charge + if (!enemy_visible) + return; + + if (time < self.attack_finished) + return; + + if ( fabs(self.origin_z - self.enemy.origin_z) > 20) + return; // too much height change + + if ( vlen (self.origin - self.enemy.origin) < 80) + return; // use regular attack + + // charge + SUB_AttackFinished (2); + hknight_char_a1 (); +}; + +void() CheckContinueCharge = +{ + if (time > self.attack_finished) + { + SUB_AttackFinished (3); + hknight_run1 (); + return; // done charging + } + + if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); + else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +}; + +//=========================================================================== + +void() hknight_stand1 =[ $stand1, hknight_stand2 ] {ai_stand();}; +void() hknight_stand2 =[ $stand2, hknight_stand3 ] {ai_stand();}; +void() hknight_stand3 =[ $stand3, hknight_stand4 ] {ai_stand();}; +void() hknight_stand4 =[ $stand4, hknight_stand5 ] {ai_stand();}; +void() hknight_stand5 =[ $stand5, hknight_stand6 ] {ai_stand();}; +void() hknight_stand6 =[ $stand6, hknight_stand7 ] {ai_stand();}; +void() hknight_stand7 =[ $stand7, hknight_stand8 ] {ai_stand();}; +void() hknight_stand8 =[ $stand8, hknight_stand9 ] {ai_stand();}; +void() hknight_stand9 =[ $stand9, hknight_stand1 ] {ai_stand();}; + +//=========================================================================== + +void() hknight_walk1 =[ $walk1, hknight_walk2 ] { + hk_idle_sound(); + ai_walk(2); +}; + +void() hknight_walk2 =[ $walk2, hknight_walk3 ] {ai_walk(5);}; +void() hknight_walk3 =[ $walk3, hknight_walk4 ] {ai_walk(5);}; +void() hknight_walk4 =[ $walk4, hknight_walk5 ] {ai_walk(4);}; +void() hknight_walk5 =[ $walk5, hknight_walk6 ] {ai_walk(4);}; +void() hknight_walk6 =[ $walk6, hknight_walk7 ] {ai_walk(2);}; +void() hknight_walk7 =[ $walk7, hknight_walk8 ] {ai_walk(2);}; +void() hknight_walk8 =[ $walk8, hknight_walk9 ] {ai_walk(3);}; +void() hknight_walk9 =[ $walk9, hknight_walk10 ] {ai_walk(3);}; +void() hknight_walk10 =[ $walk10, hknight_walk11 ] {ai_walk(4);}; +void() hknight_walk11 =[ $walk11, hknight_walk12 ] {ai_walk(3);}; +void() hknight_walk12 =[ $walk12, hknight_walk13 ] {ai_walk(4);}; +void() hknight_walk13 =[ $walk13, hknight_walk14 ] {ai_walk(6);}; +void() hknight_walk14 =[ $walk14, hknight_walk15 ] {ai_walk(2);}; +void() hknight_walk15 =[ $walk15, hknight_walk16 ] {ai_walk(2);}; +void() hknight_walk16 =[ $walk16, hknight_walk17 ] {ai_walk(4);}; +void() hknight_walk17 =[ $walk17, hknight_walk18 ] {ai_walk(3);}; +void() hknight_walk18 =[ $walk18, hknight_walk19 ] {ai_walk(3);}; +void() hknight_walk19 =[ $walk19, hknight_walk20 ] {ai_walk(3);}; +void() hknight_walk20 =[ $walk20, hknight_walk1 ] {ai_walk(2);}; + +//=========================================================================== + +void() hknight_run1 =[ $run1, hknight_run2 ] { + hk_idle_sound(); + ai_run (20); + CheckForCharge (); +}; + +void() hknight_run2 =[ $run2, hknight_run3 ] {ai_run(25);}; +void() hknight_run3 =[ $run3, hknight_run4 ] {ai_run(18);}; +void() hknight_run4 =[ $run4, hknight_run5 ] {ai_run(16);}; +void() hknight_run5 =[ $run5, hknight_run6 ] {ai_run(14);}; +void() hknight_run6 =[ $run6, hknight_run7 ] {ai_run(25);}; +void() hknight_run7 =[ $run7, hknight_run8 ] {ai_run(21);}; +void() hknight_run8 =[ $run8, hknight_run1 ] {ai_run(13);}; + +//============================================================================ + +void() hknight_pain1 =[ $pain1, hknight_pain2 ] { + sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM); +}; + +void() hknight_pain2 =[ $pain2, hknight_pain3 ] {}; +void() hknight_pain3 =[ $pain3, hknight_pain4 ] {}; +void() hknight_pain4 =[ $pain4, hknight_pain5 ] {}; +void() hknight_pain5 =[ $pain5, hknight_run1 ] {}; + +//============================================================================ + +void() hknight_die1 =[ $death1, hknight_die2 ] {ai_forward(10);}; +void() hknight_die2 =[ $death2, hknight_die3 ] {ai_forward(8);}; +void() hknight_die3 =[ $death3, hknight_die4 ] { + self.solid = SOLID_NOT; + ai_forward(7); +}; + +void() hknight_die4 =[ $death4, hknight_die5 ] {}; +void() hknight_die5 =[ $death5, hknight_die6 ] {}; +void() hknight_die6 =[ $death6, hknight_die7 ] {}; +void() hknight_die7 =[ $death7, hknight_die8 ] {}; +void() hknight_die8 =[ $death8, hknight_die9 ] {ai_forward(10);}; +void() hknight_die9 =[ $death9, hknight_die10 ] {ai_forward(11);}; +void() hknight_die10 =[ $death10, hknight_die11 ] {}; +void() hknight_die11 =[ $death11, hknight_die12 ] {}; +void() hknight_die12 =[ $death12, hknight_die12 ] {}; + +void() hknight_dieb1 =[ $deathb1, hknight_dieb2 ] {}; +void() hknight_dieb2 =[ $deathb2, hknight_dieb3 ] {}; +void() hknight_dieb3 =[ $deathb3, hknight_dieb4 ] {self.solid = SOLID_NOT;}; +void() hknight_dieb4 =[ $deathb4, hknight_dieb5 ] {}; +void() hknight_dieb5 =[ $deathb5, hknight_dieb6 ] {}; +void() hknight_dieb6 =[ $deathb6, hknight_dieb7 ] {}; +void() hknight_dieb7 =[ $deathb7, hknight_dieb8 ] {}; +void() hknight_dieb8 =[ $deathb8, hknight_dieb9 ] {}; +void() hknight_dieb9 =[ $deathb9, hknight_dieb9 ] {}; + +void() hknight_die = +{ + // check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_hellkn.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + // regular death + sound (self, CHAN_VOICE, "hknight/death1.wav", 1, ATTN_NORM); + + if (random() > 0.5) + hknight_die1 (); + else + hknight_dieb1 (); +}; + + +//============================================================================ + +void() hknight_magica1 =[ $magica1, hknight_magica2 ] {ai_face();}; +void() hknight_magica2 =[ $magica2, hknight_magica3 ] {ai_face();}; +void() hknight_magica3 =[ $magica3, hknight_magica4 ] {ai_face();}; +void() hknight_magica4 =[ $magica4, hknight_magica5 ] {ai_face();}; +void() hknight_magica5 =[ $magica5, hknight_magica6 ] {ai_face();}; +void() hknight_magica6 =[ $magica6, hknight_magica7 ] {ai_face();}; +void() hknight_magica7 =[ $magica7, hknight_magica8 ] {hknight_shot(-2);}; +void() hknight_magica8 =[ $magica8, hknight_magica9 ] {hknight_shot(-1);}; +void() hknight_magica9 =[ $magica9, hknight_magica10] {hknight_shot(0);}; +void() hknight_magica10 =[ $magica10, hknight_magica11] {hknight_shot(1);}; +void() hknight_magica11 =[ $magica11, hknight_magica12] {hknight_shot(2);}; +void() hknight_magica12 =[ $magica12, hknight_magica13] {hknight_shot(3);}; +void() hknight_magica13 =[ $magica13, hknight_magica14] {ai_face();}; +void() hknight_magica14 =[ $magica14, hknight_run1 ] {ai_face();}; + +//============================================================================ + +void() hknight_magicb1 =[ $magicb1, hknight_magicb2 ] {ai_face();}; +void() hknight_magicb2 =[ $magicb2, hknight_magicb3 ] {ai_face();}; +void() hknight_magicb3 =[ $magicb3, hknight_magicb4 ] {ai_face();}; +void() hknight_magicb4 =[ $magicb4, hknight_magicb5 ] {ai_face();}; +void() hknight_magicb5 =[ $magicb5, hknight_magicb6 ] {ai_face();}; +void() hknight_magicb6 =[ $magicb6, hknight_magicb7 ] {ai_face();}; +void() hknight_magicb7 =[ $magicb7, hknight_magicb8 ] {hknight_shot(-2);}; +void() hknight_magicb8 =[ $magicb8, hknight_magicb9 ] {hknight_shot(-1);}; +void() hknight_magicb9 =[ $magicb9, hknight_magicb10] {hknight_shot(0);}; +void() hknight_magicb10 =[ $magicb10, hknight_magicb11] {hknight_shot(1);}; +void() hknight_magicb11 =[ $magicb11, hknight_magicb12] {hknight_shot(2);}; +void() hknight_magicb12 =[ $magicb12, hknight_magicb13] {hknight_shot(3);}; +void() hknight_magicb13 =[ $magicb13, hknight_run1] {ai_face();}; + +//============================================================================ + +void() hknight_magicc1 =[ $magicc1, hknight_magicc2 ] {ai_face();}; +void() hknight_magicc2 =[ $magicc2, hknight_magicc3 ] {ai_face();}; +void() hknight_magicc3 =[ $magicc3, hknight_magicc4 ] {ai_face();}; +void() hknight_magicc4 =[ $magicc4, hknight_magicc5 ] {ai_face();}; +void() hknight_magicc5 =[ $magicc5, hknight_magicc6 ] {ai_face();}; +void() hknight_magicc6 =[ $magicc6, hknight_magicc7 ] {hknight_shot(-2);}; +void() hknight_magicc7 =[ $magicc7, hknight_magicc8 ] {hknight_shot(-1);}; +void() hknight_magicc8 =[ $magicc8, hknight_magicc9 ] {hknight_shot(0);}; +void() hknight_magicc9 =[ $magicc9, hknight_magicc10] {hknight_shot(1);}; +void() hknight_magicc10 =[ $magicc10, hknight_magicc11] {hknight_shot(2);}; +void() hknight_magicc11 =[ $magicc11, hknight_run1] {hknight_shot(3);}; + +//=========================================================================== + +void() hknight_char_a1 =[ $char_a1, hknight_char_a2 ] {ai_charge(20);}; +void() hknight_char_a2 =[ $char_a2, hknight_char_a3 ] {ai_charge(25);}; +void() hknight_char_a3 =[ $char_a3, hknight_char_a4 ] {ai_charge(18);}; +void() hknight_char_a4 =[ $char_a4, hknight_char_a5 ] {ai_charge(16);}; +void() hknight_char_a5 =[ $char_a5, hknight_char_a6 ] {ai_charge(14);}; +void() hknight_char_a6 =[ $char_a6, hknight_char_a7 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a7 =[ $char_a7, hknight_char_a8 ] {ai_charge(21); ai_melee();}; +void() hknight_char_a8 =[ $char_a8, hknight_char_a9 ] {ai_charge(13); ai_melee();}; +void() hknight_char_a9 =[ $char_a9, hknight_char_a10 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a10=[ $char_a10, hknight_char_a11 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a11=[ $char_a11, hknight_char_a12 ] {ai_charge(18); ai_melee();}; +void() hknight_char_a12=[ $char_a12, hknight_char_a13 ] {ai_charge(16);}; +void() hknight_char_a13=[ $char_a13, hknight_char_a14 ] {ai_charge(14);}; +void() hknight_char_a14=[ $char_a14, hknight_char_a15 ] {ai_charge(25);}; +void() hknight_char_a15=[ $char_a15, hknight_char_a16 ] {ai_charge(21);}; +void() hknight_char_a16=[ $char_a16, hknight_run1 ] {ai_charge(13);}; + +//=========================================================================== + +void() hknight_char_b1 =[ $char_b1, hknight_char_b2 ] { + CheckContinueCharge(); + ai_charge(23); + ai_melee(); +}; + +void() hknight_char_b2 =[ $char_b2, hknight_char_b3 ] {ai_charge(17); ai_melee();}; +void() hknight_char_b3 =[ $char_b3, hknight_char_b4 ] {ai_charge(12); ai_melee();}; +void() hknight_char_b4 =[ $char_b4, hknight_char_b5 ] {ai_charge(22); ai_melee();}; +void() hknight_char_b5 =[ $char_b5, hknight_char_b6 ] {ai_charge(18); ai_melee();}; +void() hknight_char_b6 =[ $char_b6, hknight_char_b1 ] {ai_charge(8); ai_melee();}; + +//=========================================================================== + +void() hknight_slice1 =[ $slice1, hknight_slice2 ] {ai_charge(9);}; +void() hknight_slice2 =[ $slice2, hknight_slice3 ] {ai_charge(6);}; +void() hknight_slice3 =[ $slice3, hknight_slice4 ] {ai_charge(13);}; +void() hknight_slice4 =[ $slice4, hknight_slice5 ] {ai_charge(4);}; +void() hknight_slice5 =[ $slice5, hknight_slice6 ] {ai_charge(7); ai_melee();}; +void() hknight_slice6 =[ $slice6, hknight_slice7 ] {ai_charge(15); ai_melee();}; +void() hknight_slice7 =[ $slice7, hknight_slice8 ] {ai_charge(8); ai_melee();}; +void() hknight_slice8 =[ $slice8, hknight_slice9 ] {ai_charge(2); ai_melee();}; +void() hknight_slice9 =[ $slice9, hknight_slice10 ] {ai_melee();}; +void() hknight_slice10 =[ $slice10, hknight_run1 ] {ai_charge(3);}; + +//=========================================================================== + +void() hknight_smash1 =[ $smash1, hknight_smash2 ] {ai_charge(1);}; +void() hknight_smash2 =[ $smash2, hknight_smash3 ] {ai_charge(13);}; +void() hknight_smash3 =[ $smash3, hknight_smash4 ] {ai_charge(9);}; +void() hknight_smash4 =[ $smash4, hknight_smash5 ] {ai_charge(11);}; +void() hknight_smash5 =[ $smash5, hknight_smash6 ] {ai_charge(10); ai_melee();}; +void() hknight_smash6 =[ $smash6, hknight_smash7 ] {ai_charge(7); ai_melee();}; +void() hknight_smash7 =[ $smash7, hknight_smash8 ] {ai_charge(12); ai_melee();}; +void() hknight_smash8 =[ $smash8, hknight_smash9 ] {ai_charge(2); ai_melee();}; +void() hknight_smash9 =[ $smash9, hknight_smash10 ] {ai_charge(3); ai_melee();}; +void() hknight_smash10 =[ $smash10, hknight_smash11 ] {ai_charge(0);}; +void() hknight_smash11 =[ $smash11, hknight_run1 ] {ai_charge(0);}; + +//============================================================================ + +void() hknight_watk1 =[ $w_attack1, hknight_watk2 ] {ai_charge(2);}; +void() hknight_watk2 =[ $w_attack2, hknight_watk3 ] {ai_charge(0);}; +void() hknight_watk3 =[ $w_attack3, hknight_watk4 ] {ai_charge(0);}; +void() hknight_watk4 =[ $w_attack4, hknight_watk5 ] {ai_melee();}; +void() hknight_watk5 =[ $w_attack5, hknight_watk6 ] {ai_melee();}; +void() hknight_watk6 =[ $w_attack6, hknight_watk7 ] {ai_melee();}; +void() hknight_watk7 =[ $w_attack7, hknight_watk8 ] {ai_charge(1);}; +void() hknight_watk8 =[ $w_attack8, hknight_watk9 ] {ai_charge(4);}; +void() hknight_watk9 =[ $w_attack9, hknight_watk10 ] {ai_charge(5);}; +void() hknight_watk10 =[ $w_attack10, hknight_watk11 ] {ai_charge(3); ai_melee();}; +void() hknight_watk11 =[ $w_attack11, hknight_watk12 ] {ai_charge(2); ai_melee();}; +void() hknight_watk12 =[ $w_attack12, hknight_watk13 ] {ai_charge(2); ai_melee();}; +void() hknight_watk13 =[ $w_attack13, hknight_watk14 ] {ai_charge(0);}; +void() hknight_watk14 =[ $w_attack14, hknight_watk15 ] {ai_charge(0);}; +void() hknight_watk15 =[ $w_attack15, hknight_watk16 ] {ai_charge(0);}; +void() hknight_watk16 =[ $w_attack16, hknight_watk17 ] {ai_charge(1);}; +void() hknight_watk17 =[ $w_attack17, hknight_watk18 ] {ai_charge(1); ai_melee();}; +void() hknight_watk18 =[ $w_attack18, hknight_watk19 ] {ai_charge(3); ai_melee();}; +void() hknight_watk19 =[ $w_attack19, hknight_watk20 ] {ai_charge(4); ai_melee();}; +void() hknight_watk20 =[ $w_attack20, hknight_watk21 ] {ai_charge(6);}; +void() hknight_watk21 =[ $w_attack21, hknight_watk22 ] {ai_charge(7);}; +void() hknight_watk22 =[ $w_attack22, hknight_run1 ] {ai_charge(3);}; + +//============================================================================ + +void() hk_idle_sound = +{ + if (random() < 0.2) + sound (self, CHAN_VOICE, "hknight/idle.wav", 1, ATTN_NORM); +}; + +void(entity attacker, float damage) hknight_pain = +{ + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM); + + if (time - self.pain_finished > 5) + { // always go into pain frame if it has been a while + hknight_pain1 (); + self.pain_finished = time + 1; + return; + } + + if ((random()*30 > damage) ) + return; // didn't flinch + + self.pain_finished = time + 1; + hknight_pain1 (); +}; + +float hknight_type; + +void() hknight_melee = +{ + hknight_type = hknight_type + 1; + + sound (self, CHAN_WEAPON, "hknight/slash1.wav", 1, ATTN_NORM); + if (hknight_type == 1) + hknight_slice1 (); + else if (hknight_type == 2) + hknight_smash1 (); + else if (hknight_type == 3) + { + hknight_watk1 (); + hknight_type = 0; + } +}; + +/*QUAKED monster_hell_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush*/ +void() monster_hell_knight = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/hknight.mdl"); + precache_model2 ("progs/k_spike.mdl"); + precache_model2 ("progs/h_hellkn.mdl"); + + + precache_sound2 ("hknight/attack1.wav"); + precache_sound2 ("hknight/death1.wav"); + precache_sound2 ("hknight/pain1.wav"); + precache_sound2 ("hknight/sight1.wav"); + precache_sound ("hknight/hit.wav"); // used by C code, so don't sound2 + precache_sound2 ("hknight/slash1.wav"); + precache_sound2 ("hknight/idle.wav"); + precache_sound2 ("hknight/grunt.wav"); + + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/hknight.mdl"); + + self.noise = "hknight/sight1.wav"; + self.netname = "$qc_death_knight"; + self.killstring = "$qc_ks_deathknight"; + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 250; + self.max_health = 250; + + self.th_stand = hknight_stand1; + self.th_walk = hknight_walk1; + self.th_run = hknight_run1; + self.th_melee = hknight_melee; + self.th_missile = hknight_magicc1; + self.th_pain = hknight_pain; + self.th_die = hknight_die; + self.allowPathFind = TRUE; + self.combat_style = CS_MIXED; + + walkmonster_start (); +}; diff --git a/quakec/monsters/knight.qc b/quakec/monsters/knight.qc new file mode 100644 index 0000000..9d01424 --- /dev/null +++ b/quakec/monsters/knight.qc @@ -0,0 +1,293 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd id1/models/knight +$origin 0 0 24 +$base base +$skin badass3 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame runb1 runb2 runb3 runb4 runb5 runb6 runb7 runb8 + +$frame runattack1 runattack2 runattack3 runattack4 runattack5 +$frame runattack6 runattack7 runattack8 runattack9 runattack10 +$frame runattack11 + +$frame pain1 pain2 pain3 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 +$frame painb10 painb11 + +$frame attackb1 attackb2 attackb3 attackb4 attackb5 attackb6 +$frame attackb7 attackb8 attackb9 attackb10 attackb11 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 + +$frame kneel1 kneel2 kneel3 kneel4 kneel5 + +$frame standing2 standing3 standing4 standing5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 deathb10 deathb11 + +void() knight_stand1 =[ $stand1, knight_stand2 ] {ai_stand();}; +void() knight_stand2 =[ $stand2, knight_stand3 ] {ai_stand();}; +void() knight_stand3 =[ $stand3, knight_stand4 ] {ai_stand();}; +void() knight_stand4 =[ $stand4, knight_stand5 ] {ai_stand();}; +void() knight_stand5 =[ $stand5, knight_stand6 ] {ai_stand();}; +void() knight_stand6 =[ $stand6, knight_stand7 ] {ai_stand();}; +void() knight_stand7 =[ $stand7, knight_stand8 ] {ai_stand();}; +void() knight_stand8 =[ $stand8, knight_stand9 ] {ai_stand();}; +void() knight_stand9 =[ $stand9, knight_stand1 ] {ai_stand();}; + +void() knight_walk1 =[ $walk1, knight_walk2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "knight/idle.wav", 1, ATTN_IDLE); + + ai_walk(3); +}; + +void() knight_walk2 =[ $walk2, knight_walk3 ] {ai_walk(2);}; +void() knight_walk3 =[ $walk3, knight_walk4 ] {ai_walk(3);}; +void() knight_walk4 =[ $walk4, knight_walk5 ] {ai_walk(4);}; +void() knight_walk5 =[ $walk5, knight_walk6 ] {ai_walk(3);}; +void() knight_walk6 =[ $walk6, knight_walk7 ] {ai_walk(3);}; +void() knight_walk7 =[ $walk7, knight_walk8 ] {ai_walk(3);}; +void() knight_walk8 =[ $walk8, knight_walk9 ] {ai_walk(4);}; +void() knight_walk9 =[ $walk9, knight_walk10 ] {ai_walk(3);}; +void() knight_walk10 =[ $walk10, knight_walk11 ] {ai_walk(3);}; +void() knight_walk11 =[ $walk11, knight_walk12 ] {ai_walk(2);}; +void() knight_walk12 =[ $walk12, knight_walk13 ] {ai_walk(3);}; +void() knight_walk13 =[ $walk13, knight_walk14 ] {ai_walk(4);}; +void() knight_walk14 =[ $walk14, knight_walk1 ] {ai_walk(3);}; + +void() knight_run1 =[ $runb1, knight_run2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "knight/idle.wav", 1, ATTN_IDLE); + + ai_run(16); +}; + +void() knight_run2 =[ $runb2, knight_run3 ] {ai_run(20);}; +void() knight_run3 =[ $runb3, knight_run4 ] {ai_run(13);}; +void() knight_run4 =[ $runb4, knight_run5 ] {ai_run(7);}; +void() knight_run5 =[ $runb5, knight_run6 ] {ai_run(16);}; +void() knight_run6 =[ $runb6, knight_run7 ] {ai_run(20);}; +void() knight_run7 =[ $runb7, knight_run8 ] {ai_run(14);}; +void() knight_run8 =[ $runb8, knight_run1 ] {ai_run(6);}; + +void() knight_runatk1 =[ $runattack1, knight_runatk2 ] { + if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); + else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); + + ai_charge(20); +}; + +void() knight_runatk2 =[ $runattack2, knight_runatk3 ] {ai_charge_side();}; +void() knight_runatk3 =[ $runattack3, knight_runatk4 ] {ai_charge_side();}; +void() knight_runatk4 =[ $runattack4, knight_runatk5 ] {ai_charge_side();}; +void() knight_runatk5 =[ $runattack5, knight_runatk6 ] {ai_melee_side();}; +void() knight_runatk6 =[ $runattack6, knight_runatk7 ] {ai_melee_side();}; +void() knight_runatk7 =[ $runattack7, knight_runatk8 ] {ai_melee_side();}; +void() knight_runatk8 =[ $runattack8, knight_runatk9 ] {ai_melee_side();}; +void() knight_runatk9 =[ $runattack9, knight_runatk10 ] {ai_melee_side();}; +void() knight_runatk10 =[ $runattack10, knight_runatk11 ] {ai_charge_side();}; +void() knight_runatk11 =[ $runattack11, knight_run1 ] {ai_charge(10);}; + +void() knight_atk1 =[ $attackb1, knight_atk2 ] { + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); + ai_charge(0); +}; + +void() knight_atk2 =[ $attackb2, knight_atk3 ] {ai_charge(7);}; +void() knight_atk3 =[ $attackb3, knight_atk4 ] {ai_charge(4);}; +void() knight_atk4 =[ $attackb4, knight_atk5 ] {ai_charge(0);}; +void() knight_atk5 =[ $attackb5, knight_atk6 ] {ai_charge(3);}; +void() knight_atk6 =[ $attackb6, knight_atk7 ] {ai_charge(4); ai_melee();}; +void() knight_atk7 =[ $attackb7, knight_atk8 ] {ai_charge(1); ai_melee();}; +void() knight_atk8 =[ $attackb8, knight_atk9 ] {ai_charge(3); ai_melee();}; +void() knight_atk9 =[ $attackb9, knight_atk10] {ai_charge(1);}; +void() knight_atk10 =[ $attackb10, knight_run1 ] {ai_charge(5);}; + +//=========================================================================== + +void() knight_pain1 =[ $pain1, knight_pain2 ] {}; +void() knight_pain2 =[ $pain2, knight_pain3 ] {}; +void() knight_pain3 =[ $pain3, knight_run1 ] {}; + +void() knight_painb1 =[ $painb1, knight_painb2 ] {ai_painforward(0);}; +void() knight_painb2 =[ $painb2, knight_painb3 ] {ai_painforward(3);}; +void() knight_painb3 =[ $painb3, knight_painb4 ] {}; +void() knight_painb4 =[ $painb4, knight_painb5 ] {}; +void() knight_painb5 =[ $painb5, knight_painb6 ] {ai_painforward(2);}; +void() knight_painb6 =[ $painb6, knight_painb7 ] {ai_painforward(4);}; +void() knight_painb7 =[ $painb7, knight_painb8 ] {ai_painforward(2);}; +void() knight_painb8 =[ $painb8, knight_painb9 ] {ai_painforward(5);}; +void() knight_painb9 =[ $painb9, knight_painb10 ] {ai_painforward(5);}; +void() knight_painb10 =[ $painb10, knight_painb11 ] {ai_painforward(0);}; +void() knight_painb11 =[ $painb11, knight_run1 ] {}; + +void(entity attacker, float damage) knight_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + sound (self, CHAN_VOICE, "knight/khurt.wav", 1, ATTN_NORM); + if (r < 0.85) + { + knight_pain1 (); + self.pain_finished = time + 1; + } + else + { + knight_painb1 (); + self.pain_finished = time + 1; + } + +}; + +//=========================================================================== + +void() knight_bow1 =[ $kneel1, knight_bow2 ] {ai_turn();}; +void() knight_bow2 =[ $kneel2, knight_bow3 ] {ai_turn();}; +void() knight_bow3 =[ $kneel3, knight_bow4 ] {ai_turn();}; +void() knight_bow4 =[ $kneel4, knight_bow5 ] {ai_turn();}; + +void() knight_bow5 =[ $kneel5, knight_bow5 ] {ai_turn();}; + +void() knight_bow6 =[ $kneel4, knight_bow7 ] {ai_turn();}; +void() knight_bow7 =[ $kneel3, knight_bow8 ] {ai_turn();}; +void() knight_bow8 =[ $kneel2, knight_bow9 ] {ai_turn();}; +void() knight_bow9 =[ $kneel1, knight_bow10 ] {ai_turn();}; +void() knight_bow10 =[ $walk1, knight_walk1 ] {ai_turn();}; + + + +void() knight_die1 =[ $death1, knight_die2 ] {}; +void() knight_die2 =[ $death2, knight_die3 ] {}; +void() knight_die3 =[ $death3, knight_die4 ] {self.solid = SOLID_NOT;}; +void() knight_die4 =[ $death4, knight_die5 ] {}; +void() knight_die5 =[ $death5, knight_die6 ] {}; +void() knight_die6 =[ $death6, knight_die7 ] {}; +void() knight_die7 =[ $death7, knight_die8 ] {}; +void() knight_die8 =[ $death8, knight_die9 ] {}; +void() knight_die9 =[ $death9, knight_die10] {}; +void() knight_die10=[ $death10, knight_die10] {}; + + +void() knight_dieb1 =[ $deathb1, knight_dieb2 ] {}; +void() knight_dieb2 =[ $deathb2, knight_dieb3 ] {}; +void() knight_dieb3 =[ $deathb3, knight_dieb4 ] {self.solid = SOLID_NOT;}; +void() knight_dieb4 =[ $deathb4, knight_dieb5 ] {}; +void() knight_dieb5 =[ $deathb5, knight_dieb6 ] {}; +void() knight_dieb6 =[ $deathb6, knight_dieb7 ] {}; +void() knight_dieb7 =[ $deathb7, knight_dieb8 ] {}; +void() knight_dieb8 =[ $deathb8, knight_dieb9 ] {}; +void() knight_dieb9 =[ $deathb9, knight_dieb10] {}; +void() knight_dieb10 = [ $deathb10, knight_dieb11] {}; +void() knight_dieb11 = [ $deathb11, knight_dieb11] {}; + + +void() knight_die = +{ + // check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_knight.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + // regular death + sound (self, CHAN_VOICE, "knight/kdeath.wav", 1, ATTN_NORM); + + if (random() < 0.5) + knight_die1 (); + else + knight_dieb1 (); +}; + + +/* +QUAKED monster_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_knight = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/knight.mdl"); + precache_model ("progs/h_knight.mdl"); + + precache_sound ("knight/kdeath.wav"); + precache_sound ("knight/khurt.wav"); + precache_sound ("knight/ksight.wav"); + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + precache_sound ("knight/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/knight.mdl"); + + self.noise = "knight/ksight.wav"; + self.netname = "$qc_knight"; + self.killstring = "$qc_ks_knight"; + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 75; + self.max_health = 75; + + self.th_stand = knight_stand1; + self.th_walk = knight_walk1; + self.th_run = knight_run1; + self.th_melee = knight_atk1; + self.th_pain = knight_pain; + self.th_die = knight_die; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + walkmonster_start (); +}; diff --git a/quakec/monsters/ogre.qc b/quakec/monsters/ogre.qc new file mode 100644 index 0000000..a82f6d9 --- /dev/null +++ b/quakec/monsters/ogre.qc @@ -0,0 +1,540 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OGRE + +============================================================================== +*/ + +$cd id1/models/ogre_c +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame swing1 swing2 swing3 swing4 swing5 swing6 swing7 +$frame swing8 swing9 swing10 swing11 swing12 swing13 swing14 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 smash13 smash14 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame painb1 painb2 painb3 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 paind14 paind15 paind16 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 death11 death12 +$frame death13 death14 + +$frame bdeath1 bdeath2 bdeath3 bdeath4 bdeath5 bdeath6 +$frame bdeath7 bdeath8 bdeath9 bdeath10 + +$frame pull1 pull2 pull3 pull4 pull5 pull6 pull7 pull8 pull9 pull10 pull11 + +//============================================================================= + + +void() OgreGrenadeExplode = +{ + T_RadiusDamage (self, self.owner, 40, world); + sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +void() OgreGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (other.takedamage == DAMAGE_AIM) + { + OgreGrenadeExplode(); + return; + } + + sound (self, CHAN_VOICE, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +/* +================ +OgreFireGrenade +================ +*/ +void() OgreFireGrenade = +{ + local entity missile; + + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.classname = "ogre_grenade"; + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + +// set missile speed + + makevectorsfixed(self.angles); + + missile.velocity = normalize(self.enemy.origin - self.origin); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + + missile.avelocity = '300 300 300'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = OgreGrenadeTouch; + + missile.effects = missile.effects | EF_CANDLELIGHT; // give the grenades a weak glow + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = OgreGrenadeExplode; + + setmodel (missile, "progs/grenade.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); +}; + + +//============================================================================= + +/* +================ +chainsaw + +FIXME +================ +*/ +void(float side) chainsaw = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + + if (!CanDamage (self.enemy, self)) + return; + + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 4; + T_Damage (self.enemy, self, self, ldmg); + + if (side) + { + makevectorsfixed(self.angles); + if (side == 1) + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + else + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + + +void() ogre_stand1 =[ $stand1, ogre_stand2 ] {ai_stand();}; +void() ogre_stand2 =[ $stand2, ogre_stand3 ] {ai_stand();}; +void() ogre_stand3 =[ $stand3, ogre_stand4 ] {ai_stand();}; +void() ogre_stand4 =[ $stand4, ogre_stand5 ] {ai_stand();}; +void() ogre_stand5 =[ $stand5, ogre_stand6 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); + + ai_stand(); +}; + +void() ogre_stand6 =[ $stand6, ogre_stand7 ] {ai_stand();}; +void() ogre_stand7 =[ $stand7, ogre_stand8 ] {ai_stand();}; +void() ogre_stand8 =[ $stand8, ogre_stand9 ] {ai_stand();}; +void() ogre_stand9 =[ $stand9, ogre_stand1 ] {ai_stand();}; + +void() ogre_walk1 =[ $walk1, ogre_walk2 ] {ai_walk(3);}; +void() ogre_walk2 =[ $walk2, ogre_walk3 ] {ai_walk(2);}; +void() ogre_walk3 =[ $walk3, ogre_walk4 ] { + ai_walk(2); + + if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); +}; + +void() ogre_walk4 =[ $walk4, ogre_walk5 ] {ai_walk(2);}; +void() ogre_walk5 =[ $walk5, ogre_walk6 ] {ai_walk(2);}; +void() ogre_walk6 =[ $walk6, ogre_walk7 ] { + ai_walk(5); + + if (random() < 0.1) + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE); +}; + +void() ogre_walk7 =[ $walk7, ogre_walk8 ] {ai_walk(3);}; +void() ogre_walk8 =[ $walk8, ogre_walk9 ] {ai_walk(2);}; +void() ogre_walk9 =[ $walk9, ogre_walk10 ] {ai_walk(3);}; +void() ogre_walk10 =[ $walk10, ogre_walk11 ] {ai_walk(1);}; +void() ogre_walk11 =[ $walk11, ogre_walk12 ] {ai_walk(2);}; +void() ogre_walk12 =[ $walk12, ogre_walk13 ] {ai_walk(3);}; +void() ogre_walk13 =[ $walk13, ogre_walk14 ] {ai_walk(3);}; +void() ogre_walk14 =[ $walk14, ogre_walk15 ] {ai_walk(3);}; +void() ogre_walk15 =[ $walk15, ogre_walk16 ] {ai_walk(3);}; +void() ogre_walk16 =[ $walk16, ogre_walk1 ] {ai_walk(4);}; + +void() ogre_run1 =[ $run1, ogre_run2 ] { + ai_run(9); + + if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle2.wav", 1, ATTN_IDLE); +}; + +void() ogre_run2 =[ $run2, ogre_run3 ] {ai_run(12);}; +void() ogre_run3 =[ $run3, ogre_run4 ] {ai_run(8);}; +void() ogre_run4 =[ $run4, ogre_run5 ] {ai_run(22);}; +void() ogre_run5 =[ $run5, ogre_run6 ] {ai_run(16);}; +void() ogre_run6 =[ $run6, ogre_run7 ] {ai_run(4);}; +void() ogre_run7 =[ $run7, ogre_run8 ] {ai_run(13);}; +void() ogre_run8 =[ $run8, ogre_run1 ] {ai_run(24);}; + +void() ogre_swing1 =[ $swing1, ogre_swing2 ] {ai_charge(11); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM);}; +void() ogre_swing2 =[ $swing2, ogre_swing3 ] {ai_charge(1);}; +void() ogre_swing3 =[ $swing3, ogre_swing4 ] {ai_charge(4);}; +void() ogre_swing4 =[ $swing4, ogre_swing5 ] {ai_charge(13);}; +void() ogre_swing5 =[ $swing5, ogre_swing6 ] { + ai_charge(9); + chainsaw(0); + self.angles_y = self.angles_y + random() * 25; +}; + +void() ogre_swing6 =[ $swing6, ogre_swing7 ] { + chainsaw(200); + self.angles_y = self.angles_y + random()* 25; +}; + +void() ogre_swing7 =[ $swing7, ogre_swing8 ] { + chainsaw(0); + self.angles_y = self.angles_y + random() * 25; +}; + +void() ogre_swing8 =[ $swing8, ogre_swing9 ] { + chainsaw(0); + self.angles_y = self.angles_y + random() * 25; +}; + +void() ogre_swing9 =[ $swing9, ogre_swing10 ] { + chainsaw(0); + self.angles_y = self.angles_y + random() * 25; +}; + +void() ogre_swing10 =[ $swing10, ogre_swing11 ] { + chainsaw(-200); + self.angles_y = self.angles_y + random() * 25; +}; + +void() ogre_swing11 =[ $swing11, ogre_swing12 ] { + chainsaw(0); + self.angles_y = self.angles_y + random() * 25; +}; + +void() ogre_swing12 =[ $swing12, ogre_swing13 ] {ai_charge(3);}; +void() ogre_swing13 =[ $swing13, ogre_swing14 ] {ai_charge(8);}; +void() ogre_swing14 =[ $swing14, ogre_run1 ] {ai_charge(9);}; + +void() ogre_smash1 =[ $smash1, ogre_smash2 ] {ai_charge(6); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM); +}; +void() ogre_smash2 =[ $smash2, ogre_smash3 ] {ai_charge(0);}; +void() ogre_smash3 =[ $smash3, ogre_smash4 ] {ai_charge(0);}; +void() ogre_smash4 =[ $smash4, ogre_smash5 ] {ai_charge(1);}; +void() ogre_smash5 =[ $smash5, ogre_smash6 ] {ai_charge(4);}; +void() ogre_smash6 =[ $smash6, ogre_smash7 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash7 =[ $smash7, ogre_smash8 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash8 =[ $smash8, ogre_smash9 ] {ai_charge(10); chainsaw(0);}; +void() ogre_smash9 =[ $smash9, ogre_smash10 ] {ai_charge(13); chainsaw(0);}; +void() ogre_smash10 =[ $smash10, ogre_smash11 ] {chainsaw(1);}; +void() ogre_smash11 =[ $smash11, ogre_smash12 ] { + ai_charge(2); + chainsaw(0); + self.nextthink = self.nextthink + random() * 0.2; +}; // slight variation + +void() ogre_smash12 =[ $smash12, ogre_smash13 ] {ai_charge(0);}; +void() ogre_smash13 =[ $smash13, ogre_smash14 ] {ai_charge(4);}; +void() ogre_smash14 =[ $smash14, ogre_run1 ] {ai_charge(12);}; + +void() ogre_nail1 =[ $shoot1, ogre_nail2 ] {ai_face();}; +void() ogre_nail2 =[ $shoot2, ogre_nail3 ] {ai_face();}; +void() ogre_nail3 =[ $shoot2, ogre_nail4 ] {ai_face();}; +void() ogre_nail4 =[ $shoot3, ogre_nail5 ] {ai_face();OgreFireGrenade();}; +void() ogre_nail5 =[ $shoot4, ogre_nail6 ] {ai_face();}; +void() ogre_nail6 =[ $shoot5, ogre_nail7 ] {ai_face();}; +void() ogre_nail7 =[ $shoot6, ogre_run1 ] {ai_face();}; + +void() ogre_pain1 =[ $pain1, ogre_pain2 ] {}; +void() ogre_pain2 =[ $pain2, ogre_pain3 ] {}; +void() ogre_pain3 =[ $pain3, ogre_pain4 ] {}; +void() ogre_pain4 =[ $pain4, ogre_pain5 ] {}; +void() ogre_pain5 =[ $pain5, ogre_run1 ] {}; + + +void() ogre_painb1 =[ $painb1, ogre_painb2 ] {}; +void() ogre_painb2 =[ $painb2, ogre_painb3 ] {}; +void() ogre_painb3 =[ $painb3, ogre_run1 ] {}; + + +void() ogre_painc1 =[ $painc1, ogre_painc2 ] {}; +void() ogre_painc2 =[ $painc2, ogre_painc3 ] {}; +void() ogre_painc3 =[ $painc3, ogre_painc4 ] {}; +void() ogre_painc4 =[ $painc4, ogre_painc5 ] {}; +void() ogre_painc5 =[ $painc5, ogre_painc6 ] {}; +void() ogre_painc6 =[ $painc6, ogre_run1 ] {}; + + +void() ogre_paind1 =[ $paind1, ogre_paind2 ] {}; +void() ogre_paind2 =[ $paind2, ogre_paind3 ] {ai_pain(10);}; +void() ogre_paind3 =[ $paind3, ogre_paind4 ] {ai_pain(9);}; +void() ogre_paind4 =[ $paind4, ogre_paind5 ] {ai_pain(4);}; +void() ogre_paind5 =[ $paind5, ogre_paind6 ] {}; +void() ogre_paind6 =[ $paind6, ogre_paind7 ] {}; +void() ogre_paind7 =[ $paind7, ogre_paind8 ] {}; +void() ogre_paind8 =[ $paind8, ogre_paind9 ] {}; +void() ogre_paind9 =[ $paind9, ogre_paind10 ] {}; +void() ogre_paind10=[ $paind10, ogre_paind11 ] {}; +void() ogre_paind11=[ $paind11, ogre_paind12 ] {}; +void() ogre_paind12=[ $paind12, ogre_paind13 ] {}; +void() ogre_paind13=[ $paind13, ogre_paind14 ] {}; +void() ogre_paind14=[ $paind14, ogre_paind15 ] {}; +void() ogre_paind15=[ $paind15, ogre_paind16 ] {}; +void() ogre_paind16=[ $paind16, ogre_run1 ] {}; + +void() ogre_paine1 =[ $paine1, ogre_paine2 ] {}; +void() ogre_paine2 =[ $paine2, ogre_paine3 ] {ai_pain(10);}; +void() ogre_paine3 =[ $paine3, ogre_paine4 ] {ai_pain(9);}; +void() ogre_paine4 =[ $paine4, ogre_paine5 ] {ai_pain(4);}; +void() ogre_paine5 =[ $paine5, ogre_paine6 ] {}; +void() ogre_paine6 =[ $paine6, ogre_paine7 ] {}; +void() ogre_paine7 =[ $paine7, ogre_paine8 ] {}; +void() ogre_paine8 =[ $paine8, ogre_paine9 ] {}; +void() ogre_paine9 =[ $paine9, ogre_paine10 ] {}; +void() ogre_paine10=[ $paine10, ogre_paine11 ] {}; +void() ogre_paine11=[ $paine11, ogre_paine12 ] {}; +void() ogre_paine12=[ $paine12, ogre_paine13 ] {}; +void() ogre_paine13=[ $paine13, ogre_paine14 ] {}; +void() ogre_paine14=[ $paine14, ogre_paine15 ] {}; +void() ogre_paine15=[ $paine15, ogre_run1 ] {}; + + +void(entity attacker, float damage) ogre_pain = +{ + local float r; + + // don't make multiple pain sounds right after each other + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "ogre/ogpain1.wav", 1, ATTN_NORM); + + r = random(); + + if (r < 0.25) + { + ogre_pain1 (); + self.pain_finished = time + 1; + } + else if (r < 0.5) + { + ogre_painb1 (); + self.pain_finished = time + 1; + } + else if (r < 0.75) + { + ogre_painc1 (); + self.pain_finished = time + 1; + } + else if (r < 0.88) + { + ogre_paind1 (); + self.pain_finished = time + 2; + } + else + { + ogre_paine1 (); + self.pain_finished = time + 2; + } +}; + +void() ogre_die1 =[ $death1, ogre_die2 ] {}; +void() ogre_die2 =[ $death2, ogre_die3 ] {}; +void() ogre_die3 =[ $death3, ogre_die4 ] { + self.solid = SOLID_NOT; + self.ammo_rockets = 2;DropBackpack(); +}; + +void() ogre_die4 =[ $death4, ogre_die5 ] {}; +void() ogre_die5 =[ $death5, ogre_die6 ] {}; +void() ogre_die6 =[ $death6, ogre_die7 ] {}; +void() ogre_die7 =[ $death7, ogre_die8 ] {}; +void() ogre_die8 =[ $death8, ogre_die9 ] {}; +void() ogre_die9 =[ $death9, ogre_die10 ] {}; +void() ogre_die10 =[ $death10, ogre_die11 ] {}; +void() ogre_die11 =[ $death11, ogre_die12 ] {}; +void() ogre_die12 =[ $death12, ogre_die13 ] {}; +void() ogre_die13 =[ $death13, ogre_die14 ] {}; +void() ogre_die14 =[ $death14, ogre_die14 ] {}; + +void() ogre_bdie1 =[ $bdeath1, ogre_bdie2 ] {}; +void() ogre_bdie2 =[ $bdeath2, ogre_bdie3 ] {ai_forward(5);}; +void() ogre_bdie3 =[ $bdeath3, ogre_bdie4 ] { + self.solid = SOLID_NOT; + self.ammo_rockets = 2; + DropBackpack(); +}; + +void() ogre_bdie4 =[ $bdeath4, ogre_bdie5 ] {ai_forward(1);}; +void() ogre_bdie5 =[ $bdeath5, ogre_bdie6 ] {ai_forward(3);}; +void() ogre_bdie6 =[ $bdeath6, ogre_bdie7 ] {ai_forward(7);}; +void() ogre_bdie7 =[ $bdeath7, ogre_bdie8 ] {ai_forward(25);}; +void() ogre_bdie8 =[ $bdeath8, ogre_bdie9 ] {}; +void() ogre_bdie9 =[ $bdeath9, ogre_bdie10 ] {}; +void() ogre_bdie10 =[ $bdeath10, ogre_bdie10 ] {}; + +void() ogre_die = +{ + // check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_ogre.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "ogre/ogdth.wav", 1, ATTN_NORM); + + if (random() < 0.5) + ogre_die1 (); + else + ogre_bdie1 (); +}; + +void() ogre_melee = +{ + if (random() > 0.5) + ogre_smash1 (); + else + ogre_swing1 (); +}; + + +/*QUAKED monster_ogre (1 0 0) (-32 -32 -24) (32 32 64) Ambush + +*/ +void() monster_ogre = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/ogre.mdl"); + precache_model ("progs/h_ogre.mdl"); + precache_model ("progs/grenade.mdl"); + + precache_sound ("ogre/ogdrag.wav"); + precache_sound ("ogre/ogdth.wav"); + precache_sound ("ogre/ogidle.wav"); + precache_sound ("ogre/ogidle2.wav"); + precache_sound ("ogre/ogpain1.wav"); + precache_sound ("ogre/ogsawatk.wav"); + precache_sound ("ogre/ogwake.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/ogre.mdl"); + + self.noise = "ogre/ogwake.wav"; + self.netname = "$qc_ogre"; + self.killstring = "$qc_ks_ogre"; + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 200; + self.max_health = 200; + + self.th_stand = ogre_stand1; + self.th_walk = ogre_walk1; + self.th_run = ogre_run1; + self.th_die = ogre_die; + self.th_melee = ogre_melee; + self.th_missile = ogre_nail1; + self.th_pain = ogre_pain; + self.allowPathFind = TRUE; + self.combat_style = CS_MIXED; + + walkmonster_start(); +}; + +void() monster_ogre_marksman = +{ + monster_ogre (); +}; + + diff --git a/quakec/monsters/rotfish.qc b/quakec/monsters/rotfish.qc new file mode 100644 index 0000000..e7cd5cf --- /dev/null +++ b/quakec/monsters/rotfish.qc @@ -0,0 +1,218 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +$cd id1/models/fish +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 attack11 attack12 attack13 +$frame attack14 attack15 attack16 attack17 attack18 + +$frame death1 death2 death3 death4 death5 death6 death7 +$frame death8 death9 death10 death11 death12 death13 death14 death15 +$frame death16 death17 death18 death19 death20 death21 + +$frame swim1 swim2 swim3 swim4 swim5 swim6 swim7 swim8 +$frame swim9 swim10 swim11 swim12 swim13 swim14 swim15 swim16 swim17 +$frame swim18 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 pain7 pain8 +$frame pain9 + +void() swimmonster_start; + +void() f_stand1 =[ $swim1, f_stand2 ] {ai_stand();}; +void() f_stand2 =[ $swim2, f_stand3 ] {ai_stand();}; +void() f_stand3 =[ $swim3, f_stand4 ] {ai_stand();}; +void() f_stand4 =[ $swim4, f_stand5 ] {ai_stand();}; +void() f_stand5 =[ $swim5, f_stand6 ] {ai_stand();}; +void() f_stand6 =[ $swim6, f_stand7 ] {ai_stand();}; +void() f_stand7 =[ $swim7, f_stand8 ] {ai_stand();}; +void() f_stand8 =[ $swim8, f_stand9 ] {ai_stand();}; +void() f_stand9 =[ $swim9, f_stand10 ] {ai_stand();}; +void() f_stand10 =[ $swim10, f_stand11 ] {ai_stand();}; +void() f_stand11 =[ $swim11, f_stand12 ] {ai_stand();}; +void() f_stand12 =[ $swim12, f_stand13 ] {ai_stand();}; +void() f_stand13 =[ $swim13, f_stand14 ] {ai_stand();}; +void() f_stand14 =[ $swim14, f_stand15 ] {ai_stand();}; +void() f_stand15 =[ $swim15, f_stand16 ] {ai_stand();}; +void() f_stand16 =[ $swim16, f_stand17 ] {ai_stand();}; +void() f_stand17 =[ $swim17, f_stand18 ] {ai_stand();}; +void() f_stand18 =[ $swim18, f_stand1 ] {ai_stand();}; + +void() f_walk1 =[ $swim1, f_walk2 ] {ai_walk(8);}; +void() f_walk2 =[ $swim2, f_walk3 ] {ai_walk(8);}; +void() f_walk3 =[ $swim3, f_walk4 ] {ai_walk(8);}; +void() f_walk4 =[ $swim4, f_walk5 ] {ai_walk(8);}; +void() f_walk5 =[ $swim5, f_walk6 ] {ai_walk(8);}; +void() f_walk6 =[ $swim6, f_walk7 ] {ai_walk(8);}; +void() f_walk7 =[ $swim7, f_walk8 ] {ai_walk(8);}; +void() f_walk8 =[ $swim8, f_walk9 ] {ai_walk(8);}; +void() f_walk9 =[ $swim9, f_walk10 ] {ai_walk(8);}; +void() f_walk10 =[ $swim10, f_walk11 ] {ai_walk(8);}; +void() f_walk11 =[ $swim11, f_walk12 ] {ai_walk(8);}; +void() f_walk12 =[ $swim12, f_walk13 ] {ai_walk(8);}; +void() f_walk13 =[ $swim13, f_walk14 ] {ai_walk(8);}; +void() f_walk14 =[ $swim14, f_walk15 ] {ai_walk(8);}; +void() f_walk15 =[ $swim15, f_walk16 ] {ai_walk(8);}; +void() f_walk16 =[ $swim16, f_walk17 ] {ai_walk(8);}; +void() f_walk17 =[ $swim17, f_walk18 ] {ai_walk(8);}; +void() f_walk18 =[ $swim18, f_walk1 ] {ai_walk(8);}; + +void() f_run1 =[ $swim1, f_run2 ] { + ai_run(12); + + if (random() < 0.5) + sound (self, CHAN_VOICE, "fish/idle.wav", 1, ATTN_NORM); +}; + +void() f_run2 =[ $swim3, f_run3 ] {ai_run(12);}; +void() f_run3 =[ $swim5, f_run4 ] {ai_run(12);}; +void() f_run4 =[ $swim7, f_run5 ] {ai_run(12);}; +void() f_run5 =[ $swim9, f_run6 ] {ai_run(12);}; +void() f_run6 =[ $swim11, f_run7 ] {ai_run(12);}; +void() f_run7 =[ $swim13, f_run8 ] {ai_run(12);}; +void() f_run8 =[ $swim15, f_run9 ] {ai_run(12);}; +void() f_run9 =[ $swim17, f_run1 ] {ai_run(12);}; + +void() fish_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + sound (self, CHAN_VOICE, "fish/bite.wav", 1, ATTN_NORM); + ldmg = (random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() f_attack1 =[ $attack1, f_attack2 ] {ai_charge(10);}; +void() f_attack2 =[ $attack2, f_attack3 ] {ai_charge(10);}; +void() f_attack3 =[ $attack3, f_attack4 ] {fish_melee();}; +void() f_attack4 =[ $attack4, f_attack5 ] {ai_charge(10);}; +void() f_attack5 =[ $attack5, f_attack6 ] {ai_charge(10);}; +void() f_attack6 =[ $attack6, f_attack7 ] {ai_charge(10);}; +void() f_attack7 =[ $attack7, f_attack8 ] {ai_charge(10);}; +void() f_attack8 =[ $attack8, f_attack9 ] {ai_charge(10);}; +void() f_attack9 =[ $attack9, f_attack10] {fish_melee();}; +void() f_attack10 =[ $attack10, f_attack11] {ai_charge(10);}; +void() f_attack11 =[ $attack11, f_attack12] {ai_charge(10);}; +void() f_attack12 =[ $attack12, f_attack13] {ai_charge(10);}; +void() f_attack13 =[ $attack13, f_attack14] {ai_charge(10);}; +void() f_attack14 =[ $attack14, f_attack15] {ai_charge(10);}; +void() f_attack15 =[ $attack15, f_attack16] {fish_melee();}; +void() f_attack16 =[ $attack16, f_attack17] {ai_charge(10);}; +void() f_attack17 =[ $attack17, f_attack18] {ai_charge(10);}; +void() f_attack18 =[ $attack18, f_run1 ] {ai_charge(10);}; + +void() f_death1 =[ $death1, f_death2 ] { + sound (self, CHAN_VOICE, "fish/death.wav", 1, ATTN_NORM); +}; + +void() f_death2 =[ $death2, f_death3 ] { + self.solid = SOLID_NOT; +}; + +void() f_death3 =[ $death3, f_death4 ] {}; +void() f_death4 =[ $death4, f_death5 ] {}; +void() f_death5 =[ $death5, f_death6 ] {}; +void() f_death6 =[ $death6, f_death7 ] {}; +void() f_death7 =[ $death7, f_death8 ] {}; +void() f_death8 =[ $death8, f_death9 ] {}; +void() f_death9 =[ $death9, f_death10 ] {}; +void() f_death10 =[ $death10, f_death11 ] {}; +void() f_death11 =[ $death11, f_death12 ] {}; +void() f_death12 =[ $death12, f_death13 ] {}; +void() f_death13 =[ $death13, f_death14 ] {}; +void() f_death14 =[ $death14, f_death15 ] {}; +void() f_death15 =[ $death15, f_death16 ] {}; +void() f_death16 =[ $death16, f_death17 ] {}; +void() f_death17 =[ $death17, f_death18 ] {}; +void() f_death18 =[ $death18, f_death19 ] {}; +void() f_death19 =[ $death19, f_death20 ] {}; +void() f_death20 =[ $death20, f_death21 ] {}; +void() f_death21 =[ $death21, f_death21 ] {}; + +void() f_pain1 =[ $pain1, f_pain2 ] {}; +void() f_pain2 =[ $pain2, f_pain3 ] {ai_pain(6);}; +void() f_pain3 =[ $pain3, f_pain4 ] {ai_pain(6);}; +void() f_pain4 =[ $pain4, f_pain5 ] {ai_pain(6);}; +void() f_pain5 =[ $pain5, f_pain6 ] {ai_pain(6);}; +void() f_pain6 =[ $pain6, f_pain7 ] {ai_pain(6);}; +void() f_pain7 =[ $pain7, f_pain8 ] {ai_pain(6);}; +void() f_pain8 =[ $pain8, f_pain9 ] {ai_pain(6);}; +void() f_pain9 =[ $pain9, f_run1 ] {ai_pain(6);}; + +void(entity attacker, float damage) fish_pain = +{ + // fish always do pain frames + f_pain1 (); +}; + + + +/* +QUAKED monster_fish (1 0 0) (-16 -16 -24) (16 16 24) Ambush +*/ +void() monster_fish = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/fish.mdl"); + + precache_sound2 ("fish/death.wav"); + precache_sound2 ("fish/bite.wav"); + precache_sound2 ("fish/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/fish.mdl"); + + self.netname = "$qc_rotfish"; + self.noise = "fish/idle.wav"; + self.killstring = "$qc_ks_rotfish"; + + setsize (self, '-16 -16 -24', '16 16 24'); + self.health = 25; + self.max_health = 25; + + self.th_stand = f_stand1; + self.th_walk = f_walk1; + self.th_run = f_run1; + self.th_die = f_death1; + self.th_pain = fish_pain; + self.th_melee = f_attack1; + + swimmonster_start (); +}; + diff --git a/quakec/monsters/rottweiler.qc b/quakec/monsters/rottweiler.qc new file mode 100644 index 0000000..29d220d --- /dev/null +++ b/quakec/monsters/rottweiler.qc @@ -0,0 +1,388 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DOG + +============================================================================== +*/ +$cd id1/models/dog +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + + +void() dog_leap1; +void() dog_run1; + +/* +================ +dog_bite + +================ +*/ +void() dog_bite = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + + ai_charge(10); + + if (!CanDamage (self.enemy, self)) + return; + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 8; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() Dog_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage) + { + if ( vlen(self.velocity) > 300 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up + self.touch = SUB_Null; + self.think = dog_leap1; + self.nextthink = time + 0.1; + } + + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = dog_run1; + self.nextthink = time + 0.1; +}; + + +void() dog_stand1 =[ $stand1, dog_stand2 ] {ai_stand();}; +void() dog_stand2 =[ $stand2, dog_stand3 ] {ai_stand();}; +void() dog_stand3 =[ $stand3, dog_stand4 ] {ai_stand();}; +void() dog_stand4 =[ $stand4, dog_stand5 ] {ai_stand();}; +void() dog_stand5 =[ $stand5, dog_stand6 ] {ai_stand();}; +void() dog_stand6 =[ $stand6, dog_stand7 ] {ai_stand();}; +void() dog_stand7 =[ $stand7, dog_stand8 ] {ai_stand();}; +void() dog_stand8 =[ $stand8, dog_stand9 ] {ai_stand();}; +void() dog_stand9 =[ $stand9, dog_stand1 ] {ai_stand();}; + +void() dog_walk1 =[ $walk1 , dog_walk2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); + + ai_walk(8); +}; + +void() dog_walk2 =[ $walk2 , dog_walk3 ] {ai_walk(8);}; +void() dog_walk3 =[ $walk3 , dog_walk4 ] {ai_walk(8);}; +void() dog_walk4 =[ $walk4 , dog_walk5 ] {ai_walk(8);}; +void() dog_walk5 =[ $walk5 , dog_walk6 ] {ai_walk(8);}; +void() dog_walk6 =[ $walk6 , dog_walk7 ] {ai_walk(8);}; +void() dog_walk7 =[ $walk7 , dog_walk8 ] {ai_walk(8);}; +void() dog_walk8 =[ $walk8 , dog_walk1 ] {ai_walk(8);}; + +void() dog_run1 =[ $run1 , dog_run2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); + + ai_run(16); +}; + +void() dog_run2 =[ $run2 , dog_run3 ] {ai_run(32);}; +void() dog_run3 =[ $run3 , dog_run4 ] {ai_run(32);}; +void() dog_run4 =[ $run4 , dog_run5 ] {ai_run(20);}; +void() dog_run5 =[ $run5 , dog_run6 ] {ai_run(64);}; +void() dog_run6 =[ $run6 , dog_run7 ] {ai_run(32);}; +void() dog_run7 =[ $run7 , dog_run8 ] {ai_run(16);}; +void() dog_run8 =[ $run8 , dog_run9 ] {ai_run(32);}; +void() dog_run9 =[ $run9 , dog_run10 ] {ai_run(32);}; +void() dog_run10 =[ $run10 , dog_run11 ] {ai_run(20);}; +void() dog_run11 =[ $run11 , dog_run12 ] {ai_run(64);}; +void() dog_run12 =[ $run12 , dog_run1 ] {ai_run(32);}; + +void() dog_atta1 =[ $attack1, dog_atta2 ] {ai_charge(10);}; +void() dog_atta2 =[ $attack2, dog_atta3 ] {ai_charge(10);}; +void() dog_atta3 =[ $attack3, dog_atta4 ] {ai_charge(10);}; +void() dog_atta4 =[ $attack4, dog_atta5 ] { + sound (self, CHAN_VOICE, "dog/dattack1.wav", 1, ATTN_NORM); + dog_bite(); +}; + +void() dog_atta5 =[ $attack5, dog_atta6 ] {ai_charge(10);}; +void() dog_atta6 =[ $attack6, dog_atta7 ] {ai_charge(10);}; +void() dog_atta7 =[ $attack7, dog_atta8 ] {ai_charge(10);}; +void() dog_atta8 =[ $attack8, dog_run1 ] {ai_charge(10);}; + +void() dog_leap1 =[ $leap1, dog_leap2 ] {ai_face();}; +void() dog_leap2 =[ $leap2, dog_leap3 ] +{ + ai_face(); + + self.touch = Dog_JumpTouch; + makevectorsfixed(self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 300 + '0 0 200'; + + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; + +void() dog_leap3 =[ $leap3, dog_leap4 ] {}; +void() dog_leap4 =[ $leap4, dog_leap5 ] {}; +void() dog_leap5 =[ $leap5, dog_leap6 ] {}; +void() dog_leap6 =[ $leap6, dog_leap7 ] {}; +void() dog_leap7 =[ $leap7, dog_leap8 ] {}; +void() dog_leap8 =[ $leap8, dog_leap9 ] {}; +void() dog_leap9 =[ $leap9, dog_leap9 ] {}; + +void() dog_pain1 =[ $pain1 , dog_pain2 ] {}; +void() dog_pain2 =[ $pain2 , dog_pain3 ] {}; +void() dog_pain3 =[ $pain3 , dog_pain4 ] {}; +void() dog_pain4 =[ $pain4 , dog_pain5 ] {}; +void() dog_pain5 =[ $pain5 , dog_pain6 ] {}; +void() dog_pain6 =[ $pain6 , dog_run1 ] {}; + +void() dog_painb1 =[ $painb1 , dog_painb2 ] {}; +void() dog_painb2 =[ $painb2 , dog_painb3 ] {}; +void() dog_painb3 =[ $painb3 , dog_painb4 ] {ai_pain(4);}; +void() dog_painb4 =[ $painb4 , dog_painb5 ] {ai_pain(12);}; +void() dog_painb5 =[ $painb5 , dog_painb6 ] {ai_pain(12);}; +void() dog_painb6 =[ $painb6 , dog_painb7 ] {ai_pain(2);}; +void() dog_painb7 =[ $painb7 , dog_painb8 ] {}; +void() dog_painb8 =[ $painb8 , dog_painb9 ] {ai_pain(4);}; +void() dog_painb9 =[ $painb9 , dog_painb10 ] {}; +void() dog_painb10 =[ $painb10 , dog_painb11 ] {ai_pain(10);}; +void() dog_painb11 =[ $painb11 , dog_painb12 ] {}; +void() dog_painb12 =[ $painb12 , dog_painb13 ] {}; +void() dog_painb13 =[ $painb13 , dog_painb14 ] {}; +void() dog_painb14 =[ $painb14 , dog_painb15 ] {}; +void() dog_painb15 =[ $painb15 , dog_painb16 ] {}; +void() dog_painb16 =[ $painb16 , dog_run1 ] {}; + +void(entity attacker, float damage) dog_pain = +{ + sound (self, CHAN_VOICE, "dog/dpain1.wav", 1, ATTN_NORM); + + if (random() > 0.5) + dog_pain1 (); + else + dog_painb1 (); +}; + +void() dog_die1 =[ $death1, dog_die2 ] {}; +void() dog_die2 =[ $death2, dog_die3 ] {}; +void() dog_die3 =[ $death3, dog_die4 ] {}; +void() dog_die4 =[ $death4, dog_die5 ] {}; +void() dog_die5 =[ $death5, dog_die6 ] {}; +void() dog_die6 =[ $death6, dog_die7 ] {}; +void() dog_die7 =[ $death7, dog_die8 ] {}; +void() dog_die8 =[ $death8, dog_die9 ] {}; +void() dog_die9 =[ $death9, dog_die9 ] {}; + +void() dog_dieb1 =[ $deathb1, dog_dieb2 ] {}; +void() dog_dieb2 =[ $deathb2, dog_dieb3 ] {}; +void() dog_dieb3 =[ $deathb3, dog_dieb4 ] {}; +void() dog_dieb4 =[ $deathb4, dog_dieb5 ] {}; +void() dog_dieb5 =[ $deathb5, dog_dieb6 ] {}; +void() dog_dieb6 =[ $deathb6, dog_dieb7 ] {}; +void() dog_dieb7 =[ $deathb7, dog_dieb8 ] {}; +void() dog_dieb8 =[ $deathb8, dog_dieb9 ] {}; +void() dog_dieb9 =[ $deathb9, dog_dieb9 ] {}; + + +void() dog_die = +{ + // check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowHead ("progs/h_dog.mdl", self.health); + return; + } + + // regular death + sound (self, CHAN_VOICE, "dog/ddeath.wav", 1, ATTN_NORM); + self.solid = SOLID_NOT; + + if (random() > 0.5) + dog_die1 (); + else + dog_dieb1 (); +}; + +//============================================================================ + +/* +============== +CheckDogMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDogMelee = +{ + if (enemy_range == RANGE_MELEE) + { // FIXME: check canreach + self.attack_state = AS_MELEE; + return TRUE; + } + + return FALSE; +}; + +/* +============== +CheckDogJump + +============== +*/ +float() CheckDogJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 80) + return FALSE; + + if (d > 150) + return FALSE; + + return TRUE; +}; + +float() DogCheckAttack = +{ + // if close enough for slashing, go for it + if (CheckDogMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDogJump ()) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +/*QUAKED monster_dog (1 0 0) (-32 -32 -24) (32 32 40) Ambush*/ +void() monster_dog = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/h_dog.mdl"); + precache_model ("progs/dog.mdl"); + + precache_sound ("dog/dattack1.wav"); + precache_sound ("dog/ddeath.wav"); + precache_sound ("dog/dpain1.wav"); + precache_sound ("dog/dsight.wav"); + precache_sound ("dog/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/dog.mdl"); + + self.noise = "dog/dsight.wav"; + self.netname = "$qc_rottweiler"; + self.killstring = "$qc_ks_rottweiler"; + + setsize (self, '-32 -32 -24', '32 32 40'); + self.health = 25; + self.max_health = 25; + + self.th_stand = dog_stand1; + self.th_walk = dog_walk1; + self.th_run = dog_run1; + self.th_pain = dog_pain; + self.th_die = dog_die; + self.th_melee = dog_atta1; + self.th_missile = dog_leap1; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + walkmonster_start(); +}; diff --git a/quakec/monsters/scrag.qc b/quakec/monsters/scrag.qc new file mode 100644 index 0000000..bb6fc76 --- /dev/null +++ b/quakec/monsters/scrag.qc @@ -0,0 +1,456 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +WIZARD + +============================================================================== +*/ + +$cd id1/models/a_wizard +$origin 0 0 24 +$base wizbase +$skin wizbase + +$frame hover1 hover2 hover3 hover4 hover5 hover6 hover7 hover8 +$frame hover9 hover10 hover11 hover12 hover13 hover14 hover15 + +$frame fly1 fly2 fly3 fly4 fly5 fly6 fly7 fly8 fly9 fly10 +$frame fly11 fly12 fly13 fly14 + +$frame magatt1 magatt2 magatt3 magatt4 magatt5 magatt6 magatt7 +$frame magatt8 magatt9 magatt10 magatt11 magatt12 magatt13 + +$frame pain1 pain2 pain3 pain4 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 + +/* +============================================================================== + +WIZARD + +If the player moves behind cover before the missile is launched, launch it +at the last visible spot with no velocity leading, in hopes that the player +will duck back out and catch it. +============================================================================== +*/ + +/* +============= +LaunchMissile + +Sets the given entities velocity and angles so that it will hit self.enemy +if self.enemy maintains it's current velocity +0.1 is moderately accurate, 0.0 is totally accurate +============= +*/ +void(entity missile, float mspeed, float accuracy) LaunchMissile = +{ + local vector vec, move; + local float fly; + + makevectorsfixed(self.angles); + + // set missile speed + vec = self.enemy.origin + self.enemy.mins + self.enemy.size * 0.7 - missile.origin; + + // calc aproximate time for missile to reach vec + fly = vlen (vec) / mspeed; + + // get the entities xy velocity + move = self.enemy.velocity; + move_z = 0; + + // project the target forward in time + vec = vec + move * fly; + + vec = normalize(vec); + vec = vec + accuracy*v_up*(random()- 0.5) + accuracy*v_right*(random()- 0.5); + + missile.velocity = vec * mspeed; + + missile.angles = '0 0 0'; + missile.angles_y = vectoyaw(missile.velocity); + + // set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; +}; + + +void() wiz_run1; +void() wiz_side1; + +/* +================= +WizardCheckAttack +================= +*/ +float() WizardCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_visible) + return FALSE; + + if (enemy_range == RANGE_FAR) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + targ = self.enemy; + + // see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != targ) + { // don't have a clear shot, so move to a side + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.6; + else if (enemy_range == RANGE_MID) + chance = 0.2; + else + chance = 0; + + if (random () < chance) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + if (enemy_range == RANGE_MID) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + } + else + { + if (self.attack_state != AS_SLIDING) + { + self.attack_state = AS_SLIDING; + wiz_side1 (); + } + } + + return FALSE; +}; + +/* +================= +WizardAttackFinished +================= +*/ +void() WizardAttackFinished = +{ + if (enemy_range >= RANGE_MID || !enemy_visible) + { + self.attack_state = AS_STRAIGHT; + self.think = wiz_run1; + } + else + { + self.attack_state = AS_SLIDING; + self.think = wiz_side1; + } +}; + +/* +============================================================================== + +FAST ATTACKS + +============================================================================== +*/ + +void() Wiz_FastFire = +{ + local vector vec; + local vector dst; + + if (self.owner.health > 0) + { + self.owner.effects = self.owner.effects | EF_MUZZLEFLASH; + + makevectorsfixed(self.enemy.angles); + dst = self.enemy.origin - 13*self.movedir; + + vec = normalize(dst - self.origin); + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + launch_spike (self.origin, vec); + newmis.velocity = vec*600; + newmis.owner = self.owner; + newmis.classname = "wizard_spike"; + setmodel (newmis, "progs/w_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + } + + remove (self); +}; + + +void() Wiz_StartFast = +{ + local entity missile; + + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + self.v_angle = self.angles; + makevectorsfixed(self.angles); + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 0.6; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right*14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.8; + missile.think = Wiz_FastFire; + missile.movedir = v_right; + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 1; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right* -14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.3; + missile.think = Wiz_FastFire; + missile.movedir = VEC_ORIGIN - v_right; +}; + + + +void() Wiz_idlesound = +{ + local float wr; + wr = random() * 5; + + if (self.waitmin < time) + { + self.waitmin = time + 2; + if (wr > 4.5) + sound (self, CHAN_VOICE, "wizard/widle1.wav", 1, ATTN_IDLE); + + if (wr < 1.5) + sound (self, CHAN_VOICE, "wizard/widle2.wav", 1, ATTN_IDLE); + } + return; +}; + +void() wiz_stand1 =[ $hover1, wiz_stand2 ] {ai_stand();}; +void() wiz_stand2 =[ $hover2, wiz_stand3 ] {ai_stand();}; +void() wiz_stand3 =[ $hover3, wiz_stand4 ] {ai_stand();}; +void() wiz_stand4 =[ $hover4, wiz_stand5 ] {ai_stand();}; +void() wiz_stand5 =[ $hover5, wiz_stand6 ] {ai_stand();}; +void() wiz_stand6 =[ $hover6, wiz_stand7 ] {ai_stand();}; +void() wiz_stand7 =[ $hover7, wiz_stand8 ] {ai_stand();}; +void() wiz_stand8 =[ $hover8, wiz_stand1 ] {ai_stand();}; + +void() wiz_walk1 =[ $hover1, wiz_walk2 ] { + ai_walk(8); + Wiz_idlesound(); +}; + +void() wiz_walk2 =[ $hover2, wiz_walk3 ] {ai_walk(8);}; +void() wiz_walk3 =[ $hover3, wiz_walk4 ] {ai_walk(8);}; +void() wiz_walk4 =[ $hover4, wiz_walk5 ] {ai_walk(8);}; +void() wiz_walk5 =[ $hover5, wiz_walk6 ] {ai_walk(8);}; +void() wiz_walk6 =[ $hover6, wiz_walk7 ] {ai_walk(8);}; +void() wiz_walk7 =[ $hover7, wiz_walk8 ] {ai_walk(8);}; +void() wiz_walk8 =[ $hover8, wiz_walk1 ] {ai_walk(8);}; + +void() wiz_side1 =[ $hover1, wiz_side2 ] { + ai_run(8); + Wiz_idlesound(); +}; + +void() wiz_side2 =[ $hover2, wiz_side3 ] {ai_run(8);}; +void() wiz_side3 =[ $hover3, wiz_side4 ] {ai_run(8);}; +void() wiz_side4 =[ $hover4, wiz_side5 ] {ai_run(8);}; +void() wiz_side5 =[ $hover5, wiz_side6 ] {ai_run(8);}; +void() wiz_side6 =[ $hover6, wiz_side7 ] {ai_run(8);}; +void() wiz_side7 =[ $hover7, wiz_side8 ] {ai_run(8);}; +void() wiz_side8 =[ $hover8, wiz_side1 ] {ai_run(8);}; + +void() wiz_run1 =[ $fly1, wiz_run2 ] { + ai_run(16); + Wiz_idlesound(); +}; + +void() wiz_run2 =[ $fly2, wiz_run3 ] {ai_run(16);}; +void() wiz_run3 =[ $fly3, wiz_run4 ] {ai_run(16);}; +void() wiz_run4 =[ $fly4, wiz_run5 ] {ai_run(16);}; +void() wiz_run5 =[ $fly5, wiz_run6 ] {ai_run(16);}; +void() wiz_run6 =[ $fly6, wiz_run7 ] {ai_run(16);}; +void() wiz_run7 =[ $fly7, wiz_run8 ] {ai_run(16);}; +void() wiz_run8 =[ $fly8, wiz_run9 ] {ai_run(16);}; +void() wiz_run9 =[ $fly9, wiz_run10 ] {ai_run(16);}; +void() wiz_run10 =[ $fly10, wiz_run11 ] {ai_run(16);}; +void() wiz_run11 =[ $fly11, wiz_run12 ] {ai_run(16);}; +void() wiz_run12 =[ $fly12, wiz_run13 ] {ai_run(16);}; +void() wiz_run13 =[ $fly13, wiz_run14 ] {ai_run(16);}; +void() wiz_run14 =[ $fly14, wiz_run1 ] {ai_run(16);}; + +void() wiz_fast1 =[ $magatt1, wiz_fast2 ] { + ai_face(); + Wiz_StartFast(); +}; + +void() wiz_fast2 =[ $magatt2, wiz_fast3 ] {ai_face();}; +void() wiz_fast3 =[ $magatt3, wiz_fast4 ] {ai_face();}; +void() wiz_fast4 =[ $magatt4, wiz_fast5 ] {ai_face();}; +void() wiz_fast5 =[ $magatt5, wiz_fast6 ] {ai_face();}; +void() wiz_fast6 =[ $magatt6, wiz_fast7 ] {ai_face();}; +void() wiz_fast7 =[ $magatt5, wiz_fast8 ] {ai_face();}; +void() wiz_fast8 =[ $magatt4, wiz_fast9 ] {ai_face();}; +void() wiz_fast9 =[ $magatt3, wiz_fast10 ] {ai_face();}; +void() wiz_fast10 =[ $magatt2, wiz_run1 ] { + ai_face(); + SUB_AttackFinished(2); + WizardAttackFinished (); +}; + +void() wiz_pain1 =[ $pain1, wiz_pain2 ] {}; +void() wiz_pain2 =[ $pain2, wiz_pain3 ] {}; +void() wiz_pain3 =[ $pain3, wiz_pain4 ] {}; +void() wiz_pain4 =[ $pain4, wiz_run1 ] {}; + +void() wiz_death1 =[ $death1, wiz_death2 ] { + self.velocity_x = -200 + 400 * random(); + self.velocity_y = -200 + 400 * random(); + self.velocity_z = 100 + 100 * random(); + self.flags = self.flags - (self.flags & FL_ONGROUND); + sound (self, CHAN_VOICE, "wizard/wdeath.wav", 1, ATTN_NORM); +}; + +void() wiz_death2 =[ $death2, wiz_death3 ] {}; +void() wiz_death3 =[ $death3, wiz_death4 ] {self.solid = SOLID_NOT;}; +void() wiz_death4 =[ $death4, wiz_death5 ] {}; +void() wiz_death5 =[ $death5, wiz_death6 ] {}; +void() wiz_death6 =[ $death6, wiz_death7 ] {}; +void() wiz_death7 =[ $death7, wiz_death8 ] {}; +void() wiz_death8 =[ $death8, wiz_death8 ] {}; + +void() wiz_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_wizard.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + return; + } + + wiz_death1 (); +}; + + +void(entity attacker, float damage) Wiz_Pain = +{ + sound (self, CHAN_VOICE, "wizard/wpain.wav", 1, ATTN_NORM); + if (random()*70 > damage) + return; // didn't flinch + + wiz_pain1 (); +}; + + +void() Wiz_Missile = +{ + wiz_fast1(); +}; + +/* +QUAKED monster_wizard (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ + +void() monster_wizard = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/wizard.mdl"); + precache_model ("progs/h_wizard.mdl"); + precache_model ("progs/w_spike.mdl"); + + precache_sound ("wizard/hit.wav"); // used by c code + precache_sound ("wizard/wattack.wav"); + precache_sound ("wizard/wdeath.wav"); + precache_sound ("wizard/widle1.wav"); + precache_sound ("wizard/widle2.wav"); + precache_sound ("wizard/wpain.wav"); + precache_sound ("wizard/wsight.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/wizard.mdl"); + + self.noise = "wizard/wsight.wav"; + self.netname = "$qc_scrag"; + self.killstring = "$qc_ks_scrag"; + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + self.max_health = 80; + + self.th_stand = wiz_stand1; + self.th_walk = wiz_walk1; + self.th_run = wiz_run1; + self.th_missile = Wiz_Missile; + self.th_pain = Wiz_Pain; + self.th_die = wiz_die; + self.combat_style = CS_RANGED; + + flymonster_start (); +}; diff --git a/quakec/monsters/shambler.qc b/quakec/monsters/shambler.qc new file mode 100644 index 0000000..1868e23 --- /dev/null +++ b/quakec/monsters/shambler.qc @@ -0,0 +1,435 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAMBLER + +============================================================================== +*/ + +$cd id1/models/shams +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 stand14 stand15 stand16 stand17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 + +$frame run1 run2 run3 run4 run5 run6 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 + +$frame swingr1 swingr2 swingr3 swingr4 swingr5 +$frame swingr6 swingr7 swingr8 swingr9 + +$frame swingl1 swingl2 swingl3 swingl4 swingl5 +$frame swingl6 swingl7 swingl8 swingl9 + +$frame magic1 magic2 magic3 magic4 magic5 +$frame magic6 magic7 magic8 magic9 magic10 magic11 magic12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 death11 + +void() sham_stand1 =[ $stand1, sham_stand2 ] {ai_stand();}; +void() sham_stand2 =[ $stand2, sham_stand3 ] {ai_stand();}; +void() sham_stand3 =[ $stand3, sham_stand4 ] {ai_stand();}; +void() sham_stand4 =[ $stand4, sham_stand5 ] {ai_stand();}; +void() sham_stand5 =[ $stand5, sham_stand6 ] {ai_stand();}; +void() sham_stand6 =[ $stand6, sham_stand7 ] {ai_stand();}; +void() sham_stand7 =[ $stand7, sham_stand8 ] {ai_stand();}; +void() sham_stand8 =[ $stand8, sham_stand9 ] {ai_stand();}; +void() sham_stand9 =[ $stand9, sham_stand10] {ai_stand();}; +void() sham_stand10 =[ $stand10, sham_stand11] {ai_stand();}; +void() sham_stand11 =[ $stand11, sham_stand12] {ai_stand();}; +void() sham_stand12 =[ $stand12, sham_stand13] {ai_stand();}; +void() sham_stand13 =[ $stand13, sham_stand14] {ai_stand();}; +void() sham_stand14 =[ $stand14, sham_stand15] {ai_stand();}; +void() sham_stand15 =[ $stand15, sham_stand16] {ai_stand();}; +void() sham_stand16 =[ $stand16, sham_stand17] {ai_stand();}; +void() sham_stand17 =[ $stand17, sham_stand1 ] {ai_stand();}; + +void() sham_walk1 =[ $walk1, sham_walk2 ] {ai_walk(10);}; +void() sham_walk2 =[ $walk2, sham_walk3 ] {ai_walk(9);}; +void() sham_walk3 =[ $walk3, sham_walk4 ] {ai_walk(9);}; +void() sham_walk4 =[ $walk4, sham_walk5 ] {ai_walk(5);}; +void() sham_walk5 =[ $walk5, sham_walk6 ] {ai_walk(6);}; +void() sham_walk6 =[ $walk6, sham_walk7 ] {ai_walk(12);}; +void() sham_walk7 =[ $walk7, sham_walk8 ] {ai_walk(8);}; +void() sham_walk8 =[ $walk8, sham_walk9 ] {ai_walk(3);}; +void() sham_walk9 =[ $walk9, sham_walk10] {ai_walk(13);}; +void() sham_walk10 =[ $walk10, sham_walk11] {ai_walk(9);}; +void() sham_walk11 =[ $walk11, sham_walk12] {ai_walk(7);}; +void() sham_walk12 =[ $walk12, sham_walk1 ] { + ai_walk(7); + + if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE); +}; + +void() sham_run1 =[ $run1, sham_run2 ] {ai_run(20);}; +void() sham_run2 =[ $run2, sham_run3 ] {ai_run(24);}; +void() sham_run3 =[ $run3, sham_run4 ] {ai_run(20);}; +void() sham_run4 =[ $run4, sham_run5 ] {ai_run(20);}; +void() sham_run5 =[ $run5, sham_run6 ] {ai_run(24);}; +void() sham_run6 =[ $run6, sham_run1 ] { + ai_run(20); + + if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE); +}; + +void() sham_smash1 =[ $smash1, sham_smash2 ] { + sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); + ai_charge(2); +}; + +void() sham_smash2 =[ $smash2, sham_smash3 ] {ai_charge(6);}; +void() sham_smash3 =[ $smash3, sham_smash4 ] {ai_charge(6);}; +void() sham_smash4 =[ $smash4, sham_smash5 ] {ai_charge(5);}; +void() sham_smash5 =[ $smash5, sham_smash6 ] {ai_charge(4);}; +void() sham_smash6 =[ $smash6, sham_smash7 ] {ai_charge(1);}; +void() sham_smash7 =[ $smash7, sham_smash8 ] {ai_charge(0);}; +void() sham_smash8 =[ $smash8, sham_smash9 ] {ai_charge(0);}; +void() sham_smash9 =[ $smash9, sham_smash10 ] {ai_charge(0);}; +void() sham_smash10 =[ $smash10, sham_smash11 ] { + local vector delta; + local float ldmg; + + if (!self.enemy) + return; + + ai_charge(0); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + if (!CanDamage (self.enemy, self)) + return; + + ldmg = (random() + random() + random()) * 40; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); +}; + +void() sham_smash11 =[ $smash11, sham_smash12 ] {ai_charge(5);}; +void() sham_smash12 =[ $smash12, sham_run1 ] {ai_charge(4);}; +void() sham_swingr1; + +void(float side) ShamClaw = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; + + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 20; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + if (side) + { + makevectorsfixed(self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + +void() sham_swingl1 =[ $swingl1, sham_swingl2 ] { + sound(self, CHAN_VOICE, "shambler/melee2.wav", 1, ATTN_NORM); + ai_charge(5); +}; + +void() sham_swingl2 =[ $swingl2, sham_swingl3 ] {ai_charge(3);}; +void() sham_swingl3 =[ $swingl3, sham_swingl4 ] {ai_charge(7);}; +void() sham_swingl4 =[ $swingl4, sham_swingl5 ] {ai_charge(3);}; +void() sham_swingl5 =[ $swingl5, sham_swingl6 ] {ai_charge(7);}; +void() sham_swingl6 =[ $swingl6, sham_swingl7 ] {ai_charge(9);}; +void() sham_swingl7 =[ $swingl7, sham_swingl8 ] {ai_charge(5); ShamClaw(250);}; +void() sham_swingl8 =[ $swingl8, sham_swingl9 ] {ai_charge(4);}; +void() sham_swingl9 =[ $swingl9, sham_run1 ] { + ai_charge(8); + + if (random()<0.5) + self.think = sham_swingr1; +}; + +void() sham_swingr1 =[ $swingr1, sham_swingr2 ] { + sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); + ai_charge(1); +}; + +void() sham_swingr2 =[ $swingr2, sham_swingr3 ] {ai_charge(8);}; +void() sham_swingr3 =[ $swingr3, sham_swingr4 ] {ai_charge(14);}; +void() sham_swingr4 =[ $swingr4, sham_swingr5 ] {ai_charge(7);}; +void() sham_swingr5 =[ $swingr5, sham_swingr6 ] {ai_charge(3);}; +void() sham_swingr6 =[ $swingr6, sham_swingr7 ] {ai_charge(6);}; +void() sham_swingr7 =[ $swingr7, sham_swingr8 ] {ai_charge(6); ShamClaw(-250);}; +void() sham_swingr8 =[ $swingr8, sham_swingr9 ] {ai_charge(3);}; +void() sham_swingr9 =[ $swingr9, sham_run1 ] { + ai_charge(1); + ai_charge(10); + + if (random()<0.5) + self.think = sham_swingl1; +}; + +void() sham_melee = +{ + local float chance; + chance = random(); + + if (chance > 0.6 || self.health == 600) + sham_smash1 (); + else if (chance > 0.3) + sham_swingr1 (); + else + sham_swingl1 (); +}; + + +//============================================================================ + +void() CastLightning = +{ + local vector org, dir; + + self.effects = self.effects | EF_MUZZLEFLASH; + + ai_face (); + + org = self.origin + '0 0 40'; + + dir = self.enemy.origin + '0 0 16' - org; + dir = normalize (dir); + + traceline (org, self.origin + dir*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING1); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + // overload the frags field which is unused on enemies + self.frags += 1; + + LightningDamage (org, trace_endpos, self, 10); +}; + +void() sham_magic1 =[ $magic1, sham_magic2 ] { + ai_face(); + sound (self, CHAN_WEAPON, "shambler/sattck1.wav", 1, ATTN_NORM); +}; + +void() sham_magic2 =[ $magic2, sham_magic3 ] {ai_face();}; +void() sham_magic3 =[ $magic3, sham_magic4 ] { + ai_face(); + self.nextthink = self.nextthink + 0.2; + local entity o; + + self.effects = self.effects | EF_MUZZLEFLASH; + ai_face(); + self.owner = spawn(); + o = self.owner; + setmodel (o, "progs/s_light.mdl"); + setorigin (o, self.origin); + o.angles = self.angles; + o.nextthink = time + 0.7; + o.think = SUB_Remove; +}; + +void() sham_magic4 =[ $magic4, sham_magic5 ] { + self.effects = self.effects | EF_MUZZLEFLASH; + self.owner.frame = 1; +}; + +void() sham_magic5 =[ $magic5, sham_magic6 ] { + self.effects = self.effects | EF_MUZZLEFLASH; + self.owner.frame = 2; +}; + +void() sham_magic6 =[ $magic6, sham_magic9 ] { + remove (self.owner); + CastLightning(); + sound (self, CHAN_WEAPON, "shambler/sboom.wav", 1, ATTN_NORM); +}; + +void() sham_magic9 =[ $magic9, sham_magic10 ]{CastLightning();}; +void() sham_magic10 =[ $magic10, sham_magic11 ]{CastLightning();}; +void() sham_magic11 =[ $magic11, sham_magic12 ] {}; + +void() sham_magic12 =[ $magic12, sham_run1 ] {}; + + + +void() sham_pain1 =[ $pain1, sham_pain2 ] {}; +void() sham_pain2 =[ $pain2, sham_pain3 ] {}; +void() sham_pain3 =[ $pain3, sham_pain4 ] {}; +void() sham_pain4 =[ $pain4, sham_pain5 ] {}; +void() sham_pain5 =[ $pain5, sham_pain6 ] {}; +void() sham_pain6 =[ $pain6, sham_run1 ] {}; + +void(entity attacker, float damage) sham_pain = +{ + sound (self, CHAN_VOICE, "shambler/shurt2.wav", 1, ATTN_NORM); + + if (damage >= self.health && attacker.classname == "player" && attacker.weapon == IT_AXE) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_CLOSE_SHAVE"); + } + + // overload frags field for shambler dance achievement + if (damage >= self.health && attacker.classname == "player" && self.frags == 0) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SHAMBLER_DANCE"); + } + + if (self.health <= 0) + { + return; // already dying, don't go into pain frame + } + + if (random()*400 > damage) + return; // didn't flinch + + if (self.pain_finished > time) + return; + + self.pain_finished = time + 2; + sham_pain1 (); +}; + + +//============================================================================ + +void() sham_death1 =[ $death1, sham_death2 ] {}; +void() sham_death2 =[ $death2, sham_death3 ] {}; +void() sham_death3 =[ $death3, sham_death4 ] {self.solid = SOLID_NOT;}; +void() sham_death4 =[ $death4, sham_death5 ] {}; +void() sham_death5 =[ $death5, sham_death6 ] {}; +void() sham_death6 =[ $death6, sham_death7 ] {}; +void() sham_death7 =[ $death7, sham_death8 ] {}; +void() sham_death8 =[ $death8, sham_death9 ] {}; +void() sham_death9 =[ $death9, sham_death10 ] {}; +void() sham_death10 =[ $death10, sham_death11 ] {}; +void() sham_death11 =[ $death11, sham_death11 ] {}; + +void() sham_die = +{ + // check for gib + if (self.health < -60) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shams.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + // regular death + sound (self, CHAN_VOICE, "shambler/sdeath.wav", 1, ATTN_NORM); + sham_death1 (); +}; + +//============================================================================ + + +/*QUAKED monster_shambler (1 0 0) (-32 -32 -24) (32 32 64) Ambush +*/ +void() monster_shambler = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/shambler.mdl"); + precache_model ("progs/s_light.mdl"); + precache_model ("progs/h_shams.mdl"); + precache_model ("progs/bolt.mdl"); + + precache_sound ("shambler/sattck1.wav"); + precache_sound ("shambler/sboom.wav"); + precache_sound ("shambler/sdeath.wav"); + precache_sound ("shambler/shurt2.wav"); + precache_sound ("shambler/sidle.wav"); + precache_sound ("shambler/ssight.wav"); + precache_sound ("shambler/melee1.wav"); + precache_sound ("shambler/melee2.wav"); + precache_sound ("shambler/smack.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + setmodel (self, "progs/shambler.mdl"); + + self.noise = "shambler/ssight.wav"; + self.netname = "$qc_shambler"; + self.killstring = "$qc_ks_shambler"; + + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 600; + self.max_health = 600; + + self.th_stand = sham_stand1; + self.th_walk = sham_walk1; + self.th_run = sham_run1; + self.th_die = sham_die; + self.th_melee = sham_melee; + self.th_missile = sham_magic1; + self.th_pain = sham_pain; + self.allowPathFind = TRUE; + self.combat_style = CS_MIXED; + + // overload frags for lightning attack counts + self.frags = 0; + + walkmonster_start(); +}; diff --git a/quakec/monsters/shub.qc b/quakec/monsters/shub.qc new file mode 100644 index 0000000..914b28d --- /dev/null +++ b/quakec/monsters/shub.qc @@ -0,0 +1,349 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OLD ONE + +============================================================================== +*/ +$cd id1/models/old_one +$origin 0 0 24 +$base base +$skin skin +$scale 1 + +void() finale_1; +void() finale_2; +void() finale_3; +void() finale_4; +void() finale_5; +void() finale_6; + + +entity shub; + +$frame old1 old2 old3 old4 old5 old6 old7 old8 old9 +$frame old10 old11 old12 old13 old14 old15 old16 old17 old18 old19 +$frame old20 old21 old22 old23 old24 old25 old26 old27 old28 old29 +$frame old30 old31 old32 old33 old34 old35 old36 old37 old38 old39 +$frame old40 old41 old42 old43 old44 old45 old46 + +$frame shake1 shake2 shake3 shake4 shake5 shake6 shake7 shake8 +$frame shake9 shake10 shake11 shake12 shake13 shake14 shake15 +$frame shake16 shake17 shake18 shake19 shake20 shake21 + +//void() old_stand =[ $old1, old_stand ] {}; + +void() old_idle1 =[ $old1, old_idle2 ] {}; +void() old_idle2 =[ $old2, old_idle3 ] {}; +void() old_idle3 =[ $old3, old_idle4 ] {}; +void() old_idle4 =[ $old4, old_idle5 ] {}; +void() old_idle5 =[ $old5, old_idle6 ] {}; +void() old_idle6 =[ $old6, old_idle7 ] {}; +void() old_idle7 =[ $old7, old_idle8 ] {}; +void() old_idle8 =[ $old8, old_idle9 ] {}; +void() old_idle9 =[ $old9, old_idle10 ] {}; +void() old_idle10 =[ $old10, old_idle11 ] {}; +void() old_idle11 =[ $old11, old_idle12 ] {}; +void() old_idle12 =[ $old12, old_idle13 ] {}; +void() old_idle13 =[ $old13, old_idle14 ] {}; +void() old_idle14 =[ $old14, old_idle15 ] {}; +void() old_idle15 =[ $old15, old_idle16 ] {}; +void() old_idle16 =[ $old16, old_idle17 ] {}; +void() old_idle17 =[ $old17, old_idle18 ] {}; +void() old_idle18 =[ $old18, old_idle19 ] {}; +void() old_idle19 =[ $old19, old_idle20 ] {}; +void() old_idle20 =[ $old20, old_idle21 ] {}; +void() old_idle21 =[ $old21, old_idle22 ] {}; +void() old_idle22 =[ $old22, old_idle23 ] {}; +void() old_idle23 =[ $old23, old_idle24 ] {}; +void() old_idle24 =[ $old24, old_idle25 ] {}; +void() old_idle25 =[ $old25, old_idle26 ] {}; +void() old_idle26 =[ $old26, old_idle27 ] {}; +void() old_idle27 =[ $old27, old_idle28 ] {}; +void() old_idle28 =[ $old28, old_idle29 ] {}; +void() old_idle29 =[ $old29, old_idle30 ] {}; +void() old_idle30 =[ $old30, old_idle31 ] {}; +void() old_idle31 =[ $old31, old_idle32 ] {}; +void() old_idle32 =[ $old32, old_idle33 ] {}; +void() old_idle33 =[ $old33, old_idle34 ] {}; +void() old_idle34 =[ $old34, old_idle35 ] {}; +void() old_idle35 =[ $old35, old_idle36 ] {}; +void() old_idle36 =[ $old36, old_idle37 ] {}; +void() old_idle37 =[ $old37, old_idle38 ] {}; +void() old_idle38 =[ $old38, old_idle39 ] {}; +void() old_idle39 =[ $old39, old_idle40 ] {}; +void() old_idle40 =[ $old40, old_idle41 ] {}; +void() old_idle41 =[ $old41, old_idle42 ] {}; +void() old_idle42 =[ $old42, old_idle43 ] {}; +void() old_idle43 =[ $old43, old_idle44 ] {}; +void() old_idle44 =[ $old44, old_idle45 ] {}; +void() old_idle45 =[ $old45, old_idle46 ] {}; +void() old_idle46 =[ $old46, old_idle1 ] {}; + + +void() old_thrash1 =[ $shake1, old_thrash2 ] {lightstyle(0, "m");}; +void() old_thrash2 =[ $shake2, old_thrash3 ] {lightstyle(0, "k");}; +void() old_thrash3 =[ $shake3, old_thrash4 ] {lightstyle(0, "k");}; +void() old_thrash4 =[ $shake4, old_thrash5 ] {lightstyle(0, "i");}; +void() old_thrash5 =[ $shake5, old_thrash6 ] {lightstyle(0, "g");}; +void() old_thrash6 =[ $shake6, old_thrash7 ] {lightstyle(0, "e");}; +void() old_thrash7 =[ $shake7, old_thrash8 ] {lightstyle(0, "c");}; +void() old_thrash8 =[ $shake8, old_thrash9 ] {lightstyle(0, "a");}; +void() old_thrash9 =[ $shake9, old_thrash10 ] {lightstyle(0, "c");}; +void() old_thrash10 =[ $shake10, old_thrash11 ] {lightstyle(0, "e");}; +void() old_thrash11 =[ $shake11, old_thrash12 ] {lightstyle(0, "g");}; +void() old_thrash12 =[ $shake12, old_thrash13 ] {lightstyle(0, "i");}; +void() old_thrash13 =[ $shake13, old_thrash14 ] {lightstyle(0, "k");}; +void() old_thrash14 =[ $shake14, old_thrash15 ] {lightstyle(0, "m");}; +void() old_thrash15 =[ $shake15, old_thrash16 ] { + lightstyle(0, "m"); + self.cnt = self.cnt + 1; + + if (self.cnt != 3) + self.think = old_thrash1; +}; + +void() old_thrash16 =[ $shake16, old_thrash17 ] {lightstyle(0, "g");}; +void() old_thrash17 =[ $shake17, old_thrash18 ] {lightstyle(0, "c");}; +void() old_thrash18 =[ $shake18, old_thrash19 ] {lightstyle(0, "b");}; +void() old_thrash19 =[ $shake19, old_thrash20 ] {lightstyle(0, "a");}; +void() old_thrash20 =[ $shake20, old_thrash20 ] {finale_4();}; + +//============================================================================ + +void() finale_1 = +{ + local entity pos, pl; + local entity timer; + + intermission_exittime = time + 10000000; // never allow exit + intermission_running = 1; + + // find the intermission spot + pos = find (world, classname, "info_intermission"); + + if (!pos) + error ("no info_intermission"); + + pl = find (world, classname, "misc_teleporttrain"); + + if (!pl) + error ("no teleporttrain"); + remove (pl); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, string_null); + + pl = find (world, classname, "player"); + + while (pl != world) + { + pl.view_ofs = '0 0 0'; + pl.angles = other.v_angle = pos.mangle; + pl.fixangle = TRUE; // turn this way immediately + pl.map = self.map; + pl.nextthink = time + 0.5; + pl.takedamage = DAMAGE_NO; + pl.solid = SOLID_NOT; + pl.movetype = MOVETYPE_NONE; + pl.modelindex = 0; + setorigin (pl, pos.origin); + pl = find (pl, classname, "player"); + } + + // make fake versions of all players as standins, and move the real + // players to the intermission spot + + // wait for 1 second + timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_2; + + if (campaign && world.model == "maps/end.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_DEFEAT_SHUB"); + if (skill == 3) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_DEFEAT_SHUB_NIGHTMARE"); + } + } +}; + +void() finale_2 = +{ + local vector o; + + // start a teleport splash inside shub + + o = shub.origin - '0 100 0'; + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, o_x); + WriteCoord (MSG_BROADCAST, o_y); + WriteCoord (MSG_BROADCAST, o_z); + + sound (shub, CHAN_VOICE, "misc/r_tele1.wav", 1, ATTN_NORM); + + self.nextthink = time + 2; + self.think = finale_3; +}; + +void() finale_3 = +{ + // start shub thrashing wildly + shub.think = old_thrash1; + sound (shub, CHAN_VOICE, "boss2/death.wav", 1, ATTN_NORM); + lightstyle(0, "abcdefghijklmlkjihgfedcb"); +}; + +void() finale_4 = +{ + // throw tons of meat chunks + local vector oldo; + local float x, y, z; + local float r; + local entity n; + + sound (self, CHAN_VOICE, "boss2/pop2.wav", 1, ATTN_NORM); + + oldo = self.origin; + + z = 16; + while (z <= 144) + { + x = -64; + while (x <= 64) + { + y = -64; + while (y <= 64) + { + self.origin_x = oldo_x + x; + self.origin_y = oldo_y + y; + self.origin_z = oldo_z + z; + + r = random(); + + if (r < 0.3) + ThrowGib ("progs/gib1.mdl", -999); + else if (r < 0.6) + ThrowGib ("progs/gib2.mdl", -999); + else + ThrowGib ("progs/gib3.mdl", -999); + + y = y + 32; + } + + x = x + 32; + } + + z = z + 96; + } + + // start the end text + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_end"); + +// put a player model down + n = spawn(); + setmodel (n, "progs/player.mdl"); + oldo = oldo - '32 264 0'; + setorigin (n, oldo); + n.angles = '0 290 0'; + n.frame = 1; + + remove (self); + +// switch cd track + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + lightstyle(0, "m"); + + // instead of sitting here forever, run the quake ex credits and send the user back to start + local entity timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_5; +}; + +void() finale_5 = +{ + if (finaleFinished()) { + self.nextthink = time + 5; + self.think = finale_6; + } else { + self.nextthink = time + 0.1; + } +} + +void() finale_6 = +{ + if (!coop) { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + } else { + changelevel("start"); + } +} + +//============================================================================ + + +/*QUAKED monster_oldone (1 0 0) (-16 -16 -24) (16 16 32) +*/ +void() monster_oldone = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/oldone.mdl"); + + precache_sound2 ("boss2/death.wav"); + precache_sound2 ("boss2/idle.wav"); + precache_sound2 ("boss2/sight.wav"); + precache_sound2 ("boss2/pop2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/oldone.mdl"); + self.netname = "$qc_shub"; + self.killstring = "$qc_ks_shub"; + + setsize (self, '-160 -128 -24', '160 128 256'); + + self.health = 40000; // kill by telefrag + self.max_health = 40000; // kill by telefrag + self.think = old_idle1; + self.nextthink = time + 0.1; + self.takedamage = DAMAGE_YES; + self.th_pain = SUB_Null; + self.th_die = finale_1; + shub = self; + + total_monsters = total_monsters + 1; +}; + diff --git a/quakec/monsters/spawn.qc b/quakec/monsters/spawn.qc new file mode 100644 index 0000000..be873b5 --- /dev/null +++ b/quakec/monsters/spawn.qc @@ -0,0 +1,243 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BLOB + +============================================================================== +*/ + +$cd id1/models/tarbaby +$origin 0 0 24 +$base base + +$skin skin + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 +$frame walk20 walk21 walk22 walk23 walk24 walk25 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 run13 +$frame run14 run15 run16 run17 run18 run19 run20 run21 run22 run23 +$frame run24 run25 + +$frame jump1 jump2 jump3 jump4 jump5 jump6 + +$frame fly1 fly2 fly3 fly4 + +$frame exp + +void() tbaby_stand1 =[ $walk1, tbaby_stand1 ] {ai_stand();}; + +void() tbaby_hang1 =[ $walk1, tbaby_hang1 ] {ai_stand();}; + +void() tbaby_walk1 =[ $walk1, tbaby_walk2 ] {ai_turn();}; +void() tbaby_walk2 =[ $walk2, tbaby_walk3 ] {ai_turn();}; +void() tbaby_walk3 =[ $walk3, tbaby_walk4 ] {ai_turn();}; +void() tbaby_walk4 =[ $walk4, tbaby_walk5 ] {ai_turn();}; +void() tbaby_walk5 =[ $walk5, tbaby_walk6 ] {ai_turn();}; +void() tbaby_walk6 =[ $walk6, tbaby_walk7 ] {ai_turn();}; +void() tbaby_walk7 =[ $walk7, tbaby_walk8 ] {ai_turn();}; +void() tbaby_walk8 =[ $walk8, tbaby_walk9 ] {ai_turn();}; +void() tbaby_walk9 =[ $walk9, tbaby_walk10 ] {ai_turn();}; +void() tbaby_walk10 =[ $walk10, tbaby_walk11 ] {ai_turn();}; +void() tbaby_walk11 =[ $walk11, tbaby_walk12 ] {ai_walk(2);}; +void() tbaby_walk12 =[ $walk12, tbaby_walk13 ] {ai_walk(2);}; +void() tbaby_walk13 =[ $walk13, tbaby_walk14 ] {ai_walk(2);}; +void() tbaby_walk14 =[ $walk14, tbaby_walk15 ] {ai_walk(2);}; +void() tbaby_walk15 =[ $walk15, tbaby_walk16 ] {ai_walk(2);}; +void() tbaby_walk16 =[ $walk16, tbaby_walk17 ] {ai_walk(2);}; +void() tbaby_walk17 =[ $walk17, tbaby_walk18 ] {ai_walk(2);}; +void() tbaby_walk18 =[ $walk18, tbaby_walk19 ] {ai_walk(2);}; +void() tbaby_walk19 =[ $walk19, tbaby_walk20 ] {ai_walk(2);}; +void() tbaby_walk20 =[ $walk20, tbaby_walk21 ] {ai_walk(2);}; +void() tbaby_walk21 =[ $walk21, tbaby_walk22 ] {ai_walk(2);}; +void() tbaby_walk22 =[ $walk22, tbaby_walk23 ] {ai_walk(2);}; +void() tbaby_walk23 =[ $walk23, tbaby_walk24 ] {ai_walk(2);}; +void() tbaby_walk24 =[ $walk24, tbaby_walk25 ] {ai_walk(2);}; +void() tbaby_walk25 =[ $walk25, tbaby_walk1 ] {ai_walk(2);}; + +void() tbaby_run1 =[ $run1, tbaby_run2 ] {ai_face();}; +void() tbaby_run2 =[ $run2, tbaby_run3 ] {ai_face();}; +void() tbaby_run3 =[ $run3, tbaby_run4 ] {ai_face();}; +void() tbaby_run4 =[ $run4, tbaby_run5 ] {ai_face();}; +void() tbaby_run5 =[ $run5, tbaby_run6 ] {ai_face();}; +void() tbaby_run6 =[ $run6, tbaby_run7 ] {ai_face();}; +void() tbaby_run7 =[ $run7, tbaby_run8 ] {ai_face();}; +void() tbaby_run8 =[ $run8, tbaby_run9 ] {ai_face();}; +void() tbaby_run9 =[ $run9, tbaby_run10 ] {ai_face();}; +void() tbaby_run10 =[ $run10, tbaby_run11 ] {ai_face();}; +void() tbaby_run11 =[ $run11, tbaby_run12 ] {ai_run(2);}; +void() tbaby_run12 =[ $run12, tbaby_run13 ] {ai_run(2);}; +void() tbaby_run13 =[ $run13, tbaby_run14 ] {ai_run(2);}; +void() tbaby_run14 =[ $run14, tbaby_run15 ] {ai_run(2);}; +void() tbaby_run15 =[ $run15, tbaby_run16 ] {ai_run(2);}; +void() tbaby_run16 =[ $run16, tbaby_run17 ] {ai_run(2);}; +void() tbaby_run17 =[ $run17, tbaby_run18 ] {ai_run(2);}; +void() tbaby_run18 =[ $run18, tbaby_run19 ] {ai_run(2);}; +void() tbaby_run19 =[ $run19, tbaby_run20 ] {ai_run(2);}; +void() tbaby_run20 =[ $run20, tbaby_run21 ] {ai_run(2);}; +void() tbaby_run21 =[ $run21, tbaby_run22 ] {ai_run(2);}; +void() tbaby_run22 =[ $run22, tbaby_run23 ] {ai_run(2);}; +void() tbaby_run23 =[ $run23, tbaby_run24 ] {ai_run(2);}; +void() tbaby_run24 =[ $run24, tbaby_run25 ] {ai_run(2);}; +void() tbaby_run25 =[ $run25, tbaby_run1 ] {ai_run(2);}; + + +//============================================================================ + + +void() tbaby_jump1; + +void() Tar_JumpTouch = +{ + local float ldmg; + + if (other.takedamage && other.classname != self.classname) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + sound (self, CHAN_WEAPON, "blob/hit1.wav", 1, ATTN_NORM); + } + } + else + sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM); + + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up + self.touch = SUB_Null; + self.think = tbaby_run1; + self.movetype = MOVETYPE_STEP; + self.nextthink = time + 0.1; + } + + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = tbaby_jump1; + self.nextthink = time + 0.1; +}; + +void() tbaby_jump5; + +void() tbaby_fly1 =[ $fly1, tbaby_fly2 ] {}; +void() tbaby_fly2 =[ $fly2, tbaby_fly3 ] {}; +void() tbaby_fly3 =[ $fly3, tbaby_fly4 ] {}; +void() tbaby_fly4 =[ $fly4, tbaby_fly1 ] { + self.cnt = self.cnt + 1; + + if (self.cnt == 4) { + tbaby_jump5(); + } +}; + +void() tbaby_jump1 =[ $jump1, tbaby_jump2 ] {ai_face();}; +void() tbaby_jump2 =[ $jump2, tbaby_jump3 ] {ai_face();}; +void() tbaby_jump3 =[ $jump3, tbaby_jump4 ] {ai_face();}; +void() tbaby_jump4 =[ $jump4, tbaby_jump5 ] {ai_face();}; +void() tbaby_jump5 =[ $jump5, tbaby_jump6 ] +{ + self.movetype = MOVETYPE_BOUNCE; + self.touch = Tar_JumpTouch; + makevectorsfixed(self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 200'; + self.velocity_z = self.velocity_z + random()*150; + + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; + + self.cnt = 0; +}; + +void() tbaby_jump6 =[ $jump6,tbaby_fly1 ] {}; + + + +//============================================================================= + +void() tbaby_die1 =[ $exp, tbaby_die2 ] {self.takedamage = DAMAGE_NO;}; +void() tbaby_die2 =[ $exp, tbaby_run1 ] { + T_RadiusDamage (self, self, 120, world); + + sound (self, CHAN_VOICE, "blob/death1.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TAREXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +//============================================================================= + + +/*QUAKED monster_tarbaby (1 0 0) (-16 -16 -24) (16 16 24) Ambush +*/ +void() monster_tarbaby = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/tarbaby.mdl"); + + precache_sound2 ("blob/death1.wav"); + precache_sound2 ("blob/hit1.wav"); + precache_sound2 ("blob/land1.wav"); + precache_sound2 ("blob/sight1.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/tarbaby.mdl"); + + self.noise = "blob/sight1.wav"; + self.netname = "$qc_spawn"; + self.killstring = "$qc_ks_spawn"; + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + self.max_health = 80; + + self.th_stand = tbaby_stand1; + self.th_walk = tbaby_walk1; + self.th_run = tbaby_run1; + self.th_missile = tbaby_jump1; + self.th_melee = tbaby_jump1; + self.th_die = tbaby_die1; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + walkmonster_start (); +}; + diff --git a/quakec/monsters/vore.qc b/quakec/monsters/vore.qc new file mode 100644 index 0000000..78fd88c --- /dev/null +++ b/quakec/monsters/vore.qc @@ -0,0 +1,278 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAL-RATH + +============================================================================== +*/ +$cd id1/models/shalrath +$origin 0 0 24 +$base base +$skin skin +$scale 0.7 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 death7 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 + +void(entity attacker, float damage) shalrath_pain; +void() ShalMissile; +void() shal_stand =[ $walk1, shal_stand ] {ai_stand();}; + +void() shal_walk1 =[ $walk2, shal_walk2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); + + ai_walk(6); +}; + +void() shal_walk2 =[ $walk3, shal_walk3 ] {ai_walk(4);}; +void() shal_walk3 =[ $walk4, shal_walk4 ] {ai_walk(0);}; +void() shal_walk4 =[ $walk5, shal_walk5 ] {ai_walk(0);}; +void() shal_walk5 =[ $walk6, shal_walk6 ] {ai_walk(0);}; +void() shal_walk6 =[ $walk7, shal_walk7 ] {ai_walk(0);}; +void() shal_walk7 =[ $walk8, shal_walk8 ] {ai_walk(5);}; +void() shal_walk8 =[ $walk9, shal_walk9 ] {ai_walk(6);}; +void() shal_walk9 =[ $walk10, shal_walk10 ] {ai_walk(5);}; +void() shal_walk10 =[ $walk11, shal_walk11 ] {ai_walk(0);}; +void() shal_walk11 =[ $walk12, shal_walk12 ] {ai_walk(4);}; +void() shal_walk12 =[ $walk1, shal_walk1 ] {ai_walk(5);}; + +void() shal_run1 =[ $walk2, shal_run2 ] { + if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); + + ai_run(6); +}; + +void() shal_run2 =[ $walk3, shal_run3 ] {ai_run(4);}; +void() shal_run3 =[ $walk4, shal_run4 ] {ai_run(0);}; +void() shal_run4 =[ $walk5, shal_run5 ] {ai_run(0);}; +void() shal_run5 =[ $walk6, shal_run6 ] {ai_run(0);}; +void() shal_run6 =[ $walk7, shal_run7 ] {ai_run(0);}; +void() shal_run7 =[ $walk8, shal_run8 ] {ai_run(5);}; +void() shal_run8 =[ $walk9, shal_run9 ] {ai_run(6);}; +void() shal_run9 =[ $walk10, shal_run10 ] {ai_run(5);}; +void() shal_run10 =[ $walk11, shal_run11 ] {ai_run(0);}; +void() shal_run11 =[ $walk12, shal_run12 ] {ai_run(4);}; +void() shal_run12 =[ $walk1, shal_run1 ] {ai_run(5);}; + +void() shal_attack1 =[ $attack1, shal_attack2 ] { + sound (self, CHAN_VOICE, "shalrath/attack.wav", 1, ATTN_NORM); + ai_face(); +}; + +void() shal_attack2 =[ $attack2, shal_attack3 ] {ai_face();}; +void() shal_attack3 =[ $attack3, shal_attack4 ] {ai_face();}; +void() shal_attack4 =[ $attack4, shal_attack5 ] {ai_face();}; +void() shal_attack5 =[ $attack5, shal_attack6 ] {ai_face();}; +void() shal_attack6 =[ $attack6, shal_attack7 ] {ai_face();}; +void() shal_attack7 =[ $attack7, shal_attack8 ] {ai_face();}; +void() shal_attack8 =[ $attack8, shal_attack9 ] {ai_face();}; +void() shal_attack9 =[ $attack9, shal_attack10 ] {ShalMissile();}; +void() shal_attack10 =[ $attack10, shal_attack11 ] {ai_face();}; +void() shal_attack11 =[ $attack11, shal_run1 ] {}; + +void() shal_pain1 =[ $pain1, shal_pain2 ] {}; +void() shal_pain2 =[ $pain2, shal_pain3 ] {}; +void() shal_pain3 =[ $pain3, shal_pain4 ] {}; +void() shal_pain4 =[ $pain4, shal_pain5 ] {}; +void() shal_pain5 =[ $pain5, shal_run1 ] {}; + +void() shal_death1 =[ $death1, shal_death2 ] {}; +void() shal_death2 =[ $death2, shal_death3 ] {}; +void() shal_death3 =[ $death3, shal_death4 ] {}; +void() shal_death4 =[ $death4, shal_death5 ] {}; +void() shal_death5 =[ $death5, shal_death6 ] {}; +void() shal_death6 =[ $death6, shal_death7 ] {}; +void() shal_death7 =[ $death7, shal_death7 ] {}; + + +void(entity attacker, float damage) shalrath_pain = +{ + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "shalrath/pain.wav", 1, ATTN_NORM); + shal_pain1(); + self.pain_finished = time + 3; +}; + +void() shalrath_die = +{ + // check for gib + if (self.health < -90) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shal.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "shalrath/death.wav", 1, ATTN_NORM); + shal_death1(); + self.solid = SOLID_NOT; + // insert death sounds here +}; + +/* +================ +ShalMissile +================ +*/ +void() ShalMissileTouch; +void() ShalHome; +void() ShalMissile = +{ + local entity missile; + local vector dir; + local float dist, flytime; + + dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + dist = vlen (self.enemy.origin - self.origin); + flytime = dist * 0.002; + + if (flytime < 0.1) + flytime = 0.1; + + self.effects = self.effects | EF_MUZZLEFLASH; + sound (self, CHAN_WEAPON, "shalrath/attack2.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.classname = "vore_ball"; + missile.owner = self; + + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + setmodel (missile, "progs/v_spike.mdl"); + + setsize (missile, '0 0 0', '0 0 0'); + + missile.origin = self.origin + '0 0 10'; + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.nextthink = flytime + time; + missile.think = ShalHome; + missile.enemy = self.enemy; + missile.touch = ShalMissileTouch; +}; + +void() ShalHome = +{ + local vector dir, vtemp; + vtemp = self.enemy.origin + '0 0 10'; + + if (self.enemy.health < 1) + { + remove(self); + return; + } + + dir = normalize(vtemp - self.origin); + + self.velocity = dir * 250; + + self.nextthink = time + 0.2; + self.think = ShalHome; +}; + +void() ShalMissileTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (other.classname == "monster_zombie") + T_Damage (other, self, self, 110); + + T_RadiusDamage (self, self.owner, 40, world); + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +//================================================================= + +/*QUAKED monster_shalrath (1 0 0) (-32 -32 -24) (32 32 48) Ambush +*/ +void() monster_shalrath = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/shalrath.mdl"); + precache_model2 ("progs/h_shal.mdl"); + precache_model2 ("progs/v_spike.mdl"); + + precache_sound2 ("shalrath/attack.wav"); + precache_sound2 ("shalrath/attack2.wav"); + precache_sound2 ("shalrath/death.wav"); + precache_sound2 ("shalrath/idle.wav"); + precache_sound2 ("shalrath/pain.wav"); + precache_sound2 ("shalrath/sight.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/shalrath.mdl"); + + self.noise = "shalrath/sight.wav"; + self.netname = "$qc_vore"; + self.killstring = "$qc_ks_vore"; + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 400; + self.max_health = 400; + + self.th_stand = shal_stand; + self.th_walk = shal_walk1; + self.th_run = shal_run1; + self.th_die = shalrath_die; + self.th_pain = shalrath_pain; + self.th_missile = shal_attack1; + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + + self.think = walkmonster_start; + self.nextthink = time + 0.1 + random () * 0.1; + +}; diff --git a/quakec/monsters/zombie.qc b/quakec/monsters/zombie.qc new file mode 100644 index 0000000..6097a46 --- /dev/null +++ b/quakec/monsters/zombie.qc @@ -0,0 +1,547 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +ZOMBIE + +============================================================================== +*/ +$cd id1/models/zombie + +$origin 0 0 24 + +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 +$frame stand9 stand10 stand11 stand12 stand13 stand14 stand15 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 walk11 +$frame walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 +$frame run13 run14 run15 run16 run17 run18 + +$frame atta1 atta2 atta3 atta4 atta5 atta6 atta7 atta8 atta9 atta10 atta11 +$frame atta12 atta13 + +$frame attb1 attb2 attb3 attb4 attb5 attb6 attb7 attb8 attb9 attb10 attb11 +$frame attb12 attb13 attb14 + +$frame attc1 attc2 attc3 attc4 attc5 attc6 attc7 attc8 attc9 attc10 attc11 +$frame attc12 + +$frame paina1 paina2 paina3 paina4 paina5 paina6 paina7 paina8 paina9 paina10 +$frame paina11 paina12 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 painb17 painb18 painb19 +$frame painb20 painb21 painb22 painb23 painb24 painb25 painb26 painb27 painb28 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 painc14 painc15 painc16 painc17 painc18 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 paine16 paine17 paine18 paine19 +$frame paine20 paine21 paine22 paine23 paine24 paine25 paine26 paine27 paine28 +$frame paine29 paine30 + +$frame cruc_1 cruc_2 cruc_3 cruc_4 cruc_5 cruc_6 + +float SPAWN_CRUCIFIED = 1; + +//============================================================================= + +.float inpain; + +void() zombie_stand1 =[ $stand1, zombie_stand2 ] {ai_stand();}; +void() zombie_stand2 =[ $stand2, zombie_stand3 ] {ai_stand();}; +void() zombie_stand3 =[ $stand3, zombie_stand4 ] {ai_stand();}; +void() zombie_stand4 =[ $stand4, zombie_stand5 ] {ai_stand();}; +void() zombie_stand5 =[ $stand5, zombie_stand6 ] {ai_stand();}; +void() zombie_stand6 =[ $stand6, zombie_stand7 ] {ai_stand();}; +void() zombie_stand7 =[ $stand7, zombie_stand8 ] {ai_stand();}; +void() zombie_stand8 =[ $stand8, zombie_stand9 ] {ai_stand();}; +void() zombie_stand9 =[ $stand9, zombie_stand10 ] {ai_stand();}; +void() zombie_stand10 =[ $stand10, zombie_stand11 ] {ai_stand();}; +void() zombie_stand11 =[ $stand11, zombie_stand12 ] {ai_stand();}; +void() zombie_stand12 =[ $stand12, zombie_stand13 ] {ai_stand();}; +void() zombie_stand13 =[ $stand13, zombie_stand14 ] {ai_stand();}; +void() zombie_stand14 =[ $stand14, zombie_stand15 ] {ai_stand();}; +void() zombie_stand15 =[ $stand15, zombie_stand1 ] {ai_stand();}; + +void() zombie_cruc1 = [ $cruc_1, zombie_cruc2 ] { +if (random() < 0.1) + sound (self, CHAN_VOICE, "zombie/idle_w2.wav", 1, ATTN_STATIC);}; +void() zombie_cruc2 = [ $cruc_2, zombie_cruc3 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc3 = [ $cruc_3, zombie_cruc4 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc4 = [ $cruc_4, zombie_cruc5 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc5 = [ $cruc_5, zombie_cruc6 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc6 = [ $cruc_6, zombie_cruc1 ] {self.nextthink = time + 0.1 + random()*0.1;}; + +void() zombie_walk1 =[ $walk1, zombie_walk2 ] {ai_walk(0);}; +void() zombie_walk2 =[ $walk2, zombie_walk3 ] {ai_walk(2);}; +void() zombie_walk3 =[ $walk3, zombie_walk4 ] {ai_walk(3);}; +void() zombie_walk4 =[ $walk4, zombie_walk5 ] {ai_walk(2);}; +void() zombie_walk5 =[ $walk5, zombie_walk6 ] {ai_walk(1);}; +void() zombie_walk6 =[ $walk6, zombie_walk7 ] {ai_walk(0);}; +void() zombie_walk7 =[ $walk7, zombie_walk8 ] {ai_walk(0);}; +void() zombie_walk8 =[ $walk8, zombie_walk9 ] {ai_walk(0);}; +void() zombie_walk9 =[ $walk9, zombie_walk10 ] {ai_walk(0);}; +void() zombie_walk10 =[ $walk10, zombie_walk11 ] {ai_walk(0);}; +void() zombie_walk11 =[ $walk11, zombie_walk12 ] {ai_walk(2);}; +void() zombie_walk12 =[ $walk12, zombie_walk13 ] {ai_walk(2);}; +void() zombie_walk13 =[ $walk13, zombie_walk14 ] {ai_walk(1);}; +void() zombie_walk14 =[ $walk14, zombie_walk15 ] {ai_walk(0);}; +void() zombie_walk15 =[ $walk15, zombie_walk16 ] {ai_walk(0);}; +void() zombie_walk16 =[ $walk16, zombie_walk17 ] {ai_walk(0);}; +void() zombie_walk17 =[ $walk17, zombie_walk18 ] {ai_walk(0);}; +void() zombie_walk18 =[ $walk18, zombie_walk19 ] {ai_walk(0);}; +void() zombie_walk19 =[ $walk19, zombie_walk1 ] { +ai_walk(0); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE);}; + +void() zombie_run1 =[ $run1, zombie_run2 ] {ai_run(1);self.inpain = 0;}; +void() zombie_run2 =[ $run2, zombie_run3 ] {ai_run(1);}; +void() zombie_run3 =[ $run3, zombie_run4 ] {ai_run(0);}; +void() zombie_run4 =[ $run4, zombie_run5 ] {ai_run(1);}; +void() zombie_run5 =[ $run5, zombie_run6 ] {ai_run(2);}; +void() zombie_run6 =[ $run6, zombie_run7 ] {ai_run(3);}; +void() zombie_run7 =[ $run7, zombie_run8 ] {ai_run(4);}; +void() zombie_run8 =[ $run8, zombie_run9 ] {ai_run(4);}; +void() zombie_run9 =[ $run9, zombie_run10 ] {ai_run(2);}; +void() zombie_run10 =[ $run10, zombie_run11 ] {ai_run(0);}; +void() zombie_run11 =[ $run11, zombie_run12 ] {ai_run(0);}; +void() zombie_run12 =[ $run12, zombie_run13 ] {ai_run(0);}; +void() zombie_run13 =[ $run13, zombie_run14 ] {ai_run(2);}; +void() zombie_run14 =[ $run14, zombie_run15 ] {ai_run(4);}; +void() zombie_run15 =[ $run15, zombie_run16 ] {ai_run(6);}; +void() zombie_run16 =[ $run16, zombie_run17 ] {ai_run(7);}; +void() zombie_run17 =[ $run17, zombie_run18 ] {ai_run(3);}; +void() zombie_run18 =[ $run18, zombie_run1 ] { +ai_run(8); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); +if (random() > 0.8) + sound (self, CHAN_VOICE, "zombie/z_idle1.wav", 1, ATTN_IDLE); +}; + +/* +============================================================================= + +ATTACKS + +============================================================================= +*/ + +void() ZombieGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (other.takedamage) + { + T_Damage (other, self, self.owner, 10 ); + sound (self, CHAN_WEAPON, "zombie/z_hit.wav", 1, ATTN_NORM); + remove (self); + return; + } + + sound (self, CHAN_WEAPON, "zombie/z_miss.wav", 1, ATTN_NORM); // bounce sound + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.touch = SUB_Remove; +}; + +/* +================ +ZombieFireGrenade +================ +*/ +void(vector st) ZombieFireGrenade = +{ + local entity missile; + local vector org; + + sound (self, CHAN_WEAPON, "zombie/z_shot1.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.classname = "zombie_grenade"; + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + + // calc org + org = self.origin + st_x * v_forward + st_y * v_right + (st_z - 24) * v_up; + + // set missile speed + + makevectorsfixed(self.angles); + + missile.velocity = normalize(self.enemy.origin - org); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + + missile.avelocity = '3000 1000 2000'; + + missile.touch = ZombieGrenadeTouch; + + // set missile duration + missile.nextthink = time + 2.5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + + +void() zombie_atta1 =[ $atta1, zombie_atta2 ] {ai_face();}; +void() zombie_atta2 =[ $atta2, zombie_atta3 ] {ai_face();}; +void() zombie_atta3 =[ $atta3, zombie_atta4 ] {ai_face();}; +void() zombie_atta4 =[ $atta4, zombie_atta5 ] {ai_face();}; +void() zombie_atta5 =[ $atta5, zombie_atta6 ] {ai_face();}; +void() zombie_atta6 =[ $atta6, zombie_atta7 ] {ai_face();}; +void() zombie_atta7 =[ $atta7, zombie_atta8 ] {ai_face();}; +void() zombie_atta8 =[ $atta8, zombie_atta9 ] {ai_face();}; +void() zombie_atta9 =[ $atta9, zombie_atta10 ] {ai_face();}; +void() zombie_atta10 =[ $atta10, zombie_atta11 ] {ai_face();}; +void() zombie_atta11 =[ $atta11, zombie_atta12 ] {ai_face();}; +void() zombie_atta12 =[ $atta12, zombie_atta13 ] {ai_face();}; +void() zombie_atta13 =[ $atta13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -22 30');}; + +void() zombie_attb1 =[ $attb1, zombie_attb2 ] {ai_face();}; +void() zombie_attb2 =[ $attb2, zombie_attb3 ] {ai_face();}; +void() zombie_attb3 =[ $attb3, zombie_attb4 ] {ai_face();}; +void() zombie_attb4 =[ $attb4, zombie_attb5 ] {ai_face();}; +void() zombie_attb5 =[ $attb5, zombie_attb6 ] {ai_face();}; +void() zombie_attb6 =[ $attb6, zombie_attb7 ] {ai_face();}; +void() zombie_attb7 =[ $attb7, zombie_attb8 ] {ai_face();}; +void() zombie_attb8 =[ $attb8, zombie_attb9 ] {ai_face();}; +void() zombie_attb9 =[ $attb9, zombie_attb10 ] {ai_face();}; +void() zombie_attb10 =[ $attb10, zombie_attb11 ] {ai_face();}; +void() zombie_attb11 =[ $attb11, zombie_attb12 ] {ai_face();}; +void() zombie_attb12 =[ $attb12, zombie_attb13 ] {ai_face();}; +void() zombie_attb13 =[ $attb13, zombie_attb14 ] {ai_face();}; +void() zombie_attb14 =[ $attb13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -24 29');}; + +void() zombie_attc1 =[ $attc1, zombie_attc2 ] {ai_face();}; +void() zombie_attc2 =[ $attc2, zombie_attc3 ] {ai_face();}; +void() zombie_attc3 =[ $attc3, zombie_attc4 ] {ai_face();}; +void() zombie_attc4 =[ $attc4, zombie_attc5 ] {ai_face();}; +void() zombie_attc5 =[ $attc5, zombie_attc6 ] {ai_face();}; +void() zombie_attc6 =[ $attc6, zombie_attc7 ] {ai_face();}; +void() zombie_attc7 =[ $attc7, zombie_attc8 ] {ai_face();}; +void() zombie_attc8 =[ $attc8, zombie_attc9 ] {ai_face();}; +void() zombie_attc9 =[ $attc9, zombie_attc10 ] {ai_face();}; +void() zombie_attc10 =[ $attc10, zombie_attc11 ] {ai_face();}; +void() zombie_attc11 =[ $attc11, zombie_attc12 ] {ai_face();}; +void() zombie_attc12 =[ $attc12, zombie_run1 ] {ai_face();ZombieFireGrenade('-12 -19 29');}; + +void() zombie_missile = +{ + local float r; + + r = random(); + + if (r < 0.3) + zombie_atta1 (); + else if (r < 0.6) + zombie_attb1 (); + else + zombie_attc1 (); +}; + + +/* +============================================================================= + +PAIN + +============================================================================= +*/ + +void() zombie_paina1 =[ $paina1, zombie_paina2 ] {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() zombie_paina2 =[ $paina2, zombie_paina3 ] {ai_painforward(3);}; +void() zombie_paina3 =[ $paina3, zombie_paina4 ] {ai_painforward(1);}; +void() zombie_paina4 =[ $paina4, zombie_paina5 ] {ai_pain(1);}; +void() zombie_paina5 =[ $paina5, zombie_paina6 ] {ai_pain(3);}; +void() zombie_paina6 =[ $paina6, zombie_paina7 ] {ai_pain(1);}; +void() zombie_paina7 =[ $paina7, zombie_paina8 ] {}; +void() zombie_paina8 =[ $paina8, zombie_paina9 ] {}; +void() zombie_paina9 =[ $paina9, zombie_paina10 ] {}; +void() zombie_paina10 =[ $paina10, zombie_paina11 ] {}; +void() zombie_paina11 =[ $paina11, zombie_paina12 ] {}; +void() zombie_paina12 =[ $paina12, zombie_run1 ] {}; + +void() zombie_painb1 =[ $painb1, zombie_painb2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painb2 =[ $painb2, zombie_painb3 ] {ai_pain(2);}; +void() zombie_painb3 =[ $painb3, zombie_painb4 ] {ai_pain(8);}; +void() zombie_painb4 =[ $painb4, zombie_painb5 ] {ai_pain(6);}; +void() zombie_painb5 =[ $painb5, zombie_painb6 ] {ai_pain(2);}; +void() zombie_painb6 =[ $painb6, zombie_painb7 ] {}; +void() zombie_painb7 =[ $painb7, zombie_painb8 ] {}; +void() zombie_painb8 =[ $painb8, zombie_painb9 ] {}; +void() zombie_painb9 =[ $painb9, zombie_painb10 ] {sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM);}; +void() zombie_painb10 =[ $painb10, zombie_painb11 ] {}; +void() zombie_painb11 =[ $painb11, zombie_painb12 ] {}; +void() zombie_painb12 =[ $painb12, zombie_painb13 ] {}; +void() zombie_painb13 =[ $painb13, zombie_painb14 ] {}; +void() zombie_painb14 =[ $painb14, zombie_painb15 ] {}; +void() zombie_painb15 =[ $painb15, zombie_painb16 ] {}; +void() zombie_painb16 =[ $painb16, zombie_painb17 ] {}; +void() zombie_painb17 =[ $painb17, zombie_painb18 ] {}; +void() zombie_painb18 =[ $painb18, zombie_painb19 ] {}; +void() zombie_painb19 =[ $painb19, zombie_painb20 ] {}; +void() zombie_painb20 =[ $painb20, zombie_painb21 ] {}; +void() zombie_painb21 =[ $painb21, zombie_painb22 ] {}; +void() zombie_painb22 =[ $painb22, zombie_painb23 ] {}; +void() zombie_painb23 =[ $painb23, zombie_painb24 ] {}; +void() zombie_painb24 =[ $painb24, zombie_painb25 ] {}; +void() zombie_painb25 =[ $painb25, zombie_painb26 ] {ai_painforward(1);}; +void() zombie_painb26 =[ $painb26, zombie_painb27 ] {}; +void() zombie_painb27 =[ $painb27, zombie_painb28 ] {}; +void() zombie_painb28 =[ $painb28, zombie_run1 ] {}; + +void() zombie_painc1 =[ $painc1, zombie_painc2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painc2 =[ $painc2, zombie_painc3 ] {}; +void() zombie_painc3 =[ $painc3, zombie_painc4 ] {ai_pain(3);}; +void() zombie_painc4 =[ $painc4, zombie_painc5 ] {ai_pain(1);}; +void() zombie_painc5 =[ $painc5, zombie_painc6 ] {}; +void() zombie_painc6 =[ $painc6, zombie_painc7 ] {}; +void() zombie_painc7 =[ $painc7, zombie_painc8 ] {}; +void() zombie_painc8 =[ $painc8, zombie_painc9 ] {}; +void() zombie_painc9 =[ $painc9, zombie_painc10 ] {}; +void() zombie_painc10 =[ $painc10, zombie_painc11 ] {}; +void() zombie_painc11 =[ $painc11, zombie_painc12 ] {ai_painforward(1);}; +void() zombie_painc12 =[ $painc12, zombie_painc13 ] {ai_painforward(1);}; +void() zombie_painc13 =[ $painc13, zombie_painc14 ] {}; +void() zombie_painc14 =[ $painc14, zombie_painc15 ] {}; +void() zombie_painc15 =[ $painc15, zombie_painc16 ] {}; +void() zombie_painc16 =[ $painc16, zombie_painc17 ] {}; +void() zombie_painc17 =[ $painc17, zombie_painc18 ] {}; +void() zombie_painc18 =[ $painc18, zombie_run1 ] {}; + +void() zombie_paind1 =[ $paind1, zombie_paind2 ] {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() zombie_paind2 =[ $paind2, zombie_paind3 ] {}; +void() zombie_paind3 =[ $paind3, zombie_paind4 ] {}; +void() zombie_paind4 =[ $paind4, zombie_paind5 ] {}; +void() zombie_paind5 =[ $paind5, zombie_paind6 ] {}; +void() zombie_paind6 =[ $paind6, zombie_paind7 ] {}; +void() zombie_paind7 =[ $paind7, zombie_paind8 ] {}; +void() zombie_paind8 =[ $paind8, zombie_paind9 ] {}; +void() zombie_paind9 =[ $paind9, zombie_paind10 ] {ai_pain(1);}; +void() zombie_paind10 =[ $paind10, zombie_paind11 ] {}; +void() zombie_paind11 =[ $paind11, zombie_paind12 ] {}; +void() zombie_paind12 =[ $paind12, zombie_paind13 ] {}; +void() zombie_paind13 =[ $paind13, zombie_run1 ] {}; + +void() zombie_paine1 =[ $paine1, zombie_paine2 ] { +sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM); +self.health = 60; +}; + +void() zombie_paine2 =[ $paine2, zombie_paine3 ] {ai_pain(8);}; +void() zombie_paine3 =[ $paine3, zombie_paine4 ] {ai_pain(5);}; +void() zombie_paine4 =[ $paine4, zombie_paine5 ] {ai_pain(3);}; +void() zombie_paine5 =[ $paine5, zombie_paine6 ] {ai_pain(1);}; +void() zombie_paine6 =[ $paine6, zombie_paine7 ] {ai_pain(2);}; +void() zombie_paine7 =[ $paine7, zombie_paine8 ] {ai_pain(1);}; +void() zombie_paine8 =[ $paine8, zombie_paine9 ] {ai_pain(1);}; +void() zombie_paine9 =[ $paine9, zombie_paine10 ] {ai_pain(2);}; +void() zombie_paine10 =[ $paine10, zombie_paine11 ] { +sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM); +self.solid = SOLID_NOT; +}; +void() zombie_paine11 =[ $paine11, zombie_paine12 ] {self.nextthink = self.nextthink + 5;self.health = 60;}; +void() zombie_paine12 =[ $paine12, zombie_paine13 ] { + // see if ok to stand up + self.health = 60; + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); + self.solid = SOLID_SLIDEBOX; + + if (!walkmove (0, 0)) + { + self.think = zombie_paine11; + self.solid = SOLID_NOT; + return; + } +}; + +void() zombie_paine13 =[ $paine13, zombie_paine14 ] {}; +void() zombie_paine14 =[ $paine14, zombie_paine15 ] {}; +void() zombie_paine15 =[ $paine15, zombie_paine16 ] {}; +void() zombie_paine16 =[ $paine16, zombie_paine17 ] {}; +void() zombie_paine17 =[ $paine17, zombie_paine18 ] {}; +void() zombie_paine18 =[ $paine18, zombie_paine19 ] {}; +void() zombie_paine19 =[ $paine19, zombie_paine20 ] {}; +void() zombie_paine20 =[ $paine20, zombie_paine21 ] {}; +void() zombie_paine21 =[ $paine21, zombie_paine22 ] {}; +void() zombie_paine22 =[ $paine22, zombie_paine23 ] {}; +void() zombie_paine23 =[ $paine23, zombie_paine24 ] {}; +void() zombie_paine24 =[ $paine24, zombie_paine25 ] {}; +void() zombie_paine25 =[ $paine25, zombie_paine26 ] {ai_painforward(5);}; +void() zombie_paine26 =[ $paine26, zombie_paine27 ] {ai_painforward(3);}; +void() zombie_paine27 =[ $paine27, zombie_paine28 ] {ai_painforward(1);}; +void() zombie_paine28 =[ $paine28, zombie_paine29 ] {ai_pain(1);}; +void() zombie_paine29 =[ $paine29, zombie_paine30 ] {}; +void() zombie_paine30 =[ $paine30, zombie_run1 ] {}; + +void() zombie_die = +{ + sound (self, CHAN_VOICE, "zombie/z_gib.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_zombie.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); +}; + +/* +================= +zombie_pain + +Zombies can only be killed (gibbed) by doing 60 hit points of damage +in a single frame (rockets, grenades, quad shotgun, quad nailgun). + +A hit of 25 points or more (super shotgun, quad nailgun) will allways put it +down to the ground. + +A hit of from 10 to 40 points in one frame will cause it to go down if it +has been twice in two seconds, otherwise it goes into one of the four +fast pain frames. + +A hit of less than 10 points of damage (winged by a shotgun) will be ignored. + +FIXME: don't use pain_finished because of nightmare hack +================= +*/ +void(entity attacker, float take) zombie_pain = +{ + local float r; + + self.health = 60; // allways reset health + + if (take < 9) + return; // totally ignore + + if (self.inpain == 2) + return; // down on ground, so don't reset any counters + + // go down immediately if a big enough hit + if (take >= 25) + { + self.inpain = 2; + zombie_paine1 (); + return; + } + + if (self.inpain) + { + // if hit again in next gre seconds while not in pain frames, definately drop + self.pain_finished = time + 3; + return; // currently going through an animation, don't change + } + + if (self.pain_finished > time) + { + // hit again, so drop down + self.inpain = 2; + zombie_paine1 (); + return; + } + + // gp into one of the fast pain animations + self.inpain = 1; + + r = random(); + if (r < 0.25) + zombie_paina1 (); + else if (r < 0.5) + zombie_painb1 (); + else if (r < 0.75) + zombie_painc1 (); + else + zombie_paind1 (); +}; + +//============================================================================ + +/*QUAKED monster_zombie (1 0 0) (-16 -16 -24) (16 16 32) Crucified ambush + +If crucified, stick the bounding box 12 pixels back into a wall to look right. +*/ +void() monster_zombie = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/zombie.mdl"); + precache_model ("progs/h_zombie.mdl"); + precache_model ("progs/zom_gib.mdl"); + + precache_sound ("zombie/z_idle.wav"); + precache_sound ("zombie/z_idle1.wav"); + precache_sound ("zombie/z_shot1.wav"); + precache_sound ("zombie/z_gib.wav"); + precache_sound ("zombie/z_pain.wav"); + precache_sound ("zombie/z_pain1.wav"); + precache_sound ("zombie/z_fall.wav"); + precache_sound ("zombie/z_miss.wav"); + precache_sound ("zombie/z_hit.wav"); + precache_sound ("zombie/idle_w2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/zombie.mdl"); + + self.noise = "zombie/z_idle.wav"; + self.netname = "$qc_zombie"; + self.killstring = "$qc_ks_zombie"; + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 60; + self.max_health = 60; + + self.th_stand = zombie_stand1; + self.th_walk = zombie_walk1; + self.th_run = zombie_run1; + self.th_pain = zombie_pain; + self.th_die = zombie_die; + self.th_missile = zombie_missile; + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + + if (self.spawnflags & SPAWN_CRUCIFIED) + { + self.movetype = MOVETYPE_NONE; + zombie_cruc1 (); + } + else + walkmonster_start(); +}; diff --git a/quakec/plats.qc b/quakec/plats.qc new file mode 100644 index 0000000..b0bfa96 --- /dev/null +++ b/quakec/plats.qc @@ -0,0 +1,396 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() plat_center_touch; +void() plat_outside_touch; +void() plat_trigger_use; +void() plat_go_up; +void() plat_go_down; +void() plat_crush; +float PLAT_LOW_TRIGGER = 1; + +void() plat_spawn_inside_trigger = +{ + local entity trigger; + local vector tmin, tmax; + +// +// middle trigger +// + trigger = spawn(); + trigger.touch = plat_center_touch; + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.enemy = self; + + tmin = self.mins + '25 25 0'; + tmax = self.maxs - '25 25 -8'; + tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8); + + if (self.spawnflags & PLAT_LOW_TRIGGER) + tmax_z = tmin_z + 8; + + if (self.size_x <= 50) + { + tmin_x = (self.mins_x + self.maxs_x) / 2; + tmax_x = tmin_x + 1; + } + + if (self.size_y <= 50) + { + tmin_y = (self.mins_y + self.maxs_y) / 2; + tmax_y = tmin_y + 1; + } + + setsize (trigger, tmin, tmax); +}; + +void() plat_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + self.think = plat_go_down; + self.nextthink = self.ltime + 3; +}; + +void() plat_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() plat_go_down = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_DOWN; + SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom); +}; + +void() plat_go_up = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos1, self.speed, plat_hit_top); +}; + +void() plat_center_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + self = self.enemy; + + if (self.state == STATE_BOTTOM) + plat_go_up (); + else if (self.state == STATE_TOP) + self.nextthink = self.ltime + 1; // delay going down +}; + +void() plat_outside_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + self = self.enemy; + + if (self.state == STATE_TOP) + plat_go_down (); +}; + +void() plat_trigger_use = +{ + if (self.think) + return; // allready activated + + plat_go_down(); +}; + + +void() plat_crush = +{ + + T_Damage (other, self, self, 1); + + if (self.state == STATE_UP) + plat_go_down (); + else if (self.state == STATE_DOWN) + plat_go_up (); + else + objerror ("plat_crush: bad self.state\n"); +}; + +void() plat_use = +{ + self.use = SUB_Null; + + if (self.state != STATE_UP) + objerror ("plat_use: not in up state"); + + plat_go_down(); +}; + + +/*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. + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height. +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + + +void() func_plat = +{ + if (!self.t_length) + self.t_length = 80; + + if (!self.t_width) + self.t_width = 10; + + if (self.sounds == 0) + self.sounds = 2; +// FIX THIS TO LOAD A GENERIC PLAT SOUND + + if (self.sounds == 1) + { + precache_sound ("plats/plat1.wav"); + precache_sound ("plats/plat2.wav"); + self.noise = "plats/plat1.wav"; + self.noise1 = "plats/plat2.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("plats/medplat1.wav"); + precache_sound ("plats/medplat2.wav"); + self.noise = "plats/medplat1.wav"; + self.noise1 = "plats/medplat2.wav"; + } + + + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "func_plat"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + + self.blocked = plat_crush; + + if (!self.speed) + self.speed = 150; + + // pos1 is the top position, pos2 is the bottom + self.pos1 = self.origin; + self.pos2 = self.origin; + + if (self.height) + self.pos2_z = self.origin_z - self.height; + else + self.pos2_z = self.origin_z - self.size_z + 8; + + self.use = plat_trigger_use; + + plat_spawn_inside_trigger (); // the "start moving" trigger + + if (self.targetname != string_null) + { + self.state = STATE_UP; + self.use = plat_use; + } + else + { + setorigin (self, self.pos2); + self.state = STATE_BOTTOM; + } +}; + +//============================================================================ + +void() train_next; +void() func_train_find; + +void() train_blocked = +{ + if (time < self.attack_finished) + return; + + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; + +void() train_use = +{ + if (self.think != func_train_find) + return; // already activated + + train_next(); +}; + +void() train_wait = +{ + if (self.wait) + { + self.nextthink = self.ltime + self.wait; + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + else + self.nextthink = self.ltime + 0.1; + + self.think = train_next; +}; + +void() train_next = +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + + if (!self.target) + objerror ("train_next: no next target"); + + if (targ.wait) + self.wait = targ.wait; + else + self.wait = 0; + + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + SUB_CalcMove (targ.origin - self.mins, self.speed, train_wait); +}; + +void() func_train_find = + +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + setorigin (self, targ.origin - self.mins); + + if (!self.targetname) + { // not triggered, so start immediately + self.nextthink = self.ltime + 0.1; + self.think = train_next; + } +}; + +/*QUAKED func_train (0 .5 .8) ? +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 +sounds +1) ratchet metal + +*/ +void() func_train = +{ + if (!self.speed) + self.speed = 100; + + if (!self.target) + objerror ("func_train without a target"); + + if (!self.dmg) + self.dmg = 2; + + if (self.sounds == 0) + { + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + } + + if (self.sounds == 1) + { + self.noise = ("plats/train2.wav");//stop sound + precache_sound ("plats/train2.wav"); + self.noise1 = ("plats/train1.wav");//move sound + precache_sound ("plats/train1.wav"); + } + + self.cnt = 1; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = train_use; + self.classname = "train"; + + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + +/*QUAKED misc_teleporttrain (0 .5 .8) (-8 -8 -8) (8 8 8) +This is used for the final bos +*/ +void() misc_teleporttrain = +{ + if (!self.speed) + self.speed = 100; + + if (!self.target) + objerror ("func_train without a target"); + + self.cnt = 1; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = train_use; + self.avelocity = '100 200 300'; + + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + + precache_model2 ("progs/teleport.mdl"); + setmodel (self, "progs/teleport.mdl"); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + diff --git a/quakec/player.qc b/quakec/player.qc new file mode 100644 index 0000000..4b8cd1d --- /dev/null +++ b/quakec/player.qc @@ -0,0 +1,714 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() bubble_bob; + +/* +============================================================================== +PLAYER +============================================================================== +*/ + +$cd /raid/quake/id1/models/player_4 +$origin 0 -6 24 +$base base +$skin skin + +// +// running +// +$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6 + +$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6 + +// +// standing +// +$frame stand1 stand2 stand3 stand4 stand5 + +$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6 +$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12 + + +// +// pain +// +$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + + +// +// death +// + +$frame axdeth1 axdeth2 axdeth3 axdeth4 axdeth5 axdeth6 +$frame axdeth7 axdeth8 axdeth9 + +$frame deatha1 deatha2 deatha3 deatha4 deatha5 deatha6 deatha7 deatha8 +$frame deatha9 deatha10 deatha11 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 deathc12 deathc13 deathc14 deathc15 + +$frame deathd1 deathd2 deathd3 deathd4 deathd5 deathd6 deathd7 +$frame deathd8 deathd9 + +$frame deathe1 deathe2 deathe3 deathe4 deathe5 deathe6 deathe7 +$frame deathe8 deathe9 + +// +// attacks +// +$frame nailatt1 nailatt2 + +$frame light1 light2 + +$frame rockatt1 rockatt2 rockatt3 rockatt4 rockatt5 rockatt6 + +$frame shotatt1 shotatt2 shotatt3 shotatt4 shotatt5 shotatt6 + +$frame axatt1 axatt2 axatt3 axatt4 axatt5 axatt6 + +$frame axattb1 axattb2 axattb3 axattb4 axattb5 axattb6 + +$frame axattc1 axattc2 axattc3 axattc4 axattc5 axattc6 + +$frame axattd1 axattd2 axattd3 axattd4 axattd5 axattd6 + + +/* +============================================================================== +PLAYER +============================================================================== +*/ + +void() player_run; + +void() player_stand1 =[ $axstnd1, player_stand1 ] +{ + self.weaponframe = 0; + + if (self.velocity_x || self.velocity_y) + { + self.walkframe=0; + player_run(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe >= 12) + self.walkframe = 0; + + self.frame = $axstnd1 + self.walkframe; + } + else + { + if (self.walkframe >= 5) + self.walkframe = 0; + + self.frame = $stand1 + self.walkframe; + } + + self.walkframe = self.walkframe + 1; +}; + +void() player_run =[ $rockrun1, player_run ] +{ + self.weaponframe = 0; + + if (!self.velocity_x && !self.velocity_y) + { + self.walkframe=0; + player_stand1(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe == 6) + self.walkframe = 0; + + self.frame = $axrun1 + self.walkframe; + } + else + { + if (self.walkframe == 6) + self.walkframe = 0; + + self.frame = self.frame + self.walkframe; + } + + self.walkframe = self.walkframe + 1; +}; + + +void() player_shot1 = [$shotatt1, player_shot2 ] { + self.weaponframe = 1; + self.effects = self.effects | EF_MUZZLEFLASH; +}; + +void() player_shot2 = [$shotatt2, player_shot3 ] {self.weaponframe=2;}; +void() player_shot3 = [$shotatt3, player_shot4 ] {self.weaponframe=3;}; +void() player_shot4 = [$shotatt4, player_shot5 ] {self.weaponframe=4;}; +void() player_shot5 = [$shotatt5, player_shot6 ] {self.weaponframe=5;}; +void() player_shot6 = [$shotatt6, player_run ] {self.weaponframe=6;}; + +void() player_axe1 = [$axatt1, player_axe2 ] {self.weaponframe=1;}; +void() player_axe2 = [$axatt2, player_axe3 ] {self.weaponframe=2;}; +void() player_axe3 = [$axatt3, player_axe4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axe4 = [$axatt4, player_run ] {self.weaponframe=4;}; + +void() player_axeb1 = [$axattb1, player_axeb2 ] {self.weaponframe=5;}; +void() player_axeb2 = [$axattb2, player_axeb3 ] {self.weaponframe=6;}; +void() player_axeb3 = [$axattb3, player_axeb4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axeb4 = [$axattb4, player_run ] {self.weaponframe=8;}; + +void() player_axec1 = [$axattc1, player_axec2 ] {self.weaponframe=1;}; +void() player_axec2 = [$axattc2, player_axec3 ] {self.weaponframe=2;}; +void() player_axec3 = [$axattc3, player_axec4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axec4 = [$axattc4, player_run ] {self.weaponframe=4;}; + +void() player_axed1 = [$axattd1, player_axed2 ] {self.weaponframe=5;}; +void() player_axed2 = [$axattd2, player_axed3 ] {self.weaponframe=6;}; +void() player_axed3 = [$axattd3, player_axed4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axed4 = [$axattd4, player_run ] {self.weaponframe=8;}; + + +//============================================================================ + +void() player_nail1 =[$nailatt1, player_nail2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + + self.weaponframe = self.weaponframe + 1; + + if (self.weaponframe == 9) + self.weaponframe = 1; + + SuperDamageSound(); + W_FireSpikes (4); + self.attack_finished = time + 0.2; +}; + +void() player_nail2 =[$nailatt2, player_nail1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + + self.weaponframe = self.weaponframe + 1; + + if (self.weaponframe == 9) + self.weaponframe = 1; + + SuperDamageSound(); + W_FireSpikes (-4); + self.attack_finished = time + 0.2; +}; + +//============================================================================ + +void() player_light1 =[$light1, player_light2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + + self.weaponframe = self.weaponframe + 1; + + if (self.weaponframe == 5) + self.weaponframe = 1; + + SuperDamageSound(); + W_FireLightning(); + self.attack_finished = time + 0.2; +}; + +void() player_light2 =[$light2, player_light1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + + self.weaponframe = self.weaponframe + 1; + + if (self.weaponframe == 5) + self.weaponframe = 1; + + SuperDamageSound(); + W_FireLightning(); + self.attack_finished = time + 0.2; +}; + +//============================================================================ + + +void() player_rocket1 =[$rockatt1, player_rocket2 ] { + self.weaponframe = 1; + self.effects = self.effects | EF_MUZZLEFLASH; +}; + +void() player_rocket2 =[$rockatt2, player_rocket3 ] {self.weaponframe=2;}; +void() player_rocket3 =[$rockatt3, player_rocket4 ] {self.weaponframe=3;}; +void() player_rocket4 =[$rockatt4, player_rocket5 ] {self.weaponframe=4;}; +void() player_rocket5 =[$rockatt5, player_rocket6 ] {self.weaponframe=5;}; +void() player_rocket6 =[$rockatt6, player_run ] {self.weaponframe=6;}; +void(float num_bubbles) DeathBubbles; + +void() PainSound = +{ + local float rs; + + if (self.health < 0) + return; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + // water pain sounds + if (self.watertype == CONTENT_WATER && self.waterlevel == 3) + { + DeathBubbles(1); + + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM); + + return; + } + + // slime pain sounds + if (self.watertype == CONTENT_SLIME) + { + // FIX ME put in some steam here + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + + return; + } + + if (self.watertype == CONTENT_LAVA) + { + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + + return; + } + + if (self.pain_finished > time) + { + self.axhitme = 0; + return; + } + + self.pain_finished = time + 0.5; + + // don't make multiple pain sounds right after each other + + // ax pain sound + if (self.axhitme == 1) + { + self.axhitme = 0; + sound (self, CHAN_VOICE, "player/axhit1.wav", 1, ATTN_NORM); + return; + } + + + rs = rint((random() * 5) + 1); + + self.noise = string_null; + + if (rs == 1) + self.noise = "player/pain1.wav"; + else if (rs == 2) + self.noise = "player/pain2.wav"; + else if (rs == 3) + self.noise = "player/pain3.wav"; + else if (rs == 4) + self.noise = "player/pain4.wav"; + else if (rs == 5) + self.noise = "player/pain5.wav"; + else + self.noise = "player/pain6.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + return; +}; + +void() player_pain1 = [ $pain1, player_pain2 ] {PainSound();self.weaponframe=0;}; +void() player_pain2 = [ $pain2, player_pain3 ] {}; +void() player_pain3 = [ $pain3, player_pain4 ] {}; +void() player_pain4 = [ $pain4, player_pain5 ] {}; +void() player_pain5 = [ $pain5, player_pain6 ] {}; +void() player_pain6 = [ $pain6, player_run ] {}; + +void() player_axpain1 = [ $axpain1, player_axpain2 ] {PainSound();self.weaponframe=0;}; +void() player_axpain2 = [ $axpain2, player_axpain3 ] {}; +void() player_axpain3 = [ $axpain3, player_axpain4 ] {}; +void() player_axpain4 = [ $axpain4, player_axpain5 ] {}; +void() player_axpain5 = [ $axpain5, player_axpain6 ] {}; +void() player_axpain6 = [ $axpain6, player_run ] {}; + +void(entity attacker, float damage) player_pain = +{ + if (self.weaponframe) + return; + + if (self.invisible_finished > time) + return; // eyes don't have pain frames + + if (self.weapon == IT_AXE) + player_axpain1 (); + else + player_pain1 (); +}; + +void() player_diea1; +void() player_dieb1; +void() player_diec1; +void() player_died1; +void() player_diee1; +void() player_die_ax1; + +void() DeathBubblesSpawn = +{ + local entity bubble; + + if (self.owner.waterlevel != 3) + return; + + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.owner.origin + '0 0 24'); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + 0.1; + self.think = DeathBubblesSpawn; + self.air_finished = self.air_finished + 1; + + if (self.air_finished >= self.bubble_count) + remove(self); +}; + +void(float num_bubbles) DeathBubbles = +{ + local entity bubble_spawner; + + bubble_spawner = spawn(); + setorigin (bubble_spawner, self.origin); + bubble_spawner.movetype = MOVETYPE_NONE; + bubble_spawner.solid = SOLID_NOT; + bubble_spawner.nextthink = time + 0.1; + bubble_spawner.think = DeathBubblesSpawn; + bubble_spawner.air_finished = 0; + bubble_spawner.owner = self; + bubble_spawner.bubble_count = num_bubbles; + return; +}; + + +void() DeathSound = +{ + local float rs; + + // water death sounds + if (self.waterlevel == 3) + { + DeathBubbles(20); + sound (self, CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + + rs = rint ((random() * 4) + 1); + + if (rs == 1) + self.noise = "player/death1.wav"; + + if (rs == 2) + self.noise = "player/death2.wav"; + + if (rs == 3) + self.noise = "player/death3.wav"; + + if (rs == 4) + self.noise = "player/death4.wav"; + + if (rs == 5) + self.noise = "player/death5.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NONE); + return; +}; + + +void() PlayerDead = +{ + self.nextthink = -1; + self.deadflag = DEAD_DEAD; // allow respawn after a certain time +}; + +vector(float dm) VelocityForDamage = +{ + local vector v; + + v_x = 100 * crandom(); + v_y = 100 * crandom(); + v_z = 200 + 100 * random(); + + if (dm > -50) + { + v = v * 0.7; + } + else if (dm > -200) + { + v = v * 2; + } + else + v = v * 10; + + return v; +}; + +void(string gibname, float dm) ThrowGib = +{ + local entity new; + + new = spawn(); + new.origin = self.origin; + setmodel (new, gibname); + setsize (new, '0 0 0', '0 0 0'); + new.velocity = VelocityForDamage (dm); + new.movetype = MOVETYPE_GIB; + new.solid = SOLID_NOT; + new.avelocity_x = random()*600; + new.avelocity_y = random()*600; + new.avelocity_z = random()*600; + new.think = SUB_Remove; + new.ltime = time; + new.nextthink = time + 10 + random()*10; + new.frame = 0; + new.flags = 0; +}; + +void(string gibname, float dm) ThrowHead = +{ + setmodel (self, gibname); + self.frame = 0; + self.nextthink = -1; + self.movetype = MOVETYPE_GIB; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.view_ofs = '0 0 8'; + setsize (self, '-16 -16 0', '16 16 56'); + self.velocity = VelocityForDamage (dm); + self.origin_z = self.origin_z - 24; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.avelocity = crandom() * '0 600 0'; +}; + +void() GibPlayer = +{ + ThrowHead ("progs/h_player.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + + self.deadflag = DEAD_DEAD; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (damage_attacker.classname == "teledeath2") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (random() < 0.5) + sound (self, CHAN_VOICE, "player/gib.wav", 1, ATTN_NONE); + else + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NONE); +}; + +void() PlayerDie = +{ + local float i; + + self.items = self.items - (self.items & ( IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD ) ); + self.invisible_finished = 0; // don't die as eyes + self.invincible_finished = 0; + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.effects = 0; + self.modelindex = modelindex_player; // don't use eyes + + if (deathmatch || coop) + DropBackpack(); + + self.weaponmodel = string_null; + self.view_ofs = '0 0 -8'; + self.deadflag = DEAD_DYING; + self.solid = SOLID_NOT; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.movetype = MOVETYPE_TOSS; + + if (self.velocity_z < 10) + self.velocity_z = self.velocity_z + random()*300; + + if (self.health < -40) + { + GibPlayer (); + return; + } + + DeathSound(); + + self.angles_x = 0; + self.angles_z = 0; + + if (self.weapon == IT_AXE) + { + player_die_ax1 (); + return; + } + + i = ( 1 + floor( random() * 6 ) ); + + if (i == 1) + player_diea1(); + else if (i == 2) + player_dieb1(); + else if (i == 3) + player_diec1(); + else if (i == 4) + player_died1(); + else + player_diee1(); +}; + +void() set_suicide_frame = +{ // used by klill command and diconnect command + + if (self.model != "progs/player.mdl") + return; // allready gibbed + + self.frame = $deatha11; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_TOSS; + self.deadflag = DEAD_DEAD; + self.nextthink = -1; +}; + + +void() player_diea1 = [ $deatha1, player_diea2 ] {}; +void() player_diea2 = [ $deatha2, player_diea3 ] {}; +void() player_diea3 = [ $deatha3, player_diea4 ] {}; +void() player_diea4 = [ $deatha4, player_diea5 ] {}; +void() player_diea5 = [ $deatha5, player_diea6 ] {}; +void() player_diea6 = [ $deatha6, player_diea7 ] {}; +void() player_diea7 = [ $deatha7, player_diea8 ] {}; +void() player_diea8 = [ $deatha8, player_diea9 ] {}; +void() player_diea9 = [ $deatha9, player_diea10 ] {}; +void() player_diea10 = [ $deatha10, player_diea11 ] {}; +void() player_diea11 = [ $deatha11, player_diea11 ] {PlayerDead();}; + +void() player_dieb1 = [ $deathb1, player_dieb2 ] {}; +void() player_dieb2 = [ $deathb2, player_dieb3 ] {}; +void() player_dieb3 = [ $deathb3, player_dieb4 ] {}; +void() player_dieb4 = [ $deathb4, player_dieb5 ] {}; +void() player_dieb5 = [ $deathb5, player_dieb6 ] {}; +void() player_dieb6 = [ $deathb6, player_dieb7 ] {}; +void() player_dieb7 = [ $deathb7, player_dieb8 ] {}; +void() player_dieb8 = [ $deathb8, player_dieb9 ] {}; +void() player_dieb9 = [ $deathb9, player_dieb9 ] {PlayerDead();}; + +void() player_diec1 = [ $deathc1, player_diec2 ] {}; +void() player_diec2 = [ $deathc2, player_diec3 ] {}; +void() player_diec3 = [ $deathc3, player_diec4 ] {}; +void() player_diec4 = [ $deathc4, player_diec5 ] {}; +void() player_diec5 = [ $deathc5, player_diec6 ] {}; +void() player_diec6 = [ $deathc6, player_diec7 ] {}; +void() player_diec7 = [ $deathc7, player_diec8 ] {}; +void() player_diec8 = [ $deathc8, player_diec9 ] {}; +void() player_diec9 = [ $deathc9, player_diec10 ] {}; +void() player_diec10 = [ $deathc10, player_diec11 ] {}; +void() player_diec11 = [ $deathc11, player_diec12 ] {}; +void() player_diec12 = [ $deathc12, player_diec13 ] {}; +void() player_diec13 = [ $deathc13, player_diec14 ] {}; +void() player_diec14 = [ $deathc14, player_diec15 ] {}; +void() player_diec15 = [ $deathc15, player_diec15 ] {PlayerDead();}; + +void() player_died1 = [ $deathd1, player_died2 ] {}; +void() player_died2 = [ $deathd2, player_died3 ] {}; +void() player_died3 = [ $deathd3, player_died4 ] {}; +void() player_died4 = [ $deathd4, player_died5 ] {}; +void() player_died5 = [ $deathd5, player_died6 ] {}; +void() player_died6 = [ $deathd6, player_died7 ] {}; +void() player_died7 = [ $deathd7, player_died8 ] {}; +void() player_died8 = [ $deathd8, player_died9 ] {}; +void() player_died9 = [ $deathd9, player_died9 ] {PlayerDead();}; + +void() player_diee1 = [ $deathe1, player_diee2 ] {}; +void() player_diee2 = [ $deathe2, player_diee3 ] {}; +void() player_diee3 = [ $deathe3, player_diee4 ] {}; +void() player_diee4 = [ $deathe4, player_diee5 ] {}; +void() player_diee5 = [ $deathe5, player_diee6 ] {}; +void() player_diee6 = [ $deathe6, player_diee7 ] {}; +void() player_diee7 = [ $deathe7, player_diee8 ] {}; +void() player_diee8 = [ $deathe8, player_diee9 ] {}; +void() player_diee9 = [ $deathe9, player_diee9 ] {PlayerDead();}; + +void() player_die_ax1 = [ $axdeth1, player_die_ax2 ] {}; +void() player_die_ax2 = [ $axdeth2, player_die_ax3 ] {}; +void() player_die_ax3 = [ $axdeth3, player_die_ax4 ] {}; +void() player_die_ax4 = [ $axdeth4, player_die_ax5 ] {}; +void() player_die_ax5 = [ $axdeth5, player_die_ax6 ] {}; +void() player_die_ax6 = [ $axdeth6, player_die_ax7 ] {}; +void() player_die_ax7 = [ $axdeth7, player_die_ax8 ] {}; +void() player_die_ax8 = [ $axdeth8, player_die_ax9 ] {}; +void() player_die_ax9 = [ $axdeth9, player_die_ax9 ] {PlayerDead();}; diff --git a/quakec/progs.src b/quakec/progs.src new file mode 100644 index 0000000..06bc686 --- /dev/null +++ b/quakec/progs.src @@ -0,0 +1,38 @@ +../progs.dat + +defs.qc +subs.qc +fight.qc +ai.qc +combat.qc +itemnames.qc +items.qc +weapons.qc +world.qc +client.qc +player.qc +monsters.qc +doors.qc +buttons.qc +triggers.qc +plats.qc +misc.qc + +monsters/ogre.qc +monsters/fiend.qc +monsters/shambler.qc +monsters/knight.qc +monsters/grunt.qc +monsters/scrag.qc +monsters/rottweiler.qc +monsters/zombie.qc +monsters/chthon.qc + +monsters/spawn.qc // registered +monsters/hellknight.qc // registered +monsters/rotfish.qc // registered +monsters/vore.qc // registered +monsters/enforcer.qc // registered +monsters/shub.qc // registered + +bots/bot.qc // bot support diff --git a/quakec/subs.qc b/quakec/subs.qc new file mode 100644 index 0000000..f3777fc --- /dev/null +++ b/quakec/subs.qc @@ -0,0 +1,332 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() SUB_Null = {}; + +void() SUB_Remove = {remove(self);}; + + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void() SetMovedir = +{ + if (self.angles == '0 -1 0') + self.movedir = '0 0 1'; + else if (self.angles == '0 -2 0') + self.movedir = '0 0 -1'; + else + { + makevectorsfixed(self.angles); + self.movedir = v_forward; + } + + self.angles = '0 0 0'; +}; + +/* +================ +InitTrigger +================ +*/ +void() InitTrigger = +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (self.angles != '0 0 0') + SetMovedir (); + + self.solid = SOLID_TRIGGER; + setmodel (self, self.model); // set size and link into world + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + self.model = string_null; +}; + +/* +============= +SUB_CalcMove + +calculate self.velocity and self.nextthink to reach dest from +self.origin traveling at speed +=============== +*/ +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt = +{ + local entity stemp; + stemp = self; + self = ent; + + SUB_CalcMove (tdest, tspeed, func); + self = stemp; +}; + +void(vector tdest, float tspeed, void() func) SUB_CalcMove = +{ + local vector vdestdelta; + local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + + self.think1 = func; + self.finaldest = tdest; + self.think = SUB_CalcMoveDone; + + if (tdest == self.origin) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + + // set destdelta to the vector needed to move + vdestdelta = tdest - self.origin; + + // calculate length of vector + len = vlen (vdestdelta); + + // divide by speed to get time to reach dest + traveltime = len / tspeed; + + if (traveltime < 0.1) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + + // set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + + // scale the destdelta vector by the time spent traveling to get velocity + self.velocity = vdestdelta * (1/traveltime); // qcc won't take vec/float +}; + +/* +============ +After moving, set origin to exact final destination +============ +*/ +void() SUB_CalcMoveDone = +{ + setorigin(self, self.finaldest); + self.velocity = '0 0 0'; + self.nextthink = -1; + + if (self.think1) + self.think1(); +}; + + +/* +============= +SUB_CalcAngleMove + +calculate self.avelocity and self.nextthink to reach destangle from +self.angles rotating + +The calling function should make sure self.think is valid +=============== +*/ +void(entity ent, vector destangle, float tspeed, void() func) SUB_CalcAngleMoveEnt = +{ + local entity stemp; + stemp = self; + self = ent; + SUB_CalcAngleMove (destangle, tspeed, func); + self = stemp; +}; + +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove = +{ + local vector destdelta; + local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + + // set destdelta to the vector needed to move + destdelta = destangle - self.angles; + + // calculate length of vector + len = vlen (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / tspeed; + + // set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + + // scale the destdelta vector by the time spent traveling to get velocity + self.avelocity = destdelta * (1 / traveltime); + + self.think1 = func; + self.finalangle = destangle; + self.think = SUB_CalcAngleMoveDone; +}; + +/* +============ +After rotating, set angle to exact final angle +============ +*/ +void() SUB_CalcAngleMoveDone = +{ + self.angles = self.finalangle; + self.avelocity = '0 0 0'; + self.nextthink = -1; + + if (self.think1) + self.think1(); +}; + + +//============================================================================= + +void() DelayThink = +{ + activator = self.enemy; + SUB_UseTargets (); + remove(self); +}; + +/* +============================== +SUB_UseTargets + +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. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void() SUB_UseTargets = +{ + local entity t, act, stemp, otemp; + +// +// check for a delay +// + if (self.delay) + { + // create a temp object to fire at a later time + t = spawn(); + t.classname = "DelayedUse"; + t.nextthink = time + self.delay; + t.think = DelayThink; + t.enemy = activator; + t.message = self.message; + t.killtarget = self.killtarget; + t.target = self.target; + return; + } + + +// +// print the message +// + if (activator.classname == "player" && self.message != string_null) + { + centerprint (activator, self.message); + + if (!self.noise) + sound (activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + + // + // kill the killtagets + // + if (self.killtarget != string_null) + { + t = find (world, targetname, self.killtarget); + + while( t ) + { + remove (t); + t = find (t, targetname, self.killtarget); + } + } + + // + // fire targets + // + if (self.target != string_null) + { + act = activator; + t = find (world, targetname, self.target); + while( t ) + { + stemp = self; + otemp = other; + self = t; + other = stemp; + if (self.use != SUB_Null) + { + if (self.use) + self.use (); + } + self = stemp; + other = otemp; + activator = act; + t = find (t, targetname, self.target); + } + } +}; + +/* +in nightmare mode, all attack_finished times become 0 +some monsters refire twice automatically +update: not anymore! it makes nightmare too easy +*/ + +void(float normal) SUB_AttackFinished = +{ + self.cnt = 0; // refire count for nightmare + + //if (skill != 3) + self.attack_finished = time + normal; +}; + +float (entity targ) visible; + +void (void() thinkst) SUB_CheckRefire = +{ + if (skill != 3) + return; + + if (self.cnt == 1) + return; + + if (!visible (self.enemy)) + return; + + self.cnt = 1; + self.think = thinkst; +}; diff --git a/quakec/triggers.qc b/quakec/triggers.qc new file mode 100644 index 0000000..64a0fb6 --- /dev/null +++ b/quakec/triggers.qc @@ -0,0 +1,697 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +entity s; + + +void() trigger_reactivate = +{ + self.solid = SOLID_TRIGGER; +}; + +//============================================================================= + +float SPAWNFLAG_NOMESSAGE = 1; +float SPAWNFLAG_NOTOUCH = 1; + +// the wait time has passed, so set back up for another activation +void() multi_wait = +{ + if (self.max_health) + { + self.health = self.max_health; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + } +}; + + +// the trigger was just touched/killed/used +// self.enemy 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 = +{ + if (self.nextthink > time) + { + return; // allready been triggered + } + + if (self.classname == "trigger_secret") + { + if (self.enemy.classname != "player") + return; + found_secrets = found_secrets + 1; + WriteByte (MSG_ALL, SVC_FOUNDSECRET); + + msg_entity = self.enemy; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_FIND_SECRET"); + } + + if (self.noise != string_null) + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + +// don't trigger again until reset + self.takedamage = DAMAGE_NO; + + activator = self.enemy; + + SUB_UseTargets(); + + if (self.wait > 0) + { + self.think = multi_wait; + self.nextthink = time + self.wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called wheil C code is looping through area links... + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + } + + if (activator.classname == "player" && world.model == "maps/e2m3.bsp" && self.message == "$map_dopefish") + { + msg_entity = activator; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_FIND_DOPEFISH"); + } +}; + +void() multi_killed = +{ + self.enemy = damage_attacker; + multi_trigger(); +}; + +void() multi_use = +{ + self.enemy = activator; + multi_trigger(); +}; + +void() multi_touch = +{ + if (other.classname != "player") + return; + + // if the trigger has an angles field, check player's facing direction + if (self.movedir != '0 0 0') + { + makevectorsfixed(other.angles); + if (v_forward * self.movedir < 0) + return; // not facing the right way + } + + self.enemy = other; + multi_trigger (); +}; + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_multiple = +{ + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + else if (self.sounds == 3) + { + precache_sound ("misc/trigger1.wav"); + self.noise = "misc/trigger1.wav"; + } + + if (!self.wait) + self.wait = 0.2; + + self.use = multi_use; + + InitTrigger (); + + if (self.health) + { + if (self.spawnflags & SPAWNFLAG_NOTOUCH) + objerror ("health and notouch don't make sense\n"); + + self.max_health = self.health; + self.th_die = multi_killed; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + setorigin (self, self.origin); // make sure it links into the world + } + else + { + if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) ) + { + self.touch = multi_touch; + } + } +}; + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. 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 "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_once = +{ + self.wait = -1; + trigger_multiple(); +}; + +//============================================================================= + +/*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. It can contain killtargets, targets, delays, and messages. +*/ +void() trigger_relay = +{ + self.use = SUB_UseTargets; +}; + + +//============================================================================= + +/*QUAKED trigger_secret (.5 .5 .5) ? +secret counter trigger +sounds +1) secret +2) beep beep +3) +4) +set "message" to text string +*/ +void() trigger_secret = +{ + total_secrets = total_secrets + 1; + self.wait = -1; + + if (!self.message) + self.message = "$qc_found_secret"; + + if (!self.sounds) + self.sounds = 1; + + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + + trigger_multiple (); +}; + +//============================================================================= + + +void() counter_use = +{ + self.count = self.count - 1; + + if (self.count < 0) + return; + + if (self.count != 0) + { + if (activator.classname == "player" && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + { + if (self.count >= 4) + centerprint (activator, "$qc_more_go"); + else if (self.count == 3) + centerprint (activator, "$qc_three_more"); + else if (self.count == 2) + centerprint (activator, "$qc_two_more"); + else + centerprint (activator, "$qc_one_more"); + } + + return; + } + + if (activator.classname == "player" && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + centerprint(activator, "$qc_sequence_completed"); + + self.enemy = activator; + multi_trigger (); +}; + +/*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 = +{ + self.wait = -1; + + if (!self.count) + self.count = 2; + + self.use = counter_use; +}; + + +/* +============================================================================== + +TELEPORT TRIGGERS + +============================================================================== +*/ + +float PLAYER_ONLY = 1; +float SILENT = 2; + +void() play_teleport = +{ + local float v; + local string tmpstr; + + v = random() * 5; + + if (v < 1) + tmpstr = "misc/r_tele1.wav"; + else if (v < 2) + tmpstr = "misc/r_tele2.wav"; + else if (v < 3) + tmpstr = "misc/r_tele3.wav"; + else if (v < 4) + tmpstr = "misc/r_tele4.wav"; + else + tmpstr = "misc/r_tele5.wav"; + + sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM); + remove (self); +}; + +void(vector org) spawn_tfog = +{ + s = spawn (); + s.origin = org; + s.nextthink = time + 0.2; + s.think = play_teleport; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); +}; + + +void() tdeath_touch = +{ + if (other == self.owner) + return; + +// frag anyone who teleports in on top of an invincible player + if (other.classname == "player") + { + if (other.invincible_finished > time) + self.classname = "teledeath2"; + + if (self.owner.classname != "player") + { // other monsters explode themselves + T_Damage (self.owner, self, self, 50000); + return; + } + + } + + if (other.health) + { + T_Damage (other, self, self, 50000); + } +}; + + +void(vector org, entity death_owner) spawn_tdeath = +{ + local entity death; + + death = spawn(); + death.classname = "teledeath"; + death.movetype = MOVETYPE_NONE; + death.solid = SOLID_TRIGGER; + death.angles = '0 0 0'; + setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1'); + setorigin (death, org); + death.touch = tdeath_touch; + death.nextthink = time + 0.2; + death.think = SUB_Remove; + death.owner = death_owner; + + force_retouch = 2; // make sure even still objects get hit +}; + +void() teleport_touch = +{ + local entity t; + local vector org; + + if (self.targetname != string_null) + { + if (self.nextthink < time) + { + return; // not fired yet + } + } + + if (self.spawnflags & PLAYER_ONLY) + { + if (other.classname != "player") + return; + } + + // only teleport living creatures + if (other.health <= 0 || other.solid != SOLID_SLIDEBOX) + return; + + SUB_UseTargets (); + + // put a tfog where the player was + spawn_tfog (other.origin); + + t = find (world, targetname, self.target); + + if (!t) + objerror ("couldn't find target"); + + // spawn a tfog flash in front of the destination + makevectors (t.mangle); + org = t.origin + 32 * v_forward; + + spawn_tfog (org); + spawn_tdeath(t.origin, other); + + // move the player and lock him down for a little while + if (!other.health) + { + other.origin = t.origin; + other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y); + return; + } + + setorigin (other, t.origin); + other.angles = t.mangle; + + if (other.classname == "player") + { + other.fixangle = 1; // turn this way immediately + other.teleport_time = time + 0.7; + + if (other.flags & FL_ONGROUND) + other.flags = other.flags - FL_ONGROUND; + + other.velocity = v_forward * 300; + } + + other.flags = other.flags - other.flags & FL_ONGROUND; +}; + +/*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32) +This is the destination marker for a teleporter. It should have a "targetname" field with the same value as a teleporter's "target" field. +*/ +void() info_teleport_destination = +{ + // this does nothing, just serves as a target spot + self.mangle = self.angles; + self.angles = '0 0 0'; + self.model = string_null; + self.origin = self.origin + '0 0 27'; + + if (!self.targetname) + objerror ("no targetname"); +}; + +void() teleport_use = +{ + self.nextthink = time + 0.2; + force_retouch = 2; // make sure even still objects get hit + self.think = SUB_Null; +}; + +/*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT +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. +*/ +void() trigger_teleport = +{ + local vector o; + + InitTrigger (); + self.touch = teleport_touch; + + // find the destination + if (!self.target) + objerror ("no target"); + self.use = teleport_use; + + self.netname = "trigger_teleport"; // lets us ID the entity in C code... + + if (!(self.spawnflags & SILENT)) + { + precache_sound ("ambience/hum1.wav"); + o = (self.mins + self.maxs)*0.5; + ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC); + } +}; + +/* +============================================================================== + +trigger_setskill + +============================================================================== +*/ + +void() trigger_skill_touch = +{ + if (other.classname != "player") + return; + + cvar_set ("skill", self.message); +}; + +/*QUAKED trigger_setskill (.5 .5 .5) ? +sets skill level to the value of "message". +Only used on start map. +*/ +void() trigger_setskill = +{ + InitTrigger (); + self.touch = trigger_skill_touch; +}; + + +/* +============================================================================== + +ONLY REGISTERED TRIGGERS + +============================================================================== +*/ + +void() trigger_onlyregistered_touch = +{ + if (other.classname != "player") + return; + + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + + if (cvar("registered")) + { + self.message = string_null; + SUB_UseTargets (); + remove (self); + } + else + { + if (self.message != string_null) + { + centerprint (other, self.message); + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } + } +}; + +/*QUAKED trigger_onlyregistered (.5 .5 .5) ? +Only fires if playing the registered version, otherwise prints the message +*/ +void() trigger_onlyregistered = +{ + precache_sound ("misc/talk.wav"); + InitTrigger (); + self.touch = trigger_onlyregistered_touch; +}; + +//============================================================================ + +void() hurt_on = +{ + self.solid = SOLID_TRIGGER; + self.nextthink = -1; +}; + +void() hurt_touch = +{ + if (other.takedamage) + { + self.solid = SOLID_NOT; + T_Damage (other, self, self, self.dmg); + self.think = hurt_on; + self.nextthink = time + 1; + } + + return; +}; + +/*QUAKED trigger_hurt (.5 .5 .5) ? +Any object touching this will be hurt +set dmg to damage amount +defalt dmg = 5 +*/ +void() trigger_hurt = +{ + InitTrigger (); + self.touch = hurt_touch; + + if (!self.dmg) + self.dmg = 5; +}; + +//============================================================================ + +float PUSH_ONCE = 1; + +void() trigger_push_touch = +{ + if (other.classname == "grenade") + other.velocity = self.speed * self.movedir * 10; + else if (other.health > 0) + { + other.velocity = self.speed * self.movedir * 10; + + if (other.classname == "player") + { + if (other.fly_sound < time) + { + other.fly_sound = time + 1.5; + sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); + } + } + } + + if (self.spawnflags & PUSH_ONCE) + remove(self); +}; + + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE +Pushes the player +*/ +void() trigger_push = +{ + InitTrigger (); + precache_sound ("ambience/windfly.wav"); + self.touch = trigger_push_touch; + + self.netname = "trigger_push"; // lets us ID the entity in C code... + + if (!self.speed) + self.speed = 1000; +}; + +//============================================================================ + +void() trigger_monsterjump_touch = +{ + if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER ) + return; + + // set XY even if not on ground, so the jump will clear lips + other.velocity_x = self.movedir_x * self.speed; + other.velocity_y = self.movedir_y * self.speed; + + if ( !(other.flags & FL_ONGROUND) ) + return; + + other.flags = other.flags - FL_ONGROUND; + + other.velocity_z = self.height; +}; + +/*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 = +{ + if (!self.speed) + self.speed = 200; + + if (!self.height) + self.height = 200; + + if (self.angles == '0 0 0') + self.angles = '0 360 0'; + + InitTrigger (); + self.touch = trigger_monsterjump_touch; +}; + diff --git a/quakec/weapons.qc b/quakec/weapons.qc new file mode 100644 index 0000000..b5d6be6 --- /dev/null +++ b/quakec/weapons.qc @@ -0,0 +1,1362 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void (entity targ, entity inflictor, entity attacker, float damage) T_Damage; +void () player_run; +void(entity bomb, entity attacker, float rad, entity ignore) T_RadiusDamage; +void(vector org, vector vel, float damage) SpawnBlood; +void() SuperDamageSound; + + +// called by worldspawn +void() W_Precache = +{ + precache_sound ("weapons/r_exp3.wav"); // new rocket explosion + precache_sound ("weapons/rocket1i.wav"); // spike gun + precache_sound ("weapons/sgun1.wav"); + precache_sound ("weapons/guncock.wav"); // player shotgun + precache_sound ("weapons/ric1.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric2.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric3.wav"); // ricochet (used in c code) + precache_sound ("weapons/spike2.wav"); // super spikes + precache_sound ("weapons/tink1.wav"); // spikes tink (used in c code) + precache_sound ("weapons/grenade.wav"); // grenade launcher + precache_sound ("weapons/bounce.wav"); // grenade bounce + precache_sound ("weapons/shotgn2.wav"); // super shotgun +}; + +float() crandom = +{ + return 2*(random() - 0.5); +}; + +/* +================ +W_FireAxe +================ +*/ +void() W_FireAxe = +{ + local vector source; + local vector org; + + makevectors (self.v_angle); + source = self.origin + '0 0 16'; + traceline (source, source + v_forward*64, FALSE, self); + if (trace_fraction == 1.0) + return; + + org = trace_endpos - v_forward*4; + + if (trace_ent.takedamage) + { + trace_ent.axhitme = 1; + SpawnBlood (org, '0 0 0', 20); + T_Damage (trace_ent, self, self, 20); + } + else + { // hit wall + sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/player/axhit2.bnvib"); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + + +//============================================================================ + + +vector() wall_velocity = +{ + local vector vel; + + vel = normalize (self.velocity); + vel = normalize(vel + v_up*(random()- 0.5) + v_right*(random()- 0.5)); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + return vel; +}; + + +/* +================ +SpawnMeatSpray +================ +*/ +void(vector org, vector vel) SpawnMeatSpray = +{ + local entity missile; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_NOT; + + makevectorsfixed(self.angles); + + missile.velocity = vel; + missile.velocity_z = missile.velocity_z + 250 + 50*random(); + + missile.avelocity = '3000 1000 2000'; + + // set missile duration + missile.nextthink = time + 1; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + +/* +================ +SpawnBlood +================ +*/ +void(vector org, vector vel, float damage) SpawnBlood = +{ + particle (org, vel*0.1, 73, damage*2); +}; + +/* +================ +spawn_touchblood +================ +*/ +void(float damage) spawn_touchblood = +{ + local vector vel; + + vel = wall_velocity () * 0.2; + SpawnBlood (self.origin + vel*0.01, vel, damage); +}; + + +/* +================ +SpawnChunk +================ +*/ +void(vector org, vector vel) SpawnChunk = +{ + particle (org, vel*0.02, 0, 10); +}; + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +entity multi_ent; +float multi_damage; + +void() ClearMultiDamage = +{ + multi_ent = world; + multi_damage = 0; +}; + +void() ApplyMultiDamage = +{ + if (!multi_ent) + return; + + T_Damage (multi_ent, self, self, multi_damage); +}; + +void(entity hit, float damage) AddMultiDamage = +{ + if (!hit) + return; + + if (hit != multi_ent) + { + ApplyMultiDamage (); + multi_damage = damage; + multi_ent = hit; + } + else + multi_damage = multi_damage + damage; +}; + +/* +============================================================================== + +BULLETS + +============================================================================== +*/ + +/* +================ +TraceAttack +================ +*/ +void(float damage, vector dir) TraceAttack = +{ + local vector vel, org; + + vel = normalize(dir + v_up*crandom() + v_right*crandom()); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + org = trace_endpos - dir*4; + + if (trace_ent.takedamage) + { + SpawnBlood (org, vel*0.2, damage); + AddMultiDamage (trace_ent, damage); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + +/* +================ +FireBullets + +Used by shotgun, super shotgun, and enemy soldier firing +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void(float shotcount, vector dir, vector spread) FireBullets = +{ + local vector direction; + local vector src; + + makevectors(self.v_angle); + + src = self.origin + v_forward*10; + src_z = self.absmin_z + self.size_z * 0.7; + + ClearMultiDamage (); + while (shotcount > 0) + { + direction = dir + crandom()*spread_x*v_right + crandom()*spread_y*v_up; + + traceline (src, src + direction*2048, FALSE, self); + if (trace_fraction != 1.0) + TraceAttack (4, direction); + + shotcount = shotcount - 1; + } + ApplyMultiDamage (); +}; + +/* +================ +W_FireShotgun +================ +*/ +void() W_FireShotgun = +{ + local vector dir; + + sound (self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/guncock.bnvib"); + + self.punchangle_x = -2; + + self.currentammo = self.ammo_shells = self.ammo_shells - 1; + dir = aim (self, 100000); + FireBullets (6, dir, '0.04 0.04 0'); +}; + + +/* +================ +W_FireSuperShotgun +================ +*/ +void() W_FireSuperShotgun = +{ + local vector dir; + + if (self.currentammo == 1) + { + W_FireShotgun (); + return; + } + + sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/shotgn2.bnvib"); + + self.punchangle_x = -4; + + self.currentammo = self.ammo_shells = self.ammo_shells - 2; + dir = aim (self, 100000); + FireBullets (14, dir, '0.14 0.08 0'); +}; + + +/* +============================================================================== + +ROCKETS + +============================================================================== +*/ + +void() s_explode1 = [0, s_explode2] {}; +void() s_explode2 = [1, s_explode3] {}; +void() s_explode3 = [2, s_explode4] {}; +void() s_explode4 = [3, s_explode5] {}; +void() s_explode5 = [4, s_explode6] {}; +void() s_explode6 = [5, SUB_Remove] {}; + +void() BecomeExplosion = +{ + self.movetype = MOVETYPE_NONE; + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +void() T_MissileTouch = +{ + local float damg; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + damg = 100 + random()*20; + + if (other.health) + { + if (other.classname == "monster_shambler") + damg = damg * 0.5; // mostly immune + T_Damage (other, self, self.owner, damg ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact + T_RadiusDamage (self, self.owner, 120, other); + + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + + + +/* +================ +W_FireRocket +================ +*/ +void() W_FireRocket = +{ + local entity missile; + + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/sgun1.bnvib"); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.solid = SOLID_BBOX; + missile.classname = "missile"; + + // set missile speed + makevectors (self.v_angle); + missile.velocity = aim(self, 1000); + missile.velocity = missile.velocity * 1000; + missile.angles = vectoangles(missile.velocity); + + missile.touch = T_MissileTouch; + + // set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/missile.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + v_forward*8 + '0 0 16'); +}; + +/* +=============================================================================== + +LIGHTNING + +=============================================================================== +*/ + +/* +================= +LightningDamage +================= +*/ +void(vector p1, vector p2, entity from, float damage) LightningDamage = +{ + local entity e1, e2; + local vector f; + + f = p2 - p1; + normalize (f); + f_x = 0 - f_y; + f_y = f_x; + f_z = 0; + f = f*16; + + e1 = e2 = world; + + traceline (p1, p2, FALSE, self); + if (trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + if (self.classname == "player") + { + if (other.classname == "player") + trace_ent.velocity_z = trace_ent.velocity_z + 400; + } + } + + e1 = trace_ent; + + traceline (p1 + f, p2 + f, FALSE, self); + if (trace_ent != e1 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } + + e2 = trace_ent; + + traceline (p1 - f, p2 - f, FALSE, self); + if (trace_ent != e1 && trace_ent != e2 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } +}; + + +void() W_FireLightning = +{ + local vector org; + local float cells; + + if (self.ammo_cells < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + + // explode if under water + if (self.waterlevel > 1) + { + cells = self.ammo_cells; + self.ammo_cells = 0; + W_SetCurrentAmmo (); + T_RadiusDamage (self, self, 35*cells, world); + return; + } + + if (self.t_width < time) + { + sound (self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/lhit.bnvib"); + self.t_width = time + 0.6; + } + + self.punchangle_x = -2; + self.currentammo = self.ammo_cells = self.ammo_cells - 1; + org = self.origin + '0 0 16'; + + traceline (org, org + v_forward*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + LightningDamage (self.origin, trace_endpos + v_forward*4, self, 30); +}; + + +//============================================================================= + + +void() GrenadeExplode = +{ + T_RadiusDamage (self, self.owner, 120, world); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +void() GrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage == DAMAGE_AIM) + { + GrenadeExplode(); + return; + } + + sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +/* +================ +W_FireGrenade +================ +*/ +void() W_FireGrenade = +{ + local entity missile; + + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/grenade.bnvib"); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + missile.classname = "grenade"; + +// set missile speed + + makevectors (self.v_angle); + + if (self.v_angle_x) + missile.velocity = v_forward*600 + v_up * 200 + crandom()*v_right*10 + crandom()*v_up*10; + else + { + missile.velocity = aim(self, 10000); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + } + + missile.avelocity = '300 300 300'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = GrenadeTouch; + + // set missile duration + missile.nextthink = time + 2.5; + missile.think = GrenadeExplode; + + setmodel (missile, "progs/grenade.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); +}; + + +//============================================================================= + +void() spike_touch; +void() superspike_touch; + + +/* +=============== +launch_spike + +Used for both the player and the ogre +=============== +*/ +void(vector org, vector dir) launch_spike = +{ + newmis = spawn (); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + + newmis.angles = vectoangles(dir); + + newmis.touch = spike_touch; + newmis.classname = "spike"; + newmis.think = SUB_Remove; + newmis.nextthink = time + 6; + setmodel (newmis, "progs/spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + setorigin (newmis, org); + + newmis.velocity = dir * 1000; +}; + +void() W_FireSuperSpikes = +{ + local vector dir; + + sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/spike2.bnvib"); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_nails = self.ammo_nails - 2; + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16', dir); + newmis.classname = "super_spike"; + newmis.touch = superspike_touch; + setmodel (newmis, "progs/s_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + self.punchangle_x = -2; +}; + +void(float ox) W_FireSpikes = +{ + local vector dir; + + makevectors (self.v_angle); + + if (self.ammo_nails >= 2 && self.weapon == IT_SUPER_NAILGUN) + { + W_FireSuperSpikes (); + return; + } + + if (self.ammo_nails < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/rocket1i.bnvib"); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_nails = self.ammo_nails - 1; + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16' + v_right*ox, dir); + + self.punchangle_x = -2; +}; + +void() spike_touch = +{ + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + // hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (9); + T_Damage (other, self, self.owner, 9); + } + + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + + if (self.classname == "wizard_spike") + WriteByte (MSG_BROADCAST, TE_WIZSPIKE); + + else if (self.classname == "knight_spike") + WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE); + else + WriteByte (MSG_BROADCAST, TE_SPIKE); + + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + +void() superspike_touch = +{ + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + // hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (18); + T_Damage (other, self, self.owner, 18); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_SUPERSPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + + +/* +=============================================================================== + +PLAYER WEAPON USE + +=============================================================================== +*/ + +void() W_SetCurrentAmmo = +{ + player_run (); // get out of any weapon firing states + self.weaponframe = 0; + + self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS) ); + + if (self.weapon == IT_AXE) + { + self.currentammo = 0; + self.weaponmodel = "progs/v_axe.mdl"; + } + else if (self.weapon == IT_SHOTGUN) + { + self.currentammo = self.ammo_shells; + self.weaponmodel = "progs/v_shot.mdl"; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.currentammo = self.ammo_shells; + self.weaponmodel = "progs/v_shot2.mdl"; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_NAILGUN) + { + self.currentammo = self.ammo_nails; + self.weaponmodel = "progs/v_nail.mdl"; + self.items = self.items | IT_NAILS; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.currentammo = self.ammo_nails; + self.weaponmodel = "progs/v_nail2.mdl"; + self.items = self.items | IT_NAILS; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.currentammo = self.ammo_rockets; + self.weaponmodel = "progs/v_rock.mdl"; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.currentammo = self.ammo_rockets; + self.weaponmodel = "progs/v_rock2.mdl"; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_LIGHTNING) + { + self.currentammo = self.ammo_cells; + self.weaponmodel = "progs/v_light.mdl"; + self.items = self.items | IT_CELLS; + } + else + { + self.currentammo = 0; + self.weaponmodel = string_null; + } +}; + +float() W_BestWeapon = +{ + local float it; + + it = self.items; + + if (self.waterlevel <= 1 && self.ammo_cells >= 1 && (it & IT_LIGHTNING) ) + return IT_LIGHTNING; + else if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) ) + return IT_SUPER_NAILGUN; + else if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) ) + return IT_SUPER_SHOTGUN; + else if(self.ammo_nails >= 1 && (it & IT_NAILGUN) ) + return IT_NAILGUN; + else if(self.ammo_shells >= 1 && (it & IT_SHOTGUN) ) + return IT_SHOTGUN; + + return IT_AXE; +}; + +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon = +{ + float playerFlags = CheckPlayerEXFlags(playerEnt); + if((playerFlags & PEF_CHANGENEVER) != 0) + { + return 0; + } + + if(((playerFlags & PEF_CHANGEONLYNEW) != 0) && old == new) + { + return 0; + } + + return 1; +}; + +float() W_HasNoAmmo = +{ + if (self.currentammo) + return FALSE; + + if (self.weapon == IT_AXE) + return FALSE; // doesnt need ammo + + self.weapon = W_BestWeapon (); + + W_SetCurrentAmmo (); + + // drop the weapon down + return TRUE; +}; + +/* +============ +W_Attack + +An attack impulse can be triggered now +============ +*/ +void() player_axe1; +void() player_axeb1; +void() player_axec1; +void() player_axed1; +void() player_shot1; +void() player_nail1; +void() player_light1; +void() player_rocket1; + +void() W_Attack = +{ + local float r; + + if ( W_HasNoAmmo() ) { + return; + } + + makevectors (self.v_angle); // calculate forward angle for velocity + self.show_hostile = time + 1; // wake monsters up + + // [NDS] haleyjd: record player attacked + if (self.weapon != IT_AXE) + { + self.fired_weapon = 1; + } + + if (self.weapon == IT_AXE) + { + sound (self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/ax1.bnvib"); + r = random(); + if (r < 0.25) + player_axe1 (); + else if (r<0.5) + player_axeb1 (); + else if (r<0.75) + player_axec1 (); + else + player_axed1 (); + self.attack_finished = time + 0.5; + } + else if (self.weapon == IT_SHOTGUN) + { + player_shot1 (); + W_FireShotgun (); + self.attack_finished = time + 0.5; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + player_shot1 (); + W_FireSuperShotgun (); + self.attack_finished = time + 0.7; + } + else if (self.weapon == IT_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + player_rocket1(); + W_FireGrenade(); + self.attack_finished = time + 0.6; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + player_rocket1(); + W_FireRocket(); + self.attack_finished = time + 0.8; + } + else if (self.weapon == IT_LIGHTNING) + { + player_light1(); + self.attack_finished = time + 0.1; + sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + //stuffcmd (self, "vibrate tactile/weapons/lstart.bnvib"); + } +}; + +/* +============ +W_ChangeWeapon + +============ +*/ +void() W_ChangeWeapon = +{ + local float it, am, fl; + + fl = it = self.items; + am = 0; + + if (self.impulse == 1) + { + fl = IT_AXE; + } + else if (self.impulse == 2) + { + fl = IT_SHOTGUN; + + if (self.ammo_shells < 1) + am = 1; + } + else if (self.impulse == 3) + { + fl = IT_SUPER_SHOTGUN; + + if (self.ammo_shells < 2) + am = 1; + } + else if (self.impulse == 4) + { + fl = IT_NAILGUN; + + if (self.ammo_nails < 1) + am = 1; + } + else if (self.impulse == 5) + { + fl = IT_SUPER_NAILGUN; + + if (self.ammo_nails < 2) + am = 1; + } + else if (self.impulse == 6) + { + fl = IT_GRENADE_LAUNCHER; + + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.impulse == 7) + { + fl = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.impulse == 8) + { + fl = IT_LIGHTNING; + if (self.ammo_cells < 1) + am = 1; + } + + self.impulse = 0; + + if (!(self.items & fl)) + { // don't have the weapon or the ammo + sprint (self, "$qc_no_weapon"); + return; + } + + if (am) + { // don't have the ammo + sprint (self, "$qc_not_enough_ammo"); + return; + } + +// +// set weapon, set ammo +// + self.weapon = fl; + W_SetCurrentAmmo (); +}; + +/* +============ +CheatCommand +============ +*/ +void CheatCommand() { + + if ( ( deathmatch || coop ) && cheats_allowed == 0 ) { // mal: allow impulse 9 IF cheats allowed + return; + } + + self.ammo_rockets = 100; + self.ammo_nails = 200; + self.ammo_shells = 100; + self.items = self.items | + IT_AXE | + IT_SHOTGUN | + IT_SUPER_SHOTGUN | + IT_NAILGUN | + IT_SUPER_NAILGUN | + IT_GRENADE_LAUNCHER | + IT_ROCKET_LAUNCHER | + IT_KEY1 | IT_KEY2; + + self.ammo_cells = 200; + self.items = self.items | IT_LIGHTNING; + + // mal: also give full armor... + self.armortype = 0.8; + self.armorvalue = 200; + self.items = self.items - ( self.items & ( IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3 ) ) + IT_ARMOR3; + + self.weapon = IT_ROCKET_LAUNCHER; + self.impulse = 0; + W_SetCurrentAmmo (); +}; + +/* +============ +CycleWeaponCommand + +Go to the next weapon with ammo +============ +*/ +void() CycleWeaponCommand = +{ + local float it, am; + + it = self.items; + self.impulse = 0; + + while (1) + { + am = 0; + + if (self.weapon == IT_LIGHTNING) + { + self.weapon = IT_AXE; + } + else if (self.weapon == IT_AXE) + { + self.weapon = IT_SHOTGUN; + + if (self.ammo_shells < 1) + am = 1; + } + else if (self.weapon == IT_SHOTGUN) + { + self.weapon = IT_SUPER_SHOTGUN; + + if (self.ammo_shells < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.weapon = IT_NAILGUN; + + if (self.ammo_nails < 1) + am = 1; + } + else if (self.weapon == IT_NAILGUN) + { + self.weapon = IT_SUPER_NAILGUN; + + if (self.ammo_nails < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.weapon = IT_GRENADE_LAUNCHER; + + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.weapon = IT_ROCKET_LAUNCHER; + + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.weapon = IT_LIGHTNING; + + if (self.ammo_cells < 1) + am = 1; + } + + if ( (self.items & self.weapon) && am == 0) + { + W_SetCurrentAmmo (); + return; + } + } +}; + + +/* +============ +CycleWeaponReverseCommand + +Go to the prev weapon with ammo +============ +*/ +void() CycleWeaponReverseCommand = +{ + local float it, am; + + it = self.items; + self.impulse = 0; + + while (1) + { + am = 0; + + if (self.weapon == IT_LIGHTNING) + { + self.weapon = IT_ROCKET_LAUNCHER; + + if (self.ammo_rockets < 1) + am = 1; + } + + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.weapon = IT_GRENADE_LAUNCHER; + + if (self.ammo_rockets < 1) + am = 1; + } + + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.weapon = IT_SUPER_NAILGUN; + + if (self.ammo_nails < 2) + am = 1; + } + + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.weapon = IT_NAILGUN; + + if (self.ammo_nails < 1) + am = 1; + } + + else if (self.weapon == IT_NAILGUN) + { + self.weapon = IT_SUPER_SHOTGUN; + + if (self.ammo_shells < 2) + am = 1; + } + + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.weapon = IT_SHOTGUN; + + if (self.ammo_shells < 1) + am = 1; + } + + else if (self.weapon == IT_SHOTGUN) + { + self.weapon = IT_AXE; + } + + else if (self.weapon == IT_AXE) + { + self.weapon = IT_LIGHTNING; + + if (self.ammo_cells < 1) + am = 1; + } + + if ( (it & self.weapon) && am == 0) + { + W_SetCurrentAmmo (); + return; + } + } +}; + +/* +============ +ServerflagsCommand + +Just for development +============ +*/ +void() ServerflagsCommand = +{ + serverflags = serverflags * 2 + 1; +}; + +void() QuadCheat = +{ + if (deathmatch || coop) + return; + + self.super_time = 1; + self.super_damage_finished = time + 30; + self.items = self.items | IT_QUAD; + dprint ("quad cheat\n"); +}; + +/* +============ +ImpulseCommands + +============ +*/ +void() ImpulseCommands = +{ + if (self.impulse >= 1 && self.impulse <= 8) + W_ChangeWeapon (); + if (self.impulse == 9) + CheatCommand (); + if (self.impulse == 10) + CycleWeaponCommand (); + if (self.impulse == 11) + ServerflagsCommand (); + if (self.impulse == 12) + CycleWeaponReverseCommand(); + if (self.impulse == 255) + QuadCheat (); + + self.impulse = 0; +}; + +/* +============ +W_WeaponFrame + +Called every frame so impulse events can be handled as well as possible +============ +*/ +void() W_WeaponFrame = +{ + if ( time < self.attack_finished ) { + return; + } + + if (self.impulse) + ImpulseCommands (); + + // check for attack + if ( self.button0 ) { + SuperDamageSound (); + W_Attack (); + } +}; + +/* +======== +SuperDamageSound + +Plays sound if needed +======== +*/ +void() SuperDamageSound = +{ + if (self.super_damage_finished > time) + { + if (self.super_sound < time) + { + self.super_sound = time + 1; + sound (self, CHAN_BODY, "items/damage3.wav", 1, ATTN_NORM); + } + } + return; +}; diff --git a/quakec/world.qc b/quakec/world.qc new file mode 100644 index 0000000..5e829b5 --- /dev/null +++ b/quakec/world.qc @@ -0,0 +1,421 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() InitBodyQueue; + + +void() main = +{ + dprint ("main function\n"); + +// these are just commands the the prog compiler to copy these files + + precache_file ("progs.dat"); + precache_file ("gfx.wad"); + precache_file ("quake.rc"); + precache_file ("default.cfg"); + + precache_file ("end1.bin"); + precache_file2 ("end2.bin"); + + precache_file ("demo1.dem"); + precache_file ("demo2.dem"); + precache_file ("demo3.dem"); + +// +// these are all of the lumps from the cached.ls files +// + precache_file ("gfx/palette.lmp"); + precache_file ("gfx/colormap.lmp"); + + precache_file2 ("gfx/pop.lmp"); + + precache_file ("gfx/complete.lmp"); + precache_file ("gfx/inter.lmp"); + + precache_file ("gfx/ranking.lmp"); + precache_file ("gfx/vidmodes.lmp"); + precache_file ("gfx/finale.lmp"); + precache_file ("gfx/conback.lmp"); + precache_file ("gfx/qplaque.lmp"); + + precache_file ("gfx/menudot1.lmp"); + precache_file ("gfx/menudot2.lmp"); + precache_file ("gfx/menudot3.lmp"); + precache_file ("gfx/menudot4.lmp"); + precache_file ("gfx/menudot5.lmp"); + precache_file ("gfx/menudot6.lmp"); + + precache_file ("gfx/menuplyr.lmp"); + precache_file ("gfx/bigbox.lmp"); + precache_file ("gfx/dim_modm.lmp"); + precache_file ("gfx/dim_drct.lmp"); + precache_file ("gfx/dim_ipx.lmp"); + precache_file ("gfx/dim_tcp.lmp"); + precache_file ("gfx/dim_mult.lmp"); + precache_file ("gfx/mainmenu.lmp"); + + precache_file ("gfx/box_tl.lmp"); + precache_file ("gfx/box_tm.lmp"); + precache_file ("gfx/box_tr.lmp"); + + precache_file ("gfx/box_ml.lmp"); + precache_file ("gfx/box_mm.lmp"); + precache_file ("gfx/box_mm2.lmp"); + precache_file ("gfx/box_mr.lmp"); + + precache_file ("gfx/box_bl.lmp"); + precache_file ("gfx/box_bm.lmp"); + precache_file ("gfx/box_br.lmp"); + + precache_file ("gfx/sp_menu.lmp"); + precache_file ("gfx/ttl_sgl.lmp"); + precache_file ("gfx/ttl_main.lmp"); + precache_file ("gfx/ttl_cstm.lmp"); + + precache_file ("gfx/mp_menu.lmp"); + + precache_file ("gfx/netmen1.lmp"); + precache_file ("gfx/netmen2.lmp"); + precache_file ("gfx/netmen3.lmp"); + precache_file ("gfx/netmen4.lmp"); + precache_file ("gfx/netmen5.lmp"); + + precache_file ("gfx/sell.lmp"); + + precache_file ("gfx/help0.lmp"); + precache_file ("gfx/help1.lmp"); + precache_file ("gfx/help2.lmp"); + precache_file ("gfx/help3.lmp"); + precache_file ("gfx/help4.lmp"); + precache_file ("gfx/help5.lmp"); + + precache_file ("gfx/pause.lmp"); + precache_file ("gfx/loading.lmp"); + + precache_file ("gfx/p_option.lmp"); + precache_file ("gfx/p_load.lmp"); + precache_file ("gfx/p_save.lmp"); + precache_file ("gfx/p_multi.lmp"); + +// sounds loaded by C code + precache_sound ("misc/menu1.wav"); + precache_sound ("misc/menu2.wav"); + precache_sound ("misc/menu3.wav"); + + precache_sound ("ambience/water1.wav"); + precache_sound ("ambience/wind2.wav"); + +// shareware + precache_file ("maps/start.bsp"); + + precache_file ("maps/e1m1.bsp"); + precache_file ("maps/e1m2.bsp"); + precache_file ("maps/e1m3.bsp"); + precache_file ("maps/e1m4.bsp"); + precache_file ("maps/e1m5.bsp"); + precache_file ("maps/e1m6.bsp"); + precache_file ("maps/e1m7.bsp"); + precache_file ("maps/e1m8.bsp"); + +// registered + precache_file2 ("gfx/pop.lmp"); + + precache_file2 ("maps/e2m1.bsp"); + precache_file2 ("maps/e2m2.bsp"); + precache_file2 ("maps/e2m3.bsp"); + precache_file2 ("maps/e2m4.bsp"); + precache_file2 ("maps/e2m5.bsp"); + precache_file2 ("maps/e2m6.bsp"); + precache_file2 ("maps/e2m7.bsp"); + + precache_file2 ("maps/e3m1.bsp"); + precache_file2 ("maps/e3m2.bsp"); + precache_file2 ("maps/e3m3.bsp"); + precache_file2 ("maps/e3m4.bsp"); + precache_file2 ("maps/e3m5.bsp"); + precache_file2 ("maps/e3m6.bsp"); + precache_file2 ("maps/e3m7.bsp"); + + precache_file2 ("maps/e4m1.bsp"); + precache_file2 ("maps/e4m2.bsp"); + precache_file2 ("maps/e4m3.bsp"); + precache_file2 ("maps/e4m4.bsp"); + precache_file2 ("maps/e4m5.bsp"); + precache_file2 ("maps/e4m6.bsp"); + precache_file2 ("maps/e4m7.bsp"); + precache_file2 ("maps/e4m8.bsp"); + + precache_file2 ("maps/end.bsp"); + + precache_file2 ("maps/dm1.bsp"); + precache_file2 ("maps/dm2.bsp"); + precache_file2 ("maps/dm3.bsp"); + precache_file2 ("maps/dm4.bsp"); + precache_file2 ("maps/dm5.bsp"); + precache_file2 ("maps/dm6.bsp"); +}; + + +entity lastspawn; + +//======================= +/*QUAKED worldspawn (0 0 0) ? +Only used for the world entity. +Set message to the level name. +Set sounds to the cd track to play. + +World Types: +0: medieval +1: metal +2: base +*/ +//======================= +float startingserverflags; + +void() worldspawn = +{ + startingserverflags = serverflags; + lastspawn = world; + InitBodyQueue (); + + // custom map attributes + if (self.model == "maps/e1m8.bsp") + cvar_set ("sv_gravity", "100"); + else + cvar_set ("sv_gravity", "800"); + + // the area based ambient sounds MUST be the first precache_sounds + + // player precaches + W_Precache (); // get weapon precaches + + // sounds used from C physics code + precache_sound ("demon/dland2.wav"); // landing thud + precache_sound ("misc/h2ohit1.wav"); // landing splash + + // setup precaches allways needed + precache_sound ("items/itembk2.wav"); // item respawn sound + precache_sound ("player/plyrjmp8.wav"); // player jump + precache_sound ("player/land.wav"); // player landing + precache_sound ("player/land2.wav"); // player hurt landing + precache_sound ("player/drown1.wav"); // drowning pain + precache_sound ("player/drown2.wav"); // drowning pain + precache_sound ("player/gasp1.wav"); // gasping for air + precache_sound ("player/gasp2.wav"); // taking breath + precache_sound ("player/h2odeath.wav"); // drowning death + + precache_sound ("misc/talk.wav"); // talk + precache_sound ("player/teledth1.wav"); // telefrag + precache_sound ("misc/r_tele1.wav"); // teleport sounds + precache_sound ("misc/r_tele2.wav"); + precache_sound ("misc/r_tele3.wav"); + precache_sound ("misc/r_tele4.wav"); + precache_sound ("misc/r_tele5.wav"); + precache_sound ("weapons/lock4.wav"); // ammo pick up + precache_sound ("weapons/pkup.wav"); // weapon up + precache_sound ("items/armor1.wav"); // armor up + precache_sound ("weapons/lhit.wav"); //lightning + precache_sound ("weapons/lstart.wav"); //lightning start + precache_sound ("items/damage3.wav"); + + precache_sound ("misc/power.wav"); //lightning for boss + +// player gib sounds + precache_sound ("player/gib.wav"); // player gib sound + precache_sound ("player/udeath.wav"); // player gib sound + precache_sound ("player/tornoff2.wav"); // gib sound + +// player pain sounds + + precache_sound ("player/pain1.wav"); + precache_sound ("player/pain2.wav"); + precache_sound ("player/pain3.wav"); + precache_sound ("player/pain4.wav"); + precache_sound ("player/pain5.wav"); + precache_sound ("player/pain6.wav"); + +// player death sounds + precache_sound ("player/death1.wav"); + precache_sound ("player/death2.wav"); + precache_sound ("player/death3.wav"); + precache_sound ("player/death4.wav"); + precache_sound ("player/death5.wav"); + +// ax sounds + precache_sound ("weapons/ax1.wav"); // ax swoosh + precache_sound ("player/axhit1.wav"); // ax hit meat + precache_sound ("player/axhit2.wav"); // ax hit world + + precache_sound ("player/h2ojump.wav"); // player jumping into water + precache_sound ("player/slimbrn2.wav"); // player enter slime + precache_sound ("player/inh2o.wav"); // player enter water + precache_sound ("player/inlava.wav"); // player enter lava + precache_sound ("misc/outwater.wav"); // leaving water sound + + precache_sound ("player/lburn1.wav"); // lava burn + precache_sound ("player/lburn2.wav"); // lava burn + + precache_sound ("misc/water1.wav"); // swimming + precache_sound ("misc/water2.wav"); // swimming + + precache_model ("progs/player.mdl"); + precache_model ("progs/eyes.mdl"); + precache_model ("progs/h_player.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_model ("progs/s_bubble.spr"); // drowning bubbles + precache_model ("progs/s_explod.spr"); // sprite explosion + + precache_model ("progs/v_axe.mdl"); + precache_model ("progs/v_shot.mdl"); + precache_model ("progs/v_nail.mdl"); + precache_model ("progs/v_rock.mdl"); + precache_model ("progs/v_shot2.mdl"); + precache_model ("progs/v_nail2.mdl"); + precache_model ("progs/v_rock2.mdl"); + + precache_model ("progs/bolt.mdl"); // for lightning gun + precache_model ("progs/bolt2.mdl"); // for lightning gun + precache_model ("progs/bolt3.mdl"); // for boss shock + precache_model ("progs/lavaball.mdl"); // for testing + + precache_model ("progs/missile.mdl"); + precache_model ("progs/grenade.mdl"); + precache_model ("progs/spike.mdl"); + precache_model ("progs/s_spike.mdl"); + + precache_model ("progs/backpack.mdl"); + + precache_model ("progs/zom_gib.mdl"); + + precache_model ("progs/v_light.mdl"); + + +// +// Setup light animation tables. 'a' is total darkness, 'z' is maxbright. +// + + // 0 normal + lightstyle(0, "m"); + + // 1 FLICKER (first variety) + lightstyle(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + lightstyle(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + lightstyle(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + lightstyle(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + lightstyle(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + lightstyle(63, "a"); +}; + +void StartFrame() { + teamplay = cvar("teamplay"); + skill = cvar("skill"); + cheats_allowed = cvar("sv_cheats"); + + // save off a global here so it gets included in savegames + if (!campaign_valid) { + campaign_valid = TRUE; + campaign = cvar("campaign"); + } else { + cvar_set("campaign", ftos(campaign)); + } + + framecount = framecount + 1; +}; + +/* +============================================================================== + +BODY QUE + +============================================================================== +*/ + +entity bodyqueue_head; + +void() bodyqueue = +{ // just here so spawn functions don't complain after the world + // creates bodyqueues +}; + +void() InitBodyQueue = +{ + bodyqueue_head = spawn(); + bodyqueue_head.classname = "bodyqueue"; + bodyqueue_head.owner = spawn(); + bodyqueue_head.owner.classname = "bodyqueue"; + bodyqueue_head.owner.owner = spawn(); + bodyqueue_head.owner.owner.classname = "bodyqueue"; + bodyqueue_head.owner.owner.owner = spawn(); + bodyqueue_head.owner.owner.owner.classname = "bodyqueue"; + bodyqueue_head.owner.owner.owner.owner = bodyqueue_head; +}; + + +// make a body que entry for the given ent so the ent can be +// respawned elsewhere +void(entity ent) CopyToBodyQueue = +{ + bodyqueue_head.angles = ent.angles; + bodyqueue_head.model = ent.model; + bodyqueue_head.modelindex = ent.modelindex; + bodyqueue_head.frame = ent.frame; + bodyqueue_head.colormap = ent.colormap; + bodyqueue_head.movetype = ent.movetype; + bodyqueue_head.velocity = ent.velocity; + bodyqueue_head.flags = 0; + setorigin (bodyqueue_head, ent.origin); + setsize (bodyqueue_head, ent.mins, ent.maxs); + bodyqueue_head = bodyqueue_head.owner; +}; + + diff --git a/quakec_hipnotic/ai.qc b/quakec_hipnotic/ai.qc new file mode 100644 index 0000000..78d8304 --- /dev/null +++ b/quakec_hipnotic/ai.qc @@ -0,0 +1,1210 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() movetarget_f; +void() t_movetarget; +void() knight_walk1; +void() knight_bow6; +void() knight_bow1; +void(entity etemp, entity stemp, entity stemp, float dmg) T_Damage; +/* + +.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. + +.huntt_ime +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 +*/ + + +// +// globals +// +float current_yaw; + +// +// when a monster becomes angry at a player, that monster will be used +// as the sight target the next frame so that monsters near that one +// will wake up even if they wouldn't have noticed the player +// +entity sight_entity; +float sight_entity_time; +void() FoundTarget; + +float(float v) anglemod = +{ + while (v >= 360) + v = v - 360; + while (v < 0) + v = v + 360; + return v; +}; + +/* +============================================================================== + +MOVETARGET CODE + +The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target. + +targetname +must be present. The name of this movetarget. + +target +the next spot to move to. If not present, stop here for good. + +pausetime +The number of seconds to spend standing or bowing for path_stand or path_bow + +============================================================================== +*/ + + +void() movetarget_f = +{ + if (!self.targetname) + objerror ("monster_movetarget: no targetname"); + + self.solid = SOLID_TRIGGER; + self.touch = t_movetarget; + setsize (self, '-8 -8 -8', '8 8 8'); + +}; + +/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8) +Monsters will continue walking towards the next target corner. +"delay" delay to wait before proceeding to next segment; +*/ +void() path_corner = +{ + movetarget_f (); +}; + + +/* +============= +t_movetarget + +Something has bumped into a movetarget. If it is a monster +moving towards it, change the next destination and continue. +============== +*/ +void() t_movetarget = +{ +local entity temp; + + if (other.movetarget != self) + return; + + if (other.enemy) + return; // fighting, not following a path + + temp = self; + self = other; + other = temp; + + if (self.classname == "monster_ogre") + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound + +//dprint ("t_movetarget\n"); +//MED + if (other.target) + { + self.goalentity = self.movetarget = find (world, targetname, other.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + if (!self.movetarget) + { + self.pausetime = time + 999999; + self.th_stand (); + return; + } + //MED 01/20/97 + else if (other.delay) + { + self.pausetime = time + other.delay; + self.th_stand (); + } + } + else + { + self.pausetime = time + 999999; + self.th_stand (); + return; + } +}; + + +//MED 01/20/97 + +/* +============= +t_followtarget + +Something has bumped into a followtarget. If it is a monster +moving towards it, change the next destination and continue. +============== +*/ +void() t_followtarget = +{ + local entity temp; + local vector spot1, spot2; + local entity targ; + local entity client; + + if (!other.flags & FL_MONSTER) + return; + if (other.classname == "monster_decoy") + return; +// if (other.enemy == world) +// return; + if (other.wetsuit_time > time) + return; + targ = other.enemy; + +// see if any entities are in the way of the shot + spot1 = other.origin + other.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, other); + if (trace_fraction == 1) + return; + + if (other.enemy) + { + // make the monster tame + other.oldenemy = other.enemy; + other.enemy = world; + other.think = other.th_walk; + } + + temp = self; + self = other; + other = temp; + + self.goalentity = self.movetarget = find (world, targetname, other.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + self.wetsuit_time = time + 2; + if (!self.movetarget) + { + if (self.oldenemy != world) + { + self.enemy = self.oldenemy; + FoundTarget(); + return; + } + else + { + client = checkclient (); + if (!client) + { + self.enemy = client; + FoundTarget(); + return; + } + self.pausetime = time + 999999; + self.th_stand (); + } + } +}; + +void() followtarget_f = +{ +// if (!self.targetname) +// objerror ("monster_followtarget: no targetname"); + + self.solid = SOLID_TRIGGER; + self.touch = t_followtarget; + setmodel (self, self.model); // set size and link into world + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + self.model = ""; +// setsize (self, '-8 -8 -8', '8 8 8'); + +}; + +/*QUAKED path_follow (0.5 0.3 0) ? +Monsters will stop what they are doing and follow to the path +*/ + +void() path_follow = +{ + followtarget_f (); +}; + +/*QUAKED path_follow2 (0.5 0.3 0) (-8 -8 -8) (8 8 8) +Monsters will stop what they are doing and follow to the path +*/ + +void() path_follow2 = +{ + self.solid = SOLID_TRIGGER; + self.touch = t_followtarget; + setsize (self, '-8 -8 -8', '8 8 8'); +}; + + +//============================================================================ + +/* +============= +range + +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 +============= +*/ +float(entity targ) range = +{ +local vector spot1, spot2; +local float r; + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + r = vlen (spot1 - spot2); + if (r < 120) + return RANGE_MELEE; + if (r < 500) + return RANGE_NEAR; + if (r < 1000) + return RANGE_MID; + return RANGE_FAR; +}; + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +float (entity targ) visible = +{ + local vector spot1, spot2; + + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + traceline (spot1, spot2, TRUE, self); // see through other monsters + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + +//MED 11/21/96 + if (trace_fraction == 1) + { + visible_distance = vlen(spot2-spot1); + return TRUE; + } + return FALSE; +}; + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +float(entity targ) infront = +{ + local vector vec; + local float dot; + + makevectors (self.angles); + vec = normalize (targ.origin - self.origin); + dot = vec * v_forward; + + if ( dot > 0.3) + { + return TRUE; + } + return FALSE; +}; + + +//============================================================================ + +/* +=========== +ChangeYaw + +Turns towards self.ideal_yaw at self.yaw_speed +Sets the global variable current_yaw +Called every 0.1 sec by monsters +============ +*/ +/* + +void() ChangeYaw = +{ + local float ideal, move; + +//current_yaw = self.ideal_yaw; +// mod down the current angle + current_yaw = anglemod( self.angles_y ); + ideal = self.ideal_yaw; + + if (current_yaw == ideal) + return; + + move = ideal - current_yaw; + if (ideal > current_yaw) + { + if (move > 180) + move = move - 360; + } + else + { + if (move < -180) + move = move + 360; + } + + if (move > 0) + { + if (move > self.yaw_speed) + move = self.yaw_speed; + } + else + { + if (move < 0-self.yaw_speed ) + move = 0-self.yaw_speed; + } + + current_yaw = anglemod (current_yaw + move); + + self.angles_y = current_yaw; +}; + +*/ + +//MED 10/18/96 added charmed stuff +//============================================================================ +void() UpdateCharmerGoal = + { + local entity targ; + local vector d; + + d = normalize(self.origin-self.charmer.origin); + + if (self.huntingcharmer == 1) + { + targ = spawn(); + self.trigger_field = targ; + setorigin(targ,self.charmer.origin); + self.huntingcharmer = 2; + self.goalentity = targ; + } + if (self.huntingcharmer == 2) + { + targ = self.trigger_field; + traceline(self.origin,self.charmer.origin,TRUE,self); + if (trace_fraction == 1.0) + { +// dprint("can see "); +// dprint(vtos(self.charmer.origin)); +// dprint("\n"); + setorigin(targ,self.charmer.origin); + } + } + else + { + targ = self.trigger_field; + setorigin(targ,self.charmer.origin + (d*300)); + } + }; + +//============================================================================ +void() HuntCharmer = +{ + self.huntingcharmer = 1; + UpdateCharmerGoal(); +// self.goalentity = self.charmer; + self.think = self.th_walk; + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + self.nextthink = time + 0.1; +}; + +//============================================================================ +void() FleeCharmer = +{ + self.huntingcharmer = 1; + UpdateCharmerGoal(); + self.huntingcharmer = 3; +// self.goalentity = self.charmer; + self.think = self.th_walk; +// self.ideal_yaw = vectoyaw(self.originself.goalentity.origin - self.origin); + self.nextthink = time + 0.1; +}; + +//============================================================================ +void() StopHuntingCharmer = +{ + self.goalentity = world; + if (self.huntingcharmer>1) + remove(self.trigger_field); + self.huntingcharmer = 0; + self.think = self.th_stand; + self.nextthink = time + 0.1; +}; + +//============================================================================ + +void() HuntTarget = +{ + self.goalentity = self.enemy; + self.think = self.th_run; + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + self.nextthink = time + 0.1; + SUB_AttackFinished (1); // wait a while before first attack +}; + +void() SightSound = +{ +local float rsnd; + + if (self.classname == "monster_ogre") + sound (self, CHAN_VOICE, "ogre/ogwake.wav", 1, ATTN_NORM); + else if (self.classname == "monster_knight") + sound (self, CHAN_VOICE, "knight/ksight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_shambler") + sound (self, CHAN_VOICE, "shambler/ssight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_demon1") + sound (self, CHAN_VOICE, "demon/sight2.wav", 1, ATTN_NORM); + else if (self.classname == "monster_wizard") + sound (self, CHAN_VOICE, "wizard/wsight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_zombie") + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_NORM); + else if (self.classname == "monster_dog") + sound (self, CHAN_VOICE, "dog/dsight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_hell_knight") + sound (self, CHAN_VOICE, "hknight/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_tarbaby") + sound (self, CHAN_VOICE, "blob/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_vomit") + sound (self, CHAN_VOICE, "vomitus/v_sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_enforcer") + { + rsnd = rint(random() * 3); + if (rsnd == 1) + sound (self, CHAN_VOICE, "enforcer/sight1.wav", 1, ATTN_NORM); + else if (rsnd == 2) + sound (self, CHAN_VOICE, "enforcer/sight2.wav", 1, ATTN_NORM); + else if (rsnd == 0) + sound (self, CHAN_VOICE, "enforcer/sight3.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "enforcer/sight4.wav", 1, ATTN_NORM); + } + else if (self.classname == "monster_army") + sound (self, CHAN_VOICE, "soldier/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_shalrath") + sound (self, CHAN_VOICE, "shalrath/sight.wav", 1, ATTN_NORM); +//MED + else if (self.classname == "monster_gremlin") + { + if (self.stoleweapon == 0) + sound (self, CHAN_VOICE, "grem/sight1.wav", 1, ATTN_NORM); + } +//MED + else if (self.classname == "monster_scourge") + sound (self, CHAN_VOICE, "scourge/sight.wav", 1, ATTN_NORM); +//MED + else if (self.classname == "monster_armagon") + sound (self, CHAN_VOICE, "armagon/sight.wav", 1, 0.1); +}; + +void() FoundTarget = +{ +//MED + if (self.enemy.classname == "player") + { + if (self.charmed) + { + if ((self.charmer == self.enemy) || (self.charmer == self.enemy.charmer)) + { + self.enemy = world; + return; + } + } + // let other monsters see this monster for a while + sight_entity = self; + sight_entity_time = time; + } + else if (self.charmed) + { + if ((self.charmer == self.enemy.charmer)) + { + self.enemy = world; + return; + } + } + + self.show_hostile = time + 1; // wake up other monsters + + SightSound (); + HuntTarget (); +}; + +/* +=========== +FindTarget + +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. +============ +*/ +float() FindTarget = +{ + local entity client; + local float r; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry + +// spawnflags & 3 is a big hack, because zombie crucified used the first +// spawn flag prior to the ambush flag, and I forgot about it, so the second +// spawn flag works as well + +//MED 10/17/96 added charmed stuff + if (self.charmed) + { + self.effects = self.effects | EF_DIMLIGHT; + if (self.huntingcharmer > 0) + { + UpdateCharmerGoal(); +// self.goalentity = self.charmer; + r = vlen(self.origin - self.goalentity.origin); + if (r < MIN_CHARMER_DISTANCE) + { +// dprint("stopping\n"); + if ((self.huntingcharmer == 3) && (r > TOOCLOSE_CHARMER_DISTANCE)) + return FALSE; +// self.huntingcharmer = 0; + StopHuntingCharmer(); + return TRUE; + } + } + else if (vlen (self.origin - self.charmer.origin) > MAX_CHARMER_DISTANCE) + { +// self.huntingcharmer = 1; +// dprint("hunting\n"); + HuntCharmer(); + return FALSE; + } + else if (vlen (self.origin - self.charmer.origin) < TOOCLOSE_CHARMER_DISTANCE) + { +// self.huntingcharmer = 1; +// dprint("fleeing\n"); + FleeCharmer(); + return FALSE; + } + } + if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) && !(self.charmed) ) + { + client = sight_entity; + if (client.enemy == self.enemy) + return; + } +//MED 10/17/96 added charmed clause + else if (self.charmed) + { + local entity head; + local entity selected; + local float dist; + + selected = world; + dist = CHARMED_RADIUS; + head = findradius(self.origin, CHARMED_RADIUS); + while(head) + { + if(!(head.flags & FL_NOTARGET) && (head.flags & FL_MONSTER)) + { + if (visible(head) && (visible_distance < dist) && (head.health>0)) + { + if ((head !=self) && (head != self.charmer) && (head.charmer != self.charmer)) + { + selected = head; + dist = visible_distance; + } + } + } + head = head.chain; + } + if (selected == world) + return FALSE; + + client = selected; + } + else + { + client = checkclient (); + if (!client) + return FALSE; // current check entity isn't in PVS + } + + if (client == self.enemy) + return FALSE; + + //MED 10/17/96 added charmed stuff + if (!self.charmed) + { + if (client.flags & FL_NOTARGET) + return FALSE; + } + if (client.items & IT_INVISIBILITY) + return FALSE; + + r = range (client); + if (r == RANGE_FAR) + return FALSE; + + if (!visible (client)) + return FALSE; + + //MED 10/17/96 added charmed stuff + if (!self.charmed) + { + if (r == RANGE_NEAR) + { + if (client.show_hostile < time && !infront (client)) + return FALSE; + } + else if (r == RANGE_MID) + { + if ( /* client.show_hostile < time || */ !infront (client)) + return FALSE; + } + } + +// +// got one +// + self.enemy = client; + +//MED 10/17/96 added charmed stuff + if ((!self.charmed) && (!self.enemy.charmed)) + { + if (self.enemy.classname != "player") + { + self.enemy = self.enemy.enemy; + if (self.enemy.classname != "player") + { + self.enemy = world; + return FALSE; + } + } + } + + FoundTarget (); + + return TRUE; +}; + + +//============================================================================= + +void(float dist) ai_forward = +{ + walkmove (self.angles_y, dist); +}; + +void(float dist) ai_back = +{ + walkmove ( (self.angles_y+180), dist); +}; + + +/* +============= +ai_pain + +stagger back a bit +============= +*/ +void(float dist) ai_pain = +{ + ai_back (dist); +/* + local float away; + + away = anglemod (vectoyaw (self.origin - self.enemy.origin) + + 180*(random()- 0.5) ); + + walkmove (away, dist); +*/ +}; + +/* +============= +ai_painforward + +stagger back a bit +============= +*/ +void(float dist) ai_painforward = +{ + walkmove (self.ideal_yaw, dist); +}; + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void(float dist) ai_walk = +{ + local vector mtemp; + + movedist = dist; + +//MED 01/20/97 + // check for noticing a player + if (FindTarget ()) + return; + +//MED 11/02/96 + if (self.huntingcharmer) + { +// movetogoal (dist*2); + movetogoal (dist); + self.nextthink = time + ((self.nextthink - time)/2); + } + else + movetogoal (dist); +}; + + +/* +============= +ai_stand + +The monster is staying in one place for a while, with slight angle turns +============= +*/ +void() ai_stand = +{ + if (FindTarget ()) + return; + + if (time > self.pausetime) + { + self.th_walk (); + return; + } + +// change angle slightly + +}; + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +============= +*/ +void() ai_turn = +{ + if (FindTarget ()) + return; + + ChangeYaw (); +}; + +//MED 11/10/96 added ai_turn_in_place +/* +============= +ai_turn_in_place + +don't move, but turn towards ideal_yaw +============= +*/ +void() ai_turn_in_place = + { + local float delta; + + self.nextthink = time + 0.1; + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + delta = fabs(self.angles_y - enemy_yaw); + if (delta > MIN_ANGLE_DELTA) + { + self.ideal_yaw = enemy_yaw; + ChangeYaw(); + } + else + { + self.think = self.th_run; + } + }; + +//============================================================================= + +/* +============= +ChooseTurn +============= +*/ +void(vector dest3) ChooseTurn = +{ + local vector dir, newdir; + + dir = self.origin - dest3; + + newdir_x = trace_plane_normal_y; + newdir_y = 0 - trace_plane_normal_x; + newdir_z = 0; + + if (dir * newdir > 0) + { + dir_x = 0 - trace_plane_normal_y; + dir_y = trace_plane_normal_x; + } + else + { + dir_x = trace_plane_normal_y; + dir_y = 0 - trace_plane_normal_x; + } + + dir_z = 0; + self.ideal_yaw = vectoyaw(dir); +}; + +/* +============ +FacingIdeal + +============ +*/ +float() FacingIdeal = +{ + local float delta; + + delta = anglemod(self.angles_y - self.ideal_yaw); + if (delta > 45 && delta < 315) + return FALSE; + return TRUE; +}; + + +//============================================================================= + +float() WizardCheckAttack; +float() DogCheckAttack; +//MED +float() GremlinCheckAttack; +float() ScourgeCheckAttack; +float() ArmagonCheckAttack; + +float() CheckAnyAttack = +{ + if (!enemy_vis) + return; + if (self.classname == "monster_army") + return SoldierCheckAttack (); + if (self.classname == "monster_ogre") + return OgreCheckAttack (); + if (self.classname == "monster_shambler") + return ShamCheckAttack (); + if (self.classname == "monster_demon1") + return DemonCheckAttack (); + if (self.classname == "monster_dog") + return DogCheckAttack (); + if (self.classname == "monster_wizard") + return WizardCheckAttack (); + if (self.classname == "monster_gremlin") + return GremlinCheckAttack (); + if (self.classname == "monster_scourge") + return ScourgeCheckAttack (); + if (self.classname == "monster_armagon") + return ArmagonCheckAttack (); + return CheckAttack (); +}; + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void() ai_run_melee = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + + if (FacingIdeal()) + { + self.th_melee (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void() ai_run_missile = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (FacingIdeal()) + { + self.th_missile (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void() ai_run_slide = +{ + local float ofs; + + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (self.lefty) + ofs = 90; + else + ofs = -90; + + if (walkmove (self.ideal_yaw + ofs, movedist)) + return; + + self.lefty = 1 - self.lefty; + + walkmove (self.ideal_yaw - ofs, movedist); +}; + +//MED +/* +============= +ai_run_dodge + +Strafe sideways, but continue moving towards the enemy +Used by the Scourge. +============= +*/ +void() ai_run_dodge = +{ + local float ofs; + local float newyaw; + +/* +// attempt to jump over missiles + if (self.enemy.weaponframe == 1) + { + if (self.flags & FL_ONGROUND) + { + self.origin_z = self.origin_z + 1; + self.velocity = self.velocity + '0 0 500'; + self.flags = self.flags - FL_ONGROUND; + } + self.ltime = self.ltime + 1.0; + } +*/ + self.nextthink = time + 0.1; + if (self.lefty) + ofs = 40; + else + ofs = -40; + + if (time > self.ltime) + { + self.lefty = 1 - self.lefty; + self.ltime = time + 0.8; + } + + newyaw = enemy_yaw + ofs; + self.ideal_yaw = enemy_yaw; + if (walkmove (newyaw, movedist)) + { + ChangeYaw (); + return; + } + + self.lefty = 1 - self.lefty; + self.ltime = time + 0.8; + newyaw = enemy_yaw - ofs; + self.ideal_yaw = enemy_yaw; + walkmove (newyaw, movedist); + ChangeYaw (); +}; + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +float RUN_STRAIGHT; +void(float dist) ai_run = +{ + local vector delta; + local float axis; + local float direct, ang_rint, ang_floor, ang_ceil; + + movedist = dist; +// see if the enemy is dead + if (self.enemy.health <= 0 || (self.charmed && (self.charmer == self.enemy))) + { + self.enemy = world; +//MED 10/30/96 added charmed stuff + if (self.charmed) + { + HuntCharmer(); + return; + } + // FIXME: look all around for other targets + if (self.oldenemy.health > 0) + { + self.enemy = self.oldenemy; + HuntTarget (); + } + else + { + if (self.movetarget) + self.th_walk (); + else + self.th_stand (); + return; + } + } + + self.show_hostile = time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self.enemy); + if (enemy_vis) + self.search_time = time + 5; + +// look for other coop players +//MED 10/17/96 added charmed stuff + if (coop && self.search_time < time && !self.charmed) + { + if (FindTarget ()) + return; + } + enemy_infront = infront(self.enemy); + enemy_range = range(self.enemy); + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + +//MED + if (self.th_turn) + { + local float angledelta; + + angledelta = fabs(self.angles_y - enemy_yaw); + if (angledelta > MIN_ANGLE_DELTA) + { + self.th_turn(); + return; + } + } + + if (self.attack_state == AS_MISSILE) + { +//dprint ("ai_run_missile\n"); + ai_run_missile (); + return; + } + if (self.attack_state == AS_MELEE) + { +//dprint ("ai_run_melee\n"); + ai_run_melee (); + return; + } + + if (CheckAnyAttack ()) + { + return; // beginning an attack + } + + if (self.attack_state == AS_SLIDING) + { + ai_run_slide (); + return; + } + + if (self.attack_state == AS_DODGING) + { + ai_run_dodge (); + return; + } + +//MED 11/11/96 + if (RUN_STRAIGHT && time > self.endtime) + { + RUN_STRAIGHT = 0; + axis = walkmove (self.angles_y, movedist); + if (!axis) + { + self.endtime = time + 3; + movetogoal (dist); // done in C code... + } +// else +// ChangeYaw(); + } + else + { + // head straight in + movetogoal (dist); // done in C code... + } +}; diff --git a/quakec_hipnotic/amtest.qc b/quakec_hipnotic/amtest.qc new file mode 100644 index 0000000..03c8263 --- /dev/null +++ b/quakec_hipnotic/amtest.qc @@ -0,0 +1,104 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/*~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~> +~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~*/ + +void() test_teleport_touch; +void() tele_done; + +/*QUAKED test_teleport (0 .5 .8) ? +Teleporter testing +*/ +void() test_teleport = +{ + precache_model ("sprites/s_aball.spr"); + setsize (self, self.mins, self.maxs); + self.touch = test_teleport_touch; + self.solid = 1; + + if (!self.target) + objerror ("no target\n"); +}; + +void() test_teleport_touch = +{ +local entity oldself; + other.movetype = MOVETYPE_TOSS; +// other.solid = SOLID_NOT; + other.dest = '256 -128 -128'; + oldself = self; + self = other; +// SUB_CalcMove (self.dest, 200, tele_done); + self.velocity = '1000 0 0 '; + self = oldself; +}; + +void() tele_done = +{ + self.movetype = MOVETYPE_WALK; + self.solid = SOLID_SLIDEBOX; +}; + +/*~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~> +~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~*/ + +void() test_goaway; +void() test_spawn; + +/*QUAKED test_fodder (0 .5 .8) ? +beating guy +*/ +void() test_fodder = +{ + self.nextthink = time + 3; + self.think = test_spawn; +}; + +void() test_spawn = +{ +local entity body; + makevectors (self.angles); + + body = spawn(); + setmodel (body, "progs/soldier.mdl"); + setorigin (body, self.origin); + body.classname = "player"; + body.health = 1000; + body.frags = 0; + body.takedamage = DAMAGE_AIM; + body.solid = SOLID_SLIDEBOX; + body.movetype = MOVETYPE_WALK; + body.show_hostile = 0; + body.weapon = 1; + body.velocity = v_forward * 200; + + body.nextthink = time + 5; + body.think = test_goaway; + +self.nextthink = time + 3; +self.think = test_spawn; + +}; + +void() test_goaway = +{ + remove (self); +}; + diff --git a/quakec_hipnotic/bots/bot.qc b/quakec_hipnotic/bots/bot.qc new file mode 100644 index 0000000..86d4a3a --- /dev/null +++ b/quakec_hipnotic/bots/bot.qc @@ -0,0 +1,52 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +======================== +Bot_PreThink + +Called by the engine every frame before running the bots update in C++ + +Example usages of this function: + + 1. Telling bots what to do or where to go. + 2. Modifying properties on the bot player ( or other players in-game ) that the bots can see/use doing their update. +======================== +*/ +void Bot_PreThink() { + + +}; + +/* +======================== +Bot_PostThink + +Called by the engine every frame after running the bots update in C++ + +Example usages of this function: + + 1. Overriding/modifying bot user cmds. + 2. Overriding/modifying bot player properties. +======================== +*/ +void Bot_PostThink() { + + +}; \ No newline at end of file diff --git a/quakec_hipnotic/buttons.qc b/quakec_hipnotic/buttons.qc new file mode 100644 index 0000000..e3ad6ff --- /dev/null +++ b/quakec_hipnotic/buttons.qc @@ -0,0 +1,161 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// button and multiple button + +void() button_wait; +void() button_return; + +void() button_wait = +{ + self.state = STATE_TOP; + self.nextthink = self.ltime + self.wait; + self.think = button_return; + activator = self.enemy; + SUB_UseTargets(); + self.frame = 1; // use alternate textures +}; + +void() button_done = +{ + self.state = STATE_BOTTOM; +}; + +void() button_return = +{ + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, button_done); + self.frame = 0; // use normal textures + if (self.health) + self.takedamage = DAMAGE_YES; // can be shot again +}; + + +void() button_blocked = +{ // do nothing, just don't ome all the way back out +}; + + +void() button_fire = +{ + if (self.state == STATE_UP || self.state == STATE_TOP) + return; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, button_wait); +}; + + +void() button_use = +{ + self.enemy = activator; + button_fire (); +}; + +void() button_touch = +{ + if (other.classname != "player") + return; + self.enemy = other; + button_fire (); +}; + +void() button_killed = +{ + self.enemy = damage_attacker; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + button_fire (); +}; + + +/*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" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +void() func_button = +{ +local float gtemp, ftemp; + + if (self.sounds == 0) + { + precache_sound ("buttons/airbut1.wav"); + self.noise = "buttons/airbut1.wav"; + } + if (self.sounds == 1) + { + precache_sound ("buttons/switch21.wav"); + self.noise = "buttons/switch21.wav"; + } + if (self.sounds == 2) + { + precache_sound ("buttons/switch02.wav"); + self.noise = "buttons/switch02.wav"; + } + if (self.sounds == 3) + { + precache_sound ("buttons/switch04.wav"); + self.noise = "buttons/switch04.wav"; + } + + SetMovedir (); + + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + setmodel (self, self.model); + + self.blocked = button_blocked; + self.use = button_use; + + if (self.health) + { + self.max_health = self.health; + self.th_die = button_killed; + self.takedamage = DAMAGE_YES; + } + else + self.touch = button_touch; + + if (!self.speed) + self.speed = 40; + if (!self.wait) + self.wait = 1; + if (!self.lip) + self.lip = 4; + + self.state = STATE_BOTTOM; + + self.pos1 = self.origin; + vector movedir_fabs = { fabs(self.movedir[0]), fabs(self.movedir[1]), fabs(self.movedir[2]) }; + self.pos2 = self.pos1 + ((movedir_fabs * self.size) - self.lip) * self.movedir; +}; + diff --git a/quakec_hipnotic/client.qc b/quakec_hipnotic/client.qc new file mode 100644 index 0000000..b74178c --- /dev/null +++ b/quakec_hipnotic/client.qc @@ -0,0 +1,2061 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +// prototypes +void () W_WeaponFrame; +void() W_SetCurrentAmmo; +void() player_pain; +void() player_stand1; +void (vector org) spawn_tfog; +void (vector org, entity death_owner) spawn_tdeath; + +float modelindex_eyes, modelindex_player, modelindex_hammer; + + +/* +============================================================================= + + LEVEL CHANGING / INTERMISSION + +============================================================================= +*/ + +float intermission_running; +float intermission_exittime; + +/*QUAKED info_intermission (1 0.5 0.5) (-16 -16 -16) (16 16 16) +This is the camera point for the intermission. +Use mangle instead of angle, so you can set pitch or roll as well as yaw. 'pitch roll yaw' +*/ +void() info_intermission = +{ +}; + + + +void() SetChangeParms = +{ + if (self.health <= 0 || deathmatch) + { + SetNewParms (); + return; + } + +//JIM +// remove items + self.items = self.items - (self.items & + (IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD ) ); +//MED + self.items2 = self.items2 - (self.items2 & + (HIP_IT_WETSUIT | HIP_IT_EMPATHY_SHIELDS ) ); +//MED + self.gravity = 1.0; + +// cap super health + if (self.health > self.max_health) + self.health = self.max_health; + if (self.health < self.max_health / 2) + self.health = self.max_health / 2; + parm1 = self.items; + parm2 = self.health; + parm3 = self.armorvalue; + if (self.ammo_shells < 25) + parm4 = 25; + else + parm4 = self.ammo_shells; + parm5 = self.ammo_nails; + parm6 = self.ammo_rockets; + parm7 = self.ammo_cells; + parm8 = self.weapon; + parm9 = self.armortype * 100; +}; + +void() SetNewParms = +{ + parm1 = IT_SHOTGUN | IT_AXE; + if (skill == 3 && !deathmatch) + parm2 = 50; + else + parm2 = 100; + parm3 = 0; + parm4 = 25; + parm5 = 0; + parm6 = 0; + parm7 = 0; + parm8 = 1; + parm9 = 0; +}; + +void() DecodeLevelParms = +{ +//HIPNOTIC + if (world.model == "maps/start.bsp") + SetNewParms (); // take away all stuff on starting new episode + if (world.model == "maps/hip1m1.bsp") + SetNewParms (); // take away all stuff on starting new episode + if (world.model == "maps/hip2m1.bsp") + SetNewParms (); // take away all stuff on starting new episode + if (world.model == "maps/hip3m1.bsp") + SetNewParms (); // take away all stuff on starting new episode + + self.items = parm1; + self.health = parm2; + self.armorvalue = parm3; + self.ammo_shells = parm4; + self.ammo_nails = parm5; + self.ammo_rockets = parm6; + self.ammo_cells = parm7; + self.weapon = parm8; + self.armortype = parm9 * 0.01; +}; + +/* +============ +FindIntermission + +Returns the entity to view from +============ +*/ +entity() FindIntermission = +{ + local entity spot; + local float cyc; + +// look for info_intermission first + spot = find (world, classname, "info_intermission"); + if (spot) + { // pick a random one + cyc = random() * 4; + while (cyc > 1) + { + spot = find (spot, classname, "info_intermission"); + if (!spot) + spot = find (spot, classname, "info_intermission"); + cyc = cyc - 1; + } + return spot; + } + +// then look for the start position + spot = find (world, classname, "info_player_start"); + if (spot) + return spot; + +// testinfo_player_start is only found in regioned levels + spot = find (world, classname, "testplayerstart"); + if (spot) + return spot; + + objerror ("FindIntermission: no spot"); +}; + + +string nextmap; +void() GotoNextMap = +{ + if (cvar("samelevel")) // if samelevel is set, stay on same level + changelevel (mapname); + else + changelevel (nextmap); +}; + +void() finale_transition = +{ + if (!coop) { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + } else { + changelevel("start"); + } +} + +void() finale_check = +{ + if (finaleFinished()) { + self.nextthink = time + 5; + self.think = finale_transition; + } else { + self.nextthink = time + 0.1; + } +} + +void() ExitIntermission = +{ +// skip any text in deathmatch + if (deathmatch) + { + GotoNextMap (); + return; + } + + intermission_exittime = time + 1; + intermission_running = intermission_running + 1; + +// +// run some text if at the end of an episode +// + if (intermission_running == 2) + { + if (world.model == "maps/e1m7.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + if (!cvar("registered")) + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e1_shareware"); + } + else + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e1"); + } + return; + } + else if (world.model == "maps/e2m6.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e2"); + return; + } + else if (world.model == "maps/e3m6.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e3"); + return; + } + else if (world.model == "maps/e4m7.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e4"); + return; + } + +//HIPNOTIC + if (world.model == "maps/hip1m4.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 6); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, SVC_FINALE); +/* + ************************************** + Deep within the bowels of the + Research Facility, you discover the + passage that the followers of Quake + have used to enter our world. + The bastards used some type of + gigantic teleporter to overload + one of our own slipgates! As long as + this portal exists, Earth will never + be safe from Quake's cruel minions. + + If you can find the source of the + portal's power, you can shut it + down--possibly forever! With only a + moment's consideration for your own + safety, you re-enter the dark domain, + knowing Hell would be a better fate + than experiencing the reign of Quake. +*/ + WriteString (MSG_ALL, "$qc_finale_hip1" ); + //WriteString (MSG_ALL, "If you can find the source of the\nportal's power, you can shut it\ndown--possibly forever! With only a\nmoment's consideration for your own\nsafety, you re-enter the dark domain,\nknowing Hell would be a better fate\nthan experiencing the reign of Quake." ); + return; + } + else if (world.model == "maps/hip2m5.bsp") + { +/* + ************************************** + After destroying the power generator, + you pass beyond the gate of Mortum's + Keep. A wave of nausea suddenly flows + over you and you find yourself cast + out into a liquid void. You float + lifelessly, yet aware, in a lavender + sea of energy. + + After what seems like an eternity, + you feel the presence of a diabolical + intelligence. You are held helpless + for a moment as your mind is open to + that of Armagon--Quake's General and + master of this realm. Recognizing + you as the one who foiled his + attempt to conquer Earth, a hellish + howl fills your mind and blots out + all consciousness. When you awake, + you find yourself on the shores of + reality, but in a time and place + unknown to you. +*/ + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 6); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_hip2" ); + //WriteString (MSG_ALL, "After what seems like an eternity,\nyou feel the presence of a diabolical\nintelligence. You are held helpless\nfor a moment as your mind is open to\nthat of Armagon--Quake's General and\nmaster of this realm. Recognizing\nyou as the one who foiled his\nattempts to conquer Earth, a hellish\nhowl fills your mind and blots out\nall consciousness. When you awake,\nyou find yourself on the shores of\nreality, but in a time and place\nunknown to you." ); + return; + } + else if (world.model == "maps/hipend.bsp") + { +/* + ************************************** + After the last echoes of Armagon's + death yell fade away, you breathe a + heavy sigh of relief. With the loss + of his magic, Armagon's fortress + begins to collapse. The rift he + created to send his grisly troops + through time slowly closes and seals + itself forever. In the chaos that + ensues, a wall collapses, revealing + one remaining time portal. With your + chances to escape rapidly growing + slim, you race for the portal, + mindless of your destination. In a + flash of light, you find yourself + back at Command HQ, safe and sound. + + Congratulations! You are victorious! + The minions of Quake have once again + fallen before your mighty hand. + Is this the last you will see of + Quake's hellions? + + Only time will tell... +*/ + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_hipend" ); + //WriteString (MSG_ALL, "Congratulations! You are victorious!\nThe minions of Quake have once again\nfallen before your mighty hand.\nIs this the last you will see of\nQuake's hellions?\n\nOnly time will tell..." ); + //intermission_exittime = time + 10000000; // never allow exit + + if (campaign && world.model == "maps/hipend.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_HIPEND"); + if (skill == 3) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_HIPEND_NIGHTMARE"); + } + } + + return; + } + GotoNextMap(); + } + + if (intermission_running == 3) + { + if (!cvar("registered")) + { // shareware episode has been completed, go to sell screen + WriteByte (MSG_ALL, SVC_SELLSCREEN); + return; + } + + if ( (serverflags&15) == 15) + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_all_runes"); + return; + } + +//HIPNOTIC + if (world.model == "maps/hip1m4.bsp") + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_hip1m4" ); + return; + } + else if (world.model == "maps/hip2m5.bsp") + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_hip2m5" ); + return; + } + else if (world.model == "maps/hipend.bsp") + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_hipend2" ); + intermission_exittime = time + 10000000; // never allow exit + + // instead of sitting here forever, run the quake ex credits and send the user back to start + local entity timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_check; + return; + } + } + + GotoNextMap(); +}; + +/* +============ +IntermissionThink + +When the player presses attack or jump, change to the next level +============ +*/ +void() IntermissionThink = +{ + if (time < intermission_exittime) + return; + + if (!self.button0 && !self.button1 && !self.button2) + return; + + ExitIntermission (); +}; + +void() execute_changelevel = +{ + local entity pos; + + intermission_running = 1; + +// enforce a wait time before allowing changelevel + if (deathmatch) + intermission_exittime = time + 5; + else + intermission_exittime = time + 2; + + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 9); + WriteByte (MSG_ALL, 3); + + pos = FindIntermission (); + + other = find (world, classname, "player"); + while (other != world) + { + other.view_ofs = '0 0 0'; + other.angles = other.v_angle = pos.mangle; + other.fixangle = TRUE; // turn this way immediately + other.nextthink = time + 0.5; + other.takedamage = DAMAGE_NO; + other.solid = SOLID_NOT; + other.movetype = MOVETYPE_NONE; + other.modelindex = 0; + setorigin (other, pos.origin); + other = find (other, classname, "player"); + } + + WriteByte (MSG_ALL, SVC_INTERMISSION); + + if (campaign && world.model == "maps/hip1m4.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_HIP1M4"); + } + else if (campaign && world.model == "maps/hip2m5.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_HIP2M5"); + } + + if (world.model == "maps/hip1m2.bsp" && nextmap == "hip1m5") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_HIP1M5"); + } + else if (world.model == "maps/hip2m1.bsp" && nextmap == "hip2m6") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_HIP2M6"); + } + else if (world.model == "maps/hip3m3.bsp" && nextmap == "hipdm1") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_HIPDM1"); + } +}; + + +void() changelevel_touch = +{ + local entity pos; + + if (other.classname != "player") + return; + + if ((cvar("noexit") == 1) || ((cvar("noexit") == 2) && (mapname != "start"))) + { + T_Damage (other, self, self, 50000); + return; + } + + if (coop || deathmatch) + { + bprint("$qc_exited", other.netname); + + } + + nextmap = self.map; + + SUB_UseTargets (); + + if ( (self.spawnflags & 1) && (deathmatch == 0) ) + { // NO_INTERMISSION + GotoNextMap(); + return; + } + + self.touch = SUB_Null; + +// we can't move people right now, because touch functions are called +// in the middle of C movement code, so set a think time to do it + self.think = execute_changelevel; + self.nextthink = time + 0.1; +}; + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ +void() trigger_changelevel = +{ + if (!self.map) + objerror ("chagnelevel trigger doesn't have map"); + + InitTrigger (); + self.touch = changelevel_touch; +}; + + +/* +============================================================================= + + PLAYER GAME EDGE FUNCTIONS + +============================================================================= +*/ + +void() set_suicide_frame; + +// called by ClientKill and DeadThink +void() respawn = +{ + if (coop) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue (self); + // get the spawn parms as they were at level start + setspawnparms (self); + // respawn + PutClientInServer (); + } + else if (deathmatch) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue (self); + // set default spawn parms + SetNewParms (); + // respawn + PutClientInServer (); + } + else + { // restart the entire server + cvar_set("campaign", ftos(campaign)); + localcmd ("restart\n"); + } +}; + + +/* +============ +ClientKill + +Player entered the suicide command +============ +*/ +void() ClientKill = +{ + bprint("$qc_suicides", self.netname); + set_suicide_frame (); + self.modelindex = modelindex_player; + self.frags = self.frags - 2; // extra penalty + respawn (); +}; + +/* +============ +PlayerVisibleToSpawnPoint + +Returns true if player can see this point +============ +*/ +float PlayerVisibleToSpawnPoint( entity point ) { + local vector spot1, spot2; + local entity player = find( world, classname, "player" ); + while ( player ) { + if ( player.health > 0 ) { + spot1 = point.origin + player.view_ofs; + spot2 = player.origin + player.view_ofs; + + traceline( spot1, spot2, TRUE, point ); + if ( trace_fraction >= 1.0f ) { + return TRUE; + } + } + + player = find( player, classname, "player" ); + } + + return FALSE; +} + +float IDEAL_DIST_FROM_DM_SPAWN_POINT = 384; +float MIN_DIST_FROM_DM_SPAWN_POINT = 84; + +/* +============ +SelectSpawnPoint + +Returns the entity to spawn at +============ +*/ +entity SelectSpawnPoint(float forceSpawn) { + local entity spot, thing; + local float numspots, totalspots; + local float pcount; + local entity spots; + + numspots = 0; + totalspots = 0; + + // testinfo_player_start is only found in regioned levels + spot = find( world, classname, "testplayerstart" ); + if ( spot ) + return spot; + + // choose a info_player_deathmatch point + if ( coop ) { + lastspawn = find( lastspawn, classname, "info_player_coop" ); + + if ( lastspawn == world ) { + lastspawn = find( lastspawn, classname, "info_player_start" ); + } + + if ( lastspawn != world ) { + return lastspawn; + } + } else if ( deathmatch ) { + + // find all spots that don't have visible players nearby + spots = world; + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + totalspots = totalspots + 1; + + thing = findradius( spot.origin, IDEAL_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { + if ( PlayerVisibleToSpawnPoint( spot ) ) { + pcount = pcount + 1; + } + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + + totalspots = totalspots - 1; + + // on small maps with few spawn points, our "ideal" spawn conditions may not be possible to meet + // so fallback to just trying to pick a point without a player on top of it, so we don't start + // a spawn frag loop + if ( numspots == 0 ) { + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + thing = findradius( spot.origin, MIN_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + } + + // uncomment to force a deferred spawn + // if (forceSpawn == FALSE) return world; + + if ( !numspots ) { + if (forceSpawn == FALSE) { + return world; + } + + // no spots available so just pick one at random + totalspots = rint( ( random() * totalspots ) ); + spot = find( world, classname, "info_player_deathmatch" ); + + while( totalspots > 0 ) { + totalspots = totalspots - 1; + spot = find( spot, classname, "info_player_deathmatch" ); + } + return spot; + } + + // Generate a random number between 1 and numspots + numspots = numspots - 1; + + numspots = rint( ( random() * numspots ) ); + + spot = spots; + while( numspots > 0 ) { + spot = spot.goalentity; + numspots = numspots - 1; + } + + return spot; + } + + if ( serverflags ) { // return with a rune to start + spot = find( world, classname, "info_player_start2" ); + + if ( spot ) { + return spot; + } + } + + spot = find( world, classname, "info_player_start" ); + + if ( !spot ) { + error( "PutClientInServer: no info_player_start on level" ); + } + + return spot; +}; + +/* +=========== +PutClientInServer + +called each time a player is spawned +============ +*/ +void() DecodeLevelParms; +void() PlayerDie; + + +void() PutClientInServer = +{ + local entity spot; + + spot = SelectSpawnPoint (); + + self.classname = "player"; + if (skill == 3 && !deathmatch) + self.health = 50; + else + self.health = 100; + self.takedamage = DAMAGE_AIM; + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_WALK; + self.show_hostile = 0; + if (skill == 3 && !deathmatch) + self.max_health = 50; + else + self.max_health = 100; + self.flags = FL_CLIENT; + self.air_finished = time + 12; + self.dmg = 2; // initial water damage + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.invisible_finished = 0; + self.invincible_finished = 0; + self.effects = 0; + self.invincible_time = 0; + //JIM + self.wetsuit_finished = 0; + //MED + self.empathy_finished = 0; + //MED + self.items2 = 0; + self.gravity = 1.0; + + if ( coop ) { + self.team = TEAM_HUMANS; + } + + DecodeLevelParms (); + + W_SetCurrentAmmo (); + + self.attack_finished = time; + self.th_pain = player_pain; + self.th_die = PlayerDie; + + self.deadflag = DEAD_NO; +// paustime is set by teleporters to keep the player from moving a while + self.pausetime = 0; + +// spot = SelectSpawnPoint (); + + self.origin = spot.origin + '0 0 1'; + self.angles = spot.angles; + self.fixangle = TRUE; // turn this way immediately + + //JIM + // Clear out velocity so you're not launched into the air + // when you respawn. + self.velocity = '0 0 0'; + +// oh, this is a hack! + setmodel (self, "progs/playham.mdl"); + modelindex_hammer = self.modelindex; + + setmodel (self, "progs/eyes.mdl"); + modelindex_eyes = self.modelindex; + + setmodel (self, "progs/player.mdl"); + modelindex_player = self.modelindex; + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + + self.view_ofs = '0 0 22'; + + player_stand1 (); + + if (deathmatch || coop) + { + makevectors(self.angles); + spawn_tfog (self.origin + v_forward*20); + } + + spawn_tdeath (self.origin, self); + + stuffcmd(self, "-attack\n"); // prevent shooting after respawning +}; + + +/* +============================================================================= + + QUAKED FUNCTIONS + +============================================================================= +*/ + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 24) +The normal starting point for a level. +*/ +void() info_player_start = +{ +}; + + +/*QUAKED info_player_start2 (1 0 0) (-16 -16 -24) (16 16 24) +Only used on start map for the return point from an episode. +*/ +void() info_player_start2 = +{ +}; + + +/* +saved out by quaked in region mode +*/ +void() testplayerstart = +{ +}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for deathmatch games +*/ +void() info_player_deathmatch = +{ +}; + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for coop games +*/ +void() info_player_coop = +{ +}; + +/* +=============================================================================== + +RULES + +=============================================================================== +*/ + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void() NextLevel = +{ + local entity o; + +//HIPNOTIC +//Commented out so that timelimit and fraglimit work on start map + /* + if (mapname == "start") + { + if (!cvar("registered")) + { + mapname = "e1m1"; + } + else if (!(serverflags & 1)) + { + mapname = "e1m1"; + serverflags = serverflags | 1; + } + else if (!(serverflags & 2)) + { + mapname = "e2m1"; + serverflags = serverflags | 2; + } + else if (!(serverflags & 4)) + { + mapname = "e3m1"; + serverflags = serverflags | 4; + } + else if (!(serverflags & 8)) + { + mapname = "e4m1"; + serverflags = serverflags - 7; + } + + o = spawn(); + o.map = mapname; + } + else + { +*/ + // find a trigger changelevel + o = find(world, classname, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (!o) + { + mapname = "start"; + o = spawn(); + o.map = mapname; + } +// } + + nextmap = o.map; + gameover = TRUE; + + if (o.nextthink < time) + { + o.think = execute_changelevel; + o.nextthink = time + 0.1; + } +}; + +/* +============ +CheckRules + +Exit deathmatch games upon conditions +============ +*/ +void() CheckRules = +{ + local float timelimit; + local float fraglimit; + + if (gameover) // someone else quit the game already + return; + + timelimit = cvar("timelimit") * 60; + fraglimit = cvar("fraglimit"); + + if (timelimit && time >= timelimit) + { + NextLevel (); + return; + } + + if (fraglimit && self.frags >= fraglimit) + { + NextLevel (); + return; + } +}; + +//============================================================================ + +void() PlayerDeathThink = +{ + local entity old_self; + local float forward; + + if ((self.flags & FL_ONGROUND)) + { + forward = vlen (self.velocity); + forward = forward - 20; + if (forward <= 0) + self.velocity = '0 0 0'; + else + self.velocity = forward * normalize(self.velocity); + } + + if (self.spawn_deferred) + { + local entity spot; + + spot = SelectSpawnPoint(FALSE); + //dprint("time {} >= self.spawn_deferred {}\n", ftos(time), ftos(self.spawn_deferred)); + if (spot != world || time >= self.spawn_deferred) { + respawn(); + } + + return; + } + + +// wait for all buttons released + if (self.deadflag == DEAD_DEAD) + { + if (self.button2 || self.button1 || self.button0) + return; + self.deadflag = DEAD_RESPAWNABLE; + return; + } + +// wait for any button down + if (!self.button2 && !self.button1 && !self.button0) + return; + + self.button0 = 0; + self.button1 = 0; + self.button2 = 0; + respawn(); +}; + + +void() PlayerJump = +{ + local vector start, end; + + if (self.flags & FL_WATERJUMP) + return; + + if (self.waterlevel >= 2) + { + if (self.watertype == CONTENT_WATER) + self.velocity_z = 100; + else if (self.watertype == CONTENT_SLIME) + self.velocity_z = 80; + else + self.velocity_z = 50; + +// play swiming sound + if (self.swim_flag < time) + { + self.swim_flag = time + 1; + if (random() < 0.5) + sound (self, CHAN_BODY, "misc/water1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_BODY, "misc/water2.wav", 1, ATTN_NORM); + } + + return; + } + + if (!(self.flags & FL_ONGROUND)) + return; + + if ( !(self.flags & FL_JUMPRELEASED) ) + return; // don't pogo stick + + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + + self.flags = self.flags - FL_ONGROUND; // don't stairwalk + + self.button2 = 0; +// player jumping sound + sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM); + self.velocity_z = self.velocity_z + 270; +}; + + +/* +=========== +WaterMove + +============ +*/ +.float dmgtime; + +void() WaterMove = +{ +//dprint (ftos(self.waterlevel)); + if (self.movetype == MOVETYPE_NOCLIP) + return; + if (self.health < 0) + return; + + if (self.waterlevel != 3) + { + if (self.air_finished < time) + sound (self, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM); + else if (self.air_finished < time + 9) + sound (self, CHAN_VOICE, "player/gasp1.wav", 1, ATTN_NORM); + self.air_finished = time + 12; + self.dmg = 2; + } + else if (self.air_finished < time) + { // drown! + if (self.pain_finished < time) + { + self.dmg = self.dmg + 2; + if (self.dmg > 15) + self.dmg = 10; + T_Damage (self, world, world, self.dmg); + self.pain_finished = time + 1; + } + } + + if (!self.waterlevel) + { + if (self.flags & FL_INWATER) + { + // play leave water sound + sound (self, CHAN_BODY, "misc/outwater.wav", 1, ATTN_NORM); + self.flags = self.flags - FL_INWATER; + } + return; + } + + if (self.watertype == CONTENT_LAVA) + { // do damage + if (self.dmgtime < time) + { + if (self.radsuit_finished > time) + self.dmgtime = time + 1; + else + self.dmgtime = time + 0.2; + + T_Damage (self, world, world, 10*self.waterlevel); + } + } + else if (self.watertype == CONTENT_SLIME) + { // do damage + if (self.dmgtime < time && self.radsuit_finished < time) + { + self.dmgtime = time + 1; + T_Damage (self, world, world, 4*self.waterlevel); + } + } + + if ( !(self.flags & FL_INWATER) ) + { + +// player enter water sound + + if (self.watertype == CONTENT_LAVA) + sound (self, CHAN_BODY, "player/inlava.wav", 1, ATTN_NORM); + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/inh2o.wav", 1, ATTN_NORM); + if (self.watertype == CONTENT_SLIME) + sound (self, CHAN_BODY, "player/slimbrn2.wav", 1, ATTN_NORM); + + self.flags = self.flags + FL_INWATER; + self.dmgtime = 0; + } + + if (! (self.flags & FL_WATERJUMP) ) + self.velocity = self.velocity - 0.8*self.waterlevel*frametime*self.velocity; +}; + +void() CheckWaterJump = +{ + local vector start, end; + +// check for a jump-out-of-water + makevectors (self.angles); + start = self.origin; + start_z = start_z + 8; + v_forward_z = 0; + normalize(v_forward); + end = start + v_forward*24; + traceline (start, end, TRUE, self); + if (trace_fraction < 1) + { // solid at waist + start_z = start_z + self.maxs_z - 8; + end = start + v_forward*24; + self.movedir = trace_plane_normal * -50; + traceline (start, end, TRUE, self); + if (trace_fraction == 1) + { // open at eye level + self.flags = self.flags | FL_WATERJUMP; + self.velocity_z = 225; + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + self.teleport_time = time + 2; // safety net + return; + } + } +}; + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +//MED 01/17/97 +void(float num_bubbles) DeathBubbles; +void() PlayerPreThink = +{ + local float mspeed, aspeed; + local float r; + + if (intermission_running) + { + earthquake_prethink(); + IntermissionThink (); // otherwise a button could be missed between + return; // the think tics + } + + if (self.view_ofs == '0 0 0') + return; // intermission or finale + +//JIM + // Kill player on Edge of Oblivion + if ( ( self.origin_z < -1300 ) && (world.model == "maps/hipdm1.bsp") && + ( self.health > 0 ) ) + { + self.deathtype = "falling"; + + if (self.invincible_finished >= time) + { + self.invincible_finished = 0; + self.items = self.items - (self.items & IT_INVULNERABILITY); + self.invincible_time = 0; + self.invincible_finished = 0; + self.effects = self.effects - (self.effects & EF_PENTALIGHT); + } + T_Damage( self, self, world, self.health + 1000 ); + } + + //JIM +// if (!deathmatch) +// { + earthquake_prethink(); +// } + + makevectors (self.v_angle); // is this still used + + CheckRules (); + WaterMove (); + + //JIM + //WETSUIT + if (self.wetsuit_finished > time) + { + if (self.waterlevel==2) + { + self.velocity = self.velocity * 1.25; + } + if (self.waterlevel==3) + { + self.velocity = self.velocity * 1.5; + } + if (self.waterlevel >= 2) + { + // play scuba sound + if (self.swim_flag < time) + { + self.swim_flag = time + 7; + sound (self, CHAN_BODY, "misc/wetsuit.wav", 1, ATTN_NORM); + } + //MED 01/17/97 + else + { + if (fabs(self.swim_flag - time - 6)<0.04) + { + DeathBubbles(1); + } + else if (fabs(self.swim_flag - time - 5.5)<0.04) + { + DeathBubbles(1); + } + else if (fabs(self.swim_flag - time - 5)<0.04) + { + DeathBubbles(1); + } + } + } + + } + + if (self.waterlevel == 2) + CheckWaterJump (); + + if (self.deadflag >= DEAD_DEAD) + { + PlayerDeathThink (); + return; + } + + if (self.deadflag == DEAD_DYING) + return; // dying, so do nothing + + if (self.button2) + { + PlayerJump (); + } + else + self.flags = self.flags | FL_JUMPRELEASED; + +// teleporters can force a non-moving pause time + if (time < self.pausetime) + self.velocity = '0 0 0'; + + if(time > self.attack_finished && self.currentammo == 0 && self.weapon != IT_AXE && self.weapon != IT_MJOLNIR) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + } +}; + +/* +================ +CheckPowerups + +Check for turning off powerups +================ +*/ +void() CheckPowerups = +{ + if (self.health <= 0) + return; + +// invisibility + if (self.invisible_finished) + { +// sound and screen flash when items starts to run out + if (self.invisible_sound < time) + { + sound (self, CHAN_AUTO, "items/inv3.wav", 0.5, ATTN_IDLE); + self.invisible_sound = time + ((random() * 3) + 1); + } + + + if (self.invisible_finished < time + 3) + { + if (self.invisible_time == 1) + { + sprint(self, "$qc_ring_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/inv2.wav", 1, ATTN_NORM); + self.invisible_time = time + 1; + } + + if (self.invisible_time < time) + { + self.invisible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invisible_finished < time) + { // just stopped + self.items = self.items - IT_INVISIBILITY; + self.invisible_finished = 0; + self.invisible_time = 0; + } + + // use the eyes + self.frame = 0; + self.modelindex = modelindex_eyes; + } +//MED 12/04/96 added mjolnir stuff + else if (self.weapon == IT_MJOLNIR) + self.modelindex = modelindex_hammer; // don't use eyes + else + self.modelindex = modelindex_player; // don't use eyes + +// invincibility + if (self.invincible_finished) + { +// sound and screen flash when items starts to run out + if (self.invincible_finished < time + 3) + { + if (self.invincible_time == 1) + { + sprint(self, "$qc_protection_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/protect2.wav", 1, ATTN_NORM); + self.invincible_time = time + 1; + } + + if (self.invincible_time < time) + { + self.invincible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invincible_finished < time) + { // just stopped + self.items = self.items - IT_INVULNERABILITY; + self.invincible_time = 0; + self.invincible_finished = 0; + } + if (self.invincible_finished > time) + self.effects = self.effects | EF_PENTALIGHT; + else + self.effects = self.effects - (self.effects & EF_PENTALIGHT); + } + +// super damage + if (self.super_damage_finished) + { + +// sound and screen flash when items starts to run out + + if (self.super_damage_finished < time + 3) + { + if (self.super_time == 1) + { + sprint(self, "$qc_quad_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/damage2.wav", 1, ATTN_NORM); + self.super_time = time + 1; + } + + if (self.super_time < time) + { + self.super_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.super_damage_finished < time) + { // just stopped + self.items = self.items - IT_QUAD; + self.super_damage_finished = 0; + self.super_time = 0; + } + if (self.super_damage_finished > time) + self.effects = self.effects | EF_QUADLIGHT; + else + self.effects = self.effects - (self.effects & EF_QUADLIGHT); + } + +// suit + if (self.radsuit_finished) + { + self.air_finished = time + 12; // don't drown + +// sound and screen flash when items starts to run out + if (self.radsuit_finished < time + 3) + { + if (self.rad_time == 1) + { + sprint(self, "$qc_biosuit_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/suit2.wav", 1, ATTN_NORM); + self.rad_time = time + 1; + } + + if (self.rad_time < time) + { + self.rad_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.radsuit_finished < time) + { // just stopped + self.items = self.items - IT_SUIT; + self.rad_time = 0; + self.radsuit_finished = 0; + } + } + +//JIM +// wetsuit + if (self.wetsuit_finished) + { + self.air_finished = time + 12; // don't drown + + // sound and screen flash when items starts to run out + if (self.wetsuit_finished < time + 3) + { + if (self.wetsuit_time == 1) + { + sprint (self, "$qc_wetsuit_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/suit2.wav", 1, ATTN_NORM); + self.wetsuit_time = time + 1; + } + + if (self.wetsuit_time < time) + { + self.wetsuit_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.wetsuit_finished < time) + { // just stopped +//MED + self.items2 = self.items2 - HIP_IT_WETSUIT; + self.wetsuit_time = 0; + self.wetsuit_finished = 0; + } + } + +//MED +// empathy shields + if (self.empathy_finished) + { + // sound and screen flash when items starts to run out + if (self.empathy_finished < time + 3) + { + if (self.empathy_time == 1) + { + sprint (self, "$qc_empathy_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/suit2.wav", 1, ATTN_NORM); + self.empathy_time = time + 1; + } + + if (self.empathy_time < time) + { + self.empathy_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.empathy_finished < time) + { // just stopped +//MED + self.items2 = self.items2 - HIP_IT_EMPATHY_SHIELDS; + self.empathy_time = 0; + self.empathy_finished = 0; + } +//MED + if (self.empathy_finished > time) + self.effects = self.effects | EF_DIMLIGHT; + else + self.effects = self.effects - (self.effects & EF_DIMLIGHT); + } +}; + + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void() PlayerPostThink = +{ + local float mspeed, aspeed; + local float r; + + if (self.view_ofs == '0 0 0') + { + earthquake_postthink(); + return; // intermission or finale + } + + //JIM + //WETSUIT + if (self.wetsuit_finished > time) + { + if (self.waterlevel==2) + { + self.velocity = self.velocity * 0.8; + } + if (self.waterlevel==3) + { + self.velocity = self.velocity * 0.66; + } + } + + //JIM +// if (!deathmatch) +// { + earthquake_postthink(); +// } + + if (self.deadflag) + return; + +// do weapon stuff + + W_WeaponFrame (); + +// check to see if player landed and play landing sound + if ((self.jump_flag < -300) && (self.flags & FL_ONGROUND) && (self.health > 0)) + { + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM); + else if (self.jump_flag < -650) + { + T_Damage (self, world, world, 5); + sound (self, CHAN_VOICE, "player/land2.wav", 1, ATTN_NORM); + self.deathtype = "falling"; + } + else + sound (self, CHAN_VOICE, "player/land.wav", 1, ATTN_NORM); + + self.jump_flag = 0; + } + + if (!(self.flags & FL_ONGROUND)) + self.jump_flag = self.velocity_z; + + CheckPowerups (); +}; + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +void() ClientConnect = +{ + bprint("$qc_entered", self.netname); + +// a client connecting during an intermission can cause problems + if (intermission_running) + ExitIntermission (); +}; + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server +============ +*/ +void() ClientDisconnect = +{ + if (gameover) + return; + // if the level end trigger has been activated, just return + // since they aren't *really* leaving + + // let everyone else know + bprint("$qc_left_game", self.netname, ftos(self.frags)); + + sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE); + + self->effects = 0; + set_suicide_frame (); +}; + +/* +=========== +ClientObituary + +called when a player dies +============ +*/ +void(entity targ, entity attacker) ClientObituary = +{ + local float rnum; + local string deathstring, deathstring2; + rnum = random(); + + if (targ.classname == "player") + { + if (attacker.classname == "teledeath") + { + bprint("$qc_telefragged", targ.netname, attacker.owner.netname); + + attacker.owner.frags = attacker.owner.frags + 1; + return; + } + + if (attacker.classname == "teledeath2") + { + bprint("$qc_satans_power", targ.netname); + + targ.frags = targ.frags - 1; + return; + } + + if (attacker.classname == "player") + { + if (targ == attacker) + { + // killed self + attacker.frags = attacker.frags - 1; + + if (targ.weapon == 64 && targ.waterlevel > 1) + { + if (targ.watertype == CONTENT_SLIME) + bprint("$qc_discharge_slime", targ.netname); + + else if (targ.watertype == CONTENT_LAVA) + bprint("$qc_discharge_lava", targ.netname); + + else + bprint("$qc_discharge_water", targ.netname); + + return; + } + if (targ.weapon == 16) + bprint("$qc_suicide_pin", targ.netname); + else if (rnum) + bprint("$qc_suicide_bored", targ.netname); + else + bprint("$qc_suicide_loaded", targ.netname); + return; + } + else if ( (teamplay == 2) && (targ.team == attacker.team) && + (attacker.team != 0) ) + { + if (rnum < 0.25) + bprint("$qc_ff_teammate", attacker.netname); + else if (rnum < 0.50) + bprint("$qc_ff_glasses", attacker.netname); + else if (rnum < 0.75) + bprint("$qc_ff_otherteam", attacker.netname); + else + bprint("$qc_ff_friend", attacker.netname); + + attacker.frags = attacker.frags - 1; + return; + } + else + { + attacker.frags = attacker.frags + 1; +//MED 01/19/97 + if (empathyused == 1) + { + if (random()<0.5) + bprint("$qc_death_empathy1", targ.netname, attacker.netname); + else + bprint("$qc_death_empathy2", targ.netname, attacker.netname); + return; + } + +//MED 11/18/96 + if (targ.dmg_inflictor.classname == "proximity_grenade") + { + if (random()<0.5) + bprint("$qc_death_bomb1", targ.netname, attacker.netname); + else + bprint("$qc_death_bomb2", targ.netname, attacker.netname); + return; + } + + rnum = attacker.weapon; + if (rnum == IT_AXE) + { + bprint("$qc_death_ax", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SHOTGUN) + { + bprint("$qc_death_sg", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SUPER_SHOTGUN) + { + bprint("$qc_death_dbl", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_NAILGUN) + { + bprint("$qc_death_nail", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SUPER_NAILGUN) + { + bprint("$qc_death_sng", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_GRENADE_LAUNCHER) + { + if (targ.health < -40) + { + bprint("$qc_death_gl1", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_gl2", targ.netname, attacker.netname); + return; + } + } + + if (rnum == IT_ROCKET_LAUNCHER) + { + if (attacker.super_damage_finished > 0 && targ.health < -40) + { + rnum = random(); + + if (rnum < 0.3) + { + bprint("$qc_death_rl_quad1", targ.netname, attacker.netname); + return; + } + else if (rnum < 0.6) + { + bprint("$qc_death_rl_quad2", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_rl1", targ.netname, attacker.netname); + return; + } + } + else + { + if (targ.health < -40) + { + bprint("$qc_death_rl2", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_rl3", targ.netname, attacker.netname); + return; + } + } + } + + if (rnum == IT_LIGHTNING) + { + if (attacker.waterlevel > 1) + { + bprint("$qc_death_lg1", targ.netname, attacker.netname); + if (attacker.invincible_finished) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SURVIVE_DISCHARGE"); + } + } + else + bprint("$qc_death_lg2", targ.netname, attacker.netname); + + return; + } +//MED + if (rnum == IT_LASER_CANNON) + { + if (random()<0.5) + { + bprint("$qc_death_laser1", targ.netname, attacker.netname); + } + else + { + bprint("$qc_death_laser2", targ.netname, attacker.netname); + } + } +//MED + if (rnum == IT_MJOLNIR) + { + bprint("$qc_death_hammer", targ.netname, attacker.netname); + } + } + return; + } + else + { + targ.frags = targ.frags - 1; // killed self + rnum = targ.watertype; + + +//JIM + if ( attacker.deathtype ) + { + bprint(attacker.deathtype, targ.netname); + return; + } + + if (rnum == -3) + { + if (random() < 0.5) + bprint ("$qc_death_drown1", targ.netname); + else + bprint ("$qc_death_drown2", targ.netname); + return; + } + else if (rnum == -4) + { + if (random() < 0.5) + bprint ("$qc_death_slime1", targ.netname); + else + bprint ("$qc_death_slime2", targ.netname); + return; + } + else if (rnum == -5) + { + if (targ.health < -15) + { + bprint ("$qc_death_lava1", targ.netname); + return; + } + if (random() < 0.5) + bprint ("$qc_death_lava2", targ.netname); + else + bprint ("$qc_death_lava3", targ.netname); + return; + } + + if (attacker.flags & FL_MONSTER) + { + if (attacker.classname == "monster_army") + bprint ("$qc_ks_grunt", targ.netname); + if (attacker.classname == "monster_demon1") + bprint ("$qc_ks_fiend", targ.netname); + if (attacker.classname == "monster_dog") + bprint ("$qc_ks_rottweiler", targ.netname); + if (attacker.classname == "monster_dragon") + bprint ("$qc_ks_dragon", targ.netname); + if (attacker.classname == "monster_enforcer") + bprint ("$qc_ks_enforcer", targ.netname); + if (attacker.classname == "monster_fish") + bprint ("$qc_ks_rotfish", targ.netname); + if (attacker.classname == "monster_hell_knight") + bprint ("$qc_ks_deathknight", targ.netname); + if (attacker.classname == "monster_knight") + bprint ("$qc_ks_knight", targ.netname); + if (attacker.classname == "monster_ogre") + bprint ("$qc_ks_ogre", targ.netname); + if (attacker.classname == "monster_oldone") + bprint ("$qc_ks_shub", targ.netname); + if (attacker.classname == "monster_shalrath") + bprint ("$qc_ks_vore", targ.netname); + if (attacker.classname == "monster_shambler") + bprint ("$qc_ks_shambler", targ.netname); + if (attacker.classname == "monster_tarbaby") + bprint ("$qc_ks_spawn", targ.netname); + if (attacker.classname == "monster_vomit") + bprint ("$qc_ks_vomitus", targ.netname); + if (attacker.classname == "monster_wizard") + bprint ("$qc_ks_scrag", targ.netname); + if (attacker.classname == "monster_zombie") + bprint ("$qc_ks_zombie", targ.netname); +//MED + if (attacker.classname == "monster_gremlin") + bprint ("$qc_ks_gremlin", targ.netname); +//MED + if (attacker.classname == "monster_scourge") + bprint ("$qc_ks_centroid", targ.netname); +//MED + if (attacker.classname == "monster_armagon") + bprint ("$qc_ks_armagon", targ.netname); + + return; + } + if (attacker.classname == "explo_box") + { + bprint ("$qc_ks_blew_up", targ.netname); + return; + } + if (attacker.solid == SOLID_BSP && attacker != world) + { + bprint ("$qc_death_squish", targ.netname); + return; + } + if (targ.deathtype == "falling") + { + targ.deathtype = ""; + bprint ("$qc_death_fall", targ.netname); + return; + } + if (attacker.classname == "trap_shooter" || attacker.classname == "trap_spikeshooter") + { + bprint ("$qc_ks_spiked", targ.netname); + return; + } + if (attacker.classname == "fireball") + { + bprint ("$qc_ks_lavaball", targ.netname); + return; + } + if (attacker.classname == "trigger_changelevel") + { + bprint ("$qc_ks_tried_leave", targ.netname); + return; + } + + bprint ("$qc_death_died", targ.netname); + } + } +}; diff --git a/quakec_hipnotic/combat.qc b/quakec_hipnotic/combat.qc new file mode 100644 index 0000000..04c013f --- /dev/null +++ b/quakec_hipnotic/combat.qc @@ -0,0 +1,366 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +void() T_MissileTouch; +void() info_player_start; +void(entity targ, entity attacker) ClientObituary; + +void() monster_death_use; + +//============================================================================ + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +float(entity targ, entity inflictor) CanDamage = +{ +// bmodels need special checking because their origin is 0,0,0 + if (targ.movetype == MOVETYPE_PUSH) + { + traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self); + if (trace_fraction == 1) + return TRUE; + if (trace_ent == targ) + return TRUE; + return FALSE; + } + + traceline(inflictor.origin, targ.origin, TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '15 -15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + + return FALSE; +}; + + +/* +============ +Killed +============ +*/ +void(entity targ, entity attacker) Killed = +{ + local entity oself; + + oself = self; + self = targ; + + if (self.health < -99) + self.health = -99; // don't let sbar look bad if a player +//MED + if (self.charmed) + { + self.effects = self.effects - (self.effects & EF_DIMLIGHT); + } +// self.effects = self.effects - EF_BRIGHTFIELD; + + if (self.movetype == MOVETYPE_PUSH || self.movetype == MOVETYPE_NONE) + { // doors, triggers, etc + self.th_die (); + self = oself; + return; + } + + self.enemy = attacker; + +// bump the monster counter + if (self.flags & FL_MONSTER) + { + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); + if (attacker.classname == "player") + { + attacker.frags = attacker.frags + 1; + } + + if (attacker != self && attacker.flags & FL_MONSTER) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FRIENDLY_FIRE"); + } + } + + ClientObituary(self, attacker); + + self.takedamage = DAMAGE_NO; + self.touch = SUB_Null; + + monster_death_use(); + self.th_die (); + + self = oself; +}; + + +/* +============ +T_Damage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +============ +*/ +//MED 01/10/97 added empathyused variable +float empathyused; +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage= +{ + local vector dir; + local entity oldself; + local float save; + local float take; + + if (!targ.takedamage) + return; + + // mal: in Coop, don't let bots hurt human teammates - that would be REALLY annoying... + if ( coop && targ != attacker && targ.classname == "player" && attacker.classname == "player" ) { + if ( attacker.flags & FL_ISBOT && !( targ.flags & FL_ISBOT ) ) { + return; + } + } + + + if (discharged && targ.wetsuit_finished ) + return; +//MED moved damage_attacker down a bit +/* + dprint("netname = "); + dprint(attacker.netname); + dprint(" classname = "); + dprint(attacker.classname); + dprint(" classname = "); + dprint(inflictor.classname); + dprint("\n"); +*/ +// check for quad damage powerup on the attacker + if (attacker.super_damage_finished > time) + damage = damage * 4; + + +//MED +//check for empathy shields + if ((targ.items2 & HIP_IT_EMPATHY_SHIELDS) && !(inflictor.items2 & HIP_IT_EMPATHY_SHIELDS) && (targ != attacker)) + { + empathyused = 1; + damage = damage/2; + T_Damage (attacker,targ,targ,damage); + empathyused = 0; + } +//MED +// used by buttons and triggers to set activator for target firing + damage_attacker = attacker; + +//MED +// used to keep track of what hit us + damage_inflictor = inflictor; + +// save damage based on the target's armor level + + save = ceil(targ.armortype*damage); + if (save >= targ.armorvalue) + { + save = targ.armorvalue; + targ.armortype = 0; // lost all armor + targ.items = targ.items - (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); + } + + targ.armorvalue = targ.armorvalue - save; + take = ceil(damage-save); + +// add to the damage total for clients, which will be sent as a single +// message at the end of the frame +// FIXME: remove after combining shotgun blasts? + if (targ.flags & FL_CLIENT) + { + targ.dmg_take = targ.dmg_take + take; + targ.dmg_save = targ.dmg_save + save; + targ.dmg_inflictor = inflictor; + } + +// figure momentum add + if ( (inflictor != world) && (targ.movetype == MOVETYPE_WALK) ) + { + dir = targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; + dir = normalize(dir); + targ.velocity = targ.velocity + dir*damage*8; + } + +// check for godmode or invincibility + if (targ.flags & FL_GODMODE) + return; + if (targ.invincible_finished >= time) + { + if (self.invincible_sound < time) + { + sound (targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + self.invincible_sound = time + 2; + } + return; + } +//MED + if (targ.items2 & HIP_IT_EMPATHY_SHIELDS) + { + if (self.empathy_sound < time) + { + sound (targ, CHAN_ITEM, "hipitems/empathy2.wav", 1, ATTN_NORM); + self.empathy_sound = time + 0.5; + } + } + +// team play damage avoidance + if ( (teamplay == 1 && targ != attacker) && (targ.team > 0)&&(targ.team == attacker.team) ) + return; + +// do the damage + targ.health = targ.health - take; + + if (targ.health <= 0) + { + Killed (targ, attacker); + return; + } + +// react to the damage + oldself = self; + self = targ; + +//MED 10/17/96 added charmed stuff + if ( (self.flags & FL_MONSTER) && attacker != world) + { + // get mad unless of the same class (except for soldiers) + if (self != attacker && attacker != self.enemy && (self.charmer!=attacker)) + { + if ( (self.classname != attacker.classname) + || (self.classname == "monster_army" ) || (self.classname == "monster_armagon" ) ) + { + if (self.enemy.classname == "player") + self.oldenemy = self.enemy; + self.enemy = attacker; + FoundTarget (); + } + } + } + + if (self.th_pain) + { + self.th_pain (attacker, take); + // nightmare mode monsters don't go into pain frames often + if (skill == 3) + self.pain_finished = time + 5; + } + + self = oldself; +}; + +/* +============ +T_RadiusDamage +============ +*/ +void(entity inflictor, entity attacker, float damage, entity ignore) T_RadiusDamage = +{ + local float points; + local entity head; + local vector org; + + head = findradius(inflictor.origin, damage+40); + + while (head) + { + if (head != ignore) + { + if (head.takedamage) + { + org = head.origin + (head.mins + head.maxs)*0.5; + points = 0.5*vlen (inflictor.origin - org); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (head, inflictor)) + { // shambler takes half damage from all explosions + if (head.classname == "monster_shambler") + T_Damage (head, inflictor, attacker, points*0.5); + else + T_Damage (head, inflictor, attacker, points); + } + } + } + } + head = head.chain; + } +}; + +/* +============ +T_BeamDamage +============ +*/ +void(entity attacker, float damage) T_BeamDamage = +{ + local float points; + local entity head; + + head = findradius(attacker.origin, damage+40); + + while (head) + { + if (head.takedamage) + { + points = 0.5*vlen (attacker.origin - head.origin); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (head, attacker)) + { + if (head.classname == "monster_shambler") + T_Damage (head, attacker, attacker, points*0.5); + else + T_Damage (head, attacker, attacker, points); + } + } + } + head = head.chain; + } +}; diff --git a/quakec_hipnotic/defs.qc b/quakec_hipnotic/defs.qc new file mode 100644 index 0000000..999e851 --- /dev/null +++ b/quakec_hipnotic/defs.qc @@ -0,0 +1,781 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* +============================================================================== + + SOURCE FOR GLOBALVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system globals +// +entity self; +entity other; +entity world; +float time; +float frametime; + +float force_retouch; // force all entities to touch triggers + // next frame. this is needed because + // non-moving things don't normally scan + // for triggers, and when a trigger is + // created (like a teleport trigger), it + // needs to catch everything. + // decremented each frame, so set to 2 + // to guarantee everything is touched +string mapname; + +float deathmatch; +float coop; +float teamplay; + +float serverflags; // propagated from level to level, used to + // keep track of completed episodes + +float total_secrets; +float total_monsters; + +float found_secrets; // number of secrets found +float killed_monsters; // number of monsters killed + + +// spawnparms are used to encode information about clients across server +// level changes +float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16; + +// +// global variables set by built in functions +// +vector v_forward, v_up, v_right; // set by makevectors() + +// set by traceline / tracebox +float trace_allsolid; +float trace_startsolid; +float trace_fraction; +vector trace_endpos; +vector trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; + +entity msg_entity; // destination of single entity writes + +// +// required prog functions +// +void() main; // only for testing + +void() StartFrame; + +void() PlayerPreThink; +void() PlayerPostThink; + +void() ClientKill; +void() ClientConnect; +void() PutClientInServer; // call after setting the parm1... parms +void() ClientDisconnect; + +void() SetNewParms; // called when a client first connects to + // a server. sets parms so they can be + // saved off for restarts + +void() SetChangeParms; // call to set parms for self so they can + // be saved for a level transition + +//================================================ +void end_sys_globals; // flag for structure dumping +//================================================ + +/* +============================================================================== + + SOURCE FOR ENTVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system fields (*** = do not set in prog code, maintained by C code) +// +.float modelindex; // *** model index in the precached list +.vector absmin, absmax; // *** origin + mins / maxs + +.float ltime; // local time for entity +.float movetype; +.float solid; + +.vector origin; // *** +.vector oldorigin; // *** +.vector velocity; +.vector angles; +.vector avelocity; + +.vector punchangle; // temp angle adjust from damage or recoil + +.string classname; // spawn function +.string model; +.float frame; +.float skin; +.float effects; + +.vector mins, maxs; // bounding box extents reletive to origin +.vector size; // maxs - mins + +.void() touch; +.void() use; +.void() think; +.void() blocked; // for doors or plats, called when can't push other + +.float nextthink; +.entity groundentity; + +// stats +.float health; +.float frags; +.float weapon; // one of the IT_SHOTGUN, etc flags +.string weaponmodel; +.float weaponframe; +.float currentammo; +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; + +.float items; // bit flags + +.float takedamage; +.entity chain; +.float deadflag; + +.vector view_ofs; // add to origin to get eye point + + +.float button0; // fire +.float button1; // use +.float button2; // jump + +.float impulse; // weapon changes + +.float fixangle; +.vector v_angle; // view / targeting angle for players +.float idealpitch; // calculated pitch angle for lookup up slopes + + +.string netname; + +.entity enemy; + +.float flags; + +.float colormap; +.float team; + +.float max_health; // players maximum health is stored here + +.float teleport_time; // don't back up + +.float armortype; // save this fraction of incoming damage +.float armorvalue; + +.float waterlevel; // 0 = not in, 1 = feet, 2 = wast, 3 = eyes +.float watertype; // a contents value + +.float ideal_yaw; +.float yaw_speed; + +.entity aiment; + +.entity goalentity; // a movetarget or an enemy + +.float spawnflags; + +.string target; +.string targetname; + +// damage is accumulated through a frame. and sent as one single +// message, so the super shotgun doesn't generate huge messages +.float dmg_take; +.float dmg_save; +.entity dmg_inflictor; + +.entity owner; // who launched a missile +.vector movedir; // mostly for doors, but also used for waterjump + +.string message; // trigger messages + +.float sounds; // either a cd track number or sound number + +.string noise, noise1, noise2, noise3; // contains names of wavs to play + +//================================================ +void end_sys_fields; // flag for structure dumping +//================================================ + +/* +============================================================================== + + VARS NOT REFERENCED BY C CODE + +============================================================================== +*/ + + +// +// constants +// + +float FALSE = 0; +float TRUE = 1; + +// edict.flags +float FL_FLY = 1; +float FL_SWIM = 2; +float FL_CLIENT = 8; // set for all client edicts +float FL_INWATER = 16; // for enter / leave water splash +float FL_MONSTER = 32; +float FL_GODMODE = 64; // player cheat +float FL_NOTARGET = 128; // player cheat +float FL_ITEM = 256; // extra wide size for bonus items +float FL_ONGROUND = 512; // standing on something +float FL_PARTIALGROUND = 1024; // not all corners are valid +float FL_WATERJUMP = 2048; // player jumping out of water +float FL_JUMPRELEASED = 4096; // for jump debouncing +float FL_ISBOT = 8192; // mal: this will be set on bot players +float FL_NO_PLAYERS = 16384; // mal: used by the spawn point editor +float FL_NO_MONSTERS = 32768; // mal: used by the spawn point editor +float FL_NO_BOTS = 65536; // mal: any item marked with this will be ignored by bots. +float FL_OBJECTIVE = 131072; // mal: any item marked with this is an objective of some kind. + +// edict.movetype values +float MOVETYPE_NONE = 0; // never moves +//float MOVETYPE_ANGLENOCLIP = 1; +//float MOVETYPE_ANGLECLIP = 2; +float MOVETYPE_WALK = 3; // players only +float MOVETYPE_STEP = 4; // discrete, not real time unless fall +float MOVETYPE_FLY = 5; +float MOVETYPE_TOSS = 6; // gravity +float MOVETYPE_PUSH = 7; // no clip to world, push and crush +float MOVETYPE_NOCLIP = 8; +float MOVETYPE_FLYMISSILE = 9; // fly with extra size against monsters +float MOVETYPE_BOUNCE = 10; +float MOVETYPE_BOUNCEMISSILE = 11; // bounce with extra size + +// edict.solid values +float SOLID_NOT = 0; // no interaction with other objects +float SOLID_TRIGGER = 1; // touch on edge, but not blocking +float SOLID_BBOX = 2; // touch on edge, block +float SOLID_SLIDEBOX = 3; // touch on edge, but not an onground +float SOLID_BSP = 4; // bsp clip, touch on edge, block + +// range values +float RANGE_MELEE = 0; +float RANGE_NEAR = 1; +float RANGE_MID = 2; +float RANGE_FAR = 3; + +// deadflag values + +float DEAD_NO = 0; +float DEAD_DYING = 1; +float DEAD_DEAD = 2; +float DEAD_RESPAWNABLE = 3; + +// takedamage values + +float DAMAGE_NO = 0; +float DAMAGE_YES = 1; +float DAMAGE_AIM = 2; + +// items +float IT_AXE = 4096; +float IT_SHOTGUN = 1; +float IT_SUPER_SHOTGUN = 2; +float IT_NAILGUN = 4; +float IT_SUPER_NAILGUN = 8; +float IT_GRENADE_LAUNCHER = 16; +float IT_ROCKET_LAUNCHER = 32; +float IT_LIGHTNING = 64; +float IT_EXTRA_WEAPON = 128; + +float IT_SHELLS = 256; +float IT_NAILS = 512; +float IT_ROCKETS = 1024; +float IT_CELLS = 2048; + +float IT_ARMOR1 = 8192; +float IT_ARMOR2 = 16384; +float IT_ARMOR3 = 32768; +//MED 11/02/96 added new hip weapon removed SUPERHEALTH +//float IT_SUPERHEALTH = 65536; + +float IT_KEY1 = 131072; +float IT_KEY2 = 262144; + +float IT_INVISIBILITY = 524288; +float IT_INVULNERABILITY = 1048576; +float IT_SUIT = 2097152; +float IT_QUAD = 4194304; + +// point content values + +float CONTENT_EMPTY = -1; +float CONTENT_SOLID = -2; +float CONTENT_WATER = -3; +float CONTENT_SLIME = -4; +float CONTENT_LAVA = -5; +float CONTENT_SKY = -6; + +float STATE_TOP = 0; +float STATE_BOTTOM = 1; +float STATE_UP = 2; +float STATE_DOWN = 3; + +vector VEC_ORIGIN = '0 0 0'; +vector VEC_HULL_MIN = '-16 -16 -24'; +vector VEC_HULL_MAX = '16 16 32'; + +vector VEC_HULL2_MIN = '-32 -32 -24'; +vector VEC_HULL2_MAX = '32 32 64'; + +// protocol bytes +float SVC_TEMPENTITY = 23; +float SVC_KILLEDMONSTER = 27; +float SVC_FOUNDSECRET = 28; +float SVC_INTERMISSION = 30; +float SVC_FINALE = 31; +float SVC_CDTRACK = 32; +float SVC_SELLSCREEN = 33; + +float SVC_ACHIEVEMENT = 52; + +float TE_SPIKE = 0; +float TE_SUPERSPIKE = 1; +float TE_GUNSHOT = 2; +float TE_EXPLOSION = 3; +float TE_TAREXPLOSION = 4; +float TE_LIGHTNING1 = 5; +float TE_LIGHTNING2 = 6; +float TE_WIZSPIKE = 7; +float TE_KNIGHTSPIKE = 8; +float TE_LIGHTNING3 = 9; +float TE_LAVASPLASH = 10; +float TE_TELEPORT = 11; + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +float CHAN_AUTO = 0; +float CHAN_WEAPON = 1; +float CHAN_VOICE = 2; +float CHAN_ITEM = 3; +float CHAN_BODY = 4; + +float ATTN_NONE = 0; +float ATTN_NORM = 1; +float ATTN_IDLE = 2; +float ATTN_STATIC = 3; + +// update types + +float UPDATE_GENERAL = 0; +float UPDATE_STATIC = 1; +float UPDATE_BINARY = 2; +float UPDATE_TEMP = 3; + +// entity effects + +float EF_BRIGHTFIELD = 1; +float EF_MUZZLEFLASH = 2; +float EF_BRIGHTLIGHT = 4; +float EF_DIMLIGHT = 8; +float EF_QUADLIGHT = 16; +float EF_PENTALIGHT = 32; +float EF_CANDLELIGHT = 64; + + +// messages +float MSG_BROADCAST = 0; // unreliable to all +float MSG_ONE = 1; // reliable to one (msg_entity) +float MSG_ALL = 2; // reliable to all +float MSG_INIT = 3; // write to the init string + +// team numbers for Coop +float TEAM_MONSTERS = 0; +float TEAM_HUMANS = 1; + +// mal: basic colors - based on the Quake color palette. +float COLOR_RED = 251; +float COLOR_GREEN = 184; +float COLOR_BLUE = 208; +float COLOR_YELLOW = 192; +float COLOR_WHITE = 254; +float COLOR_BLACK = 0; +float COLOR_CYAN = 244; +float COLOR_ORANGE = 95; + +float DRAW_AI_SINGLE_FRAME = .1; // mal: use for AI that think every 10th of a second +float DRAW_GAME_SINGLE_FRAME = -1; // mal: use in game code that updates every frame. + +// player expanded settings flags +float PEF_CHANGEONLYNEW = 1; +float PEF_CHANGENEVER = 2; + +enum class BotGoalReturnCode : int { + Error = 0, + Success, + InProgress +}; + +//================================================ + +// +// globals +// +float movedist; +float gameover; // set when a rule exits + +string string_null; // null string, nothing should be held here +float empty_float; + +entity newmis; // launch_spike sets this after spawning it + +entity activator; // the entity that activated a trigger or brush + +entity damage_attacker; // set by T_Damage +float framecount; + +float skill; +float campaign_valid; +float campaign; + +float cheats_allowed; // mal: let's us run cheat commands in DM/Coop games. + +//================================================ + +// +// world fields (FIXME: make globals) +// +.string wad; +.string map; +.float worldtype; // 0=medieval 1=metal 2=base + +//================================================ + +.string killtarget; + +// +// quakeed fields +// +.float light_lev; // not used by game, but parsed by light util +.float style; + + +// +// monster ai +// +.void() th_stand; +.void() th_walk; +.void() th_run; +.void() th_missile; +.void() th_melee; +.void(entity attacker, float damage) th_pain; +.void() th_die; + +.entity oldenemy; // mad at this player before taking damage + +.float speed; + +.float lefty; + +.float search_time; +.float attack_state; + +float AS_STRAIGHT = 1; +float AS_SLIDING = 2; +float AS_MELEE = 3; +float AS_MISSILE = 4; + +// +// player only fields +// +.float walkframe; + +.float attack_finished; +.float pain_finished; + +.float invincible_finished; +.float invisible_finished; +.float super_damage_finished; +.float radsuit_finished; + +.float invincible_time, invincible_sound; +.float invisible_time, invisible_sound; +.float super_time, super_sound; +.float rad_time; +.float fly_sound; + +.float axhitme; + +.float show_hostile; // set to time+0.2 whenever a client fires a + // weapon or takes damage. Used to alert + // monsters that otherwise would let the player go +.float jump_flag; // player jump flag +.float swim_flag; // player swimming sound flag +.float air_finished; // when time > air_finished, start drowning +.float bubble_count; // keeps track of the number of bubbles +.string deathtype; // keeps track of how the player died + +// +// object stuff +// +.string mdl; +.vector mangle; // angle at start + +.vector oldorigin; // only used by secret door + +.float t_length, t_width; + + +// +// doors, etc +// +.vector dest, dest1, dest2; +.float wait; // time from firing to restarting +.float delay; // time from activation to firing +.entity trigger_field; // door's trigger entity +.string noise4; + +// +// monsters +// +.float pausetime; +.entity movetarget; + + +// +// doors +// +.float aflag; +.float dmg; // damage done by door when hit + +// +// misc +// +.float cnt; // misc flag + +// +// subs +// +.void() think1; +.vector finaldest, finalangle; + +// +// triggers +// +.float count; // for counting triggers + + +// +// plats / doors / buttons +// +.float lip; +.float state; +.vector pos1, pos2; // top and bottom positions +.float height; + +// +// sounds +// +.float waitmin, waitmax; +.float distance; +.float volume; + +// +// new to ex +// +.float spawn_deferred; + + +//=========================================================================== + + +// +// builtin functions +// + +void(vector ang) makevectors = #1; // sets v_forward, etc globals +void(entity e, vector o) setorigin = #2; +void(entity e, string m) setmodel = #3; // set movetype and solid first +void(entity e, vector min, vector max) setsize = #4; +// #5 was removed +void() break = #6; +float() random = #7; // returns 0 - 1 +void(entity e, float chan, string samp, float vol, float atten) sound = #8; +vector(vector v) normalize = #9; +void(string e) error = #10; +void(string e) objerror = #11; +float(vector v) vlen = #12; +float(vector v) vectoyaw = #13; +entity() spawn = #14; +void(entity e) remove = #15; + +// sets trace_* globals +// nomonsters can be: +// An entity will also be ignored for testing if forent == test, +// forent->owner == test, or test->owner == forent +// a forent of world is ignored +void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16; + +entity() checkclient = #17; // returns a client to look for +entity(entity start, .string fld, string match) find = #18; +string(string s) precache_sound = #19; +string(string s) precache_model = #20; +void(entity client, string s)stuffcmd = #21; +entity(vector org, float rad) findradius = #22; +void(string s, ...) bprint = #0:ex_bprint; +void(entity client, string s, ...) sprint = #0:ex_sprint; +void(string s) dprint = #25; +string(float f) ftos = #26; +string(vector v) vtos = #27; +void() coredump = #28; // prints all edicts +void() traceon = #29; // turns statment trace on +void() traceoff = #30; +void(entity e) eprint = #31; // prints an entire edict +float(float yaw, float dist) walkmove = #32; // returns TRUE or FALSE +// #33 was removed +float(float yaw, float dist) droptofloor= #34; // TRUE if landed on floor +void(float style, string value) lightstyle = #35; +float(float v) rint = #36; // round to nearest int +float(float v) floor = #37; // largest integer <= v +float(float v) ceil = #38; // smallest integer >= v +// #39 was removed +float(entity e) checkbottom = #40; // true if self is on ground +float(vector v) pointcontents = #41; // returns a CONTENT_* +// #42 was removed +float(float f) fabs = #43; +vector(entity e, float speed) aim = #44; // returns the shooting vector +float(string s) cvar = #45; // return cvar.value +void(string s) localcmd = #46; // put string into local que +entity(entity e) nextent = #47; // for looping through all ents +void(vector o, vector d, float color, float count) particle = #48;// start a particle effect +void() ChangeYaw = #49; // turn towards self.ideal_yaw + // at self.yaw_speed +// #50 was removed +vector(vector v) vectoangles = #51; + +// +// direct client message generation +// +void(float to, float f) WriteByte = #52; +void(float to, float f) WriteChar = #53; +void(float to, float f) WriteShort = #54; +void(float to, float f) WriteLong = #55; +void(float to, float f) WriteCoord = #56; +void(float to, float f) WriteAngle = #57; +void(float to, string s) WriteString = #58; +void(float to, entity s) WriteEntity = #59; + +// +// broadcast client message generation +// + +// void(float f) bWriteByte = #59; +// void(float f) bWriteChar = #60; +// void(float f) bWriteShort = #61; +// void(float f) bWriteLong = #62; +// void(float f) bWriteCoord = #63; +// void(float f) bWriteAngle = #64; +// void(string s) bWriteString = #65; +// void(entity e) bWriteEntity = #66; + +void(float step) movetogoal = #67; + +string(string s) precache_file = #68; // no effect except for -copy +void(entity e) makestatic = #69; +void(string s) changelevel = #70; + +//#71 was removed + +void(string var, string val) cvar_set = #72; // sets cvar.value + +void(entity client, string s, ...) centerprint = #0:ex_centerprint; // sprint, but in middle + +void(vector pos, string samp, float vol, float atten) ambientsound = #74; + +string(string s) precache_model2 = #75; // registered version only +string(string s) precache_sound2 = #76; // registered version only +string(string s) precache_file2 = #77; // registered version only + +void(entity e) setspawnparms = #78; // set parm1... to the + // values at level start + // for coop respawn + +float() finaleFinished = #0:ex_finaleFinished; + +// Plays a sound that only "client" can hear. Useful for announcers... +void localsound( entity client, string sample ) = #0:ex_localsound; + +// Debug rendering functions. +void draw_point( vector point, float colormap, float lifetime, float depthtest ) = #0:ex_draw_point; +void draw_line( vector start, vector end, float colormap, float lifetime, float depthtest ) = #0:ex_draw_line; +void draw_arrow( vector start, vector end, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_arrow; +void draw_ray( vector start, vector direction, float length, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_ray; +void draw_circle( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_circle; +void draw_bounds( vector min, vector max, float colormap, float lifetime, float depthtest ) = #0:ex_draw_bounds; +void draw_worldtext( string s, vector origin, float size, float lifetime, float depthtest ) = #0:ex_draw_worldtext; +void draw_sphere( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_sphere; +void draw_cylinder( vector origin, float halfHeight, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_cylinder; + +// Bot functions that return a value based on "BOT_GOAL_", depending on the status of the goal. +float bot_movetopoint( entity bot, vector point ) = #0:ex_bot_movetopoint; +float bot_followentity( entity bot, entity goal ) = #0:ex_bot_followentity; + +// Function for checking new player configured flags, such as weapon swap behaviour +float CheckPlayerEXFlags( entity playerEnt ) = #0:ex_CheckPlayerEXFlags; + +// Path monsters to the goal using any available nav nodes on the map +float walkpathtogoal( float movedist, vector goal ) = #0:ex_walkpathtogoal; + +float checkextension( string s ) = #99; + +//============================================================================ + +// +// subs.qc +// +void(vector tdest, float tspeed, void() func) SUB_CalcMove; +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt; +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove; +void() SUB_CalcMoveDone; +void() SUB_CalcAngleMoveDone; +void() SUB_Null; +void() SUB_UseTargets; +void() SUB_Remove; + +// +// combat.qc +// +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage; + + +float (entity e, float healamount, float ignore) T_Heal; // health function + +float(entity targ, entity inflictor) CanDamage; diff --git a/quakec_hipnotic/doors.qc b/quakec_hipnotic/doors.qc new file mode 100644 index 0000000..d24cc73 --- /dev/null +++ b/quakec_hipnotic/doors.qc @@ -0,0 +1,798 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +float DOOR_START_OPEN = 1; +float DOOR_DONT_LINK = 4; +float DOOR_GOLD_KEY = 8; +float DOOR_SILVER_KEY = 16; +float DOOR_TOGGLE = 32; + +/* + +Doors are similar to buttons, but can spawn a fat trigger field around them +to open without a touch, and they link together to form simultanious +double/quad doors. + +Door.owner is the master door. If there is only one door, it points to itself. +If multiple doors, all will point to a single one. + +Door.enemy chains from the master door through all doors linked in the chain. + +*/ + +/* +============================================================================= + +THINK FUNCTIONS + +============================================================================= +*/ + +void() door_go_down; +void() door_go_up; + +void() door_blocked = +{ + T_Damage (other, self, self, self.dmg); + +// 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.wait >= 0) + { + if (self.state == STATE_DOWN) + door_go_up (); + else + door_go_down (); + } +}; + + +void() door_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + if (self.spawnflags & DOOR_TOGGLE) + return; // don't come down automatically + self.think = door_go_down; + self.nextthink = self.ltime + self.wait; +}; + +void() door_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() door_go_down = +{ + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + if (self.max_health) + { + self.takedamage = DAMAGE_YES; + self.health = self.max_health; + } + + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, door_hit_bottom); +}; + +void() door_go_up = +{ + if (self.state == STATE_UP) + return; // allready going up + + if (self.state == STATE_TOP) + { // reset top wait time + self.nextthink = self.ltime + self.wait; + return; + } + + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, door_hit_top); + + SUB_UseTargets(); +}; + + +/* +============================================================================= + +ACTIVATION FUNCTIONS + +============================================================================= +*/ + +void() door_fire = +{ + local entity oself; + local entity starte; + + if (self.owner != self) + objerror ("door_fire: self.owner != self"); + +// play use key sound + + if (self.items) + sound (self, CHAN_VOICE, self.noise4, 1, ATTN_NORM); + + self.message = string_null; // no more message + oself = self; + + if (self.spawnflags & DOOR_TOGGLE) + { + if (self.state == STATE_UP || self.state == STATE_TOP) + { + starte = self; + do + { + door_go_down (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + self = oself; + return; + } + } + +// trigger all paired doors + starte = self; + do + { + door_go_up (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + self = oself; +}; + + +void() door_use = +{ + local entity oself; + + self.message = ""; // door message are for touch only + self.owner.message = ""; + self.enemy.message = ""; + oself = self; + self = self.owner; + door_fire (); + self = oself; +}; + + +void() door_trigger_touch = +{ + if (other.health <= 0) + return; + + if (time < self.attack_finished) + return; + self.attack_finished = time + 1; + + activator = other; + + self = self.owner; + door_use (); +}; + + +void() door_killed = +{ + local entity oself; + + oself = self; + self = self.owner; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + door_use (); + self = oself; +}; + + +/* +================ +door_touch + +Prints messages and opens key doors +================ +*/ +void() door_touch = +{ + if (other.classname != "player") + return; + if (self.owner.attack_finished > time) + return; + + self.owner.attack_finished = time + 2; + + if (self.owner.message != "") + { + centerprint (other, self.owner.message); + sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + +// key door stuff + if (!self.items) + return; + +// FIXME: blink key on player's status bar + if ( (self.items & other.items) != self.items ) + { + if (self.owner.items == IT_KEY1) + { + if (world.worldtype == 2) + { + centerprint (other, "$qc_need_silver_keycard"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 1) + { + centerprint (other, "$qc_need_silver_runekey"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 0) + { + centerprint (other, "$qc_need_silver_key"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + } + else + { + if (world.worldtype == 2) + { + centerprint (other, "$qc_need_gold_keycard"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 1) + { + centerprint (other, "$qc_need_gold_runekey"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 0) + { + centerprint (other, "$qc_need_gold_key"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + } + return; + } + + other.items = other.items - self.items; + self.touch = SUB_Null; + if (self.enemy) + self.enemy.touch = SUB_Null; // get paired door + door_use (); +}; + +/* +============================================================================= + +SPAWNING FUNCTIONS + +============================================================================= +*/ + + +entity(vector fmins, vector fmaxs) spawn_field = +{ + local entity trigger; + local vector t1, t2; + + trigger = spawn(); + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.owner = self; + trigger.touch = door_trigger_touch; + + t1 = fmins; + t2 = fmaxs; + setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); + return (trigger); +}; + + +float (entity e1, entity e2) EntitiesTouching = +{ + if (e1.mins_x > e2.maxs_x) + return FALSE; + if (e1.mins_y > e2.maxs_y) + return FALSE; + if (e1.mins_z > e2.maxs_z) + return FALSE; + if (e1.maxs_x < e2.mins_x) + return FALSE; + if (e1.maxs_y < e2.mins_y) + return FALSE; + if (e1.maxs_z < e2.mins_z) + return FALSE; + return TRUE; +}; + + +/* +============= +LinkDoors + + +============= +*/ +void() LinkDoors = +{ + local entity t, starte; + local vector cmins, cmaxs; + + if (self.enemy) + return; // already linked by another door + if (self.spawnflags & 4) + { + self.owner = self.enemy = self; + return; // don't want to link this door + } + + cmins = self.mins; + cmaxs = self.maxs; + + starte = self; + t = self; + + do + { + self.owner = starte; // master door + + if (self.health) + starte.health = self.health; + if (self.targetname) + starte.targetname = self.targetname; + if (self.message != "") + starte.message = self.message; + + t = find (t, classname, self.classname); + if (!t) + { + self.enemy = starte; // make the chain a loop + + // shootable, fired, or key doors just needed the owner/enemy links, + // they don't spawn a field + + self = self.owner; + + if (self.health) + return; + if (self.targetname) + return; + if (self.items) + return; + + self.owner.trigger_field = spawn_field(cmins, cmaxs); + + return; + } + + if (EntitiesTouching(self,t)) + { + if (t.enemy) + objerror ("cross connected doors"); + + self.enemy = t; + self = t; + + if (t.mins_x < cmins_x) + cmins_x = t.mins_x; + if (t.mins_y < cmins_y) + cmins_y = t.mins_y; + if (t.mins_z < cmins_z) + cmins_z = t.mins_z; + if (t.maxs_x > cmaxs_x) + cmaxs_x = t.maxs_x; + if (t.maxs_y > cmaxs_y) + cmaxs_y = t.maxs_y; + if (t.maxs_z > cmaxs_z) + cmaxs_z = t.maxs_z; + } + } while (1 ); + +}; + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). + +Key doors are allways wait -1. + +"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" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +void() func_door = + +{ + + if (world.worldtype == 0) + { + precache_sound ("doors/medtry.wav"); + precache_sound ("doors/meduse.wav"); + self.noise3 = "doors/medtry.wav"; + self.noise4 = "doors/meduse.wav"; + } + else if (world.worldtype == 1) + { + precache_sound ("doors/runetry.wav"); + precache_sound ("doors/runeuse.wav"); + self.noise3 = "doors/runetry.wav"; + self.noise4 = "doors/runeuse.wav"; + } + else if (world.worldtype == 2) + { + precache_sound ("doors/basetry.wav"); + precache_sound ("doors/baseuse.wav"); + self.noise3 = "doors/basetry.wav"; + self.noise4 = "doors/baseuse.wav"; + } + else + { + dprint ("no worldtype set!\n"); + } + if (self.sounds == 0) + { + precache_sound ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = "misc/null.wav"; + self.noise2 = "misc/null.wav"; + } + if (self.sounds == 1) + { + precache_sound ("doors/drclos4.wav"); + precache_sound ("doors/doormv1.wav"); + self.noise1 = "doors/drclos4.wav"; + self.noise2 = "doors/doormv1.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/hydro1.wav"); + precache_sound ("doors/hydro2.wav"); + self.noise2 = "doors/hydro1.wav"; + self.noise1 = "doors/hydro2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/stndr1.wav"); + precache_sound ("doors/stndr2.wav"); + self.noise2 = "doors/stndr1.wav"; + self.noise1 = "doors/stndr2.wav"; + } + if (self.sounds == 4) + { + precache_sound ("doors/ddoor1.wav"); + precache_sound ("doors/ddoor2.wav"); + self.noise1 = "doors/ddoor2.wav"; + self.noise2 = "doors/ddoor1.wav"; + } + + + SetMovedir (); + + self.max_health = self.health; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + self.classname = "door"; + + self.blocked = door_blocked; + self.use = door_use; + + if (self.spawnflags & DOOR_SILVER_KEY) + self.items = IT_KEY1; + if (self.spawnflags & DOOR_GOLD_KEY) + self.items = IT_KEY2; + + if (!self.speed) + self.speed = 100; + if (!self.wait) + self.wait = 3; + if (!self.lip) + self.lip = 8; + if (!self.dmg) + self.dmg = 2; + + self.pos1 = self.origin; + vector movedir_fabs = { fabs(self.movedir[0]), fabs(self.movedir[1]), fabs(self.movedir[2]) }; + self.pos2 = self.pos1 + ((movedir_fabs * self.size) - self.lip) * self.movedir; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if (self.spawnflags & DOOR_START_OPEN) + { + setorigin (self, self.pos2); + self.pos2 = self.pos1; + self.pos1 = self.origin; + } + + self.state = STATE_BOTTOM; + + if (self.health) + { + self.takedamage = DAMAGE_YES; + self.th_die = door_killed; + } + + if (self.items) + self.wait = -1; + + self.touch = door_touch; + +// LinkDoors can't be done until all of the doors have been spawned, so +// the sizes can be detected properly. + self.think = LinkDoors; + self.nextthink = self.ltime + 0.1; +}; + +/* +============================================================================= + +SECRET DOORS + +============================================================================= +*/ + +void() fd_secret_move1; +void() fd_secret_move2; +void() fd_secret_move3; +void() fd_secret_move4; +void() fd_secret_move5; +void() fd_secret_move6; +void() fd_secret_done; + +float SECRET_OPEN_ONCE = 1; // stays open +float SECRET_1ST_LEFT = 2; // 1st move is left of arrow +float SECRET_1ST_DOWN = 4; // 1st move is down from arrow +float SECRET_NO_SHOOT = 8; // only opened by trigger +float SECRET_YES_SHOOT = 16; // shootable even if targeted + + +void () fd_secret_use = +{ + local float temp; + + self.health = 10000; + + // exit if still moving around... + if (self.origin != self.oldorigin) + return; + + self.message = string_null; // no more message + + SUB_UseTargets(); // fire all targets / killtargets + + if (!(self.spawnflags & SECRET_NO_SHOOT)) + { + self.th_pain = SUB_Null; + self.takedamage = DAMAGE_NO; + } + self.velocity = '0 0 0'; + + // Make a sound, wait a little... + + sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.nextthink = self.ltime + 0.1; + + temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1 + makevectors(self.mangle); + + if (!self.t_width) + { + if (self.spawnflags & SECRET_1ST_DOWN) + self. t_width = fabs(v_up * self.size); + else + self. t_width = fabs(v_right * self.size); + } + + if (!self.t_length) + self. t_length = fabs(v_forward * self.size); + + if (self.spawnflags & SECRET_1ST_DOWN) + self.dest1 = self.origin - v_up * self.t_width; + else + self.dest1 = self.origin + v_right * (self.t_width * temp); + + self.dest2 = self.dest1 + v_forward * self.t_length; + SUB_CalcMove(self.dest1, self.speed, fd_secret_move1); + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); +}; + +// Wait after first movement... +void () fd_secret_move1 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move2; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +// Start moving sideways w/sound... +void () fd_secret_move2 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest2, self.speed, fd_secret_move3); +}; + +// Wait here until time to go back... +void () fd_secret_move3 = +{ + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + if (!(self.spawnflags & SECRET_OPEN_ONCE)) + { + self.nextthink = self.ltime + self.wait; + self.think = fd_secret_move4; + } +}; + +// Move backward... +void () fd_secret_move4 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest1, self.speed, fd_secret_move5); +}; + +// Wait 1 second... +void () fd_secret_move5 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move6; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () fd_secret_move6 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done); +}; + +void () fd_secret_done = +{ + if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use; + } + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () secret_blocked = +{ + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; + +/* +================ +secret_touch + +Prints messages +================ +*/ +void() secret_touch = +{ + if (other.classname != "player") + return; + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + + if (self.message) + { + centerprint (other, self.message); + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } +}; + + +/*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot +Basic secret door. Slides back, then to the side. Angle determines direction. +wait = # of seconds before coming back +1st_left = 1st move is left of arrow +1st_down = 1st move is down from arrow +always_shoot = even if targeted, keep shootable +t_width = override WIDTH to move back (or height if going down) +t_length = override LENGTH to move sideways +"dmg" damage to inflict when blocked (2 default) + +If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. +"sounds" +1) medieval +2) metal +3) base +*/ + +void () func_door_secret = +{ + if (self.sounds == 0) + self.sounds = 3; + if (self.sounds == 1) + { + precache_sound ("doors/latch2.wav"); + precache_sound ("doors/winch2.wav"); + precache_sound ("doors/drclos4.wav"); + self.noise1 = "doors/latch2.wav"; + self.noise2 = "doors/winch2.wav"; + self.noise3 = "doors/drclos4.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/airdoor1.wav"); + precache_sound ("doors/airdoor2.wav"); + self.noise2 = "doors/airdoor1.wav"; + self.noise1 = "doors/airdoor2.wav"; + self.noise3 = "doors/airdoor2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/basesec1.wav"); + precache_sound ("doors/basesec2.wav"); + self.noise2 = "doors/basesec1.wav"; + self.noise1 = "doors/basesec2.wav"; + self.noise3 = "doors/basesec2.wav"; + } + + if (!self.dmg) + self.dmg = 2; + + // Magic formula... + self.mangle = self.angles; + self.angles = '0 0 0'; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.classname = "door"; + setmodel (self, self.model); + setorigin (self, self.origin); + + self.touch = secret_touch; + self.blocked = secret_blocked; + self.speed = 50; + self.use = fd_secret_use; + if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use; + self.th_die = fd_secret_use; + } + self.oldorigin = self.origin; + if (!self.wait) + self.wait = 5; // 5 seconds before closing +}; diff --git a/quakec_hipnotic/fight.qc b/quakec_hipnotic/fight.qc new file mode 100644 index 0000000..26a6f21 --- /dev/null +++ b/quakec_hipnotic/fight.qc @@ -0,0 +1,428 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* + +A monster is in fight mode if it thinks it can effectively attack its +enemy. + +When it decides it can't attack, it goes into hunt mode. + +*/ + +float(float v) anglemod; + +void() knight_atk1; +void() knight_runatk1; +void() ogre_smash1; +void() ogre_swing1; + +void() sham_smash1; +void() sham_swingr1; +void() sham_swingl1; + +float() DemonCheckAttack; +void(float side) Demon_Melee; + +void(vector dest) ChooseTurn; + +void() ai_face; + + +float enemy_vis, enemy_infront, enemy_range; +float enemy_yaw; + + +void() knight_attack = +{ + local float len; + +// decide if now is a good swing time + len = vlen(self.enemy.origin+self.enemy.view_ofs - (self.origin+self.view_ofs)); + + if (len<80) + knight_atk1 (); + else + knight_runatk1 (); +}; + +//============================================================================= + +/* +=========== +CheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() CheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + +//MED 10/17/96 added charmed stuff + if ((trace_ent != targ) && (!self.charmed)) + return FALSE; // don't have a clear shot + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (enemy_range == RANGE_MELEE) + { // melee attack + if (self.th_melee) + { + if (self.classname == "monster_knight") + knight_attack (); + else + self.th_melee (); + return TRUE; + } + } + +// missile attack + if (!self.th_missile) + return FALSE; + + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + { + chance = 0.9; + self.attack_finished = 0; + } + else if (enemy_range == RANGE_NEAR) + { + if (self.th_melee) + chance = 0.2; + else + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + if (self.th_melee) + chance = 0.05; + else + chance = 0.1; + } + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (2*random()); + return TRUE; + } + + return FALSE; +}; + + +/* +============= +ai_face + +Stay facing the enemy +============= +*/ +void() ai_face = +{ + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); +}; + +/* +============= +ai_charge + +The monster is in a melee attack, so get as close as possible to .enemy +============= +*/ +float (entity targ) visible; +float(entity targ) infront; +float(entity targ) range; + +void(float d) ai_charge = +{ + ai_face (); + movetogoal (d); // done in C code... +}; + +void() ai_charge_side = +{ + local vector dtemp; + local float heading; + +// aim to the left of the enemy for a flyby + + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); + + makevectors (self.angles); + dtemp = self.enemy.origin - 30*v_right; + heading = vectoyaw(dtemp - self.origin); + + walkmove(heading, 20); +}; + + +/* +============= +ai_melee + +============= +*/ +void() ai_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +void() ai_melee_side = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + ai_charge_side(); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + if (!CanDamage (self.enemy, self)) + return; + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +//============================================================================= + +/* +=========== +SoldierCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() SoldierCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + return FALSE; // don't have a clear shot + + +// missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.4; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (1 + random()); + if (random() < 0.3) + self.lefty = !self.lefty; + + return TRUE; + } + + return FALSE; +}; +//============================================================================= + +/* +=========== +ShamCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() ShamCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + local float enemy_yaw; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_vis) + return FALSE; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + if (vlen(spot1 - spot2) > 600) + return FALSE; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + +// missile attack + if (enemy_range == RANGE_FAR) + return FALSE; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (2 + 2*random()); + return TRUE; +}; + +//============================================================================ + +/* +=========== +OgreCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() OgreCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_vis) + return FALSE; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + +// missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + else if (enemy_range == RANGE_NEAR) + chance = 0.10; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (1 + 2*random()); + return TRUE; +}; diff --git a/quakec_hipnotic/flag.qc b/quakec_hipnotic/flag.qc new file mode 100644 index 0000000..5dc09c7 --- /dev/null +++ b/quakec_hipnotic/flag.qc @@ -0,0 +1,27 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/*QUAKED item_deathball (.3 .3 1) (0 0 0) (32 32 32) +*/ +void() deathball_touch; + +void() item_deathball = +{ + self.touch = deathball_touch; +}; \ No newline at end of file diff --git a/quakec_hipnotic/hip_brk.qc b/quakec_hipnotic/hip_brk.qc new file mode 100644 index 0000000..1a7b282 --- /dev/null +++ b/quakec_hipnotic/hip_brk.qc @@ -0,0 +1,109 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Breakaway walls QuickC program + By Jim Dose' 9/11/96 + +*/ + +float MULTI_USE = 1; +float INVISIBLE = 2; + +void() damagethreshold_killed = + { + self.health = self.max_health; + +// self.solid = SOLID_NOT; + activator = damage_attacker; + self.takedamage = DAMAGE_NO; + SUB_UseTargets (); + self.takedamage = DAMAGE_YES; + + if ( !( self.spawnflags & MULTI_USE ) ) + { + remove( self ); + } + }; + +void() damagethreshold_pain = + { + self.health = self.max_health; + }; + +/*QUAKED trigger_damagethreshold (0 .5 .8) ? MULTI_USE INVISIBLE +Triggers only when a threshold of damage is exceeded. +When used in conjunction with func_breakawaywall, allows +walls that may be destroyed with a rocket blast. + +MULTI_USE tells the trigger to not to remove itself after +being fired. Allows the trigger to be used multiple times. + +INVISIBLE tells the trigger to not be visible. + +"health" specifies how much damage must occur before trigger fires. +Default is 60. + +*/ + +void() trigger_damagethreshold = + +{ + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "damagethreshold"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + if ( self.spawnflags & INVISIBLE ) + { + self.model = string_null; + } + + if (!self.health) + { + self.health = 60; + } + self.max_health = self.health; + self.takedamage = DAMAGE_YES; + + self.blocked = SUB_Null; + self.th_pain = damagethreshold_pain; + self.th_die = damagethreshold_killed; + }; + +/*QUAKED func_breakawaywall (0 .5 .8) ? +Special walltype that removes itself when triggered. +*/ + +void() func_breakawaywall = + { + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "breakaway"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + self.use = SUB_Remove; + }; diff --git a/quakec_hipnotic/hip_expl.qc b/quakec_hipnotic/hip_expl.qc new file mode 100644 index 0000000..03b7bde --- /dev/null +++ b/quakec_hipnotic/hip_expl.qc @@ -0,0 +1,221 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Exploder QuickC program + By Jim Dose' 9/13/96 + +*/ + +float USE_PARTICLES = 1; + +void() BecomeExplosion; + +void() exploder_fire = + { + local entity temp; + + temp = self; + activator = other; + SUB_UseTargets (); + self = temp; + other = self; + if (self.dmg<120) + { + sound (self , CHAN_AUTO, "misc/shortexp.wav", self.volume, self.speed); + } + else + { + sound (self , CHAN_AUTO, "misc/longexpl.wav", self.volume, self.speed); + } + T_RadiusDamage (self, self.owner, self.dmg, other); + if (self.spawnflags & USE_PARTICLES) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + BecomeExplosion(); + }; + +void() exploder_use = + { + if (self.delay) + { + self.nextthink = time + self.delay; + self.delay = 0; + self.think = exploder_fire; + } + else + { + exploder_fire(); + } + }; + +/*QUAKED func_exploder (0.4 0 0) (0 0 0) (8 8 8) particles + Spawns an explosion when triggered. Triggers any targets. + + "dmg" specifies how much damage to cause. Negative values + indicate no damage. Default or 0 indicates 120. + "volume" volume at which to play explosions (default 1.0) + "speed" attenuation for explosions (default normal) +*/ +void() func_exploder = + { + precache_sound ("misc/shortexp.wav"); + precache_sound ("misc/longexpl.wav"); + self.classname = "exploder"; + self.use = exploder_use; + if ( self.dmg == 0 ) + { + self.dmg = 120; + } + if ( self.dmg < 0 ) + { + self.dmg = 0; + } + if ( self.speed == 0 ) + { + self.speed = 1; + } + if ( self.volume == 0 ) + { + self.volume = 1.0; + } + }; + +void() multi_exploder_fire = + { + local entity temp; + local entity expl; + + self.nextthink = time + self.wait; + if (self.state == 0) + { + self.state = 1; + self.duration = time + self.duration; + temp = self; + activator = other; + SUB_UseTargets (); + self = temp; + other = self; + } + if (time > self.duration) + { + remove(self); + return; + } + expl = spawn(); + expl.owner = self.owner; + expl.dmg = self.dmg; + expl.origin_x = self.absmin_x + (random() * (self.absmax_x - self.absmin_x)); + expl.origin_y = self.absmin_y + (random() * (self.absmax_y - self.absmin_y)); + expl.origin_z = self.absmin_z + (random() * (self.absmax_z - self.absmin_z)); + sound (expl , CHAN_VOICE, "misc/shortexp.wav", self.volume, self.speed); + T_RadiusDamage (expl, self.owner, self.dmg, other); + if (self.spawnflags & USE_PARTICLES) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, expl.origin_x); + WriteCoord (MSG_BROADCAST, expl.origin_y); + WriteCoord (MSG_BROADCAST, expl.origin_z); + } + temp = self; + self = expl; + BecomeExplosion(); + self = temp; + }; + +void( vector loc, float rad, float damage, float dur, float pause, float vol) multi_explosion = + { + local entity temp; + + temp = self; + self = spawn(); + self.origin = loc; + self.dmg = damage; + self.duration = dur; + self.wait = pause; + self.owner = world; + self.absmin = self.origin - (rad * '1 1 1'); + self.absmax = self.origin + (rad * '1 1 1'); + self.think = multi_exploder_fire; + self.volume = vol; + multi_exploder_fire(); + self = temp; + }; + +void() multi_exploder_use = + { + if (self.delay) + { + self.nextthink = time + self.delay; + self.delay = 0; + self.think = multi_exploder_fire; + } + else + { + self.think = multi_exploder_fire; + multi_exploder_fire(); + } + }; + +/*QUAKED func_multi_exploder (0.4 0 0) ? + Spawns an explosion when triggered. Triggers any targets. + size of brush determines where explosions will occur. + + "dmg" specifies how much damage to cause from each explosion + Negative values indicate no damage. Default or 0 indicates 120. + "delay" delay before exploding (Default 0 seconds) + "duration" how long to explode for (Default 1 second) + "wait" time between each explosion (default 0.25 seconds) + "volume" volume to play explosion sound at (default 0.5) + "speed" attenuation for explosions (default normal) + +*/ +void() func_multi_exploder = + { + precache_sound ("misc/shortexp.wav"); + precache_sound ("misc/longexpl.wav"); + self.classname = "exploder"; + self.use = multi_exploder_use; + setmodel(self,self.model); + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + self.model = ""; + if ( self.dmg == 0 ) + { + self.dmg = 120; + } + if ( self.dmg < 0 ) + { + self.dmg = 0; + } + if (self.duration == 0) + self.duration = 1.0; + if (self.speed == 0) + self.speed = 1.0; + if (self.volume == 0) + self.volume = 0.5; + if (self.wait == 0) + self.wait = 0.25; + self.state = 0; + }; diff --git a/quakec_hipnotic/hip_part.qc b/quakec_hipnotic/hip_part.qc new file mode 100644 index 0000000..c949d0d --- /dev/null +++ b/quakec_hipnotic/hip_part.qc @@ -0,0 +1,324 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Particle effects QuickC program + By Jim Dose' 9/19/96 + +*/ + +//float START_OFF = 1; +float USE_COUNT = 1; + +void () particlefield_XZ = + { + local vector pos; + local vector start; + local vector end; + + if ( ( self.spawnflags & USE_COUNT ) && + ( counter_GetCount( other ) != self.cnt ) ) + { + return; + } +// dprint( "XZ\n" ); + + self.ltime = time + 0.25; + if ( self.noise ) + { + sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + + // Only show particles if client is visible. + // This helps to keep network traffic down to a minimum. + if (!checkclient() ) + return; + + start = self.dest1 + self.origin; + end = self.dest2 + self.origin; + pos_y = start_y; + pos_z = start_z; + while( pos_z <= end_z ) + { + pos_x = start_x; + while( pos_x <= end_x ) + { + particle ( pos, '0 0 0', self.color, self.count ); + pos_x = pos_x + 16; + } + pos_z = pos_z + 16; + } + }; + +void () particlefield_YZ = + { + local vector pos; + local vector start; + local vector end; + + if ( ( self.spawnflags & USE_COUNT ) && + ( counter_GetCount( other ) != self.cnt ) ) + { + return; + } + +// dprint( "YZ: " ); +// dprint( vtos( self.dest1 ) ); +// dprint( " - " ); +// dprint( vtos( self.dest2 ) ); +// dprint( "\n" ); + self.ltime = time + 0.25; + if ( self.noise ) + { + sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + + // Only show particles if client is visible. + // This helps to keep network traffic down to a minimum. + if (!checkclient() ) + return; + + start = self.dest1 + self.origin; + end = self.dest2 + self.origin; + pos_x = start_x; + pos_z = start_z; + while( pos_z < end_z ) + { + pos_y = start_y; + while( pos_y < end_y ) + { + particle ( pos, '0 0 0', self.color, self.count ); + pos_y = pos_y + 16; + } + pos_z = pos_z + 16; + } + }; + +void () particlefield_XY = + { + local vector pos; + local vector start; + local vector end; + + if ( ( self.spawnflags & USE_COUNT ) && + ( counter_GetCount( other ) != self.cnt ) ) + { + return; + } + +// dprint( "XY\n" ); + self.ltime = time + 0.25; + if ( self.noise ) + { + sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + + // Only show particles if client is visible. + // This helps to keep network traffic down to a minimum. + if (!checkclient() ) + return; + + + start = self.dest1 + self.origin; + end = self.dest2 + self.origin; + pos_x = start_x; + pos_z = start_z; + while( pos_x < end_x ) + { + pos_y = start_y; + while( pos_y < end_y ) + { + particle ( pos, '0 0 0', self.color, self.count ); + pos_y = pos_y + 16; + } + pos_x = pos_x + 16; + } + }; + +void () particlefield_touch = + { + if ( !self.dmg ) + return; + + if ( time > self.ltime) + return; + + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); + }; + +/*QUAKED func_particlefield (0 .5 .8) ? USE_COUNT +Creates a brief particle flash roughly the size of the defining +brush each time it is triggered. + +USE_COUNT when the activator is a func_counter, the field will only + activate when count is equal to "cnt". Same as using a func_oncount + to trigger. + +"cnt" is the count to activate on when USE_COUNT is set. +"color" is the color of the particles. Default is 192 (yellow). +"count" is the density of the particles. Default is 2. +"noise" is the sound to play when triggered. Do not use a looping sound here. +"dmg" is the amount of damage to cause when touched. +*/ + +void() func_particlefield = + { + if ( !self.color ) + { + self.color = 192; + } + if ( self.count == 0 ) + { + self.count = 2; + } + self.classname = "particlefield"; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + setmodel (self, self.model); + self.model = string_null; + + self.origin = ( self.mins + self.maxs ) * 0.5; + setorigin (self, self.origin); + self.dest = self.maxs - self.mins - '16 16 16'; + self.dest1 = self.mins + '8 8 8' - self.origin; + self.dest2 = self.maxs + '7.9 7.9 7.9' - self.origin; + setsize (self, self.mins, self.maxs); + self.touch = particlefield_touch; +// dprint( vtos( self.dest ) ); +// dprint( " " ); + if ( self.dest_x > self.dest_z ) + { + if ( self.dest_y > self.dest_z ) + { +// dprint( "XY1 - " ); +// dprint( ftos( self.cnt ) ); +// dprint( "\n" ); + self.use = particlefield_XY; + self.dest1_z = ( self.dest1_z + self.dest2_z ) / 2; + } + else + { +// dprint( "XZ1 - " ); +// dprint( ftos( self.cnt ) ); +// dprint( "\n" ); + self.use = particlefield_XZ; + self.dest1_y = ( self.dest1_y + self.dest2_y ) / 2; + } + } + else + { + if ( self.dest_y > self.dest_x ) + { +// dprint( "YZ2 - " ); +// dprint( ftos( self.cnt ) ); +// dprint( "\n" ); + self.use = particlefield_YZ; + self.dest1_x = ( self.dest1_x + self.dest2_x ) / 2; + } + else + { +// dprint( "XZ2 - " ); +// dprint( ftos( self.cnt ) ); +// dprint( "\n" ); + self.use = particlefield_XZ; + self.dest1_y = ( self.dest1_y + self.dest2_y ) / 2; + } + } + + if ( self.noise ) + { + precache_sound( self.noise ); + } + self.ltime = time; + }; + +void () blocker_touch = + { + if ( !self.dmg ) + return; + + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); + }; + +void () blocker_use = + { + if ( !self.state ) + { + self.state = 1; + setorigin( self, self.origin - '8000 8000 8000' ); + sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + } + else + { + self.state = 0; + setorigin( self, self.origin + '8000 8000 8000' ); + sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + }; + +/*QUAKED func_togglewall (0 .5 .8) ? START_OFF +Creates a invisible wall that can be toggled on and off. + +START_OFF wall doesn't block until triggered. + +"noise" is the sound to play when wall is turned off. +"noise1" is the sound to play when wall is blocking. +"dmg" is the amount of damage to cause when touched. +*/ + +void() func_togglewall = + { + self.classname = "togglewall"; + self.movetype = MOVETYPE_PUSH; + self.mdl = self.model; + setmodel (self, self.model); + setsize (self, self.mins, self.maxs); + setorigin (self, self.origin); + self.touch = blocker_touch; + self.use = blocker_use; + if ( !self.noise ) + { + self.noise = ("misc/null.wav"); + } + if ( !self.noise1 ) + { + self.noise1 = ("misc/null.wav"); + } + + precache_sound( self.noise ); + precache_sound( self.noise1 ); + + self.solid = SOLID_BSP; + self.model = string_null; + if ( self.spawnflags & START_OFF ) + { + self.state = 0; + setorigin( self, self.origin + '8000 8000 8000' ); + } + else + { + self.state = 1; + sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + } + }; diff --git a/quakec_hipnotic/hip_push.qc b/quakec_hipnotic/hip_push.qc new file mode 100644 index 0000000..2225131 --- /dev/null +++ b/quakec_hipnotic/hip_push.qc @@ -0,0 +1,128 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Pushable walls QuickC program + By Jim Dose' 9/16/96 + +*/ + +void() push_use = + { + local vector delta; + local float x; + local float y; + +// walkmove (other.angles_y, 16 * frametime); +/* + if ( (other.angles_y >= 315) || (other.angles_y < 45)) + { + walkmove ( 0, 16 * frametime); + } + else if ( (other.angles_y >= 45) && (other.angles_y < 135)) + { + walkmove ( 90, 16 * frametime); + } + else if ( (other.angles_y >= 135) && (other.angles_y < 225)) + { + walkmove ( 180, 16 * frametime); + } + else if ( (other.angles_y >= 225) && (other.angles_y < 315)) + { + walkmove ( 270, 16 * frametime); + } + else + { + return; + } +*/ + makevectors(other.angles); + +// x = fabs( v_forward_x ); +// y = fabs( v_forward_y ); + x = fabs( other.velocity_x ); + y = fabs( other.velocity_y ); + dprint( ftos( x ) ); + dprint( ", " ); + dprint( ftos( y ) ); + if ( x > y ) + { + dprint( " x move\n\n\n\n" ); + if ( other.velocity_x > 0 ) + { + walkmove ( 0, 16 * frametime); + } + else + { + walkmove( 180, 16 * frametime ); + } + } + else + { + dprint( " y move\n\n\n\n" ); + if ( other.velocity_y > 0 ) + { + walkmove ( 90, 16 * frametime); + } + else + { + walkmove( 270, 16 * frametime ); + } + } + + delta = self.origin - self.oldorigin; + setorigin (self.owner, self.owner.oldorigin + delta ); + }; + +/*QUAKED func_pushable (0 .5 .8) ? +Pushable walls. +*/ + +void() func_pushable = + { + local entity new; + local vector newsize; + + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "pushablewall"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setmodel (self, self.model); + setorigin( self, self.origin ); + setsize (self, self.mins, self.maxs ); + self.oldorigin = self.origin; + + new = spawn(); + new.owner = self; + new.mangle = self.mangle; + new.angles = self.angles; + + new.classname = "pushablewallproxy"; + new.solid = SOLID_BBOX; + new.movetype = MOVETYPE_STEP; + new.origin = ( self.mins + self.maxs ) * 0.5 + '0 0 1'; + newsize = ( self.maxs - self.mins ) * 0.5; + new.mins = '-1 -1 0' - newsize; + new.maxs = '1 1 -2' + newsize; + setsize( new, new.mins, new.maxs ); + setorigin( new, new.origin ); + new.oldorigin = new.origin; + new.touch = push_use; + }; diff --git a/quakec_hipnotic/hiparma.qc b/quakec_hipnotic/hiparma.qc new file mode 100644 index 0000000..801fde8 --- /dev/null +++ b/quakec_hipnotic/hiparma.qc @@ -0,0 +1,981 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +armagon + +============================================================================== +*/ + + +$cd hipwork\models\armagon +$origin 0 0 24 +$base bodybase.asc + +$skin armab + +$frame lwalk1.asc lwalk2.asc lwalk3.asc lwalk4.asc lwalk5.asc lwalk6.asc lwalk7.asc +$frame lwalk8.asc lwalk9.asc lwalk10.asc lwalk11.asc lwalk12.asc + +$frame lw0fir1.asc lw0fir2.asc lw0fir3.asc lw0fir4.asc lw0fir5.asc lw0fir6.asc +$frame lw0fir7.asc lw0fir8.asc lw0fir9.asc lw0fir10.asc lw0fir11.asc lw0fir12.asc +$frame lw0fir13.asc + +$frame lw1fir1.asc lw1fir2.asc lw1fir3.asc lw1fir4.asc lw1fir5.asc lw1fir6.asc +$frame lw1fir7.asc lw1fir8.asc lw1fir9.asc lw1fir10.asc lw1fir11.asc lw1fir12.asc +$frame lw1fir13.asc lw1fir14.asc lw1fir15.asc + +$frame lw2fir1.asc lw2fir2.asc lw2fir3.asc lw2fir4.asc lw2fir5.asc lw2fir6.asc +$frame lw2fir7.asc lw2fir8.asc lw2fir9.asc lw2fir10.asc lw2fir11.asc lw2fir12.asc + +$frame lsfire1.asc lsfire2.asc lsfire3.asc lsfire4.asc lsfire5.asc lsfire6.asc +$frame lsfire7.asc lsfire8.asc lsfire9.asc lsfire10.asc lsfire11.asc lsfire12.asc + +$frame lstand1.asc lstand2.asc lstand3.asc lstand4.asc lstand5.asc lstand6.asc +$frame lstand7.asc lstand8.asc lstand9.asc lstand10.asc lstand11.asc lstand12.asc +$frame lstand13.asc lstand14.asc lstand15.asc lstand16.asc lstand17.asc +$frame lstand18.asc lstand19.asc lstand20.asc + +$frame tranl1.asc tranl2.asc + +$frame ldie1.asc ldie2.asc ldie3.asc ldie4.asc ldie5.asc ldie6.asc +$frame ldie7.asc ldie8.asc ldie9.asc ldie10.asc ldie11.asc ldie12.asc + +float ATTN_ARMAGON = 0.5; +float SERVO_VOLUME = 0.5; + +void(float ofs, float turn) armagon_launch_missile = + { + local vector src, vec, targ; + local vector ang; + local entity missile; + local float dist; + + ang = self.angles; + ang_y = ang_y + self.fixangle; + if (turn==1) + { + ang_y = ang_y + 165; + } + else if (turn==2) + { + ang_y = ang_y - 165; + } + makevectors(ang); + src = self.origin + '0 0 66' + v_right*ofs + v_forward*84; + targ = self.enemy.origin + self.enemy.view_ofs; + if (skill!=0) + { + dist = vlen(targ-src); + targ = targ + (self.enemy.velocity * (dist/1000)); + } + vec = (targ - src); + vec = normalize(vec); + dist = vec*v_forward; + if (dist 5) + { + self.fixangle = self.fixangle + 9; + } + else if (angdelta < -5) + { + self.fixangle = self.fixangle - 9; + } + else + { + self.fixangle = delta; + } + } + }; +void() armagon_overleft1; +void() armagon_overright1; + +void() armagon_think = + { + local float delta; + local float angdelta; + setorigin (self.trigger_field, self.origin); + self.trigger_field.angles = self.angles; + self.trigger_field.frame = self.frame; + self.trigger_field.angles_y = self.trigger_field.angles_y + self.fixangle; + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + self.ideal_yaw = enemy_yaw; + delta = self.ideal_yaw - self.angles_y; + self.cnt = 0; + if (delta > 180) delta = delta - 360; + if (delta < -180) delta = delta + 360; + if (fabs(delta) > 90) + { + delta = 0; + self.cnt = 1; + } + angdelta = delta-self.fixangle; + armagon_turn(angdelta,delta); +// self.nextthink = time + 0.1; + if (self.health < 0) + return; + if (time > self.super_time) + { + local float r; + + self.super_time = time + 3; + if (random() < 0.5) + { + r = random(); + if (r < 0.25) + sound (self, CHAN_VOICE, "armagon/idle1.wav", 1, ATTN_ARMAGON); + else if (r < 0.5) + sound (self, CHAN_VOICE, "armagon/idle2.wav", 1, ATTN_ARMAGON); + else if (r < 0.75) + sound (self, CHAN_VOICE, "armagon/idle3.wav", 1, ATTN_ARMAGON); + else + sound (self, CHAN_VOICE, "armagon/idle4.wav", 1, ATTN_ARMAGON); + } + } + + if(vlen(self.trigger_field.oldorigin - self.trigger_field.origin) > 50) + { + self.trigger_field.movetype = MOVETYPE_STEP; + } + }; + +void() armagon_walkthink = + { + local float delta; + local float angdelta; + local entity client; + setorigin (self.trigger_field, self.origin); + self.trigger_field.movetype = MOVETYPE_STEP; + self.trigger_field.angles = self.angles; + self.trigger_field.frame = self.frame; + self.trigger_field.angles_y = self.trigger_field.angles_y + self.fixangle; + ChangeYaw(); + delta = 0; + self.cnt = 0; + if (delta > 180) delta = delta - 360; + if (delta < -180) delta = delta + 360; + if (fabs(delta) > 90) + { + delta = 0; + self.cnt = 1; + } + angdelta = delta-self.fixangle; + armagon_turn(angdelta,delta); + if (self.health < 0) + return; + if (time > self.super_time) + { + local float r; + + self.super_time = time + 3; + if (random() < 0.5) + { + r = random(); + if (r < 0.25) + sound (self, CHAN_VOICE, "armagon/idle1.wav", 1, ATTN_ARMAGON); + else if (r < 0.5) + sound (self, CHAN_VOICE, "armagon/idle2.wav", 1, ATTN_ARMAGON); + else if (r < 0.75) + sound (self, CHAN_VOICE, "armagon/idle3.wav", 1, ATTN_ARMAGON); + else + sound (self, CHAN_VOICE, "armagon/idle4.wav", 1, ATTN_ARMAGON); + } + } + client = checkclient (); + if (!client) + return; // current check entity isn't in PVS + if (visible(client)) + { + self.enemy = client; + FoundTarget(); + } + }; + +void() armagon_overleft_think = + { + local float delta; + local float angdelta; + + ChangeYaw(); + walkmove (self.angles_y, 14); + setorigin (self.trigger_field, self.origin); + self.trigger_field.angles = self.angles; + self.trigger_field.frame = self.frame; + self.trigger_field.angles_y = self.trigger_field.angles_y + self.fixangle; + if (self.count == 0) + { + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + self.ideal_yaw = enemy_yaw; + delta = self.ideal_yaw - self.angles_y - 165; + if (delta > 180) delta = delta - 360; + if (delta < -180) delta = delta + 360; + angdelta = delta-self.fixangle; + } + else if (self.count == 1) + { + armagon_launch_missile(40,1); + return; + } + else + { + delta = 0; + angdelta = delta-self.fixangle; + } + armagon_turn(angdelta,delta); +// self.nextthink = time + 0.1; + }; + +void() armagon_overright_think = + { + local float delta; + local float angdelta; + + ChangeYaw(); + walkmove (self.angles_y, 14); + setorigin (self.trigger_field, self.origin); + self.trigger_field.angles = self.angles; + self.trigger_field.frame = self.frame; + self.trigger_field.angles_y = self.trigger_field.angles_y + self.fixangle; + if (self.count == 0) + { + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + self.ideal_yaw = enemy_yaw; + delta = self.ideal_yaw - self.angles_y + 165; + if (delta > 180) delta = delta - 360; + if (delta < -180) delta = delta + 360; + angdelta = delta-self.fixangle; + } + else if (self.count == 1) + { + armagon_launch_missile(-40,2); + return; + } + else + { + delta = 0; + angdelta = delta-self.fixangle; + } + armagon_turn(angdelta,delta); +// self.nextthink = time + 0.1; + }; + +void() armagon_stand_attack; + +void() armagon_stand1 =[ $lstand1.asc, armagon_stand2 ] {ai_stand();armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand2 =[ $lstand2.asc, armagon_stand3 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand3 =[ $lstand3.asc, armagon_stand4 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand4 =[ $lstand4.asc, armagon_stand5 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand5 =[ $lstand5.asc, armagon_stand6 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand6 =[ $lstand6.asc, armagon_stand7 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand7 =[ $lstand7.asc, armagon_stand8 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand8 =[ $lstand8.asc, armagon_stand9 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand9 =[ $lstand9.asc, armagon_stand10 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand10 =[ $lstand10.asc, armagon_stand11 ] {ai_stand();armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand11 =[ $lstand11.asc, armagon_stand12 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand12 =[ $lstand12.asc, armagon_stand13 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand13 =[ $lstand13.asc, armagon_stand14 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand14 =[ $lstand14.asc, armagon_stand15 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand15 =[ $lstand15.asc, armagon_stand16 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand16 =[ $lstand16.asc, armagon_stand17 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand17 =[ $lstand17.asc, armagon_stand18 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand18 =[ $lstand18.asc, armagon_stand19 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand19 =[ $lstand19.asc, armagon_stand20 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_stand20 =[ $lstand20.asc, armagon_stand1 ] {ai_stand();armagon_think();self.nextthink = time + 0.2;}; + +void() armagon_walk1 =[ $lwalk1.asc , armagon_walk2 ] + { + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk2 =[ $lwalk2.asc , armagon_walk3 ] + { + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk3 =[ $lwalk3.asc , armagon_walk4 ] + { + sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON); + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk4 =[ $lwalk4.asc , armagon_walk5 ] + { + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk5 =[ $lwalk5.asc , armagon_walk6 ] + { + sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON); + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk6 =[ $lwalk6.asc , armagon_walk7 ] + { + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk7 =[ $lwalk7.asc , armagon_walk8 ] + { + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk8 =[ $lwalk8.asc , armagon_walk9 ] + { + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk9 =[ $lwalk9.asc , armagon_walk10 ] + { + sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON); + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk10 =[ $lwalk10.asc , armagon_walk11 ] + { + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk11 =[ $lwalk11.asc , armagon_walk12 ] + { + sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON); + movetogoal(14); + armagon_walkthink(); + }; +void() armagon_walk12 =[ $lwalk12.asc , armagon_walk1 ] + { + movetogoal(14); + armagon_walkthink(); + }; + +void() armagon_run1 =[ $lwalk1.asc , armagon_run2 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run2 =[ $lwalk2.asc , armagon_run3 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run3 =[ $lwalk3.asc , armagon_run4 ] + { + sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON); + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run4 =[ $lwalk4.asc , armagon_run5 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run5 =[ $lwalk5.asc , armagon_run6 ] + { + sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON); + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run6 =[ $lwalk6.asc , armagon_run7 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run7 =[ $lwalk7.asc , armagon_run8 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run8 =[ $lwalk8.asc , armagon_run9 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run9 =[ $lwalk9.asc , armagon_run10 ] + { + sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON); + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run10 =[ $lwalk10.asc , armagon_run11 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run11 =[ $lwalk11.asc , armagon_run12 ] + { + sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON); + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + }; +void() armagon_run12 =[ $lwalk12.asc , armagon_run1 ] + { + ChangeYaw(); + RUN_STRAIGHT = 1; + ai_run(14); + armagon_think(); + if ((self.cnt == 1) && (time > self.attack_finished)) + { + local float delta; + delta = self.ideal_yaw - self.angles_y; + if (delta > 180) delta = delta - 360; + if (delta < -180) delta = delta + 360; + if (delta>0) + self.think = armagon_overleft1; + else + self.think = armagon_overright1; + return; + } + if (self.lefty == 1) + { + self.lefty = 0; + self.think = self.th_missile; + } + }; + +void() armagon_watk1 =[ $lw0fir1.asc, armagon_watk2 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk2 =[ $lw0fir2.asc, armagon_watk3 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk3 =[ $lw0fir3.asc, armagon_watk4 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk4 =[ $lw0fir4.asc, armagon_watk5 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk5 =[ $lw0fir5.asc, armagon_watk6 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk6 =[ $lw0fir6.asc, armagon_watk7 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();armagon_launch_missile(40,0);}; +void() armagon_watk7 =[ $lw0fir7.asc, armagon_watk8 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk8 =[ $lw0fir8.asc, armagon_watk9 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk9 =[ $lw0fir9.asc, armagon_watk10 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk10 =[ $lw0fir10.asc, armagon_watk11 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk11 =[ $lw0fir11.asc, armagon_watk13 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();armagon_launch_missile(-40,0);}; +//void() armagon_watk12 =[ $lw0fir12.asc, armagon_watk13 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_watk13 =[ $lw0fir12.asc, armagon_run1 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();SUB_AttackFinished (1.0);}; + +void() armagon_wlaseratk1 =[ $lw0fir1.asc, armagon_wlaseratk2 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk2 =[ $lw0fir2.asc, armagon_wlaseratk3 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk3 =[ $lw0fir3.asc, armagon_wlaseratk4 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk4 =[ $lw0fir4.asc, armagon_wlaseratk5 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk5 =[ $lw0fir5.asc, armagon_wlaseratk6 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk6 =[ $lw0fir6.asc, armagon_wlaseratk7 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();armagon_launch_laser(40);}; +void() armagon_wlaseratk7 =[ $lw0fir7.asc, armagon_wlaseratk8 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk8 =[ $lw0fir8.asc, armagon_wlaseratk9 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk9 =[ $lw0fir9.asc, armagon_wlaseratk10 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk10 =[ $lw0fir10.asc, armagon_wlaseratk11 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk11 =[ $lw0fir11.asc, armagon_wlaseratk13 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();armagon_launch_laser(-40);}; +//void() armagon_wlaseratk12 =[ $lw0fir12.asc, armagon_wlaseratk13 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();}; +void() armagon_wlaseratk13 =[ $lw0fir12.asc, armagon_run1 ] {ChangeYaw();walkmove (self.angles_y, 14);armagon_think();SUB_AttackFinished (1.0);}; + +void() armagon_overleft1 =[ $lw1fir1.asc, armagon_overleft2 ] {self.count=0;armagon_overleft_think();}; +void() armagon_overleft2 =[ $lw1fir2.asc, armagon_overleft3 ] {armagon_overleft_think();}; +void() armagon_overleft3 =[ $lw1fir3.asc, armagon_overleft4 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);armagon_overleft_think();}; +void() armagon_overleft4 =[ $lw1fir4.asc, armagon_overleft5 ] {armagon_overleft_think();}; +void() armagon_overleft5 =[ $lw1fir5.asc, armagon_overleft6 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);armagon_overleft_think();}; +void() armagon_overleft6 =[ $lw1fir6.asc, armagon_overleft7 ] {armagon_overleft_think();}; +void() armagon_overleft7 =[ $lw1fir7.asc, armagon_overleft8 ] {armagon_overleft_think();}; +void() armagon_overleft8 =[ $lw1fir8.asc, armagon_overleft9 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);armagon_overleft_think();}; +void() armagon_overleft9 =[ $lw1fir9.asc, armagon_overleft10 ] {armagon_overleft_think();}; +void() armagon_overleft10 =[ $lw1fir10.asc, armagon_overleft11 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);armagon_overleft_think();}; +void() armagon_overleft11 =[ $lw1fir11.asc, armagon_overleft12 ] {self.count=1;armagon_overleft_think();}; +void() armagon_overleft12 =[ $lw1fir12.asc, armagon_overleft13 ] {self.count=2;armagon_overleft_think();}; +void() armagon_overleft13 =[ $lw1fir13.asc, armagon_overleft14 ] {armagon_overleft_think();}; +void() armagon_overleft14 =[ $lw1fir14.asc, armagon_overleft15 ] {armagon_overleft_think();}; +void() armagon_overleft15 =[ $lw1fir15.asc, armagon_run1 ] {armagon_overleft_think();SUB_AttackFinished (1.0);}; + +void() armagon_overright1 =[ $lw2fir1.asc, armagon_overright2 ] {self.count=0;armagon_overright_think();}; +void() armagon_overright2 =[ $lw2fir2.asc, armagon_overright3 ] {armagon_overright_think();}; +void() armagon_overright3 =[ $lw2fir3.asc, armagon_overright4 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);armagon_overright_think();}; +void() armagon_overright4 =[ $lw2fir4.asc, armagon_overright5 ] {armagon_overright_think();}; +void() armagon_overright5 =[ $lw2fir5.asc, armagon_overright6 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);self.count=1;armagon_overright_think();}; +void() armagon_overright6 =[ $lw2fir6.asc, armagon_overright7 ] {self.count=2;armagon_overright_think();}; +void() armagon_overright7 =[ $lw2fir7.asc, armagon_overright8 ] {armagon_overright_think();}; +void() armagon_overright8 =[ $lw2fir8.asc, armagon_overright9 ] {sound (self, 7, "armagon/servo.wav", SERVO_VOLUME, ATTN_ARMAGON);armagon_overright_think();}; +void() armagon_overright9 =[ $lw2fir9.asc, armagon_overright10 ] {armagon_overright_think();}; +void() armagon_overright10 =[ $lw2fir10.asc, armagon_overright11 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);armagon_overright_think();}; +void() armagon_overright11 =[ $lw2fir11.asc, armagon_overright12 ] {armagon_overright_think();}; +void() armagon_overright12 =[ $lw2fir12.asc, armagon_run1 ] {armagon_overright_think();SUB_AttackFinished (1.0);}; + +void() armagon_plant1 =[ $lstand1.asc, armagon_plant1 ] {armagon_stand_attack();armagon_think();}; + +void() armagon_stop1 =[ $tranl1.asc, armagon_stop2 ] {armagon_think();}; +void() armagon_stop2 =[ $tranl2.asc, armagon_plant1 ] {sound (self, 6, "armagon/footfall.wav",1,ATTN_ARMAGON);armagon_think();}; + + + +void() armagon_satk1 =[ $lsfire1.asc, armagon_satk2 ] {armagon_think();}; +void() armagon_satk2 =[ $lsfire2.asc, armagon_satk3 ] {armagon_think();}; +void() armagon_satk3 =[ $lsfire3.asc, armagon_satk4 ] {armagon_think();}; +void() armagon_satk4 =[ $lsfire4.asc, armagon_satk5 ] {armagon_think();}; +void() armagon_satk5 =[ $lsfire5.asc, armagon_satk6 ] {armagon_think();}; +void() armagon_satk6 =[ $lsfire6.asc, armagon_satk7 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);armagon_think();}; +void() armagon_satk7 =[ $lsfire7.asc, armagon_satk8 ] {armagon_think();}; +void() armagon_satk8 =[ $lsfire8.asc, armagon_satk9 ] {armagon_think();}; +void() armagon_satk9 =[ $lsfire9.asc, armagon_satk10 ] {armagon_think();armagon_launch_missile(40,0);armagon_launch_missile(-40,0);}; +void() armagon_satk10 =[ $lsfire10.asc, armagon_satk11 ] {armagon_think();}; +void() armagon_satk11 =[ $lsfire9.asc, armagon_satk12 ] {armagon_think();armagon_launch_missile(40,0);armagon_launch_missile(-40,0);}; +void() armagon_satk12 =[ $lsfire10.asc, armagon_satk13 ] {armagon_think();}; +void() armagon_satk13 =[ $lsfire9.asc, armagon_satk14 ] {armagon_think();armagon_launch_missile(40,0);armagon_launch_missile(-40,0);}; +void() armagon_satk14 =[ $lsfire10.asc, armagon_satk15 ] {armagon_think();}; +void() armagon_satk15 =[ $lsfire11.asc, armagon_satk16 ] {armagon_think();}; +void() armagon_satk16 =[ $lsfire12.asc, armagon_plant1 ] {armagon_think();SUB_AttackFinished (0.3);}; + +void() armagon_slaser1 =[ $lsfire1.asc, armagon_slaser2 ] {armagon_think();}; +void() armagon_slaser2 =[ $lsfire2.asc, armagon_slaser3 ] {armagon_think();}; +void() armagon_slaser3 =[ $lsfire3.asc, armagon_slaser4 ] {armagon_think();}; +void() armagon_slaser4 =[ $lsfire4.asc, armagon_slaser5 ] {armagon_think();}; +void() armagon_slaser5 =[ $lsfire5.asc, armagon_slaser6 ] {armagon_think();}; +void() armagon_slaser6 =[ $lsfire6.asc, armagon_slaser7 ] {sound (self, 6, "armagon/footfall.wav", 1, ATTN_ARMAGON);armagon_think();}; +void() armagon_slaser7 =[ $lsfire7.asc, armagon_slaser8 ] {armagon_think();}; +void() armagon_slaser8 =[ $lsfire8.asc, armagon_slaser9 ] {armagon_think();}; +void() armagon_slaser9 =[ $lsfire9.asc, armagon_slaser10 ] {armagon_think();armagon_launch_laser(40);armagon_launch_laser(-40);}; +void() armagon_slaser10 =[ $lsfire10.asc, armagon_slaser11 ] {armagon_think();}; +void() armagon_slaser11 =[ $lsfire9.asc, armagon_slaser12 ] {armagon_think();armagon_launch_laser(40);armagon_launch_laser(-40);}; +void() armagon_slaser12 =[ $lsfire10.asc, armagon_slaser13 ] {armagon_think();}; +void() armagon_slaser13 =[ $lsfire9.asc, armagon_slaser14 ] {armagon_think();armagon_launch_laser(40);armagon_launch_laser(-40);}; +void() armagon_slaser14 =[ $lsfire10.asc, armagon_slaser15 ] {armagon_think();}; +void() armagon_slaser15 =[ $lsfire9.asc, armagon_slaser16 ] {armagon_think();armagon_launch_laser(40);armagon_launch_laser(-40);}; +void() armagon_slaser16 =[ $lsfire10.asc, armagon_slaser17 ] {armagon_think();}; +void() armagon_slaser17 =[ $lsfire9.asc, armagon_slaser18 ] {armagon_think();armagon_launch_laser(40);armagon_launch_laser(-40);}; +void() armagon_slaser18 =[ $lsfire10.asc, armagon_slaser19 ] {armagon_think();}; +void() armagon_slaser19 =[ $lsfire11.asc, armagon_slaser20 ] {armagon_think();}; +void() armagon_slaser20 =[ $lsfire12.asc, armagon_plant1 ] {armagon_think();SUB_AttackFinished (0.3);}; + +void() armagon_body_explode1; + +void() armagon_die1 =[ $tranl1.asc, armagon_die2 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die2 =[ $tranl2.asc, armagon_die3 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die3 =[ $ldie1.asc, armagon_die4 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die4 =[ $ldie2.asc, armagon_die5 ] + { + armagon_think(); + multi_explosion( self.origin + '0 0 48', 48, 10, 6, 0.3, 0.3); + sound (self, CHAN_AUTO, "armagon/death.wav", 1, ATTN_NONE); + self.nextthink = time + 0.2; + }; +void() armagon_die5 =[ $ldie3.asc, armagon_die6 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die6 =[ $ldie4.asc, armagon_die7 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die7 =[ $ldie5.asc, armagon_die8 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die8 =[ $ldie6.asc, armagon_die9 ] + { + armagon_think(); + self.nextthink = time + 2; + }; +void() armagon_die9 =[ $ldie7.asc, armagon_die10 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die10 =[ $ldie8.asc, armagon_die11 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die11 =[ $ldie9.asc, armagon_die12 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die12 =[ $ldie10.asc, armagon_die13 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die13 =[ $ldie11.asc, armagon_die14 ] {armagon_think();self.nextthink = time + 0.2;}; +void() armagon_die14 =[ $ldie12.asc, armagon_die14 ] + { + local entity body; + + armagon_think(); + multi_explosion( self.origin + '0 0 80', 20, 10, 3, 0.1, 0.5); + // legs + self.nextthink = -1; + self.movetype = MOVETYPE_NONE; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + setsize (self, '-32 -32 -24', '32 32 32' ); + self.flags = 0; + self.gorging = TRUE; + self.wait = time + 5; + + //body + body = self.trigger_field; + body.nextthink = -1; + self.movetype = MOVETYPE_NONE; + body.takedamage = DAMAGE_NO; + body.solid = SOLID_NOT; +// setsize (body, '-32 -32 30', '32 32 64' ); + body.think = armagon_body_explode1; + body.nextthink = time + 0.1; + body.gorging = TRUE; + }; + +void() armagon_body_explode2 = + { + local entity expl; + + sound (self , CHAN_AUTO, "misc/longexpl.wav", 1, ATTN_ARMAGON); + self.think = armagon_body_explode2; + ThrowGib ("progs/gib1.mdl", -200); + ThrowGib ("progs/gib2.mdl", -200); + ThrowGib ("progs/gib3.mdl", -200); + ThrowGib ("progs/gib1.mdl", -200); + ThrowGib ("progs/gib2.mdl", -200); + ThrowGib ("progs/gib3.mdl", -200); + ThrowGib ("progs/gib1.mdl", -200); + ThrowGib ("progs/gib2.mdl", -200); + ThrowGib ("progs/gib3.mdl", -200); + self.movetype = MOVETYPE_NONE; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + self.nextthink = time + 0.1; + s_explode1 (); + }; + +void() armagon_body_explode1 = + { + + self.think = armagon_body_explode1; + self.nextthink = time + 0.1; + if (self.cnt == 0) + { + self.count = 0; + } + if (self.cnt < 25) + { + if (self.cnt > self.count) + { + ThrowGib ("progs/gib1.mdl", -100); + ThrowGib ("progs/gib2.mdl", -100); + ThrowGib ("progs/gib3.mdl", -100); + self.count = self.cnt + 1; + } + self.cnt = self.cnt + 1; + } + else + { + self.cnt = 0; + self.think = armagon_body_explode2; + } + }; + +void(entity attacker, float damage) armagon_pain = + { + if (self.health <= 0) + return; // allready dying, don't go into pain frame + + if (damage < 25) + return; + if (self.pain_finished > time) + return; + self.pain_finished = time + 2; + + sound (self, CHAN_VOICE, "armagon/pain.wav", 1, ATTN_NORM); + }; + + + +void() armagon_missile_attack = + { + if (random()<0.5) + armagon_watk1(); + else + armagon_wlaseratk1(); +// self.nextthink = time + 0.1; + }; + +void() armagon_repulse_attack = + { + armagon_think(); + if (self.state == 0) + { + SUB_AttackFinished (0.5); + sound (self, CHAN_BODY, "armagon/repel.wav", 1, ATTN_NORM); + self.state = 1; + return; + } + else if (self.state == 1) + { + local entity head; + local vector dir; + + // look in our immediate vicinity + head = findradius(self.origin, 300); + while(head) + { + if(!(head.flags & FL_NOTARGET) && (head.flags & FL_CLIENT)) + { + if (visible(head) && (head.health > 0)) + { + dir = head.origin - (self.origin - '0 0 24'); + dir = normalize(dir); + head.velocity = head.velocity + dir*1500; + } + } + head = head.chain; + } + T_RadiusDamage (self, self, 60, self); + self.state = 0; + SUB_AttackFinished (0.1); + } +// self.nextthink = time + 0.1; + }; + +void() armagon_stand_attack = + { + local vector spot1, spot2; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = self.enemy.origin + self.enemy.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != self.enemy) + { + armagon_run1(); + return; + } + + if (trace_inopen && trace_inwater) + { + armagon_run1(); + return; + } + + if (time < self.attack_finished) + { + return; + } + + if ((vlen(spot2-spot1) < 200) && (self.enemy.classname == "player")) + { + armagon_repulse_attack(); + return; + } + + self.state = 0; + + if (vlen(spot2-spot1) > 450) + { + armagon_run1(); + return; + } + + if (random()<0.5) + armagon_satk1(); + else + armagon_slaser1(); + + if (self.cnt == 1) + { + armagon_run1(); + return; + } + }; + + +float() ArmagonCheckAttack = + { + local vector spot1, spot2; + local entity targ; + local float ang; + local float delta; + local float dist; + + self.lefty = 0; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if ((trace_ent != targ) && (!self.charmed)) + return FALSE; // don't have a clear shot + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (time < self.attack_finished) + return FALSE; + + ang = self.angles_y + self.fixangle; + delta = self.ideal_yaw - ang; + dist = vlen(spot2-spot1); + if ((fabs(delta) > 10 && (dist > 200)) || (self.enemy.classname != "player")) + return FALSE; + if (dist < 400) + { // melee attack + self.th_melee (); + return TRUE; + } +// missile attack + self.lefty = 1; +// self.th_missile (); +// SUB_AttackFinished (1.5*random()); + return FALSE; + }; + + +/*QUAKED monster_armagon (1 0 0) (-32 -32 -24) (32 32 64) Ambush +*/ +void() monster_armagon = +{ + local entity body; + local float sk; + if (deathmatch) + { + remove(self); + return; + } + + self.lefty = 0; + + sk = cvar("skill"); + + precache_model ("progs/armabody.mdl"); + precache_model ("progs/armalegs.mdl"); + + precache_sound ("armagon/footfall.wav"); + precache_sound ("armagon/servo.wav"); +// precache_sound ("armagon/footfall2.wav"); + precache_sound ("armagon/death.wav"); +// precache_sound ("armagon/death2.wav"); + precache_sound ("armagon/pain.wav"); + precache_sound ("armagon/repel.wav"); +// precache_sound ("armagon/idle.wav"); + precache_sound ("armagon/sight.wav"); + precache_sound ("armagon/idle1.wav"); + precache_sound ("armagon/idle2.wav"); + precache_sound ("armagon/idle3.wav"); + precache_sound ("armagon/idle4.wav"); + + body = spawn(); + body.origin = self.origin; + body.origin = body.origin - '0 0 64'; + body.oldorigin = self.origin; +// SUB_CopyEntity(self,body); + + self.solid = SOLID_SLIDEBOX; + self.fixangle = 0; + self.movetype = MOVETYPE_STEP; + body.solid = SOLID_NOT; + //body.movetype = MOVETYPE_STEP; + self.trigger_field = body; + body.trigger_field = self; + + setmodel (body, "progs/armabody.mdl"); + setmodel (self, "progs/armalegs.mdl"); + + setsize (self, '-48 -48 -24', '48 48 84' ); + setorigin (self, self.origin); + setorigin (body, body.origin); + if (sk==0) + { + self.yaw_speed = 5; + self.health = 2000; + self.worldtype = 0.9; + } + else if (sk==1) + { + self.yaw_speed = 9; + self.health = 2500; + self.worldtype = 0.85; + } + else + { + self.yaw_speed = 12; + self.health = 3500; + self.worldtype = 0.75; + } + self.state = 0; + self.super_time = 0; + self.endtime = 0; + + self.th_stand = armagon_stand1; + self.th_walk = armagon_walk1; + self.th_run = armagon_run1; + self.th_die = armagon_die1; + self.th_missile = armagon_missile_attack; + self.th_melee = armagon_stop1; + self.th_pain = armagon_pain; + walkmonster_start(); +}; diff --git a/quakec_hipnotic/hipclock.qc b/quakec_hipnotic/hipclock.qc new file mode 100644 index 0000000..bd0b20f --- /dev/null +++ b/quakec_hipnotic/hipclock.qc @@ -0,0 +1,104 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Clock QuickC program + By Jim Dose' 11/25/96 + +*/ + +void() clock_setpos = + { + local float pos; + local float ang; + local float seconds; + local string temp; + + // How much time has elapsed. + seconds = time + self.cnt; + + // divide by time it takes for one revolution + pos = seconds / self.count; + + // chop off non-fractional component + pos = pos - floor( pos ); + + ang = 360 * pos; + if ( self.event ) + { + if ( self.ltime > ang ) + { + // past twelve + temp = self.target; + self.target = self.event; + SUB_UseTargets(); + self.target = temp; + } + } + + self.angles_x = ang * self.movedir_x; + self.angles_y = ang * self.movedir_y; + self.angles_z = ang * self.movedir_z; + RotateTargetsFinal(); + + self.ltime = ang; + }; + +void() clock_think = + { + clock_setpos(); + self.nextthink = time + 1; + }; + +void() clock_firstthink = + { + LinkRotateTargets(); + self.think = clock_think; + clock_think(); + }; + +/*QUAKED func_clock (0 0 0.5) (0 0 0) (32 32 32) +Creates one hand of a "clock". + +Set the angle to be the direction the clock is facing. + +"event" is the targetname of the entities to trigger when hand strikes 12. +"cnt" is the time to start at. +"count" is the # of seconds it takes to make a full revolution (seconds is 60, minutes 3600, hours 43200). default is 60. +*/ + +void() func_clock = + { + local vector temp; + + self.classname = "clock"; + self.think = clock_firstthink; + self.nextthink = time + 0.1; + self.ltime = time; + SetMovedir(); + temp = self.movedir; + self.movedir_x = 0 - temp_y; + self.movedir_y = 0 - temp_z; + self.movedir_z = 0 - temp_x; + + if ( !self.count ) + { + self.count = 60; + } + self.cnt = self.cnt * ( self.count / 12 ); + }; diff --git a/quakec_hipnotic/hipcount.qc b/quakec_hipnotic/hipcount.qc new file mode 100644 index 0000000..1498f9b --- /dev/null +++ b/quakec_hipnotic/hipcount.qc @@ -0,0 +1,208 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Counter QuickC program + By Jim Dose' 9/13/96 +*/ + +float COUNTER_TOGGLE = 1; +float COUNTER_LOOP = 2; +float COUNTER_STEP = 4; +float COUNTER_RESET = 8; +float COUNTER_RANDOM = 16; +float COUNTER_FINISHCOUNT = 32; +float COUNTER_START_ON = 64; + +void() counter_on_use; +void() counter_off_use; + +void() counter_think = + { + self.cnt = self.cnt + 1; + if ( self.spawnflags & COUNTER_RANDOM ) + { + self.state = random() * self.count; + self.state = floor( self.state ) + 1; + } + else + { + self.state = self.cnt; + } + + activator = other; + SUB_UseTargets(); + self.nextthink = time + self.wait; + + if ( self.spawnflags & COUNTER_STEP ) + { + counter_on_use(); + } + + if ( self.cnt >= self.count ) + { + self.cnt = 0; + if ( ( self.aflag ) || !( self.spawnflags & COUNTER_LOOP ) ) + { + if (self.spawnflags & COUNTER_TOGGLE) + { + counter_on_use(); + } + else + { + remove (self); + } + } + } + }; + +void() counter_on_use = + { + if ( ( self.cnt != 0 ) && ( self.spawnflags & COUNTER_FINISHCOUNT ) ) + { + self.aflag = TRUE; + return; + } + + self.use = counter_off_use; + self.think = SUB_Null; + self.aflag = FALSE; + }; + +void() counter_off_use = + { + self.aflag = FALSE; + if (self.spawnflags & COUNTER_TOGGLE) + { + self.use = counter_on_use; + } + else + { + self.use = SUB_Null; + } + + if ( self.spawnflags & COUNTER_RESET ) + { + self.cnt = 0; + self.state = 0; + } + self.think = counter_think; + if (self.delay) + { + self.nextthink = time + self.delay; + } + else + { + counter_think(); + } + }; + +float( entity counter ) counter_GetCount = + { + local float value; + + if ( counter.classname == "counter" ) + { + return counter.state; + } + return 0; + }; + +/*QUAKED func_counter (0 0 0.5) (0 0 0) (32 32 32) TOGGLE LOOP STEP RESET RANDOM FINISHCOUNT START_ON +TOGGLE causes the counter to switch between an on and off state +each time the counter is triggered. + +LOOP causes the counter to repeat infinitly. The count resets to zero +after reaching the value in "count". + +STEP causes the counter to only increment when triggered. Effectively, +this turns the counter into a relay with counting abilities. + +RESET causes the counter to reset to 0 when restarted. + +RANDOM causes the counter to generate random values in the range 1 to "count" +at the specified interval. + +FINISHCOUNT causes the counter to continue counting until it reaches "count" +before shutting down even after being set to an off state. + +START_ON causes the counter to be on when the level starts. + +"count" specifies how many times to repeat the event. If LOOP is set, +it specifies how high to count before reseting to zero. Default is 10. + +"wait" the length of time between each trigger event. Default is 1 second. + +"delay" how much time to wait before firing after being switched on. +*/ + +void() func_counter = + { + if ( !self.wait ) + { + self.wait = 1; + } + + self.count = floor( self.count ); + if ( self.count <= 0 ) + { + self.count = 10; + } + self.cnt = 0; + self.state = 0; + + self.classname = "counter"; + self.use = counter_off_use; + self.think = SUB_Null; + if ( self.spawnflags & COUNTER_START_ON ) + { + self.think = counter_off_use; + self.nextthink = time + 0.1; + } + }; + +void() oncount_use = + { + if ( counter_GetCount( other ) == self.count ) + { + activator = other; + SUB_UseTargets(); + } + }; + +/*QUAKED func_oncount (0 0 0.5) (0 0 0) (16 16 16) +Must be used as the target for func_counter. When the counter +reaches the value set by count, func_oncount triggers its targets. + +"count" specifies the value to trigger on. Default is 1. + +"delay" how much time to wait before firing after being triggered. +*/ + +void() func_oncount = + { + self.count = floor( self.count ); + if ( self.count <= 0 ) + { + self.count = 1; + } + + self.classname = "oncount"; + self.use = oncount_use; + self.think = SUB_Null; + }; diff --git a/quakec_hipnotic/hipdecoy.qc b/quakec_hipnotic/hipdecoy.qc new file mode 100644 index 0000000..35846f3 --- /dev/null +++ b/quakec_hipnotic/hipdecoy.qc @@ -0,0 +1,188 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +PLAYER decoy + +============================================================================== +*/ + +$cd id1/models/player_4 +$origin 0 -6 24 +$base base +$skin skin + +// +// running +// +$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6 + +$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6 + +// +// standing +// +$frame stand1 stand2 stand3 stand4 stand5 + +$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6 +$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12 + + +void() decoy_stand1 =[ $axstnd1, decoy_stand1 ] + { + ChangeYaw(); + if (self.walkframe >= 5) + self.walkframe = 0; + self.frame = $stand1 + self.walkframe; + self.walkframe = self.walkframe + 1; + if (time > self.pausetime) + self.th_walk (); + }; + +void() decoy_walk1 =[ $rockrun1, decoy_walk1 ] + { +// local vector a; + movetogoal (12); +// a = '0 0 0'; +// a_y = self.ideal_yaw; +// makevectors(a); +// self.velocity = 1*v_forward; + self.weaponframe=0; + if (self.walkframe == 6) + self.walkframe = 0; + if (self.walkframe == 2 || self.walkframe == 5) + { + local float r; + r = random(); + if (r < 0.14) + sound (self, CHAN_VOICE, "misc/foot1.wav", 0.5, ATTN_NORM); + else if (r < 0.29) + sound (self, CHAN_VOICE, "misc/foot2.wav", 0.5, ATTN_NORM); + else if (r < 0.43) + sound (self, CHAN_VOICE, "misc/foot3.wav", 0.5, ATTN_NORM); + else if (r < 0.58) + sound (self, CHAN_VOICE, "misc/foot4.wav", 0.5, ATTN_NORM); + else if (r < 0.72) + sound (self, CHAN_VOICE, "misc/foot5.wav", 0.5, ATTN_NORM); + else if (r < 0.86) + sound (self, CHAN_VOICE, "misc/foot6.wav", 0.5, ATTN_NORM); + else + sound (self, CHAN_VOICE, "misc/foot7.wav", 0.5, ATTN_NORM); + } + self.frame = self.frame + self.walkframe; + self.walkframe = self.walkframe + 1; + }; + +void(entity decoy) setup_decoy = +{ + local entity pl; + + decoy.classname = "monster_decoy"; + setmodel (decoy, "progs/player.mdl"); + setsize (decoy, VEC_HULL_MIN, VEC_HULL_MAX); + decoy.view_ofs = '0 0 22'; + + decoy.solid = SOLID_SLIDEBOX; + decoy.movetype = MOVETYPE_STEP; + + decoy.health = 3000000; + + decoy.th_stand = decoy_stand1; + decoy.th_walk = decoy_walk1; + decoy.th_run = decoy_walk1; + decoy.th_missile = decoy_stand1; + decoy.th_pain = decoy_stand1; + decoy.th_die = decoy_stand1; + + pl = find (world, classname, "player"); + decoy.colormap = pl.colormap; +}; + +/*QUAKED monster_decoy (1 0 0) (-16 -16 -24) (16 16 40) +*/ +void() monster_decoy = +{ + if (deathmatch) + { + remove(self); + return; + } + setup_decoy(self); + + walkmonster_start (); + total_monsters = total_monsters - 1; +}; + +void(string targ, vector orig) become_decoy = +{ + local entity pl; + local entity decoy; + local entity temp; + + decoy = hipdecoy = spawn(); + + temp = self; + self = decoy; + + setup_decoy(decoy); + + setorigin(decoy, orig); + + decoy.origin = orig; + + decoy.target = targ; + + decoy.takedamage = DAMAGE_AIM; + + decoy.ideal_yaw = decoy.angles * '0 1 0'; + if (!decoy.yaw_speed) + decoy.yaw_speed = 20; + decoy.use = monster_use; + + decoy.flags = decoy.flags | FL_MONSTER; + + if (decoy.target) + { + decoy.goalentity = decoy.movetarget = find(world, targetname, decoy.target); + decoy.ideal_yaw = vectoyaw(decoy.goalentity.origin - decoy.origin); + if (!decoy.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(decoy.origin)); + dprint ("\n"); + } +// this used to be an objerror + if (decoy.movetarget.classname == "path_corner") + decoy.th_walk (); + else + decoy.pausetime = 99999999; + decoy.th_stand (); + } + else + { + decoy.pausetime = 99999999; + decoy.th_stand (); + } + +// spread think times so they don't all happen at same time + decoy.nextthink = decoy.nextthink + random()*0.5; + self = temp; +}; diff --git a/quakec_hipnotic/hipdefs.qc b/quakec_hipnotic/hipdefs.qc new file mode 100644 index 0000000..9ac7681 --- /dev/null +++ b/quakec_hipnotic/hipdefs.qc @@ -0,0 +1,141 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Level pack definitons + +*/ + +entity nullentity; +entity bulletholes; +entity lastbullet; +float numbulletholes; +float IT_MJOLNIR = 128; +float IT_LASER_CANNON = 8388608; +float IT_PROXIMITY_GUN = 65536; +float HIP_IT_WETSUIT = 2; +float HIP_IT_EMPATHY_SHIELDS = 4; +float HIP_IT_HORN_OF_CONJURING = 8; +//added update stat message +float SVC_UPDATESTAT = 3; +float SVC_CUTSCENE = 34; +//added total monster message +float STAT_TOTALMONSTERS = 12; +float STAT_TOTALSECRETS = 11; +.void() th_turn; +float AS_DODGING = 5; +float CHARMED_RADIUS = 1500; +float MAX_CHARMER_DISTANCE = 200; +float MIN_CHARMER_DISTANCE = 150; +float TOOCLOSE_CHARMER_DISTANCE = 120; +float visible_distance; +entity damage_inflictor; +float footsteps; + + +//MED 11/09/96 added new spawnflags +float SPAWNFLAG_SUPERSPIKE = 1; +float SPAWNFLAG_LASER = 2; +float SPAWNFLAG_LAVABALL = 4; +float SPAWNFLAG_ROCKET = 8; +float SPAWNFLAG_SILENT = 16; + +// Hipnotic Constants +float MIN_ANGLE_DELTA = 10; + +.float wetsuit_finished; +.float wetsuit_time; +.float empathy_finished; +.float empathy_time; +.float empathy_sound; +.float color; + +// +// Hipnotic entries +// +// have to use a new items flag because we ran out of bits in the original +.float items2; +// gremlin stuff +.float gorging; +.float stoleweapon; +.entity lastvictim; +// spawn variables +.void() spawnfunction; +.string spawnclassname; +.float spawnsolidtype; +.string spawnmodel; +.void() spawnthink; +.entity spawnmaster; +.vector spawnmins; +.vector spawnmaxs; +.float spawnsilent; +.float spawnmulti; +// horn of conjuring +.float charmed; +.entity charmer; +.float huntingcharmer; +float horn_active; +entity horn_charmer; +// laser cannon +.vector old_velocity; +//misc +.float duration; +// used for linked list of entities +.entity next_ent; +// Mjolnir +.float struck_by_mjolnir; +// teleport invulnerability +.float last_teleport_time; + +// Rotation +.vector neworigin; +.vector rotate; +.float endtime; +.float rotate_type; +.string path; +.string group; +.string event; + +// Miscellaneous +.float gravity; + +//MED 01/05/97 added discharge variable +float discharged; +//MED 01/05/97 added hipnotic decoy +entity hipdecoy; +// +// Hipnotic's Prototypes +// +void() StopEarthQuake; +void( float value ) EarthQuakeTime; +void() earthquake_postthink; +void() earthquake_prethink; +float( entity counter ) counter_GetCount; +vector ( vector ang ) SUB_NormalizeAngles; +void (entity srcent, entity destent) SUB_CopyEntity; +//multi explosion +void( vector loc, float rad, float damage, float dur, float pause, float vol) multi_explosion; +void(string targ, vector orig) become_decoy; + + +//void(vector origin, vector dir, float color, float count, float lifespan) particlestream = #79; + +void() RotateTargets; +void() RotateTargetsFinal; +void() SetTargetOrigin; +void() LinkRotateTargets; diff --git a/quakec_hipnotic/hipgrem.qc b/quakec_hipnotic/hipgrem.qc new file mode 100644 index 0000000..263bc27 --- /dev/null +++ b/quakec_hipnotic/hipgrem.qc @@ -0,0 +1,1877 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +gremlin + +============================================================================== +*/ +$cd hipwork\models\grem +$origin 0 0 23 +$scale 2 +$base grembase.asc + +$skin grem + +$frame stand1.asc stand2.asc stand3.asc stand4.asc stand5.asc stand6.asc +$frame stand7.asc stand8.asc stand9.asc stand10.asc stand11.asc stand12.asc +$frame stand13.asc stand14.asc stand15.asc stand16.asc stand17.asc + +$frame walk1.asc walk2.asc walk3.asc walk4.asc walk5.asc walk6.asc +$frame walk7.asc walk8.asc walk9.asc walk10.asc walk11.asc walk12.asc + +$frame run1.asc run2.asc run3.asc run4.asc run5.asc run6.asc run7.asc run8.asc +$frame run9.asc run10.asc run11.asc run12.asc run13.asc run14.asc run15.asc + +$frame jump1.asc jump2.asc jump3.asc jump4.asc jump5.asc jump6.asc jump7.asc +$frame jump8.asc jump9.asc jump10.asc jump11.asc jump12.asc jump13.asc jump14.asc +$frame jump15.asc jump16.asc + +$frame attk1.asc attk2.asc attk3.asc attk4.asc attk5.asc attk6.asc +$frame attk7.asc attk8.asc attk9.asc attk10.asc attk11.asc + +$frame maul1.asc maul2.asc maul3.asc maul4.asc maul5.asc maul6.asc +$frame maul7.asc maul8.asc maul9.asc maul10.asc maul11.asc maul12.asc maul13.asc + +$frame spawn1.asc spawn2.asc spawn3.asc spawn4.asc spawn5.asc spawn6.asc + +$frame look1.asc look2.asc look3.asc look4.asc look5.asc look6.asc look7.asc +$frame look8.asc look9.asc look10.asc + +$frame pain1.asc pain2.asc pain3.asc pain4.asc + +$frame death1.asc death2.asc death3.asc death4.asc death5.asc death6.asc death7.asc +$frame death8.asc death9.asc death10.asc death11.asc death12.asc + +$frame flip1.asc flip2.asc flip3.asc flip4.asc flip5.asc flip6.asc flip7.asc +$frame flip8.asc + +$frame lunge1.asc lunge2.asc lunge3.asc lunge4.asc lunge5.asc lunge6.asc lunge7.asc +$frame lunge8.asc lunge9.asc lunge10.asc lunge11.asc + +$frame gfire1.asc gfire2.asc gfire3.asc gfire4.asc gfire5.asc gfire6.asc + +$frame glook1.asc glook2.asc glook3.asc glook4.asc glook5.asc glook6.asc glook7.asc +$frame glook8.asc glook9.asc glook10.asc glook11.asc glook12.asc glook13.asc +$frame glook14.asc glook15.asc glook16.asc glook17.asc glook18.asc glook19.asc glook20.asc + +$frame gpain1.asc gpain2.asc gpain3.asc + +$frame grun1.asc grun2.asc grun3.asc grun4.asc grun5.asc grun6.asc grun7.asc +$frame grun8.asc grun9.asc grun10.asc grun11.asc grun12.asc grun13.asc grun14.asc grun15.asc + +//============================================================================ + +float NumGremlins; +float NumSpawnGremlins; + +/* +=========== +GremlinAttemptWeaponSteal + +see if we can steal enemy's weapon + +============ +*/ +entity()GremlinFindVictim; +float() GremlinFindTarget; + +float() GremlinAttemptWeaponSteal = + { + local vector delta; + local entity tempself; + local float best; + local entity victim; + local float amount; + + if (self.stoleweapon) + { + dprint("gremlin trying to steal a weapon again\n"); + return FALSE; + } + if (!(self.enemy.flags & FL_CLIENT)) + { + return FALSE; + } + + delta = (self.enemy.origin - self.origin); + + if (vlen(delta) > 100) + return FALSE; + if (random() < 0.5) + return FALSE; + // + // we are within range so lets go for it + // + victim = self.enemy; + best = victim.weapon; + if (best == IT_AXE || best == IT_SHOTGUN || best == IT_MJOLNIR) + return FALSE; + // take that weapon from the entity + victim.items = victim.items - (victim.items & best); + // give it to our gremlin + self.items = self.items | best; + self.weapon = best; + // take some ammo while we are at it + self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS ) ); + if (best == IT_SUPER_SHOTGUN) + { + amount = victim.ammo_shells; + if (amount > 20) + amount = 20; + victim.ammo_shells = victim.ammo_shells - amount; + self.ammo_shells = self.ammo_shells + amount; + self.items = self.items | IT_SHELLS; + self.currentammo = self.ammo_shells; + sprint (victim, "$qc_gremlin_ssg"); + } + else if (best == IT_NAILGUN) + { + amount = victim.ammo_nails; + if (amount > 40) + amount = 40; + victim.ammo_nails = victim.ammo_nails - amount; + self.ammo_nails = self.ammo_nails + amount; + self.items = self.items | IT_NAILS; + self.currentammo = self.ammo_nails; + sprint (victim, "$qc_gremlin_ng"); + } + else if (best == IT_SUPER_NAILGUN) + { + amount = victim.ammo_nails; + if (amount > 40) + amount = 40; + victim.ammo_nails = victim.ammo_nails - amount; + self.ammo_nails = self.ammo_nails + amount; + self.items = self.items | IT_NAILS; + self.currentammo = self.ammo_nails; + sprint (victim, "$qc_gremlin_sng"); + } + else if (best == IT_GRENADE_LAUNCHER) + { + amount = victim.ammo_rockets; + if (amount > 5) + amount = 5; + victim.ammo_rockets = victim.ammo_rockets - amount; + self.ammo_rockets = self.ammo_rockets + amount; + self.items = self.items | IT_ROCKETS; + self.currentammo = self.ammo_rockets; + sprint (victim, "$qc_gremlin_gl"); + } + else if (best == IT_ROCKET_LAUNCHER) + { + amount = victim.ammo_rockets; + if (amount > 5) + amount = 5; + victim.ammo_rockets = victim.ammo_rockets - amount; + self.ammo_rockets = self.ammo_rockets + amount; + self.items = self.items | IT_ROCKETS; + self.currentammo = self.ammo_rockets; + sprint (victim, "$qc_gremlin_rl"); + } + else if (best == IT_LIGHTNING) + { + amount = victim.ammo_cells; + if (amount > 40) + amount = 40; + victim.ammo_cells = victim.ammo_cells - amount; + self.ammo_cells = self.ammo_cells + amount; + self.items = self.items | IT_CELLS; + self.currentammo = self.ammo_cells; + sprint (victim, "$qc_gremlin_lg"); + } + else if (best == IT_LASER_CANNON) + { + amount = victim.ammo_cells; + if (amount > 40) + amount = 40; + victim.ammo_cells = victim.ammo_cells - amount; + self.ammo_cells = self.ammo_cells + amount; + self.items = self.items | IT_CELLS; + self.currentammo = self.ammo_cells; + sprint (victim, "$qc_gremlin_lc"); + } + else if (best == IT_PROXIMITY_GUN) + { + amount = victim.ammo_rockets; + if (amount > 5) + amount = 5; + victim.ammo_rockets = victim.ammo_rockets - amount; + self.ammo_rockets = self.ammo_rockets + amount; + self.items = self.items | IT_ROCKETS; + self.currentammo = self.ammo_rockets; + sprint (victim, "$qc_gremlin_prox"); + } + tempself = self; + self = victim; + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + self = tempself; + + // tag the gremlin as having stolen a weapon + self.stoleweapon = TRUE; + self.attack_finished = time; + // don't fire the first shot at the person we stole the weapon from + // all the time + if (random()>0.65) + self.lastvictim = victim; + else + self.lastvictim = self; +// self.attack_state = AS_STRAIGHT; + // find a recipient + victim = GremlinFindVictim(); + if (victim != world) + { + self.enemy = victim; + FoundTarget(); + self.attack_finished = time; + self.search_time = time + 1.0; + } + + return TRUE; + }; +//============================================================================ + +/* +=========== +GremlinFindTarget + +gremlin is currently not attacking anything, so try to find a target + +============ +*/ +float() GremlinFindTarget = + { + local entity head; + local entity gorge; + local float dist; + local float result; + + if ((self.stoleweapon==0) && time > self.wait) + { + self.wait = time + 1.0; + dist = 2000; + gorge = world; + head = nextent(world); + while (head!=world) + { + if ((head.health < 1) && (head.flags & (FL_MONSTER|FL_CLIENT))) + { + result = fabs(head.origin_z - self.origin_z); + if ((visible(head)) && (result<80) && (head.gorging == FALSE) && (visible_distance self.pausetime) + { + self.th_walk (); + return; + } + }; + +//============================================================================ +float() GremlinCheckNoAmmo; +void() gremlin_glook1; +/* +============= +gremlin_run + +The monster has an enemy it is trying to kill +============= +*/ +void(float dist) gremlin_run = + { + local entity oldtarget; + local float r; + local vector d; + + if (self.watertype == CONTENT_LAVA) + { // do damage + T_Damage (self, world, world, 2000); + } + movedist = dist; + if (self.stoleweapon) + self.frame = self.frame + $grun1.asc - $run1.asc; + + if (self.gorging) + { + traceline(self.origin, self.enemy.origin, TRUE, self); + if (trace_fraction != 1.0) + { + self.gorging = FALSE; + return; + } + if (!visible(self.enemy)) + { + self.gorging = FALSE; + return; + } + r = vlen(self.enemy.origin - self.origin); + if (r < 130) + { + ai_face(); + if (r < 45) + { + self.th_melee (); + self.attack_state = AS_STRAIGHT; + return; + } + else if (walkmove (self.angles_y, dist) == FALSE) + { + self.gorging = FALSE; + return; + } + return; + } + movetogoal (dist); // done in C code... + } + else + { + if (random()>0.97) + { + if (GremlinFindTarget()) + return; + } + // get away from player if we stole a weapon + if (self.stoleweapon) + { + if (self.enemy.health < 0 && self.enemy.classname == "player") + { + gremlin_glook1(); + return; + } + if (!GremlinCheckNoAmmo()) + { + if (self.t_length==1) + { + remove(self.trigger_field); + self.goalentity = self.enemy; + self.t_length = 0; + } + return; + } + r = vlen(self.enemy.origin - self.origin); + d = normalize(self.origin-self.enemy.origin); + if (self.t_length == 0) + { + if (r<150) + { + self.trigger_field = spawn(); + setsize(self.trigger_field,'-1 -1 -1','1 1 1'); + self.t_length = 1; + } + } + if (self.t_length==1) + { + if (r > 250) + { + remove(self.trigger_field); + self.goalentity = self.enemy; + self.t_length = 0; +// self.attack_state = AS_SLIDING; + } + else + { + if (r < 160) + { + local vector ang; + local float done; + local vector end; + local float c; + + ang = vectoangles(d); + done = 0; + c = 0; + while (done == 0) + { + makevectors(ang); + end = self.enemy.origin + v_forward * 350; + traceline(self.enemy.origin,end,FALSE,self); + if (trace_fraction == 1.0) + { + traceline(self.origin,end,FALSE,self); + if (trace_fraction == 1.0) + done = 1; + } + ang_y = anglemod(ang_y + 36); + c = c + 1; + if (c == 10) + { + done = 1; + } + } + setorigin(self.trigger_field,end); + } + self.goalentity = self.trigger_field; + self.ideal_yaw = vectoyaw(normalize(self.goalentity.origin-self.origin)); + ChangeYaw (); + movetogoal (dist); // done in C code... + self.nextthink = time + 0.1; + return; + } + } + } + ai_run(dist); + self.nextthink = time + 0.1; + } + }; + +//============================================================================ + +void() Gremlin_JumpTouch; +void() Gremlin_FlipTouch; +void(float side) Gremlin_Melee; +void(float side) Gremlin_Gorge; + +void() gremlin_stand1 =[ $stand1.asc, gremlin_stand2 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand2 =[ $stand2.asc, gremlin_stand3 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand3 =[ $stand3.asc, gremlin_stand4 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand4 =[ $stand4.asc, gremlin_stand5 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand5 =[ $stand5.asc, gremlin_stand6 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand6 =[ $stand6.asc, gremlin_stand7 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand7 =[ $stand7.asc, gremlin_stand8 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand8 =[ $stand8.asc, gremlin_stand9 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand9 =[ $stand9.asc, gremlin_stand10 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand10 =[ $stand10.asc, gremlin_stand11 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand11 =[ $stand11.asc, gremlin_stand12 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand12 =[ $stand12.asc, gremlin_stand13 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand13 =[ $stand13.asc, gremlin_stand14 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand14 =[ $stand14.asc, gremlin_stand15 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand15 =[ $stand15.asc, gremlin_stand16 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand16 =[ $stand16.asc, gremlin_stand17 ] {gremlin_stand();self.nextthink = time + 0.2;}; +void() gremlin_stand17 =[ $stand17.asc, gremlin_stand1 ] {gremlin_stand();self.nextthink = time + 0.2;}; + +void() gremlin_walk1 =[ $walk1.asc, gremlin_walk2 ] { +if (random() < 0.1) + sound (self, CHAN_VOICE, "grem/idle.wav", 1, ATTN_IDLE); +gremlin_walk(8);}; +void() gremlin_walk2 =[ $walk2.asc, gremlin_walk3 ] {gremlin_walk(8);}; +void() gremlin_walk3 =[ $walk3.asc, gremlin_walk4 ] {gremlin_walk(8);}; +void() gremlin_walk4 =[ $walk4.asc, gremlin_walk5 ] {gremlin_walk(8);}; +void() gremlin_walk5 =[ $walk5.asc, gremlin_walk6 ] {gremlin_walk(8);}; +void() gremlin_walk6 =[ $walk6.asc, gremlin_walk7 ] {gremlin_walk(8);}; +void() gremlin_walk7 =[ $walk7.asc, gremlin_walk8 ] {gremlin_walk(8);}; +void() gremlin_walk8 =[ $walk8.asc, gremlin_walk9 ] {gremlin_walk(8);}; +void() gremlin_walk9 =[ $walk9.asc, gremlin_walk10 ] {gremlin_walk(8);}; +void() gremlin_walk10 =[ $walk10.asc, gremlin_walk11 ] {gremlin_walk(8);}; +void() gremlin_walk11 =[ $walk11.asc, gremlin_walk12 ] {gremlin_walk(8);}; +void() gremlin_walk12 =[ $walk12.asc, gremlin_walk1 ] {gremlin_walk(8);}; + +void() gremlin_run1 =[ $run1.asc, gremlin_run2 ] { +if (random() < 0.1) + sound (self, CHAN_VOICE, "grem/idle.wav", 1, ATTN_IDLE); +gremlin_run(0);}; +void() gremlin_run2 =[ $run2.asc, gremlin_run3 ] {gremlin_run(8);}; +void() gremlin_run3 =[ $run3.asc, gremlin_run4 ] {gremlin_run(12);}; +void() gremlin_run4 =[ $run4.asc, gremlin_run5 ] {gremlin_run(16);}; +void() gremlin_run5 =[ $run5.asc, gremlin_run6 ] {gremlin_run(16);}; +void() gremlin_run6 =[ $run6.asc, gremlin_run7 ] {gremlin_run(12);}; +void() gremlin_run7 =[ $run7.asc, gremlin_run8 ] {gremlin_run(8);}; +void() gremlin_run8 =[ $run8.asc, gremlin_run9 ] {gremlin_run(0);}; +void() gremlin_run9 =[ $run9.asc, gremlin_run10 ] {gremlin_run(8);}; +void() gremlin_run10 =[ $run10.asc, gremlin_run11 ] {gremlin_run(12);}; +void() gremlin_run11 =[ $run11.asc, gremlin_run12 ] {gremlin_run(16);}; +void() gremlin_run12 =[ $run12.asc, gremlin_run1 ] {gremlin_run(16);}; +void() gremlin_run13 =[ $run13.asc, gremlin_run14 ] {gremlin_run(12);}; +void() gremlin_run14 =[ $run14.asc, gremlin_run15 ] {gremlin_run(8);}; +void() gremlin_run15 =[ $run15.asc, gremlin_run1 ] {gremlin_run(0);}; + +void() gremlin_jump1 =[ $jump1.asc, gremlin_jump2 ] {ai_face();}; +void() gremlin_jump2 =[ $jump2.asc, gremlin_jump3 ] {ai_face();}; +void() gremlin_jump3 =[ $jump3.asc, gremlin_jump4 ] {ai_face();}; +void() gremlin_jump4 =[ $jump4.asc, gremlin_jump5 ] {ai_face();}; +void() gremlin_jump5 =[ $jump5.asc, gremlin_jump6 ] +{ + ai_face(); + + if (self.flags & FL_ONGROUND) + { + self.touch = Gremlin_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 300 + '0 0 300'; + self.flags = self.flags - FL_ONGROUND; + } + else + { + gremlin_run1(); + } +}; +void() gremlin_jump6 =[ $jump6.asc, gremlin_jump7 ] {}; +void() gremlin_jump7 =[ $jump7.asc, gremlin_jump8 ] {}; +void() gremlin_jump8 =[ $jump8.asc, gremlin_jump9 ] {}; +void() gremlin_jump9 =[ $jump9.asc, gremlin_jump10 ] {}; +void() gremlin_jump10 =[ $jump10.asc, gremlin_jump11 ] {}; +void() gremlin_jump11 =[ $jump11.asc, gremlin_jump1 ] + { + self.nextthink = time + 3; + // if three seconds pass, assume gremlin is stuck and jump again + }; + +void() gremlin_jump12 =[ $jump12.asc, gremlin_jump13 ] {}; +void() gremlin_jump13 =[ $jump13.asc, gremlin_jump14 ] {}; +void() gremlin_jump14 =[ $jump14.asc, gremlin_jump15 ] {}; +void() gremlin_jump15 =[ $jump15.asc, gremlin_jump16 ] {}; +void() gremlin_jump16 =[ $jump16.asc, gremlin_run1 ] {}; + +void() gremlin_shot1 = [$gfire1.asc, gremlin_shot2 ] {self.effects = self.effects | EF_MUZZLEFLASH;}; +void() gremlin_shot2 = [$gfire2.asc, gremlin_shot3 ] {}; +void() gremlin_shot3 = [$gfire3.asc, gremlin_shot4 ] {}; +void() gremlin_shot4 = [$gfire4.asc, gremlin_shot5 ] {}; +void() gremlin_shot5 = [$gfire5.asc, gremlin_shot6 ] {}; +void() gremlin_shot6 = [$gfire6.asc, gremlin_run1 ] {}; + + +//============================================================================ +void(float ox) Gremlin_FireNailGun; + +void() gremlin_nail1 =[$gfire1.asc, gremlin_nail2 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireNailGun(4); + }; +void() gremlin_nail2 =[$gfire1.asc, gremlin_nail3 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireNailGun(4); + }; +void() gremlin_nail3 =[$gfire1.asc, gremlin_nail4 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireNailGun(4); + }; +void() gremlin_nail4 =[$gfire1.asc, gremlin_nail5 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireNailGun(4); + }; +void() gremlin_nail5 =[$gfire1.asc, gremlin_nail6 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireNailGun(4); + }; +void() gremlin_nail6 =[$gfire1.asc, gremlin_nail7 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireNailGun(4); + }; +void() gremlin_nail7 = [$gfire1.asc, gremlin_run1 ] {}; + + +void(float ox) Gremlin_FireLaserGun; + +void() gremlin_laser1 =[$gfire1.asc, gremlin_laser2 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireLaserGun(4); + }; +void() gremlin_laser2 =[$gfire1.asc, gremlin_laser3 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireLaserGun(4); + }; +void() gremlin_laser3 =[$gfire1.asc, gremlin_laser4 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireLaserGun(4); + }; +void() gremlin_laser4 =[$gfire1.asc, gremlin_laser5 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireLaserGun(4); + }; +void() gremlin_laser5 =[$gfire1.asc, gremlin_laser6 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireLaserGun(4); + }; +void() gremlin_laser6 =[$gfire1.asc, gremlin_laser7 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + Gremlin_FireLaserGun(4); + }; +void() gremlin_laser7 = [$gfire1.asc, gremlin_run1 ] {}; + +//============================================================================ + +void() Gremlin_FireLightningGun; +void() gremlin_light1 =[$gfire1.asc, gremlin_light2 ] + { + Gremlin_FireLightningGun(); + }; +void() gremlin_light2 = [$gfire1.asc, gremlin_light3 ] + { + Gremlin_FireLightningGun(); + }; +void() gremlin_light3 =[$gfire1.asc, gremlin_light4 ] + { + Gremlin_FireLightningGun(); + }; +void() gremlin_light4 = [$gfire1.asc, gremlin_light5 ] +{ + Gremlin_FireLightningGun(); +}; +void() gremlin_light5 = [$gfire1.asc, gremlin_run1 ] {}; + +//============================================================================ + +void() gremlin_rocket1 = [$gfire1.asc, gremlin_rocket2 ] {self.effects = self.effects | EF_MUZZLEFLASH;}; +void() gremlin_rocket2 = [$gfire2.asc, gremlin_rocket3 ] {}; +void() gremlin_rocket3 = [$gfire3.asc, gremlin_rocket4 ] {}; +void() gremlin_rocket4 = [$gfire4.asc, gremlin_rocket5 ] {}; +void() gremlin_rocket5 = [$gfire5.asc, gremlin_rocket6 ] {}; +void() gremlin_rocket6 = [$gfire6.asc, gremlin_run1 ] {}; + +void() gremlin_lunge1 =[ $lunge1.asc, gremlin_lunge2 ] {ai_charge(0);}; +void() gremlin_lunge2 =[ $lunge2.asc, gremlin_lunge3 ] {ai_charge(0);}; +void() gremlin_lunge3 =[ $lunge3.asc, gremlin_lunge4 ] {ai_charge(0);}; +void() gremlin_lunge4 =[ $lunge4.asc, gremlin_lunge5 ] {ai_charge(0);}; +void() gremlin_lunge5 =[ $lunge5.asc, gremlin_lunge6 ] {ai_charge(0);}; +void() gremlin_lunge6 =[ $lunge6.asc, gremlin_lunge7 ] {ai_charge(0);}; +void() gremlin_lunge7 =[ $lunge7.asc, gremlin_lunge8 ] {ai_charge(15);}; +void() gremlin_lunge8 =[ $lunge8.asc, gremlin_lunge9 ] {ai_charge(0);Gremlin_Melee(0);}; +void() gremlin_lunge9 =[ $lunge9.asc, gremlin_lunge10] {ai_charge(0);}; +void() gremlin_lunge10 =[ $lunge10.asc, gremlin_lunge11] {ai_charge(0);}; +void() gremlin_lunge11 =[ $lunge11.asc, gremlin_run1] {ai_charge(0);}; + +void() gremlin_claw1 =[ $attk1.asc, gremlin_claw2 ] {ai_charge(0);}; +void() gremlin_claw2 =[ $attk2.asc, gremlin_claw3 ] {ai_charge(0);}; +void() gremlin_claw3 =[ $attk3.asc, gremlin_claw4 ] {ai_charge(0);}; +void() gremlin_claw4 =[ $attk4.asc, gremlin_claw5 ] {ai_charge(0);}; +void() gremlin_claw5 =[ $attk5.asc, gremlin_claw6 ] {ai_charge(0);}; +void() gremlin_claw6 =[ $attk6.asc, gremlin_claw7 ] {ai_charge(0);Gremlin_Melee(200);}; +void() gremlin_claw7 =[ $attk7.asc, gremlin_claw8 ] {ai_charge(15);}; +void() gremlin_claw8 =[ $attk8.asc, gremlin_claw9 ] {ai_charge(0);}; +void() gremlin_claw9 =[ $attk9.asc, gremlin_claw10] {ai_charge(0);}; +void() gremlin_claw10 =[ $attk10.asc, gremlin_claw11] {ai_charge(0);}; +void() gremlin_claw11 =[ $attk11.asc, gremlin_run1] {ai_charge(0);}; + +void() gremlin_gorge1 =[ $maul1.asc, gremlin_gorge2] {ai_charge(1);}; +void() gremlin_gorge2 =[ $maul2.asc, gremlin_gorge3] {ai_charge(1);}; +void() gremlin_gorge3 =[ $maul3.asc, gremlin_gorge4] {ai_charge(2);}; +void() gremlin_gorge4 =[ $maul4.asc, gremlin_gorge5] {ai_charge(0);}; +void() gremlin_gorge5 =[ $maul5.asc, gremlin_gorge6] {ai_charge(0);}; +void() gremlin_gorge6 =[ $maul6.asc, gremlin_gorge7] {ai_charge(0);Gremlin_Gorge(200);}; +void() gremlin_gorge7 =[ $maul7.asc, gremlin_gorge8] {ai_charge(0);}; +void() gremlin_gorge8 =[ $maul8.asc, gremlin_gorge9] {ai_charge(0);Gremlin_Gorge(-200);}; +void() gremlin_gorge9 =[ $maul9.asc, gremlin_gorge10] {ai_charge(0);}; +void() gremlin_gorge10 =[ $maul10.asc, gremlin_gorge11] {ai_charge(0);}; +void() gremlin_gorge11 =[ $maul11.asc, gremlin_gorge12] {ai_charge(0);}; +void() gremlin_gorge12 =[ $maul12.asc, gremlin_gorge13] {ai_charge(0);}; +void() gremlin_gorge13 =[ $maul13.asc, gremlin_gorge1] {ai_charge(0);}; + +void() gremlin_look1 =[ $look1.asc, gremlin_look2] {self.nextthink = time + 0.2;}; +void() gremlin_look2 =[ $look2.asc, gremlin_look3] {self.nextthink = time + 0.2;}; +void() gremlin_look3 =[ $look3.asc, gremlin_look4] {self.nextthink = time + 0.2;}; +void() gremlin_look4 =[ $look4.asc, gremlin_look5] {self.nextthink = time + 0.2;}; +void() gremlin_look5 =[ $look5.asc, gremlin_look6] {self.nextthink = time + 0.2;}; +void() gremlin_look6 =[ $look6.asc, gremlin_look7] {self.nextthink = time + 0.2;}; +void() gremlin_look7 =[ $look7.asc, gremlin_look8] {self.nextthink = time + 0.2;}; +void() gremlin_look8 =[ $look8.asc, gremlin_look9] {self.nextthink = time + 0.2;}; +void() gremlin_look9 =[ $look9.asc, gremlin_run1] + { + if (self.oldenemy.health > 0) + { + self.enemy = self.oldenemy; + HuntTarget (); + } + else + { + if (self.movetarget) + self.th_walk (); + else + self.th_stand (); + } + }; + + +void() GremlinDropBackpack; +void() gremlin_glook1 =[ $glook1.asc, gremlin_glook2] {}; +void() gremlin_glook2 =[ $glook2.asc, gremlin_glook3] {}; +void() gremlin_glook3 =[ $glook3.asc, gremlin_glook4] {}; +void() gremlin_glook4 =[ $glook4.asc, gremlin_glook5] {}; +void() gremlin_glook5 =[ $glook5.asc, gremlin_glook6] {}; +void() gremlin_glook6 =[ $glook6.asc, gremlin_glook7] {}; +void() gremlin_glook7 =[ $glook7.asc, gremlin_glook8] {}; +void() gremlin_glook8 =[ $glook8.asc, gremlin_glook9] {}; +void() gremlin_glook9 =[ $glook9.asc, gremlin_glook10] {}; +void() gremlin_glook10 =[ $glook10.asc, gremlin_glook11] {}; +void() gremlin_glook11 =[ $glook11.asc, gremlin_glook12] {}; +void() gremlin_glook12 =[ $glook12.asc, gremlin_glook13] {}; +void() gremlin_glook13 =[ $glook13.asc, gremlin_glook14] {}; +void() gremlin_glook14 =[ $glook14.asc, gremlin_glook15] {}; +void() gremlin_glook15 =[ $glook15.asc, gremlin_glook16] {}; +void() gremlin_glook16 =[ $glook16.asc, gremlin_glook17] {}; +void() gremlin_glook17 =[ $glook17.asc, gremlin_glook18] {}; +void() gremlin_glook18 =[ $glook18.asc, gremlin_glook19] {}; +void() gremlin_glook19 =[ $glook19.asc, gremlin_glook20] {}; +void() gremlin_glook20 =[ $glook20.asc, gremlin_glook20] + { + GremlinDropBackpack(); + self.stoleweapon = FALSE; + if (self.oldenemy.health > 0) + { + self.enemy = self.oldenemy; + HuntTarget (); + } + else + { + if (self.movetarget) + self.th_walk (); + else + self.th_stand (); + } + }; + +void() gremlin_pain1 =[ $pain1.asc, gremlin_pain2 ] {ai_back(4);}; +void() gremlin_pain2 =[ $pain2.asc, gremlin_pain3 ] {ai_back(4);}; +void() gremlin_pain3 =[ $pain3.asc, gremlin_pain4 ] {ai_back(2);}; +void() gremlin_pain4 =[ $pain4.asc, gremlin_run1 ] {}; + +void() gremlin_gunpain1 =[ $gpain1.asc, gremlin_gunpain2 ] {ai_back(4);}; +void() gremlin_gunpain2 =[ $gpain2.asc, gremlin_gunpain3 ] {ai_back(2);}; +void() gremlin_gunpain3 =[ $gpain3.asc, gremlin_run1 ] {}; + +void(entity attacker, float damage) gremlin_pain = +{ + local float r; + + if (random()<0.8) + { + self.gorging = FALSE; + self.enemy = attacker; + FoundTarget(); + } + if (self.touch == Gremlin_JumpTouch) + return; + + if (self.pain_finished > time) + return; + + self.pain_finished = time + 1; +// if (random()<0.5) +// sound (self, CHAN_VOICE, "grem/pain2.wav", 1, ATTN_NORM); +// else + r = random(); + if (r<0.33) + sound (self, CHAN_VOICE, "grem/pain1.wav", 1, ATTN_NORM); + else if (r<0.66) + sound (self, CHAN_VOICE, "grem/pain2.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "grem/pain3.wav", 1, ATTN_NORM); + + if (self.stoleweapon) + { + gremlin_gunpain1 (); + } + else + { + gremlin_pain1 (); + } +}; + +void() gremlin_spawn1 =[ $spawn1.asc, gremlin_spawn2 ] {self.nextthink = time + 0.3; + self.th_pain = SUB_Null; +// sound (self, CHAN_VOICE, "grem/idle.wav", 1, ATTN_IDLE); +}; +void() gremlin_spawn2 =[ $spawn2.asc, gremlin_spawn3 ] {self.nextthink = time + 0.3;}; +void() gremlin_spawn3 =[ $spawn3.asc, gremlin_spawn4 ] {self.nextthink = time + 0.3;}; +void() gremlin_spawn4 =[ $spawn4.asc, gremlin_spawn5 ] {self.nextthink = time + 0.3;}; +void() gremlin_spawn5 =[ $spawn5.asc, gremlin_spawn6 ] {self.nextthink = time + 0.3;}; +void() gremlin_spawn6 =[ $spawn6.asc, gremlin_run1 ] { + self.th_pain = gremlin_pain; +}; + + +void() gremlin_die1 =[ $death1.asc, gremlin_die2 ] { +sound (self, CHAN_VOICE, "grem/death.wav", 1, ATTN_NORM);}; +void() gremlin_die2 =[ $death2.asc, gremlin_die3 ] {ai_forward(2);}; +void() gremlin_die3 =[ $death3.asc, gremlin_die4 ] {ai_forward(1);}; +void() gremlin_die4 =[ $death4.asc, gremlin_die5 ] {ai_forward(2);}; +void() gremlin_die5 =[ $death5.asc, gremlin_die6 ] {ai_forward(1);}; +void() gremlin_die6 =[ $death6.asc, gremlin_die7 ] +{self.solid = SOLID_NOT;}; +void() gremlin_die7 =[ $death7.asc, gremlin_die8 ] {ai_forward(2);}; +void() gremlin_die8 =[ $death8.asc, gremlin_die9 ] {ai_forward(1);}; +void() gremlin_die9 =[ $death9.asc, gremlin_die10 ] {ai_forward(2);}; +void() gremlin_die10 =[ $death10.asc, gremlin_die11 ] {ai_forward(1);}; +void() gremlin_die11 =[ $death11.asc, gremlin_die12 ] {ai_forward(2);}; +void() gremlin_die12 =[ $death12.asc, gremlin_die12 ] {}; + +void() gremlin_gib= + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_grem.mdl", -35); + ThrowGib ("progs/gib1.mdl", -35); + ThrowGib ("progs/gib1.mdl", -35); + ThrowGib ("progs/gib1.mdl", -35); + }; + +void() gremlin_flip1 =[ $flip1.asc, gremlin_flip2 ] + { + ai_face(); + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = '0 0 350' - (v_forward * 200); + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; + sound (self, CHAN_VOICE, "grem/death.wav", 1, ATTN_NORM); + }; +void() gremlin_flip2 =[ $flip2.asc, gremlin_flip3 ] {ai_face();}; +void() gremlin_flip3 =[ $flip3.asc, gremlin_flip4 ] {}; +void() gremlin_flip4 =[ $flip4.asc, gremlin_flip5 ] {}; +void() gremlin_flip5 =[ $flip5.asc, gremlin_flip6 ] {}; +void() gremlin_flip6 =[ $flip6.asc, gremlin_flip7 ] {self.touch = Gremlin_FlipTouch;}; +void() gremlin_flip7 =[ $flip7.asc, gremlin_gib ] + { + self.nextthink = time + 3; + // if three seconds pass, assume gremlin is stuck and jump again + }; +void() gremlin_flip8 =[ $flip8.asc, gremlin_flip8 ] + { + self.solid = SOLID_NOT; + }; + +/* +=============== +GremlinDropBackpack +=============== +*/ +void() GremlinDropBackpack = +{ + local entity item; + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS ) ); + item.items = self.items; + + if (item.items & IT_AXE) + item.netname = "$qc_axe"; + else if (item.items & IT_SHOTGUN) + item.netname = "$qc_shotgun"; + else if (item.items & IT_SUPER_SHOTGUN) + item.netname = "$qc_double_shotgun"; + else if (item.items & IT_NAILGUN) + item.netname = "$qc_nailgun"; + else if (item.items & IT_SUPER_NAILGUN) + item.netname = "$qc_super_nailgun"; + else if (item.items & IT_GRENADE_LAUNCHER) + item.netname = "$qc_grenade_launcher"; + else if (item.items & IT_ROCKET_LAUNCHER) + item.netname = "$qc_rocket_launcher"; + else if (item.items & IT_LIGHTNING) + item.netname = "$qc_thunderbolt"; + else if (item.items & IT_LASER_CANNON) + item.netname = "$qc_laser_cannon"; + else if (item.items & IT_PROXIMITY_GUN) + item.netname = "$qc_prox_gun"; + else if (item.items & IT_MJOLNIR) + item.netname = "$qc_mjolnir"; + else + item.netname = ""; + + item.ammo_shells = self.ammo_shells; + item.ammo_nails = self.ammo_nails; + item.ammo_rockets = self.ammo_rockets; + item.ammo_cells = self.ammo_cells; + if (item.ammo_shells < 0) item.ammo_shells = 0; + if (item.ammo_nails < 0) item.ammo_nails = 0; + if (item.ammo_rockets < 0) item.ammo_rockets = 0; + if (item.ammo_cells < 0) item.ammo_cells = 0; + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + setmodel (item, "progs/backpack.mdl"); + setsize (item, '-16 -16 0', '16 16 56'); + item.touch = BackpackTouch; + + item.nextthink = time + 120; // remove after 2 minutes + item.think = SUB_Remove; +}; + +void() gremlin_die = +{ + local vector vec; + local float dot; + +// check for gib + if (self.items & (IT_SUPER_SHOTGUN|IT_NAILGUN|IT_SUPER_NAILGUN|IT_GRENADE_LAUNCHER|IT_ROCKET_LAUNCHER|IT_LIGHTNING|IT_LASER_CANNON|IT_PROXIMITY_GUN)) + { + GremlinDropBackpack(); + self.stoleweapon = FALSE; + } + + makevectors (self.angles); + vec = normalize (damage_attacker.origin - self.origin); + dot = vec * v_forward; + + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_grem.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + return; + } + else if (dot>0.7 && (random()<0.5) && (self.flags & FL_ONGROUND)) + { + gremlin_flip1(); + return; + } +// regular death + gremlin_die1 (); +}; + + +float() GremlinWeaponAttack; +void() Gremlin_MeleeAttack = + { + local float num; + if (self.gorging) + { + gremlin_gorge1(); + } + else + { + if (self.stoleweapon == 1) + { + objerror("gremlin meleeing with stolen weapon"); + } + else if ((self.enemy.flags & FL_CLIENT) && random() < 0.4) + { + if (GremlinAttemptWeaponSteal()) + return; + } + num = random(); + if (num<0.3) + { + gremlin_claw1 (); + } + else if (num < 0.6) + { + gremlin_lunge1 (); + } + else + { + gremlin_claw1 (); + } + } + }; + +/* +============ +gremlin_recoil +============ +*/ +/* +void() gremlin_recoil = + { + self.nextthink = time + 0.1; + self.flags = self.flags - FL_ONGROUND; + if (vlen(self.velocity)<2) + { + self.movetype = self.spawnsolidtype; + self.think = self.spawnthink; + } + }; +*/ +/* +============ +GremlinRecoil +============ +*/ +/* +void(vector dir, float mag) GremlinRecoil = + { + self.spawnsolidtype = self.movetype; + self.movetype = MOVETYPE_BOUNCE; + self.spawnthink = self.think; + self.think = gremlin_recoil; + self.nextthink = time; + self.velocity = mag * dir; + self.velocity_z = self.velocity_z + 100; + self.flags = self.flags - FL_ONGROUND; + }; +*/ + +/* +============ +GremlinCheckNoAmmo + +attack with a weapon +============ +*/ +float() GremlinCheckNoAmmo = + { + if (self.currentammo > 0) + return TRUE; + else + { + self.stoleweapon = FALSE; + return FALSE; + } + }; + +/* +============ +GremlinFindVictim + +find a victim to shoot at +============ +*/ +entity() GremlinFindVictim = + { + local entity head; + local entity selected; + local float dist; + local float head_dist; + local float decision; + + self.search_time = time + 1.0; +// look in our immediate vicinity + + selected = world; + dist = 1000; + head = findradius(self.origin, 1000); + while(head) + { + if(!(head.flags & FL_NOTARGET) && ((head.flags & FL_MONSTER) || (head.flags & FL_CLIENT))) + { + if (visible(head) && (head.health > 0) && (head !=self)) + { + head_dist = vlen(head.origin-self.origin); + if (head == self.lastvictim) + head_dist = head_dist * 2; + if (head.flags & FL_CLIENT) + head_dist = head_dist / 1.5; + if (head.classname == self.classname) + head_dist = head_dist * 1.5; + if (head_dist < dist) + { + selected = head; + dist = head_dist; + } + } + } + head = head.chain; + } + self.lastvictim = selected; + + return selected; + }; + +/* +============ +Gremlin_FireRocket + +fire a rocket +============ +*/ +void() Gremlin_FireRocket = +{ + local entity missile, mpuff; + local vector dir; + + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.solid = SOLID_BBOX; + missile.classname = "missile"; + +// set missile speed + + dir = normalize(self.enemy.origin - self.origin); + self.v_angle = vectoangles(dir); + makevectors(self.v_angle); + dir = dir + crandom()* 0.1 * v_right + crandom()* 0.1 *v_up; + missile.velocity = normalize(dir); + missile.velocity = missile.velocity * 1000; + missile.angles = vectoangles(missile.velocity); + + missile.touch = T_MissileTouch; + +// set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/missile.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + v_forward*8 + '0 0 16'); +// GremlinRecoil(dir,-1000); +}; + +/* +============ +Gremlin_FireNailGun + +fire a nailgun +============ +*/ +void(float ox) Gremlin_FireNailGun = +{ + local vector dir; + + self.currentammo = self.ammo_nails = self.ammo_nails - 1; + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + dir = normalize(self.enemy.origin - self.origin); + self.v_angle = vectoangles(dir); + makevectors(self.v_angle); + dir = dir + crandom()* 0.1 * v_right + crandom()* 0.1 *v_up; + dir = normalize(dir); + launch_spike (self.origin + '0 0 16', dir); +}; + +/* +============ +Gremlin_FireLaserGun + +fire a laser cannon +============ +*/ +void(float ox) Gremlin_FireLaserGun = +{ + local vector dir; + + self.currentammo = self.ammo_cells = self.ammo_cells - 1; + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + dir = normalize(self.enemy.origin - self.origin); + self.v_angle = vectoangles(dir); + makevectors(self.v_angle); + dir = dir + crandom()* 0.1 * v_right + crandom()* 0.1 *v_up; + dir = normalize(dir); + HIP_LaunchLaser(self.origin + '0 0 16', dir, 0); +}; + +/* +============ +Gremlin_FireShotGun + +fire a shotgun +============ +*/ +void() Gremlin_FireShotGun = +{ + local vector dir; + + self.currentammo = self.ammo_shells = self.ammo_shells - 1; + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM); + dir = normalize(self.enemy.origin - self.origin); + self.v_angle = vectoangles(dir); + makevectors(self.v_angle); + dir = dir + crandom()* 0.1 *v_right + crandom()* 0.1 *v_up; + dir = normalize(dir); + self.v_angle = vectoangles(dir); + FireBullets (6, dir, '0.04 0.04 0'); +}; + +/* +============ +Gremlin_FireSuperShotGun + +fire a shotgun +============ +*/ +void() Gremlin_FireSuperShotGun = +{ + local vector dir; + + self.currentammo = self.ammo_shells = self.ammo_shells - 2; + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM); + dir = normalize(self.enemy.origin - self.origin); + self.v_angle = vectoangles(dir); + makevectors(self.v_angle); + dir = dir + crandom()* 0.3 *v_right + crandom()* 0.3 *v_up; + dir = normalize(dir); + self.v_angle = vectoangles(dir); + FireBullets (14, dir, '0.14 0.08 0'); +}; + +/* +============ +Gremlin_FireLightningGun + +fire lightning gun +============ +*/ +void() Gremlin_FireLightningGun = + { + local vector org, dir; + local float cells; + +// explode if under water + if (self.watertype <= CONTENT_WATER) + { + cells = self.ammo_cells; + self.ammo_cells = 0; + discharged = 1; + T_RadiusDamage (self, self, 35*cells, world); + discharged = 0; +// W_SetCurrentAmmo (); + return; + } + + self.effects = self.effects | EF_MUZZLEFLASH; + + ai_face (); + + self.currentammo = self.ammo_cells = self.ammo_cells - 2; + org = self.origin + '0 0 16'; + + dir = self.enemy.origin + '0 0 16' - org; + dir = normalize (dir); + dir = normalize(self.enemy.origin - self.origin); + self.v_angle = vectoangles(dir); + makevectors(self.v_angle); + dir = dir + crandom()* 0.1 *v_right + crandom()* 0.1 *v_up; + dir = normalize(dir); + + traceline (org, self.origin + dir*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + LightningDamage (org, trace_endpos+(dir*4), self, 30); +}; + +/* +================ +GremlinFireProximityGrenade +================ +*/ +void() GremlinFireProximityGrenade = +{ + local entity missile; + local vector dir; + + NumProximityGrenades = NumProximityGrenades + 1; + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + missile.lastvictim = self; + missile.movetype = MOVETYPE_TOSS; + missile.solid = SOLID_BBOX; + missile.classname = "proximity_grenade"; + missile.takedamage = DAMAGE_NO; + missile.health = 5; + missile.state = 0; + +// set missile speed + + dir = normalize(self.enemy.origin - self.origin); + self.v_angle = vectoangles(dir); + makevectors(self.v_angle); + dir = dir + crandom()* 0.1 *v_right + crandom()* 0.1 *v_up; + dir = normalize(dir); + missile.velocity = dir * 600; + missile.velocity_z = 200; + + missile.avelocity = '100 600 100'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = ProximityGrenadeTouch; + +// set missile duration + missile.nextthink = time + 2; + missile.delay = time + 15 + (10*random()); + missile.think = ProximityBomb; + missile.th_die = ProximityGrenadeExplode; + + setmodel (missile, "progs/proxbomb.mdl"); + setorigin (missile, self.origin); + setsize (missile, '-1 -1 -1', '1 1 1'); +}; + +/* +============ +GremlinWeaponAttack + +attack with a weapon +============ +*/ +void() gremlin_shot1; +void() gremlin_nail1; +void() gremlin_light1; +void() gremlin_rocket1; + +float() GremlinWeaponAttack = + { + local float r; + local entity targ; + local vector vec; + + + if (!GremlinCheckNoAmmo ()) + return FALSE; + + self.show_hostile = time + 1; // wake monsters up + + if (self.weapon == IT_SHOTGUN) + { + gremlin_shot1 (); + Gremlin_FireShotGun(); + SUB_AttackFinished(1); + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + gremlin_shot1 (); + Gremlin_FireSuperShotGun(); + SUB_AttackFinished(1); + } + else if (self.weapon == IT_NAILGUN) + { + gremlin_nail3 (); + SUB_AttackFinished(1); + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + gremlin_nail3 (); + SUB_AttackFinished(1); + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + gremlin_rocket1(); + OgreFireGrenade(); + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + SUB_AttackFinished(1); + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + gremlin_rocket1(); + Gremlin_FireRocket(); + SUB_AttackFinished(1); + } + else if (self.weapon == IT_LIGHTNING) + { + gremlin_light1(); + SUB_AttackFinished(1); + sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + } + else if (self.weapon == IT_LASER_CANNON) + { + gremlin_laser3(); + SUB_AttackFinished(1); + } + else if (self.weapon == IT_PROXIMITY_GUN) + { + gremlin_rocket1(); + GremlinFireProximityGrenade(); + SUB_AttackFinished(1); + } + return TRUE; + }; + +void() Gremlin_MissileAttack = + { + if (self.stoleweapon) + { + if (GremlinWeaponAttack()) + return; + else if ((random()<0.1) && (self.flags & FL_ONGROUND)) + { + gremlin_jump1(); + return; + } + } + if (self.flags & FL_ONGROUND) + gremlin_jump1(); + }; + +/*QUAKED monster_gremlin (1 0 0) (-32 -32 -24) (32 32 64) Ambush + +*/ +void() monster_gremlin = +{ + if (deathmatch) + { + remove(self); + return; + } + NumGremlins = NumGremlins + 1; + precache_model ("progs/grem.mdl"); + precache_model ("progs/h_grem.mdl"); + + precache_sound ("grem/death.wav"); + precache_sound ("grem/attack.wav"); + precache_sound ("demon/djump.wav"); + precache_sound ("demon/dhit2.wav"); + precache_sound ("grem/pain1.wav"); + precache_sound ("grem/pain2.wav"); + precache_sound ("grem/pain3.wav"); + precache_sound ("grem/idle.wav"); + precache_sound ("grem/sight1.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/grem.mdl"); + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + self.health = 100; + self.max_health = 101; + self.yaw_speed = 40; + + self.th_stand = gremlin_stand1; + self.th_walk = gremlin_walk1; + self.th_run = gremlin_run1; + self.th_die = gremlin_die; + self.th_melee = Gremlin_MeleeAttack; // one of two attacks + self.th_missile = Gremlin_MissileAttack; // check for random jump or firing of weapon + self.th_pain = gremlin_pain; + + walkmonster_start(); +}; + +float() GremlinCheckAttack = + { + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + + if (time < self.attack_finished) + return FALSE; + +// see if any entities are in the way of the shot + spot1 = self.origin;// + self.view_ofs; + spot2 = targ.origin;// + targ.view_ofs; + + if ((vlen(spot2 - spot1) <= 90) && (self.stoleweapon == 0)) + { + self.attack_state = AS_MELEE; + return TRUE; + } +// missile attack + chance = 0.03 + self.stoleweapon; + if (random() < chance) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + return FALSE; + }; + + +//=========================================================================== + +void(float side) Gremlin_Melee = +{ + local float ldmg; + local vector delta; + + ai_face (); + +// walkmove (self.ideal_yaw, 12); // allow a little closing + + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + sound (self, CHAN_WEAPON, "grem/attack.wav", 1, ATTN_NORM); + ldmg = 10 + 5*random(); + T_Damage (self.enemy, self, self, ldmg); + + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); +}; + +//=========================================================================== +void(float dm) Gremlin_ThrowHead = + { + local string gibname; + if (self.classname == "monster_ogre") + gibname = "progs/h_ogre.mdl"; + else if (self.classname == "monster_knight") + gibname = "progs/h_knight.mdl"; + else if (self.classname == "monster_shambler") + gibname = "progs/h_shams.mdl"; + else if (self.classname == "monster_demon1") + gibname = "progs/h_demon.mdl"; + else if (self.classname == "monster_wizard") + gibname = "progs/h_wizard.mdl"; + else if (self.classname == "monster_zombie") + gibname = "progs/h_zombie.mdl"; + else if (self.classname == "monster_dog") + gibname = "progs/h_dog.mdl"; + else if (self.classname == "monster_hell_knight") + gibname = "progs/h_hellkn.mdl"; + else if (self.classname == "monster_enforcer") + gibname = "progs/h_mega.mdl"; + else if (self.classname == "monster_army") + gibname = "progs/h_guard.mdl"; + else if (self.classname == "monster_shalrath") + gibname = "progs/h_shal.mdl"; + else if (self.classname == "monster_gremlin") + gibname = "progs/h_grem.mdl"; + else if (self.classname == "monster_scourge") + gibname = "progs/h_scourg.mdl"; + else if (self.classname == "monster_fish") + gibname = "progs/gib1.mdl"; + else + gibname = "progs/h_player.mdl"; + ThrowHead(gibname,dm); + }; +//=========================================================================== + +/* +============ +Gremlin_Damage + +The damage is coming from inflictor, but get mad at attacker +============ +*/ +void(entity targ, entity inflictor, entity attacker, float damage) Gremlin_Damage= +{ +// check for godmode or invincibility + if (targ.flags & FL_GODMODE) + return; + if (targ.invincible_finished >= time) + { + if (self.invincible_sound < time) + { + sound (targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + self.invincible_sound = time + 2; + } + return; + } + +// team play damage avoidance + if ( (teamplay == 1) && (targ.team > 0)&&(targ.team == attacker.team) ) + return; + +// do the damage + targ.health = targ.health - damage; + }; + +void() Gremlin_Split = + { + local entity grem; + local entity temp; + local entity head; + local float done; + local vector ang; + local float c; + local vector pos; + local float proceed; + + if (NumSpawnGremlins >= (NumGremlins*2)) + return; + done = 0; + c = 0; + ang = self.angles; + while (done == 0) + { + makevectors(ang); + pos = self.origin + (80 * v_forward); + head = findradius(pos, 35); + proceed = 1; + while (head) + { + if ((head.health > 0) && (head.flags & (FL_MONSTER | FL_CLIENT))) + proceed = 0; + head = head.chain; + } + traceline(self.origin,pos,FALSE,self); + if (trace_fraction == 1 && (proceed == 1)) + { + traceline(self.origin,(pos-'40 40 0'),FALSE,self); + if (trace_fraction == 1) + { + traceline(self.origin,(pos+'40 40 0'),FALSE,self); + if (trace_fraction == 1) + { + traceline(self.origin,(pos + '0 0 64'),FALSE,self); + if (trace_fraction == 1) + { + traceline(self.origin,(pos - '0 0 64'),FALSE,self); + if (trace_fraction != 1) + { + done = 1; + } + } + } + } + } + if (done == 0) + { + ang_y = ang_y + 36; + c = c + 1; + if (c==10) + { + return; + } + } + } + NumSpawnGremlins = NumSpawnGremlins + 1; + grem = spawn(); + SUB_CopyEntity(self,grem); + grem.solid = SOLID_SLIDEBOX; + grem.movetype = MOVETYPE_STEP; + setmodel (grem, "progs/grem.mdl"); + setsize (grem, VEC_HULL_MIN, VEC_HULL_MAX); + if (self.health < 100) self.health = 100; + grem.health = self.health / 2; + self.health = self.health / 2; +// grem.max_health = 101; +// grem.gorging = FALSE; + grem.stoleweapon = FALSE; + grem.items = 0; + total_monsters = total_monsters + 1; + WriteByte (MSG_BROADCAST, SVC_UPDATESTAT); + WriteByte (MSG_BROADCAST, STAT_TOTALMONSTERS); + WriteLong (MSG_BROADCAST, total_monsters); + setorigin(grem, pos); + temp = self; + self = grem; + gremlin_spawn1(); + self.enemy = world; + self.gorging = FALSE; + self = temp; + }; + +void(float side) Gremlin_Gorge = + { + local float ldmg; + local vector delta; + local entity temp; + +// ai_face (); +// walkmove (self.ideal_yaw, 6); // allow a little closing + + delta = self.enemy.origin - self.origin; +// self.enemy.takedamage = DAMAGE_YES; + + sound (self, CHAN_WEAPON, "demon/dhit2.wav", 1, ATTN_NORM); +// sound (self, CHAN_WEAPON, "grem/attack.wav", 1, ATTN_NORM); + ldmg = 7+5*random(); + Gremlin_Damage (self.enemy, self, self, ldmg); + + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + if (self.enemy.health < -200.0) + { + if (self.enemy.gorging==FALSE) + { + self.enemy.gorging = TRUE; + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + temp = self; + self = self.enemy; + Gremlin_ThrowHead(-15); +// ThrowGib ("progs/gib1.mdl", -15); +// ThrowGib ("progs/gib2.mdl", -15); +// ThrowGib ("progs/gib3.mdl", -15); + self = temp; + ldmg = 150 + 100*random(); + T_Heal(self,ldmg,FALSE); +// if (self.health >= self.max_health) + Gremlin_Split(); + } + self.enemy = world; + self.gorging = FALSE; +/* + if (self.stoleweapon) + { + temp = GremlinFindVictim(); + if (temp != world) + { + self.enemy = temp; + FoundTarget(); + self.search_time = time + 1.0; +// return; + } + } +*/ +// FindTarget(); +/* + if (self.oldenemy.health > 0) + { + self.enemy = self.oldenemy; + HuntTarget (); + } + else + { + if (self.movetarget) + self.th_walk (); + else + self.th_stand (); + } +*/ + gremlin_look1(); + } + }; + + +void() Gremlin_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + +/* + if (other.takedamage) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 40 + 10*random(); + T_Damage (other, self, self, ldmg); + } + } +*/ + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { + self.touch = SUB_Null; + self.think = gremlin_jump1; + self.nextthink = time + 0.1; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = gremlin_jump12; + self.nextthink = time + 0.1; +}; + +void() Gremlin_FlipTouch = +{ + local float ldmg; + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { + self.touch = SUB_Null; + self.think = gremlin_flip1; + self.nextthink = time + 0.1; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = gremlin_flip8; + self.nextthink = time + 0.1; +}; diff --git a/quakec_hipnotic/hipholes.qc b/quakec_hipnotic/hipholes.qc new file mode 100644 index 0000000..b6cc3db --- /dev/null +++ b/quakec_hipnotic/hipholes.qc @@ -0,0 +1,129 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Bullet holes QuickC program + By Jim Dose' 11/20/96 +*/ + +/*QUAKED wallsprite (0 1 0) (-8 -8 -8) (8 8 8) +Places a sprite on a wall. Angles should be opposite of face. + +"model" sprite to place on wall. Default is "progs/s_blood1.spr". +*/ +void() wallsprite = + { + if ( !self.model ) + { + self.model = "progs/s_blood1.spr"; + } + + precache_model( self.model ); + setmodel (self, self.model ); + + // QuakeEd doesn't save up and down angles properly. + if (self.angles == '0 -1 0') + self.angles = '-90 0 0'; + else if (self.angles == '0 -2 0') + self.angles = '90 0 0'; + + // Pull the sprite away from the wall slightly to + // get rid of z sort errors. + makevectors(self.angles); + setorigin( self, self.origin - ( v_forward * 0.2 ) ); + makestatic (self); + }; + +void() InitBulletHoles = + { + precache_model ("progs/s_bullet.spr"); + + bulletholes = nullentity; + lastbullet = nullentity; + numbulletholes = 0; + }; + +void() remove_bullethole = + { + local entity ent; + + // There is a possibility that this is not the first bullet + // in the list, but it doesn't really matter. All that + // matters is there is one less bullet. Just make sure + // we don't remove the world! + if ( bulletholes == nullentity ) + { + objerror("remove_bullethole: bulletholes == nullentity! " ); + } + + ent = bulletholes; + if ( ent.classname != "bullethole" ) + { + objerror("remove_bullethole: Tried to remove non-bullethole!" ); + } + + bulletholes = bulletholes.lastvictim; + remove( ent ); + if ( lastbullet == ent ) + { + lastbullet = nullentity; + } + numbulletholes = numbulletholes - 1; + }; + +void( vector pos ) placebullethole = +{ + local entity new; + local entity ent; + local vector norm; + + new = spawn(); + new.owner = new; + new.movetype = MOVETYPE_NONE; + new.solid = SOLID_NOT; + new.classname = "bullethole"; + setmodel( new, "progs/s_bullet.spr" ); + setsize (new, '0 0 0', '0 0 0'); + + norm = trace_plane_normal; + norm_x = 0 - norm_x; + norm_y = 0 - norm_y; + new.angles = vectoangles( norm ); + makevectors(self.angles); + setorigin( new, pos - ( v_forward * 0.2 ) ); + + new.think = remove_bullethole; + new.nextthink = time + 300; + + numbulletholes = numbulletholes + 1; + if ( numbulletholes > 10 ) + { + remove_bullethole(); + } + + if ( lastbullet != nullentity ) + { + lastbullet.lastvictim = new; + } + else + { + bulletholes = new; + } + new.lastvictim = nullentity; + lastbullet = new; + }; diff --git a/quakec_hipnotic/hipitems.qc b/quakec_hipnotic/hipitems.qc new file mode 100644 index 0000000..9e5635b --- /dev/null +++ b/quakec_hipnotic/hipitems.qc @@ -0,0 +1,899 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Items QuickC program + By Jim Dose' 9/13/96 +*/ + +float UNDERWATER = 2; + +/* +=============================================================================== + +HIPNOTIC ITEMS + +=============================================================================== +*/ +// +// hip_powerup_touch function +// +void() hip_powerup_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + sprint(other, "$qc_got_item", self.netname); + + if (deathmatch) + { + self.mdl = self.model; + +// if ((self.classname == "item_artifact_invulnerability") || +// (self.classname == "item_artifact_invisibility")) +// self.nextthink = time + 60*5; +// else + self.nextthink = time + 60; + + self.think = SUB_regen; + } + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items2 = other.items2 | self.items2; + self.model = string_null; + +// do the apropriate action + if ( self.classname == "item_artifact_wetsuit" ) + { + other.wetsuit_time = 1; + other.wetsuit_finished = time + 30; + } + if ( self.classname == "item_artifact_empathy_shields" ) + { + other.empathy_time = 1; + other.empathy_finished = time + 30; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_artifact_wetsuit (0 .5 .8) (-16 -16 -24) (16 16 32) +Player takes no damage from electrical attacks and swims faster for 30 seconds +*/ +void() item_artifact_wetsuit = +{ + self.touch = hip_powerup_touch; + + precache_model ("progs/wetsuit.mdl"); + precache_sound ("misc/wetsuit.wav"); + precache_sound ("misc/weton.wav"); + precache_sound ("items/suit2.wav"); + self.noise = "misc/weton.wav"; + setmodel (self, "progs/wetsuit.mdl"); + self.netname = "$qc_wetsuit"; + self.items2 = HIP_IT_WETSUIT; + setsize (self, '-16 -16 -24', '16 16 32'); + + StartItem (); +}; + +/* +=============================================================================== +// +// Horn of Conjuring +// +=============================================================================== +*/ + +void() horn_touch = +{ + local float amount; + local float value; + + if (other.classname != "player") + return; + + if (deathmatch) + { + self.mdl = self.model; + + self.nextthink = time + 60; + + self.think = SUB_regen; + } + + self.solid = SOLID_NOT; + self.model = string_null; + sprint (other, "$qc_got_horn"); + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NONE); + stuffcmd (other, "bf\n"); + activator = other; + horn_active = 1; + horn_charmer = other; + SUB_UseTargets(); // fire all targets / killtargets + horn_active = 0; +}; + +/*QUAKED item_hornofconjuring (0 .5 .8) (-16 -16 0) (16 16 32) +Horn of Conjuring. +You must make func_spawn entities connected to this entity +to spawn the charmed creature. +*/ +void() item_hornofconjuring = + { + self.touch = horn_touch; + + precache_model("progs/horn.mdl"); + precache_sound("hipitems/horn.wav"); + setmodel(self, "progs/horn.mdl"); + self.noise = "hipitems/horn.wav"; + setsize (self, '-16 -16 0', '16 16 32'); + StartItem (); + }; + +/*QUAKED item_artifact_empathy_shields (0 .5 .8) (-16 -16 0) (16 16 32) +Empathy Shield. +*/ +void() item_artifact_empathy_shields = + { + self.touch = hip_powerup_touch; + + precache_model("progs/empathy.mdl"); + precache_sound("hipitems/empathy.wav"); + precache_sound("hipitems/empathy2.wav"); + precache_sound ("items/suit2.wav"); + setmodel(self, "progs/empathy.mdl"); + self.noise = "hipitems/empathy.wav"; + self.netname = "$qc_empathy_shields"; + self.items2 = HIP_IT_EMPATHY_SHIELDS; + setsize (self, '-16 -16 0', '16 16 32'); + StartItem (); + }; +/* +=============================================================================== + +HIPNOTIC WEAPONS + +=============================================================================== +*/ + +/*QUAKED weapon_mjolnir (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_mjolnir = +{ + precache_model ("progs/g_hammer.mdl"); + setmodel (self, "progs/g_hammer.mdl"); + self.weapon = 3; + self.netname = "$qc_mjolnir"; + self.items = IT_MJOLNIR; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_laser_gun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_laser_gun = +{ + precache_model ("progs/g_laserg.mdl"); + setmodel (self, "progs/g_laserg.mdl"); + self.weapon = 3; + self.netname = "$qc_laser_cannon"; + self.items = IT_LASER_CANNON; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_proximity_gun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_proximity_gun = +{ + precache_model ("progs/g_prox.mdl"); + setmodel (self, "progs/g_prox.mdl"); + self.weapon = 3; + self.netname = "$qc_prox_gun"; + self.items = IT_PROXIMITY_GUN; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/* +=============================================================================== + +HIPNOTIC HAZARDS + +=============================================================================== +*/ + +void() spikemine_Home = + { + local entity head; + local entity selected; + local float cur_dist; + local float head_dist; + local vector dir, vtemp; + + self.frame = self.frame + 1; + if (self.frame==9) self.frame = 0; + self.nextthink = time + 0.2; + self.think = spikemine_Home; + +// look in our immediate vicinity + + if (self.search_time < time) + { + selected = world; + cur_dist = 2000; + head = findradius(self.origin, 2000); + while(head) + { + if(!(head.flags & FL_NOTARGET) && (head.flags & FL_CLIENT)) + { + if (visible(head) && (head.health > 0)) + { + head_dist = vlen(head.origin-self.origin); + if (head_dist < cur_dist) + { + selected = head; + cur_dist = head_dist; + } + } + } + head = head.chain; + } +// if (selected != world && selected != self.enemy) + if (selected != world) + sound (self, CHAN_VOICE, "hipitems/spikmine.wav", 1, ATTN_NORM); + self.enemy = selected; + self.search_time = time + 1.3; + } + if (self.enemy == world) + { + sound (self, CHAN_VOICE, "misc/null.wav", 1, ATTN_NORM); + self.velocity = '0 0 0'; + return; + } + vtemp = self.enemy.origin + '0 0 10'; + dir = normalize(vtemp - self.origin); + if (infront(self.enemy)) + { + self.velocity = dir * ((skill*50) + 50); + } + else + { + self.velocity = dir * ((skill*50) + 150); + } + }; + +void() spikemine_Touch = + { + if (self.health>0) + { + if (other.classname == "trap_spike_mine") + return; + if (other.classname == "missile") + return; + if (other.classname == "grenade") + return; + if (other.classname == "hiplaser") + return; + if (other.classname == "proximity_grenade") + return; + + T_Damage(self,self,self,self.health+10); +// killed_monsters = killed_monsters + 1; +// WriteByte (MSG_ALL, SVC_KILLEDMONSTER); + } +// self.effects = self.effects | EF_MUZZLEFLASH; + + T_RadiusDamage (self, self, 110, world); + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + sound (self, CHAN_VOICE, "misc/null.wav", 1, ATTN_NORM); + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); + }; + +/* +spike_mine_first_think +*/ + +void() spike_mine_first_think = + { + self.think = spikemine_Home; + self.nextthink = time + 0.1; + self.search_time = 0; + self.takedamage = DAMAGE_AIM; + self.use = monster_use; + }; + +/*QUAKED trap_spike_mine (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() trap_spike_mine = + { + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/spikmine.mdl"); + precache_sound ("weapons/r_exp3.wav"); + precache_sound ("hipitems/spikmine.wav"); + precache_sound ("misc/null.wav"); + setmodel (self, "progs/spikmine.mdl"); +// setmodel (self, "progs/spike.mdl"); + setsize (self, self.mins, self.maxs); + self.classname = "trap_spike_mine"; + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_FLYMISSILE; +// setsize (self, '0 0 0', '0 0 0'); +// self.avelocity = '-100 100 -100'; + self.avelocity = '-50 100 150'; + if (cvar("skill") <= 1) + self.health = 200; + else + self.health = 400; + self.frame = 0; + self.think = spike_mine_first_think; + self.touch = spikemine_Touch; + self.th_die = spikemine_Touch; + self.th_stand = spikemine_Home; + self.th_walk = spikemine_Home; + self.th_run = spikemine_Home; + self.th_melee = spikemine_Home; + self.th_missile = spikemine_Home; + self.nextthink = time + 0.2; + total_monsters = total_monsters + 1; + self.flags = self.flags | FL_MONSTER; + self.deathtype = "$qc_suicide_mine"; + }; + +//============================================================================ +float LIGHTNING_RANDOM = 1; +float LIGHTNING_BOOM = 2; + +void() SpawnLightningThink = + { + if (time > self.delay) + { + remove(self); + return; + } + self.think = SpawnLightningThink; + if (checkclient()) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteCoord (MSG_BROADCAST, self.oldorigin_x); + WriteCoord (MSG_BROADCAST, self.oldorigin_y); + WriteCoord (MSG_BROADCAST, self.oldorigin_z); + } + LightningDamage(self.origin, self.oldorigin, self.lastvictim, self.dmg); + self.nextthink = time + 0.1; + }; + +void() trap_lightning_use = + { + local vector p1, p2; + local vector dir; + local float dst; + local float remainder; + + if (time >= self.pausetime) + { + if (self.spawnflags & LIGHTNING_BOOM) + sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + else + sound (self, CHAN_AUTO, "weapons/lhit.wav", 1, ATTN_NORM); + if (self.classname == "trap_lightning_triggered") + self.pausetime = time + 0.1; + } + if (self.target) + { + p1 = self.origin; + p2 = self.enemy.origin; + } + else + { + makevectors (self.angles); + self.movedir = v_forward; + traceline (self.origin, self.origin + self.movedir*600, TRUE, self); + p1 = self.origin; + p2 = trace_endpos; + } + // fix up both ends of the lightning + // lightning bolts are 30 units long each + dir = normalize( p2-p1 ); + dst = vlen(p2-p1); + dst = dst / 30.0; + remainder = dst - floor(dst); + if (remainder > 0) + { + remainder = remainder - 1; + // split half the remainder with the front and back + remainder = remainder * 15; + p1 = p1 + (remainder*dir); + p2 = p2 - (remainder*dir); + } + if (self.duration > 0.1) + { + local entity temp; + + temp = self; + self = spawn(); + self.origin = p1; + self.oldorigin = p2; + self.lastvictim = temp; + self.dmg = temp.dmg; + self.delay = time + temp.duration; + SpawnLightningThink(); + self = temp; + } + else if (checkclient()) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, p1_x); + WriteCoord (MSG_BROADCAST, p1_y); + WriteCoord (MSG_BROADCAST, p1_z); + WriteCoord (MSG_BROADCAST, p2_x); + WriteCoord (MSG_BROADCAST, p2_y); + WriteCoord (MSG_BROADCAST, p2_z); + LightningDamage(p1, p2, self, self.dmg); + } + else + LightningDamage(p1, p2, self, self.dmg); + }; + +void() lightning_think = + { + local float timedelay; + + if (self.state) + { + trap_lightning_use(); + } + if (self.cnt == 0) + { + if (self.spawnflags & LIGHTNING_RANDOM) + { + timedelay = self.wait*random(); + } + else + { + timedelay = self.wait; + } + self.cnt = 1; + self.t_length = time + self.duration - 0.1; + self.pausetime = time + self.duration - 0.1; + if (self.pausetime < time + 0.3) + self.pausetime = time + 0.3; + if (timedelay < self.duration) + timedelay = self.duration; + self.t_width = time + timedelay; + } + if (time >= self.t_length) + { + self.cnt = 0; + self.nextthink = self.t_width; + } + else + { + self.nextthink = time + 0.2; + } + }; + +void() lightning_firstthink = + { + local entity targ; + if (self.target) + { + targ = find(world,targetname,self.target); + self.dest = targ.origin; + self.enemy = targ; + } + self.think = SUB_Null; + self.nextthink = 0; + if (self.classname != "trap_lightning_triggered") + { + self.nextthink = self.huntingcharmer + self.wait + self.ltime; + self.think = lightning_think; + } + }; + +/*QUAKED trap_lightning_triggered (0 .5 .8) (-8 -8 -8) (8 8 8) random boom +When triggered, fires lightning in the direction set in QuakeEd. +"wait" how long to wait between blasts (1.0 default) + if in random mode wait is multiplied by random +"dmg" how much damage lightning should inflict (30 default) +"duration" how long each lightning attack should last (0.1 default) +*/ + +void() trap_lightning_triggered = + { + if (self.wait == 0) + self.wait = 1.0; + if (self.dmg == 0) + self.dmg = 30; + if (self.duration == 0) + self.duration = 0.1; + self.cnt = 0; + self.use = trap_lightning_use; + precache_sound ("weapons/lhit.wav"); + precache_sound ("weapons/lstart.wav"); + self.huntingcharmer = self.nextthink; + self.think = lightning_firstthink; + self.nextthink = time + 0.25; + self.deathtype = "$qc_suicide_electrocuted"; + }; + + +/*QUAKED trap_lightning (0 .5 .8) (-8 -8 -8) (8 8 8) random boom +Continuously fire lightning. +"wait" how long to wait between blasts (1.0 default) + if in random mode wait is multiplied by random +"nextthink" delay before firing first lightning, so multiple traps can be stagered. +"dmg" how much damage lightning should inflict (30 default) +"duration" how long each lightning attack should last (0.1 default) +*/ +void() trap_lightning = + { + trap_lightning_triggered (); + self.state = 1; + }; + +void() trap_lightning_switched_use = + { + self.state = 1 - self.state; + if (self.state == 1) + self.nextthink = self.huntingcharmer; + }; +/*QUAKED trap_lightning_switched (0 .5 .8) (-8 -8 -8) (8 8 8) random boom +Continuously fires lightning. +"wait" how long to wait between blasts (1.0 default) + if in random mode wait is multiplied by random +"nextthink" delay before firing first lightning, so multiple traps can be stagered. +"dmg" how much damage lightning should inflict (30 default) +"duration" how long each lightning attack should last (0.1 default) +"state" 0 (default) initially off, 1 initially on. +*/ +void() trap_lightning_switched = + { + trap_lightning_triggered (); + self.use = trap_lightning_switched_use; + }; + + +entity tesla_target; +float tesla_numtargets; +void() trap_tesla_scan = + { + local entity head; + local entity prev; + +// look in our immediate vicinity + + tesla_numtargets = 0; + head = findradius(self.origin, self.distance); + while(head) + { + if(!(head.flags & FL_NOTARGET) && (head.flags & self.cnt)) + { + if (visible(head) && (head.health > 0) && (head.struck_by_mjolnir==0)) + { + if (tesla_numtargets == 0) + { + tesla_target = head; + } + else + { + prev.next_ent = head; + } + tesla_numtargets = tesla_numtargets + 1; + prev = head; + if (tesla_numtargets==self.count) + return; + } + } + head = head.chain; + } + }; + +void() TeslaLightningThink = + { + self.owner.attack_state = 2; + if (time > self.delay) + { + self.enemy.struck_by_mjolnir = 0; + remove(self); + return; + } + traceline (self.origin, self.enemy.origin, TRUE, self); + + if (trace_fraction != 1.0 || self.enemy.health<=0 || vlen(self.origin-self.enemy.origin) > (self.distance+10)) + { + self.enemy.struck_by_mjolnir = 0; + remove(self); + return; + } + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + LightningDamage(self.origin, trace_endpos, self.lastvictim, self.dmg); + self.nextthink = time + 0.1; + }; + +void(entity targ) SpawnTeslaLightning = + { + local entity lgt; + // spawn actual lightning + lgt = spawn(); + if (self.duration>0) + { + lgt.delay = time + self.duration; + } + else + { + lgt.delay = time + 9999; + } + lgt.enemy = targ; + targ.struck_by_mjolnir = 1; + lgt.distance = self.distance; + lgt.owner = self; + lgt.lastvictim = self.lastvictim; + lgt.dmg = self.dmg; + lgt.origin = self.origin; + lgt.think = TeslaLightningThink; + lgt.nextthink = time; + lgt.deathtype = self.deathtype; + }; + +void() trap_tesla_think = + { + if (self.state == 0) + { + self.nextthink = time + 0.25; + return; + } + if (self.attack_state == 0) + { + self.think = trap_tesla_think; + trap_tesla_scan(); + if (tesla_numtargets > 0) + { + if (self.wait > 0) + sound (self, CHAN_AUTO, "misc/tesla.wav", 1, ATTN_NORM); + self.attack_state = 1; + self.nextthink = time + self.wait; + return; + } + self.nextthink = time + 0.25; + if (self.delay > 0) + { + if (time > self.search_time) + { + self.attack_state = 3; + } + } + } + else if (self.attack_state == 1) + { + trap_tesla_scan(); + while (tesla_numtargets > 0) + { + sound (self, CHAN_AUTO, "hipweap/mjolhit.wav", 1, ATTN_NORM); + SpawnTeslaLightning (tesla_target); + tesla_target = tesla_target.next_ent; + tesla_numtargets = tesla_numtargets - 1; + } + self.attack_state = 2; + self.nextthink = time + 1; + } + else if (self.attack_state == 2) + { + self.attack_state = 3; + self.nextthink = time + 0.2; + } + else if (self.attack_state == 3) + { + self.attack_state = 0; + self.nextthink = time + 0.1; + if (self.classname == "trap_gods_wrath") + { + self.nextthink = -1; + } + } + }; + +/*QUAKED trap_tesla_coil (0 .5 .8) (-8 -8 -8) (8 8 8) targetenemies +targets enemies in vicinity and fires at them +"wait" how long build up should be (2 second default) +"dmg" how much damage lightning should inflict (2 + 5*skill default) +"duration" how long each lightning attack should last (continuous default) +"distance" how far the tesla coil should reach (600 default) +"state" on/off for the coil (0 default is off) +"count" number of people to target (2 default) +*/ +void() trap_tesla_coil = + { + precache_sound ("misc/tesla.wav"); + precache_sound ("hipweap/mjolhit.wav"); // lightning sound + if (self.wait == 0) + self.wait = 2; + if (self.dmg == 0) + self.dmg = 2 + (5*cvar("skill")); + if (self.duration == 0) + self.duration = -1; + if (self.distance == 0) + self.distance = 600; + if (self.spawnflags & 1) + self.cnt = FL_CLIENT | FL_MONSTER; + else + self.cnt = FL_CLIENT; + self.use = trap_lightning_switched_use; + if (self.delay == 0) + self.delay = -1; + self.nextthink = time + random(); + self.think = trap_tesla_think; + self.lastvictim = world; + tesla_numtargets = 0; + self.attack_state = 0; + self.deathtype = "$qc_suicide_electrocuted"; + }; + +void() trap_gods_wrath_use = + { + if (self.attack_state==0) + { + self.search_time = time + self.delay; + self.lastvictim = activator; + trap_tesla_think(); + } + }; + +/*QUAKED trap_gods_wrath (0 .5 .8) (-8 -8 -8) (8 8 8) targetenemies +targets enemies in vicinity and fires at them +"dmg" how much damage lightning should inflict (5 default) +"duration" how long each lightning attack should last (continuous default) +"distance" how far god's wrath should reach (600 default) +"delay" how long to wait until god calms down + this is only needed if no one is targetted (5 seconds default) +"count" number of people to target (2 default) +*/ +void() trap_gods_wrath = + { + if (self.delay == 0) + self.delay = 5; + trap_tesla_coil(); + self.wait = 0; + self.state = 1; + self.nextthink = -1; + self.deathtype = "$qc_suicide_wrath"; +// self.attack_state = 1; + self.use = trap_gods_wrath_use; + }; + +void() trap_gravity_touch = + { + if ( self.attack_finished > time ) + return; + + if (other.takedamage) + { + T_Damage (other, self, self, self.dmg ); + self.attack_finished = time + 0.2; + } + }; + +void() trap_gravity_think = + { + local vector vel; + local vector dir; + local vector delta; + + self.ltime = time; + trap_tesla_scan(); + while (tesla_numtargets > 0) + { + delta = self.origin - tesla_target.origin; + dir = normalize( delta ); + vel = dir * self.speed; + if ( ( tesla_target.wetsuit_finished > time ) && + ( self.spawnflags & UNDERWATER ) ) + { + vel = vel * 0.6; + } + + tesla_target.velocity = tesla_target.velocity + vel; + tesla_target = tesla_target.next_ent; + tesla_numtargets = tesla_numtargets - 1; + } + self.nextthink = time + 0.1; + }; + +/*QUAKED trap_gravity_well (.8 .5 0) (-8 -8 -8) (8 8 8) targetenemies UNDERWATER +targets enemies in vicinity and draws them near, gibbing them on contact. + +UNDERWATER cuts the pull in half for players wearing the wetsuit + +"distance" how far the gravity well should reach (600 default) +"count" number of people to target (2 default) +"speed" is how strong the pull is. (210 default) +"dmg" is how much damage to do each touch. (10000 default) +*/ +void() trap_gravity_well = + { + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_NONE; + setsize( self, '-16 -16 -16','16 16 16'); + if ( self.dmg == 0 ) + { + self.dmg = 10000; + } + if ( self.speed == 0 ) + self.speed = 210; + if (self.distance == 0) + self.distance = 600; + if (self.spawnflags & 1) + self.cnt = FL_CLIENT | FL_MONSTER; + else + self.cnt = FL_CLIENT; + + self.attack_finished = 0; + self.think = trap_gravity_think; + self.touch = trap_gravity_touch; + self.lastvictim = world; + tesla_numtargets = 0; + self.nextthink = time + 0.1; + self.ltime = time; + }; diff --git a/quakec_hipnotic/hipmisc.qc b/quakec_hipnotic/hipmisc.qc new file mode 100644 index 0000000..faefc35 --- /dev/null +++ b/quakec_hipnotic/hipmisc.qc @@ -0,0 +1,572 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Miscelanneous QuickC program + +*/ + +void() play_sound_use = + { + if (self.spawnflags & 1) + { + if (self.state == 0) + { + self.state = 1; + sound (self, self.impulse, self.noise, self.volume, self.speed); + } + else + { + self.state = 0; + sound (self, self.impulse, "misc/null.wav", self.volume, self.speed); + } + } + else + { + sound (self, self.impulse, self.noise, self.volume, self.speed); + } + }; + +void() PlaySoundThink = + { + local float t; + t = self.wait * random(); + if (t < self.delay) + t = self.delay; + self.nextthink = time + t; + play_sound_use(); + }; + +/*QUAKED play_sound_triggered (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) toggle +play a sound when it is used +"toggle" determines whether sound should be stopped when triggered again +"volume" how loud (1 default full volume) +"noise" sound to play +"impulse" channel on which to play sound (0-7) (0 automatic is default) +"speed" attenuation factor + -1 - no attenuation + 1 - normal + 2 - idle + 3 - static +*/ +void() play_sound_triggered = + { + precache_sound (self.noise); + precache_sound ("misc/null.wav"); + if (self.volume == 0) + self.volume = 1; + if (self.speed == 0) + self.speed = 1; + if (self.speed == -1) + self.speed = 0; + if (self.spawnflags & 1) + if (self.impulse == 0) + self.impulse = 7; + self.use = play_sound_use; + }; + +/*QUAKED play_sound (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +play a sound on a periodic basis +"volume" how loud (1 default full volume) +"noise" sound to play +"wait" random time between sounds (default 20) +"delay" minimum delay between sounds (default 2) +"impulse" channel on which to play sound (0-7) (0 automatic is default) +"speed" attenuation factor + -1 - no attenuation + 1 - normal + 2 - idle + 3 - static +*/ +void() play_sound = + { + local float t; + + play_sound_triggered(); + if (self.wait == 0) + self.wait = 20; + if (self.delay == 0) + self.delay = 2; + self.think = PlaySoundThink; + t = self.wait * random(); + if (t < self.delay) + t = self.delay; + self.nextthink = time + t; + }; + +/*QUAKED random_thunder (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +"wait" random time between strikes (default 20) +"delay" minimum delay between strikes (default 2) +"volume" how loud (1 default full volume) +"speed" attenuation factor + -1 - no attenuation + 1 - normal + 2 - idle + 3 - static +*/ +void() random_thunder = + { + self.noise = "ambience/thunder1.wav"; + play_sound(); + self.impulse = 6; + }; + +/*QUAKED random_thunder_triggered (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) toggle +"toggle" determines whether sound should be stopped when triggered again +"volume" how loud (1 default full volume) +"speed" attenuation factor + -1 - no attenuation + 1 - normal + 2 - idle + 3 - static +*/ +void() random_thunder_triggered = + { + self.noise = "ambience/thunder1.wav"; + play_sound_triggered(); + self.impulse = 6; + }; + +/*QUAKED ambient_humming (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + "volume" how loud it should be (0.5 is default) +*/ +void() ambient_humming = +{ + if (self.volume==0) + self.volume = 0.5; + precache_sound ("ambient/humming.wav"); + ambientsound (self.origin, "ambient/humming.wav", self.volume, ATTN_STATIC); +}; + +/*QUAKED ambient_rushing (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + "volume" how loud it should be (0.5 is default) +*/ +void() ambient_rushing = +{ + if (self.volume==0) + self.volume = 0.5; + precache_sound ("ambient/rushing.wav"); + ambientsound (self.origin, "ambient/rushing.wav", self.volume, ATTN_STATIC); +}; + +/*QUAKED ambient_running_water (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + "volume" how loud it should be (0.5 is default) +*/ +void() ambient_running_water = +{ + if (self.volume==0) + self.volume = 0.5; + precache_sound ("ambient/runwater.wav"); + ambientsound (self.origin, "ambient/runwater.wav", self.volume, ATTN_STATIC); +}; + +/*QUAKED ambient_fan_blowing (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + "volume" how loud it should be (0.5 is default) +*/ +void() ambient_fan_blowing = +{ + if (self.volume==0) + self.volume = 0.5; + precache_sound ("ambient/fanblow.wav"); + ambientsound (self.origin, "ambient/fanblow.wav", self.volume, ATTN_STATIC); +}; + +/*QUAKED ambient_waterfall (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + "volume" how loud it should be (0.5 is default) +*/ +void() ambient_waterfall = +{ + if (self.volume==0) + self.volume = 0.5; + precache_sound ("ambient/waterfal.wav"); + ambientsound (self.origin, "ambient/waterfal.wav", self.volume, ATTN_STATIC); +}; + +/*QUAKED ambient_riftpower (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + "volume" how loud it should be (0.5 is default) +*/ +void() ambient_riftpower = +{ + if (self.volume==0) + self.volume = 0.5; + precache_sound ("ambient/riftpowr.wav"); + ambientsound (self.origin, "ambient/riftpowr.wav", self.volume, ATTN_STATIC); +}; + + +/*QUAKED info_command (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + Stuffs a command into the console to allow map designers + to set server variables. + + "message" is the command to send to the console. +*/ + +void() info_command = + { + if ( self.message ) + localcmd( self.message ); + }; + +void() effect_teleport_use = + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + sound (self, CHAN_VOICE, "misc/r_tele1.wav", 1, ATTN_NORM); + }; + +/*QUAKED effect_teleport (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) + Create a teleport effect when triggered +*/ + +void() effect_teleport = + { + precache_sound("misc/r_tele1.wav"); + self.use = effect_teleport_use; + }; + + +/* +============= +camerathink + +camera think function +============== +*/ +/* +void() camerathink = + { + local entity pl; + local vector d_diff; + local vector a_diff; + local vector p; + local vector a; + local float dist; + local float timeelapsed; + + timeelapsed = (time - self.cnt) * self.duration; + if ( timeelapsed > 1 ) + timeelapsed = 1; + + p = self.oldorigin + (timeelapsed * (self.movetarget.origin - self.oldorigin)); + d_diff = p - self.origin; + + a = self.mangle + (timeelapsed * (self.movetarget.mangle - self.mangle)); + a_diff = a - self.angles; + + self.origin = self.origin + self.pos1 + (1.0 * d_diff); + self.angles = self.v_angle = self.angles + self.avelocity + (1.0 * a_diff); + + self.nextthink = time + 0.1; + }; +*/ +/* +============= +camerachase + +camera chase function +============== +*/ +/* +void() camerachase = + { + local float timeelapsed; + local vector v; + local vector a; + + + timeelapsed = time - self.cnt; + if ( timeelapsed > 1 ) + timeelapsed = 1; + self.cnt = time; + + self.velocity = (self.movetarget.origin - self.oldorigin)*(1/timeelapsed); + self.oldorigin = self.movetarget.origin; + if (self.state == 0) // setup + { + self.velocity = '0 0 0'; + self.cnt = time; + } + // calculate new camera position + a = '0 0 0'; + a_y = vectoyaw( normalize( self.movetarget.origin - self.movetarget.movetarget.origin) ); + makevectors(a); + self.origin = self.movetarget.origin + v_forward * self.distance; + self.origin_z = self.origin_z + self.height; + + v = normalize( self.movetarget.origin - self.origin ); + a = self.angles; + a_y = vectoyaw(v); + v_x = v_y; + v_y = 0 - v_z; + a_x = vectoyaw(v); + self.angles = a; +// SetViewPoint(self, self); +// UpdateCamera(self, self); + SetViewAngle(self, a); + + self.think = camerachase; + self.nextthink = time + 0.1; + }; +*/ + +/* +============= +camerafollow + +camera follow function +============== +*/ +/* +void() camerafollow = + { + local vector v; + local vector a; + + v = normalize( self.movetarget.origin - self.origin ); + a = self.angles; + a_y = vectoyaw(v); + v_x = v_y; + v_y = 0 - v_z; + a_x = vectoyaw(v); + self.angles = a; + SetViewAngle(self, a); + + self.think = camerafollow; + self.nextthink = time + 0.01; + }; +*/ +/* +============= +t_cameratarget + +a camera has reached a path trigger +============== +*/ +/* +void() t_cameratarget = +{ +local entity temp; + + if (other.movetarget != self) + return; + + temp = self; + self = other; + other = temp; + + self.angles = self.movetarget.mangle; + self.movetarget = find (world, targetname, other.target); + if (!self.movetarget) + { + self.nextthink = time + 999999; + } + else + { + local vector d_diff; + local vector a_diff; + local float t; + + self.think = camerathink; + self.duration = 1 / other.duration; + self.cnt = time; + + self.oldorigin = self.origin; + d_diff = self.movetarget.origin - self.oldorigin; + self.pos1 = d_diff*self.duration; + + self.mangles = self.angles; + a_diff = self.movetarget.mangle - self.mangle; + self.avelocity = a_diff*self.duration; + if (other.delay) + { + self.nextthink = time + other.delay; + self.cnt = time + other.delay; + } + } +}; +*/ + +/*QUAKED path_camera (0.5 0.3 0) (-8 -8 -8) (8 8 8) +path for a camera +"delay" delay to wait before proceeding to next segment; +"mangle" position the camera should be when it gets here +"duration" how long it should take to cover the distance + (default 10 seconds) +*/ +/* +void() path_camera = +{ + self.solid = SOLID_TRIGGER; + self.touch = t_cameratarget; + if (!self.duration) + self.duration = 10; + setsize (self, '-8 -8 -8', '8 8 8'); +}; +*/ +float SVC_UPDATEENTITY = 128; // Net.Protocol 0x80 +void(entity me, entity camera) UpdateCamera = +{ + msg_entity = me; // target of message + WriteByte (MSG_ONE, SVC_UPDATEENTITY|15); // 0x80|1|2|4|8 + WriteByte (MSG_ONE, 64); // 0x40 + WriteEntity(MSG_ONE,camera); + WriteCoord(MSG_ONE,camera.origin_x); + WriteCoord(MSG_ONE,camera.origin_y); + WriteCoord(MSG_ONE,camera.origin_z); +}; + + +//void() effect_finale_think = +// { +// self.flags = self.flags - (self.flags & FL_ONGROUND); +// self.angles = self.mangle; +// self.v_angle = self.mangle; +// self.fixangle = TRUE; +// NezuUpdateCamera(self,self.trigger_field); +// NezuSetViewPoint(self,self.trigger_field); +// NezuSetViewAngle(self,self.mangle); +// self.think = effect_finale_think; +// self.nextthink = time + 0.05; +// }; + + +void() effect_finale_use = +{ + local entity pos, pl, targ; + local entity temp; + + if (self.state == 1) + return; + +// intermission_exittime = time + 10000000; // never allow exit +// intermission_running = 1; + self.state = 1; + + // find the intermission spot + pos = find (world, targetname, self.target); + if (!pos) + error ("no target in finale"); + +// WriteByte (MSG_ALL, SVC_FINALE); + WriteByte (MSG_ALL, SVC_CUTSCENE); + WriteString (MSG_ALL, ""); + + //setup decoy + if (!self.spawnflags & 2) + { + if (self.spawnflags & 1) + { + pl = find (world, classname, "player"); + targ = find (world, targetname, self.mdl); + become_decoy( targ.target, pl.origin ); + } + else + { + targ = find (world, targetname, self.mdl); + become_decoy( targ.target, targ.origin ); + } + } + + pl = find (world, classname, "player"); + while (pl != world) + { + pl.view_ofs = '0 0 0'; + pl.angles = other.v_angle = pos.mangle; + pl.mangle = pos.mangle; + pl.fixangle = TRUE; // turn this way immediately + pl.trigger_field = self; + pl.nextthink = time + 0.5; + pl.takedamage = DAMAGE_NO; + pl.solid = SOLID_NOT; + pl.movetype = MOVETYPE_NONE; + pl.modelindex = 0; + setorigin (pl, pos.origin); + pl.origin = pos.origin; + pl.oldorigin = pos.origin; + UpdateCamera(pl,pl); + UpdateCamera(pl,pl.trigger_field); + pl = find (pl, classname, "player"); + } + + // wait for next sequence + if (self.spawnfunction) + { + self.nextthink = time + self.wait; + self.think = self.spawnfunction; + } +// intermission_running = 0; +}; +/* + "count" 0 - static camera + 1 - path camera + 2 - chase camera + "distance" distance from chase guy + (default 100) + "height" height distance from chase guy + (default 64) +*/ + +/*QUAKED effect_finale (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) useplayer nodecoy + start the finale sequence + "target" the camera to go to. + "mdl" if useplayer is specified, this is a + path corner with target of the next + path_corner to run to. + if use player isn't specified this becomes + the spawn point as well. + "spawnfunction" next routine to run + "delay" time to wait until running routine + useplayer - use the current player as + decoy location. + nodecoy - no decoy, only the camera +*/ + +void() effect_finale = + { + if (deathmatch) + { + remove(self); + return; + } + setorigin(self,self.origin); + self.angles = self.mangle; + self.use = effect_finale_use; +// setsize(self,'-16 -16 -16','16 16 16'); +// self.touch = effect_finale_use; +// InitTrigger (); + self.state = 0; + }; + +void() info_startendtext_use = + { + intermission_running = 1; + ExitIntermission(); + }; + +/*QUAKED info_startendtext (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) + start the end text +*/ + +void() info_startendtext = + { + self.use = info_startendtext_use; + }; diff --git a/quakec_hipnotic/hipmodel.qc b/quakec_hipnotic/hipmodel.qc new file mode 100644 index 0000000..da43b82 --- /dev/null +++ b/quakec_hipnotic/hipmodel.qc @@ -0,0 +1,219 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* +=============================================================================== + +HIP WEAPONS + +=============================================================================== +*/ + +$modelname g_hammer +$cd hipwork/models/g_hammer +$origin 0 0 -24 +$scale 1 +$flags 8 // client side rotate +$base mjolbase.asc +$skin skin +$frame mjol.asc + + +$modelname g_laserg +$cd hipwork/models/g_laserg +$scale 1 +$flags 8 // client side rotate +$origin 0 0 -24 +$base base.asc +$skin skin +$frame frame1.asc + +$modelname g_prox +$cd hipwork/models/g_prox +$scale 1 +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame frame1 + + +/* +=============================================================================== + +VIEW WEAPONS + +=============================================================================== +*/ + +$modelname v_hammer +$cd hipwork/models/v_hammer +$origin 0 5 54 +$base v_base.asc +$skin mjol +$frame strike1.asc strike2.asc strike3.asc strike4.asc strike5.asc + + +$modelname v_laserg +$cd hipwork/models/v_laserg +$scale 1.5 +$origin 0 -19.5 49 +$base firebase.asc +$skin skin +$frame fire1.asc fire2.asc fire3.asc fire4.asc fire5.asc fire6.asc fire7.asc + +$modelname v_prox +$cd hipwork/models/v_prox +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot6 + +/* +=============================================================================== + +ITEMS + +=============================================================================== +*/ + + +$modelname empathy +$cd hipwork/models/empathy +$flags 8 // client side rotate +$origin 0 0 -40 +$scale 2.5 +$base base.asc +$skin skin +$frame frame1.asc + +$modelname wetsuit +$cd hipwork/models/wetsuit +$flags 8 // client side rotate +$base suitbase.asc +$skin skin +$frame suit.asc + +$modelname horn +$cd hipwork/models/horn +$origin 0 0 16 +$scale 2.75 +$flags 8 // client side rotate +$base base.asc +$skin skin +$frame frame1.asc + +$modelname spikmine +$cd hipwork/models/spike +$origin 0 0 0 +$base spikbase.asc +$skin spike +$frame spike1.asc spike2.asc spike3.asc spike4.asc spike5.asc spike6.asc +$frame spike7.asc spike8.asc spike9.asc + +$modelname proxbomb +$cd hipwork/models/proxbomb +$origin 0 0 0 +$scale 1.5 +$base base.asc +$skin skin2 +$frame frame1.asc + +/* +=============================================================================== + +GIBS + +=============================================================================== +*/ +$modelname rubble2 +$cd hipwork/models/rubble2 +$flags 2 // EF_GRENADE +$origin 0 0 0 +$base base +$skin skin +$skin skin2 +$skin skin3 +$frame frame1 + + +$modelname rubble1 +$cd hipwork/models/rubble1 +$flags 2 // EF_GRENADE +$origin 0 0 0 +$base base +//$skin skin +//$skin skin2 +$skin skin3 +$frame frame1 + + +$modelname rubble3 +$cd hipwork/models/rubble3 +$flags 2 // EF_GRENADE +$origin 0 0 0 +$base base +$skin skin +$skin skin2 +$skin skin3 +$frame frame1 + +$modelname h_scourg +$cd hipwork/models/h_scourg +$flags 4 // EF_GIB +$scale 2.0 +$origin 0 0 0 +$base h_base.asc +$skin skin +$frame head.asc + +$modelname h_grem +$cd hipwork/models/h_grem +$flags 4 // EF_GIB +$scale 3.0 +$origin 0 0 0 +$base h_base.asc +$skin skin +$frame head.asc + +/* +=============================================================================== + +MISC + +=============================================================================== +*/ + +$modelname lasrspik +$cd hipwork/models/lasrspik +$origin 0 0 0 +$scale 2.3 +$base base +$skin skin +$frame frame1 + +$modelname lavarock +$cd hipwork/models/lava2 +$flags 1 // EF_ROCKET +$origin 0 0 8 +//$scale 0.7 +$base base +$skin skin +$frame frame1 diff --git a/quakec_hipnotic/hipquake.qc b/quakec_hipnotic/hipquake.qc new file mode 100644 index 0000000..0a37585 --- /dev/null +++ b/quakec_hipnotic/hipquake.qc @@ -0,0 +1,100 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Earthquake QuickC program + By Jim Dose' 9/13/96 +*/ +//JIM +float earthquake; +float quakeactive; + +void() StopEarthQuake = + { + earthquake = 0; + }; + +void( float value ) EarthQuakeTime = + { + value = value + time; + if ( value > earthquake ) + { + earthquake = value; + } + }; + +void() earthquake_prethink = + { +// if ( lastearthquake ) +// { +// self.view_ofs_z = self.savedz; +// lastearthquake = 0; +// } + }; + +void() earthquake_postthink = + { + if ( earthquake > time ) + { + if (quakeactive == 0) + { + sound (self, CHAN_VOICE, "misc/quake.wav", 1, ATTN_NONE); + quakeactive = 1; + } +// lastearthquake = 1; +// self.savedz = self.view_ofs_z; + if ( self.flags & FL_ONGROUND ) + { +// self.view_ofs_z = self.view_ofs_z - 5 + random() * 10; + self.velocity = self.velocity + (random() * '0 0 150'); + } + } + else + { + if (quakeactive == 1) + { + sound (self, CHAN_VOICE, "misc/quakeend.wav", 1, ATTN_NONE); + quakeactive = 0; + } + } + }; + +void() earthquake_use = + { + EarthQuakeTime( self.dmg ); + }; + +/*QUAKED func_earthquake (0 0 0.5) (0 0 0) (32 32 32) +Causes an earthquake. Triggers targets. + +"dmg" is the duration of the earthquake. Default is 0.8 seconds. +*/ + +void() func_earthquake = + { + quakeactive = 0; + precache_sound("misc/quake.wav"); + precache_sound("misc/quakeend.wav"); + self.classname = "earthquake"; + self.use = earthquake_use; + self.think = SUB_Null; + if ( !self.dmg ) + { + self.dmg = 0.8; + } + }; diff --git a/quakec_hipnotic/hiprot.qc b/quakec_hipnotic/hiprot.qc new file mode 100644 index 0000000..9d5eb39 --- /dev/null +++ b/quakec_hipnotic/hiprot.qc @@ -0,0 +1,1139 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Rotate QuickC program + By Jim Dose' 10/17/96 +*/ + +float STATE_ACTIVE = 0; +float STATE_INACTIVE = 1; +float STATE_SPEEDINGUP = 2; +float STATE_SLOWINGDOWN = 3; + +float STATE_CLOSED = 4; +float STATE_OPEN = 5; +float STATE_OPENING = 6; +float STATE_CLOSING = 7; + +float STATE_WAIT = 0; +float STATE_MOVE = 1; +float STATE_STOP = 2; +float STATE_FIND = 3; +float STATE_NEXT = 4; + +float OBJECT_ROTATE = 0; +float OBJECT_MOVEWALL = 1; +float OBJECT_SETORIGIN = 2; + +float TOGGLE = 1; +float START_ON = 2; + +float ROTATION = 1; +float ANGLES = 2; +float STOP = 4; +float NO_ROTATE = 8; +float DAMAGE = 16; +float MOVETIME = 32; +float SET_DAMAGE = 64; + +float VISIBLE = 1; +float TOUCH = 2; +float NONBLOCKING = 4; + +float STAYOPEN = 1; + +/*QUAKED info_rotate (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as the point of rotation for rotatable objects. +*/ +void() info_rotate = +{ +// remove self after a little while, to make sure that entities that +// have targeted it have had a chance to spawn + self.nextthink = time + 2; + self.think = SUB_Remove; +}; + +void() RotateTargets = + { + local entity ent; + local vector vx; + local vector vy; + local vector vz; + local vector org; + + makevectors (self.angles); + + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.rotate_type == OBJECT_SETORIGIN ) + { + org = ent.oldorigin; + vx = ( v_forward * org_x ); + vy = ( v_right * org_y ); + vy = vy * -1; + vz = ( v_up * org_z ); + ent.neworigin = vx + vy + vz; + setorigin( ent, ent.neworigin + self.origin ); + } + else if ( ent.rotate_type == OBJECT_ROTATE ) + { + ent.angles = self.angles; + org = ent.oldorigin; + vx = ( v_forward * org_x ); + vy = ( v_right * org_y ); + vy = vy * -1; + vz = ( v_up * org_z ); + ent.neworigin = vx + vy + vz; + setorigin( ent, ent.neworigin + self.origin ); + } + else + { + org = ent.oldorigin; + vx = ( v_forward * org_x ); + vy = ( v_right * org_y ); + vy = vy * -1; + vz = ( v_up * org_z ); + ent.neworigin = vx + vy + vz; + ent.neworigin = self.origin - self.oldorigin + (ent.neworigin - ent.oldorigin); + ent.velocity = (ent.neworigin-ent.origin)*25; + } + ent = find( ent, targetname, self.target); + } + }; + +void() RotateTargetsFinal = + { + local entity ent; + + ent = find( world, targetname, self.target); + while( ent ) + { + ent.velocity = '0 0 0'; + if ( ent.rotate_type == OBJECT_ROTATE ) + { + ent.angles = self.angles; + } + ent = find( ent, targetname, self.target); + } + }; + +void() SetTargetOrigin = + { + local entity ent; + + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.rotate_type == OBJECT_MOVEWALL ) + { + setorigin( ent, self.origin - self.oldorigin + + (ent.neworigin - ent.oldorigin) ); + } + else + { + setorigin( ent, ent.neworigin + self.origin ); + } + ent = find( ent, targetname, self.target); + } + }; + +void() LinkRotateTargets = + { + local entity ent; + local vector tempvec; + + self.oldorigin = self.origin; + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.classname == "rotate_object" ) + { + ent.rotate_type = OBJECT_ROTATE; + ent.oldorigin = ent.origin - self.oldorigin; + ent.neworigin = ent.origin - self.oldorigin; + ent.owner = self; + } + else if ( ent.classname == "func_movewall" ) + { + ent.rotate_type = OBJECT_MOVEWALL; + tempvec = ( ent.absmin + ent.absmax ) * 0.5; + ent.oldorigin = tempvec - self.oldorigin; + ent.neworigin = ent.oldorigin; + ent.owner = self; + } + else + { + ent.rotate_type = OBJECT_SETORIGIN; + ent.oldorigin = ent.origin - self.oldorigin; + ent.neworigin = ent.origin - self.oldorigin; + } + ent = find (ent, targetname, self.target); + } + }; + +void( float amount ) SetDamageOnTargets = + { + local entity ent; + + ent = find( world, targetname, self.target); + while( ent ) + { + if ( ent.classname == "trigger_hurt" ) + { + hurt_setdamage( ent, amount ); + } + else if ( ent.classname == "func_movewall" ) + { + ent.dmg = amount; + } + ent = find( ent, targetname, self.target); + } + }; + + +//************************************************ +// +// Simple continual rotatation +// +//************************************************ + +void() rotate_entity_think = + { + local float t; + + t = time - self.ltime; + self.ltime = time; + + if ( self.state == STATE_SPEEDINGUP ) + { + self.count = self.count + self.cnt * t; + if ( self.count > 1 ) + { + self.count = 1; + } + + // get rate of rotation + t = t * self.count; + } + else if ( self.state == STATE_SLOWINGDOWN ) + { + self.count = self.count - self.cnt * t; + if ( self.count < 0 ) + { + RotateTargetsFinal(); + self.state = STATE_INACTIVE; + self.think = SUB_Null; + return; + } + + // get rate of rotation + t = t * self.count; + } + + self.angles = self.angles + ( self.rotate * t ); + self.angles = SUB_NormalizeAngles( self.angles ); + RotateTargets(); + self.nextthink = time + 0.02; + }; + +void() rotate_entity_use = + { + // change to alternate textures + self.frame = 1 - self.frame; + + if ( self.state == STATE_ACTIVE ) + { + if ( self.spawnflags & TOGGLE ) + { + if ( self.speed ) + { + self.count = 1; + self.state = STATE_SLOWINGDOWN; + } + else + { + self.state = STATE_INACTIVE; + self.think = SUB_Null; + } + } + } + else if ( self.state == STATE_INACTIVE ) + { + self.think = rotate_entity_think; + self.nextthink = time + 0.02; + self.ltime = time; + if ( self.speed ) + { + self.count = 0; + self.state = STATE_SPEEDINGUP; + } + else + { + self.state = STATE_ACTIVE; + } + } + else if ( self.state == STATE_SPEEDINGUP ) + { + if ( self.spawnflags & TOGGLE ) + { + self.state = STATE_SLOWINGDOWN; + } + } + else + { + self.state = STATE_SPEEDINGUP; + } + }; + +void() rotate_entity_firstthink = + { + LinkRotateTargets(); + if ( self.spawnflags & START_ON ) + { + self.state = STATE_ACTIVE; + self.think = rotate_entity_think; + self.nextthink = time + 0.02; + self.ltime = time; + } + else + { + self.state = STATE_INACTIVE; + self.think = SUB_Null; + } + self.use = rotate_entity_use; + }; + +/*QUAKED func_rotate_entity (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE START_ON +Creates an entity that continually rotates. Can be toggled on and +off if targeted. + +TOGGLE = allows the rotation to be toggled on/off + +START_ON = wether the entity is spinning when spawned. If TOGGLE is 0, entity can be turned on, but not off. + +If "deathtype" is set with a string, this is the message that will appear when a player is killed by the train. + +"rotate" is the rate to rotate. +"target" is the center of rotation. +"speed" is how long the entity takes to go from standing still to full speed and vice-versa. +*/ + +void() func_rotate_entity = + { + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + + setmodel (self, self.model); + setsize( self, self.mins, self.maxs ); + + if ( self.speed != 0 ) + { + self.cnt = 1 / self.speed; + } + + self.think = rotate_entity_firstthink; + self.nextthink = time + 0.1; + self.ltime = time; + }; + +//************************************************ +// +// Train with rotation functionality +// +//************************************************ + +/*QUAKED path_rotate (0.5 0.3 0) (-8 -8 -8) (8 8 8) ROTATION ANGLES STOP NO_ROTATE DAMAGE MOVETIME SET_DAMAGE + Path for rotate_train. + + ROTATION tells train to rotate at rate specified by "rotate". Use '0 0 0' to stop rotation. + + ANGLES tells train to rotate to the angles specified by "angles" while traveling to this path_rotate. Use values < 0 or > 360 to guarantee that it turns in a certain direction. Having this flag set automatically clears any rotation. + + STOP tells the train to stop and wait to be retriggered. + + NO_ROTATE tells the train to stop rotating when waiting to be triggered. + + DAMAGE tells the train to cause damage based on "dmg". + + MOVETIME tells the train to interpret "speed" as the length of time to take moving from one corner to another. + + SET_DAMAGE tells the train to set all targets damage to "dmg" + + "noise" contains the name of the sound to play when train stops. + "noise1" contains the name of the sound to play when train moves. + "event" is a target to trigger when train arrives at path_rotate. +*/ +void() path_rotate = + { + if ( self.noise ) + { + precache_sound( self.noise ); + } + if ( self.noise1 ) + { + precache_sound( self.noise1 ); + } + }; + + +void() rotate_train; +void() rotate_train_next; +void() rotate_train_find; + +void() rotate_train_think = + { + local float t; + local float timeelapsed; + + t = time - self.ltime; + self.ltime = time; + + if ( ( self.endtime ) && ( time >= self.endtime ) ) + { + self.endtime = 0; + if ( self.state == STATE_MOVE ) + { + setorigin(self, self.finaldest); + self.velocity = '0 0 0'; + } + + if (self.think1) + self.think1(); + } + else + { + timeelapsed = (time - self.cnt) * self.duration; + if ( timeelapsed > 1 ) + timeelapsed = 1; + setorigin( self, self.dest1 + ( self.dest2 * timeelapsed ) ); + } + + self.angles = self.angles + ( self.rotate * t ); + self.angles = SUB_NormalizeAngles( self.angles ); + RotateTargets(); + + self.nextthink = time + 0.02; + }; + +void() rotate_train_use = + { + if (self.think1 != rotate_train_find) + { + if ( self.velocity != '0 0 0' ) + return; // already activated + if ( self.think1 ) + { + self.think1(); + } + } + }; + +void() rotate_train_wait = + { + self.state = STATE_WAIT; + + if ( self.goalentity.noise ) + { + sound (self, CHAN_VOICE, self.goalentity.noise, 1, ATTN_NORM); + } + else + { + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + if ( self.goalentity.spawnflags & ANGLES ) + { + self.rotate = '0 0 0'; + self.angles = self.finalangle; + } + if ( self.goalentity.spawnflags & NO_ROTATE ) + { + self.rotate = '0 0 0'; + } + self.endtime = self.ltime + self.goalentity.wait; + self.think1 = rotate_train_next; + }; + +void() rotate_train_stop = + { + self.state = STATE_STOP; + + if ( self.goalentity.noise ) + { + sound (self, CHAN_VOICE, self.goalentity.noise, 1, ATTN_NORM); + } + else + { + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + if ( self.goalentity.spawnflags & ANGLES ) + { + self.rotate = '0 0 0'; + self.angles = self.finalangle; + } + if ( self.goalentity.spawnflags & NO_ROTATE ) + { + self.rotate = '0 0 0'; + } + + self.dmg = 0; + self.think1 = rotate_train_next; + }; + +void() rotate_train_next = +{ + local entity targ; + local entity current; + local vector vdestdelta; + local float len, traveltime, div; + local string temp; + + self.state = STATE_NEXT; + + current = self.goalentity; + targ = find (world, targetname, self.path ); + if ( targ.classname != "path_rotate" ) + objerror( "Next target is not path_rotate" ); + + if ( self.goalentity.noise1 ) + { + self.noise1 = self.goalentity.noise1; + } + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + + self.goalentity = targ; + self.path = targ.target; + if (!self.path ) + objerror ("rotate_train_next: no next target"); + + if ( targ.spawnflags & STOP ) + { + self.think1 = rotate_train_stop; + } + else if (targ.wait) + { + self.think1 = rotate_train_wait; + } + else + { + self.think1 = rotate_train_next; + } + + if ( current.event ) + { + // Trigger any events that should happen at the corner. + temp = self.target; + self.target = current.event; + self.message = current.message; + SUB_UseTargets(); + self.target = temp; + self.message = string_null; + } + + if ( current.spawnflags & ANGLES ) + { + self.rotate = '0 0 0'; + self.angles = self.finalangle; + } + + if ( current.spawnflags & ROTATION ) + { + self.rotate = current.rotate; + } + + if ( current.spawnflags & DAMAGE ) + { + self.dmg = current.dmg; + } + + if ( current.spawnflags & SET_DAMAGE ) + { + SetDamageOnTargets( current.dmg ); + } + + if ( current.speed == -1 ) + { + // Warp to the next path_corner + setorigin( self, targ.origin ); + self.endtime = self.ltime + 0.01; + SetTargetOrigin(); + + if ( targ.spawnflags & ANGLES ) + { + self.angles = targ.angles; + } + + self.duration = 1; // 1 / duration + self.cnt = time; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + self.finaldest = self.origin; + } + else + { + self.state = STATE_MOVE; + + self.finaldest = targ.origin; + if (self.finaldest == self.origin) + { + self.velocity = '0 0 0'; + self.endtime = self.ltime + 0.1; + + self.duration = 1; // 1 / duration + self.cnt = time; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + self.finaldest = self.origin; + return; + } + // set destdelta to the vector needed to move + vdestdelta = self.finaldest - self.origin; + + // calculate length of vector + len = vlen (vdestdelta); + + if ( current.spawnflags & MOVETIME ) + { + traveltime = current.speed; + } + else + { + // check if there's a speed change + if (current.speed>0) + self.speed = current.speed; + + if (!self.speed) + objerror("No speed is defined!"); + + // divide by speed to get time to reach dest + traveltime = len / self.speed; + } + + if (traveltime < 0.1) + { + self.velocity = '0 0 0'; + self.endtime = self.ltime + 0.1; + if ( targ.spawnflags & ANGLES ) + { + self.angles = targ.angles; + } + return; + } + + // qcc won't take vec/float + div = 1 / traveltime; + + if ( targ.spawnflags & ANGLES ) + { + self.finalangle = SUB_NormalizeAngles( targ.angles ); + self.rotate = ( targ.angles - self.angles ) * div; + } + + // set endtime to trigger a think when dest is reached + self.endtime = self.ltime + traveltime; + + // scale the destdelta vector by the time spent traveling to get velocity + self.velocity = vdestdelta * div; + + self.duration = div; // 1 / duration + self.cnt = time; // start time + self.dest2 = vdestdelta; // delta + self.dest1 = self.origin; // original position + } + }; + +void() rotate_train_find = + { + local entity targ; + + self.state = STATE_FIND; + + LinkRotateTargets(); + + // the first target is the point of rotation. + // the second target is the path. + targ = find ( world, targetname, self.path); + if ( targ.classname != "path_rotate" ) + objerror( "Next target is not path_rotate" ); + + // Save the current entity + self.goalentity = targ; + + if ( targ.spawnflags & ANGLES ) + { + self.angles = targ.angles; + self.finalangle = SUB_NormalizeAngles( targ.angles ); + } + + self.path = targ.target; + setorigin (self, targ.origin ); + SetTargetOrigin(); + RotateTargetsFinal(); + self.think1 = rotate_train_next; + if (!self.targetname) + { + // not triggered, so start immediately + self.endtime = self.ltime + 0.1; + } + else + { + self.endtime = 0; + } + + self.duration = 1; // 1 / duration + self.cnt = time; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + }; + +/*QUAKED func_rotate_train (0 .5 .8) (-8 -8 -8) (8 8 8) +In path_rotate, set speed to be the new speed of the train after it reaches +the path change. If speed is -1, the train will warp directly to the next +path change after the specified wait time. If MOVETIME is set on the +path_rotate, the train to interprets "speed" as the length of time to +take moving from one corner to another. + +"noise" contains the name of the sound to play when train stops. +"noise1" contains the name of the sound to play when train moves. +Both "noise" and "noise1" defaults depend upon "sounds" variable and +can be overridden by the "noise" and "noise1" variable in path_rotate. + +Also in path_rotate, if STOP is set, the train will wait until it is +retriggered before moving on to the next goal. + +Trains are moving platforms that players can ride. +"path" specifies the first path_rotate and is the starting position. +If the train is the target of a button or trigger, it will not begin moving until activated. +The func_rotate_train entity is the center of rotation of all objects targeted by it. + +If "deathtype" is set with a string, this is the message that will appear when a player is killed by the train. + +speed default 100 +dmg default 0 +sounds +1) ratchet metal +*/ + +void() rotate_train = + { + objerror ("rotate_train entities should be changed to rotate_object with\nfunc_rotate_train controllers\n"); + }; + +void() func_rotate_train = + { + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("rotate_train without a target"); + + if ( !self.noise ) + { + if (self.sounds == 0) + { + self.noise = ("misc/null.wav"); + } + + if (self.sounds == 1) + { + self.noise = ("plats/train2.wav"); + } + } + if ( !self.noise1 ) + { + if (self.sounds == 0) + { + self.noise1 = ("misc/null.wav"); + } + if (self.sounds == 1) + { + self.noise1 = ("plats/train1.wav"); + } + } + + precache_sound( self.noise ); + precache_sound( self.noise1 ); + + self.cnt = 1; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_STEP; + self.use = rotate_train_use; + + setmodel (self, self.model); + setsize (self, self.mins, self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.ltime = time; + self.nextthink = self.ltime + 0.1; + self.endtime = self.ltime + 0.1; + self.think = rotate_train_think; + self.think1 = rotate_train_find; + self.state = STATE_FIND; + + self.duration = 1; // 1 / duration + self.cnt = 0.1; // start time + self.dest2 = '0 0 0'; // delta + self.dest1 = self.origin; // original position + + + self.flags = self.flags | FL_ONGROUND; + }; + +//************************************************ +// +// Moving clip walls +// +//************************************************ + +void() rotate_door_reversedirection; +void() rotate_door_group_reversedirection; + +void() movewall_touch = + { + if (time < self.owner.attack_finished) + return; + + if ( self.dmg ) + { + T_Damage (other, self, self.owner, self.dmg); + self.owner.attack_finished = time + 0.5; + } + else if ( self.owner.dmg ) + { + T_Damage (other, self, self.owner, self.owner.dmg); + self.owner.attack_finished = time + 0.5; + } + }; + +void() movewall_blocked = + { + local entity temp; + + if (time < self.owner.attack_finished) + return; + + self.owner.attack_finished = time + 0.5; + + if ( self.owner.classname == "func_rotate_door" ) + { + temp = self; + self = self.owner; + rotate_door_group_reversedirection(); + self = temp; + } + + if ( self.dmg ) + { + T_Damage (other, self, self.owner, self.dmg); + self.owner.attack_finished = time + 0.5; + } + else if ( self.owner.dmg ) + { + T_Damage (other, self, self.owner, self.owner.dmg); + self.owner.attack_finished = time + 0.5; + } + }; + +void() movewall_think = + { + self.ltime = time; + self.nextthink = time + 0.02; + }; + +/*QUAKED func_movewall (0 .5 .8) ? VISIBLE TOUCH NONBLOCKING +Used to emulate collision on rotating objects. + +VISIBLE causes brush to be displayed. + +TOUCH specifies whether to cause damage when touched by player. + +NONBLOCKING makes the brush non-solid. This is useless if VISIBLE is set. + +"dmg" specifies the damage to cause when touched or blocked. +*/ +void() func_movewall = + { + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; + if ( self.spawnflags & NONBLOCKING ) + { + self.solid = SOLID_NOT; + } + else + { + self.solid = SOLID_BSP; + self.blocked = movewall_blocked; + } + if ( self.spawnflags & TOUCH ) + { + self.touch = movewall_touch; + } + setmodel (self,self.model); + if ( !( self.spawnflags & VISIBLE ) ) + { + self.model = string_null; + } + self.think = movewall_think; + self.nextthink = time + 0.02; + self.ltime = time; + }; + +/*QUAKED rotate_object (0 .5 .8) ? +This defines an object to be rotated. Used as the target of func_rotate_door. +*/ +void() rotate_object = + { + self.classname = "rotate_object"; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + setmodel (self,self.model); + setsize( self, self.mins, self.maxs ); + self.think = SUB_Null; + }; + +//************************************************ +// +// Rotating doors +// +//************************************************ + +void() rotate_door_think2 = + { + local float t; + + t = time - self.ltime; + self.ltime = time; + + // change to alternate textures + self.frame = 1 - self.frame; + + self.angles = self.dest; + + if ( self.state == STATE_OPENING ) + { + self.state = STATE_OPEN; + } + else + { + if ( self.spawnflags & STAYOPEN ) + { + rotate_door_group_reversedirection(); + return; + } + self.state = STATE_CLOSED; + } + + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + self.think = SUB_Null; + + RotateTargetsFinal(); + }; + +void() rotate_door_think = + { + local float t; + + t = time - self.ltime; + self.ltime = time; + + if ( time < self.endtime ) + { + self.angles = self.angles + ( self.rotate * t ); + RotateTargets(); + } + else + { + self.angles = self.dest; + RotateTargets(); + self.think = rotate_door_think2; + } + + self.nextthink = time + 0.01; + }; + +void() rotate_door_reversedirection = + { + local vector start; + + // change to alternate textures + self.frame = 1 - self.frame; + + if ( self.state == STATE_CLOSING ) + { + start = self.dest1; + self.dest = self.dest2; + self.state = STATE_OPENING; + } + else + { + start = self.dest2; + self.dest = self.dest1; + self.state = STATE_CLOSING; + } + + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + + self.rotate = ( self.dest - start ) * ( 1 / self.speed ); + self.think = rotate_door_think; + self.nextthink = time + 0.02; + self.endtime = time + self.speed - ( self.endtime - time ); + self.ltime = time; + }; + +void() rotate_door_group_reversedirection = + { + local string name; + + // tell all associated rotaters to reverse direction + if ( self.group ) + { + name = self.group; + self = find( world, group, name); + while( self ) + { + rotate_door_reversedirection(); + self = find( self, group, name); + } + } + else + { + rotate_door_reversedirection(); + } + }; + +void() rotate_door_use = + { + local entity t; + local vector start; + + if ( ( self.state != STATE_OPEN ) && ( self.state != STATE_CLOSED ) ) + return; + + if ( !self.cnt ) + { + self.cnt = 1; + LinkRotateTargets(); + } + + // change to alternate textures + self.frame = 1 - self.frame; + + if ( self.state == STATE_CLOSED ) + { + start = self.dest1; + self.dest = self.dest2; + self.state = STATE_OPENING; + } + else + { + start = self.dest2; + self.dest = self.dest1; + self.state = STATE_CLOSING; + } + + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + + self.rotate = ( self.dest - start ) * ( 1 / self.speed ); + self.think = rotate_door_think; + self.nextthink = time + 0.01; + self.endtime = time + self.speed; + self.ltime = time; + }; + + +/*QUAKED func_rotate_door (0 .5 .8) (-8 -8 -8) (8 8 8) STAYOPEN +Creates a door that rotates between two positions around a point of +rotation each time it's triggered. + +STAYOPEN tells the door to reopen after closing. This prevents a trigger- +once door from closing again when it's blocked. + +"dmg" specifies the damage to cause when blocked. Defaults to 2. Negative numbers indicate no damage. +"speed" specifies how the time it takes to rotate + +"sounds" +1) medieval (default) +2) metal +3) base +*/ + +void() func_rotate_door = + { + if (!self.target) + { + objerror( "rotate_door without target." ); + } + + self.dest1 = '0 0 0'; + self.dest2 = self.angles; + self.angles = self.dest1; + + // default to 2 seconds + if ( !self.speed ) + { + self.speed = 2; + } + + self.cnt = 0; + + if (!self.dmg) + self.dmg = 2; + else if ( self.dmg < 0 ) + { + self.dmg = 0; + } + + if (self.sounds == 0) + self.sounds = 1; + if (self.sounds == 1) + { + precache_sound ("doors/latch2.wav"); + precache_sound ("doors/winch2.wav"); + precache_sound ("doors/drclos4.wav"); + self.noise1 = "doors/latch2.wav"; + self.noise2 = "doors/winch2.wav"; + self.noise3 = "doors/drclos4.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/airdoor1.wav"); + precache_sound ("doors/airdoor2.wav"); + self.noise2 = "doors/airdoor1.wav"; + self.noise1 = "doors/airdoor2.wav"; + self.noise3 = "doors/airdoor2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/basesec1.wav"); + precache_sound ("doors/basesec2.wav"); + self.noise2 = "doors/basesec1.wav"; + self.noise1 = "doors/basesec2.wav"; + self.noise3 = "doors/basesec2.wav"; + } + + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + setmodel (self, self.model); + setorigin( self, self.origin ); + setsize( self, self.mins, self.maxs ); + self.state = STATE_CLOSED; + self.use = rotate_door_use; + self.think = SUB_Null; + }; diff --git a/quakec_hipnotic/hiprubbl.qc b/quakec_hipnotic/hiprubbl.qc new file mode 100644 index 0000000..8ec40a2 --- /dev/null +++ b/quakec_hipnotic/hiprubbl.qc @@ -0,0 +1,151 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Rubble QuickC program + By Jim Dose' 9/15/96 +*/ + +void() hipRubbleTouch = + { + if ( self.ltime < self.pausetime ) + return; + + if (other.takedamage) + { + T_Damage (other, self, self.owner, 10 ); + sound (self, CHAN_WEAPON, "zombie/z_hit.wav", 1, ATTN_NORM); + self.pausetime = self.ltime + 0.1; + } + }; + +void(string rubblename) hipThrowRubble = +{ + local entity new; + + new = spawn(); + new.origin = self.origin; + setmodel (new, rubblename ); + setsize (new, '0 0 0', '0 0 0'); + new.velocity_x = 70 * crandom(); + new.velocity_y = 70 * crandom(); + new.velocity_z = 140 + 70 * random(); + new.movetype = MOVETYPE_BOUNCE; + new.solid = SOLID_BBOX; + new.avelocity_x = random()*600; + new.avelocity_y = random()*600; + new.avelocity_z = random()*600; + new.think = SUB_Remove; + new.touch = hipRubbleTouch; + new.ltime = time; + new.nextthink = time + 13 + random()*10; + self.pausetime = time; + new.frame = 0; + new.flags = 0; +}; + +void() rubble_use = + { + local float which; + local float index; + + index = 0; + + do + { + which = self.cnt; + if ( self.cnt == 0 ) + { + which = 1 + 3*random(); + which = floor( which ); + } + if ( which == 1 ) + { + hipThrowRubble( "progs/rubble1.mdl" ); + } + else if ( which == 2 ) + { + hipThrowRubble( "progs/rubble3.mdl" ); + } + else + { + hipThrowRubble( "progs/rubble2.mdl" ); + } + index = index + 1; + } + while( index < self.count ); + }; + +/*QUAKED func_rubble (0.4 0.4 0.2) (0 0 0) (32 32 32) + Spawns random sized rubble when triggered. + + "count" is the number of pieces of rubble to spawn. Default is 1. +*/ +void() func_rubble = + { + precache_model ("progs/rubble1.mdl"); + precache_model ("progs/rubble2.mdl"); + precache_model ("progs/rubble3.mdl"); + precache_sound ("zombie/z_hit.wav"); + self.classname = "rubble"; + self.cnt = 0; + self.use = rubble_use; + }; + +/*QUAKED func_rubble1 (0.4 0.4 0.2) (0 0 0) (8 8 8) + Spawns small rubble when triggered. + + "count" is the number of pieces of rubble to spawn. Default is 1. +*/ +void() func_rubble1 = + { + precache_model ("progs/rubble1.mdl"); + precache_sound ("zombie/z_hit.wav"); + self.classname = "rubble1"; + self.cnt = 1; + self.use = rubble_use; + }; + +/*QUAKED func_rubble2 (0.4 0.4 0.2) (0 0 0) (16 16 16) + Spawns medium rubble when triggered. + + "count" is the number of pieces of rubble to spawn. Default is 1. +*/ +void() func_rubble2 = + { + precache_model ("progs/rubble3.mdl"); + precache_sound ("zombie/z_hit.wav"); + self.classname = "rubble2"; + self.cnt = 2; + self.use = rubble_use; + }; + +/*QUAKED func_rubble3 (0.4 0.4 0.2) (0 0 0) (32 32 32) + Spawns large rubble when triggered. + + "count" is the number of pieces of rubble to spawn. Default is 1. +*/ +void() func_rubble3 = + { + precache_model ("progs/rubble2.mdl"); + precache_sound ("zombie/z_hit.wav"); + self.classname = "rubble3"; + self.cnt = 3; + self.use = rubble_use; + }; + diff --git a/quakec_hipnotic/hipscrge.qc b/quakec_hipnotic/hipscrge.qc new file mode 100644 index 0000000..b54a013 --- /dev/null +++ b/quakec_hipnotic/hipscrge.qc @@ -0,0 +1,530 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SCOURGE + +============================================================================== +*/ + +$cd hipwork\models\scourge +$origin 0 0 23 +$base scorbase.asc + +$skin skin + +$frame stand1.asc stand2.asc stand3.asc stand4.asc stand5.asc stand6.asc +$frame stand7.asc stand8.asc stand9.asc stand10.asc stand11.asc stand12.asc + +$frame walk1.asc walk2.asc walk3.asc walk4.asc walk5.asc walk6.asc + +$frame shoot1.asc shoot2.asc + +$frame sting1.asc sting2.asc sting3.asc sting4.asc sting5.asc sting6.asc +$frame sting7.asc sting8.asc sting9.asc sting10.asc sting11.asc + +$frame pain1.asc pain2.asc pain3.asc pain4.asc pain5.asc + +$frame death1.asc death2.asc death3.asc death4.asc death5.asc +/* +=========== +ScourgeCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() ScourgeCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + local float enemy_yaw; + + targ = self.enemy; +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + if (vlen(spot1 - spot2) <= 100 ) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + if (time < self.attack_finished) + return FALSE; + + if (!enemy_vis) + return FALSE; + + + chance = spot2_z - spot1_z; + if (chance > 64) + return FALSE; + else if (chance < -200) + return FALSE; + + if (vlen(spot1 - spot2) > 1000) + return FALSE; + + if (vlen(spot1 - spot2) < 150) + return FALSE; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + +// missile attack +// if (enemy_range == RANGE_FAR) +// return FALSE; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (2 + 2*random()); + return TRUE; +}; + +void(float ox) SCOURGE_FireSpikes = +{ + local vector src, vec; + + ai_face(); + makevectors(self.angles); + src = self.origin - '0 0 19' + v_right*ox + v_forward*14; + vec = (self.enemy.origin + (200*v_forward)) - src; + vec = normalize(vec); + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + launch_spike (src, vec); + + self.attack_finished = time + 0.2; +}; + +// Attack_With_Tail +// +void() Attack_With_Tail = + { + local float ldmg; + local vector delta; + + ai_face (); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + ldmg = (random() + random() + random()) * 40; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_WEAPON, "shambler/smack.wav", 1, ATTN_NORM); + + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 50 * v_right); + }; + +void() ScourgeTriggerThink; +void() ScourgeTriggerTouch; +void() scourge_think = + { + local entity trig; + if (!self.state) + { + trig = spawn(); + trig.angles = '0 0 0'; + trig.solid = SOLID_TRIGGER; + setsize( trig, '-64 -64 -24', '64 64 64'); + trig.takedamage = DAMAGE_NO; + trig.movetype = MOVETYPE_NONE; + trig.modelindex = 0; + trig.model = ""; + trig.lastvictim = self; + trig.think = ScourgeTriggerThink; + trig.touch = ScourgeTriggerTouch; + trig.nextthink = time + 0.1 + random(); + setorigin(trig,self.origin); + self.lastvictim = trig; + self.state = 1; + } + if ((self.spawnsilent == 0) && (self.spawnmulti == 1)) + { + sound (self, CHAN_BODY, "misc/null.wav", 1, ATTN_IDLE); + } + else if ((self.spawnsilent == 1) && (self.spawnmulti == 0)) + { + sound (self, CHAN_BODY, "scourge/walk.wav", 1, ATTN_IDLE); + } + self.spawnmulti = self.spawnsilent; + }; + +void(float dist) ai_right = +{ + walkmove ( (self.angles_y+90), dist); +}; + +void(float dist) ai_left = +{ + walkmove ( (self.angles_y+270), dist); +}; +//============================================================================ + +//void() scourge_wake1; +void() scourge_stand1 =[ $stand1.asc, scourge_stand2 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand2 =[ $stand2.asc, scourge_stand3 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand3 =[ $stand3.asc, scourge_stand4 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand4 =[ $stand4.asc, scourge_stand5 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand5 =[ $stand5.asc, scourge_stand6 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand6 =[ $stand6.asc, scourge_stand7 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand7 =[ $stand7.asc, scourge_stand8 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand8 =[ $stand8.asc, scourge_stand9 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand9 =[ $stand9.asc, scourge_stand10 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand10 =[ $stand10.asc, scourge_stand11 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand11 =[ $stand11.asc, scourge_stand12 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; +void() scourge_stand12 =[ $stand12.asc, scourge_stand1 ] {self.spawnsilent = 0;ai_stand();scourge_think();}; + +void() scourge_walk1 =[ $walk1.asc , scourge_walk2 ] { +if (random() < 0.1) + sound (self, CHAN_VOICE, "scourge/idle.wav", 1, ATTN_IDLE); +self.spawnsilent = 1; +scourge_think(); +ai_walk(8);}; +void() scourge_walk2 =[ $walk2.asc , scourge_walk3 ] {ai_walk(8);}; +void() scourge_walk3 =[ $walk3.asc , scourge_walk4 ] {ai_walk(8);}; +void() scourge_walk4 =[ $walk4.asc , scourge_walk5 ] {ai_walk(8);}; +void() scourge_walk5 =[ $walk5.asc , scourge_walk6 ] {ai_walk(8);}; +void() scourge_walk6 =[ $walk6.asc , scourge_walk1 ] {ai_walk(8);}; + +void() scourge_run1 =[ $walk1.asc , scourge_run2 ] { +if (random() < 0.1) + sound (self, CHAN_VOICE, "scourge/idle.wav", 1, ATTN_IDLE); +self.spawnsilent = 1; +scourge_think(); +ai_run(18);}; +void() scourge_run2 =[ $walk2.asc , scourge_run3 ] {scourge_think();ai_run(14);}; +void() scourge_run3 =[ $walk3.asc , scourge_run4 ] {ai_run(14);}; +void() scourge_run4 =[ $walk4.asc , scourge_run5 ] {ai_run(14);}; +void() scourge_run5 =[ $walk5.asc , scourge_run6 ] {ai_run(14);}; +void() scourge_run6 =[ $walk6.asc , scourge_run1 ] {ai_run(14);}; + +void() scourge_strafeleft1 =[ $walk1.asc , scourge_strafeleft2 ] {self.spawnsilent = 1;scourge_think();ai_left(20);}; +void() scourge_strafeleft2 =[ $walk2.asc , scourge_strafeleft3 ] {ai_left(20);}; +void() scourge_strafeleft3 =[ $walk3.asc , scourge_strafeleft4 ] {ai_left(20);}; +void() scourge_strafeleft4 =[ $walk4.asc , scourge_strafeleft5 ] {ai_left(14);}; +void() scourge_strafeleft5 =[ $walk5.asc , scourge_strafeleft6 ] {ai_left(14);}; +void() scourge_strafeleft6 =[ $walk6.asc , scourge_run1 ] {ai_left(14);}; + +void() scourge_straferight1 =[ $walk1.asc , scourge_straferight2 ] {self.spawnsilent = 1;scourge_think();ai_right(20);}; +void() scourge_straferight2 =[ $walk2.asc , scourge_straferight3 ] {ai_right(20);}; +void() scourge_straferight3 =[ $walk3.asc , scourge_straferight4 ] {ai_right(20);}; +void() scourge_straferight4 =[ $walk4.asc , scourge_straferight5 ] {ai_right(14);}; +void() scourge_straferight5 =[ $walk5.asc , scourge_straferight6 ] {ai_right(14);}; +void() scourge_straferight6 =[ $walk6.asc , scourge_run1 ] {ai_right(14);}; + +/* +void() scourge_wake1 =[ $stand2.asc, scourge_wake2 ] {self.spawnsilent = 0;scourge_think();}; +void() scourge_wake2 =[ $stand3.asc, scourge_wake3 ] {}; +void() scourge_wake3 =[ $stand4.asc, scourge_wake4 ] {}; +void() scourge_wake4 =[ $stand5.asc, scourge_wake5 ] {}; +void() scourge_wake5 =[ $stand6.asc, scourge_wake6 ] {}; +void() scourge_wake6 =[ $stand7.asc, scourge_run1 ] + { + SUB_AttackFinished(1); + }; +//void() scourge_wake7 =[ $stand7.asc, scourge_run1 ] {}; +*/ + +void() scourge_turn1 =[ $walk1.asc, scourge_turn2 ] {self.spawnsilent = 1;scourge_think();ai_turn_in_place();}; +void() scourge_turn2 =[ $walk2.asc, scourge_turn3 ] {ai_turn_in_place();}; +void() scourge_turn3 =[ $walk3.asc, scourge_turn4 ] {ai_turn_in_place();}; +void() scourge_turn4 =[ $walk4.asc, scourge_turn5 ] {ai_turn_in_place();}; +void() scourge_turn5 =[ $walk5.asc, scourge_turn6 ] {ai_turn_in_place();}; +void() scourge_turn6 =[ $walk6.asc, scourge_turn1 ] {ai_turn_in_place();}; + +void() scourge_atk1 =[ $shoot1.asc, scourge_atk2 ] + { + self.spawnsilent = 0; + scourge_think(); + self.effects = self.effects | EF_MUZZLEFLASH; + SCOURGE_FireSpikes(40); +// SCOURGE_FireSpikes(-40); + }; +void() scourge_atk2 =[ $shoot2.asc, scourge_atk3 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; +// SCOURGE_FireSpikes(56); + SCOURGE_FireSpikes(-56); + }; +void() scourge_atk3 =[ $shoot1.asc, scourge_atk4 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; +// SCOURGE_FireSpikes(40); + SCOURGE_FireSpikes(-40); + }; +void() scourge_atk4 =[ $shoot2.asc, scourge_atk5 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + SCOURGE_FireSpikes(56); +// SCOURGE_FireSpikes(-56); + }; +void() scourge_atk5 =[ $shoot1.asc, scourge_atk6 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + SCOURGE_FireSpikes(40); +// SCOURGE_FireSpikes(-40); + }; +void() scourge_atk6 =[ $shoot2.asc, scourge_atk7 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; +// SCOURGE_FireSpikes(56); + SCOURGE_FireSpikes(-56); + }; +void() scourge_atk7 =[ $shoot1.asc, scourge_atk8 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; +// SCOURGE_FireSpikes(40); + SCOURGE_FireSpikes(-40); + }; +void() scourge_atk8 =[ $shoot2.asc, scourge_run1 ] + { + self.effects = self.effects | EF_MUZZLEFLASH; + SCOURGE_FireSpikes(56); +// SCOURGE_FireSpikes(-56); + SUB_AttackFinished (4*random()); + }; + +void() scourge_melee1 =[ $sting1.asc, scourge_melee2 ] {self.spawnsilent = 0;scourge_think();ai_charge(3);}; +void() scourge_melee2 =[ $sting2.asc, scourge_melee3 ] {ai_charge(3);}; +void() scourge_melee3 =[ $sting3.asc, scourge_melee4 ] {ai_charge(2);}; +void() scourge_melee4 =[ $sting4.asc, scourge_melee5 ] {ai_charge(2);}; +void() scourge_melee5 =[ $sting5.asc, scourge_melee6 ] + { + sound (self, CHAN_WEAPON, "scourge/tailswng.wav", 1, ATTN_NORM); + ai_charge(3); + }; +void() scourge_melee6 =[ $sting6.asc, scourge_melee7 ] {ai_charge(1);}; +void() scourge_melee7 =[ $sting7.asc, scourge_melee8 ] + { + Attack_With_Tail(); + }; +void() scourge_melee8 =[ $sting8.asc, scourge_melee9 ] {ai_face();}; +void() scourge_melee9 =[ $sting9.asc, scourge_melee10 ] {ai_face();}; +void() scourge_melee10 =[ $sting10.asc, scourge_melee11 ] {ai_face();}; +void() scourge_melee11 =[ $sting11.asc, scourge_run1 ] {ai_face(); +SUB_CheckRefire (scourge_melee1); +}; + +void() scourge_pain1 =[ $pain1.asc, scourge_pain2 ] {self.spawnsilent = 0;scourge_think();}; +void() scourge_pain2 =[ $pain2.asc, scourge_pain3 ] {}; +void() scourge_pain3 =[ $pain3.asc, scourge_pain4 ] {}; +void() scourge_pain4 =[ $pain4.asc, scourge_pain5 ] {}; +void() scourge_pain5 =[ $pain5.asc, scourge_run1 ] {}; + +void(entity attacker, float damage) scourge_pain = +{ + local float r; + + + if (random()*50 > damage) + return; // didn't flinch + + if (self.pain_finished > time) + return; + + + r = random (); + sound (self, CHAN_VOICE, "scourge/pain.wav", 1, ATTN_NORM); + + self.pain_finished = time + 2.0; + scourge_pain1 (); +}; + +//============================================================================ + + + + +void() scourge_die1 =[ $death1.asc, scourge_die2 ] {self.spawnsilent = 0;scourge_think();}; +void() scourge_die2 =[ $death2.asc, scourge_die3 ] {}; +void() scourge_die3 =[ $death3.asc, scourge_die4 ] {self.solid = SOLID_NOT;}; +void() scourge_die4 =[ $death4.asc, scourge_die5 ] {}; +void() scourge_die5 =[ $death5.asc, scourge_die5 ] {}; + +void() scourge_die = +{ +// check for gib + // stop sound + remove (self.lastvictim); + self.spawnsilent = 0; + scourge_think(); + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_scourg.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "scourge/pain2.wav", 1, ATTN_NORM); + scourge_die1 (); +}; + +// +// scourge_melee +// +void() scourge_melee = + { + local float chance; + +// chance = random(); +// if (chance < 0.5 ) +// { + scourge_melee1(); +// } +// else +// return; + SUB_AttackFinished (2*random()); + }; + +// +// ScourgeTriggerThink +// +void() ScourgeTriggerThink = + { + local entity targ; + + if (self.lastvictim.health<=0) + { + remove(self); + return; + } + targ = self.lastvictim; + makevectors(targ.angles); + setorigin(self, targ.origin + (v_forward*300) ); + self.nextthink = time + 0.1; + }; + +// +// ScourgeTriggerTouch +// +void() ScourgeTriggerTouch = + { + local entity targ; + local entity tempent; + local vector dir; + + if (other.flags & (FL_MONSTER|FL_CLIENT)) + return; + if (other.movetype != MOVETYPE_FLYMISSILE) + return; + targ = self.lastvictim; + if (targ.health<=0) + { + remove(self); + return; + } + dir = targ.origin - other.origin; + dir = normalize(dir); + if ((dir * normalize(other.velocity))<0.8) + return; + +// targ.think = targ.th_run; +// if (targ.flags & FL_ONGROUND) +// { + if (time > self.duration) + { + tempent = self; + self = targ; + if (random()<0.5) + scourge_strafeleft1(); + else + scourge_straferight1(); + self.duration = time + 1.5; + } +/* + targ.origin_z = targ.origin_z + 1; + targ.velocity = targ.velocity + '0 0 500'; + targ.flags = targ.flags - FL_ONGROUND; +*/ + self = tempent; +// } +// dprint("ouch\n"); + }; + +/*QUAKED monster_scourge (1 0 0) (-16 -16 -24) (16 16 40) Ambush + +*/ +void() monster_scourge = +{ + local entity trig; + + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/scor.mdl"); + precache_model2 ("progs/h_scourg.mdl"); + precache_model2 ("progs/laser.mdl"); + precache_model2 ("progs/s_light.mdl"); + precache_model2 ("progs/spike.mdl"); + + precache_sound ("misc/null.wav"); + precache_sound ("scourge/idle.wav"); + precache_sound ("scourge/pain.wav"); + precache_sound ("scourge/pain2.wav"); + precache_sound ("scourge/sight.wav"); + precache_sound ("scourge/tailswng.wav"); + precache_sound ("scourge/walk.wav"); + precache_sound2 ("shambler/smack.wav"); + precache_sound2 ("weapons/rocket1i.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/scor.mdl"); + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 300; + + self.th_stand = scourge_stand1; + self.th_walk = scourge_walk1; + self.th_run = scourge_run1; + self.th_pain = scourge_pain; + self.th_die = scourge_die; + self.th_melee = scourge_melee; + self.th_missile = scourge_atk1; +// self.th_turn = scourge_turn1; + self.yaw_speed = 60; + self.attack_state = AS_DODGING; + self.state = 0; + + walkmonster_start(); +}; diff --git a/quakec_hipnotic/hipspawn.qc b/quakec_hipnotic/hipspawn.qc new file mode 100644 index 0000000..4e0d515 --- /dev/null +++ b/quakec_hipnotic/hipspawn.qc @@ -0,0 +1,382 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +//===================================================================== +// +// Spawning Functions (Hipnotic) +// +//===================================================================== + +//================ +// +// spawn_think +// +//================ + +void() spawn_think = + { + self.think = spawn_think; + self.nextthink = time + 1; + }; + +//================ +// +// spawn_use +// +//================ + +void() spawn_use = + { + local entity spawnentity; + local entity tempself; + + if ((self.spawnmulti == 1) || (horn_active)) + { + // spawn the new entity + spawnentity = spawn(); + // copy the master mold + SUB_CopyEntity(self.spawnmaster,spawnentity); + } + else + { + spawnentity = self.spawnmaster; + } + // restore the model + spawnentity.model = spawnentity.spawnmodel; + //restore solid flag + spawnentity.solid = spawnentity.spawnsolidtype; + //restore thinking function + spawnentity.think = spawnentity.spawnthink; + setmodel (spawnentity, spawnentity.model); + setorigin (spawnentity, spawnentity.origin); + spawnentity.mins = spawnentity.spawnmins; + spawnentity.maxs = spawnentity.spawnmaxs; + setsize (spawnentity, spawnentity.mins, spawnentity.maxs); + // spawn the teleport effect + if (self.spawnsilent == 0) + spawn_tfog (spawnentity.origin); +// horn_active = 0; +// horn_charmer = find( world, classname, "player" ); + // call spawnentity think function + if (horn_active) + { + spawnentity.charmer = horn_charmer; + spawnentity.charmed = 1; + } +// if (spawnentity.think) +// { +// spawnentity.nextthink = time+0.1; +// tempself = self; +// self = spawnentity; +// self.think(); +// self = tempself; +// spawnentity.nextthink = time+0.1; +// self.nextthink = 1; +// if (spawnentity.nextthink < time) +// spawnentity.nextthink = 1; +// } + // check to see if it is a monster + if (spawnentity.flags & FL_MONSTER) + { + if ((self.spawnmulti != 0) && (horn_active == 0)) + { + total_monsters = total_monsters + 1; + WriteByte (MSG_BROADCAST, SVC_UPDATESTAT); + WriteByte (MSG_BROADCAST, STAT_TOTALMONSTERS); + WriteLong (MSG_BROADCAST, total_monsters); + } + // spawn the telefrag effect +// if (self.spawnsilent == 0) +// spawn_tdeath(spawnentity.origin, spawnentity); + if (horn_active) + { + spawnentity.effects = spawnentity.effects | EF_DIMLIGHT; +// spawnentity.effects = spawnentity.effects | EF_BRIGHTFIELD; + } + } + if ((self.spawnmulti == 0) && (horn_active == 0)) + { + remove(self); + } + }; + +//================ +// +// func_spawn +// +//================ +/*QUAKED func_spawn (0 .5 .8) (-32 -32 -24) (32 32 64) big/ambush megahealth +This will spawn a thing upon being used. The thing that +is spawned depends upon the value of "spawnfunction". +"spawnclassname" should contain the same value as "spawnfunction". +If "spawnfunction" is unspecified a random monster is chosen. +The angles, target and all flags are passed on +Think of it like setting up a normal entity. +"spawnsilent" set this to 1 if you want a silent spawn. +"spawnmulti" set this to 1 if you want this spawn to be reoccuring. +*/ + +void() func_spawn = + { + local entity tempself; + local entity monster; + local float tempdeathmatch; + local float temptotal_monsters; + local vector mn,mx; + +// if (deathmatch) +// { +// remove(self); +// return; +// } + + // save off deathmatch and zero it out + tempself = self; + tempdeathmatch = deathmatch; + deathmatch = 0; + if (!self.spawnfunction) + { + local float spawnchance; + + spawnchance = random(); + monster = tempself; + + // save off monster count so it doesn't get f'ed up + temptotal_monsters = total_monsters; + + // spawn dog + // spawn the new entity + self = spawn(); + // copy over everything + SUB_CopyEntity(tempself,self); + self.spawnfunction = monster_dog; + self.spawnclassname = "monster_dog"; + self.classname = self.spawnclassname; + + // call the named spawn function + self.spawnfunction(); + self.spawnmodel = self.model; + self.spawnmins = self.mins; + self.spawnmaxs = self.maxs; + setmodel (self, ""); + setsize (self, self.spawnmins, self.spawnmaxs); + //save off solid flag + self.spawnsolidtype = self.solid; + self.solid = SOLID_NOT; + //save off think func and + //get rid of his thinking + self.spawnthink = self.think; + self.think = spawn_think; + self.nextthink = time + 1; + + if (spawnchance<0.5 && monster==tempself) + { + monster = self; + } + + // spawn ogre + // spawn the new entity + self = spawn(); + // copy over everything + SUB_CopyEntity(tempself,self); + self.spawnfunction = monster_ogre; + self.spawnclassname = "monster_ogre"; + self.classname = self.spawnclassname; + + // call the named spawn function + self.spawnfunction(); + self.spawnmodel = self.model; + self.spawnmins = self.mins; + self.spawnmaxs = self.maxs; + setmodel (self, ""); + setsize (self, self.spawnmins, self.spawnmaxs); + //save off solid flag + self.spawnsolidtype = self.solid; + self.solid = SOLID_NOT; + //save off think func and + //get rid of his thinking + self.spawnthink = self.think; + self.think = spawn_think; + self.nextthink = time + 1; + + if (spawnchance<0.8 && monster==tempself) + { + monster = self; + } + + // spawn fiend + // spawn the new entity + self = spawn(); + // copy over everything + SUB_CopyEntity(tempself,self); + self.spawnfunction = monster_demon1; + self.spawnclassname = "monster_demon1"; + self.classname = self.spawnclassname; + + // call the named spawn function + self.spawnfunction(); + self.spawnmodel = self.model; + self.spawnmins = self.mins; + self.spawnmaxs = self.maxs; + setmodel (self, ""); + setsize (self, self.spawnmins, self.spawnmaxs); + //save off solid flag + self.spawnsolidtype = self.solid; + self.solid = SOLID_NOT; + //save off think func and + //get rid of his thinking + self.spawnthink = self.think; + self.think = spawn_think; + self.nextthink = time + 1; + + if (spawnchance<0.92 && monster==tempself) + { + monster = self; + } + + // spawn zombie + // spawn the new entity + self = spawn(); + // copy over everything + SUB_CopyEntity(tempself,self); + self.spawnfunction = monster_zombie; + self.spawnclassname = "monster_zombie"; + self.classname = self.spawnclassname; + + // call the named spawn function + self.spawnfunction(); + self.spawnmodel = self.model; + self.spawnmins = self.mins; + self.spawnmaxs = self.maxs; + setmodel (self, ""); + setsize (self, self.spawnmins, self.spawnmaxs); + //save off solid flag + self.spawnsolidtype = self.solid; + self.solid = SOLID_NOT; + //save off think func and + //get rid of his thinking + self.spawnthink = self.think; + self.think = spawn_think; + self.nextthink = time + 1; + + if (spawnchance<0.97 && monster==tempself) + { + monster = self; + } + + // spawn shambler + // spawn the new entity + self = spawn(); + // copy over everything + SUB_CopyEntity(tempself,self); + self.spawnfunction = monster_shambler; + self.spawnclassname = "monster_shambler"; + self.classname = self.spawnclassname; + + // call the named spawn function + self.spawnfunction(); + self.spawnmodel = self.model; + self.spawnmins = self.mins; + self.spawnmaxs = self.maxs; + setmodel (self, ""); + setsize (self, self.spawnmins, self.spawnmaxs); + //save off solid flag + self.spawnsolidtype = self.solid; + self.solid = SOLID_NOT; + //save off think func and + //get rid of his thinking + self.spawnthink = self.think; + self.think = spawn_think; + self.nextthink = time + 1; + + if (monster==tempself) + { + monster = self; + } + // make sure monster count is correct + total_monsters = temptotal_monsters + 1; + } + else + { + // spawn the new entity + self = spawn(); + // copy over everything + SUB_CopyEntity(tempself,self); + + // save off monster count so it doesn't get f'ed up + temptotal_monsters = total_monsters; + if (self.spawnclassname == string_null) + { + objerror("No spawnclassname defined"); + } + self.classname = self.spawnclassname; + // call the named spawn function + self.spawnfunction(); + if (self.spawnmulti != 0) + { + // make sure monster count is correct + total_monsters = temptotal_monsters; + } + self.spawnmodel = self.model; + self.spawnmins = self.mins; + self.spawnmaxs = self.maxs; + setmodel (self, ""); + self.model = ""; + setsize (self, self.spawnmins, self.spawnmaxs); + //save off solid flag + self.spawnsolidtype = self.solid; + self.solid = SOLID_NOT; + //save off think func and + //get rid of his thinking + self.spawnthink = self.think; + self.think = spawn_think; + self.nextthink = time + 1; + monster = self; + } + self = tempself; + deathmatch = tempdeathmatch; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + self.model = ""; + setmodel (self, self.model); // set size and link into world + self.use = spawn_use; + self.spawnmaster = monster; + }; + +//================ +// +// func_spawn_small +// +//================ +/*QUAKED func_spawn_small (0 .5 .8) (-16 -16 -24) (16 16 40) big/ambush megahealth +This will spawn a thing upon being used. The thing that +is spawned depends upon the value of "spawnfunction". +"spawnclassname" should contain the same value as "spawnfunction". +If "spawnfunction" is unspecified a random monster is chosen. +The angles, target and all flags are passed on +Think of it like setting up a normal entity. +"spawnsilent" set this to 1 if you want a silent spawn. +"spawnmulti" set this to 1 if you want this spawn to be reoccuring. +*/ + +void() func_spawn_small = + { + func_spawn(); + }; diff --git a/quakec_hipnotic/hipspike.qc b/quakec_hipnotic/hipspike.qc new file mode 100644 index 0000000..1a7d6b5 --- /dev/null +++ b/quakec_hipnotic/hipspike.qc @@ -0,0 +1,85 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +spike mine + +============================================================================== +*/ + +$cd id1/models/demon3 +$scale 0.8 +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + +$frame run1 run2 run3 run4 run5 run6 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 leap10 +$frame leap11 leap12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attacka1 attacka2 attacka3 attacka4 attacka5 attacka6 attacka7 attacka8 +$frame attacka9 attacka10 attacka11 attacka12 attacka13 attacka14 attacka15 + +void() monster_spikemine = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/demon.mdl"); + precache_model ("progs/h_demon.mdl"); + + precache_sound ("demon/ddeath.wav"); + precache_sound ("demon/dhit2.wav"); + precache_sound ("demon/djump.wav"); + precache_sound ("demon/dpain1.wav"); + precache_sound ("demon/idle1.wav"); + precache_sound ("demon/sight2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/demon.mdl"); + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + +/* + self.th_stand = gremlin_stand1; + self.th_walk = gremlin_walk1; + self.th_run = gremlin_run1; + self.th_die = gremlin_die; + self.th_melee = Gremlin_MeleeAttack; // one of two attacks + self.th_missile = Gremlin_MissileAttack; // check for random jump or firing of weapon + self.th_pain = gremlin_pain; + walkmonster_start(); +*/ +}; diff --git a/quakec_hipnotic/hipspr.qc b/quakec_hipnotic/hipspr.qc new file mode 100644 index 0000000..23ee070 --- /dev/null +++ b/quakec_hipnotic/hipspr.qc @@ -0,0 +1,34 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Sprites QuickC program + By Jim Dose' 11/20/96 +*/ + +// Bullethole +$spritename s_bullet +$type oriented +$load h:/quake/hipwork/gfx/sprites/blood.lbm +$frame 96 16 8 8 + +// Blood Splat +$spritename s_blood1 +$type oriented +$load h:/quake/hipwork/gfx/sprites/blood.lbm +$frame 32 32 56 56 diff --git a/quakec_hipnotic/hipsubs.qc b/quakec_hipnotic/hipsubs.qc new file mode 100644 index 0000000..ceb0724 --- /dev/null +++ b/quakec_hipnotic/hipsubs.qc @@ -0,0 +1,230 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +//========================= +// +// Hipnotic Sub-routines +// +//========================= + + +//========================= +// +// SUB_CopyEntity +// +//========================= + +void (entity srcent, entity destent) SUB_CopyEntity = + { + destent.modelindex=srcent.modelindex; + destent.absmin=srcent.absmin; + destent.absmax=srcent.absmax; + destent.ltime=srcent.ltime; + destent.movetype=srcent.movetype; + destent.solid=srcent.solid; + destent.origin=srcent.origin; + destent.oldorigin=srcent.oldorigin; + destent.velocity=srcent.velocity; + destent.angles=srcent.angles; + destent.avelocity=srcent.avelocity; + destent.punchangle=srcent.punchangle; + destent.classname=srcent.classname; + destent.model=srcent.model; + destent.frame=srcent.frame; + destent.skin=srcent.skin; + destent.effects=srcent.effects; + destent.mins=srcent.mins; + destent.maxs=srcent.maxs; + destent.size=srcent.size; + destent.touch=srcent.touch; + destent.use=srcent.use; + destent.think=srcent.think; + destent.blocked=srcent.blocked; + destent.nextthink=srcent.nextthink; + destent.groundentity=srcent.groundentity; + destent.health=srcent.health; + destent.frags=srcent.frags; + destent.weapon=srcent.weapon; + destent.weaponmodel=srcent.weaponmodel; + destent.weaponframe=srcent.weaponframe; + destent.currentammo=srcent.currentammo; + destent.ammo_shells=srcent.ammo_shells; + destent.ammo_nails=srcent.ammo_nails; + destent.ammo_rockets=srcent.ammo_rockets; + destent.ammo_cells=srcent.ammo_cells; + destent.items=srcent.items; + destent.takedamage=srcent.takedamage; + destent.chain=srcent.chain; + destent.deadflag=srcent.deadflag; + destent.view_ofs=srcent.view_ofs; + destent.button0=srcent.button0; + destent.button1=srcent.button1; + destent.button2=srcent.button2; + destent.impulse=srcent.impulse; + destent.fixangle=srcent.fixangle; + destent.v_angle=srcent.v_angle; + destent.idealpitch=srcent.idealpitch; + destent.netname=srcent.netname; + destent.enemy=srcent.enemy; + destent.flags=srcent.flags; + destent.colormap=srcent.colormap; + destent.team=srcent.team; + destent.max_health=srcent.max_health; + destent.teleport_time=srcent.teleport_time; + destent.armortype=srcent.armortype; + destent.armorvalue=srcent.armorvalue; + destent.waterlevel=srcent.waterlevel; + destent.watertype=srcent.watertype; + destent.ideal_yaw=srcent.ideal_yaw; + destent.yaw_speed=srcent.yaw_speed; + destent.aiment=srcent.aiment; + destent.goalentity=srcent.goalentity; + destent.spawnflags=srcent.spawnflags; + destent.target=srcent.target; + destent.targetname=srcent.targetname; + destent.dmg_take=srcent.dmg_take; + destent.dmg_save=srcent.dmg_save; + destent.dmg_inflictor=srcent.dmg_inflictor; + destent.owner=srcent.owner; + destent.movedir=srcent.movedir; + destent.message=srcent.message; + destent.sounds=srcent.sounds; + destent.noise=srcent.noise; + destent.noise1=srcent.noise1; + destent.noise2=srcent.noise2; + destent.noise3=srcent.noise3; + destent.wad=srcent.wad; + destent.map=srcent.map; + destent.worldtype=srcent.worldtype; + destent.killtarget=srcent.killtarget; + destent.light_lev=srcent.light_lev; + destent.style=srcent.style; + destent.th_stand=srcent.th_stand; + destent.th_walk=srcent.th_walk; + destent.th_run=srcent.th_run; + destent.th_missile=srcent.th_missile; + destent.th_melee=srcent.th_melee; + destent.th_pain=srcent.th_pain; + destent.th_die=srcent.th_die; + destent.th_turn=srcent.th_turn; + destent.oldenemy=srcent.oldenemy; + destent.speed=srcent.speed; + destent.lefty=srcent.lefty; + destent.search_time=srcent.search_time; + destent.attack_state=srcent.attack_state; + destent.walkframe=srcent.walkframe; + destent.attack_finished=srcent.attack_finished; + destent.pain_finished=srcent.pain_finished; + destent.invincible_finished=srcent.invincible_finished; + destent.invisible_finished=srcent.invisible_finished; + destent.super_damage_finished=srcent.super_damage_finished; + destent.radsuit_finished=srcent.radsuit_finished; + destent.invincible_time=srcent.invincible_time; + destent.invincible_sound=srcent.invincible_sound; + destent.invisible_time=srcent.invisible_time; + destent.invisible_sound=srcent.invisible_sound; + destent.super_time=srcent.super_time; + destent.super_sound=srcent.super_sound; + destent.rad_time=srcent.rad_time; + destent.fly_sound=srcent.fly_sound; + destent.axhitme=srcent.axhitme; + destent.show_hostile=srcent.show_hostile; + destent.jump_flag=srcent.jump_flag; + destent.swim_flag=srcent.swim_flag; + destent.air_finished=srcent.air_finished; + destent.bubble_count=srcent.bubble_count; + destent.deathtype=srcent.deathtype; + destent.mdl=srcent.mdl; + destent.mangle=srcent.mangle; + destent.oldorigin=srcent.oldorigin; + destent.t_length=srcent.t_length; + destent.t_width=srcent.t_width; + destent.dest=srcent.dest; + destent.dest1=srcent.dest1; + destent.dest2=srcent.dest2; + destent.wait=srcent.wait; + destent.delay=srcent.delay; + destent.trigger_field=srcent.trigger_field; + destent.noise4=srcent.noise4; + destent.pausetime=srcent.pausetime; + destent.movetarget=srcent.movetarget; + destent.aflag=srcent.aflag; + destent.dmg=srcent.dmg; + destent.cnt=srcent.cnt; + destent.think1=srcent.think1; + destent.finaldest=srcent.finaldest; + destent.finalangle=srcent.finalangle; + destent.count=srcent.count; + destent.lip=srcent.lip; + destent.state=srcent.state; + destent.pos1=srcent.pos1; + destent.pos2=srcent.pos2; + destent.height=srcent.height; + destent.waitmin=srcent.waitmin; + destent.waitmax=srcent.waitmax; + destent.distance=srcent.distance; + destent.volume=srcent.volume; + destent.gorging=srcent.gorging; + destent.stoleweapon=srcent.stoleweapon; + destent.spawnfunction=srcent.spawnfunction; + destent.spawnthink=srcent.spawnthink; + destent.spawnmodel=srcent.spawnmodel; + destent.spawnsolidtype=srcent.spawnsolidtype; + destent.spawnmins=srcent.spawnmins; + destent.spawnmaxs=srcent.spawnmaxs; + destent.spawnclassname=srcent.spawnclassname; + }; + +//========================= +// +// SUB_NormalizeAngles +// +//========================= + +vector ( vector ang ) SUB_NormalizeAngles = + { + while( ang_x > 360 ) + { + ang_x = ang_x - 360; + } + while( ang_x < 0 ) + { + ang_x = ang_x + 360; + } + + while( ang_y > 360 ) + { + ang_y = ang_y - 360; + } + while( ang_y < 0 ) + { + ang_y = ang_y + 360; + } + + while( ang_z > 360 ) + { + ang_z = ang_z - 360; + } + while( ang_z < 0 ) + { + ang_z = ang_z + 360; + } + + return ang; + }; diff --git a/quakec_hipnotic/hiptrain.qc b/quakec_hipnotic/hiptrain.qc new file mode 100644 index 0000000..8a767b4 --- /dev/null +++ b/quakec_hipnotic/hiptrain.qc @@ -0,0 +1,221 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Train QuickC program + By Jim Dose' 9/11/96 +*/ + +void() train_blocked; +void() func_train; +void() hip_train_next; +void() hip_func_train_find; + +void() hip_train_use = +{ + if (self.think != hip_func_train_find) + { + if ( self.velocity != '0 0 0' ) + return; // already activated + } + hip_train_next(); +}; + +void() hip_train_wait = + { + if (self.wait) + { + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + if ( self.wait != -1 ) + { + self.nextthink = self.ltime + self.wait; + self.wait = 0; + } + } + else + self.nextthink = self.ltime + 0.1; + + self.think = hip_train_next; + }; + + +void() hip_train_next = +{ + local entity targ; + local float current; + local string temp; + + // Get the speed of the current path_corner. + // (we must save this off at each path change since + // we don't have a pointer to the current path_corner). + current = self.cnt; + + targ = find (world, targetname, self.target); + + // Save the speed in cnt for later use + self.cnt = targ.speed; + self.target = targ.target; + if (!self.target) + objerror ("hip_train_next: no next target"); + + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + + self.wait = targ.wait; + if (targ.wait) + { + self.think = hip_train_wait; + } + else + { + self.think = hip_train_next; + } + + if ( self.goalentity.event ) + { + // Trigger any events that should happen at the corner. + temp = self.target; + self.target = self.goalentity.event; + self.message = self.goalentity.message; + SUB_UseTargets(); + self.target = temp; + self.message = string_null; + } + + // Save the current entity + self.goalentity = targ; + + if ( current == -1 ) + { + // Warp to the next path_corner + setorigin (self, targ.origin - self.mins ); + self.nextthink = self.ltime + 0.01; + } + else + { + // check if there's a speed change + if (current>0) + self.speed = current; + + // travel to the next path change + SUB_CalcMove (targ.origin - self.mins, self.speed, self.think ); + } + }; + +void() hip_func_train_find = + +{ + local entity targ; + + targ = find (world, targetname, self.target); + + // Save the current entity + self.goalentity = targ; + + // Save the speed in cnt for later use + self.cnt = targ.speed; + + self.target = targ.target; + setorigin (self, targ.origin - self.mins); + if (!self.targetname) + { // not triggered, so start immediately + self.nextthink = self.ltime + 0.1; + self.think = hip_train_next; + } +}; + +/*QUAKED func_train2 (0 .5 .8) ? +This is a modification of the standard func_train entity. +It is functionally equivalent, except that it removes a slight delay that +would occur after each path entry, and it adds a speed variable to the +path_corner entity. + +"noise" contains the name of the sound to play when train stops. +"noise1" contains the name of the sound to play when train moves. +Both "noise" and "noise1" defaults depend upon "sounds" variable. + +In path_corner, set speed to be the new speed of the train after it reaches +the path change. If speed is -1, the train will warp directly to the next +path change after the specified wait time. + +Also in path_corner, if wait is set to -1, the train will wait until it is +retriggered before moving on to the next goal. + +Here is a reiteration of the func_train docs: + +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 +sounds +1) ratchet metal + +*/ +void() func_train2 = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("func_train without a target"); + if (!self.dmg) + self.dmg = 2; + + if ( !self.noise ) + { + if (self.sounds == 0) + { + self.noise = ("misc/null.wav"); + } + + if (self.sounds == 1) + { + self.noise = ("plats/train2.wav"); + } + } + if ( !self.noise1 ) + { + if (self.sounds == 0) + { + self.noise1 = ("misc/null.wav"); + } + if (self.sounds == 1) + { + self.noise1 = ("plats/train1.wav"); + } + } + + precache_sound( self.noise ); + precache_sound( self.noise1 ); + + self.cnt = 1; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = hip_train_use; + self.classname = "train2"; + + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = hip_func_train_find; +}; diff --git a/quakec_hipnotic/hiptrig.qc b/quakec_hipnotic/hiptrig.qc new file mode 100644 index 0000000..4381616 --- /dev/null +++ b/quakec_hipnotic/hiptrig.qc @@ -0,0 +1,298 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Trigger QuickC program + By Jim Dose' 12/2/96 +*/ + +float USE_GOLD_KEY = 1; + +void() keytrigger_use = + { + if (activator.classname != "player") + return; + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + +// FIXME: blink key on player's status bar + if ( (self.items & activator.items) != self.items ) + { + if (self.message != "") + { + centerprint (activator, self.message); + } + else + { + if (self.owner.items == IT_KEY1) + { + if (world.worldtype == 2) + { + centerprint (activator, "$qc_need_silver_keycard"); + } + else if (world.worldtype == 1) + { + centerprint (activator, "$qc_need_silver_runekey"); + } + else if (world.worldtype == 0) + { + centerprint (activator, "$qc_need_silver_key"); + } + } + else + { + if (world.worldtype == 2) + { + centerprint (activator, "$qc_need_gold_keycard"); + } + else if (world.worldtype == 1) + { + centerprint (activator, "$qc_need_gold_runekey"); + } + else if (world.worldtype == 0) + { + centerprint (activator, "$qc_need_gold_key"); + } + } + } + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + return; + } + + activator.items = activator.items - self.items; + + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + self.touch = SUB_Null; + self.use = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + self.message = ""; + + sound (self, CHAN_VOICE, self.noise4, 1, ATTN_NORM); + + SUB_UseTargets(); + }; + +void() keytrigger_touch = + { + activator = other; + keytrigger_use(); + }; + +/*QUAKED trigger_usekey (0 .5 0) ? USE_GOLD_KEY +Variable sized single use trigger that requires a key to trigger targets. Must be targeted at one or more entities. + +"message" is printed when the trigger is touched without having the right key. +*/ + +void() trigger_usekey = + { + if (world.worldtype == 0) + { + precache_sound ("doors/medtry.wav"); + precache_sound ("doors/meduse.wav"); + self.noise3 = "doors/medtry.wav"; + self.noise4 = "doors/meduse.wav"; + } + else if (world.worldtype == 1) + { + precache_sound ("doors/runetry.wav"); + precache_sound ("doors/runeuse.wav"); + self.noise3 = "doors/runetry.wav"; + self.noise4 = "doors/runeuse.wav"; + } + else if (world.worldtype == 2) + { + precache_sound ("doors/basetry.wav"); + precache_sound ("doors/baseuse.wav"); + self.noise3 = "doors/basetry.wav"; + self.noise4 = "doors/baseuse.wav"; + } + else + { + dprint ("no worldtype set!\n"); + } + + if (self.spawnflags & USE_GOLD_KEY) + self.items = IT_KEY2; + else + self.items = IT_KEY1; + + self.use = keytrigger_use; + self.touch = keytrigger_touch; + + InitTrigger (); + }; + +void() remove_touch = + { + if (other.flags & self.cnt) + return; + other.touch = SUB_Null; + other.model = ""; + remove(self); +// other.nextthink = time + 0.1; +// other.think = SUB_Remove; + }; + +/*QUAKED trigger_remove (.5 .5 .5) ? ignoremonsters ignoreplayers +Variable sized trigger that removes the thing +that touches it. Does not affect monsters or +players. +*/ +void() trigger_remove = + { + self.cnt = FL_CLIENT|FL_MONSTER; + if (self.spawnflags & 1) + self.cnt = self.cnt - FL_MONSTER; + if (self.spawnflags & 2) + self.cnt = self.cnt - FL_CLIENT; + InitTrigger (); + self.touch = remove_touch; + }; +/* +============================================================================== + +trigger_setgravity + +============================================================================== +*/ + +void() trigger_gravity_touch = +{ + if (other.classname != "player") + return; + if (self.gravity == -1) + other.gravity = 1.0; + else + other.gravity = self.gravity; +}; + +/*QUAKED trigger_setgravity (.5 .5 .5) ? +set the gravity of a player +"gravity" what to set the players gravity to + - 0 (default) normal gravity + - 1 no gravity + - 2 almost no gravity + - ... + - 101 normal gravity + - 102 slightly higher gravity + - ... + - 1000 very high gravity +*/ +void() trigger_setgravity = +{ + InitTrigger (); + self.touch = trigger_gravity_touch; + if (!self.gravity) + { + self.gravity = -1; + } + else + { + self.gravity = ((self.gravity - 1) / 100); + } +}; + +void() trigger_command_use = + { + if ( self.message ) + localcmd( self.message ); + }; + +/*QUAKED trigger_command (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) + When triggered, stuffs a command into the console to allow map + designers to set server variables. + + "message" is the command to send to the console. +*/ + +void() trigger_command = + { + self.use = oncount_use; + self.think = SUB_Null; + }; + +void() trigger_decoy_touch = + { + if (other.classname != "monster_decoy") + return; + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + SUB_UseTargets(); + }; + +/*QUAKED trigger_decoy_use (.5 .5 .5) ? + only the decoy player can trigger this + once triggers, all targets are used +*/ + +void() trigger_decoy_use = + { + if (deathmatch) + { + remove(self); + return; + } + InitTrigger (); + self.touch = trigger_decoy_touch; + }; + +void() trigger_waterfall_touch = + { + // only affect players + if (!(other.flags & FL_CLIENT)) + { + return; + } + + other.velocity = other.velocity + self.movedir; + other.velocity_x = other.velocity_x + self.count * ( random() - 0.5 ); + other.velocity_y = other.velocity_y + self.count * ( random() - 0.5 ); + }; + +/*QUAKED trigger_waterfall (.2 .5 .2) ? + Pushes the player in the direction specified by angles. + + "speed" is the strength of the push (default 50). + "count" amount of random xy movement to add to velocity (default 100). +*/ + +void() trigger_waterfall = + { + InitTrigger (); + self.touch = trigger_waterfall_touch; + + if ( self.count == 0 ) + { + self.count = 100; + } + + if ( self.speed == 0 ) + { + self.movedir = self.movedir * 50; + } + else + { + self.movedir = self.movedir * self.speed; + } + }; diff --git a/quakec_hipnotic/hipwater.qc b/quakec_hipnotic/hipwater.qc new file mode 100644 index 0000000..f7c81dd --- /dev/null +++ b/quakec_hipnotic/hipwater.qc @@ -0,0 +1,69 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* Water QuickC program + By Jim Dose' 12/16/96 +*/ + +void() bobbingwater_think = + { + local vector ang; + + self.count = self.count + self.speed * ( time - self.ltime ); + if ( self.count > 360 ) + { + self.count = self.count - 360; + } + ang_x = self.count; + ang_y = 0; + ang_z = 0; + makevectors( ang ); + self.origin_z = v_forward_z * self.cnt; + setorigin( self, self.origin ); + self.ltime = time; + self.nextthink = time + 0.02; + }; + +/*QUAKED func_bobbingwater (0 .5 .8) ? +Used to emulate water. To use, create a thin water brush and center it +on the water line of the body of water to bob. The amount of the bob +is the depth of the brush. + +"speed" is how long in seconds it takes the brush to do one full bob. +*/ +void() func_bobbingwater = + { + self.angles = '0 0 0'; + self.movetype = MOVETYPE_STEP; + self.solid = SOLID_NOT; + setmodel (self,self.model); + self.think = bobbingwater_think; + + self.count = 0; + self.cnt = self.size_z / 2; + if ( !self.speed ) + { + self.speed = 4; + } + + self.speed = 360 / self.speed; + + self.nextthink = time + 0.02; + self.ltime = time; + }; diff --git a/quakec_hipnotic/items.qc b/quakec_hipnotic/items.qc new file mode 100644 index 0000000..d57a178 --- /dev/null +++ b/quakec_hipnotic/items.qc @@ -0,0 +1,1485 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() W_SetCurrentAmmo; +/* ALL LIGHTS SHOULD BE 0 1 0 IN COLOR ALL OTHER ITEMS SHOULD +BE .8 .3 .4 IN COLOR */ + + +void() SUB_regen = +{ + self.model = self.mdl; // restore original model + self.solid = SOLID_TRIGGER; // allow it to be touched again + sound (self, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); // play respawn sound + setorigin (self, self.origin); +}; + + + +/*QUAKED noclass (0 0 0) (-8 -8 -8) (8 8 8) +prints a warning message when spawned +*/ +void() noclass = +{ + dprint ("noclass spawned at"); + dprint (vtos(self.origin)); + dprint ("\n"); + remove (self); +}; + + + +/* +============ +PlaceItem + +plants the object on the floor +============ +*/ +void() PlaceItem = +{ + local float oldz; + + self.mdl = self.model; // so it can be restored on respawn + self.flags = FL_ITEM; // make extra wide + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_TOSS; + self.velocity = '0 0 0'; + self.origin_z = self.origin_z + 6; + oldz = self.origin_z; + if (!droptofloor()) + { + dprint ("Bonus item fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + return; + } +}; + +/* +============ +StartItem + +Sets the clipping size and plants the object on the floor +============ +*/ +void() StartItem = +{ + self.nextthink = time + 0.2; // items start after other solids + self.think = PlaceItem; +}; + +/* +========================================================================= + +HEALTH BOX + +========================================================================= +*/ +// +// T_Heal: add health to an entity, limiting health to max_health +// "ignore" will ignore max_health limit +// +float (entity e, float healamount, float ignore) T_Heal = +{ + if (e.health <= 0) + return 0; + if ((!ignore) && (e.health >= other.max_health)) + return 0; + healamount = ceil(healamount); + + e.health = e.health + healamount; + if ((!ignore) && (e.health >= other.max_health)) + e.health = other.max_health; + + if (e.health > 250) + e.health = 250; + return 1; +}; + +/*QUAKED item_health (.3 .3 1) (0 0 0) (32 32 32) rotten megahealth +Health box. Normally gives 25 points. +Rotten box heals 5-10 points, +megahealth will add 100 health, then +rot you down to your maximum health limit, +one point per second. +*/ + +float H_ROTTEN = 1; +float H_MEGA = 2; +.float healamount, healtype; +void() health_touch; +void() item_megahealth_rot; + +void() item_health = +{ + self.touch = health_touch; + + if (self.spawnflags & H_ROTTEN) + { + precache_model("maps/b_bh10.bsp"); + + precache_sound("items/r_item1.wav"); + setmodel(self, "maps/b_bh10.bsp"); + self.noise = "items/r_item1.wav"; + self.healamount = 15; + self.healtype = 0; + } + else + if (self.spawnflags & H_MEGA) + { + precache_model("maps/b_bh100.bsp"); + precache_sound("items/r_item2.wav"); + setmodel(self, "maps/b_bh100.bsp"); + self.noise = "items/r_item2.wav"; + self.healamount = 100; + self.healtype = 2; + } + else + { + precache_model("maps/b_bh25.bsp"); + precache_sound("items/health1.wav"); + setmodel(self, "maps/b_bh25.bsp"); + self.noise = "items/health1.wav"; + self.healamount = 25; + self.healtype = 1; + } + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +void() health_touch = +{ + local float amount; + local string s; + + if (other.classname != "player") + return; + + if (self.healtype == 2) // Megahealth? Ignore max_health... + { + if (other.health >= 250) + return; + if (!T_Heal(other, self.healamount, 1)) + return; + } + else + { + if (!T_Heal(other, self.healamount, 0)) + return; + } + + sprint(other, "$qc_item_health", ftos(self.healamount)); + +// health touch sound + sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + + stuffcmd (other, "bf\n"); + + self.model = string_null; + self.solid = SOLID_NOT; + + // Megahealth = rot down the player's super health + if (self.healtype == 2) + { +// 11/02/96 removed SUPERHEALTH from defs.qc +// other.items = other.items | IT_SUPERHEALTH; + self.nextthink = time + 5; + self.think = item_megahealth_rot; + self.owner = other; + } + else + { + if (deathmatch != 2) // deathmatch 2 is the silly old rules + { + if (deathmatch) + self.nextthink = time + 20; + self.think = SUB_regen; + } + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + +void() item_megahealth_rot = +{ + other = self.owner; + + if (other.health > other.max_health) + { + other.health = other.health - 1; + self.nextthink = time + 1; + return; + } + +// it is possible for a player to die and respawn between rots, so don't +// just blindly subtract the flag off +// MED 11/02/96 removed SUPERHEALTH +// other.items = other.items - (other.items & IT_SUPERHEALTH); + + if (deathmatch && deathmatch != 2) // deathmatch 2 is silly old rules + { + self.nextthink = time + 20; + self.think = SUB_regen; + } +}; + +/* +=============================================================================== + +ARMOR + +=============================================================================== +*/ + +void() armor_touch; + +void() armor_touch = +{ + local float type, value, bit; + + if (other.health <= 0) + return; + if (other.classname != "player") + return; + + if (self.classname == "item_armor1") + { + type = 0.3; + value = 100; + bit = IT_ARMOR1; + } + if (self.classname == "item_armor2") + { + type = 0.6; + value = 150; + bit = IT_ARMOR2; + } + if (self.classname == "item_armorInv") + { + type = 0.8; + value = 200; + bit = IT_ARMOR3; + } + if (other.armortype*other.armorvalue >= type*value) + return; + + other.armortype = type; + other.armorvalue = value; + other.items = other.items - (other.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + bit; + + self.solid = SOLID_NOT; + self.model = string_null; + if (deathmatch && deathmatch != 2) + self.nextthink = time + 20; + self.think = SUB_regen; + + sprint(other, "$qc_item_armor"); +// armor touch sound + sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_armor1 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armor1 = +{ + self.touch = armor_touch; + self.armortype = 0.3; + self.armorvalue = 100; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 0; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED item_armor2 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armor2 = +{ + self.touch = armor_touch; + self.armortype = 0.6; + self.armorvalue = 150; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 1; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED item_armorInv (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armorInv = +{ + self.touch = armor_touch; + self.armortype = 0.8; + self.armorvalue = 200; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 2; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/* +=============================================================================== + +WEAPONS + +=============================================================================== +*/ + +void() bound_other_ammo = +{ + if (other.ammo_shells > 100) + other.ammo_shells = 100; + if (other.ammo_nails > 200) + other.ammo_nails = 200; + if (other.ammo_rockets > 100) + other.ammo_rockets = 100; + if (other.ammo_cells > 100) + other.ammo_cells = 100; +}; + + +//MED 01/06/97 added hipnotic weapons into rankings +float(float w) RankForWeapon = +{ + if (w == IT_LIGHTNING) + return 1; + if (w == IT_ROCKET_LAUNCHER) + return 2; + if (w == IT_LASER_CANNON) + return 3; + if (w == IT_SUPER_NAILGUN) + return 4; + if (w == IT_PROXIMITY_GUN) + return 5; + if (w == IT_GRENADE_LAUNCHER) + return 6; + if (w == IT_SUPER_SHOTGUN) + return 7; + if (w == IT_NAILGUN) + return 8; + if (w == IT_MJOLNIR) + return 9; + return 10; +}; + +/* +============= +Deathmatch_Weapon + +Deathmatch weapon change rules for picking up a weapon + +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; +============= +*/ +void(float old, float new) Deathmatch_Weapon = +{ + local float or, nr; + + if ( self.flags & FL_ISBOT ) { + return; + } // mal: let bots pick their own weapon. + +// change self.weapon if desired + or = RankForWeapon (self.weapon); + nr = RankForWeapon (new); + if ( nr < or ) + self.weapon = new; +}; + +/* +============= +weapon_touch +============= +*/ +float() W_BestWeapon; +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon; + +void() weapon_touch = +{ + local float hadammo, best, new, old; + local entity stemp; + local float leave; + + if (!(other.flags & FL_CLIENT)) + return; + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + if (coop || deathmatch == 2 || deathmatch == 3 || deathmatch == 5) + leave = 1; + else + leave = 0; + + if (self.classname == "weapon_nailgun") + { + if (leave && (other.items & IT_NAILGUN) ) + return; + hadammo = other.ammo_nails; + new = IT_NAILGUN; + other.ammo_nails = other.ammo_nails + 30; + } + else if (self.classname == "weapon_supernailgun") + { + if (leave && (other.items & IT_SUPER_NAILGUN) ) + return; + hadammo = other.ammo_rockets; + new = IT_SUPER_NAILGUN; + other.ammo_nails = other.ammo_nails + 30; + } + else if (self.classname == "weapon_supershotgun") + { + if (leave && (other.items & IT_SUPER_SHOTGUN) ) + return; + hadammo = other.ammo_rockets; + new = IT_SUPER_SHOTGUN; + other.ammo_shells = other.ammo_shells + 5; + } + else if (self.classname == "weapon_rocketlauncher") + { + if (leave && (other.items & IT_ROCKET_LAUNCHER) ) + return; + hadammo = other.ammo_rockets; + new = IT_ROCKET_LAUNCHER; + other.ammo_rockets = other.ammo_rockets + 5; + } + else if (self.classname == "weapon_grenadelauncher") + { + if (leave && (other.items & IT_GRENADE_LAUNCHER) ) + return; + hadammo = other.ammo_rockets; + new = IT_GRENADE_LAUNCHER; + other.ammo_rockets = other.ammo_rockets + 5; + } + else if (self.classname == "weapon_lightning") + { + if (leave && (other.items & IT_LIGHTNING) ) + return; + hadammo = other.ammo_rockets; + new = IT_LIGHTNING; + other.ammo_cells = other.ammo_cells + 15; + } +//MED + else if (self.classname == "weapon_laser_gun") + { + if (leave && (other.items & IT_LASER_CANNON) ) + return; + hadammo = other.ammo_rockets; + new = IT_LASER_CANNON; + other.ammo_cells = other.ammo_cells + 30; + } +//MED + else if (self.classname == "weapon_mjolnir") + { + if (leave && (other.items & IT_MJOLNIR) ) + return; + hadammo = other.ammo_rockets; + new = IT_MJOLNIR; + other.ammo_cells = other.ammo_cells + 30; + } +//MED + else if (self.classname == "weapon_proximity_gun") + { + if (leave && (other.items & IT_PROXIMITY_GUN) ) + return; + hadammo = other.ammo_rockets; + new = IT_PROXIMITY_GUN; + other.ammo_rockets = other.ammo_rockets + 6; + } + else + objerror ("weapon_touch: unknown classname"); + + sprint(other, "$qc_got_item", self.netname); +// weapon touch sound + sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + bound_other_ammo (); + +// change to the weapon + old = other.items; + other.items = other.items | new; + + stemp = self; + self = other; + + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (!deathmatch) + self.weapon = new; + else + Deathmatch_Weapon (old, new); + } + + W_SetCurrentAmmo(); + + self = stemp; + + if (leave) + return; + +// remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + if (deathmatch && deathmatch != 2) + self.nextthink = time + 30; + self.think = SUB_regen; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED weapon_supershotgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_supershotgun = +{ + precache_model ("progs/g_shot.mdl"); + setmodel (self, "progs/g_shot.mdl"); + self.weapon = IT_SUPER_SHOTGUN; + self.netname = "$qc_double_shotgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_nailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_nailgun = +{ + precache_model ("progs/g_nail.mdl"); + setmodel (self, "progs/g_nail.mdl"); + self.weapon = IT_NAILGUN; + self.netname = "$qc_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_supernailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_supernailgun = +{ + precache_model ("progs/g_nail2.mdl"); + setmodel (self, "progs/g_nail2.mdl"); + self.weapon = IT_SUPER_NAILGUN; + self.netname = "$qc_super_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_grenadelauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_grenadelauncher = +{ + precache_model ("progs/g_rock.mdl"); + setmodel (self, "progs/g_rock.mdl"); + self.weapon = 3; + self.items = IT_GRENADE_LAUNCHER; + self.netname = "$qc_grenade_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_rocketlauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_rocketlauncher = +{ + precache_model ("progs/g_rock2.mdl"); + setmodel (self, "progs/g_rock2.mdl"); + self.weapon = 3; + self.items = IT_ROCKET_LAUNCHER; + self.netname = "$qc_rocket_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/*QUAKED weapon_lightning (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_lightning = +{ + precache_model ("progs/g_light.mdl"); + setmodel (self, "progs/g_light.mdl"); + self.weapon = 3; + self.items = IT_LIGHTNING; + self.netname = "$qc_thunderbolt"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/* +=============================================================================== + +AMMO + +=============================================================================== +*/ + +void() ammo_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + +// shotgun + if (self.weapon == 1) + { + if (other.ammo_shells >= 100) + return; + other.ammo_shells = other.ammo_shells + self.aflag; + } + +// spikes + if (self.weapon == 2) + { + if (other.ammo_nails >= 200) + return; + other.ammo_nails = other.ammo_nails + self.aflag; + } + +// rockets + if (self.weapon == 3) + { + if (other.ammo_rockets >= 100) + return; + other.ammo_rockets = other.ammo_rockets + self.aflag; + } + +// cells + if (self.weapon == 4) + { + if (other.ammo_cells >= 100) + return; + other.ammo_cells = other.ammo_cells + self.aflag; + } + + bound_other_ammo (); + + sprint(other, "$qc_got_item", self.netname); +// ammo touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + +// change to a better weapon if appropriate + + if ( other.weapon == best && (W_WantsToChangeWeapon(other, 0, 1) == 1)) + { + stemp = self; + self = other; + self.weapon = W_BestWeapon(); + W_SetCurrentAmmo (); + self = stemp; + } + +// if changed current ammo, update it + stemp = self; + self = other; + W_SetCurrentAmmo(); + self = stemp; + +// remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + + if (deathmatch) { + if (deathmatch == 3 || deathmatch == 5) { + self.nextthink = time + 15; + } else if (deathmatch != 2) { + self.nextthink = time + 30; + } + } + + self.think = SUB_regen; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + + + +float WEAPON_BIG2 = 1; + +/*QUAKED item_shells (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_shells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_shell1.bsp"); + setmodel (self, "maps/b_shell1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_shell0.bsp"); + setmodel (self, "maps/b_shell0.bsp"); + self.aflag = 20; + } + self.weapon = 1; + self.netname = "$qc_shells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_spikes (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_spikes = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_nail1.bsp"); + setmodel (self, "maps/b_nail1.bsp"); + self.aflag = 50; + } + else + { + precache_model ("maps/b_nail0.bsp"); + setmodel (self, "maps/b_nail0.bsp"); + self.aflag = 25; + } + self.weapon = 2; + self.netname = "$qc_nails"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_rockets (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_rockets = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_rock1.bsp"); + setmodel (self, "maps/b_rock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_rock0.bsp"); + setmodel (self, "maps/b_rock0.bsp"); + self.aflag = 5; + } + self.weapon = 3; + self.netname = "$qc_rockets"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/*QUAKED item_cells (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_cells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_batt1.bsp"); + setmodel (self, "maps/b_batt1.bsp"); + self.aflag = 12; + } + else + { + precache_model ("maps/b_batt0.bsp"); + setmodel (self, "maps/b_batt0.bsp"); + self.aflag = 6; + } + self.weapon = 4; + self.netname = "$qc_cells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/*QUAKED item_weapon (0 .5 .8) (0 0 0) (32 32 32) shotgun rocket spikes big +DO NOT USE THIS!!!! IT WILL BE REMOVED! +*/ + +float WEAPON_SHOTGUN = 1; +float WEAPON_ROCKET = 2; +float WEAPON_SPIKES = 4; +float WEAPON_BIG = 8; +void() item_weapon = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_SHOTGUN) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_shell1.bsp"); + setmodel (self, "maps/b_shell1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_shell0.bsp"); + setmodel (self, "maps/b_shell0.bsp"); + self.aflag = 20; + } + self.weapon = 1; + self.netname = "$qc_shells"; + } + + if (self.spawnflags & WEAPON_SPIKES) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_nail1.bsp"); + setmodel (self, "maps/b_nail1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_nail0.bsp"); + setmodel (self, "maps/b_nail0.bsp"); + self.aflag = 20; + } + self.weapon = 2; + self.netname = "$qc_spikes"; + } + + if (self.spawnflags & WEAPON_ROCKET) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_rock1.bsp"); + setmodel (self, "maps/b_rock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_rock0.bsp"); + setmodel (self, "maps/b_rock0.bsp"); + self.aflag = 5; + } + self.weapon = 3; + self.netname = "$qc_rockets"; + } + + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/* +=============================================================================== + +KEYS + +=============================================================================== +*/ + +void() key_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (other.items & self.items) + return; + + sprint(other, "$qc_got_item", self.netname); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + other.items = other.items | self.items; + + if (!coop) + { + self.solid = SOLID_NOT; + self.model = string_null; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void() key_setsounds = +{ + if (world.worldtype == 0) + { + precache_sound ("misc/medkey.wav"); + self.noise = "misc/medkey.wav"; + } + if (world.worldtype == 1) + { + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + } + if (world.worldtype == 2) + { + precache_sound2 ("misc/basekey.wav"); + self.noise = "misc/basekey.wav"; + } +}; + +/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) +SILVER key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key1 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_s_key.mdl"); + setmodel (self, "progs/w_s_key.mdl"); + self.netname = "$qc_silver_key"; + } + else if (world.worldtype == 1) + { + precache_model ("progs/m_s_key.mdl"); + setmodel (self, "progs/m_s_key.mdl"); + self.netname = "$qc_silver_runekey"; + } + else if (world.worldtype == 2) + { + precache_model2 ("progs/b_s_key.mdl"); + setmodel (self, "progs/b_s_key.mdl"); + self.netname = "$qc_silver_keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY1; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) +GOLD key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key2 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_g_key.mdl"); + setmodel (self, "progs/w_g_key.mdl"); + self.netname = "$qc_gold_key"; + } + if (world.worldtype == 1) + { + precache_model ("progs/m_g_key.mdl"); + setmodel (self, "progs/m_g_key.mdl"); + self.netname = "$qc_gold_runekey"; + } + if (world.worldtype == 2) + { + precache_model2 ("progs/b_g_key.mdl"); + setmodel (self, "progs/b_g_key.mdl"); + self.netname = "$qc_gold_keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY2; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +END OF LEVEL RUNES + +=============================================================================== +*/ + +void() sigil_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + centerprint(other, "$qc_got_rune"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + self.model = string_null; + serverflags = serverflags | (self.spawnflags & 15); + self.classname = ""; // so rune doors won't find it + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_sigil (0 .5 .8) (-16 -16 -24) (16 16 32) E1 E2 E3 E4 +End of level sigil, pick up to end episode and return to jrstart. +*/ + +void() item_sigil = +{ + if (!self.spawnflags) + objerror ("no spawnflags"); + + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + + if (self.spawnflags & 1) + { + precache_model ("progs/end1.mdl"); + setmodel (self, "progs/end1.mdl"); + } + if (self.spawnflags & 2) + { + precache_model2 ("progs/end2.mdl"); + setmodel (self, "progs/end2.mdl"); + } + if (self.spawnflags & 4) + { + precache_model2 ("progs/end3.mdl"); + setmodel (self, "progs/end3.mdl"); + } + if (self.spawnflags & 8) + { + precache_model2 ("progs/end4.mdl"); + setmodel (self, "progs/end4.mdl"); + } + + self.touch = sigil_touch; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/* +=============================================================================== + +POWERUPS + +=============================================================================== +*/ + +void() powerup_touch; + + +void() powerup_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + sprint(other, "$qc_got_item", self.netname); + + if (deathmatch) + { + self.mdl = self.model; + + if ((self.classname == "item_artifact_invulnerability") || + (self.classname == "item_artifact_invisibility")) + self.nextthink = time + 60*5; + else + self.nextthink = time + 60; + + self.think = SUB_regen; + } + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | self.items; + self.model = string_null; + +// do the apropriate action + if (self.classname == "item_artifact_envirosuit") + { + other.rad_time = 1; + other.radsuit_finished = time + 30; + } + + if (self.classname == "item_artifact_invulnerability") + { + other.invincible_time = 1; + other.invincible_finished = time + 30; + } + + if (self.classname == "item_artifact_invisibility") + { + other.invisible_time = 1; + other.invisible_finished = time + 30; + } + + if (self.classname == "item_artifact_super_damage") + { + other.super_time = 1; + other.super_damage_finished = time + 30; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + + +/*QUAKED item_artifact_invulnerability (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invulnerable for 30 seconds +*/ +void() item_artifact_invulnerability = +{ + self.touch = powerup_touch; + + precache_model ("progs/invulner.mdl"); + precache_sound ("items/protect.wav"); + precache_sound ("items/protect2.wav"); + precache_sound ("items/protect3.wav"); + self.noise = "items/protect.wav"; + setmodel (self, "progs/invulner.mdl"); + self.netname = "$qc_pentagram_of_protection"; + self.items = IT_INVULNERABILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_artifact_envirosuit (0 .5 .8) (-16 -16 -24) (16 16 32) +Player takes no damage from water or slime for 30 seconds +*/ +void() item_artifact_envirosuit = +{ + self.touch = powerup_touch; + + precache_model ("progs/suit.mdl"); + precache_sound ("items/suit.wav"); + precache_sound ("items/suit2.wav"); + self.noise = "items/suit.wav"; + setmodel (self, "progs/suit.mdl"); + self.netname = "$qc_biosuit"; + self.items = IT_SUIT; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_invisibility (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invisible for 30 seconds +*/ +void() item_artifact_invisibility = +{ + self.touch = powerup_touch; + + precache_model ("progs/invisibl.mdl"); + precache_sound ("items/inv1.wav"); + precache_sound ("items/inv2.wav"); + precache_sound ("items/inv3.wav"); + self.noise = "items/inv1.wav"; + setmodel (self, "progs/invisibl.mdl"); + self.netname = "$qc_ring_of_shadows"; + self.items = IT_INVISIBILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_super_damage (0 .5 .8) (-16 -16 -24) (16 16 32) +The next attack from the player will do 4x damage +*/ +void() item_artifact_super_damage = +{ + self.touch = powerup_touch; + + precache_model ("progs/quaddama.mdl"); + precache_sound ("items/damage.wav"); + precache_sound ("items/damage2.wav"); + precache_sound ("items/damage3.wav"); + self.noise = "items/damage.wav"; + setmodel (self, "progs/quaddama.mdl"); + self.netname = "$qc_quad_damage"; + self.items = IT_QUAD; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +PLAYER BACKPACKS + +=============================================================================== +*/ + +void() BackpackTouch = +{ + local float best, old, new; + local entity stemp; + local float acount; + + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + acount = 0; + sprint(other, "$qc_backpack_got"); + + if (self.items) + { + if ((other.items & self.items) == 0) + { + acount = 1; + sprint(other, "{}", self.netname); + } + } + + // if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + // change weapons + other.ammo_shells = other.ammo_shells + self.ammo_shells; + other.ammo_nails = other.ammo_nails + self.ammo_nails; + other.ammo_rockets = other.ammo_rockets + self.ammo_rockets; + other.ammo_cells = other.ammo_cells + self.ammo_cells; + + new = self.items; + if (!new) + new = other.weapon; + + old = other.items; + other.items = other.items | self.items; + + bound_other_ammo (); + + if (self.ammo_shells) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_shells", ftos(self.ammo_shells)); + } + + if (self.ammo_nails) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_nails", ftos(self.ammo_nails)); + } + + if (self.ammo_rockets) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_rockets", ftos(self.ammo_rockets)); + } + if (self.ammo_cells) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_cells", ftos(self.ammo_cells)); + } + + sprint (other, "\n"); + + // backpack touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + remove(self); + self = other; + + // change to the weapon + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (self.flags & FL_INWATER) + { + if (new != IT_LIGHTNING) + { + Deathmatch_Weapon (old, new); + } + } + + else + { + Deathmatch_Weapon (old, new); + } + } + + W_SetCurrentAmmo (); +}; + +/* +=============== +DropBackpack +=============== +*/ +void() DropBackpack = +{ + local entity item; + + if (!(self.ammo_shells + self.ammo_nails + self.ammo_rockets + self.ammo_cells)) + return; // nothing in it + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.items = self.weapon; + + item.classname = "item_backpack"; // added for bots. + + if (item.items == IT_AXE) + item.netname = "$qc_axe"; + else if (item.items == IT_SHOTGUN) + item.netname = "$qc_shotgun"; + else if (item.items == IT_SUPER_SHOTGUN) + item.netname = "$qc_double_shotgun"; + else if (item.items == IT_NAILGUN) + item.netname = "$qc_nailgun"; + else if (item.items == IT_SUPER_NAILGUN) + item.netname = "$qc_super_nailgun"; + else if (item.items == IT_GRENADE_LAUNCHER) + item.netname = "$qc_grenade_launcher"; + else if (item.items == IT_ROCKET_LAUNCHER) + item.netname = "$qc_rocket_launcher"; + else if (item.items == IT_LIGHTNING) + item.netname = "$qc_thunderbolt"; + else if (item.items == IT_LASER_CANNON) + item.netname = "$qc_laser_cannon"; + else if (item.items == IT_PROXIMITY_GUN) + item.netname = "$qc_prox_gun"; + else if (item.items == IT_MJOLNIR) + item.netname = "$qc_mjolnir"; + else + item.netname = ""; + + item.ammo_shells = self.ammo_shells; + item.ammo_nails = self.ammo_nails; + item.ammo_rockets = self.ammo_rockets; + item.ammo_cells = self.ammo_cells; + + // include some ammo in the backpack if the weapon is being given + if (item.ammo_shells < 5 && (item.items == IT_SHOTGUN || item.items == IT_SUPER_SHOTGUN)) { + item.ammo_shells = 5; + } + + if (item.ammo_nails < 20 && (item.items == IT_NAILGUN || item.items == IT_SUPER_NAILGUN)) { + item.ammo_nails = 20; + } + + if (item.ammo_rockets < 5 && (item.items == IT_GRENADE_LAUNCHER || item.items == IT_ROCKET_LAUNCHER)) { + item.ammo_rockets = 5; + } + + if (item.ammo_cells < 15 && (item.items == IT_LIGHTNING || item.items == IT_LASER_CANNON || item.items == IT_MJOLNIR)) { + item.ammo_cells = 15; + } + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + setmodel (item, "progs/backpack.mdl"); + setsize (item, '-16 -16 0', '16 16 56'); + item.touch = BackpackTouch; + + item.nextthink = time + 120; // remove after 2 minutes + item.think = SUB_Remove; +}; diff --git a/quakec_hipnotic/jctest.qc b/quakec_hipnotic/jctest.qc new file mode 100644 index 0000000..89e0883 --- /dev/null +++ b/quakec_hipnotic/jctest.qc @@ -0,0 +1,34 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +void() jctrig = +{ +dprint ("here\n\n"); + lightstyle(0, "az"); +}; + +/*QUAKED trigger_jctest (.5 .5 .5) ? +*/ +void() trigger_jctest = +{ + setsize (self, self.mins, self.maxs); + self.solid = SOLID_EDGE; + self.touch = jctrig; +}; diff --git a/quakec_hipnotic/misc.qc b/quakec_hipnotic/misc.qc new file mode 100644 index 0000000..70e6c16 --- /dev/null +++ b/quakec_hipnotic/misc.qc @@ -0,0 +1,764 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void() info_null = +{ + remove(self); +}; + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void() info_notnull = +{ +}; + +//============================================================================ + +float START_OFF = 1; + +void() light_use = +{ + if (self.spawnflags & START_OFF) + { + lightstyle(self.style, "m"); + self.spawnflags = self.spawnflags - START_OFF; + } + else + { + lightstyle(self.style, "a"); + self.spawnflags = self.spawnflags + START_OFF; + } +}; + +/*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, it will toggle between on or off. +*/ +void() light = +{ + if (!self.targetname) + { // inert light + remove(self); + return; + } + + if (self.style >= 32) + { + self.use = light_use; + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } +}; + +/*QUAKED light_fluoro (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, it will toggle between on or off. +Makes steady fluorescent humming sound +*/ +void() light_fluoro = +{ + if (self.style >= 32) + { + self.use = light_use; + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } + + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_fluorospark (0 1 0) (-8 -8 -8) (8 8 8) +Non-displayed light. +Default light value is 300 +Default style is 10 +Makes sparking, broken fluorescent sound +*/ +void() light_fluorospark = +{ + if (!self.style) + self.style = 10; + + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_globe (0 1 0) (-8 -8 -8) (8 8 8) +Sphere globe light. +Default light value is 300 +Default style is 0 +*/ +void() light_globe = +{ + precache_model ("progs/s_light.spr"); + setmodel (self, "progs/s_light.spr"); + makestatic (self); +}; + +void() FireAmbient = +{ + precache_sound ("ambience/fire1.wav"); +// attenuate fast + ambientsound (self.origin, "ambience/fire1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_torch_small_walltorch (0 .5 0) (-10 -10 -20) (10 10 20) +Short wall torch +Default light value is 200 +Default style is 0 +*/ +void() light_torch_small_walltorch = +{ + precache_model ("progs/flame.mdl"); + setmodel (self, "progs/flame.mdl"); + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_large_yellow (0 1 0) (-10 -10 -12) (12 12 18) +Large yellow flame ball +*/ +void() light_flame_large_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + self.frame = 1; + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_yellow (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Small yellow flame ball +*/ +void() light_flame_small_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_white (0 1 0) (-10 -10 -40) (10 10 40) START_OFF +Small white flame ball +*/ +void() light_flame_small_white = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + FireAmbient (); + makestatic (self); +}; + +//============================================================================ + + +/*QUAKED misc_fireball (0 .5 .8) (-8 -8 -8) (8 8 8) +Lava Balls +*/ + +void() fire_fly; +void() fire_touch; +void() misc_fireball = +{ + + precache_model ("progs/lavaball.mdl"); + self.classname = "fireball"; + self.nextthink = time + (random() * 5); + self.think = fire_fly; + if (!self.speed) + self.speed == 1000; +}; + +void() fire_fly = +{ +local entity fireball; + + fireball = spawn(); + fireball.solid = SOLID_TRIGGER; + fireball.movetype = MOVETYPE_TOSS; + fireball.velocity = '0 0 1000'; + fireball.velocity_x = (random() * 100) - 50; + fireball.velocity_y = (random() * 100) - 50; + fireball.velocity_z = self.speed + (random() * 200); + fireball.classname = "fireball"; + setmodel (fireball, "progs/lavaball.mdl"); + setsize (fireball, '0 0 0', '0 0 0'); + setorigin (fireball, self.origin); + fireball.nextthink = time + 5; + fireball.think = SUB_Remove; + fireball.touch = fire_touch; + + self.nextthink = time + (random() * 5) + 3; + self.think = fire_fly; +}; + + +void() fire_touch = +{ + T_Damage (other, self, self, 20); + remove(self); +}; + +//============================================================================ + + +void() barrel_explode = +{ + self.takedamage = DAMAGE_NO; + self.classname = "explo_box"; + // did say self.owner + T_RadiusDamage (self, self, 160, world); + sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM); + particle (self.origin, '0 0 0', 75, 255); + + self.origin_z = self.origin_z + 32; + BecomeExplosion (); +}; + + + +/*QUAKED misc_explobox (0 .5 .8) (0 0 0) (32 32 64) +TESTING THING +*/ + +void() misc_explobox = +{ + local float oldz; + + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_NONE; + precache_model ("maps/b_explob.bsp"); + setmodel (self, "maps/b_explob.bsp"); + precache_sound ("weapons/r_exp3.wav"); + self.health = 20; + self.th_die = barrel_explode; + self.takedamage = DAMAGE_AIM; + + self.origin_z = self.origin_z + 2; + oldz = self.origin_z; + droptofloor(); + if (oldz - self.origin_z > 250) + { + dprint ("item fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + } +}; + + + + +/*QUAKED misc_explobox2 (0 .5 .8) (0 0 0) (32 32 64) +Smaller exploding box, REGISTERED ONLY +*/ + +void() misc_explobox2 = +{ + local float oldz; + + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_NONE; + precache_model2 ("maps/b_exbox2.bsp"); + setmodel (self, "maps/b_exbox2.bsp"); + precache_sound ("weapons/r_exp3.wav"); + self.health = 20; + self.th_die = barrel_explode; + self.takedamage = DAMAGE_AIM; + + self.origin_z = self.origin_z + 2; + oldz = self.origin_z; + droptofloor(); + if (oldz - self.origin_z > 250) + { + dprint ("item fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + } +}; + +//============================================================================ + + +void(vector org, vector vec) LaunchLaser; + +//MED 11/09/96 added lava ball and rocket +void() spikeshooter_use = +{ + local entity lavaball; + if (self.spawnflags & SPAWNFLAG_LASER) + { + if (!self.spawnflags & SPAWNFLAG_SILENT) + sound (self, CHAN_VOICE, "enforcer/enfire.wav", 1, ATTN_NORM); + LaunchLaser (self.origin, self.movedir); + newmis.spawnflags = self.spawnflags; + } + else if (self.spawnflags & SPAWNFLAG_LAVABALL) + { + if (!self.spawnflags & SPAWNFLAG_SILENT) + sound (self, CHAN_VOICE, "misc/spike.wav", 1, ATTN_NORM); + lavaball = spawn(); + lavaball.movetype = MOVETYPE_FLYMISSILE; + lavaball.solid = SOLID_BBOX; + lavaball.classname = "lavaball"; + // set lavaball speed + lavaball.velocity = self.movedir * 300; + lavaball.angles = vectoangles(lavaball.velocity); + lavaball.owner = self; + lavaball.touch = T_MissileTouch; + setmodel (lavaball, "progs/lavarock.mdl"); + setsize (lavaball, '-4 -4 -4', '4 4 4'); + setorigin (lavaball, self.origin); + lavaball.avelocity = '0 0 400'; + lavaball.nextthink = time + 5; + lavaball.think = SUB_Remove; + } + else if (self.spawnflags & SPAWNFLAG_ROCKET) + { + if (!self.spawnflags & SPAWNFLAG_SILENT) + sound (self, CHAN_VOICE, "weapons/sgun1.wav", 1, ATTN_NORM); + W_FireRocket(); + newmis.velocity = self.movedir*1000; + newmis.angles = vectoangles(newmis.velocity); + setorigin (newmis, self.origin + self.movedir*8); + } + else + { + if (!self.spawnflags & SPAWNFLAG_SILENT) + sound (self, CHAN_VOICE, "weapons/spike2.wav", 1, ATTN_NORM); + launch_spike (self.origin, self.movedir); + newmis.velocity = self.movedir * 500; + if (self.spawnflags & SPAWNFLAG_SUPERSPIKE) + newmis.touch = superspike_touch; + } +}; + +//MED 11/01/96 added state capability +void() shooter_think = +{ + if (self.state) + { + spikeshooter_use (); + } + self.nextthink = time + self.wait; +}; + + +/*QUAKED trap_spikeshooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser lavaball rocket silent +When triggered, fires a spike in the direction set in QuakeEd. +Laser is only for REGISTERED. +*/ + +//MED 11/01/96 commented out setmovedir +void() trap_spikeshooter = +{ + SetMovedir (); + self.use = spikeshooter_use; + if (self.spawnflags & SPAWNFLAG_LASER) + { + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + } + else if (self.spawnflags & SPAWNFLAG_LAVABALL) + { + precache_model ("progs/lavarock.mdl"); +// self.classname = "fireball"; + precache_sound2 ("misc/spike.wav"); + } + else if (self.spawnflags & SPAWNFLAG_ROCKET) + { + precache_model ("progs/missile.mdl"); + precache_sound ("weapons/sgun1.wav"); + } + else + precache_sound ("weapons/spike2.wav"); +}; + + +/*QUAKED trap_shooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser lavaball rocket silent +Continuously fires spikes. +"wait" time between spike (1.0 default) +"nextthink" delay before firing first spike, so multiple shooters can be stagered. +*/ +void() trap_shooter = +{ + trap_spikeshooter (); + + if (self.wait == 0) + self.wait = 1; +//MED 11/01/96 added state capability + self.state = 1; + self.nextthink = self.nextthink + self.wait + self.ltime; + self.think = shooter_think; +}; + +//MED 11/01/96 added new use function +void() trap_shooter_use = + { + self.state = 1 - self.state; + }; +//MED 11/01/96 added new function +/*QUAKED trap_switched_shooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser lavaball rocket silent +Continuously fires spikes. +"wait" time between spike (1.0 default) +"nextthink" delay before firing first spike, so multiple shooters can be stagered. +"state" 0 initially off, 1 initially on. (0 default) +*/ +void() trap_switched_shooter = + { + trap_spikeshooter (); + + if (self.wait == 0) + self.wait = 1; +//MED 11/01/96 added state capability + self.nextthink = self.nextthink + self.wait + self.ltime; + self.think = shooter_think; + self.use = trap_shooter_use; + }; + + + +/* +=============================================================================== + + +=============================================================================== +*/ + + +void() make_bubbles; +void() bubble_remove; +void() bubble_bob; + +/*QUAKED air_bubbles (0 .5 .8) (-8 -8 -8) (8 8 8) + +testing air bubbles +*/ + +void() air_bubbles = + +{ + if (deathmatch) + { + remove (self); + return; + } + precache_model ("progs/s_bubble.spr"); + self.nextthink = time + 1; + self.think = make_bubbles; +}; + +void() make_bubbles = +{ +local entity bubble; + + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + random() + 0.5; + self.think = make_bubbles; +}; + +void() bubble_split = +{ +local entity bubble; + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = self.velocity; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 1; + bubble.cnt = 10; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.frame = 1; + self.cnt = 10; + if (self.waterlevel != 3) + remove (self); +}; + +void() bubble_remove = +{ + if (other.classname == self.classname) + { +// dprint ("bump"); + return; + } + remove(self); +}; + +void() bubble_bob = +{ +local float rnd1, rnd2, rnd3; +local vector vtmp1, modi; + + self.cnt = self.cnt + 1; + if (self.cnt == 4) + bubble_split(); + if (self.cnt == 20) + remove(self); + + rnd1 = self.velocity_x + (-10 + (random() * 20)); + rnd2 = self.velocity_y + (-10 + (random() * 20)); + rnd3 = self.velocity_z + 10 + random() * 10; + + if (rnd1 > 10) + rnd1 = 5; + if (rnd1 < -10) + rnd1 = -5; + + if (rnd2 > 10) + rnd2 = 5; + if (rnd2 < -10) + rnd2 = -5; + + if (rnd3 < 10) + rnd3 = 15; + if (rnd3 > 30) + rnd3 = 25; + + self.velocity_x = rnd1; + self.velocity_y = rnd2; + self.velocity_z = rnd3; + + self.nextthink = time + 0.5; + self.think = bubble_bob; +}; + +/*~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~> +~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~*/ + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) + +Just for the debugging level. Don't use +*/ + +void() viewthing = + +{ + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + precache_model ("progs/player.mdl"); + setmodel (self, "progs/player.mdl"); +}; + + +/* +============================================================================== + +SIMPLE BMODELS + +============================================================================== +*/ + +void() func_wall_use = +{ // change to alternate textures + self.frame = 1 - self.frame; +}; + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibitted +*/ +void() func_wall = +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +void() func_illusionary = + +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + setmodel (self, self.model); + makestatic (); +}; + +/*QUAKED func_episodegate (0 .5 .8) ? E1 E2 E3 E4 +This bmodel will appear if the episode has allready been completed, so players can't reenter it. +*/ +void() func_episodegate = + +{ + if (!(serverflags & self.spawnflags)) + return; // can still enter episode + + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + +/*QUAKED func_bossgate (0 .5 .8) ? +This bmodel appears unless players have all of the episode sigils. +*/ +void() func_bossgate = + +{ + if ( (serverflags & 15) == 15) + return; // all episodes completed + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + +//============================================================================ +/*QUAKED ambient_suck_wind (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_suck_wind = +{ + precache_sound ("ambience/suck1.wav"); + ambientsound (self.origin, "ambience/suck1.wav", 1, ATTN_STATIC); +}; + +/*QUAKED ambient_drone (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drone = +{ + precache_sound ("ambience/drone6.wav"); + ambientsound (self.origin, "ambience/drone6.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED ambient_flouro_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_flouro_buzz = +{ + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 1, ATTN_STATIC); +}; +/*QUAKED ambient_drip (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drip = +{ + precache_sound ("ambience/drip1.wav"); + ambientsound (self.origin, "ambience/drip1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_comp_hum (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_comp_hum = +{ + precache_sound ("ambience/comp1.wav"); + ambientsound (self.origin, "ambience/comp1.wav", 1, ATTN_STATIC); +}; +/*QUAKED ambient_thunder (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_thunder = +{ + precache_sound ("ambience/thunder1.wav"); + ambientsound (self.origin, "ambience/thunder1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_light_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_light_buzz = +{ + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_swamp1 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp1 = +{ + precache_sound ("ambience/swamp1.wav"); + ambientsound (self.origin, "ambience/swamp1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_swamp2 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp2 = +{ + precache_sound ("ambience/swamp2.wav"); + ambientsound (self.origin, "ambience/swamp2.wav", 0.5, ATTN_STATIC); +}; + +//============================================================================ + +void() noise_think = +{ + self.nextthink = time + 0.5; + sound (self, 1, "enforcer/enfire.wav", 1, ATTN_NORM); + sound (self, 2, "enforcer/enfstop.wav", 1, ATTN_NORM); + sound (self, 3, "enforcer/sight1.wav", 1, ATTN_NORM); + sound (self, 4, "enforcer/sight2.wav", 1, ATTN_NORM); + sound (self, 5, "enforcer/sight3.wav", 1, ATTN_NORM); + sound (self, 6, "enforcer/sight4.wav", 1, ATTN_NORM); + sound (self, 7, "enforcer/pain1.wav", 1, ATTN_NORM); +}; + +/*QUAKED misc_noisemaker (1 0.5 0) (-10 -10 -10) (10 10 10) + +For optimzation testing, starts a lot of sounds. +*/ + +void() misc_noisemaker = + +{ + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/idle1.wav"); + + self.nextthink = time + 0.1 + random(); + self.think = noise_think; +}; diff --git a/quakec_hipnotic/models.qc b/quakec_hipnotic/models.qc new file mode 100644 index 0000000..7088e46 --- /dev/null +++ b/quakec_hipnotic/models.qc @@ -0,0 +1,604 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* +=============================================================================== + +WORLD WEAPONS + +=============================================================================== +*/ + +$modelname g_shot +$cd id1/models/g_shot +$origin 0 0 -24 +$flags 8 // client side rotate +$base base +$skin skin +$frame shot1 + + +$modelname g_nail +$cd id1/models/g_nail +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + + +$modelname g_nail2 +$cd id1/models/g_nail2 +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot2 + + +$modelname g_rock +$cd id1/models/g_rock +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + + +$modelname g_rock2 +$cd id1/models/g_rock2 +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + +$modelname g_light +$cd id1/models/g_light +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + +/* +=============================================================================== + +VIEW WEAPONS + +=============================================================================== +*/ + +$modelname v_axe +$cd id1/models/v_axe +$origin 0 5 54 +$base base +$skin skin +$frame frame1 frame2 frame3 frame4 frame5 frame6 frame7 frame8 frame9 + + +$modelname v_shot +$cd id1/models/v_shot +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 + + +$modelname v_shot2 +$cd id1/models/v_shot2 +$origin 0 0 56 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 + + +$modelname v_rock2 +$cd id1/models/v_rock2 +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot6 + + +$modelname v_rock +$cd id1/models/v_rock +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 + + +$modelname v_nail2 +$cd id1/models/v_nail2 +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 shot8 shot9 + + +$modelname v_nail +$cd id1/models/v_nail +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 shot8 shot9 + +$modelname v_light +$cd id1/models/v_light +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 + + +/* +=============================================================================== + +ITEMS + +=============================================================================== +*/ + +$modelname w_g_key +$cd id1/models/w_g_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname w_s_key +$cd id1/models/w_s_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname m_g_key +$cd id1/models/m_g_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname m_s_key +$cd id1/models/m_s_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname b_g_key +$cd id1/models/b_g_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname b_s_key +$cd id1/models/b_s_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + + +$modelname quaddama +$cd id1/models/quaddama +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname invisibl +$cd id1/models/invisibl +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname invulner +$flags 8 // client side rotate +$cd id1/models/invulner +$base base +$skin skin +$frame frame1 + +//modelname jetpack +//cd id1/models/jetpack +//flags 8 // client side rotate +//base base +//skin skin +//frame frame1 + +$modelname cube +$cd id1/models/cube +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname suit +$cd id1/models/suit +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname boots +$cd id1/models/boots +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end1 +$cd id1/models/end1 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end2 +$cd id1/models/end2 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end3 +$cd id1/models/end3 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end4 +$cd id1/models/end4 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + + +/* +=============================================================================== + +GIBS + +=============================================================================== +*/ + +$modelname gib1 +$cd id1/models/gib1 +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + + +// torso +$modelname gib2 +$cd id1/models/gib2 +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname gib3 +$cd id1/models/gib3 +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + + +// heads + +$modelname h_player +$cd id1/models/h_player +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_dog +$cd id1/models/h_dog +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_mega +$cd id1/models/h_mega +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_guard +$cd id1/models/h_guard +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_wizard +$cd id1/models/h_wizard +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_knight +$cd id1/models/h_knight +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_hellkn +$cd id1/models/h_hellkn +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_zombie +$cd id1/models/h_zombie +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_shams +$cd id1/models/h_shams +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_shal +$cd id1/models/h_shal +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_ogre +$cd id1/models/h_ogre +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_demon +$cd id1/models/h_demon +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +/* +=============================================================================== + +MISC + +=============================================================================== +*/ + +$modelname armor +$cd id1/models/armor +$flags 8 // client side rotate +$origin 0 0 -8 +$base base +$skin skin +$skin skin2 +$skin skin3 +$frame armor + +$modelname s_light // shambler lightning ready +$cd id1/models/s_light +$origin 0 0 24 +$base base +$skin skin +$frame frame1 frame2 frame3 + +$modelname bolt3 // lightning towar bolts +$cd id1/models/bolt2 +$origin 0 0 0 +$base base +$scale 4 +$skin skin +$frame light + +$modelname bolt2 +$cd id1/models/bolt2 +$origin 0 0 0 +$base base +$skin skin +$frame light + +$modelname bolt +$cd id1/models/bolt +$origin 0 0 0 +$base light +$skin light +$frame light + +$modelname laser +$cd id1/models/laser +$base base +$skin skin +$scale 2 +$frame frame1 + +$modelname flame // with torch +$cd id1/models/flame +$origin 0 0 12 +$base base +$skin skin +$framegroupstart +$frame flame1 0.1 +$frame flame2 0.1 +$frame flame3 0.1 +$frame flame4 0.1 +$frame flame5 0.1 +$frame flame6 0.1 +$framegroupend + +$modelname flame2 // standing flame, no torch +$cd id1/models/flame2 +$origin 0 0 12 +$base base +$skin skin +$framegroupstart +$frame flame1 0.1 +$frame flame2 0.1 +$frame flame3 0.1 +$frame flame4 0.1 +$frame flame5 0.1 +$frame flame6 0.1 +$framegroupend +$framegroupstart +$frame flameb1 +$frame flameb2 +$frame flameb3 +$frame flameb4 +$frame flameb5 +$frame flameb6 +$frame flameb7 +$frame flameb8 +$frame flameb9 +$frame flameb10 +$frame flameb11 +$framegroupend + +$modelname zom_gib +$cd id1/models/zom_gib +$flags 32 // EF_ZOMGIB +$base base +$skin skin +$frame frame1 + +$modelname eyes +$cd id1/models/eyes +$origin 0 0 -24 +$base base +$skin skin +$frame frame1 + +$modelname spike +$cd id1/models/spike +$origin 0 0 0 +$base spike +$skin skin +$frame spike + +$modelname s_spike +$cd id1/models/s_spike +$origin 0 0 0 +$base spike +$skin skin +$frame spike + +$modelname v_spike +$cd id1/models/v_spike +$flags 128 // EF_TRACER3 +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname w_spike +$cd id1/models/w_spike +$flags 16 // EF_TRACER +$origin 0 0 0 +$base base +$skin skin +$framegroupstart +$frame frame1 0.1 +$frame frame2 0.1 +$frame frame3 0.1 +$frame frame4 0.1 +$framegroupend + +$modelname k_spike +$cd id1/models/k_spike +$flags 64 // EF_TRACER2 +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname backpack +$cd id1/models/backpack +$flags 8 // EF_ROTATE +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname grenade +$cd id1/models/grenade2 +$flags 2 // EF_GRENADE +$origin 0 0 0 +$base base +$skin skin +$frame grenade + +$modelname missile +$cd id1/models/missile +$flags 1 // EF_ROCKET +$origin 0 0 0 +$base base +$skin skin +$frame missile + +$modelname lavaball +$cd id1/models/lavaball +$flags 1 // EF_ROCKET +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname teleport +$cd id1/models/teleport +$origin 0 0 24 +$base base +$skin skin +$frame frame1 + diff --git a/quakec_hipnotic/monsters.qc b/quakec_hipnotic/monsters.qc new file mode 100644 index 0000000..5550572 --- /dev/null +++ b/quakec_hipnotic/monsters.qc @@ -0,0 +1,260 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* ALL MONSTERS SHOULD BE 1 0 0 IN COLOR */ + +// name =[framenum, nexttime, nextthink] {code} +// expands to: +// name () +// { +// self.frame=framenum; +// self.nextthink = time + nexttime; +// self.think = nextthink +// +// }; + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void() monster_use = +{ + if (self.enemy) + return; + if (self.health <= 0) + return; + if (activator.items & IT_INVISIBILITY) + return; + if (activator.flags & FL_NOTARGET) + return; + if (activator.classname != "player") + return; + +// delay reaction so if the monster is teleported, its sound is still +// heard + self.enemy = activator; + self.nextthink = time + 0.1; + self.think = FoundTarget; +}; + +/* +================ +monster_death_use + +When a mosnter dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void() monster_death_use = +{ + local entity ent, otemp, stemp; + +// fall to ground + if (self.flags & FL_FLY) + self.flags = self.flags - FL_FLY; + if (self.flags & FL_SWIM) + self.flags = self.flags - FL_SWIM; + + if (!self.target) + return; + + activator = self.enemy; + SUB_UseTargets (); +}; + + +//============================================================================ + +void() walkmonster_start_go = +{ +local string stemp; +local entity etemp; + + self.origin_z = self.origin_z + 1; // raise off floor a bit + droptofloor(); + + if (!walkmove(0,0)) + { + dprint ("walkmonster in wall at: "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 20; + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + + self.flags = self.flags | FL_MONSTER; + + if (self.target) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } +// this used to be an objerror + if (self.movetarget.classname == "path_corner") + self.th_walk (); + else + self.pausetime = 99999999; + self.th_stand (); + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } + +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; +}; + + +void() walkmonster_start = +{ +// delay drop to floor to make sure all doors have been spawned +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.flags = self.flags | FL_MONSTER; + self.think = walkmonster_start_go; + total_monsters = total_monsters + 1; +}; + + + +void() flymonster_start_go = +{ + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 10; + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + + self.flags = self.flags | FL_FLY; + self.flags = self.flags | FL_MONSTER; + + if (!walkmove(0,0)) + { + dprint ("flymonster in wall at: "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + if (self.target) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } +// this used to be an objerror + if (self.movetarget.classname == "path_corner") + self.th_walk (); + else + self.pausetime = 99999999; + self.th_stand (); + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } +}; + +void() flymonster_start = +{ +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.flags = self.flags | FL_MONSTER; + self.think = flymonster_start_go; + total_monsters = total_monsters + 1; +}; + + +void() swimmonster_start_go = +{ + if (deathmatch) + { + remove(self); + return; + } + + self.takedamage = DAMAGE_AIM; +//MED +// total_monsters = total_monsters + 1; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 10; + self.view_ofs = '0 0 10'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + + self.flags = self.flags | FL_SWIM; + self.flags = self.flags | FL_MONSTER; + + if (self.target) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } +// this used to be an objerror + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + self.th_walk (); + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } + +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; +}; + +void() swimmonster_start = +{ +// spread think times so they don't all happen at same time + self.flags = self.flags | FL_MONSTER; + self.nextthink = self.nextthink + random()*0.5; + self.think = swimmonster_start_go; + total_monsters = total_monsters + 1; +}; diff --git a/quakec_hipnotic/monsters/chthon.qc b/quakec_hipnotic/monsters/chthon.qc new file mode 100644 index 0000000..0245256 --- /dev/null +++ b/quakec_hipnotic/monsters/chthon.qc @@ -0,0 +1,404 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BOSS-ONE + +============================================================================== +*/ +$cd id1/models/boss1 +$origin 0 0 -15 +$base base +$skin skin +$scale 5 + +$frame rise1 rise2 rise3 rise4 rise5 rise6 rise7 rise8 rise9 rise10 +$frame rise11 rise12 rise13 rise14 rise15 rise16 rise17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 +$frame walk9 walk10 walk11 walk12 walk13 walk14 walk15 +$frame walk16 walk17 walk18 walk19 walk20 walk21 walk22 +$frame walk23 walk24 walk25 walk26 walk27 walk28 walk29 walk30 walk31 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 attack12 attack13 attack14 attack15 +$frame attack16 attack17 attack18 attack19 attack20 attack21 attack22 +$frame attack23 + +$frame shocka1 shocka2 shocka3 shocka4 shocka5 shocka6 shocka7 shocka8 +$frame shocka9 shocka10 + +$frame shockb1 shockb2 shockb3 shockb4 shockb5 shockb6 + +$frame shockc1 shockc2 shockc3 shockc4 shockc5 shockc6 shockc7 shockc8 +$frame shockc9 shockc10 + + +void(vector p) boss_missile; + +void() boss_face = +{ + +// go for another player if multi player + if (self.enemy.health <= 0 || random() < 0.02) + { + self.enemy = find(self.enemy, classname, "player"); + if (!self.enemy) + self.enemy = find(self.enemy, classname, "player"); + } + ai_face(); +}; + +void() boss_rise1 =[ $rise1, boss_rise2 ] { +sound (self, CHAN_WEAPON, "boss1/out1.wav", 1, ATTN_NORM); +}; +void() boss_rise2 =[ $rise2, boss_rise3 ] { +sound (self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); +}; +void() boss_rise3 =[ $rise3, boss_rise4 ] {}; +void() boss_rise4 =[ $rise4, boss_rise5 ] {}; +void() boss_rise5 =[ $rise5, boss_rise6 ] {}; +void() boss_rise6 =[ $rise6, boss_rise7 ] {}; +void() boss_rise7 =[ $rise7, boss_rise8 ] {}; +void() boss_rise8 =[ $rise8, boss_rise9 ] {}; +void() boss_rise9 =[ $rise9, boss_rise10 ] {}; +void() boss_rise10 =[ $rise10, boss_rise11 ] {}; +void() boss_rise11 =[ $rise11, boss_rise12 ] {}; +void() boss_rise12 =[ $rise12, boss_rise13 ] {}; +void() boss_rise13 =[ $rise13, boss_rise14 ] {}; +void() boss_rise14 =[ $rise14, boss_rise15 ] {}; +void() boss_rise15 =[ $rise15, boss_rise16 ] {}; +void() boss_rise16 =[ $rise16, boss_rise17 ] {}; +void() boss_rise17 =[ $rise17, boss_missile1 ] {}; + +void() boss_idle1 =[ $walk1, boss_idle2 ] +{ +// look for other players +}; +void() boss_idle2 =[ $walk2, boss_idle3 ] {boss_face();}; +void() boss_idle3 =[ $walk3, boss_idle4 ] {boss_face();}; +void() boss_idle4 =[ $walk4, boss_idle5 ] {boss_face();}; +void() boss_idle5 =[ $walk5, boss_idle6 ] {boss_face();}; +void() boss_idle6 =[ $walk6, boss_idle7 ] {boss_face();}; +void() boss_idle7 =[ $walk7, boss_idle8 ] {boss_face();}; +void() boss_idle8 =[ $walk8, boss_idle9 ] {boss_face();}; +void() boss_idle9 =[ $walk9, boss_idle10 ] {boss_face();}; +void() boss_idle10 =[ $walk10, boss_idle11 ] {boss_face();}; +void() boss_idle11 =[ $walk11, boss_idle12 ] {boss_face();}; +void() boss_idle12 =[ $walk12, boss_idle13 ] {boss_face();}; +void() boss_idle13 =[ $walk13, boss_idle14 ] {boss_face();}; +void() boss_idle14 =[ $walk14, boss_idle15 ] {boss_face();}; +void() boss_idle15 =[ $walk15, boss_idle16 ] {boss_face();}; +void() boss_idle16 =[ $walk16, boss_idle17 ] {boss_face();}; +void() boss_idle17 =[ $walk17, boss_idle18 ] {boss_face();}; +void() boss_idle18 =[ $walk18, boss_idle19 ] {boss_face();}; +void() boss_idle19 =[ $walk19, boss_idle20 ] {boss_face();}; +void() boss_idle20 =[ $walk20, boss_idle21 ] {boss_face();}; +void() boss_idle21 =[ $walk21, boss_idle22 ] {boss_face();}; +void() boss_idle22 =[ $walk22, boss_idle23 ] {boss_face();}; +void() boss_idle23 =[ $walk23, boss_idle24 ] {boss_face();}; +void() boss_idle24 =[ $walk24, boss_idle25 ] {boss_face();}; +void() boss_idle25 =[ $walk25, boss_idle26 ] {boss_face();}; +void() boss_idle26 =[ $walk26, boss_idle27 ] {boss_face();}; +void() boss_idle27 =[ $walk27, boss_idle28 ] {boss_face();}; +void() boss_idle28 =[ $walk28, boss_idle29 ] {boss_face();}; +void() boss_idle29 =[ $walk29, boss_idle30 ] {boss_face();}; +void() boss_idle30 =[ $walk30, boss_idle31 ] {boss_face();}; +void() boss_idle31 =[ $walk31, boss_idle1 ] {boss_face();}; + +void() boss_missile1 =[ $attack1, boss_missile2 ] {boss_face();}; +void() boss_missile2 =[ $attack2, boss_missile3 ] {boss_face();}; +void() boss_missile3 =[ $attack3, boss_missile4 ] {boss_face();}; +void() boss_missile4 =[ $attack4, boss_missile5 ] {boss_face();}; +void() boss_missile5 =[ $attack5, boss_missile6 ] {boss_face();}; +void() boss_missile6 =[ $attack6, boss_missile7 ] {boss_face();}; +void() boss_missile7 =[ $attack7, boss_missile8 ] {boss_face();}; +void() boss_missile8 =[ $attack8, boss_missile9 ] {boss_face();}; +void() boss_missile9 =[ $attack9, boss_missile10 ] {boss_missile('100 100 200');}; +void() boss_missile10 =[ $attack10, boss_missile11 ] {boss_face();}; +void() boss_missile11 =[ $attack11, boss_missile12 ] {boss_face();}; +void() boss_missile12 =[ $attack12, boss_missile13 ] {boss_face();}; +void() boss_missile13 =[ $attack13, boss_missile14 ] {boss_face();}; +void() boss_missile14 =[ $attack14, boss_missile15 ] {boss_face();}; +void() boss_missile15 =[ $attack15, boss_missile16 ] {boss_face();}; +void() boss_missile16 =[ $attack16, boss_missile17 ] {boss_face();}; +void() boss_missile17 =[ $attack17, boss_missile18 ] {boss_face();}; +void() boss_missile18 =[ $attack18, boss_missile19 ] {boss_face();}; +void() boss_missile19 =[ $attack19, boss_missile20 ] {boss_face();}; +void() boss_missile20 =[ $attack20, boss_missile21 ] {boss_missile('100 -100 200');}; +void() boss_missile21 =[ $attack21, boss_missile22 ] {boss_face();}; +void() boss_missile22 =[ $attack22, boss_missile23 ] {boss_face();}; +void() boss_missile23 =[ $attack23, boss_missile1 ] {boss_face();}; + +void() boss_shocka1 =[ $shocka1, boss_shocka2 ] {}; +void() boss_shocka2 =[ $shocka2, boss_shocka3 ] {}; +void() boss_shocka3 =[ $shocka3, boss_shocka4 ] {}; +void() boss_shocka4 =[ $shocka4, boss_shocka5 ] {}; +void() boss_shocka5 =[ $shocka5, boss_shocka6 ] {}; +void() boss_shocka6 =[ $shocka6, boss_shocka7 ] {}; +void() boss_shocka7 =[ $shocka7, boss_shocka8 ] {}; +void() boss_shocka8 =[ $shocka8, boss_shocka9 ] {}; +void() boss_shocka9 =[ $shocka9, boss_shocka10 ] {}; +void() boss_shocka10 =[ $shocka10, boss_missile1 ] {}; + +void() boss_shockb1 =[ $shockb1, boss_shockb2 ] {}; +void() boss_shockb2 =[ $shockb2, boss_shockb3 ] {}; +void() boss_shockb3 =[ $shockb3, boss_shockb4 ] {}; +void() boss_shockb4 =[ $shockb4, boss_shockb5 ] {}; +void() boss_shockb5 =[ $shockb5, boss_shockb6 ] {}; +void() boss_shockb6 =[ $shockb6, boss_shockb7 ] {}; +void() boss_shockb7 =[ $shockb1, boss_shockb8 ] {}; +void() boss_shockb8 =[ $shockb2, boss_shockb9 ] {}; +void() boss_shockb9 =[ $shockb3, boss_shockb10 ] {}; +void() boss_shockb10 =[ $shockb4, boss_missile1 ] {}; + +void() boss_shockc1 =[ $shockc1, boss_shockc2 ] {}; +void() boss_shockc2 =[ $shockc2, boss_shockc3 ] {}; +void() boss_shockc3 =[ $shockc3, boss_shockc4 ] {}; +void() boss_shockc4 =[ $shockc4, boss_shockc5 ] {}; +void() boss_shockc5 =[ $shockc5, boss_shockc6 ] {}; +void() boss_shockc6 =[ $shockc6, boss_shockc7 ] {}; +void() boss_shockc7 =[ $shockc7, boss_shockc8 ] {}; +void() boss_shockc8 =[ $shockc8, boss_shockc9 ] {}; +void() boss_shockc9 =[ $shockc9, boss_shockc10 ] {}; +void() boss_shockc10 =[ $shockc10, boss_death1 ] {}; + +void() boss_death1 = [$death1, boss_death2] { +sound (self, CHAN_VOICE, "boss1/death.wav", 1, ATTN_NORM); +}; +void() boss_death2 = [$death2, boss_death3] {}; +void() boss_death3 = [$death3, boss_death4] {}; +void() boss_death4 = [$death4, boss_death5] {}; +void() boss_death5 = [$death5, boss_death6] {}; +void() boss_death6 = [$death6, boss_death7] {}; +void() boss_death7 = [$death7, boss_death8] {}; +void() boss_death8 = [$death8, boss_death9] {}; +void() boss_death9 = [$death9, boss_death10] +{ + sound (self, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); +}; + +void() boss_death10 = [$death9, boss_death10] +{ + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); // FIXME: reliable broadcast + SUB_UseTargets (); + remove (self); +}; + +void(vector p) boss_missile = +{ + local vector offang; + local vector org, vec, d; + local float t; + + offang = vectoangles (self.enemy.origin - self.origin); + makevectors (offang); + + org = self.origin + p_x*v_forward + p_y*v_right + p_z*'0 0 1'; + +// lead the player on hard mode + if (skill > 1) + { + t = vlen(self.enemy.origin - org) / 300; + vec = self.enemy.velocity; + vec_z = 0; + d = self.enemy.origin + t * vec; + } + else + { + d = self.enemy.origin; + } + + vec = normalize (d - org); + + launch_spike (org, vec); + setmodel (newmis, "progs/lavaball.mdl"); + newmis.avelocity = '200 100 300'; + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.touch = T_MissileTouch; // rocket explosion + sound (self, CHAN_WEAPON, "boss1/throw.wav", 1, ATTN_NORM); + +// check for dead enemy + if (self.enemy.health <= 0) + boss_idle1 (); +}; + + +void() boss_awake = +{ + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + self.takedamage = DAMAGE_NO; + + setmodel (self, "progs/boss.mdl"); + setsize (self, '-128 -128 -24', '128 128 256'); + + if (skill == 0) + self.health = 1; + else + self.health = 3; + + self.enemy = activator; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.yaw_speed = 20; + boss_rise1 (); +}; + + +/*QUAKED monster_boss (1 0 0) (-128 -128 -24) (128 128 256) +*/ +void() monster_boss = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/boss.mdl"); + precache_model ("progs/lavaball.mdl"); + + precache_sound ("weapons/rocket1i.wav"); + precache_sound ("boss1/out1.wav"); + precache_sound ("boss1/sight1.wav"); + precache_sound ("misc/power.wav"); + precache_sound ("boss1/throw.wav"); + precache_sound ("boss1/pain.wav"); + precache_sound ("boss1/death.wav"); + + total_monsters = total_monsters + 1; + + self.use = boss_awake; +}; + +//=========================================================================== + +entity le1, le2; +float lightning_end; + +void() lightning_fire = +{ + local vector p1, p2; + + if (time >= lightning_end) + { // done here, put the terminals back up + self = le1; + door_go_down (); + self = le2; + door_go_down (); + return; + } + + p1 = (le1.mins + le1.maxs) * 0.5; + p1_z = le1.absmin_z - 16; + + p2 = (le2.mins + le2.maxs) * 0.5; + p2_z = le2.absmin_z - 16; + + // compensate for length of bolt + p2 = p2 - normalize(p2-p1)*100; + + self.nextthink = time + 0.1; + self.think = lightning_fire; + + WriteByte (MSG_ALL, SVC_TEMPENTITY); + WriteByte (MSG_ALL, TE_LIGHTNING3); + WriteEntity (MSG_ALL, world); + WriteCoord (MSG_ALL, p1_x); + WriteCoord (MSG_ALL, p1_y); + WriteCoord (MSG_ALL, p1_z); + WriteCoord (MSG_ALL, p2_x); + WriteCoord (MSG_ALL, p2_y); + WriteCoord (MSG_ALL, p2_z); +}; + +void() lightning_use = +{ + if (lightning_end >= time + 1) + return; + + le1 = find( world, target, "lightning"); + le2 = find( le1, target, "lightning"); + if (!le1 || !le2) + { + dprint ("missing lightning targets\n"); + return; + } + + if ( + (le1.state != STATE_TOP && le1.state != STATE_BOTTOM) + || (le2.state != STATE_TOP && le2.state != STATE_BOTTOM) + || (le1.state != le2.state) ) + { +// dprint ("not aligned\n"); + return; + } + +// don't let the electrodes go back up until the bolt is done + le1.nextthink = -1; + le2.nextthink = -1; + lightning_end = time + 1; + + sound (self, CHAN_VOICE, "misc/power.wav", 1, ATTN_NORM); + lightning_fire (); + +// advance the boss pain if down + self = find (world, classname, "monster_boss"); + if (!self) + return; + self.enemy = activator; + if (le1.state == STATE_TOP && self.health > 0) + { + sound (self, CHAN_VOICE, "boss1/pain.wav", 1, ATTN_NORM); + self.health = self.health - 1; + if (self.health >= 2) + boss_shocka1(); + else if (self.health == 1) + boss_shockb1(); + else if (self.health == 0) + boss_shockc1(); + } +}; + + +/*QUAKED event_lightning (0 1 1) (-16 -16 -16) (16 16 16) +Just for boss level. +*/ +void() event_lightning = +{ + self.use = lightning_use; +}; + + diff --git a/quakec_hipnotic/monsters/enforcer.qc b/quakec_hipnotic/monsters/enforcer.qc new file mode 100644 index 0000000..a3bfb54 --- /dev/null +++ b/quakec_hipnotic/monsters/enforcer.qc @@ -0,0 +1,375 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd id1/models/enforcer +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 death12 death13 death14 + +$frame fdeath1 fdeath2 fdeath3 fdeath4 fdeath5 fdeath6 fdeath7 fdeath8 +$frame fdeath9 fdeath10 fdeath11 + +$frame paina1 paina2 paina3 paina4 + +$frame painb1 painb2 painb3 painb4 painb5 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 +$frame paind9 paind10 paind11 paind12 paind13 paind14 paind15 paind16 +$frame paind17 paind18 paind19 + + +void() Laser_Touch = +{ + local vector org; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +//MED 01/16/97 added silent command + + if (!(self.spawnflags & SPAWNFLAG_SILENT)) + sound (self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); + org = self.origin - 8*normalize(self.velocity); + + if (other.health) + { + SpawnBlood (org, self.velocity*0.2, 15); + T_Damage (other, self, self.owner, 15); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } + + remove(self); +}; + +void(vector org, vector vec) LaunchLaser = +{ + local vector vec; + + if (self.classname == "monster_enforcer") + sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM); + + vec = normalize(vec); + + newmis = spawn(); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLY; + newmis.solid = SOLID_BBOX; + newmis.effects = EF_DIMLIGHT; + + setmodel (newmis, "progs/laser.mdl"); + setsize (newmis, '0 0 0', '0 0 0'); + + setorigin (newmis, org); + + newmis.velocity = vec * 600; + newmis.angles = vectoangles(newmis.velocity); + + newmis.nextthink = time + 5; + newmis.think = SUB_Remove; + newmis.touch = Laser_Touch; +}; + + + +void() enforcer_fire = +{ + local vector org; + + self.effects = self.effects | EF_MUZZLEFLASH; + makevectors (self.angles); + + org = self.origin + v_forward * 30 + v_right * 8.5 + '0 0 16'; + + LaunchLaser(org, self.enemy.origin - self.origin); +}; + +//============================================================================ + +void() enf_stand1 =[ $stand1, enf_stand2 ] {ai_stand();}; +void() enf_stand2 =[ $stand2, enf_stand3 ] {ai_stand();}; +void() enf_stand3 =[ $stand3, enf_stand4 ] {ai_stand();}; +void() enf_stand4 =[ $stand4, enf_stand5 ] {ai_stand();}; +void() enf_stand5 =[ $stand5, enf_stand6 ] {ai_stand();}; +void() enf_stand6 =[ $stand6, enf_stand7 ] {ai_stand();}; +void() enf_stand7 =[ $stand7, enf_stand1 ] {ai_stand();}; + +void() enf_walk1 =[ $walk1 , enf_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); +ai_walk(2);}; +void() enf_walk2 =[ $walk2 , enf_walk3 ] {ai_walk(4);}; +void() enf_walk3 =[ $walk3 , enf_walk4 ] {ai_walk(4);}; +void() enf_walk4 =[ $walk4 , enf_walk5 ] {ai_walk(3);}; +void() enf_walk5 =[ $walk5 , enf_walk6 ] {ai_walk(1);}; +void() enf_walk6 =[ $walk6 , enf_walk7 ] {ai_walk(2);}; +void() enf_walk7 =[ $walk7 , enf_walk8 ] {ai_walk(2);}; +void() enf_walk8 =[ $walk8 , enf_walk9 ] {ai_walk(1);}; +void() enf_walk9 =[ $walk9 , enf_walk10 ] {ai_walk(2);}; +void() enf_walk10 =[ $walk10, enf_walk11 ] {ai_walk(4);}; +void() enf_walk11 =[ $walk11, enf_walk12 ] {ai_walk(4);}; +void() enf_walk12 =[ $walk12, enf_walk13 ] {ai_walk(1);}; +void() enf_walk13 =[ $walk13, enf_walk14 ] {ai_walk(2);}; +void() enf_walk14 =[ $walk14, enf_walk15 ] {ai_walk(3);}; +void() enf_walk15 =[ $walk15, enf_walk16 ] {ai_walk(4);}; +void() enf_walk16 =[ $walk16, enf_walk1 ] {ai_walk(2);}; + +void() enf_run1 =[ $run1 , enf_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); +ai_run(18);}; +void() enf_run2 =[ $run2 , enf_run3 ] {ai_run(14);}; +void() enf_run3 =[ $run3 , enf_run4 ] {ai_run(7);}; +void() enf_run4 =[ $run4 , enf_run5 ] {ai_run(12);}; +void() enf_run5 =[ $run5 , enf_run6 ] {ai_run(14);}; +void() enf_run6 =[ $run6 , enf_run7 ] {ai_run(14);}; +void() enf_run7 =[ $run7 , enf_run8 ] {ai_run(7);}; +void() enf_run8 =[ $run8 , enf_run1 ] {ai_run(11);}; + +void() enf_atk1 =[ $attack1, enf_atk2 ] {ai_face();}; +void() enf_atk2 =[ $attack2, enf_atk3 ] {ai_face();}; +void() enf_atk3 =[ $attack3, enf_atk4 ] {ai_face();}; +void() enf_atk4 =[ $attack4, enf_atk5 ] {ai_face();}; +void() enf_atk5 =[ $attack5, enf_atk6 ] {ai_face();}; +void() enf_atk6 =[ $attack6, enf_atk7 ] {enforcer_fire();}; +void() enf_atk7 =[ $attack7, enf_atk8 ] {ai_face();}; +void() enf_atk8 =[ $attack8, enf_atk9 ] {ai_face();}; +void() enf_atk9 =[ $attack5, enf_atk10 ] {ai_face();}; +void() enf_atk10 =[ $attack6, enf_atk11 ] {enforcer_fire();}; +void() enf_atk11 =[ $attack7, enf_atk12 ] {ai_face();}; +void() enf_atk12 =[ $attack8, enf_atk13 ] {ai_face();}; +void() enf_atk13 =[ $attack9, enf_atk14 ] {ai_face();}; +void() enf_atk14 =[ $attack10, enf_run1 ] {ai_face(); +SUB_CheckRefire (enf_atk1); +}; + +void() enf_paina1 =[ $paina1, enf_paina2 ] {}; +void() enf_paina2 =[ $paina2, enf_paina3 ] {}; +void() enf_paina3 =[ $paina3, enf_paina4 ] {}; +void() enf_paina4 =[ $paina4, enf_run1 ] {}; + +void() enf_painb1 =[ $painb1, enf_painb2 ] {}; +void() enf_painb2 =[ $painb2, enf_painb3 ] {}; +void() enf_painb3 =[ $painb3, enf_painb4 ] {}; +void() enf_painb4 =[ $painb4, enf_painb5 ] {}; +void() enf_painb5 =[ $painb5, enf_run1 ] {}; + +void() enf_painc1 =[ $painc1, enf_painc2 ] {}; +void() enf_painc2 =[ $painc2, enf_painc3 ] {}; +void() enf_painc3 =[ $painc3, enf_painc4 ] {}; +void() enf_painc4 =[ $painc4, enf_painc5 ] {}; +void() enf_painc5 =[ $painc5, enf_painc6 ] {}; +void() enf_painc6 =[ $painc6, enf_painc7 ] {}; +void() enf_painc7 =[ $painc7, enf_painc8 ] {}; +void() enf_painc8 =[ $painc8, enf_run1 ] {}; + +void() enf_paind1 =[ $paind1, enf_paind2 ] {}; +void() enf_paind2 =[ $paind2, enf_paind3 ] {}; +void() enf_paind3 =[ $paind3, enf_paind4 ] {}; +void() enf_paind4 =[ $paind4, enf_paind5 ] {ai_painforward(2);}; +void() enf_paind5 =[ $paind5, enf_paind6 ] {ai_painforward(1);}; +void() enf_paind6 =[ $paind6, enf_paind7 ] {}; +void() enf_paind7 =[ $paind7, enf_paind8 ] {}; +void() enf_paind8 =[ $paind8, enf_paind9 ] {}; +void() enf_paind9 =[ $paind9, enf_paind10 ] {}; +void() enf_paind10 =[ $paind10, enf_paind11 ] {}; +void() enf_paind11 =[ $paind11, enf_paind12 ] {ai_painforward(1);}; +void() enf_paind12 =[ $paind12, enf_paind13 ] {ai_painforward(1);}; +void() enf_paind13 =[ $paind13, enf_paind14 ] {ai_painforward(1);}; +void() enf_paind14 =[ $paind14, enf_paind15 ] {}; +void() enf_paind15 =[ $paind15, enf_paind16 ] {}; +void() enf_paind16 =[ $paind16, enf_paind17 ] {ai_pain(1);}; +void() enf_paind17 =[ $paind17, enf_paind18 ] {ai_pain(1);}; +void() enf_paind18 =[ $paind18, enf_paind19 ] {}; +void() enf_paind19 =[ $paind19, enf_run1 ] {}; + +void(entity attacker, float damage) enf_pain = +{ + local float r; + + r = random (); + if (self.pain_finished > time) + return; + + + if (r < 0.5) + sound (self, CHAN_VOICE, "enforcer/pain1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "enforcer/pain2.wav", 1, ATTN_NORM); + + if (r < 0.2) + { + self.pain_finished = time + 1; + enf_paina1 (); + } + else if (r < 0.4) + { + self.pain_finished = time + 1; + enf_painb1 (); + } + else if (r < 0.7) + { + self.pain_finished = time + 1; + enf_painc1 (); + } + else + { + self.pain_finished = time + 2; + enf_paind1 (); + } +}; + +//============================================================================ + + + + +void() enf_die1 =[ $death1, enf_die2 ] {}; +void() enf_die2 =[ $death2, enf_die3 ] {}; +void() enf_die3 =[ $death3, enf_die4 ] +{self.solid = SOLID_NOT;self.ammo_cells = 5;DropBackpack();}; +void() enf_die4 =[ $death4, enf_die5 ] {ai_forward(14);}; +void() enf_die5 =[ $death5, enf_die6 ] {ai_forward(2);}; +void() enf_die6 =[ $death6, enf_die7 ] {}; +void() enf_die7 =[ $death7, enf_die8 ] {}; +void() enf_die8 =[ $death8, enf_die9 ] {}; +void() enf_die9 =[ $death9, enf_die10 ] {ai_forward(3);}; +void() enf_die10 =[ $death10, enf_die11 ] {ai_forward(5);}; +void() enf_die11 =[ $death11, enf_die12 ] {ai_forward(5);}; +void() enf_die12 =[ $death12, enf_die13 ] {ai_forward(5);}; +void() enf_die13 =[ $death13, enf_die14 ] {}; +void() enf_die14 =[ $death14, enf_die14 ] + { + }; + +void() enf_fdie1 =[ $fdeath1, enf_fdie2 ] {}; +void() enf_fdie2 =[ $fdeath2, enf_fdie3 ] {}; +void() enf_fdie3 =[ $fdeath3, enf_fdie4 ] +{self.solid = SOLID_NOT;self.ammo_cells = 5;DropBackpack();}; +void() enf_fdie4 =[ $fdeath4, enf_fdie5 ] {}; +void() enf_fdie5 =[ $fdeath5, enf_fdie6 ] {}; +void() enf_fdie6 =[ $fdeath6, enf_fdie7 ] {}; +void() enf_fdie7 =[ $fdeath7, enf_fdie8 ] {}; +void() enf_fdie8 =[ $fdeath8, enf_fdie9 ] {}; +void() enf_fdie9 =[ $fdeath9, enf_fdie10 ] {}; +void() enf_fdie10 =[ $fdeath10, enf_fdie11 ] {}; +void() enf_fdie11 =[ $fdeath11, enf_fdie11 ] + { + }; + + +void() enf_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_mega.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "enforcer/death1.wav", 1, ATTN_NORM); + if (random() > 0.5) + enf_die1 (); + else + enf_fdie1 (); +}; + + +/*QUAKED monster_enforcer (1 0 0) (-16 -16 -24) (16 16 40) Ambush + +*/ +void() monster_enforcer = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/enforcer.mdl"); + precache_model2 ("progs/h_mega.mdl"); + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/idle1.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/enforcer.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + + self.th_stand = enf_stand1; + self.th_walk = enf_walk1; + self.th_run = enf_run1; + self.th_pain = enf_pain; + self.th_die = enf_die; + self.th_missile = enf_atk1; + + walkmonster_start(); +}; diff --git a/quakec_hipnotic/monsters/fiend.qc b/quakec_hipnotic/monsters/fiend.qc new file mode 100644 index 0000000..439f50b --- /dev/null +++ b/quakec_hipnotic/monsters/fiend.qc @@ -0,0 +1,382 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +$cd id1/models/demon3 +$scale 0.8 +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + +$frame run1 run2 run3 run4 run5 run6 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 leap10 +$frame leap11 leap12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attacka1 attacka2 attacka3 attacka4 attacka5 attacka6 attacka7 attacka8 +$frame attacka9 attacka10 attacka11 attacka12 attacka13 attacka14 attacka15 + +//============================================================================ + +void() Demon_JumpTouch; + +void() demon1_stand1 =[ $stand1, demon1_stand2 ] {ai_stand();}; +void() demon1_stand2 =[ $stand2, demon1_stand3 ] {ai_stand();}; +void() demon1_stand3 =[ $stand3, demon1_stand4 ] {ai_stand();}; +void() demon1_stand4 =[ $stand4, demon1_stand5 ] {ai_stand();}; +void() demon1_stand5 =[ $stand5, demon1_stand6 ] {ai_stand();}; +void() demon1_stand6 =[ $stand6, demon1_stand7 ] {ai_stand();}; +void() demon1_stand7 =[ $stand7, demon1_stand8 ] {ai_stand();}; +void() demon1_stand8 =[ $stand8, demon1_stand9 ] {ai_stand();}; +void() demon1_stand9 =[ $stand9, demon1_stand10 ] {ai_stand();}; +void() demon1_stand10 =[ $stand10, demon1_stand11 ] {ai_stand();}; +void() demon1_stand11 =[ $stand11, demon1_stand12 ] {ai_stand();}; +void() demon1_stand12 =[ $stand12, demon1_stand13 ] {ai_stand();}; +void() demon1_stand13 =[ $stand13, demon1_stand1 ] {ai_stand();}; + +void() demon1_walk1 =[ $walk1, demon1_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); +ai_walk(8); +}; +void() demon1_walk2 =[ $walk2, demon1_walk3 ] {ai_walk(6);}; +void() demon1_walk3 =[ $walk3, demon1_walk4 ] {ai_walk(6);}; +void() demon1_walk4 =[ $walk4, demon1_walk5 ] {ai_walk(7);}; +void() demon1_walk5 =[ $walk5, demon1_walk6 ] {ai_walk(4);}; +void() demon1_walk6 =[ $walk6, demon1_walk7 ] {ai_walk(6);}; +void() demon1_walk7 =[ $walk7, demon1_walk8 ] {ai_walk(10);}; +void() demon1_walk8 =[ $walk8, demon1_walk1 ] {ai_walk(10);}; + +void() demon1_run1 =[ $run1, demon1_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); +ai_run(20);}; +void() demon1_run2 =[ $run2, demon1_run3 ] {ai_run(15);}; +void() demon1_run3 =[ $run3, demon1_run4 ] {ai_run(36);}; +void() demon1_run4 =[ $run4, demon1_run5 ] {ai_run(20);}; +void() demon1_run5 =[ $run5, demon1_run6 ] {ai_run(15);}; +void() demon1_run6 =[ $run6, demon1_run1 ] {ai_run(36);}; + +void() demon1_jump1 =[ $leap1, demon1_jump2 ] {ai_face();}; +void() demon1_jump2 =[ $leap2, demon1_jump3 ] {ai_face();}; +void() demon1_jump3 =[ $leap3, demon1_jump4 ] {ai_face();}; +void() demon1_jump4 =[ $leap4, demon1_jump5 ] +{ + ai_face(); + + self.touch = Demon_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 250'; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; +void() demon1_jump5 =[ $leap5, demon1_jump6 ] {}; +void() demon1_jump6 =[ $leap6, demon1_jump7 ] {}; +void() demon1_jump7 =[ $leap7, demon1_jump8 ] {}; +void() demon1_jump8 =[ $leap8, demon1_jump9 ] {}; +void() demon1_jump9 =[ $leap9, demon1_jump10 ] {}; +void() demon1_jump10 =[ $leap10, demon1_jump1 ] { +self.nextthink = time + 3; +// if three seconds pass, assume demon is stuck and jump again +}; + +void() demon1_jump11 =[ $leap11, demon1_jump12 ] {}; +void() demon1_jump12 =[ $leap12, demon1_run1 ] {}; + + +void() demon1_atta1 =[ $attacka1, demon1_atta2 ] {ai_charge(4);}; +void() demon1_atta2 =[ $attacka2, demon1_atta3 ] {ai_charge(0);}; +void() demon1_atta3 =[ $attacka3, demon1_atta4 ] {ai_charge(0);}; +void() demon1_atta4 =[ $attacka4, demon1_atta5 ] {ai_charge(1);}; +void() demon1_atta5 =[ $attacka5, demon1_atta6 ] {ai_charge(2); Demon_Melee(200);}; +void() demon1_atta6 =[ $attacka6, demon1_atta7 ] {ai_charge(1);}; +void() demon1_atta7 =[ $attacka7, demon1_atta8 ] {ai_charge(6);}; +void() demon1_atta8 =[ $attacka8, demon1_atta9 ] {ai_charge(8);}; +void() demon1_atta9 =[ $attacka9, demon1_atta10] {ai_charge(4);}; +void() demon1_atta10 =[ $attacka10, demon1_atta11] {ai_charge(2);}; +void() demon1_atta11 =[ $attacka11, demon1_atta12] {Demon_Melee(-200);}; +void() demon1_atta12 =[ $attacka12, demon1_atta13] {ai_charge(5);}; +void() demon1_atta13 =[ $attacka13, demon1_atta14] {ai_charge(8);}; +void() demon1_atta14 =[ $attacka14, demon1_atta15] {ai_charge(4);}; +void() demon1_atta15 =[ $attacka15, demon1_run1] {ai_charge(4);}; + +void() demon1_pain1 =[ $pain1, demon1_pain2 ] {}; +void() demon1_pain2 =[ $pain2, demon1_pain3 ] {}; +void() demon1_pain3 =[ $pain3, demon1_pain4 ] {}; +void() demon1_pain4 =[ $pain4, demon1_pain5 ] {}; +void() demon1_pain5 =[ $pain5, demon1_pain6 ] {}; +void() demon1_pain6 =[ $pain6, demon1_run1 ] {}; + +void(entity attacker, float damage) demon1_pain = +{ + if (self.touch == Demon_JumpTouch) + return; + + if (self.pain_finished > time) + return; + + self.pain_finished = time + 1; + sound (self, CHAN_VOICE, "demon/dpain1.wav", 1, ATTN_NORM); + + if (random()*200 > damage) + return; // didn't flinch + + demon1_pain1 (); +}; + +void() demon1_die1 =[ $death1, demon1_die2 ] { +sound (self, CHAN_VOICE, "demon/ddeath.wav", 1, ATTN_NORM);}; +void() demon1_die2 =[ $death2, demon1_die3 ] {}; +void() demon1_die3 =[ $death3, demon1_die4 ] {}; +void() demon1_die4 =[ $death4, demon1_die5 ] {}; +void() demon1_die5 =[ $death5, demon1_die6 ] {}; +void() demon1_die6 =[ $death6, demon1_die7 ] +{self.solid = SOLID_NOT;}; +void() demon1_die7 =[ $death7, demon1_die8 ] {}; +void() demon1_die8 =[ $death8, demon1_die9 ] {}; +void() demon1_die9 =[ $death9, demon1_die9 ] {}; + +void() demon_die = +{ +// check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_demon.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + return; + } + +// regular death + demon1_die1 (); +}; + + +void() Demon_MeleeAttack = +{ + demon1_atta1 (); +}; + + +/*QUAKED monster_demon1 (1 0 0) (-32 -32 -24) (32 32 64) Ambush + +*/ +void() monster_demon1 = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/demon.mdl"); + precache_model ("progs/h_demon.mdl"); + + precache_sound ("demon/ddeath.wav"); + precache_sound ("demon/dhit2.wav"); + precache_sound ("demon/djump.wav"); + precache_sound ("demon/dpain1.wav"); + precache_sound ("demon/idle1.wav"); + precache_sound ("demon/sight2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/demon.mdl"); + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 300; + + self.th_stand = demon1_stand1; + self.th_walk = demon1_walk1; + self.th_run = demon1_run1; + self.th_die = demon_die; + self.th_melee = Demon_MeleeAttack; // one of two attacks + self.th_missile = demon1_jump1; // jump attack + self.th_pain = demon1_pain; + + walkmonster_start(); +}; + + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +/* +============== +CheckDemonMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDemonMelee = +{ + if (enemy_range == RANGE_MELEE) + { // FIXME: check canreach + self.attack_state = AS_MELEE; + return TRUE; + } + return FALSE; +}; + +/* +============== +CheckDemonJump + +============== +*/ +float() CheckDemonJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 100) + return FALSE; + + if (d > 200) + { + if (random() < 0.9) + return FALSE; + } + + return TRUE; +}; + +float() DemonCheckAttack = +{ + local vector vec; + +// if close enough for slashing, go for it + if (CheckDemonMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDemonJump ()) + { + self.attack_state = AS_MISSILE; + sound (self, CHAN_VOICE, "demon/djump.wav", 1, ATTN_NORM); + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +void(float side) Demon_Melee = +{ + local float ldmg; + local vector delta; + + ai_face (); + walkmove (self.ideal_yaw, 12); // allow a little closing + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + sound (self, CHAN_WEAPON, "demon/dhit2.wav", 1, ATTN_NORM); + ldmg = 10 + 5*random(); + T_Damage (self.enemy, self, self, ldmg); + + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); +}; + + +void() Demon_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 40 + 10*random(); + T_Damage (other, self, self, ldmg); + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = demon1_jump1; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = demon1_jump11; + self.nextthink = time + 0.1; +}; + diff --git a/quakec_hipnotic/monsters/grunt.qc b/quakec_hipnotic/monsters/grunt.qc new file mode 100644 index 0000000..74f1868 --- /dev/null +++ b/quakec_hipnotic/monsters/grunt.qc @@ -0,0 +1,303 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd id1/models/soldier3 +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 + +$frame load1 load2 load3 load4 load5 load6 load7 load8 load9 load10 load11 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 shoot7 shoot8 shoot9 + +$frame prowl_1 prowl_2 prowl_3 prowl_4 prowl_5 prowl_6 prowl_7 prowl_8 +$frame prowl_9 prowl_10 prowl_11 prowl_12 prowl_13 prowl_14 prowl_15 prowl_16 +$frame prowl_17 prowl_18 prowl_19 prowl_20 prowl_21 prowl_22 prowl_23 prowl_24 + +/* +============================================================================== +SOLDIER CODE +============================================================================== +*/ + +void() army_fire; + +void() army_stand1 =[ $stand1, army_stand2 ] {ai_stand();}; +void() army_stand2 =[ $stand2, army_stand3 ] {ai_stand();}; +void() army_stand3 =[ $stand3, army_stand4 ] {ai_stand();}; +void() army_stand4 =[ $stand4, army_stand5 ] {ai_stand();}; +void() army_stand5 =[ $stand5, army_stand6 ] {ai_stand();}; +void() army_stand6 =[ $stand6, army_stand7 ] {ai_stand();}; +void() army_stand7 =[ $stand7, army_stand8 ] {ai_stand();}; +void() army_stand8 =[ $stand8, army_stand1 ] {ai_stand();}; + +void() army_walk1 =[ $prowl_1, army_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); +ai_walk(1);}; +void() army_walk2 =[ $prowl_2, army_walk3 ] {ai_walk(1);}; +void() army_walk3 =[ $prowl_3, army_walk4 ] {ai_walk(1);}; +void() army_walk4 =[ $prowl_4, army_walk5 ] {ai_walk(1);}; +void() army_walk5 =[ $prowl_5, army_walk6 ] {ai_walk(2);}; +void() army_walk6 =[ $prowl_6, army_walk7 ] {ai_walk(3);}; +void() army_walk7 =[ $prowl_7, army_walk8 ] {ai_walk(4);}; +void() army_walk8 =[ $prowl_8, army_walk9 ] {ai_walk(4);}; +void() army_walk9 =[ $prowl_9, army_walk10 ] {ai_walk(2);}; +void() army_walk10 =[ $prowl_10, army_walk11 ] {ai_walk(2);}; +void() army_walk11 =[ $prowl_11, army_walk12 ] {ai_walk(2);}; +void() army_walk12 =[ $prowl_12, army_walk13 ] {ai_walk(1);}; +void() army_walk13 =[ $prowl_13, army_walk14 ] {ai_walk(0);}; +void() army_walk14 =[ $prowl_14, army_walk15 ] {ai_walk(1);}; +void() army_walk15 =[ $prowl_15, army_walk16 ] {ai_walk(1);}; +void() army_walk16 =[ $prowl_16, army_walk17 ] {ai_walk(1);}; +void() army_walk17 =[ $prowl_17, army_walk18 ] {ai_walk(3);}; +void() army_walk18 =[ $prowl_18, army_walk19 ] {ai_walk(3);}; +void() army_walk19 =[ $prowl_19, army_walk20 ] {ai_walk(3);}; +void() army_walk20 =[ $prowl_20, army_walk21 ] {ai_walk(3);}; +void() army_walk21 =[ $prowl_21, army_walk22 ] {ai_walk(2);}; +void() army_walk22 =[ $prowl_22, army_walk23 ] {ai_walk(1);}; +void() army_walk23 =[ $prowl_23, army_walk24 ] {ai_walk(1);}; +void() army_walk24 =[ $prowl_24, army_walk1 ] {ai_walk(1);}; + +void() army_run1 =[ $run1, army_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); +ai_run(11);}; +void() army_run2 =[ $run2, army_run3 ] {ai_run(15);}; +void() army_run3 =[ $run3, army_run4 ] {ai_run(10);}; +void() army_run4 =[ $run4, army_run5 ] {ai_run(10);}; +void() army_run5 =[ $run5, army_run6 ] {ai_run(8);}; +void() army_run6 =[ $run6, army_run7 ] {ai_run(15);}; +void() army_run7 =[ $run7, army_run8 ] {ai_run(10);}; +void() army_run8 =[ $run8, army_run1 ] {ai_run(8);}; + +void() army_atk1 =[ $shoot1, army_atk2 ] {ai_face();}; +void() army_atk2 =[ $shoot2, army_atk3 ] {ai_face();}; +void() army_atk3 =[ $shoot3, army_atk4 ] {ai_face();}; +void() army_atk4 =[ $shoot4, army_atk5 ] {ai_face();}; +void() army_atk5 =[ $shoot5, army_atk6 ] {ai_face();army_fire(); +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() army_atk6 =[ $shoot6, army_atk7 ] {ai_face();}; +void() army_atk7 =[ $shoot7, army_atk8 ] {ai_face();SUB_CheckRefire (army_atk1);}; +void() army_atk8 =[ $shoot8, army_atk9 ] {ai_face();}; +void() army_atk9 =[ $shoot9, army_run1 ] {ai_face();}; + + +void() army_pain1 =[ $pain1, army_pain2 ] {}; +void() army_pain2 =[ $pain2, army_pain3 ] {}; +void() army_pain3 =[ $pain3, army_pain4 ] {}; +void() army_pain4 =[ $pain4, army_pain5 ] {}; +void() army_pain5 =[ $pain5, army_pain6 ] {}; +void() army_pain6 =[ $pain6, army_run1 ] {ai_pain(1);}; + +void() army_painb1 =[ $painb1, army_painb2 ] {}; +void() army_painb2 =[ $painb2, army_painb3 ] {ai_painforward(13);}; +void() army_painb3 =[ $painb3, army_painb4 ] {ai_painforward(9);}; +void() army_painb4 =[ $painb4, army_painb5 ] {}; +void() army_painb5 =[ $painb5, army_painb6 ] {}; +void() army_painb6 =[ $painb6, army_painb7 ] {}; +void() army_painb7 =[ $painb7, army_painb8 ] {}; +void() army_painb8 =[ $painb8, army_painb9 ] {}; +void() army_painb9 =[ $painb9, army_painb10] {}; +void() army_painb10=[ $painb10, army_painb11] {}; +void() army_painb11=[ $painb11, army_painb12] {}; +void() army_painb12=[ $painb12, army_painb13] {ai_pain(2);}; +void() army_painb13=[ $painb13, army_painb14] {}; +void() army_painb14=[ $painb14, army_run1 ] {}; + +void() army_painc1 =[ $painc1, army_painc2 ] {}; +void() army_painc2 =[ $painc2, army_painc3 ] {ai_pain(1);}; +void() army_painc3 =[ $painc3, army_painc4 ] {}; +void() army_painc4 =[ $painc4, army_painc5 ] {}; +void() army_painc5 =[ $painc5, army_painc6 ] {ai_painforward(1);}; +void() army_painc6 =[ $painc6, army_painc7 ] {ai_painforward(1);}; +void() army_painc7 =[ $painc7, army_painc8 ] {}; +void() army_painc8 =[ $painc8, army_painc9 ] {ai_pain(1);}; +void() army_painc9 =[ $painc9, army_painc10] {ai_painforward(4);}; +void() army_painc10=[ $painc10, army_painc11] {ai_painforward(3);}; +void() army_painc11=[ $painc11, army_painc12] {ai_painforward(6);}; +void() army_painc12=[ $painc12, army_painc13] {ai_painforward(8);}; +void() army_painc13=[ $painc13, army_run1] {}; + +void(entity attacker, float damage) army_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (r < 0.2) + { + self.pain_finished = time + 0.6; + army_pain1 (); + sound (self, CHAN_VOICE, "soldier/pain1.wav", 1, ATTN_NORM); + } + else if (r < 0.6) + { + self.pain_finished = time + 1.1; + army_painb1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } + else + { + self.pain_finished = time + 1.1; + army_painc1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } +}; + + +void() army_fire = +{ + local vector dir; + local entity en; + + ai_face(); + + sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM); + +// fire somewhat behind the player, so a dodging player is harder to hit + en = self.enemy; + + dir = en.origin - en.velocity*0.2; + dir = normalize (dir - self.origin); + + FireBullets (4, dir, '0.1 0.1 0'); +}; + + + +void() army_die1 =[ $death1, army_die2 ] {}; +void() army_die2 =[ $death2, army_die3 ] {}; +void() army_die3 =[ $death3, army_die4 ] +{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();}; +void() army_die4 =[ $death4, army_die5 ] {}; +void() army_die5 =[ $death5, army_die6 ] {}; +void() army_die6 =[ $death6, army_die7 ] {}; +void() army_die7 =[ $death7, army_die8 ] {}; +void() army_die8 =[ $death8, army_die9 ] {}; +void() army_die9 =[ $death9, army_die10 ] {}; +void() army_die10 =[ $death10, army_die10 ] {}; + +void() army_cdie1 =[ $deathc1, army_cdie2 ] {}; +void() army_cdie2 =[ $deathc2, army_cdie3 ] {ai_back(5);}; +void() army_cdie3 =[ $deathc3, army_cdie4 ] +{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();ai_back(4);}; +void() army_cdie4 =[ $deathc4, army_cdie5 ] {ai_back(13);}; +void() army_cdie5 =[ $deathc5, army_cdie6 ] {ai_back(3);}; +void() army_cdie6 =[ $deathc6, army_cdie7 ] {ai_back(4);}; +void() army_cdie7 =[ $deathc7, army_cdie8 ] {}; +void() army_cdie8 =[ $deathc8, army_cdie9 ] {}; +void() army_cdie9 =[ $deathc9, army_cdie10 ] {}; +void() army_cdie10 =[ $deathc10, army_cdie11 ] {}; +void() army_cdie11 =[ $deathc11, army_cdie11 ] {}; + + +void() army_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_guard.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "soldier/death1.wav", 1, ATTN_NORM); + if (random() < 0.5) + army_die1 (); + else + army_cdie1 (); +}; + + +/*QUAKED monster_army (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_army = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/soldier.mdl"); + precache_model ("progs/h_guard.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_sound ("soldier/death1.wav"); + precache_sound ("soldier/idle.wav"); + precache_sound ("soldier/pain1.wav"); + precache_sound ("soldier/pain2.wav"); + precache_sound ("soldier/sattck1.wav"); + precache_sound ("soldier/sight1.wav"); + + precache_sound ("player/udeath.wav"); // gib death + + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/soldier.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 30; + + self.th_stand = army_stand1; + self.th_walk = army_walk1; + self.th_run = army_run1; + self.th_missile = army_atk1; + self.th_pain = army_pain; + self.th_die = army_die; + + walkmonster_start (); +}; diff --git a/quakec_hipnotic/monsters/hellknight.qc b/quakec_hipnotic/monsters/hellknight.qc new file mode 100644 index 0000000..b7ae083 --- /dev/null +++ b/quakec_hipnotic/monsters/hellknight.qc @@ -0,0 +1,460 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd id1/models/knight2 +$origin 0 0 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 walk15 walk16 walk17 +$frame walk18 walk19 walk20 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 death12 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame char_a1 char_a2 char_a3 char_a4 char_a5 char_a6 char_a7 char_a8 +$frame char_a9 char_a10 char_a11 char_a12 char_a13 char_a14 char_a15 char_a16 + +$frame magica1 magica2 magica3 magica4 magica5 magica6 magica7 magica8 +$frame magica9 magica10 magica11 magica12 magica13 magica14 + +$frame magicb1 magicb2 magicb3 magicb4 magicb5 magicb6 magicb7 magicb8 +$frame magicb9 magicb10 magicb11 magicb12 magicb13 + +$frame char_b1 char_b2 char_b3 char_b4 char_b5 char_b6 + +$frame slice1 slice2 slice3 slice4 slice5 slice6 slice7 slice8 slice9 slice10 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 smash8 smash9 smash10 +$frame smash11 + +$frame w_attack1 w_attack2 w_attack3 w_attack4 w_attack5 w_attack6 w_attack7 +$frame w_attack8 w_attack9 w_attack10 w_attack11 w_attack12 w_attack13 w_attack14 +$frame w_attack15 w_attack16 w_attack17 w_attack18 w_attack19 w_attack20 +$frame w_attack21 w_attack22 + +$frame magicc1 magicc2 magicc3 magicc4 magicc5 magicc6 magicc7 magicc8 +$frame magicc9 magicc10 magicc11 + + +void() hknight_char_a1; +void() hknight_run1; +void() hk_idle_sound; + +void(float offset) hknight_shot = +{ + local vector offang; + local vector org, vec; + + offang = vectoangles (self.enemy.origin - self.origin); + offang_y = offang_y + offset * 6; + + makevectors (offang); + + org = self.origin + self.mins + self.size*0.5 + v_forward * 20; + +// set missile speed + vec = normalize (v_forward); + vec_z = 0 - vec_z + (random() - 0.5)*0.1; + + launch_spike (org, vec); + newmis.classname = "knightspike"; + setmodel (newmis, "progs/k_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.effects = newmis.effects | EF_CANDLELIGHT; // give the projectiles a weak glow + sound (self, CHAN_WEAPON, "hknight/attack1.wav", 1, ATTN_NORM); +}; + +void() CheckForCharge = +{ +// check for mad charge +if (!enemy_vis) + return; +if (time < self.attack_finished) + return; +if ( fabs(self.origin_z - self.enemy.origin_z) > 20) + return; // too much height change +if ( vlen (self.origin - self.enemy.origin) < 80) + return; // use regular attack + +// charge + SUB_AttackFinished (2); + hknight_char_a1 (); + +}; + +void() CheckContinueCharge = +{ + if (time > self.attack_finished) + { + SUB_AttackFinished (3); + hknight_run1 (); + return; // done charging + } + if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); + else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +}; + +//=========================================================================== + +void() hknight_stand1 =[ $stand1, hknight_stand2 ] {ai_stand();}; +void() hknight_stand2 =[ $stand2, hknight_stand3 ] {ai_stand();}; +void() hknight_stand3 =[ $stand3, hknight_stand4 ] {ai_stand();}; +void() hknight_stand4 =[ $stand4, hknight_stand5 ] {ai_stand();}; +void() hknight_stand5 =[ $stand5, hknight_stand6 ] {ai_stand();}; +void() hknight_stand6 =[ $stand6, hknight_stand7 ] {ai_stand();}; +void() hknight_stand7 =[ $stand7, hknight_stand8 ] {ai_stand();}; +void() hknight_stand8 =[ $stand8, hknight_stand9 ] {ai_stand();}; +void() hknight_stand9 =[ $stand9, hknight_stand1 ] {ai_stand();}; + +//=========================================================================== + +void() hknight_walk1 =[ $walk1, hknight_walk2 ] { +hk_idle_sound(); +ai_walk(2);}; +void() hknight_walk2 =[ $walk2, hknight_walk3 ] {ai_walk(5);}; +void() hknight_walk3 =[ $walk3, hknight_walk4 ] {ai_walk(5);}; +void() hknight_walk4 =[ $walk4, hknight_walk5 ] {ai_walk(4);}; +void() hknight_walk5 =[ $walk5, hknight_walk6 ] {ai_walk(4);}; +void() hknight_walk6 =[ $walk6, hknight_walk7 ] {ai_walk(2);}; +void() hknight_walk7 =[ $walk7, hknight_walk8 ] {ai_walk(2);}; +void() hknight_walk8 =[ $walk8, hknight_walk9 ] {ai_walk(3);}; +void() hknight_walk9 =[ $walk9, hknight_walk10 ] {ai_walk(3);}; +void() hknight_walk10 =[ $walk10, hknight_walk11 ] {ai_walk(4);}; +void() hknight_walk11 =[ $walk11, hknight_walk12 ] {ai_walk(3);}; +void() hknight_walk12 =[ $walk12, hknight_walk13 ] {ai_walk(4);}; +void() hknight_walk13 =[ $walk13, hknight_walk14 ] {ai_walk(6);}; +void() hknight_walk14 =[ $walk14, hknight_walk15 ] {ai_walk(2);}; +void() hknight_walk15 =[ $walk15, hknight_walk16 ] {ai_walk(2);}; +void() hknight_walk16 =[ $walk16, hknight_walk17 ] {ai_walk(4);}; +void() hknight_walk17 =[ $walk17, hknight_walk18 ] {ai_walk(3);}; +void() hknight_walk18 =[ $walk18, hknight_walk19 ] {ai_walk(3);}; +void() hknight_walk19 =[ $walk19, hknight_walk20 ] {ai_walk(3);}; +void() hknight_walk20 =[ $walk20, hknight_walk1 ] {ai_walk(2);}; + +//=========================================================================== + +void() hknight_run1 =[ $run1, hknight_run2 ] { +hk_idle_sound(); +ai_run (20); CheckForCharge (); }; +void() hknight_run2 =[ $run2, hknight_run3 ] {ai_run(25);}; +void() hknight_run3 =[ $run3, hknight_run4 ] {ai_run(18);}; +void() hknight_run4 =[ $run4, hknight_run5 ] {ai_run(16);}; +void() hknight_run5 =[ $run5, hknight_run6 ] {ai_run(14);}; +void() hknight_run6 =[ $run6, hknight_run7 ] {ai_run(25);}; +void() hknight_run7 =[ $run7, hknight_run8 ] {ai_run(21);}; +void() hknight_run8 =[ $run8, hknight_run1 ] {ai_run(13);}; + +//============================================================================ + +void() hknight_pain1 =[ $pain1, hknight_pain2 ] {sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM);}; +void() hknight_pain2 =[ $pain2, hknight_pain3 ] {}; +void() hknight_pain3 =[ $pain3, hknight_pain4 ] {}; +void() hknight_pain4 =[ $pain4, hknight_pain5 ] {}; +void() hknight_pain5 =[ $pain5, hknight_run1 ] {}; + +//============================================================================ + +void() hknight_die1 =[ $death1, hknight_die2 ] {ai_forward(10);}; +void() hknight_die2 =[ $death2, hknight_die3 ] {ai_forward(8);}; +void() hknight_die3 =[ $death3, hknight_die4 ] +{self.solid = SOLID_NOT; ai_forward(7);}; +void() hknight_die4 =[ $death4, hknight_die5 ] {}; +void() hknight_die5 =[ $death5, hknight_die6 ] {}; +void() hknight_die6 =[ $death6, hknight_die7 ] {}; +void() hknight_die7 =[ $death7, hknight_die8 ] {}; +void() hknight_die8 =[ $death8, hknight_die9 ] {ai_forward(10);}; +void() hknight_die9 =[ $death9, hknight_die10 ] {ai_forward(11);}; +void() hknight_die10 =[ $death10, hknight_die11 ] {}; +void() hknight_die11 =[ $death11, hknight_die12 ] {}; +void() hknight_die12 =[ $death12, hknight_die12 ] {}; + +void() hknight_dieb1 =[ $deathb1, hknight_dieb2 ] {}; +void() hknight_dieb2 =[ $deathb2, hknight_dieb3 ] {}; +void() hknight_dieb3 =[ $deathb3, hknight_dieb4 ] +{self.solid = SOLID_NOT;}; +void() hknight_dieb4 =[ $deathb4, hknight_dieb5 ] {}; +void() hknight_dieb5 =[ $deathb5, hknight_dieb6 ] {}; +void() hknight_dieb6 =[ $deathb6, hknight_dieb7 ] {}; +void() hknight_dieb7 =[ $deathb7, hknight_dieb8 ] {}; +void() hknight_dieb8 =[ $deathb8, hknight_dieb9 ] {}; +void() hknight_dieb9 =[ $deathb9, hknight_dieb9 ] {}; + +void() hknight_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_hellkn.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "hknight/death1.wav", 1, ATTN_NORM); + if (random() > 0.5) + hknight_die1 (); + else + hknight_dieb1 (); +}; + + +//============================================================================ + +void() hknight_magica1 =[ $magica1, hknight_magica2 ] {ai_face();}; +void() hknight_magica2 =[ $magica2, hknight_magica3 ] {ai_face();}; +void() hknight_magica3 =[ $magica3, hknight_magica4 ] {ai_face();}; +void() hknight_magica4 =[ $magica4, hknight_magica5 ] {ai_face();}; +void() hknight_magica5 =[ $magica5, hknight_magica6 ] {ai_face();}; +void() hknight_magica6 =[ $magica6, hknight_magica7 ] {ai_face();}; +void() hknight_magica7 =[ $magica7, hknight_magica8 ] {hknight_shot(-2);}; +void() hknight_magica8 =[ $magica8, hknight_magica9 ] {hknight_shot(-1);}; +void() hknight_magica9 =[ $magica9, hknight_magica10] {hknight_shot(0);}; +void() hknight_magica10 =[ $magica10, hknight_magica11] {hknight_shot(1);}; +void() hknight_magica11 =[ $magica11, hknight_magica12] {hknight_shot(2);}; +void() hknight_magica12 =[ $magica12, hknight_magica13] {hknight_shot(3);}; +void() hknight_magica13 =[ $magica13, hknight_magica14] {ai_face();}; +void() hknight_magica14 =[ $magica14, hknight_run1 ] {ai_face();}; + +//============================================================================ + +void() hknight_magicb1 =[ $magicb1, hknight_magicb2 ] {ai_face();}; +void() hknight_magicb2 =[ $magicb2, hknight_magicb3 ] {ai_face();}; +void() hknight_magicb3 =[ $magicb3, hknight_magicb4 ] {ai_face();}; +void() hknight_magicb4 =[ $magicb4, hknight_magicb5 ] {ai_face();}; +void() hknight_magicb5 =[ $magicb5, hknight_magicb6 ] {ai_face();}; +void() hknight_magicb6 =[ $magicb6, hknight_magicb7 ] {ai_face();}; +void() hknight_magicb7 =[ $magicb7, hknight_magicb8 ] {hknight_shot(-2);}; +void() hknight_magicb8 =[ $magicb8, hknight_magicb9 ] {hknight_shot(-1);}; +void() hknight_magicb9 =[ $magicb9, hknight_magicb10] {hknight_shot(0);}; +void() hknight_magicb10 =[ $magicb10, hknight_magicb11] {hknight_shot(1);}; +void() hknight_magicb11 =[ $magicb11, hknight_magicb12] {hknight_shot(2);}; +void() hknight_magicb12 =[ $magicb12, hknight_magicb13] {hknight_shot(3);}; +void() hknight_magicb13 =[ $magicb13, hknight_run1] {ai_face();}; + +//============================================================================ + +void() hknight_magicc1 =[ $magicc1, hknight_magicc2 ] {ai_face();}; +void() hknight_magicc2 =[ $magicc2, hknight_magicc3 ] {ai_face();}; +void() hknight_magicc3 =[ $magicc3, hknight_magicc4 ] {ai_face();}; +void() hknight_magicc4 =[ $magicc4, hknight_magicc5 ] {ai_face();}; +void() hknight_magicc5 =[ $magicc5, hknight_magicc6 ] {ai_face();}; +void() hknight_magicc6 =[ $magicc6, hknight_magicc7 ] {hknight_shot(-2);}; +void() hknight_magicc7 =[ $magicc7, hknight_magicc8 ] {hknight_shot(-1);}; +void() hknight_magicc8 =[ $magicc8, hknight_magicc9 ] {hknight_shot(0);}; +void() hknight_magicc9 =[ $magicc9, hknight_magicc10] {hknight_shot(1);}; +void() hknight_magicc10 =[ $magicc10, hknight_magicc11] {hknight_shot(2);}; +void() hknight_magicc11 =[ $magicc11, hknight_run1] {hknight_shot(3);}; + +//=========================================================================== + +void() hknight_char_a1 =[ $char_a1, hknight_char_a2 ] {ai_charge(20);}; +void() hknight_char_a2 =[ $char_a2, hknight_char_a3 ] {ai_charge(25);}; +void() hknight_char_a3 =[ $char_a3, hknight_char_a4 ] {ai_charge(18);}; +void() hknight_char_a4 =[ $char_a4, hknight_char_a5 ] {ai_charge(16);}; +void() hknight_char_a5 =[ $char_a5, hknight_char_a6 ] {ai_charge(14);}; +void() hknight_char_a6 =[ $char_a6, hknight_char_a7 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a7 =[ $char_a7, hknight_char_a8 ] {ai_charge(21); ai_melee();}; +void() hknight_char_a8 =[ $char_a8, hknight_char_a9 ] {ai_charge(13); ai_melee();}; +void() hknight_char_a9 =[ $char_a9, hknight_char_a10 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a10=[ $char_a10, hknight_char_a11 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a11=[ $char_a11, hknight_char_a12 ] {ai_charge(18); ai_melee();}; +void() hknight_char_a12=[ $char_a12, hknight_char_a13 ] {ai_charge(16);}; +void() hknight_char_a13=[ $char_a13, hknight_char_a14 ] {ai_charge(14);}; +void() hknight_char_a14=[ $char_a14, hknight_char_a15 ] {ai_charge(25);}; +void() hknight_char_a15=[ $char_a15, hknight_char_a16 ] {ai_charge(21);}; +void() hknight_char_a16=[ $char_a16, hknight_run1 ] {ai_charge(13);}; + +//=========================================================================== + +void() hknight_char_b1 =[ $char_b1, hknight_char_b2 ] +{CheckContinueCharge (); ai_charge(23); ai_melee();}; +void() hknight_char_b2 =[ $char_b2, hknight_char_b3 ] {ai_charge(17); ai_melee();}; +void() hknight_char_b3 =[ $char_b3, hknight_char_b4 ] {ai_charge(12); ai_melee();}; +void() hknight_char_b4 =[ $char_b4, hknight_char_b5 ] {ai_charge(22); ai_melee();}; +void() hknight_char_b5 =[ $char_b5, hknight_char_b6 ] {ai_charge(18); ai_melee();}; +void() hknight_char_b6 =[ $char_b6, hknight_char_b1 ] {ai_charge(8); ai_melee();}; + +//=========================================================================== + +void() hknight_slice1 =[ $slice1, hknight_slice2 ] {ai_charge(9);}; +void() hknight_slice2 =[ $slice2, hknight_slice3 ] {ai_charge(6);}; +void() hknight_slice3 =[ $slice3, hknight_slice4 ] {ai_charge(13);}; +void() hknight_slice4 =[ $slice4, hknight_slice5 ] {ai_charge(4);}; +void() hknight_slice5 =[ $slice5, hknight_slice6 ] {ai_charge(7); ai_melee();}; +void() hknight_slice6 =[ $slice6, hknight_slice7 ] {ai_charge(15); ai_melee();}; +void() hknight_slice7 =[ $slice7, hknight_slice8 ] {ai_charge(8); ai_melee();}; +void() hknight_slice8 =[ $slice8, hknight_slice9 ] {ai_charge(2); ai_melee();}; +void() hknight_slice9 =[ $slice9, hknight_slice10 ] {ai_melee();}; +void() hknight_slice10 =[ $slice10, hknight_run1 ] {ai_charge(3);}; + +//=========================================================================== + +void() hknight_smash1 =[ $smash1, hknight_smash2 ] {ai_charge(1);}; +void() hknight_smash2 =[ $smash2, hknight_smash3 ] {ai_charge(13);}; +void() hknight_smash3 =[ $smash3, hknight_smash4 ] {ai_charge(9);}; +void() hknight_smash4 =[ $smash4, hknight_smash5 ] {ai_charge(11);}; +void() hknight_smash5 =[ $smash5, hknight_smash6 ] {ai_charge(10); ai_melee();}; +void() hknight_smash6 =[ $smash6, hknight_smash7 ] {ai_charge(7); ai_melee();}; +void() hknight_smash7 =[ $smash7, hknight_smash8 ] {ai_charge(12); ai_melee();}; +void() hknight_smash8 =[ $smash8, hknight_smash9 ] {ai_charge(2); ai_melee();}; +void() hknight_smash9 =[ $smash9, hknight_smash10 ] {ai_charge(3); ai_melee();}; +void() hknight_smash10 =[ $smash10, hknight_smash11 ] {ai_charge(0);}; +void() hknight_smash11 =[ $smash11, hknight_run1 ] {ai_charge(0);}; + +//============================================================================ + +void() hknight_watk1 =[ $w_attack1, hknight_watk2 ] {ai_charge(2);}; +void() hknight_watk2 =[ $w_attack2, hknight_watk3 ] {ai_charge(0);}; +void() hknight_watk3 =[ $w_attack3, hknight_watk4 ] {ai_charge(0);}; +void() hknight_watk4 =[ $w_attack4, hknight_watk5 ] {ai_melee();}; +void() hknight_watk5 =[ $w_attack5, hknight_watk6 ] {ai_melee();}; +void() hknight_watk6 =[ $w_attack6, hknight_watk7 ] {ai_melee();}; +void() hknight_watk7 =[ $w_attack7, hknight_watk8 ] {ai_charge(1);}; +void() hknight_watk8 =[ $w_attack8, hknight_watk9 ] {ai_charge(4);}; +void() hknight_watk9 =[ $w_attack9, hknight_watk10 ] {ai_charge(5);}; +void() hknight_watk10 =[ $w_attack10, hknight_watk11 ] {ai_charge(3); ai_melee();}; +void() hknight_watk11 =[ $w_attack11, hknight_watk12 ] {ai_charge(2); ai_melee();}; +void() hknight_watk12 =[ $w_attack12, hknight_watk13 ] {ai_charge(2); ai_melee();}; +void() hknight_watk13 =[ $w_attack13, hknight_watk14 ] {ai_charge(0);}; +void() hknight_watk14 =[ $w_attack14, hknight_watk15 ] {ai_charge(0);}; +void() hknight_watk15 =[ $w_attack15, hknight_watk16 ] {ai_charge(0);}; +void() hknight_watk16 =[ $w_attack16, hknight_watk17 ] {ai_charge(1);}; +void() hknight_watk17 =[ $w_attack17, hknight_watk18 ] {ai_charge(1); ai_melee();}; +void() hknight_watk18 =[ $w_attack18, hknight_watk19 ] {ai_charge(3); ai_melee();}; +void() hknight_watk19 =[ $w_attack19, hknight_watk20 ] {ai_charge(4); ai_melee();}; +void() hknight_watk20 =[ $w_attack20, hknight_watk21 ] {ai_charge(6);}; +void() hknight_watk21 =[ $w_attack21, hknight_watk22 ] {ai_charge(7);}; +void() hknight_watk22 =[ $w_attack22, hknight_run1 ] {ai_charge(3);}; + +//============================================================================ + +void() hk_idle_sound = +{ + if (random() < 0.2) + sound (self, CHAN_VOICE, "hknight/idle.wav", 1, ATTN_NORM); +}; + +void(entity attacker, float damage) hknight_pain = +{ + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM); + + if (time - self.pain_finished > 5) + { // allways go into pain frame if it has been a while + hknight_pain1 (); + self.pain_finished = time + 1; + return; + } + + if ((random()*30 > damage) ) + return; // didn't flinch + + self.pain_finished = time + 1; + hknight_pain1 (); +}; + +float hknight_type; + +void() hknight_melee = +{ + hknight_type = hknight_type + 1; + + sound (self, CHAN_WEAPON, "hknight/slash1.wav", 1, ATTN_NORM); + if (hknight_type == 1) + hknight_slice1 (); + else if (hknight_type == 2) + hknight_smash1 (); + else if (hknight_type == 3) + { + hknight_watk1 (); + hknight_type = 0; + } +}; + +/*QUAKED monster_hell_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_hell_knight = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/hknight.mdl"); + precache_model2 ("progs/k_spike.mdl"); + precache_model2 ("progs/h_hellkn.mdl"); + + + precache_sound2 ("hknight/attack1.wav"); + precache_sound2 ("hknight/death1.wav"); + precache_sound2 ("hknight/pain1.wav"); + precache_sound2 ("hknight/sight1.wav"); + precache_sound ("hknight/hit.wav"); // used by C code, so don't sound2 + precache_sound2 ("hknight/slash1.wav"); + precache_sound2 ("hknight/idle.wav"); + precache_sound2 ("hknight/grunt.wav"); + + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/hknight.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 250; + + self.th_stand = hknight_stand1; + self.th_walk = hknight_walk1; + self.th_run = hknight_run1; + self.th_melee = hknight_melee; + self.th_missile = hknight_magicc1; + self.th_pain = hknight_pain; + self.th_die = hknight_die; + + walkmonster_start (); +}; diff --git a/quakec_hipnotic/monsters/knight.qc b/quakec_hipnotic/monsters/knight.qc new file mode 100644 index 0000000..c935959 --- /dev/null +++ b/quakec_hipnotic/monsters/knight.qc @@ -0,0 +1,289 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd id1/models/knight +$origin 0 0 24 +$base base +$skin badass3 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame runb1 runb2 runb3 runb4 runb5 runb6 runb7 runb8 + +//frame runc1 runc2 runc3 runc4 runc5 runc6 + +$frame runattack1 runattack2 runattack3 runattack4 runattack5 +$frame runattack6 runattack7 runattack8 runattack9 runattack10 +$frame runattack11 + +$frame pain1 pain2 pain3 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 +$frame painb10 painb11 + +//frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 +//frame attack8 attack9 attack10 attack11 + +$frame attackb1 attackb1 attackb2 attackb3 attackb4 attackb5 +$frame attackb6 attackb7 attackb8 attackb9 attackb10 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 + +$frame kneel1 kneel2 kneel3 kneel4 kneel5 + +$frame standing2 standing3 standing4 standing5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 deathb10 deathb11 + +void() knight_stand1 =[ $stand1, knight_stand2 ] {ai_stand();}; +void() knight_stand2 =[ $stand2, knight_stand3 ] {ai_stand();}; +void() knight_stand3 =[ $stand3, knight_stand4 ] {ai_stand();}; +void() knight_stand4 =[ $stand4, knight_stand5 ] {ai_stand();}; +void() knight_stand5 =[ $stand5, knight_stand6 ] {ai_stand();}; +void() knight_stand6 =[ $stand6, knight_stand7 ] {ai_stand();}; +void() knight_stand7 =[ $stand7, knight_stand8 ] {ai_stand();}; +void() knight_stand8 =[ $stand8, knight_stand9 ] {ai_stand();}; +void() knight_stand9 =[ $stand9, knight_stand1 ] {ai_stand();}; + +void() knight_walk1 =[ $walk1, knight_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "knight/idle.wav", 1, ATTN_IDLE); +ai_walk(3);}; +void() knight_walk2 =[ $walk2, knight_walk3 ] {ai_walk(2);}; +void() knight_walk3 =[ $walk3, knight_walk4 ] {ai_walk(3);}; +void() knight_walk4 =[ $walk4, knight_walk5 ] {ai_walk(4);}; +void() knight_walk5 =[ $walk5, knight_walk6 ] {ai_walk(3);}; +void() knight_walk6 =[ $walk6, knight_walk7 ] {ai_walk(3);}; +void() knight_walk7 =[ $walk7, knight_walk8 ] {ai_walk(3);}; +void() knight_walk8 =[ $walk8, knight_walk9 ] {ai_walk(4);}; +void() knight_walk9 =[ $walk9, knight_walk10 ] {ai_walk(3);}; +void() knight_walk10 =[ $walk10, knight_walk11 ] {ai_walk(3);}; +void() knight_walk11 =[ $walk11, knight_walk12 ] {ai_walk(2);}; +void() knight_walk12 =[ $walk12, knight_walk13 ] {ai_walk(3);}; +void() knight_walk13 =[ $walk13, knight_walk14 ] {ai_walk(4);}; +void() knight_walk14 =[ $walk14, knight_walk1 ] {ai_walk(3);}; + + +void() knight_run1 =[ $runb1, knight_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "knight/idle.wav", 1, ATTN_IDLE); +ai_run(16);}; +void() knight_run2 =[ $runb2, knight_run3 ] {ai_run(20);}; +void() knight_run3 =[ $runb3, knight_run4 ] {ai_run(13);}; +void() knight_run4 =[ $runb4, knight_run5 ] {ai_run(7);}; +void() knight_run5 =[ $runb5, knight_run6 ] {ai_run(16);}; +void() knight_run6 =[ $runb6, knight_run7 ] {ai_run(20);}; +void() knight_run7 =[ $runb7, knight_run8 ] {ai_run(14);}; +void() knight_run8 =[ $runb8, knight_run1 ] {ai_run(6);}; + + +void() knight_runatk1 =[ $runattack1, knight_runatk2 ] +{ +if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); +else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +ai_charge(20); +}; +void() knight_runatk2 =[ $runattack2, knight_runatk3 ] {ai_charge_side();}; +void() knight_runatk3 =[ $runattack3, knight_runatk4 ] {ai_charge_side();}; +void() knight_runatk4 =[ $runattack4, knight_runatk5 ] {ai_charge_side();}; +void() knight_runatk5 =[ $runattack5, knight_runatk6 ] {ai_melee_side();}; +void() knight_runatk6 =[ $runattack6, knight_runatk7 ] {ai_melee_side();}; +void() knight_runatk7 =[ $runattack7, knight_runatk8 ] {ai_melee_side();}; +void() knight_runatk8 =[ $runattack8, knight_runatk9 ] {ai_melee_side();}; +void() knight_runatk9 =[ $runattack9, knight_runatk10 ] {ai_melee_side();}; +void() knight_runatk10 =[ $runattack10, knight_runatk11 ] {ai_charge_side();}; +void() knight_runatk11 =[ $runattack11, knight_run1 ] {ai_charge(10);}; + +void() knight_atk1 =[ $attackb1, knight_atk2 ] +{ +sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +ai_charge(0);}; +void() knight_atk2 =[ $attackb2, knight_atk3 ] {ai_charge(7);}; +void() knight_atk3 =[ $attackb3, knight_atk4 ] {ai_charge(4);}; +void() knight_atk4 =[ $attackb4, knight_atk5 ] {ai_charge(0);}; +void() knight_atk5 =[ $attackb5, knight_atk6 ] {ai_charge(3);}; +void() knight_atk6 =[ $attackb6, knight_atk7 ] {ai_charge(4); ai_melee();}; +void() knight_atk7 =[ $attackb7, knight_atk8 ] {ai_charge(1); ai_melee();}; +void() knight_atk8 =[ $attackb8, knight_atk9 ] {ai_charge(3); +ai_melee();}; +void() knight_atk9 =[ $attackb9, knight_atk10] {ai_charge(1);}; +void() knight_atk10=[ $attackb10, knight_run1 ] {ai_charge(5);}; + +//void() knight_atk9 =[ $attack9, knight_atk10 ] {}; +//void() knight_atk10 =[ $attack10, knight_atk11 ] {}; +//void() knight_atk11 =[ $attack11, knight_run1 ] {}; + +//=========================================================================== + +void() knight_pain1 =[ $pain1, knight_pain2 ] {}; +void() knight_pain2 =[ $pain2, knight_pain3 ] {}; +void() knight_pain3 =[ $pain3, knight_run1 ] {}; + +void() knight_painb1 =[ $painb1, knight_painb2 ] {ai_painforward(0);}; +void() knight_painb2 =[ $painb2, knight_painb3 ] {ai_painforward(3);}; +void() knight_painb3 =[ $painb3, knight_painb4 ] {}; +void() knight_painb4 =[ $painb4, knight_painb5 ] {}; +void() knight_painb5 =[ $painb5, knight_painb6 ] {ai_painforward(2);}; +void() knight_painb6 =[ $painb6, knight_painb7 ] {ai_painforward(4);}; +void() knight_painb7 =[ $painb7, knight_painb8 ] {ai_painforward(2);}; +void() knight_painb8 =[ $painb8, knight_painb9 ] {ai_painforward(5);}; +void() knight_painb9 =[ $painb9, knight_painb10 ] {ai_painforward(5);}; +void() knight_painb10 =[ $painb10, knight_painb11 ] {ai_painforward(0);}; +void() knight_painb11 =[ $painb11, knight_run1 ] {}; + +void(entity attacker, float damage) knight_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + sound (self, CHAN_VOICE, "knight/khurt.wav", 1, ATTN_NORM); + if (r < 0.85) + { + knight_pain1 (); + self.pain_finished = time + 1; + } + else + { + knight_painb1 (); + self.pain_finished = time + 1; + } + +}; + +//=========================================================================== + +void() knight_bow1 =[ $kneel1, knight_bow2 ] {ai_turn();}; +void() knight_bow2 =[ $kneel2, knight_bow3 ] {ai_turn();}; +void() knight_bow3 =[ $kneel3, knight_bow4 ] {ai_turn();}; +void() knight_bow4 =[ $kneel4, knight_bow5 ] {ai_turn();}; + +void() knight_bow5 =[ $kneel5, knight_bow5 ] {ai_turn();}; + +void() knight_bow6 =[ $kneel4, knight_bow7 ] {ai_turn();}; +void() knight_bow7 =[ $kneel3, knight_bow8 ] {ai_turn();}; +void() knight_bow8 =[ $kneel2, knight_bow9 ] {ai_turn();}; +void() knight_bow9 =[ $kneel1, knight_bow10 ] {ai_turn();}; +void() knight_bow10 =[ $walk1, knight_walk1 ] {ai_turn();}; + + + +void() knight_die1 =[ $death1, knight_die2 ] {}; +void() knight_die2 =[ $death2, knight_die3 ] {}; +void() knight_die3 =[ $death3, knight_die4 ] +{self.solid = SOLID_NOT;}; +void() knight_die4 =[ $death4, knight_die5 ] {}; +void() knight_die5 =[ $death5, knight_die6 ] {}; +void() knight_die6 =[ $death6, knight_die7 ] {}; +void() knight_die7 =[ $death7, knight_die8 ] {}; +void() knight_die8 =[ $death8, knight_die9 ] {}; +void() knight_die9 =[ $death9, knight_die10] {}; +void() knight_die10=[ $death10, knight_die10] {}; + + +void() knight_dieb1 =[ $deathb1, knight_dieb2 ] {}; +void() knight_dieb2 =[ $deathb2, knight_dieb3 ] {}; +void() knight_dieb3 =[ $deathb3, knight_dieb4 ] +{self.solid = SOLID_NOT;}; +void() knight_dieb4 =[ $deathb4, knight_dieb5 ] {}; +void() knight_dieb5 =[ $deathb5, knight_dieb6 ] {}; +void() knight_dieb6 =[ $deathb6, knight_dieb7 ] {}; +void() knight_dieb7 =[ $deathb7, knight_dieb8 ] {}; +void() knight_dieb8 =[ $deathb8, knight_dieb9 ] {}; +void() knight_dieb9 =[ $deathb9, knight_dieb10] {}; +void() knight_dieb10 = [ $deathb10, knight_dieb11] {}; +void() knight_dieb11 = [ $deathb11, knight_dieb11] {}; + + +void() knight_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_knight.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "knight/kdeath.wav", 1, ATTN_NORM); + if (random() < 0.5) + knight_die1 (); + else + knight_dieb1 (); +}; + + +/*QUAKED monster_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_knight = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/knight.mdl"); + precache_model ("progs/h_knight.mdl"); + + precache_sound ("knight/kdeath.wav"); + precache_sound ("knight/khurt.wav"); + precache_sound ("knight/ksight.wav"); + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + precache_sound ("knight/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/knight.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 75; + + self.th_stand = knight_stand1; + self.th_walk = knight_walk1; + self.th_run = knight_run1; + self.th_melee = knight_atk1; + self.th_pain = knight_pain; + self.th_die = knight_die; + + walkmonster_start (); +}; diff --git a/quakec_hipnotic/monsters/ogre.qc b/quakec_hipnotic/monsters/ogre.qc new file mode 100644 index 0000000..e0499d2 --- /dev/null +++ b/quakec_hipnotic/monsters/ogre.qc @@ -0,0 +1,481 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OGRE + +============================================================================== +*/ + +$cd id1/models/ogre_c +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame swing1 swing2 swing3 swing4 swing5 swing6 swing7 +$frame swing8 swing9 swing10 swing11 swing12 swing13 swing14 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 smash13 smash14 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame painb1 painb2 painb3 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 paind14 paind15 paind16 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 death11 death12 +$frame death13 death14 + +$frame bdeath1 bdeath2 bdeath3 bdeath4 bdeath5 bdeath6 +$frame bdeath7 bdeath8 bdeath9 bdeath10 + +$frame pull1 pull2 pull3 pull4 pull5 pull6 pull7 pull8 pull9 pull10 pull11 + +//============================================================================= + + +void() OgreGrenadeExplode = +{ + T_RadiusDamage (self, self.owner, 40, world); + sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +void() OgreGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage == DAMAGE_AIM) + { + OgreGrenadeExplode(); + return; + } + sound (self, CHAN_VOICE, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +/* +================ +OgreFireGrenade +================ +*/ +void() OgreFireGrenade = +{ + local entity missile, mpuff; + + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + missile = spawn (); + +//JIM + missile.classname = "grenade"; + + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + +// set missile speed + + makevectors (self.angles); + + missile.velocity = normalize(self.enemy.origin - self.origin); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + + missile.avelocity = '300 300 300'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = OgreGrenadeTouch; + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = OgreGrenadeExplode; + + setmodel (missile, "progs/grenade.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); +}; + + +//============================================================================= + +/* +================ +chainsaw + +FIXME +================ +*/ +void(float side) chainsaw = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 4; + T_Damage (self.enemy, self, self, ldmg); + + if (side) + { + makevectors (self.angles); + if (side == 1) + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + else + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + + +void() ogre_stand1 =[ $stand1, ogre_stand2 ] {ai_stand();}; +void() ogre_stand2 =[ $stand2, ogre_stand3 ] {ai_stand();}; +void() ogre_stand3 =[ $stand3, ogre_stand4 ] {ai_stand();}; +void() ogre_stand4 =[ $stand4, ogre_stand5 ] {ai_stand();}; +void() ogre_stand5 =[ $stand5, ogre_stand6 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); +ai_stand(); +}; +void() ogre_stand6 =[ $stand6, ogre_stand7 ] {ai_stand();}; +void() ogre_stand7 =[ $stand7, ogre_stand8 ] {ai_stand();}; +void() ogre_stand8 =[ $stand8, ogre_stand9 ] {ai_stand();}; +void() ogre_stand9 =[ $stand9, ogre_stand1 ] {ai_stand();}; + +void() ogre_walk1 =[ $walk1, ogre_walk2 ] {ai_walk(3);}; +void() ogre_walk2 =[ $walk2, ogre_walk3 ] {ai_walk(2);}; +void() ogre_walk3 =[ $walk3, ogre_walk4 ] { +ai_walk(2); +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); +}; +void() ogre_walk4 =[ $walk4, ogre_walk5 ] {ai_walk(2);}; +void() ogre_walk5 =[ $walk5, ogre_walk6 ] {ai_walk(2);}; +void() ogre_walk6 =[ $walk6, ogre_walk7 ] { +ai_walk(5); +if (random() < 0.1) + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE); +}; +void() ogre_walk7 =[ $walk7, ogre_walk8 ] {ai_walk(3);}; +void() ogre_walk8 =[ $walk8, ogre_walk9 ] {ai_walk(2);}; +void() ogre_walk9 =[ $walk9, ogre_walk10 ] {ai_walk(3);}; +void() ogre_walk10 =[ $walk10, ogre_walk11 ] {ai_walk(1);}; +void() ogre_walk11 =[ $walk11, ogre_walk12 ] {ai_walk(2);}; +void() ogre_walk12 =[ $walk12, ogre_walk13 ] {ai_walk(3);}; +void() ogre_walk13 =[ $walk13, ogre_walk14 ] {ai_walk(3);}; +void() ogre_walk14 =[ $walk14, ogre_walk15 ] {ai_walk(3);}; +void() ogre_walk15 =[ $walk15, ogre_walk16 ] {ai_walk(3);}; +void() ogre_walk16 =[ $walk16, ogre_walk1 ] {ai_walk(4);}; + +void() ogre_run1 =[ $run1, ogre_run2 ] {ai_run(9); +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle2.wav", 1, ATTN_IDLE); +}; +void() ogre_run2 =[ $run2, ogre_run3 ] {ai_run(12);}; +void() ogre_run3 =[ $run3, ogre_run4 ] {ai_run(8);}; +void() ogre_run4 =[ $run4, ogre_run5 ] {ai_run(22);}; +void() ogre_run5 =[ $run5, ogre_run6 ] {ai_run(16);}; +void() ogre_run6 =[ $run6, ogre_run7 ] {ai_run(4);}; +void() ogre_run7 =[ $run7, ogre_run8 ] {ai_run(13);}; +void() ogre_run8 =[ $run8, ogre_run1 ] {ai_run(24);}; + +void() ogre_swing1 =[ $swing1, ogre_swing2 ] {ai_charge(11); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM); +}; +void() ogre_swing2 =[ $swing2, ogre_swing3 ] {ai_charge(1);}; +void() ogre_swing3 =[ $swing3, ogre_swing4 ] {ai_charge(4);}; +void() ogre_swing4 =[ $swing4, ogre_swing5 ] {ai_charge(13);}; +void() ogre_swing5 =[ $swing5, ogre_swing6 ] {ai_charge(9); chainsaw(0);self.angles_y = self.angles_y + random()*25;}; +void() ogre_swing6 =[ $swing6, ogre_swing7 ] {chainsaw(200);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing7 =[ $swing7, ogre_swing8 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing8 =[ $swing8, ogre_swing9 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing9 =[ $swing9, ogre_swing10 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing10 =[ $swing10, ogre_swing11 ] {chainsaw(-200);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing11 =[ $swing11, ogre_swing12 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing12 =[ $swing12, ogre_swing13 ] {ai_charge(3);}; +void() ogre_swing13 =[ $swing13, ogre_swing14 ] {ai_charge(8);}; +void() ogre_swing14 =[ $swing14, ogre_run1 ] {ai_charge(9);}; + +void() ogre_smash1 =[ $smash1, ogre_smash2 ] {ai_charge(6); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM); +}; +void() ogre_smash2 =[ $smash2, ogre_smash3 ] {ai_charge(0);}; +void() ogre_smash3 =[ $smash3, ogre_smash4 ] {ai_charge(0);}; +void() ogre_smash4 =[ $smash4, ogre_smash5 ] {ai_charge(1);}; +void() ogre_smash5 =[ $smash5, ogre_smash6 ] {ai_charge(4);}; +void() ogre_smash6 =[ $smash6, ogre_smash7 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash7 =[ $smash7, ogre_smash8 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash8 =[ $smash8, ogre_smash9 ] {ai_charge(10); chainsaw(0);}; +void() ogre_smash9 =[ $smash9, ogre_smash10 ] {ai_charge(13); chainsaw(0);}; +void() ogre_smash10 =[ $smash10, ogre_smash11 ] {chainsaw(1);}; +void() ogre_smash11 =[ $smash11, ogre_smash12 ] {ai_charge(2); chainsaw(0); +self.nextthink = self.nextthink + random()*0.2;}; // slight variation +void() ogre_smash12 =[ $smash12, ogre_smash13 ] {ai_charge();}; +void() ogre_smash13 =[ $smash13, ogre_smash14 ] {ai_charge(4);}; +void() ogre_smash14 =[ $smash14, ogre_run1 ] {ai_charge(12);}; + +void() ogre_nail1 =[ $shoot1, ogre_nail2 ] {ai_face();}; +void() ogre_nail2 =[ $shoot2, ogre_nail3 ] {ai_face();}; +void() ogre_nail3 =[ $shoot2, ogre_nail4 ] {ai_face();}; +void() ogre_nail4 =[ $shoot3, ogre_nail5 ] {ai_face();OgreFireGrenade();}; +void() ogre_nail5 =[ $shoot4, ogre_nail6 ] {ai_face();}; +void() ogre_nail6 =[ $shoot5, ogre_nail7 ] {ai_face();}; +void() ogre_nail7 =[ $shoot6, ogre_run1 ] {ai_face();}; + +void() ogre_pain1 =[ $pain1, ogre_pain2 ] {}; +void() ogre_pain2 =[ $pain2, ogre_pain3 ] {}; +void() ogre_pain3 =[ $pain3, ogre_pain4 ] {}; +void() ogre_pain4 =[ $pain4, ogre_pain5 ] {}; +void() ogre_pain5 =[ $pain5, ogre_run1 ] {}; + + +void() ogre_painb1 =[ $painb1, ogre_painb2 ] {}; +void() ogre_painb2 =[ $painb2, ogre_painb3 ] {}; +void() ogre_painb3 =[ $painb3, ogre_run1 ] {}; + + +void() ogre_painc1 =[ $painc1, ogre_painc2 ] {}; +void() ogre_painc2 =[ $painc2, ogre_painc3 ] {}; +void() ogre_painc3 =[ $painc3, ogre_painc4 ] {}; +void() ogre_painc4 =[ $painc4, ogre_painc5 ] {}; +void() ogre_painc5 =[ $painc5, ogre_painc6 ] {}; +void() ogre_painc6 =[ $painc6, ogre_run1 ] {}; + + +void() ogre_paind1 =[ $paind1, ogre_paind2 ] {}; +void() ogre_paind2 =[ $paind2, ogre_paind3 ] {ai_pain(10);}; +void() ogre_paind3 =[ $paind3, ogre_paind4 ] {ai_pain(9);}; +void() ogre_paind4 =[ $paind4, ogre_paind5 ] {ai_pain(4);}; +void() ogre_paind5 =[ $paind5, ogre_paind6 ] {}; +void() ogre_paind6 =[ $paind6, ogre_paind7 ] {}; +void() ogre_paind7 =[ $paind7, ogre_paind8 ] {}; +void() ogre_paind8 =[ $paind8, ogre_paind9 ] {}; +void() ogre_paind9 =[ $paind9, ogre_paind10 ] {}; +void() ogre_paind10=[ $paind10, ogre_paind11 ] {}; +void() ogre_paind11=[ $paind11, ogre_paind12 ] {}; +void() ogre_paind12=[ $paind12, ogre_paind13 ] {}; +void() ogre_paind13=[ $paind13, ogre_paind14 ] {}; +void() ogre_paind14=[ $paind14, ogre_paind15 ] {}; +void() ogre_paind15=[ $paind15, ogre_paind16 ] {}; +void() ogre_paind16=[ $paind16, ogre_run1 ] {}; + +void() ogre_paine1 =[ $paine1, ogre_paine2 ] {}; +void() ogre_paine2 =[ $paine2, ogre_paine3 ] {ai_pain(10);}; +void() ogre_paine3 =[ $paine3, ogre_paine4 ] {ai_pain(9);}; +void() ogre_paine4 =[ $paine4, ogre_paine5 ] {ai_pain(4);}; +void() ogre_paine5 =[ $paine5, ogre_paine6 ] {}; +void() ogre_paine6 =[ $paine6, ogre_paine7 ] {}; +void() ogre_paine7 =[ $paine7, ogre_paine8 ] {}; +void() ogre_paine8 =[ $paine8, ogre_paine9 ] {}; +void() ogre_paine9 =[ $paine9, ogre_paine10 ] {}; +void() ogre_paine10=[ $paine10, ogre_paine11 ] {}; +void() ogre_paine11=[ $paine11, ogre_paine12 ] {}; +void() ogre_paine12=[ $paine12, ogre_paine13 ] {}; +void() ogre_paine13=[ $paine13, ogre_paine14 ] {}; +void() ogre_paine14=[ $paine14, ogre_paine15 ] {}; +void() ogre_paine15=[ $paine15, ogre_run1 ] {}; + + +void(entity attacker, float damage) ogre_pain = +{ + local float r; + +// don't make multiple pain sounds right after each other + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "ogre/ogpain1.wav", 1, ATTN_NORM); + + r = random(); + + if (r < 0.25) + { + ogre_pain1 (); + self.pain_finished = time + 1; + } + else if (r < 0.5) + { + ogre_painb1 (); + self.pain_finished = time + 1; + } + else if (r < 0.75) + { + ogre_painc1 (); + self.pain_finished = time + 1; + } + else if (r < 0.88) + { + ogre_paind1 (); + self.pain_finished = time + 2; + } + else + { + ogre_paine1 (); + self.pain_finished = time + 2; + } +}; + +void() ogre_die1 =[ $death1, ogre_die2 ] {}; +void() ogre_die2 =[ $death2, ogre_die3 ] {}; +void() ogre_die3 =[ $death3, ogre_die4 ] +{self.solid = SOLID_NOT; +self.ammo_rockets = 2;DropBackpack();}; +void() ogre_die4 =[ $death4, ogre_die5 ] {}; +void() ogre_die5 =[ $death5, ogre_die6 ] {}; +void() ogre_die6 =[ $death6, ogre_die7 ] {}; +void() ogre_die7 =[ $death7, ogre_die8 ] {}; +void() ogre_die8 =[ $death8, ogre_die9 ] {}; +void() ogre_die9 =[ $death9, ogre_die10 ] {}; +void() ogre_die10 =[ $death10, ogre_die11 ] {}; +void() ogre_die11 =[ $death11, ogre_die12 ] {}; +void() ogre_die12 =[ $death12, ogre_die13 ] {}; +void() ogre_die13 =[ $death13, ogre_die14 ] {}; +void() ogre_die14 =[ $death14, ogre_die14 ] {}; + +void() ogre_bdie1 =[ $bdeath1, ogre_bdie2 ] {}; +void() ogre_bdie2 =[ $bdeath2, ogre_bdie3 ] {ai_forward(5);}; +void() ogre_bdie3 =[ $bdeath3, ogre_bdie4 ] +{self.solid = SOLID_NOT; +self.ammo_rockets = 2;DropBackpack();}; +void() ogre_bdie4 =[ $bdeath4, ogre_bdie5 ] {ai_forward(1);}; +void() ogre_bdie5 =[ $bdeath5, ogre_bdie6 ] {ai_forward(3);}; +void() ogre_bdie6 =[ $bdeath6, ogre_bdie7 ] {ai_forward(7);}; +void() ogre_bdie7 =[ $bdeath7, ogre_bdie8 ] {ai_forward(25);}; +void() ogre_bdie8 =[ $bdeath8, ogre_bdie9 ] {}; +void() ogre_bdie9 =[ $bdeath9, ogre_bdie10 ] {}; +void() ogre_bdie10 =[ $bdeath10, ogre_bdie10 ] {}; + +void() ogre_die = +{ +// check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_ogre.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "ogre/ogdth.wav", 1, ATTN_NORM); + + if (random() < 0.5) + ogre_die1 (); + else + ogre_bdie1 (); +}; + +void() ogre_melee = +{ + if (random() > 0.5) + ogre_smash1 (); + else + ogre_swing1 (); +}; + + +/*QUAKED monster_ogre (1 0 0) (-32 -32 -24) (32 32 64) Ambush + +*/ +void() monster_ogre = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/ogre.mdl"); + precache_model ("progs/h_ogre.mdl"); + precache_model ("progs/grenade.mdl"); + + precache_sound ("ogre/ogdrag.wav"); + precache_sound ("ogre/ogdth.wav"); + precache_sound ("ogre/ogidle.wav"); + precache_sound ("ogre/ogidle2.wav"); + precache_sound ("ogre/ogpain1.wav"); + precache_sound ("ogre/ogsawatk.wav"); + precache_sound ("ogre/ogwake.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/ogre.mdl"); + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 200; + + self.th_stand = ogre_stand1; + self.th_walk = ogre_walk1; + self.th_run = ogre_run1; + self.th_die = ogre_die; + self.th_melee = ogre_melee; + self.th_missile = ogre_nail1; + self.th_pain = ogre_pain; + + walkmonster_start(); +}; + +void() monster_ogre_marksman = +{ + monster_ogre (); +}; diff --git a/quakec_hipnotic/monsters/rotfish.qc b/quakec_hipnotic/monsters/rotfish.qc new file mode 100644 index 0000000..763bdc8 --- /dev/null +++ b/quakec_hipnotic/monsters/rotfish.qc @@ -0,0 +1,228 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +$cd id1/models/fish +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 attack11 attack12 attack13 +$frame attack14 attack15 attack16 attack17 attack18 + +$frame death1 death2 death3 death4 death5 death6 death7 +$frame death8 death9 death10 death11 death12 death13 death14 death15 +$frame death16 death17 death18 death19 death20 death21 + +$frame swim1 swim2 swim3 swim4 swim5 swim6 swim7 swim8 +$frame swim9 swim10 swim11 swim12 swim13 swim14 swim15 swim16 swim17 +$frame swim18 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 pain7 pain8 +$frame pain9 + +void() swimmonster_start; + +void() f_stand1 =[ $swim1, f_stand2 ] {ai_stand();}; +void() f_stand2 =[ $swim2, f_stand3 ] {ai_stand();}; +void() f_stand3 =[ $swim3, f_stand4 ] {ai_stand();}; +void() f_stand4 =[ $swim4, f_stand5 ] {ai_stand();}; +void() f_stand5 =[ $swim5, f_stand6 ] {ai_stand();}; +void() f_stand6 =[ $swim6, f_stand7 ] {ai_stand();}; +void() f_stand7 =[ $swim7, f_stand8 ] {ai_stand();}; +void() f_stand8 =[ $swim8, f_stand9 ] {ai_stand();}; +void() f_stand9 =[ $swim9, f_stand10 ] {ai_stand();}; +void() f_stand10 =[ $swim10, f_stand11 ] {ai_stand();}; +void() f_stand11 =[ $swim11, f_stand12 ] {ai_stand();}; +void() f_stand12 =[ $swim12, f_stand13 ] {ai_stand();}; +void() f_stand13 =[ $swim13, f_stand14 ] {ai_stand();}; +void() f_stand14 =[ $swim14, f_stand15 ] {ai_stand();}; +void() f_stand15 =[ $swim15, f_stand16 ] {ai_stand();}; +void() f_stand16 =[ $swim16, f_stand17 ] {ai_stand();}; +void() f_stand17 =[ $swim17, f_stand18 ] {ai_stand();}; +void() f_stand18 =[ $swim18, f_stand1 ] {ai_stand();}; + +void() f_walk1 =[ $swim1, f_walk2 ] {ai_walk(8);}; +void() f_walk2 =[ $swim2, f_walk3 ] {ai_walk(8);}; +void() f_walk3 =[ $swim3, f_walk4 ] {ai_walk(8);}; +void() f_walk4 =[ $swim4, f_walk5 ] {ai_walk(8);}; +void() f_walk5 =[ $swim5, f_walk6 ] {ai_walk(8);}; +void() f_walk6 =[ $swim6, f_walk7 ] {ai_walk(8);}; +void() f_walk7 =[ $swim7, f_walk8 ] {ai_walk(8);}; +void() f_walk8 =[ $swim8, f_walk9 ] {ai_walk(8);}; +void() f_walk9 =[ $swim9, f_walk10 ] {ai_walk(8);}; +void() f_walk10 =[ $swim10, f_walk11 ] {ai_walk(8);}; +void() f_walk11 =[ $swim11, f_walk12 ] {ai_walk(8);}; +void() f_walk12 =[ $swim12, f_walk13 ] {ai_walk(8);}; +void() f_walk13 =[ $swim13, f_walk14 ] {ai_walk(8);}; +void() f_walk14 =[ $swim14, f_walk15 ] {ai_walk(8);}; +void() f_walk15 =[ $swim15, f_walk16 ] {ai_walk(8);}; +void() f_walk16 =[ $swim16, f_walk17 ] {ai_walk(8);}; +void() f_walk17 =[ $swim17, f_walk18 ] {ai_walk(8);}; +void() f_walk18 =[ $swim18, f_walk1 ] {ai_walk(8);}; + +void() f_run1 =[ $swim1, f_run2 ] {ai_run(12); + if (random() < 0.5) + sound (self, CHAN_VOICE, "fish/idle.wav", 1, ATTN_NORM); +}; +void() f_run2 =[ $swim3, f_run3 ] {ai_run(12);}; +void() f_run3 =[ $swim5, f_run4 ] {ai_run(12);}; +void() f_run4 =[ $swim7, f_run5 ] {ai_run(12);}; +void() f_run5 =[ $swim9, f_run6 ] {ai_run(12);}; +void() f_run6 =[ $swim11, f_run7 ] {ai_run(12);}; +void() f_run7 =[ $swim13, f_run8 ] {ai_run(12);}; +void() f_run8 =[ $swim15, f_run9 ] {ai_run(12);}; +void() f_run9 =[ $swim17, f_run1 ] {ai_run(12);}; + +void() fish_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + sound (self, CHAN_VOICE, "fish/bite.wav", 1, ATTN_NORM); + ldmg = (random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() f_attack1 =[ $attack1, f_attack2 ] {ai_charge(10);}; +void() f_attack2 =[ $attack2, f_attack3 ] {ai_charge(10);}; +void() f_attack3 =[ $attack3, f_attack4 ] {fish_melee();}; +void() f_attack4 =[ $attack4, f_attack5 ] {ai_charge(10);}; +void() f_attack5 =[ $attack5, f_attack6 ] {ai_charge(10);}; +void() f_attack6 =[ $attack6, f_attack7 ] {ai_charge(10);}; +void() f_attack7 =[ $attack7, f_attack8 ] {ai_charge(10);}; +void() f_attack8 =[ $attack8, f_attack9 ] {ai_charge(10);}; +void() f_attack9 =[ $attack9, f_attack10] {fish_melee();}; +void() f_attack10 =[ $attack10, f_attack11] {ai_charge(10);}; +void() f_attack11 =[ $attack11, f_attack12] {ai_charge(10);}; +void() f_attack12 =[ $attack12, f_attack13] {ai_charge(10);}; +void() f_attack13 =[ $attack13, f_attack14] {ai_charge(10);}; +void() f_attack14 =[ $attack14, f_attack15] {ai_charge(10);}; +void() f_attack15 =[ $attack15, f_attack16] {fish_melee();}; +void() f_attack16 =[ $attack16, f_attack17] {ai_charge(10);}; +void() f_attack17 =[ $attack17, f_attack18] {ai_charge(10);}; +void() f_attack18 =[ $attack18, f_run1 ] {ai_charge(10);}; + +void() f_death1 =[ $death1, f_death2 ] { +sound (self, CHAN_VOICE, "fish/death.wav", 1, ATTN_NORM); +}; +void() f_death2 =[ $death2, f_death3 ] {}; +void() f_death3 =[ $death3, f_death4 ] {}; +void() f_death4 =[ $death4, f_death5 ] {}; +//MED 01/15/97 moved solid to here +void() f_death5 =[ $death5, f_death6 ] {self.solid = SOLID_NOT;}; +void() f_death6 =[ $death6, f_death7 ] {}; +void() f_death7 =[ $death7, f_death8 ] {}; +void() f_death8 =[ $death8, f_death9 ] {}; +void() f_death9 =[ $death9, f_death10 ] {}; +void() f_death10 =[ $death10, f_death11 ] {}; +void() f_death11 =[ $death11, f_death12 ] {}; +void() f_death12 =[ $death12, f_death13 ] {}; +void() f_death13 =[ $death13, f_death14 ] {}; +void() f_death14 =[ $death14, f_death15 ] {}; +void() f_death15 =[ $death15, f_death16 ] {}; +void() f_death16 =[ $death16, f_death17 ] {}; +void() f_death17 =[ $death17, f_death18 ] {}; +void() f_death18 =[ $death18, f_death19 ] {}; +void() f_death19 =[ $death19, f_death20 ] {}; +void() f_death20 =[ $death20, f_death21 ] {}; +//MED 01/15/97 removed solid from here +//void() f_death21 =[ $death21, f_death21 ] {self.solid = SOLID_NOT;}; +void() f_death21 =[ $death21, f_death21 ] {}; + +//MED 01/15/97 added fish_die +void() fish_die = +{ +// check for gib + if (self.health < -20) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + self.think = SUB_Remove; + self.nextthink= time + 0.1; + return; + } + +// regular death + f_death1 (); +}; + +void() f_pain1 =[ $pain1, f_pain2 ] {}; +void() f_pain2 =[ $pain2, f_pain3 ] {ai_pain(6);}; +void() f_pain3 =[ $pain3, f_pain4 ] {ai_pain(6);}; +void() f_pain4 =[ $pain4, f_pain5 ] {ai_pain(6);}; +void() f_pain5 =[ $pain5, f_pain6 ] {ai_pain(6);}; +void() f_pain6 =[ $pain6, f_pain7 ] {ai_pain(6);}; +void() f_pain7 =[ $pain7, f_pain8 ] {ai_pain(6);}; +void() f_pain8 =[ $pain8, f_pain9 ] {ai_pain(6);}; +void() f_pain9 =[ $pain9, f_run1 ] {ai_pain(6);}; + +void(entity attacker, float damage) fish_pain = +{ + +// fish allways do pain frames + f_pain1 (); +}; + + + +/*QUAKED monster_fish (1 0 0) (-16 -16 -24) (16 16 24) Ambush +*/ +void() monster_fish = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/fish.mdl"); + + precache_sound2 ("fish/death.wav"); + precache_sound2 ("fish/bite.wav"); + precache_sound2 ("fish/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/fish.mdl"); + + setsize (self, '-16 -16 -24', '16 16 24'); + self.health = 25; + + self.th_stand = f_stand1; + self.th_walk = f_walk1; + self.th_run = f_run1; +//MED 01/15/97 added fish death +// self.th_die = f_death1; + self.th_die = fish_die; + self.th_pain = fish_pain; + self.th_melee = f_attack1; + + swimmonster_start (); +}; diff --git a/quakec_hipnotic/monsters/rottweiler.qc b/quakec_hipnotic/monsters/rottweiler.qc new file mode 100644 index 0000000..bc6f2c5 --- /dev/null +++ b/quakec_hipnotic/monsters/rottweiler.qc @@ -0,0 +1,382 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DOG + +============================================================================== +*/ +$cd id1/models/dog +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + + +void() dog_leap1; +void() dog_run1; + +/* +================ +dog_bite + +================ +*/ +void() dog_bite = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + + ai_charge(10); + + if (!CanDamage (self.enemy, self)) + return; + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 8; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() Dog_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage) + { + if ( vlen(self.velocity) > 300 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = dog_leap1; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = dog_run1; + self.nextthink = time + 0.1; +}; + + +void() dog_stand1 =[ $stand1, dog_stand2 ] {ai_stand();}; +void() dog_stand2 =[ $stand2, dog_stand3 ] {ai_stand();}; +void() dog_stand3 =[ $stand3, dog_stand4 ] {ai_stand();}; +void() dog_stand4 =[ $stand4, dog_stand5 ] {ai_stand();}; +void() dog_stand5 =[ $stand5, dog_stand6 ] {ai_stand();}; +void() dog_stand6 =[ $stand6, dog_stand7 ] {ai_stand();}; +void() dog_stand7 =[ $stand7, dog_stand8 ] {ai_stand();}; +void() dog_stand8 =[ $stand8, dog_stand9 ] {ai_stand();}; +void() dog_stand9 =[ $stand9, dog_stand1 ] {ai_stand();}; + +void() dog_walk1 =[ $walk1 , dog_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); +ai_walk(8);}; +void() dog_walk2 =[ $walk2 , dog_walk3 ] {ai_walk(8);}; +void() dog_walk3 =[ $walk3 , dog_walk4 ] {ai_walk(8);}; +void() dog_walk4 =[ $walk4 , dog_walk5 ] {ai_walk(8);}; +void() dog_walk5 =[ $walk5 , dog_walk6 ] {ai_walk(8);}; +void() dog_walk6 =[ $walk6 , dog_walk7 ] {ai_walk(8);}; +void() dog_walk7 =[ $walk7 , dog_walk8 ] {ai_walk(8);}; +void() dog_walk8 =[ $walk8 , dog_walk1 ] {ai_walk(8);}; + +void() dog_run1 =[ $run1 , dog_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); +ai_run(16);}; +void() dog_run2 =[ $run2 , dog_run3 ] {ai_run(32);}; +void() dog_run3 =[ $run3 , dog_run4 ] {ai_run(32);}; +void() dog_run4 =[ $run4 , dog_run5 ] {ai_run(20);}; +void() dog_run5 =[ $run5 , dog_run6 ] {ai_run(64);}; +void() dog_run6 =[ $run6 , dog_run7 ] {ai_run(32);}; +void() dog_run7 =[ $run7 , dog_run8 ] {ai_run(16);}; +void() dog_run8 =[ $run8 , dog_run9 ] {ai_run(32);}; +void() dog_run9 =[ $run9 , dog_run10 ] {ai_run(32);}; +void() dog_run10 =[ $run10 , dog_run11 ] {ai_run(20);}; +void() dog_run11 =[ $run11 , dog_run12 ] {ai_run(64);}; +void() dog_run12 =[ $run12 , dog_run1 ] {ai_run(32);}; + +void() dog_atta1 =[ $attack1, dog_atta2 ] {ai_charge(10);}; +void() dog_atta2 =[ $attack2, dog_atta3 ] {ai_charge(10);}; +void() dog_atta3 =[ $attack3, dog_atta4 ] {ai_charge(10);}; +void() dog_atta4 =[ $attack4, dog_atta5 ] { +sound (self, CHAN_VOICE, "dog/dattack1.wav", 1, ATTN_NORM); +dog_bite();}; +void() dog_atta5 =[ $attack5, dog_atta6 ] {ai_charge(10);}; +void() dog_atta6 =[ $attack6, dog_atta7 ] {ai_charge(10);}; +void() dog_atta7 =[ $attack7, dog_atta8 ] {ai_charge(10);}; +void() dog_atta8 =[ $attack8, dog_run1 ] {ai_charge(10);}; + +void() dog_leap1 =[ $leap1, dog_leap2 ] {ai_face();}; +void() dog_leap2 =[ $leap2, dog_leap3 ] +{ + ai_face(); + + self.touch = Dog_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 300 + '0 0 200'; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; + +void() dog_leap3 =[ $leap3, dog_leap4 ] {}; +void() dog_leap4 =[ $leap4, dog_leap5 ] {}; +void() dog_leap5 =[ $leap5, dog_leap6 ] {}; +void() dog_leap6 =[ $leap6, dog_leap7 ] {}; +void() dog_leap7 =[ $leap7, dog_leap8 ] {}; +void() dog_leap8 =[ $leap8, dog_leap9 ] {}; +void() dog_leap9 =[ $leap9, dog_leap9 ] {}; + +void() dog_pain1 =[ $pain1 , dog_pain2 ] {}; +void() dog_pain2 =[ $pain2 , dog_pain3 ] {}; +void() dog_pain3 =[ $pain3 , dog_pain4 ] {}; +void() dog_pain4 =[ $pain4 , dog_pain5 ] {}; +void() dog_pain5 =[ $pain5 , dog_pain6 ] {}; +void() dog_pain6 =[ $pain6 , dog_run1 ] {}; + +void() dog_painb1 =[ $painb1 , dog_painb2 ] {}; +void() dog_painb2 =[ $painb2 , dog_painb3 ] {}; +void() dog_painb3 =[ $painb3 , dog_painb4 ] {ai_pain(4);}; +void() dog_painb4 =[ $painb4 , dog_painb5 ] {ai_pain(12);}; +void() dog_painb5 =[ $painb5 , dog_painb6 ] {ai_pain(12);}; +void() dog_painb6 =[ $painb6 , dog_painb7 ] {ai_pain(2);}; +void() dog_painb7 =[ $painb7 , dog_painb8 ] {}; +void() dog_painb8 =[ $painb8 , dog_painb9 ] {ai_pain(4);}; +void() dog_painb9 =[ $painb9 , dog_painb10 ] {}; +void() dog_painb10 =[ $painb10 , dog_painb11 ] {ai_pain(10);}; +void() dog_painb11 =[ $painb11 , dog_painb12 ] {}; +void() dog_painb12 =[ $painb12 , dog_painb13 ] {}; +void() dog_painb13 =[ $painb13 , dog_painb14 ] {}; +void() dog_painb14 =[ $painb14 , dog_painb15 ] {}; +void() dog_painb15 =[ $painb15 , dog_painb16 ] {}; +void() dog_painb16 =[ $painb16 , dog_run1 ] {}; + +void() dog_pain = +{ + sound (self, CHAN_VOICE, "dog/dpain1.wav", 1, ATTN_NORM); + + if (random() > 0.5) + dog_pain1 (); + else + dog_painb1 (); +}; + +void() dog_die1 =[ $death1, dog_die2 ] {}; +void() dog_die2 =[ $death2, dog_die3 ] {}; +void() dog_die3 =[ $death3, dog_die4 ] {}; +void() dog_die4 =[ $death4, dog_die5 ] {}; +void() dog_die5 =[ $death5, dog_die6 ] {}; +void() dog_die6 =[ $death6, dog_die7 ] {}; +void() dog_die7 =[ $death7, dog_die8 ] {}; +void() dog_die8 =[ $death8, dog_die9 ] {}; +void() dog_die9 =[ $death9, dog_die9 ] {}; + +void() dog_dieb1 =[ $deathb1, dog_dieb2 ] {}; +void() dog_dieb2 =[ $deathb2, dog_dieb3 ] {}; +void() dog_dieb3 =[ $deathb3, dog_dieb4 ] {}; +void() dog_dieb4 =[ $deathb4, dog_dieb5 ] {}; +void() dog_dieb5 =[ $deathb5, dog_dieb6 ] {}; +void() dog_dieb6 =[ $deathb6, dog_dieb7 ] {}; +void() dog_dieb7 =[ $deathb7, dog_dieb8 ] {}; +void() dog_dieb8 =[ $deathb8, dog_dieb9 ] {}; +void() dog_dieb9 =[ $deathb9, dog_dieb9 ] {}; + + +void() dog_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowHead ("progs/h_dog.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "dog/ddeath.wav", 1, ATTN_NORM); + self.solid = SOLID_NOT; + + if (random() > 0.5) + dog_die1 (); + else + dog_dieb1 (); +}; + +//============================================================================ + +/* +============== +CheckDogMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDogMelee = +{ + if (enemy_range == RANGE_MELEE) + { // FIXME: check canreach + self.attack_state = AS_MELEE; + return TRUE; + } + return FALSE; +}; + +/* +============== +CheckDogJump + +============== +*/ +float() CheckDogJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 80) + return FALSE; + + if (d > 150) + return FALSE; + + return TRUE; +}; + +float() DogCheckAttack = +{ + local vector vec; + +// if close enough for slashing, go for it + if (CheckDogMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDogJump ()) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +/*QUAKED monster_dog (1 0 0) (-32 -32 -24) (32 32 40) Ambush + +*/ +void() monster_dog = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/h_dog.mdl"); + precache_model ("progs/dog.mdl"); + + precache_sound ("dog/dattack1.wav"); + precache_sound ("dog/ddeath.wav"); + precache_sound ("dog/dpain1.wav"); + precache_sound ("dog/dsight.wav"); + precache_sound ("dog/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/dog.mdl"); + + setsize (self, '-32 -32 -24', '32 32 40'); + self.health = 25; + + self.th_stand = dog_stand1; + self.th_walk = dog_walk1; + self.th_run = dog_run1; + self.th_pain = dog_pain; + self.th_die = dog_die; + self.th_melee = dog_atta1; + self.th_missile = dog_leap1; + + walkmonster_start(); +}; diff --git a/quakec_hipnotic/monsters/scrag.qc b/quakec_hipnotic/monsters/scrag.qc new file mode 100644 index 0000000..24a63cf --- /dev/null +++ b/quakec_hipnotic/monsters/scrag.qc @@ -0,0 +1,429 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +WIZARD + +============================================================================== +*/ + +$cd id1/models/a_wizard +$origin 0 0 24 +$base wizbase +$skin wizbase + +$frame hover1 hover2 hover3 hover4 hover5 hover6 hover7 hover8 +$frame hover9 hover10 hover11 hover12 hover13 hover14 hover15 + +$frame fly1 fly2 fly3 fly4 fly5 fly6 fly7 fly8 fly9 fly10 +$frame fly11 fly12 fly13 fly14 + +$frame magatt1 magatt2 magatt3 magatt4 magatt5 magatt6 magatt7 +$frame magatt8 magatt9 magatt10 magatt11 magatt12 magatt13 + +$frame pain1 pain2 pain3 pain4 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 + +/* +============================================================================== + +WIZARD + +If the player moves behind cover before the missile is launched, launch it +at the last visible spot with no velocity leading, in hopes that the player +will duck back out and catch it. +============================================================================== +*/ + +/* +============= +LaunchMissile + +Sets the given entities velocity and angles so that it will hit self.enemy +if self.enemy maintains it's current velocity +0.1 is moderately accurate, 0.0 is totally accurate +============= +*/ +void(entity missile, float mspeed, float accuracy) LaunchMissile = +{ + local vector vec, move; + local float fly; + + makevectors (self.angles); + +// set missile speed + vec = self.enemy.origin + self.enemy.mins + self.enemy.size * 0.7 - missile.origin; + +// calc aproximate time for missile to reach vec + fly = vlen (vec) / mspeed; + +// get the entities xy velocity + move = self.enemy.velocity; + move_z = 0; + +// project the target forward in time + vec = vec + move * fly; + + vec = normalize(vec); + vec = vec + accuracy*v_up*(random()- 0.5) + accuracy*v_right*(random()- 0.5); + + missile.velocity = vec * mspeed; + + missile.angles = '0 0 0'; + missile.angles_y = vectoyaw(missile.velocity); + +// set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; +}; + + +void() wiz_run1; +void() wiz_side1; + +/* +================= +WizardCheckAttack +================= +*/ +float() WizardCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (time < self.attack_finished) + return FALSE; + if (!enemy_vis) + return FALSE; + + if (enemy_range == RANGE_FAR) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != targ) + { // don't have a clear shot, so move to a side + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.6; + else if (enemy_range == RANGE_MID) + chance = 0.2; + else + chance = 0; + + if (random () < chance) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + if (enemy_range == RANGE_MID) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + } + else + { + if (self.attack_state != AS_SLIDING) + { + self.attack_state = AS_SLIDING; + wiz_side1 (); + } + } + + return FALSE; +}; + +/* +================= +WizardAttackFinished +================= +*/ +float() WizardAttackFinished = +{ + if (enemy_range >= RANGE_MID || !enemy_vis) + { + self.attack_state = AS_STRAIGHT; + self.think = wiz_run1; + } + else + { + self.attack_state = AS_SLIDING; + self.think = wiz_side1; + } +}; + +/* +============================================================================== + +FAST ATTACKS + +============================================================================== +*/ + +void() Wiz_FastFire = +{ + local vector vec; + local vector dst; + + if (self.owner.health > 0) + { + self.owner.effects = self.owner.effects | EF_MUZZLEFLASH; + + makevectors (self.enemy.angles); + dst = self.enemy.origin - 13*self.movedir; + + vec = normalize(dst - self.origin); + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + launch_spike (self.origin, vec); + newmis.velocity = vec*600; + newmis.owner = self.owner; + newmis.classname = "wizspike"; + setmodel (newmis, "progs/w_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + } + + remove (self); +}; + + +void() Wiz_StartFast = +{ + local entity missile; + + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + self.v_angle = self.angles; + makevectors (self.angles); + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 0.6; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right*14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.8; + missile.think = Wiz_FastFire; + missile.movedir = v_right; + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 1; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right* -14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.3; + missile.think = Wiz_FastFire; + missile.movedir = VEC_ORIGIN - v_right; +}; + + + +void() Wiz_idlesound = +{ +local float wr; + wr = random() * 5; + + if (self.waitmin < time) + { + self.waitmin = time + 2; + if (wr > 4.5) + sound (self, CHAN_VOICE, "wizard/widle1.wav", 1, ATTN_IDLE); + if (wr < 1.5) + sound (self, CHAN_VOICE, "wizard/widle2.wav", 1, ATTN_IDLE); + } + return; +}; + +void() wiz_stand1 =[ $hover1, wiz_stand2 ] {ai_stand();}; +void() wiz_stand2 =[ $hover2, wiz_stand3 ] {ai_stand();}; +void() wiz_stand3 =[ $hover3, wiz_stand4 ] {ai_stand();}; +void() wiz_stand4 =[ $hover4, wiz_stand5 ] {ai_stand();}; +void() wiz_stand5 =[ $hover5, wiz_stand6 ] {ai_stand();}; +void() wiz_stand6 =[ $hover6, wiz_stand7 ] {ai_stand();}; +void() wiz_stand7 =[ $hover7, wiz_stand8 ] {ai_stand();}; +void() wiz_stand8 =[ $hover8, wiz_stand1 ] {ai_stand();}; + +void() wiz_walk1 =[ $hover1, wiz_walk2 ] {ai_walk(8); +Wiz_idlesound();}; +void() wiz_walk2 =[ $hover2, wiz_walk3 ] {ai_walk(8);}; +void() wiz_walk3 =[ $hover3, wiz_walk4 ] {ai_walk(8);}; +void() wiz_walk4 =[ $hover4, wiz_walk5 ] {ai_walk(8);}; +void() wiz_walk5 =[ $hover5, wiz_walk6 ] {ai_walk(8);}; +void() wiz_walk6 =[ $hover6, wiz_walk7 ] {ai_walk(8);}; +void() wiz_walk7 =[ $hover7, wiz_walk8 ] {ai_walk(8);}; +void() wiz_walk8 =[ $hover8, wiz_walk1 ] {ai_walk(8);}; + +void() wiz_side1 =[ $hover1, wiz_side2 ] {ai_run(8); +Wiz_idlesound();}; +void() wiz_side2 =[ $hover2, wiz_side3 ] {ai_run(8);}; +void() wiz_side3 =[ $hover3, wiz_side4 ] {ai_run(8);}; +void() wiz_side4 =[ $hover4, wiz_side5 ] {ai_run(8);}; +void() wiz_side5 =[ $hover5, wiz_side6 ] {ai_run(8);}; +void() wiz_side6 =[ $hover6, wiz_side7 ] {ai_run(8);}; +void() wiz_side7 =[ $hover7, wiz_side8 ] {ai_run(8);}; +void() wiz_side8 =[ $hover8, wiz_side1 ] {ai_run(8);}; + +void() wiz_run1 =[ $fly1, wiz_run2 ] {ai_run(16); +Wiz_idlesound(); +}; +void() wiz_run2 =[ $fly2, wiz_run3 ] {ai_run(16);}; +void() wiz_run3 =[ $fly3, wiz_run4 ] {ai_run(16);}; +void() wiz_run4 =[ $fly4, wiz_run5 ] {ai_run(16);}; +void() wiz_run5 =[ $fly5, wiz_run6 ] {ai_run(16);}; +void() wiz_run6 =[ $fly6, wiz_run7 ] {ai_run(16);}; +void() wiz_run7 =[ $fly7, wiz_run8 ] {ai_run(16);}; +void() wiz_run8 =[ $fly8, wiz_run9 ] {ai_run(16);}; +void() wiz_run9 =[ $fly9, wiz_run10 ] {ai_run(16);}; +void() wiz_run10 =[ $fly10, wiz_run11 ] {ai_run(16);}; +void() wiz_run11 =[ $fly11, wiz_run12 ] {ai_run(16);}; +void() wiz_run12 =[ $fly12, wiz_run13 ] {ai_run(16);}; +void() wiz_run13 =[ $fly13, wiz_run14 ] {ai_run(16);}; +void() wiz_run14 =[ $fly14, wiz_run1 ] {ai_run(16);}; + +void() wiz_fast1 =[ $magatt1, wiz_fast2 ] {ai_face();Wiz_StartFast();}; +void() wiz_fast2 =[ $magatt2, wiz_fast3 ] {ai_face();}; +void() wiz_fast3 =[ $magatt3, wiz_fast4 ] {ai_face();}; +void() wiz_fast4 =[ $magatt4, wiz_fast5 ] {ai_face();}; +void() wiz_fast5 =[ $magatt5, wiz_fast6 ] {ai_face();}; +void() wiz_fast6 =[ $magatt6, wiz_fast7 ] {ai_face();}; +void() wiz_fast7 =[ $magatt5, wiz_fast8 ] {ai_face();}; +void() wiz_fast8 =[ $magatt4, wiz_fast9 ] {ai_face();}; +void() wiz_fast9 =[ $magatt3, wiz_fast10 ] {ai_face();}; +void() wiz_fast10 =[ $magatt2, wiz_run1 ] {ai_face();SUB_AttackFinished(2);WizardAttackFinished ();}; + +void() wiz_pain1 =[ $pain1, wiz_pain2 ] {}; +void() wiz_pain2 =[ $pain2, wiz_pain3 ] {}; +void() wiz_pain3 =[ $pain3, wiz_pain4 ] {}; +void() wiz_pain4 =[ $pain4, wiz_run1 ] {}; + +void() wiz_death1 =[ $death1, wiz_death2 ] { + +self.velocity_x = -200 + 400*random(); +self.velocity_y = -200 + 400*random(); +self.velocity_z = 100 + 100*random(); +self.flags = self.flags - (self.flags & FL_ONGROUND); +sound (self, CHAN_VOICE, "wizard/wdeath.wav", 1, ATTN_NORM); +}; +void() wiz_death2 =[ $death2, wiz_death3 ] {}; +void() wiz_death3 =[ $death3, wiz_death4 ]{self.solid = SOLID_NOT;}; +void() wiz_death4 =[ $death4, wiz_death5 ] {}; +void() wiz_death5 =[ $death5, wiz_death6 ] {}; +void() wiz_death6 =[ $death6, wiz_death7 ] {}; +void() wiz_death7 =[ $death7, wiz_death8 ] {}; +void() wiz_death8 =[ $death8, wiz_death8 ] {}; + +void() wiz_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_wizard.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + return; + } + + wiz_death1 (); +}; + + +void(entity attacker, float damage) Wiz_Pain = +{ + sound (self, CHAN_VOICE, "wizard/wpain.wav", 1, ATTN_NORM); + if (random()*70 > damage) + return; // didn't flinch + + wiz_pain1 (); +}; + + +void() Wiz_Missile = +{ + wiz_fast1(); +}; + +/*QUAKED monster_wizard (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_wizard = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/wizard.mdl"); + precache_model ("progs/h_wizard.mdl"); + precache_model ("progs/w_spike.mdl"); + + precache_sound ("wizard/hit.wav"); // used by c code + precache_sound ("wizard/wattack.wav"); + precache_sound ("wizard/wdeath.wav"); + precache_sound ("wizard/widle1.wav"); + precache_sound ("wizard/widle2.wav"); + precache_sound ("wizard/wpain.wav"); + precache_sound ("wizard/wsight.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/wizard.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + + self.th_stand = wiz_stand1; + self.th_walk = wiz_walk1; + self.th_run = wiz_run1; + self.th_missile = Wiz_Missile; + self.th_pain = Wiz_Pain; + self.th_die = wiz_die; + + flymonster_start (); +}; diff --git a/quakec_hipnotic/monsters/shambler.qc b/quakec_hipnotic/monsters/shambler.qc new file mode 100644 index 0000000..7964c69 --- /dev/null +++ b/quakec_hipnotic/monsters/shambler.qc @@ -0,0 +1,403 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAMBLER + +============================================================================== +*/ + +$cd id1/models/shams +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 stand14 stand15 stand16 stand17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 + +$frame run1 run2 run3 run4 run5 run6 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 + +$frame swingr1 swingr2 swingr3 swingr4 swingr5 +$frame swingr6 swingr7 swingr8 swingr9 + +$frame swingl1 swingl2 swingl3 swingl4 swingl5 +$frame swingl6 swingl7 swingl8 swingl9 + +$frame magic1 magic2 magic3 magic4 magic5 +$frame magic6 magic7 magic8 magic9 magic10 magic11 magic12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 death11 + +void() sham_stand1 =[ $stand1, sham_stand2 ] {ai_stand();}; +void() sham_stand2 =[ $stand2, sham_stand3 ] {ai_stand();}; +void() sham_stand3 =[ $stand3, sham_stand4 ] {ai_stand();}; +void() sham_stand4 =[ $stand4, sham_stand5 ] {ai_stand();}; +void() sham_stand5 =[ $stand5, sham_stand6 ] {ai_stand();}; +void() sham_stand6 =[ $stand6, sham_stand7 ] {ai_stand();}; +void() sham_stand7 =[ $stand7, sham_stand8 ] {ai_stand();}; +void() sham_stand8 =[ $stand8, sham_stand9 ] {ai_stand();}; +void() sham_stand9 =[ $stand9, sham_stand10] {ai_stand();}; +void() sham_stand10 =[ $stand10, sham_stand11] {ai_stand();}; +void() sham_stand11 =[ $stand11, sham_stand12] {ai_stand();}; +void() sham_stand12 =[ $stand12, sham_stand13] {ai_stand();}; +void() sham_stand13 =[ $stand13, sham_stand14] {ai_stand();}; +void() sham_stand14 =[ $stand14, sham_stand15] {ai_stand();}; +void() sham_stand15 =[ $stand15, sham_stand16] {ai_stand();}; +void() sham_stand16 =[ $stand16, sham_stand17] {ai_stand();}; +void() sham_stand17 =[ $stand17, sham_stand1 ] {ai_stand();}; + +void() sham_walk1 =[ $walk1, sham_walk2 ] {ai_walk(10);}; +void() sham_walk2 =[ $walk2, sham_walk3 ] {ai_walk(9);}; +void() sham_walk3 =[ $walk3, sham_walk4 ] {ai_walk(9);}; +void() sham_walk4 =[ $walk4, sham_walk5 ] {ai_walk(5);}; +void() sham_walk5 =[ $walk5, sham_walk6 ] {ai_walk(6);}; +void() sham_walk6 =[ $walk6, sham_walk7 ] {ai_walk(12);}; +void() sham_walk7 =[ $walk7, sham_walk8 ] {ai_walk(8);}; +void() sham_walk8 =[ $walk8, sham_walk9 ] {ai_walk(3);}; +void() sham_walk9 =[ $walk9, sham_walk10] {ai_walk(13);}; +void() sham_walk10 =[ $walk10, sham_walk11] {ai_walk(9);}; +void() sham_walk11 =[ $walk11, sham_walk12] {ai_walk(7);}; +void() sham_walk12 =[ $walk12, sham_walk1 ] {ai_walk(7); +if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE);}; + +void() sham_run1 =[ $run1, sham_run2 ] {ai_run(20);}; +void() sham_run2 =[ $run2, sham_run3 ] {ai_run(24);}; +void() sham_run3 =[ $run3, sham_run4 ] {ai_run(20);}; +void() sham_run4 =[ $run4, sham_run5 ] {ai_run(20);}; +void() sham_run5 =[ $run5, sham_run6 ] {ai_run(24);}; +void() sham_run6 =[ $run6, sham_run1 ] {ai_run(20); +if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE); +}; + +void() sham_smash1 =[ $smash1, sham_smash2 ] { +sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); +ai_charge(2);}; +void() sham_smash2 =[ $smash2, sham_smash3 ] {ai_charge(6);}; +void() sham_smash3 =[ $smash3, sham_smash4 ] {ai_charge(6);}; +void() sham_smash4 =[ $smash4, sham_smash5 ] {ai_charge(5);}; +void() sham_smash5 =[ $smash5, sham_smash6 ] {ai_charge(4);}; +void() sham_smash6 =[ $smash6, sham_smash7 ] {ai_charge(1);}; +void() sham_smash7 =[ $smash7, sham_smash8 ] {ai_charge(0);}; +void() sham_smash8 =[ $smash8, sham_smash9 ] {ai_charge(0);}; +void() sham_smash9 =[ $smash9, sham_smash10 ] {ai_charge(0);}; +void() sham_smash10 =[ $smash10, sham_smash11 ] { +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + ai_charge(0); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + ldmg = (random() + random() + random()) * 40; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); +}; +void() sham_smash11 =[ $smash11, sham_smash12 ] {ai_charge(5);}; +void() sham_smash12 =[ $smash12, sham_run1 ] {ai_charge(4);}; + +void() sham_swingr1; + +void(float side) ShamClaw = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 20; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + if (side) + { + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + +void() sham_swingl1 =[ $swingl1, sham_swingl2 ] { +sound (self, CHAN_VOICE, "shambler/melee2.wav", 1, ATTN_NORM); +ai_charge(5);}; +void() sham_swingl2 =[ $swingl2, sham_swingl3 ] {ai_charge(3);}; +void() sham_swingl3 =[ $swingl3, sham_swingl4 ] {ai_charge(7);}; +void() sham_swingl4 =[ $swingl4, sham_swingl5 ] {ai_charge(3);}; +void() sham_swingl5 =[ $swingl5, sham_swingl6 ] {ai_charge(7);}; +void() sham_swingl6 =[ $swingl6, sham_swingl7 ] {ai_charge(9);}; +void() sham_swingl7 =[ $swingl7, sham_swingl8 ] {ai_charge(5); ShamClaw(250);}; +void() sham_swingl8 =[ $swingl8, sham_swingl9 ] {ai_charge(4);}; +void() sham_swingl9 =[ $swingl9, sham_run1 ] { +ai_charge(8); +if (random()<0.5) + self.think = sham_swingr1; +}; + +void() sham_swingr1 =[ $swingr1, sham_swingr2 ] { +sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); +ai_charge(1);}; +void() sham_swingr2 =[ $swingr2, sham_swingr3 ] {ai_charge(8);}; +void() sham_swingr3 =[ $swingr3, sham_swingr4 ] {ai_charge(14);}; +void() sham_swingr4 =[ $swingr4, sham_swingr5 ] {ai_charge(7);}; +void() sham_swingr5 =[ $swingr5, sham_swingr6 ] {ai_charge(3);}; +void() sham_swingr6 =[ $swingr6, sham_swingr7 ] {ai_charge(6);}; +void() sham_swingr7 =[ $swingr7, sham_swingr8 ] {ai_charge(6); ShamClaw(-250);}; +void() sham_swingr8 =[ $swingr8, sham_swingr9 ] {ai_charge(3);}; +void() sham_swingr9 =[ $swingr9, sham_run1 ] {ai_charge(1); +ai_charge(10); +if (random()<0.5) + self.think = sham_swingl1; +}; + +void() sham_melee = +{ + local float chance; + + chance = random(); + if (chance > 0.6 || self.health == 600) + sham_smash1 (); + else if (chance > 0.3) + sham_swingr1 (); + else + sham_swingl1 (); +}; + + +//============================================================================ + +void() CastLightning = +{ + local vector org, dir; + + self.effects = self.effects | EF_MUZZLEFLASH; + + ai_face (); + + org = self.origin + '0 0 40'; + + dir = self.enemy.origin + '0 0 16' - org; + dir = normalize (dir); + + traceline (org, self.origin + dir*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING1); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + // overload the frags field which is unused on enemies + self.frags += 1; + + LightningDamage (org, trace_endpos, self, 10); +}; + +void() sham_magic1 =[ $magic1, sham_magic2 ] {ai_face(); + sound (self, CHAN_WEAPON, "shambler/sattck1.wav", 1, ATTN_NORM); +}; +void() sham_magic2 =[ $magic2, sham_magic3 ] {ai_face();}; +void() sham_magic3 =[ $magic3, sham_magic4 ] {ai_face();self.nextthink = self.nextthink + 0.2; +local entity o; + +self.effects = self.effects | EF_MUZZLEFLASH; +ai_face(); +self.owner = spawn(); +o = self.owner; +setmodel (o, "progs/s_light.mdl"); +setorigin (o, self.origin); +o.angles = self.angles; +o.nextthink = time + 0.7; +o.think = SUB_Remove; +}; +void() sham_magic4 =[ $magic4, sham_magic5 ] +{ +self.effects = self.effects | EF_MUZZLEFLASH; +self.owner.frame = 1; +}; +void() sham_magic5 =[ $magic5, sham_magic6 ] +{ +self.effects = self.effects | EF_MUZZLEFLASH; +self.owner.frame = 2; +}; +void() sham_magic6 =[ $magic6, sham_magic9 ] +{ +remove (self.owner); +CastLightning(); +sound (self, CHAN_WEAPON, "shambler/sboom.wav", 1, ATTN_NORM); +}; +void() sham_magic9 =[ $magic9, sham_magic10 ] +{CastLightning();}; +void() sham_magic10 =[ $magic10, sham_magic11 ] +{CastLightning();}; +void() sham_magic11 =[ $magic11, sham_magic12 ] {}; +void() sham_magic12 =[ $magic12, sham_run1 ] {}; + + + +void() sham_pain1 =[ $pain1, sham_pain2 ] {}; +void() sham_pain2 =[ $pain2, sham_pain3 ] {}; +void() sham_pain3 =[ $pain3, sham_pain4 ] {}; +void() sham_pain4 =[ $pain4, sham_pain5 ] {}; +void() sham_pain5 =[ $pain5, sham_pain6 ] {}; +void() sham_pain6 =[ $pain6, sham_run1 ] {}; + +void(entity attacker, float damage) sham_pain = +{ + sound (self, CHAN_VOICE, "shambler/shurt2.wav", 1, ATTN_NORM); + + if (damage >= self.health && attacker.classname == "player" && attacker.weapon == IT_AXE) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_CLOSE_SHAVE"); + } + + // overload frags field for shambler dance achievement + if (damage >= self.health && attacker.classname == "player" && self.frags == 0) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SHAMBLER_DANCE"); + } + + if (self.health <= 0) + return; // allready dying, don't go into pain frame + + if (random()*400 > damage) + return; // didn't flinch + + if (self.pain_finished > time) + return; + self.pain_finished = time + 2; + + sham_pain1 (); +}; + + +//============================================================================ + +void() sham_death1 =[ $death1, sham_death2 ] {}; +void() sham_death2 =[ $death2, sham_death3 ] {}; +void() sham_death3 =[ $death3, sham_death4 ] {self.solid = SOLID_NOT;}; +void() sham_death4 =[ $death4, sham_death5 ] {}; +void() sham_death5 =[ $death5, sham_death6 ] {}; +void() sham_death6 =[ $death6, sham_death7 ] {}; +void() sham_death7 =[ $death7, sham_death8 ] {}; +void() sham_death8 =[ $death8, sham_death9 ] {}; +void() sham_death9 =[ $death9, sham_death10 ] {}; +void() sham_death10 =[ $death10, sham_death11 ] {}; +void() sham_death11 =[ $death11, sham_death11 ] {}; + +void() sham_die = +{ +// check for gib + if (self.health < -60) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shams.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "shambler/sdeath.wav", 1, ATTN_NORM); + sham_death1 (); +}; + +//============================================================================ + + +/*QUAKED monster_shambler (1 0 0) (-32 -32 -24) (32 32 64) Ambush +*/ +void() monster_shambler = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/shambler.mdl"); + precache_model ("progs/s_light.mdl"); + precache_model ("progs/h_shams.mdl"); + precache_model ("progs/bolt.mdl"); + + precache_sound ("shambler/sattck1.wav"); + precache_sound ("shambler/sboom.wav"); + precache_sound ("shambler/sdeath.wav"); + precache_sound ("shambler/shurt2.wav"); + precache_sound ("shambler/sidle.wav"); + precache_sound ("shambler/ssight.wav"); + precache_sound ("shambler/melee1.wav"); + precache_sound ("shambler/melee2.wav"); + precache_sound ("shambler/smack.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + setmodel (self, "progs/shambler.mdl"); + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 600; + + self.th_stand = sham_stand1; + self.th_walk = sham_walk1; + self.th_run = sham_run1; + self.th_die = sham_die; + self.th_melee = sham_melee; + self.th_missile = sham_magic1; + self.th_pain = sham_pain; + + // overload frags for lightning attack counts + self.frags = 0; + + walkmonster_start(); +}; diff --git a/quakec_hipnotic/monsters/shub.qc b/quakec_hipnotic/monsters/shub.qc new file mode 100644 index 0000000..c46b2e6 --- /dev/null +++ b/quakec_hipnotic/monsters/shub.qc @@ -0,0 +1,327 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OLD ONE + +============================================================================== +*/ +$cd id1/models/old_one +$origin 0 0 24 +$base base +$skin skin +$scale 1 + +void() finale_1; +void() finale_2; +void() finale_3; +void() finale_4; +void() finale_5; +void() finale_6; + +entity shub; + +$frame old1 old2 old3 old4 old5 old6 old7 old8 old9 +$frame old10 old11 old12 old13 old14 old15 old16 old17 old18 old19 +$frame old20 old21 old22 old23 old24 old25 old26 old27 old28 old29 +$frame old30 old31 old32 old33 old34 old35 old36 old37 old38 old39 +$frame old40 old41 old42 old43 old44 old45 old46 + +$frame shake1 shake2 shake3 shake4 shake5 shake6 shake7 shake8 +$frame shake9 shake10 shake11 shake12 shake12 shake13 shake14 +$frame shake15 shake16 shake17 shake18 shake19 shake20 + +//void() old_stand =[ $old1, old_stand ] {}; + +void() old_idle1 =[ $old1, old_idle2 ] {}; +void() old_idle2 =[ $old2, old_idle3 ] {}; +void() old_idle3 =[ $old3, old_idle4 ] {}; +void() old_idle4 =[ $old4, old_idle5 ] {}; +void() old_idle5 =[ $old5, old_idle6 ] {}; +void() old_idle6 =[ $old6, old_idle7 ] {}; +void() old_idle7 =[ $old7, old_idle8 ] {}; +void() old_idle8 =[ $old8, old_idle9 ] {}; +void() old_idle9 =[ $old9, old_idle10 ] {}; +void() old_idle10 =[ $old10, old_idle11 ] {}; +void() old_idle11 =[ $old11, old_idle12 ] {}; +void() old_idle12 =[ $old12, old_idle13 ] {}; +void() old_idle13 =[ $old13, old_idle14 ] {}; +void() old_idle14 =[ $old14, old_idle15 ] {}; +void() old_idle15 =[ $old15, old_idle16 ] {}; +void() old_idle16 =[ $old16, old_idle17 ] {}; +void() old_idle17 =[ $old17, old_idle18 ] {}; +void() old_idle18 =[ $old18, old_idle19 ] {}; +void() old_idle19 =[ $old19, old_idle20 ] {}; +void() old_idle20 =[ $old20, old_idle21 ] {}; +void() old_idle21 =[ $old21, old_idle22 ] {}; +void() old_idle22 =[ $old22, old_idle23 ] {}; +void() old_idle23 =[ $old23, old_idle24 ] {}; +void() old_idle24 =[ $old24, old_idle25 ] {}; +void() old_idle25 =[ $old25, old_idle26 ] {}; +void() old_idle26 =[ $old26, old_idle27 ] {}; +void() old_idle27 =[ $old27, old_idle28 ] {}; +void() old_idle28 =[ $old28, old_idle29 ] {}; +void() old_idle29 =[ $old29, old_idle30 ] {}; +void() old_idle30 =[ $old30, old_idle31 ] {}; +void() old_idle31 =[ $old31, old_idle32 ] {}; +void() old_idle32 =[ $old32, old_idle33 ] {}; +void() old_idle33 =[ $old33, old_idle34 ] {}; +void() old_idle34 =[ $old34, old_idle35 ] {}; +void() old_idle35 =[ $old35, old_idle36 ] {}; +void() old_idle36 =[ $old36, old_idle37 ] {}; +void() old_idle37 =[ $old37, old_idle38 ] {}; +void() old_idle38 =[ $old38, old_idle39 ] {}; +void() old_idle39 =[ $old39, old_idle40 ] {}; +void() old_idle40 =[ $old40, old_idle41 ] {}; +void() old_idle41 =[ $old41, old_idle42 ] {}; +void() old_idle42 =[ $old42, old_idle43 ] {}; +void() old_idle43 =[ $old43, old_idle44 ] {}; +void() old_idle44 =[ $old44, old_idle45 ] {}; +void() old_idle45 =[ $old45, old_idle46 ] {}; +void() old_idle46 =[ $old46, old_idle1 ] {}; + + +void() old_thrash1 =[ $shake1, old_thrash2 ] {lightstyle(0, "m");}; +void() old_thrash2 =[ $shake2, old_thrash3 ] {lightstyle(0, "k");}; +void() old_thrash3 =[ $shake3, old_thrash4 ] {lightstyle(0, "k");}; +void() old_thrash4 =[ $shake4, old_thrash5 ] {lightstyle(0, "i");}; +void() old_thrash5 =[ $shake5, old_thrash6 ] {lightstyle(0, "g");}; +void() old_thrash6 =[ $shake6, old_thrash7 ] {lightstyle(0, "e");}; +void() old_thrash7 =[ $shake7, old_thrash8 ] {lightstyle(0, "c");}; +void() old_thrash8 =[ $shake8, old_thrash9 ] {lightstyle(0, "a");}; +void() old_thrash9 =[ $shake9, old_thrash10 ] {lightstyle(0, "c");}; +void() old_thrash10 =[ $shake10, old_thrash11 ] {lightstyle(0, "e");}; +void() old_thrash11 =[ $shake11, old_thrash12 ] {lightstyle(0, "g");}; +void() old_thrash12 =[ $shake12, old_thrash13 ] {lightstyle(0, "i");}; +void() old_thrash13 =[ $shake13, old_thrash14 ] {lightstyle(0, "k");}; +void() old_thrash14 =[ $shake14, old_thrash15 ] {lightstyle(0, "m");}; +void() old_thrash15 =[ $shake15, old_thrash16 ] {lightstyle(0, "m"); +self.cnt = self.cnt + 1; +if (self.cnt != 3) + self.think = old_thrash1; +}; +void() old_thrash16 =[ $shake16, old_thrash17 ] {lightstyle(0, "g");}; +void() old_thrash17 =[ $shake17, old_thrash18 ] {lightstyle(0, "c");}; +void() old_thrash18 =[ $shake18, old_thrash19 ] {lightstyle(0, "b");}; +void() old_thrash19 =[ $shake19, old_thrash20 ] {lightstyle(0, "a");}; +void() old_thrash20 =[ $shake20, old_thrash20 ] {finale_4();}; + +//============================================================================ + +void() finale_1 = +{ + local entity pos, pl; + local entity timer; + + intermission_exittime = time + 10000000; // never allow exit + intermission_running = 1; + + // find the intermission spot + pos = find (world, classname, "info_intermission"); + if (!pos) + error ("no info_intermission"); + pl = find (world, classname, "misc_teleporttrain"); + if (!pl) + error ("no teleporttrain"); + remove (pl); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, ""); + + pl = find (world, classname, "player"); + while (pl != world) + { + pl.view_ofs = '0 0 0'; + pl.angles = other.v_angle = pos.mangle; + pl.fixangle = TRUE; // turn this way immediately + pl.map = self.map; + pl.nextthink = time + 0.5; + pl.takedamage = DAMAGE_NO; + pl.solid = SOLID_NOT; + pl.movetype = MOVETYPE_NONE; + pl.modelindex = 0; + setorigin (pl, pos.origin); + pl = find (pl, classname, "player"); + } + + // make fake versions of all players as standins, and move the real + // players to the intermission spot + + // wait for 1 second + timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_2; +}; + +void() finale_2 = +{ + local vector o; + + // start a teleport splash inside shub + + o = shub.origin - '0 100 0'; + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, o_x); + WriteCoord (MSG_BROADCAST, o_y); + WriteCoord (MSG_BROADCAST, o_z); + + sound (shub, CHAN_VOICE, "misc/r_tele1.wav", 1, ATTN_NORM); + + self.nextthink = time + 2; + self.think = finale_3; +}; + +void() finale_3 = +{ + // start shub thrashing wildly + shub.think = old_thrash1; + sound (shub, CHAN_VOICE, "boss2/death.wav", 1, ATTN_NORM); + lightstyle(0, "abcdefghijklmlkjihgfedcb"); +}; + +void() finale_4 = +{ + // throw tons of meat chunks + local vector oldo; + local float x, y, z; + local float r; + local entity n; + + sound (self, CHAN_VOICE, "boss2/pop2.wav", 1, ATTN_NORM); + + oldo = self.origin; + + z = 16; + while (z <= 144) + { + x = -64; + while (x <= 64) + { + y = -64; + while (y <= 64) + { + self.origin_x = oldo_x + x; + self.origin_y = oldo_y + y; + self.origin_z = oldo_z + z; + + r = random(); + if (r < 0.3) + ThrowGib ("progs/gib1.mdl", -999); + else if (r < 0.6) + ThrowGib ("progs/gib2.mdl", -999); + else + ThrowGib ("progs/gib3.mdl", -999); + y = y + 32; + } + x = x + 32; + } + z = z + 96; + } + // start the end text + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_end"); + +// put a player model down + n = spawn(); + setmodel (n, "progs/player.mdl"); + oldo = oldo - '32 264 0'; + setorigin (n, oldo); + n.angles = '0 290 0'; + n.frame = 1; + + remove (self); + +// switch cd track + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + lightstyle(0, "m"); + + // instead of sitting here forever, run the quake ex credits and send the user back to start + local entity timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_5; +}; + +void() finale_5 = +{ + if (finaleFinished()) { + self.nextthink = time + 5; + self.think = finale_6; + } else { + self.nextthink = time + 0.1; + } +} + +void() finale_6 = +{ + if (!coop) { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + } else { + changelevel("start"); + } +} +//============================================================================ + +void () nopain = +{ + self.health = 40000; +}; + +//============================================================================ + + +/*QUAKED monster_oldone (1 0 0) (-16 -16 -24) (16 16 32) +*/ +void() monster_oldone = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/oldone.mdl"); + + precache_sound2 ("boss2/death.wav"); + precache_sound2 ("boss2/idle.wav"); + precache_sound2 ("boss2/sight.wav"); + precache_sound2 ("boss2/pop2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/oldone.mdl"); + setsize (self, '-160 -128 -24', '160 128 256'); + + self.health = 40000; // kill by telefrag + self.think = old_idle1; + self.nextthink = time + 0.1; + self.takedamage = DAMAGE_YES; + self.th_pain = nopain; + self.th_die = finale_1; + shub = self; + + total_monsters = total_monsters + 1; +}; + diff --git a/quakec_hipnotic/monsters/spawn.qc b/quakec_hipnotic/monsters/spawn.qc new file mode 100644 index 0000000..c6ad903 --- /dev/null +++ b/quakec_hipnotic/monsters/spawn.qc @@ -0,0 +1,241 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BLOB + +============================================================================== +*/ + +$cd id1/models/tarbaby +$origin 0 0 24 +$base base + +$skin skin + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 +$frame walk20 walk21 walk22 walk23 walk24 walk25 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 run13 +$frame run14 run15 run16 run17 run18 run19 run20 run21 run22 run23 +$frame run24 run25 + +$frame jump1 jump2 jump3 jump4 jump5 jump6 + +$frame fly1 fly2 fly3 fly4 + +$frame exp + +void() tbaby_stand1 =[ $walk1, tbaby_stand1 ] {ai_stand();}; + +void() tbaby_hang1 =[ $walk1, tbaby_hang1 ] {ai_stand();}; + +void() tbaby_walk1 =[ $walk1, tbaby_walk2 ] {ai_turn();}; +void() tbaby_walk2 =[ $walk2, tbaby_walk3 ] {ai_turn();}; +void() tbaby_walk3 =[ $walk3, tbaby_walk4 ] {ai_turn();}; +void() tbaby_walk4 =[ $walk4, tbaby_walk5 ] {ai_turn();}; +void() tbaby_walk5 =[ $walk5, tbaby_walk6 ] {ai_turn();}; +void() tbaby_walk6 =[ $walk6, tbaby_walk7 ] {ai_turn();}; +void() tbaby_walk7 =[ $walk7, tbaby_walk8 ] {ai_turn();}; +void() tbaby_walk8 =[ $walk8, tbaby_walk9 ] {ai_turn();}; +void() tbaby_walk9 =[ $walk9, tbaby_walk10 ] {ai_turn();}; +void() tbaby_walk10 =[ $walk10, tbaby_walk11 ] {ai_turn();}; +void() tbaby_walk11 =[ $walk11, tbaby_walk12 ] {ai_walk(2);}; +void() tbaby_walk12 =[ $walk12, tbaby_walk13 ] {ai_walk(2);}; +void() tbaby_walk13 =[ $walk13, tbaby_walk14 ] {ai_walk(2);}; +void() tbaby_walk14 =[ $walk14, tbaby_walk15 ] {ai_walk(2);}; +void() tbaby_walk15 =[ $walk15, tbaby_walk16 ] {ai_walk(2);}; +void() tbaby_walk16 =[ $walk16, tbaby_walk17 ] {ai_walk(2);}; +void() tbaby_walk17 =[ $walk17, tbaby_walk18 ] {ai_walk(2);}; +void() tbaby_walk18 =[ $walk18, tbaby_walk19 ] {ai_walk(2);}; +void() tbaby_walk19 =[ $walk19, tbaby_walk20 ] {ai_walk(2);}; +void() tbaby_walk20 =[ $walk20, tbaby_walk21 ] {ai_walk(2);}; +void() tbaby_walk21 =[ $walk21, tbaby_walk22 ] {ai_walk(2);}; +void() tbaby_walk22 =[ $walk22, tbaby_walk23 ] {ai_walk(2);}; +void() tbaby_walk23 =[ $walk23, tbaby_walk24 ] {ai_walk(2);}; +void() tbaby_walk24 =[ $walk24, tbaby_walk25 ] {ai_walk(2);}; +void() tbaby_walk25 =[ $walk25, tbaby_walk1 ] {ai_walk(2);}; + +void() tbaby_run1 =[ $run1, tbaby_run2 ] {ai_face();}; +void() tbaby_run2 =[ $run2, tbaby_run3 ] {ai_face();}; +void() tbaby_run3 =[ $run3, tbaby_run4 ] {ai_face();}; +void() tbaby_run4 =[ $run4, tbaby_run5 ] {ai_face();}; +void() tbaby_run5 =[ $run5, tbaby_run6 ] {ai_face();}; +void() tbaby_run6 =[ $run6, tbaby_run7 ] {ai_face();}; +void() tbaby_run7 =[ $run7, tbaby_run8 ] {ai_face();}; +void() tbaby_run8 =[ $run8, tbaby_run9 ] {ai_face();}; +void() tbaby_run9 =[ $run9, tbaby_run10 ] {ai_face();}; +void() tbaby_run10 =[ $run10, tbaby_run11 ] {ai_face();}; +void() tbaby_run11 =[ $run11, tbaby_run12 ] {ai_run(2);}; +void() tbaby_run12 =[ $run12, tbaby_run13 ] {ai_run(2);}; +void() tbaby_run13 =[ $run13, tbaby_run14 ] {ai_run(2);}; +void() tbaby_run14 =[ $run14, tbaby_run15 ] {ai_run(2);}; +void() tbaby_run15 =[ $run15, tbaby_run16 ] {ai_run(2);}; +void() tbaby_run16 =[ $run16, tbaby_run17 ] {ai_run(2);}; +void() tbaby_run17 =[ $run17, tbaby_run18 ] {ai_run(2);}; +void() tbaby_run18 =[ $run18, tbaby_run19 ] {ai_run(2);}; +void() tbaby_run19 =[ $run19, tbaby_run20 ] {ai_run(2);}; +void() tbaby_run20 =[ $run20, tbaby_run21 ] {ai_run(2);}; +void() tbaby_run21 =[ $run21, tbaby_run22 ] {ai_run(2);}; +void() tbaby_run22 =[ $run22, tbaby_run23 ] {ai_run(2);}; +void() tbaby_run23 =[ $run23, tbaby_run24 ] {ai_run(2);}; +void() tbaby_run24 =[ $run24, tbaby_run25 ] {ai_run(2);}; +void() tbaby_run25 =[ $run25, tbaby_run1 ] {ai_run(2);}; + + +//============================================================================ + + +void() tbaby_jump1; + +void() Tar_JumpTouch = +{ + local float ldmg; + + if (other.takedamage && other.classname != self.classname) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + sound (self, CHAN_WEAPON, "blob/hit1.wav", 1, ATTN_NORM); + } + } + else + sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM); + + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = tbaby_run1; + self.movetype = MOVETYPE_STEP; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = tbaby_jump1; + self.nextthink = time + 0.1; +}; + +void() tbaby_jump5; + +void() tbaby_fly1 =[ $fly1, tbaby_fly2 ] {}; +void() tbaby_fly2 =[ $fly2, tbaby_fly3 ] {}; +void() tbaby_fly3 =[ $fly3, tbaby_fly4 ] {}; +void() tbaby_fly4 =[ $fly4, tbaby_fly1 ] { +self.cnt = self.cnt + 1; +if (self.cnt == 4) +{ +//dprint ("spawn hop\n"); +tbaby_jump5 (); +} +}; + +void() tbaby_jump1 =[ $jump1, tbaby_jump2 ] {ai_face();}; +void() tbaby_jump2 =[ $jump2, tbaby_jump3 ] {ai_face();}; +void() tbaby_jump3 =[ $jump3, tbaby_jump4 ] {ai_face();}; +void() tbaby_jump4 =[ $jump4, tbaby_jump5 ] {ai_face();}; +void() tbaby_jump5 =[ $jump5, tbaby_jump6 ] +{ + self.movetype = MOVETYPE_BOUNCE; + self.touch = Tar_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 200'; + self.velocity_z = self.velocity_z + random()*150; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; + self.cnt = 0; +}; +void() tbaby_jump6 =[ $jump6,tbaby_fly1 ] {}; + + + +//============================================================================= + +void() tbaby_die1 =[ $exp, tbaby_die2 ] { +self.takedamage = DAMAGE_NO; +}; +void() tbaby_die2 =[ $exp, tbaby_run1 ] +{ + T_RadiusDamage (self, self, 120, world); + + sound (self, CHAN_VOICE, "blob/death1.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TAREXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +//============================================================================= + + +/*QUAKED monster_tarbaby (1 0 0) (-16 -16 -24) (16 16 24) Ambush +*/ +void() monster_tarbaby = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/tarbaby.mdl"); + + precache_sound2 ("blob/death1.wav"); + precache_sound2 ("blob/hit1.wav"); + precache_sound2 ("blob/land1.wav"); + precache_sound2 ("blob/sight1.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/tarbaby.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + + self.th_stand = tbaby_stand1; + self.th_walk = tbaby_walk1; + self.th_run = tbaby_run1; + self.th_missile = tbaby_jump1; + self.th_melee = tbaby_jump1; + self.th_die = tbaby_die1; + + walkmonster_start (); +}; + diff --git a/quakec_hipnotic/monsters/vore.qc b/quakec_hipnotic/monsters/vore.qc new file mode 100644 index 0000000..0ed458c --- /dev/null +++ b/quakec_hipnotic/monsters/vore.qc @@ -0,0 +1,256 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAL-RATH + +============================================================================== +*/ +$cd id1/models/shalrath +$origin 0 0 24 +$base base +$skin skin +$scale 0.7 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 death7 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 + +void() shalrath_pain; +void() ShalMissile; +void() shal_stand =[ $walk1, shal_stand ] {ai_stand();}; + +void() shal_walk1 =[ $walk2, shal_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); +ai_walk(6);}; +void() shal_walk2 =[ $walk3, shal_walk3 ] {ai_walk(4);}; +void() shal_walk3 =[ $walk4, shal_walk4 ] {ai_walk(0);}; +void() shal_walk4 =[ $walk5, shal_walk5 ] {ai_walk(0);}; +void() shal_walk5 =[ $walk6, shal_walk6 ] {ai_walk(0);}; +void() shal_walk6 =[ $walk7, shal_walk7 ] {ai_walk(0);}; +void() shal_walk7 =[ $walk8, shal_walk8 ] {ai_walk(5);}; +void() shal_walk8 =[ $walk9, shal_walk9 ] {ai_walk(6);}; +void() shal_walk9 =[ $walk10, shal_walk10 ] {ai_walk(5);}; +void() shal_walk10 =[ $walk11, shal_walk11 ] {ai_walk(0);}; +void() shal_walk11 =[ $walk12, shal_walk12 ] {ai_walk(4);}; +void() shal_walk12 =[ $walk1, shal_walk1 ] {ai_walk(5);}; + +void() shal_run1 =[ $walk2, shal_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); +ai_run(6);}; +void() shal_run2 =[ $walk3, shal_run3 ] {ai_run(4);}; +void() shal_run3 =[ $walk4, shal_run4 ] {ai_run(0);}; +void() shal_run4 =[ $walk5, shal_run5 ] {ai_run(0);}; +void() shal_run5 =[ $walk6, shal_run6 ] {ai_run(0);}; +void() shal_run6 =[ $walk7, shal_run7 ] {ai_run(0);}; +void() shal_run7 =[ $walk8, shal_run8 ] {ai_run(5);}; +void() shal_run8 =[ $walk9, shal_run9 ] {ai_run(6);}; +void() shal_run9 =[ $walk10, shal_run10 ] {ai_run(5);}; +void() shal_run10 =[ $walk11, shal_run11 ] {ai_run(0);}; +void() shal_run11 =[ $walk12, shal_run12 ] {ai_run(4);}; +void() shal_run12 =[ $walk1, shal_run1 ] {ai_run(5);}; + +void() shal_attack1 =[ $attack1, shal_attack2 ] { +sound (self, CHAN_VOICE, "shalrath/attack.wav", 1, ATTN_NORM); +ai_face(); +}; +void() shal_attack2 =[ $attack2, shal_attack3 ] {ai_face();}; +void() shal_attack3 =[ $attack3, shal_attack4 ] {ai_face();}; +void() shal_attack4 =[ $attack4, shal_attack5 ] {ai_face();}; +void() shal_attack5 =[ $attack5, shal_attack6 ] {ai_face();}; +void() shal_attack6 =[ $attack6, shal_attack7 ] {ai_face();}; +void() shal_attack7 =[ $attack7, shal_attack8 ] {ai_face();}; +void() shal_attack8 =[ $attack8, shal_attack9 ] {ai_face();}; +void() shal_attack9 =[ $attack9, shal_attack10 ] {ShalMissile();}; +void() shal_attack10 =[ $attack10, shal_attack11 ] {ai_face();}; +void() shal_attack11 =[ $attack11, shal_run1 ] {}; + +void() shal_pain1 =[ $pain1, shal_pain2 ] {}; +void() shal_pain2 =[ $pain2, shal_pain3 ] {}; +void() shal_pain3 =[ $pain3, shal_pain4 ] {}; +void() shal_pain4 =[ $pain4, shal_pain5 ] {}; +void() shal_pain5 =[ $pain5, shal_run1 ] {}; + +void() shal_death1 =[ $death1, shal_death2 ] {}; +void() shal_death2 =[ $death2, shal_death3 ] {}; +void() shal_death3 =[ $death3, shal_death4 ] {}; +void() shal_death4 =[ $death4, shal_death5 ] {}; +void() shal_death5 =[ $death5, shal_death6 ] {}; +void() shal_death6 =[ $death6, shal_death7 ] {}; +void() shal_death7 =[ $death7, shal_death7 ] {}; + + +void() shalrath_pain = +{ + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "shalrath/pain.wav", 1, ATTN_NORM); + shal_pain1(); + self.pain_finished = time + 3; +}; + +void() shalrath_die = +{ +// check for gib + if (self.health < -90) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shal.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "shalrath/death.wav", 1, ATTN_NORM); + shal_death1(); + self.solid = SOLID_NOT; + // insert death sounds here +}; + +/* +================ +ShalMissile +================ +*/ +void() ShalMissileTouch; +void() ShalHome; +void() ShalMissile = +{ + local entity missile; + local vector dir; + local float dist, flytime; + + dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + dist = vlen (self.enemy.origin - self.origin); + flytime = dist * 0.002; + if (flytime < 0.1) + flytime = 0.1; + + self.effects = self.effects | EF_MUZZLEFLASH; + sound (self, CHAN_WEAPON, "shalrath/attack2.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + setmodel (missile, "progs/v_spike.mdl"); + + setsize (missile, '0 0 0', '0 0 0'); + + missile.origin = self.origin + '0 0 10'; + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.nextthink = flytime + time; + missile.think = ShalHome; + missile.enemy = self.enemy; + missile.touch = ShalMissileTouch; +}; + +void() ShalHome = +{ + local vector dir, vtemp; + vtemp = self.enemy.origin + '0 0 10'; + if (self.enemy.health < 1) + { + remove(self); + return; + } + dir = normalize(vtemp - self.origin); + self.velocity = dir * 250; + self.nextthink = time + 0.2; + self.think = ShalHome; +}; + +void() ShalMissileTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (other.classname == "monster_zombie") + T_Damage (other, self, self, 110); + T_RadiusDamage (self, self.owner, 40, world); + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +//================================================================= + +/*QUAKED monster_shalrath (1 0 0) (-32 -32 -24) (32 32 48) Ambush +*/ +void() monster_shalrath = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/shalrath.mdl"); + precache_model2 ("progs/h_shal.mdl"); + precache_model2 ("progs/v_spike.mdl"); + + precache_sound2 ("shalrath/attack.wav"); + precache_sound2 ("shalrath/attack2.wav"); + precache_sound2 ("shalrath/death.wav"); + precache_sound2 ("shalrath/idle.wav"); + precache_sound2 ("shalrath/pain.wav"); + precache_sound2 ("shalrath/sight.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/shalrath.mdl"); + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 400; + + self.th_stand = shal_stand; + self.th_walk = shal_walk1; + self.th_run = shal_run1; + self.th_die = shalrath_die; + self.th_pain = shalrath_pain; + self.th_missile = shal_attack1; + + walkmonster_start(); +// self.think = walkmonster_start; + self.nextthink = time + 0.1 + random ()*0.1; + +}; diff --git a/quakec_hipnotic/monsters/zombie.qc b/quakec_hipnotic/monsters/zombie.qc new file mode 100644 index 0000000..8d39b31 --- /dev/null +++ b/quakec_hipnotic/monsters/zombie.qc @@ -0,0 +1,564 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +ZOMBIE + +============================================================================== +*/ +$cd id1/models/zombie + +$origin 0 0 24 + +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 +$frame stand9 stand10 stand11 stand12 stand13 stand14 stand15 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 walk11 +$frame walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 +$frame run13 run14 run15 run16 run17 run18 + +$frame atta1 atta2 atta3 atta4 atta5 atta6 atta7 atta8 atta9 atta10 atta11 +$frame atta12 atta13 + +$frame attb1 attb2 attb3 attb4 attb5 attb6 attb7 attb8 attb9 attb10 attb11 +$frame attb12 attb13 attb14 + +$frame attc1 attc2 attc3 attc4 attc5 attc6 attc7 attc8 attc9 attc10 attc11 +$frame attc12 + +$frame paina1 paina2 paina3 paina4 paina5 paina6 paina7 paina8 paina9 paina10 +$frame paina11 paina12 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 painb17 painb18 painb19 +$frame painb20 painb21 painb22 painb23 painb24 painb25 painb26 painb27 painb28 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 painc14 painc15 painc16 painc17 painc18 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 paine16 paine17 paine18 paine19 +$frame paine20 paine21 paine22 paine23 paine24 paine25 paine26 paine27 paine28 +$frame paine29 paine30 + +$frame cruc_1 cruc_2 cruc_3 cruc_4 cruc_5 cruc_6 + +float SPAWN_CRUCIFIED = 1; +//HIPNOTIC +float SPAWN_LYINGDOWN = 4; + +//============================================================================= + +.float inpain; + +void() zombie_stand1 =[ $stand1, zombie_stand2 ] {ai_stand();}; +void() zombie_stand2 =[ $stand2, zombie_stand3 ] {ai_stand();}; +void() zombie_stand3 =[ $stand3, zombie_stand4 ] {ai_stand();}; +void() zombie_stand4 =[ $stand4, zombie_stand5 ] {ai_stand();}; +void() zombie_stand5 =[ $stand5, zombie_stand6 ] {ai_stand();}; +void() zombie_stand6 =[ $stand6, zombie_stand7 ] {ai_stand();}; +void() zombie_stand7 =[ $stand7, zombie_stand8 ] {ai_stand();}; +void() zombie_stand8 =[ $stand8, zombie_stand9 ] {ai_stand();}; +void() zombie_stand9 =[ $stand9, zombie_stand10 ] {ai_stand();}; +void() zombie_stand10 =[ $stand10, zombie_stand11 ] {ai_stand();}; +void() zombie_stand11 =[ $stand11, zombie_stand12 ] {ai_stand();}; +void() zombie_stand12 =[ $stand12, zombie_stand13 ] {ai_stand();}; +void() zombie_stand13 =[ $stand13, zombie_stand14 ] {ai_stand();}; +void() zombie_stand14 =[ $stand14, zombie_stand15 ] {ai_stand();}; +void() zombie_stand15 =[ $stand15, zombie_stand1 ] {ai_stand();}; + +void() zombie_cruc1 = [ $cruc_1, zombie_cruc2 ] { +if (random() < 0.1) + sound (self, CHAN_VOICE, "zombie/idle_w2.wav", 1, ATTN_STATIC);}; +void() zombie_cruc2 = [ $cruc_2, zombie_cruc3 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc3 = [ $cruc_3, zombie_cruc4 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc4 = [ $cruc_4, zombie_cruc5 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc5 = [ $cruc_5, zombie_cruc6 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc6 = [ $cruc_6, zombie_cruc1 ] {self.nextthink = time + 0.1 + random()*0.1;}; + +void() zombie_walk1 =[ $walk1, zombie_walk2 ] {ai_walk(0);}; +void() zombie_walk2 =[ $walk2, zombie_walk3 ] {ai_walk(2);}; +void() zombie_walk3 =[ $walk3, zombie_walk4 ] {ai_walk(3);}; +void() zombie_walk4 =[ $walk4, zombie_walk5 ] {ai_walk(2);}; +void() zombie_walk5 =[ $walk5, zombie_walk6 ] {ai_walk(1);}; +void() zombie_walk6 =[ $walk6, zombie_walk7 ] {ai_walk(0);}; +void() zombie_walk7 =[ $walk7, zombie_walk8 ] {ai_walk(0);}; +void() zombie_walk8 =[ $walk8, zombie_walk9 ] {ai_walk(0);}; +void() zombie_walk9 =[ $walk9, zombie_walk10 ] {ai_walk(0);}; +void() zombie_walk10 =[ $walk10, zombie_walk11 ] {ai_walk(0);}; +void() zombie_walk11 =[ $walk11, zombie_walk12 ] {ai_walk(2);}; +void() zombie_walk12 =[ $walk12, zombie_walk13 ] {ai_walk(2);}; +void() zombie_walk13 =[ $walk13, zombie_walk14 ] {ai_walk(1);}; +void() zombie_walk14 =[ $walk14, zombie_walk15 ] {ai_walk(0);}; +void() zombie_walk15 =[ $walk15, zombie_walk16 ] {ai_walk(0);}; +void() zombie_walk16 =[ $walk16, zombie_walk17 ] {ai_walk(0);}; +void() zombie_walk17 =[ $walk17, zombie_walk18 ] {ai_walk(0);}; +void() zombie_walk18 =[ $walk18, zombie_walk19 ] {ai_walk(0);}; +void() zombie_walk19 =[ $walk19, zombie_walk1 ] { +ai_walk(0); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE);}; + +void() zombie_run1 =[ $run1, zombie_run2 ] {ai_run(1);self.inpain = 0;}; +void() zombie_run2 =[ $run2, zombie_run3 ] {ai_run(1);}; +void() zombie_run3 =[ $run3, zombie_run4 ] {ai_run(0);}; +void() zombie_run4 =[ $run4, zombie_run5 ] {ai_run(1);}; +void() zombie_run5 =[ $run5, zombie_run6 ] {ai_run(2);}; +void() zombie_run6 =[ $run6, zombie_run7 ] {ai_run(3);}; +void() zombie_run7 =[ $run7, zombie_run8 ] {ai_run(4);}; +void() zombie_run8 =[ $run8, zombie_run9 ] {ai_run(4);}; +void() zombie_run9 =[ $run9, zombie_run10 ] {ai_run(2);}; +void() zombie_run10 =[ $run10, zombie_run11 ] {ai_run(0);}; +void() zombie_run11 =[ $run11, zombie_run12 ] {ai_run(0);}; +void() zombie_run12 =[ $run12, zombie_run13 ] {ai_run(0);}; +void() zombie_run13 =[ $run13, zombie_run14 ] {ai_run(2);}; +void() zombie_run14 =[ $run14, zombie_run15 ] {ai_run(4);}; +void() zombie_run15 =[ $run15, zombie_run16 ] {ai_run(6);}; +void() zombie_run16 =[ $run16, zombie_run17 ] {ai_run(7);}; +void() zombie_run17 =[ $run17, zombie_run18 ] {ai_run(3);}; +void() zombie_run18 =[ $run18, zombie_run1 ] { +ai_run(8); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); +if (random() > 0.8) + sound (self, CHAN_VOICE, "zombie/z_idle1.wav", 1, ATTN_IDLE); +}; + +/* +============================================================================= + +ATTACKS + +============================================================================= +*/ + +void() ZombieGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage) + { + T_Damage (other, self, self.owner, 10 ); + sound (self, CHAN_WEAPON, "zombie/z_hit.wav", 1, ATTN_NORM); + remove (self); + return; + } + sound (self, CHAN_WEAPON, "zombie/z_miss.wav", 1, ATTN_NORM); // bounce sound + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.touch = SUB_Remove; +}; + +/* +================ +ZombieFireGrenade +================ +*/ +void(vector st) ZombieFireGrenade = +{ + local entity missile, mpuff; + local vector org; + + sound (self, CHAN_WEAPON, "zombie/z_shot1.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + +// calc org + org = self.origin + st_x * v_forward + st_y * v_right + (st_z - 24) * v_up; + +// set missile speed + + makevectors (self.angles); + + missile.velocity = normalize(self.enemy.origin - org); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + + missile.avelocity = '3000 1000 2000'; + + missile.touch = ZombieGrenadeTouch; + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + + +void() zombie_atta1 =[ $atta1, zombie_atta2 ] {ai_face();}; +void() zombie_atta2 =[ $atta2, zombie_atta3 ] {ai_face();}; +void() zombie_atta3 =[ $atta3, zombie_atta4 ] {ai_face();}; +void() zombie_atta4 =[ $atta4, zombie_atta5 ] {ai_face();}; +void() zombie_atta5 =[ $atta5, zombie_atta6 ] {ai_face();}; +void() zombie_atta6 =[ $atta6, zombie_atta7 ] {ai_face();}; +void() zombie_atta7 =[ $atta7, zombie_atta8 ] {ai_face();}; +void() zombie_atta8 =[ $atta8, zombie_atta9 ] {ai_face();}; +void() zombie_atta9 =[ $atta9, zombie_atta10 ] {ai_face();}; +void() zombie_atta10 =[ $atta10, zombie_atta11 ] {ai_face();}; +void() zombie_atta11 =[ $atta11, zombie_atta12 ] {ai_face();}; +void() zombie_atta12 =[ $atta12, zombie_atta13 ] {ai_face();}; +void() zombie_atta13 =[ $atta13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -22 30');}; + +void() zombie_attb1 =[ $attb1, zombie_attb2 ] {ai_face();}; +void() zombie_attb2 =[ $attb2, zombie_attb3 ] {ai_face();}; +void() zombie_attb3 =[ $attb3, zombie_attb4 ] {ai_face();}; +void() zombie_attb4 =[ $attb4, zombie_attb5 ] {ai_face();}; +void() zombie_attb5 =[ $attb5, zombie_attb6 ] {ai_face();}; +void() zombie_attb6 =[ $attb6, zombie_attb7 ] {ai_face();}; +void() zombie_attb7 =[ $attb7, zombie_attb8 ] {ai_face();}; +void() zombie_attb8 =[ $attb8, zombie_attb9 ] {ai_face();}; +void() zombie_attb9 =[ $attb9, zombie_attb10 ] {ai_face();}; +void() zombie_attb10 =[ $attb10, zombie_attb11 ] {ai_face();}; +void() zombie_attb11 =[ $attb11, zombie_attb12 ] {ai_face();}; +void() zombie_attb12 =[ $attb12, zombie_attb13 ] {ai_face();}; +void() zombie_attb13 =[ $attb13, zombie_attb14 ] {ai_face();}; +void() zombie_attb14 =[ $attb13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -24 29');}; + +void() zombie_attc1 =[ $attc1, zombie_attc2 ] {ai_face();}; +void() zombie_attc2 =[ $attc2, zombie_attc3 ] {ai_face();}; +void() zombie_attc3 =[ $attc3, zombie_attc4 ] {ai_face();}; +void() zombie_attc4 =[ $attc4, zombie_attc5 ] {ai_face();}; +void() zombie_attc5 =[ $attc5, zombie_attc6 ] {ai_face();}; +void() zombie_attc6 =[ $attc6, zombie_attc7 ] {ai_face();}; +void() zombie_attc7 =[ $attc7, zombie_attc8 ] {ai_face();}; +void() zombie_attc8 =[ $attc8, zombie_attc9 ] {ai_face();}; +void() zombie_attc9 =[ $attc9, zombie_attc10 ] {ai_face();}; +void() zombie_attc10 =[ $attc10, zombie_attc11 ] {ai_face();}; +void() zombie_attc11 =[ $attc11, zombie_attc12 ] {ai_face();}; +void() zombie_attc12 =[ $attc12, zombie_run1 ] {ai_face();ZombieFireGrenade('-12 -19 29');}; + +void() zombie_missile = +{ + local float r; + + r = random(); + + if (r < 0.3) + zombie_atta1 (); + else if (r < 0.6) + zombie_attb1 (); + else + zombie_attc1 (); +}; + + +/* +============================================================================= + +PAIN + +============================================================================= +*/ + +void() zombie_paina1 =[ $paina1, zombie_paina2 ] {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() zombie_paina2 =[ $paina2, zombie_paina3 ] {ai_painforward(3);}; +void() zombie_paina3 =[ $paina3, zombie_paina4 ] {ai_painforward(1);}; +void() zombie_paina4 =[ $paina4, zombie_paina5 ] {ai_pain(1);}; +void() zombie_paina5 =[ $paina5, zombie_paina6 ] {ai_pain(3);}; +void() zombie_paina6 =[ $paina6, zombie_paina7 ] {ai_pain(1);}; +void() zombie_paina7 =[ $paina7, zombie_paina8 ] {}; +void() zombie_paina8 =[ $paina8, zombie_paina9 ] {}; +void() zombie_paina9 =[ $paina9, zombie_paina10 ] {}; +void() zombie_paina10 =[ $paina10, zombie_paina11 ] {}; +void() zombie_paina11 =[ $paina11, zombie_paina12 ] {}; +void() zombie_paina12 =[ $paina12, zombie_run1 ] {}; + +void() zombie_painb1 =[ $painb1, zombie_painb2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painb2 =[ $painb2, zombie_painb3 ] {ai_pain(2);}; +void() zombie_painb3 =[ $painb3, zombie_painb4 ] {ai_pain(8);}; +void() zombie_painb4 =[ $painb4, zombie_painb5 ] {ai_pain(6);}; +void() zombie_painb5 =[ $painb5, zombie_painb6 ] {ai_pain(2);}; +void() zombie_painb6 =[ $painb6, zombie_painb7 ] {}; +void() zombie_painb7 =[ $painb7, zombie_painb8 ] {}; +void() zombie_painb8 =[ $painb8, zombie_painb9 ] {}; +void() zombie_painb9 =[ $painb9, zombie_painb10 ] {sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM);}; +void() zombie_painb10 =[ $painb10, zombie_painb11 ] {}; +void() zombie_painb11 =[ $painb11, zombie_painb12 ] {}; +void() zombie_painb12 =[ $painb12, zombie_painb13 ] {}; +void() zombie_painb13 =[ $painb13, zombie_painb14 ] {}; +void() zombie_painb14 =[ $painb14, zombie_painb15 ] {}; +void() zombie_painb15 =[ $painb15, zombie_painb16 ] {}; +void() zombie_painb16 =[ $painb16, zombie_painb17 ] {}; +void() zombie_painb17 =[ $painb17, zombie_painb18 ] {}; +void() zombie_painb18 =[ $painb18, zombie_painb19 ] {}; +void() zombie_painb19 =[ $painb19, zombie_painb20 ] {}; +void() zombie_painb20 =[ $painb20, zombie_painb21 ] {}; +void() zombie_painb21 =[ $painb21, zombie_painb22 ] {}; +void() zombie_painb22 =[ $painb22, zombie_painb23 ] {}; +void() zombie_painb23 =[ $painb23, zombie_painb24 ] {}; +void() zombie_painb24 =[ $painb24, zombie_painb25 ] {}; +void() zombie_painb25 =[ $painb25, zombie_painb26 ] {ai_painforward(1);}; +void() zombie_painb26 =[ $painb26, zombie_painb27 ] {}; +void() zombie_painb27 =[ $painb27, zombie_painb28 ] {}; +void() zombie_painb28 =[ $painb28, zombie_run1 ] {}; + +void() zombie_painc1 =[ $painc1, zombie_painc2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painc2 =[ $painc2, zombie_painc3 ] {}; +void() zombie_painc3 =[ $painc3, zombie_painc4 ] {ai_pain(3);}; +void() zombie_painc4 =[ $painc4, zombie_painc5 ] {ai_pain(1);}; +void() zombie_painc5 =[ $painc5, zombie_painc6 ] {}; +void() zombie_painc6 =[ $painc6, zombie_painc7 ] {}; +void() zombie_painc7 =[ $painc7, zombie_painc8 ] {}; +void() zombie_painc8 =[ $painc8, zombie_painc9 ] {}; +void() zombie_painc9 =[ $painc9, zombie_painc10 ] {}; +void() zombie_painc10 =[ $painc10, zombie_painc11 ] {}; +void() zombie_painc11 =[ $painc11, zombie_painc12 ] {ai_painforward(1);}; +void() zombie_painc12 =[ $painc12, zombie_painc13 ] {ai_painforward(1);}; +void() zombie_painc13 =[ $painc13, zombie_painc14 ] {}; +void() zombie_painc14 =[ $painc14, zombie_painc15 ] {}; +void() zombie_painc15 =[ $painc15, zombie_painc16 ] {}; +void() zombie_painc16 =[ $painc16, zombie_painc17 ] {}; +void() zombie_painc17 =[ $painc17, zombie_painc18 ] {}; +void() zombie_painc18 =[ $painc18, zombie_run1 ] {}; + +void() zombie_paind1 =[ $paind1, zombie_paind2 ] {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() zombie_paind2 =[ $paind2, zombie_paind3 ] {}; +void() zombie_paind3 =[ $paind3, zombie_paind4 ] {}; +void() zombie_paind4 =[ $paind4, zombie_paind5 ] {}; +void() zombie_paind5 =[ $paind5, zombie_paind6 ] {}; +void() zombie_paind6 =[ $paind6, zombie_paind7 ] {}; +void() zombie_paind7 =[ $paind7, zombie_paind8 ] {}; +void() zombie_paind8 =[ $paind8, zombie_paind9 ] {}; +void() zombie_paind9 =[ $paind9, zombie_paind10 ] {ai_pain(1);}; +void() zombie_paind10 =[ $paind10, zombie_paind11 ] {}; +void() zombie_paind11 =[ $paind11, zombie_paind12 ] {}; +void() zombie_paind12 =[ $paind12, zombie_paind13 ] {}; +void() zombie_paind13 =[ $paind13, zombie_run1 ] {}; + +void() zombie_paine1 =[ $paine1, zombie_paine2 ] { +sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM); +self.health = 60; +}; +void() zombie_paine2 =[ $paine2, zombie_paine3 ] {ai_pain(8);}; +void() zombie_paine3 =[ $paine3, zombie_paine4 ] {ai_pain(5);}; +void() zombie_paine4 =[ $paine4, zombie_paine5 ] {ai_pain(3);}; +void() zombie_paine5 =[ $paine5, zombie_paine6 ] {ai_pain(1);}; +void() zombie_paine6 =[ $paine6, zombie_paine7 ] {ai_pain(2);}; +void() zombie_paine7 =[ $paine7, zombie_paine8 ] {ai_pain(1);}; +void() zombie_paine8 =[ $paine8, zombie_paine9 ] {ai_pain(1);}; +void() zombie_paine9 =[ $paine9, zombie_paine10 ] {ai_pain(2);}; +void() zombie_paine10 =[ $paine10, zombie_paine11 ] { +sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM); +self.solid = SOLID_NOT; +}; +void() zombie_paine11 =[ $paine11, zombie_paine12 ] {self.nextthink = self.nextthink + 5;self.health = 60;}; +void() zombie_paine12 =[ $paine12, zombie_paine13 ]{ +// see if ok to stand up +self.health = 60; +sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); +self.solid = SOLID_SLIDEBOX; +if (!walkmove (0, 0)) +{ + self.think = zombie_paine11; + self.solid = SOLID_NOT; + return; +} +}; +void() zombie_paine13 =[ $paine13, zombie_paine14 ] {}; +void() zombie_paine14 =[ $paine14, zombie_paine15 ] {}; +void() zombie_paine15 =[ $paine15, zombie_paine16 ] {}; +void() zombie_paine16 =[ $paine16, zombie_paine17 ] {}; +void() zombie_paine17 =[ $paine17, zombie_paine18 ] {}; +void() zombie_paine18 =[ $paine18, zombie_paine19 ] {}; +void() zombie_paine19 =[ $paine19, zombie_paine20 ] {}; +void() zombie_paine20 =[ $paine20, zombie_paine21 ] {}; +void() zombie_paine21 =[ $paine21, zombie_paine22 ] {}; +void() zombie_paine22 =[ $paine22, zombie_paine23 ] {}; +void() zombie_paine23 =[ $paine23, zombie_paine24 ] {}; +void() zombie_paine24 =[ $paine24, zombie_paine25 ] {}; +void() zombie_paine25 =[ $paine25, zombie_paine26 ] {ai_painforward(5);}; +void() zombie_paine26 =[ $paine26, zombie_paine27 ] {ai_painforward(3);}; +void() zombie_paine27 =[ $paine27, zombie_paine28 ] {ai_painforward(1);}; +void() zombie_paine28 =[ $paine28, zombie_paine29 ] {ai_pain(1);}; +void() zombie_paine29 =[ $paine29, zombie_paine30 ] {}; +void() zombie_paine30 =[ $paine30, zombie_run1 ] {}; + +void() zombie_die = +{ + sound (self, CHAN_VOICE, "zombie/z_gib.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_zombie.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); +}; + +/* +================= +zombie_pain + +Zombies can only be killed (gibbed) by doing 60 hit points of damage +in a single frame (rockets, grenades, quad shotgun, quad nailgun). + +A hit of 25 points or more (super shotgun, quad nailgun) will allways put it +down to the ground. + +A hit of from 10 to 40 points in one frame will cause it to go down if it +has been twice in two seconds, otherwise it goes into one of the four +fast pain frames. + +A hit of less than 10 points of damage (winged by a shotgun) will be ignored. + +FIXME: don't use pain_finished because of nightmare hack +================= +*/ +void(entity attacker, float take) zombie_pain = +{ + local float r; + + self.health = 60; // allways reset health + + if (take < 9) + return; // totally ignore + + if (self.inpain == 2) + return; // down on ground, so don't reset any counters + +// go down immediately if a big enough hit + if (take >= 25) + { + self.inpain = 2; + zombie_paine1 (); + return; + } + + if (self.inpain) + { +// if hit again in next gre seconds while not in pain frames, definately drop + self.pain_finished = time + 3; + return; // currently going through an animation, don't change + } + + if (self.pain_finished > time) + { +// hit again, so drop down + self.inpain = 2; + zombie_paine1 (); + return; + } + +// gp into one of the fast pain animations + self.inpain = 1; + + r = random(); + if (r < 0.25) + zombie_paina1 (); + else if (r < 0.5) + zombie_painb1 (); + else if (r < 0.75) + zombie_painc1 (); + else + zombie_paind1 (); +}; + +//HIPNOTIC +void() zombie_lay12 =[ $paine12, zombie_paine13 ] + {self.th_pain = zombie_pain;}; + +void() zombie_lay =[ $paine11, zombie_lay ] + { + self.th_stand = zombie_stand1; + self.th_pain = zombie_lay12; + + self.nextthink = self.nextthink + 0.1; + self.health = 60; + + if (FindTarget ()) + { + zombie_lay12(); + return; + } + }; + +//HIPNOTIC +//============================================================================ + +/*QUAKED monster_zombie (1 0 0) (-16 -16 -24) (16 16 32) Crucified ambush lying_down + +If crucified, stick the bounding box 12 pixels back into a wall to look right. +If lying_down, the zombie will be lying down when spawned and will rise up +when awakened by the player. +*/ +void() monster_zombie = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/zombie.mdl"); + precache_model ("progs/h_zombie.mdl"); + precache_model ("progs/zom_gib.mdl"); + + precache_sound ("zombie/z_idle.wav"); + precache_sound ("zombie/z_idle1.wav"); + precache_sound ("zombie/z_shot1.wav"); + precache_sound ("zombie/z_gib.wav"); + precache_sound ("zombie/z_pain.wav"); + precache_sound ("zombie/z_pain1.wav"); + precache_sound ("zombie/z_fall.wav"); + precache_sound ("zombie/z_miss.wav"); + precache_sound ("zombie/z_hit.wav"); + precache_sound ("zombie/idle_w2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/zombie.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 60; + + self.th_stand = zombie_stand1; + self.th_walk = zombie_walk1; + self.th_run = zombie_run1; + self.th_pain = zombie_pain; + self.th_die = zombie_die; + self.th_missile = zombie_missile; + + if (self.spawnflags & SPAWN_CRUCIFIED) + { + self.movetype = MOVETYPE_NONE; + zombie_cruc1 (); + } + else + walkmonster_start(); + + //HIPNOTIC + if (self.spawnflags & SPAWN_LYINGDOWN) + { + self.th_stand = zombie_lay; + } +}; diff --git a/quakec_hipnotic/plats.qc b/quakec_hipnotic/plats.qc new file mode 100644 index 0000000..1c6fca8 --- /dev/null +++ b/quakec_hipnotic/plats.qc @@ -0,0 +1,383 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + + +void() plat_center_touch; +void() plat_outside_touch; +void() plat_trigger_use; +void() plat_go_up; +void() plat_go_down; +void() plat_crush; +float PLAT_LOW_TRIGGER = 1; + +void() plat_spawn_inside_trigger = +{ + local entity trigger; + local vector tmin, tmax; + +// +// middle trigger +// + trigger = spawn(); + trigger.touch = plat_center_touch; + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.enemy = self; + + tmin = self.mins + '25 25 0'; + tmax = self.maxs - '25 25 -8'; + tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8); + if (self.spawnflags & PLAT_LOW_TRIGGER) + tmax_z = tmin_z + 8; + + if (self.size_x <= 50) + { + tmin_x = (self.mins_x + self.maxs_x) / 2; + tmax_x = tmin_x + 1; + } + if (self.size_y <= 50) + { + tmin_y = (self.mins_y + self.maxs_y) / 2; + tmax_y = tmin_y + 1; + } + + setsize (trigger, tmin, tmax); +}; + +void() plat_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + self.think = plat_go_down; + self.nextthink = self.ltime + 3; +}; + +void() plat_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() plat_go_down = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_DOWN; + SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom); +}; + +void() plat_go_up = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos1, self.speed, plat_hit_top); +}; + +void() plat_center_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + self = self.enemy; + if (self.state == STATE_BOTTOM) + plat_go_up (); + else if (self.state == STATE_TOP) + self.nextthink = self.ltime + 1; // delay going down +}; + +void() plat_outside_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + +//dprint ("plat_outside_touch\n"); + self = self.enemy; + if (self.state == STATE_TOP) + plat_go_down (); +}; + +void() plat_trigger_use = +{ + if (self.think) + return; // allready activated + plat_go_down(); +}; + + +void() plat_crush = +{ +//dprint ("plat_crush\n"); + + T_Damage (other, self, self, 1); + + if (self.state == STATE_UP) + plat_go_down (); + else if (self.state == STATE_DOWN) + plat_go_up (); + else + objerror ("plat_crush: bad self.state\n"); +}; + +void() plat_use = +{ + self.use = SUB_Null; + if (self.state != STATE_UP) + objerror ("plat_use: not in up state"); + plat_go_down(); +}; + + +/*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. + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height. +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + + +void() func_plat = + +{ +local entity t; + + if (!self.t_length) + self.t_length = 80; + if (!self.t_width) + self.t_width = 10; + + if (self.sounds == 0) + self.sounds = 2; +// FIX THIS TO LOAD A GENERIC PLAT SOUND + + if (self.sounds == 1) + { + precache_sound ("plats/plat1.wav"); + precache_sound ("plats/plat2.wav"); + self.noise = "plats/plat1.wav"; + self.noise1 = "plats/plat2.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("plats/medplat1.wav"); + precache_sound ("plats/medplat2.wav"); + self.noise = "plats/medplat1.wav"; + self.noise1 = "plats/medplat2.wav"; + } + + + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "plat"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + + self.blocked = plat_crush; + if (!self.speed) + self.speed = 150; + +// pos1 is the top position, pos2 is the bottom + self.pos1 = self.origin; + self.pos2 = self.origin; + if (self.height) + self.pos2_z = self.origin_z - self.height; + else + self.pos2_z = self.origin_z - self.size_z + 8; + + self.use = plat_trigger_use; + + plat_spawn_inside_trigger (); // the "start moving" trigger + + if (self.targetname) + { + self.state = STATE_UP; + self.use = plat_use; + } + else + { + setorigin (self, self.pos2); + self.state = STATE_BOTTOM; + } +}; + +//============================================================================ + +void() train_next; +void() func_train_find; + +void() train_blocked = +{ + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; +void() train_use = +{ + if (self.think != func_train_find) + return; // already activated + train_next(); +}; + +void() train_wait = +{ + if (self.wait) + { + self.nextthink = self.ltime + self.wait; + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + else + self.nextthink = self.ltime + 0.1; + + self.think = train_next; +}; + +void() train_next = +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + if (!self.target) + objerror ("train_next: no next target"); + if (targ.wait) + self.wait = targ.wait; + else + self.wait = 0; + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + SUB_CalcMove (targ.origin - self.mins, self.speed, train_wait); +}; + +void() func_train_find = + +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + setorigin (self, targ.origin - self.mins); + if (!self.targetname) + { // not triggered, so start immediately + self.nextthink = self.ltime + 0.1; + self.think = train_next; + } +}; + +/*QUAKED func_train (0 .5 .8) ? +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 +sounds +1) ratchet metal + +*/ +void() func_train = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("func_train without a target"); + if (!self.dmg) + self.dmg = 2; + + if (self.sounds == 0) + { + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + } + + if (self.sounds == 1) + { + self.noise = ("plats/train2.wav"); + precache_sound ("plats/train2.wav"); + self.noise1 = ("plats/train1.wav"); + precache_sound ("plats/train1.wav"); + } + + self.cnt = 1; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = train_use; + self.classname = "train"; + + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + +/*QUAKED misc_teleporttrain (0 .5 .8) (-8 -8 -8) (8 8 8) +This is used for the final bos +*/ +void() misc_teleporttrain = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("func_train without a target"); + + self.cnt = 1; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = train_use; + self.avelocity = '100 200 300'; + + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + + precache_model2 ("progs/teleport.mdl"); + setmodel (self, "progs/teleport.mdl"); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + diff --git a/quakec_hipnotic/player.qc b/quakec_hipnotic/player.qc new file mode 100644 index 0000000..f6b154d --- /dev/null +++ b/quakec_hipnotic/player.qc @@ -0,0 +1,810 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +void() bubble_bob; + +/* +============================================================================== + +PLAYER + +============================================================================== +*/ + +$cd id1/models/player_4 +$origin 0 -6 24 +$base base +$skin skin + +// +// running +// +$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6 + +$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6 + +// +// standing +// +$frame stand1 stand2 stand3 stand4 stand5 + +$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6 +$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12 + + +// +// pain +// +$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + + +// +// death +// + +$frame axdeth1 axdeth2 axdeth3 axdeth4 axdeth5 axdeth6 +$frame axdeth7 axdeth8 axdeth9 + +$frame deatha1 deatha2 deatha3 deatha4 deatha5 deatha6 deatha7 deatha8 +$frame deatha9 deatha10 deatha11 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 deathc12 deathc13 deathc14 deathc15 + +$frame deathd1 deathd2 deathd3 deathd4 deathd5 deathd6 deathd7 +$frame deathd8 deathd9 + +$frame deathe1 deathe2 deathe3 deathe4 deathe5 deathe6 deathe7 +$frame deathe8 deathe9 + +// +// attacks +// +$frame nailatt1 nailatt2 + +$frame light1 light2 + +$frame rockatt1 rockatt2 rockatt3 rockatt4 rockatt5 rockatt6 + +$frame shotatt1 shotatt2 shotatt3 shotatt4 shotatt5 shotatt6 + +$frame axatt1 axatt2 axatt3 axatt4 axatt5 axatt6 + +$frame axattb1 axattb2 axattb3 axattb4 axattb5 axattb6 + +$frame axattc1 axattc2 axattc3 axattc4 axattc5 axattc6 + +$frame axattd1 axattd2 axattd3 axattd4 axattd5 axattd6 + + +/* +============================================================================== +PLAYER +============================================================================== +*/ + +void() player_run; + +void() player_stand1 =[ $axstnd1, player_stand1 ] +{ + self.weaponframe=0; + if (self.velocity_x || self.velocity_y) + { + self.walkframe=0; + player_run(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe >= 12) + self.walkframe = 0; + self.frame = $axstnd1 + self.walkframe; + } + else if (self.weapon == IT_MJOLNIR) + { + if (self.walkframe >= 12) + self.walkframe = 0; + self.frame = 6 + self.walkframe; + } + else + { + if (self.walkframe >= 5) + self.walkframe = 0; + self.frame = $stand1 + self.walkframe; + } + self.walkframe = self.walkframe + 1; +}; + +void() player_run =[ $rockrun1, player_run ] +{ + self.weaponframe=0; + if (!self.velocity_x && !self.velocity_y) + { + self.walkframe=0; + player_stand1(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe == 6) + self.walkframe = 0; + self.frame = $axrun1 + self.walkframe; + } + else if (self.weapon == IT_MJOLNIR) + { + if (self.walkframe == 6) + self.walkframe = 0; + self.frame = self.walkframe; + } + else + { + if (self.walkframe == 6) + self.walkframe = 0; + self.frame = self.frame + self.walkframe; + } + if (footsteps == 1) + { + self.spawnsilent = self.spawnsilent + vlen(self.origin - self.old_velocity); + self.old_velocity = self.origin; + if (self.flags & FL_ONGROUND) + { + if (self.spawnsilent > 95) + { + local float r; + if (self.spawnsilent > 190) + self.spawnsilent = 0; + else + { + self.spawnsilent = 0.5*(self.spawnsilent - 95); + } + r = random(); + if (r < 0.14) + sound (self, CHAN_VOICE, "misc/foot1.wav", 0.5, ATTN_NORM); + else if (r < 0.29) + sound (self, CHAN_VOICE, "misc/foot2.wav", 0.5, ATTN_NORM); + else if (r < 0.43) + sound (self, CHAN_VOICE, "misc/foot3.wav", 0.5, ATTN_NORM); + else if (r < 0.58) + sound (self, CHAN_VOICE, "misc/foot4.wav", 0.5, ATTN_NORM); + else if (r < 0.72) + sound (self, CHAN_VOICE, "misc/foot5.wav", 0.5, ATTN_NORM); + else if (r < 0.86) + sound (self, CHAN_VOICE, "misc/foot6.wav", 0.5, ATTN_NORM); + else + sound (self, CHAN_VOICE, "misc/foot7.wav", 0.5, ATTN_NORM); + } + } + } + self.walkframe = self.walkframe + 1; +}; + + +void() player_shot1 = [$shotatt1, player_shot2 ] {self.weaponframe=1; +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() player_shot2 = [$shotatt2, player_shot3 ] {self.weaponframe=2;}; +void() player_shot3 = [$shotatt3, player_shot4 ] {self.weaponframe=3;}; +void() player_shot4 = [$shotatt4, player_shot5 ] {self.weaponframe=4;}; +void() player_shot5 = [$shotatt5, player_shot6 ] {self.weaponframe=5;}; +void() player_shot6 = [$shotatt6, player_run ] {self.weaponframe=6;}; + +void() player_axe1 = [$axatt1, player_axe2 ] {self.weaponframe=1;}; +void() player_axe2 = [$axatt2, player_axe3 ] {self.weaponframe=2;}; +void() player_axe3 = [$axatt3, player_axe4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axe4 = [$axatt4, player_run ] {self.weaponframe=4;}; + +void() player_axeb1 = [$axattb1, player_axeb2 ] {self.weaponframe=5;}; +void() player_axeb2 = [$axattb2, player_axeb3 ] {self.weaponframe=6;}; +void() player_axeb3 = [$axattb3, player_axeb4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axeb4 = [$axattb4, player_run ] {self.weaponframe=8;}; + +void() player_axec1 = [$axattc1, player_axec2 ] {self.weaponframe=1;}; +void() player_axec2 = [$axattc2, player_axec3 ] {self.weaponframe=2;}; +void() player_axec3 = [$axattc3, player_axec4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axec4 = [$axattc4, player_run ] {self.weaponframe=4;}; + +void() player_axed1 = [$axattd1, player_axed2 ] {self.weaponframe=5;}; +void() player_axed2 = [$axattd2, player_axed3 ] {self.weaponframe=6;}; +void() player_axed3 = [$axattd3, player_axed4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axed4 = [$axattd4, player_run ] {self.weaponframe=8;}; + + +//============================================================================ + +void() player_nail1 =[$nailatt1, player_nail2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireSpikes (4); + self.attack_finished = time + 0.2; +}; +void() player_nail2 =[$nailatt2, player_nail1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireSpikes (-4); + self.attack_finished = time + 0.2; +}; + +//============================================================================ + +void() player_light1 =[$light1, player_light2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) + self.weaponframe = 1; + SuperDamageSound(); + W_FireLightning(); + self.attack_finished = time + 0.2; +}; +void() player_light2 =[$light2, player_light1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) + self.weaponframe = 1; + SuperDamageSound(); + W_FireLightning(); + self.attack_finished = time + 0.2; +}; + +//MED 10/18/96 added HIPWEAPONS +//============================================================================ + +void() player_hammer1 = [32, player_hammer2 ] {self.weaponframe=1;}; +void() player_hammer2 = [33, player_hammer3 ] {self.weaponframe=2;}; +void() player_hammer3 = [34, player_hammer4 ] {self.weaponframe=3;}; +void() player_hammer4 = [35, player_hammer5 ] {self.weaponframe=4;HIP_FireMjolnir();}; +void() player_hammer5 = [36, player_hammer6 ] {self.weaponframe=4;}; +void() player_hammer6 = [37, player_run ] {self.weaponframe=4;}; + +void() player_mjolnir1 = [38, player_mjolnir2 ] {self.weaponframe=1;}; +void() player_mjolnir2 = [39, player_mjolnir3 ] {self.weaponframe=2;}; +void() player_mjolnir3 = [40, player_mjolnir4 ] {self.weaponframe=3;}; +void() player_mjolnir4 = [41, player_mjolnir5 ] {self.weaponframe=4;HIP_FireMjolnir();}; +void() player_mjolnir5 = [42, player_mjolnir6 ] {self.weaponframe=4;}; +void() player_mjolnir6 = [43, player_run ] {self.weaponframe=4;}; + +//============================================================================ + +//MED 10/18/96 added HIPSWEAPONS +//void() player_laser1 =[$nailatt1, player_laser2 ] {self.attack_finished = time + 0.1;self.weaponframe=1;self.nextthink = time + 0.09;HIP_FireLaser(0);}; +//void() player_laser2 =[$nailatt2, player_laser4 ] {self.attack_finished = time + 0.1;self.weaponframe=2;self.nextthink = time + 0.09;}; +//void() player_laser3 =[$nailatt1, player_laser4 ] {self.attack_finished = time + 0.1;self.weaponframe=3;self.nextthink = time + 0.09;}; +//void() player_laser4 =[$nailatt2, player_laser5 ] {self.attack_finished = time + 0.1;self.weaponframe=4;self.nextthink = time + 0.09;HIP_FireLaser(1);}; +//void() player_laser5 =[$nailatt1, player_laser1 ] {self.attack_finished = time + 0.1;self.weaponframe=5;self.nextthink = time + 0.09;}; +//void() player_laser6 =[$nailatt2, player_laser1 ] {self.attack_finished = time + 0.1;self.weaponframe=6;self.nextthink = time + 0.09;}; +void() player_laser1 =[$nailatt1, player_laser3 ] {self.attack_finished = time + 0.1;self.weaponframe=1;self.nextthink = time + 0.1;HIP_FireLaser(0);}; +void() player_laser2 =[$nailatt2, player_laser3 ] {self.attack_finished = time + 0.1;self.weaponframe=2;self.nextthink = time + 0.1;}; +void() player_laser3 =[$nailatt2, player_laser1 ] {self.attack_finished = time + 0.1;self.weaponframe=4;self.nextthink = time + 0.1;HIP_FireLaser(1);}; +void() player_laser4 =[$nailatt1, player_laser1 ] {self.attack_finished = time + 0.1;self.weaponframe=5;self.nextthink = time + 0.1;}; +//============================================================================ + + +void() player_rocket1 =[$rockatt1, player_rocket2 ] {self.weaponframe=1; +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() player_rocket2 =[$rockatt2, player_rocket3 ] {self.weaponframe=2;}; +void() player_rocket3 =[$rockatt3, player_rocket4 ] {self.weaponframe=3;}; +void() player_rocket4 =[$rockatt4, player_rocket5 ] {self.weaponframe=4;}; +void() player_rocket5 =[$rockatt5, player_rocket6 ] {self.weaponframe=5;}; +void() player_rocket6 =[$rockatt6, player_run ] {self.weaponframe=6;}; +void(float num_bubbles) DeathBubbles; + +void() PainSound = +{ +local float rs; + + if (self.health < 0) + return; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + +// water pain sounds + if (self.watertype == CONTENT_WATER && self.waterlevel == 3) + { + DeathBubbles(1); + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM); + return; + } + +// slime pain sounds + if (self.watertype == CONTENT_SLIME) + { +// FIX ME put in some steam here + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + return; + } + + if (self.watertype == CONTENT_LAVA) + { + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + return; + } + + if (self.pain_finished > time) + { + self.axhitme = 0; + return; + } + self.pain_finished = time + 0.5; + +// don't make multiple pain sounds right after each other + +// ax pain sound + if (self.axhitme == 1) + { + self.axhitme = 0; + sound (self, CHAN_VOICE, "player/axhit1.wav", 1, ATTN_NORM); + return; + } + + + rs = rint((random() * 5) + 1); + + self.noise = ""; + if (rs == 1) + self.noise = "player/pain1.wav"; + else if (rs == 2) + self.noise = "player/pain2.wav"; + else if (rs == 3) + self.noise = "player/pain3.wav"; + else if (rs == 4) + self.noise = "player/pain4.wav"; + else if (rs == 5) + self.noise = "player/pain5.wav"; + else + self.noise = "player/pain6.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + return; +}; + +void() player_pain1 = [ $pain1, player_pain2 ] {PainSound();self.weaponframe=0;}; +void() player_pain2 = [ $pain2, player_pain3 ] {}; +void() player_pain3 = [ $pain3, player_pain4 ] {}; +void() player_pain4 = [ $pain4, player_pain5 ] {}; +void() player_pain5 = [ $pain5, player_pain6 ] {}; +void() player_pain6 = [ $pain6, player_run ] {}; + +void() player_axpain1 = [ $axpain1, player_axpain2 ] {PainSound();self.weaponframe=0;}; +void() player_axpain2 = [ $axpain2, player_axpain3 ] {}; +void() player_axpain3 = [ $axpain3, player_axpain4 ] {}; +void() player_axpain4 = [ $axpain4, player_axpain5 ] {}; +void() player_axpain5 = [ $axpain5, player_axpain6 ] {}; +void() player_axpain6 = [ $axpain6, player_run ] {}; + +void() player_hampain1 = [ 18, player_hampain2 ] {PainSound();self.weaponframe=0;}; +void() player_hampain2 = [ 19, player_hampain3 ] {}; +void() player_hampain3 = [ 20, player_hampain4 ] {}; +void() player_hampain4 = [ 21, player_hampain5 ] {}; +void() player_hampain5 = [ 22, player_hampain6 ] {}; +void() player_hampain6 = [ 23, player_run ] {}; + +void() player_pain = +{ + if (self.weaponframe) + return; + + if (self.invisible_finished > time) + return; // eyes don't have pain frames + + if (self.weapon == IT_AXE) + player_axpain1 (); + else if (self.weapon == IT_MJOLNIR) + player_hampain1 (); + else + player_pain1 (); +}; + +void() player_diea1; +void() player_dieb1; +void() player_diec1; +void() player_died1; +void() player_diee1; +void() player_die_ax1; +void() player_die_ham1; + +void() DeathBubblesSpawn = +{ +local entity bubble; + if (self.owner.waterlevel != 3) + return; + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.owner.origin + '0 0 24'); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + 0.1; + self.think = DeathBubblesSpawn; + self.air_finished = self.air_finished + 1; + if (self.air_finished >= self.bubble_count) + remove(self); +}; + +void(float num_bubbles) DeathBubbles = +{ +local entity bubble_spawner; + + bubble_spawner = spawn(); + setorigin (bubble_spawner, self.origin); + bubble_spawner.movetype = MOVETYPE_NONE; + bubble_spawner.solid = SOLID_NOT; + bubble_spawner.nextthink = time + 0.1; + bubble_spawner.think = DeathBubblesSpawn; + bubble_spawner.air_finished = 0; + bubble_spawner.owner = self; + bubble_spawner.bubble_count = num_bubbles; + return; +}; + + +void() DeathSound = +{ +local float rs; + + // water death sounds + if (self.waterlevel == 3) + { + DeathBubbles(20); + sound (self, CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + + rs = rint ((random() * 4) + 1); + if (rs == 1) + self.noise = "player/death1.wav"; + if (rs == 2) + self.noise = "player/death2.wav"; + if (rs == 3) + self.noise = "player/death3.wav"; + if (rs == 4) + self.noise = "player/death4.wav"; + if (rs == 5) + self.noise = "player/death5.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NONE); + return; +}; + + +void() PlayerDead = +{ + self.nextthink = -1; +// allow respawn after a certain time + self.deadflag = DEAD_DEAD; +}; + +vector(float dm) VelocityForDamage = +{ + local vector v; + +//MED 01/17/97 + if (vlen(damage_inflictor.velocity)>0) + { + v = 0.2 * damage_inflictor.velocity; + v = v + (25 * normalize(self.origin-damage_inflictor.origin)); + v_z = 100 + 100 * random(); + v_x = v_x + (50 * crandom()); + v_y = v_y + (50 * crandom()); + } + else + { + v_x = 100 * crandom(); + v_y = 100 * crandom(); + v_z = 200 + 100 * random(); + } + if (dm > -50) + { +// dprint ("level 1\n"); + v = v * 0.7; + } + else if (dm > -200) + { +// dprint ("level 3\n"); + v = v * 2; + } + else + v = v * 10; + return v; +}; + +void(string gibname, float dm) ThrowGib = +{ + local entity new; + + new = spawn(); + new.origin = self.origin; + setmodel (new, gibname); + setsize (new, '0 0 0', '0 0 0'); + new.velocity = VelocityForDamage (dm); + new.movetype = MOVETYPE_BOUNCE; + new.solid = SOLID_NOT; + new.avelocity_x = random()*600; + new.avelocity_y = random()*600; + new.avelocity_z = random()*600; + new.think = SUB_Remove; + new.ltime = time; + new.nextthink = time + 10 + random()*10; + new.frame = 0; + new.flags = 0; +}; + +//MED +void() HeadThink = + { + self.nextthink = -1; + if (world.worldtype != 2) + { + if (random()<0.1) + sound (self, 6, "misc/flys.wav", 0.7, ATTN_STATIC); + } + }; + +void(string gibname, float dm) ThrowHead = +{ + setmodel (self, gibname); + self.frame = 0; +// self.nextthink = -1; +//MED + self.nextthink = time + 1.5; + self.movetype = MOVETYPE_BOUNCE; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.view_ofs = '0 0 8'; + setsize (self, '-16 -16 0', '16 16 56'); + self.velocity = VelocityForDamage (dm); + self.origin_z = self.origin_z - 24; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.avelocity = crandom() * '0 600 0'; +//MED + self.gorging = TRUE; + self.think = HeadThink; +}; + + +void() GibPlayer = +{ + ThrowHead ("progs/h_player.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + + self.deadflag = DEAD_DEAD; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (damage_attacker.classname == "teledeath2") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (random() < 0.5) + sound (self, CHAN_VOICE, "player/gib.wav", 1, ATTN_NONE); + else + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NONE); +}; + +void() PlayerDie = +{ + local float i; + + self.items = self.items - (self.items & IT_INVISIBILITY); + self.invisible_finished = 0; // don't die as eyes + self.invincible_finished = 0; + self.super_damage_finished = 0; + self.radsuit_finished = 0; + //JIM + self.wetsuit_finished = 0; + self.modelindex = modelindex_player; // don't use eyes + //JIM + self.empathy_finished = 0; + self.items2 = self.items2 - (self.items2 & HIP_IT_EMPATHY_SHIELDS); + + if (deathmatch || coop) + DropBackpack(); + + self.weaponmodel=""; + self.view_ofs = '0 0 -8'; + self.deadflag = DEAD_DYING; + self.solid = SOLID_NOT; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.movetype = MOVETYPE_TOSS; + if (self.velocity_z < 10) + self.velocity_z = self.velocity_z + random()*300; + + if (self.health < -40) + { + GibPlayer (); + return; + } + + DeathSound(); + + self.angles_x = 0; + self.angles_z = 0; + + if (self.weapon == IT_AXE) + { + player_die_ax1 (); + return; + } + //MED 12/04/96 + else if (self.weapon == IT_MJOLNIR) + { + self.modelindex = modelindex_hammer; // use hammer model + player_die_ham1(); + return; + } + + i = cvar("temp1"); + if (!i) + i = 1 + floor(random()*6); + + if (i == 1) + player_diea1(); + else if (i == 2) + player_dieb1(); + else if (i == 3) + player_diec1(); + else if (i == 4) + player_died1(); + else + player_diee1(); + +}; + +void() set_suicide_frame = +{ // used by klill command and diconnect command + if (self.model != "progs/player.mdl") + return; // allready gibbed + self.frame = $deatha11; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_TOSS; + self.deadflag = DEAD_DEAD; + self.nextthink = -1; +}; + + +void() player_diea1 = [ $deatha1, player_diea2 ] {}; +void() player_diea2 = [ $deatha2, player_diea3 ] {}; +void() player_diea3 = [ $deatha3, player_diea4 ] {}; +void() player_diea4 = [ $deatha4, player_diea5 ] {}; +void() player_diea5 = [ $deatha5, player_diea6 ] {}; +void() player_diea6 = [ $deatha6, player_diea7 ] {}; +void() player_diea7 = [ $deatha7, player_diea8 ] {}; +void() player_diea8 = [ $deatha8, player_diea9 ] {}; +void() player_diea9 = [ $deatha9, player_diea10 ] {}; +void() player_diea10 = [ $deatha10, player_diea11 ] {}; +void() player_diea11 = [ $deatha11, player_diea11 ] {PlayerDead();}; + +void() player_dieb1 = [ $deathb1, player_dieb2 ] {}; +void() player_dieb2 = [ $deathb2, player_dieb3 ] {}; +void() player_dieb3 = [ $deathb3, player_dieb4 ] {}; +void() player_dieb4 = [ $deathb4, player_dieb5 ] {}; +void() player_dieb5 = [ $deathb5, player_dieb6 ] {}; +void() player_dieb6 = [ $deathb6, player_dieb7 ] {}; +void() player_dieb7 = [ $deathb7, player_dieb8 ] {}; +void() player_dieb8 = [ $deathb8, player_dieb9 ] {}; +void() player_dieb9 = [ $deathb9, player_dieb9 ] {PlayerDead();}; + +void() player_diec1 = [ $deathc1, player_diec2 ] {}; +void() player_diec2 = [ $deathc2, player_diec3 ] {}; +void() player_diec3 = [ $deathc3, player_diec4 ] {}; +void() player_diec4 = [ $deathc4, player_diec5 ] {}; +void() player_diec5 = [ $deathc5, player_diec6 ] {}; +void() player_diec6 = [ $deathc6, player_diec7 ] {}; +void() player_diec7 = [ $deathc7, player_diec8 ] {}; +void() player_diec8 = [ $deathc8, player_diec9 ] {}; +void() player_diec9 = [ $deathc9, player_diec10 ] {}; +void() player_diec10 = [ $deathc10, player_diec11 ] {}; +void() player_diec11 = [ $deathc11, player_diec12 ] {}; +void() player_diec12 = [ $deathc12, player_diec13 ] {}; +void() player_diec13 = [ $deathc13, player_diec14 ] {}; +void() player_diec14 = [ $deathc14, player_diec15 ] {}; +void() player_diec15 = [ $deathc15, player_diec15 ] {PlayerDead();}; + +void() player_died1 = [ $deathd1, player_died2 ] {}; +void() player_died2 = [ $deathd2, player_died3 ] {}; +void() player_died3 = [ $deathd3, player_died4 ] {}; +void() player_died4 = [ $deathd4, player_died5 ] {}; +void() player_died5 = [ $deathd5, player_died6 ] {}; +void() player_died6 = [ $deathd6, player_died7 ] {}; +void() player_died7 = [ $deathd7, player_died8 ] {}; +void() player_died8 = [ $deathd8, player_died9 ] {}; +void() player_died9 = [ $deathd9, player_died9 ] {PlayerDead();}; + +void() player_diee1 = [ $deathe1, player_diee2 ] {}; +void() player_diee2 = [ $deathe2, player_diee3 ] {}; +void() player_diee3 = [ $deathe3, player_diee4 ] {}; +void() player_diee4 = [ $deathe4, player_diee5 ] {}; +void() player_diee5 = [ $deathe5, player_diee6 ] {}; +void() player_diee6 = [ $deathe6, player_diee7 ] {}; +void() player_diee7 = [ $deathe7, player_diee8 ] {}; +void() player_diee8 = [ $deathe8, player_diee9 ] {}; +void() player_diee9 = [ $deathe9, player_diee9 ] {PlayerDead();}; + +void() player_die_ax1 = [ $axdeth1, player_die_ax2 ] {}; +void() player_die_ax2 = [ $axdeth2, player_die_ax3 ] {}; +void() player_die_ax3 = [ $axdeth3, player_die_ax4 ] {}; +void() player_die_ax4 = [ $axdeth4, player_die_ax5 ] {}; +void() player_die_ax5 = [ $axdeth5, player_die_ax6 ] {}; +void() player_die_ax6 = [ $axdeth6, player_die_ax7 ] {}; +void() player_die_ax7 = [ $axdeth7, player_die_ax8 ] {}; +void() player_die_ax8 = [ $axdeth8, player_die_ax9 ] {}; +void() player_die_ax9 = [ $axdeth9, player_die_ax9 ] {PlayerDead();}; + +void() player_die_ham1 = [ 24 , player_die_ham2 ] {}; +void() player_die_ham2 = [ 25 , player_die_ham3 ] {}; +void() player_die_ham3 = [ 26 , player_die_ham4 ] {}; +void() player_die_ham4 = [ 27 , player_die_ham5 ] {}; +void() player_die_ham5 = [ 28 , player_die_ham6 ] {}; +void() player_die_ham6 = [ 29 , player_die_ham7 ] {}; +void() player_die_ham7 = [ 30 , player_die_ham8 ] {}; +void() player_die_ham8 = [ 31 , player_die_ham8 ] {PlayerDead();}; diff --git a/quakec_hipnotic/progs.src b/quakec_hipnotic/progs.src new file mode 100644 index 0000000..c1dbd0e --- /dev/null +++ b/quakec_hipnotic/progs.src @@ -0,0 +1,64 @@ +../progs.dat + +defs.qc +hipdefs.qc //JIM +subs.qc +fight.qc +ai.qc +combat.qc +items.qc +weapons.qc +world.qc +client.qc +player.qc +monsters.qc +doors.qc +buttons.qc +triggers.qc +plats.qc +misc.qc + +monsters/ogre.qc +monsters/fiend.qc +monsters/shambler.qc +monsters/knight.qc +monsters/grunt.qc +monsters/scrag.qc +monsters/rottweiler.qc +monsters/zombie.qc +monsters/chthon.qc + +monsters/spawn.qc // registered +monsters/hellknight.qc // registered +monsters/rotfish.qc // registered +monsters/vore.qc // registered +monsters/enforcer.qc // registered +monsters/shub.qc // registered + +// Hipnotic Quake additions + +hipsubs.qc +hip_brk.qc +hiptrain.qc +hip_expl.qc +hipquake.qc +hipcount.qc +hipitems.qc +hiprubbl.qc +hip_push.qc +hip_part.qc +hipspawn.qc +hipgrem.qc +hiparma.qc +hipspike.qc +hiprot.qc +hipscrge.qc +hipholes.qc +hipclock.qc +hiptrig.qc +hipmisc.qc +hipwater.qc +hipdecoy.qc + +bots/bot.qc // bot support + diff --git a/quakec_hipnotic/sprites.qc b/quakec_hipnotic/sprites.qc new file mode 100644 index 0000000..8631d55 --- /dev/null +++ b/quakec_hipnotic/sprites.qc @@ -0,0 +1,45 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +// these are the only sprites still in the game... + +$spritename s_explod +$type vp_parallel +$load id1/gfx/sprites/explod03.lbm +$frame 24 24 56 56 +$frame 120 24 56 56 +$frame 216 24 56 56 +$frame 24 88 56 56 +$frame 120 88 56 56 +$frame 216 88 56 56 + + +$spritename s_bubble +$type vp_parallel +$load id1/gfx/sprites/bubble.lbm +$frame 16 16 16 16 +$frame 40 16 16 16 + + +$spritename s_light +$type vp_parallel +$load id1/gfx/sprites/light.lbm +$frame 104 32 32 32 + diff --git a/quakec_hipnotic/subs.qc b/quakec_hipnotic/subs.qc new file mode 100644 index 0000000..a2e5f9f --- /dev/null +++ b/quakec_hipnotic/subs.qc @@ -0,0 +1,335 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + + +void() SUB_Null = {}; + +void() SUB_Remove = {remove(self);}; + + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +vector() SetMovedir = +{ + if (self.angles == '0 -1 0') + self.movedir = '0 0 1'; + else if (self.angles == '0 -2 0') + self.movedir = '0 0 -1'; + else + { + makevectors (self.angles); + self.movedir = v_forward; + } + + self.angles = '0 0 0'; +}; + +/* +================ +InitTrigger +================ +*/ +void() InitTrigger = +{ +// trigger angles are used for one-way touches. An angle of 0 is assumed +// to mean no restrictions, so use a yaw of 360 instead. + if (self.angles != '0 0 0') + SetMovedir (); + self.solid = SOLID_TRIGGER; + setmodel (self, self.model); // set size and link into world + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + self.model = ""; +}; + +/* +============= +SUB_CalcMove + +calculate self.velocity and self.nextthink to reach dest from +self.origin traveling at speed +=============== +*/ +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt = +{ +local entity stemp; + stemp = self; + self = ent; + + SUB_CalcMove (tdest, tspeed, func); + self = stemp; +}; + +void(vector tdest, float tspeed, void() func) SUB_CalcMove = +{ +local vector vdestdelta; +local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + + self.think1 = func; + self.finaldest = tdest; + self.think = SUB_CalcMoveDone; + + if (tdest == self.origin) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + +// set destdelta to the vector needed to move + vdestdelta = tdest - self.origin; + +// calculate length of vector + len = vlen (vdestdelta); + +// divide by speed to get time to reach dest + traveltime = len / tspeed; + + if (traveltime < 0.1) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + +// set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + +// scale the destdelta vector by the time spent traveling to get velocity + self.velocity = vdestdelta * (1/traveltime); // qcc won't take vec/float +}; + +/* +============ +After moving, set origin to exact final destination +============ +*/ +void() SUB_CalcMoveDone = +{ + setorigin(self, self.finaldest); + self.velocity = '0 0 0'; + self.nextthink = -1; + if (self.think1) + self.think1(); +}; + + +/* +============= +SUB_CalcAngleMove + +calculate self.avelocity and self.nextthink to reach destangle from +self.angles rotating + +The calling function should make sure self.think is valid +=============== +*/ +void(entity ent, vector destangle, float tspeed, void() func) SUB_CalcAngleMoveEnt = +{ +local entity stemp; + stemp = self; + self = ent; + SUB_CalcAngleMove (destangle, tspeed, func); + self = stemp; +}; + +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove = +{ +local vector destdelta; +local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + +// set destdelta to the vector needed to move + destdelta = destangle - self.angles; + +// calculate length of vector + len = vlen (destdelta); + +// divide by speed to get time to reach dest + traveltime = len / tspeed; + +// set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + +// scale the destdelta vector by the time spent traveling to get velocity + self.avelocity = destdelta * (1 / traveltime); + + self.think1 = func; + self.finalangle = destangle; + self.think = SUB_CalcAngleMoveDone; +}; + +/* +============ +After rotating, set angle to exact final angle +============ +*/ +void() SUB_CalcAngleMoveDone = +{ + self.angles = self.finalangle; + self.avelocity = '0 0 0'; + self.nextthink = -1; + if (self.think1) + self.think1(); +}; + + +//============================================================================= + +void() DelayThink = +{ + activator = self.enemy; + SUB_UseTargets (); + remove(self); +}; + +/* +============================== +SUB_UseTargets + +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. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void() SUB_UseTargets = +{ + local entity t, stemp, otemp, act; + +// +// check for a delay +// + if (self.delay) + { + // create a temp object to fire at a later time + t = spawn(); + t.classname = "DelayedUse"; + t.nextthink = time + self.delay; + t.think = DelayThink; + t.enemy = activator; + t.message = self.message; + t.killtarget = self.killtarget; + t.target = self.target; + return; + } + + +// +// print the message +// + if (activator.classname == "player" && self.message != "") + { + centerprint (activator, self.message); + if (!self.noise) + sound (activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + +// +// kill the killtagets +// + if (self.killtarget) + { + t = world; + do + { + t = find (t, targetname, self.killtarget); + if (!t) + return; + remove (t); + } while ( 1 ); + } + +// +// fire targets +// + if (self.target) + { + act = activator; + t = world; + do + { + t = find (t, targetname, self.target); + if (!t) + { + return; + } + stemp = self; + otemp = other; + self = t; + other = stemp; + if (self.use != SUB_Null) + { + if (self.use) + self.use (); + } + self = stemp; + other = otemp; + activator = act; + } while ( 1 ); + } + + +}; + + +/* + +in nightmare mode, all attack_finished times become 0 +some monsters refire twice automatically +update: not anymore! it makes nightmare too easy +*/ + +void(float normal) SUB_AttackFinished = +{ + self.cnt = 0; // refire count for nightmare + //if (skill != 3) + self.attack_finished = time + normal; +}; + +float (entity targ) visible; + +void (void() thinkst) SUB_CheckRefire = +{ + if (skill != 3) + return; + if (self.cnt == 1) + return; + if (!visible (self.enemy)) + return; + self.cnt = 1; + self.think = thinkst; +}; diff --git a/quakec_hipnotic/triggers.qc b/quakec_hipnotic/triggers.qc new file mode 100644 index 0000000..7881011 --- /dev/null +++ b/quakec_hipnotic/triggers.qc @@ -0,0 +1,734 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +entity stemp, otemp, s, old; + + +void() trigger_reactivate = +{ + self.solid = SOLID_TRIGGER; +}; + +//============================================================================= + +float SPAWNFLAG_NOMESSAGE = 1; +float SPAWNFLAG_NOTOUCH = 1; + +// the wait time has passed, so set back up for another activation +void() multi_wait = +{ + if (self.max_health) + { + self.health = self.max_health; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + } +}; + + +// the trigger was just touched/killed/used +// self.enemy 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 = +{ + if (self.nextthink > time) + { + return; // allready been triggered + } + + if (self.classname == "trigger_secret") + { + if (self.enemy.classname != "player") + return; + found_secrets = found_secrets + 1; + WriteByte (MSG_ALL, SVC_FOUNDSECRET); + + msg_entity = self.enemy; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_FIND_SECRET"); + } + + if (self.enemy.classname == "player" && world.model == "maps/hip3m1.bsp" && self.message == "$map_not_supposed_be_here") + { + msg_entity = self.enemy; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_FIND_SUPPOSED_BE_HERE"); + } + + if (self.noise) + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + +// don't trigger again until reset + self.takedamage = DAMAGE_NO; + + activator = self.enemy; + + SUB_UseTargets(); + + if (self.wait > 0) + { + self.think = multi_wait; + self.nextthink = time + self.wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called wheil C code is looping through area links... + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + } +//MED 12/01/96 added cnt stuff + if (self.cnt > 0) + { + self.cnt = self.cnt - 1; + if (self.cnt == 0) + { + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + } + } +}; + +void() multi_killed = +{ + self.enemy = damage_attacker; + multi_trigger(); +}; + +void() multi_use = +{ + self.enemy = activator; + multi_trigger(); +}; + +void() multi_touch = +{ + if (other.classname != "player") + return; + +// if the trigger has an angles field, check player's facing direction + if (self.movedir != '0 0 0') + { + makevectors (other.angles); + if (v_forward * self.movedir < 0) + return; // not facing the right way + } + + self.enemy = other; + multi_trigger (); +}; + +//MED 12/01/96 added count field +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +"cnt" how many times it can be triggered (infinite default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_multiple = +{ + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + else if (self.sounds == 3) + { + precache_sound ("misc/trigger1.wav"); + self.noise = "misc/trigger1.wav"; + } + + if (!self.wait) + self.wait = 0.2; + self.use = multi_use; + + InitTrigger (); + + if (self.health) + { + if (self.spawnflags & SPAWNFLAG_NOTOUCH) + objerror ("health and notouch don't make sense\n"); + self.max_health = self.health; + self.th_die = multi_killed; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + setorigin (self, self.origin); // make sure it links into the world + } + else + { + if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) ) + { + self.touch = multi_touch; + } + } +//MED 12/01/96 added cnt stuff + if (self.cnt == 0) + self.cnt = -1; +}; + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. 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 "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_once = +{ + self.wait = -1; + trigger_multiple(); +}; + +//============================================================================= + +/*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. It can contain killtargets, targets, delays, and messages. +*/ +void() trigger_relay = +{ + self.use = SUB_UseTargets; +}; + + +//============================================================================= + +/*QUAKED trigger_secret (.5 .5 .5) ? +secret counter trigger +sounds +1) secret +2) beep beep +3) +4) +set "message" to text string +*/ +void() trigger_secret = +{ + total_secrets = total_secrets + 1; + self.wait = -1; + if (!self.message) + self.message = "$qc_found_secret"; + if (!self.sounds) + self.sounds = 1; + + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + + trigger_multiple (); +}; + +//============================================================================= + + +void() counter_use = +{ + local string junk; + + self.count = self.count - 1; + if (self.count < 0) + return; + + if (self.count != 0) + { + if (activator.classname == "player" + && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + { + if (self.count >= 4) + centerprint (activator, "$qc_more_go"); + else if (self.count == 3) + centerprint (activator, "$qc_three_more"); + else if (self.count == 2) + centerprint (activator, "$qc_two_more"); + else + centerprint (activator, "$qc_one_more"); + } + return; + } + + if (activator.classname == "player" + && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + centerprint(activator, "$qc_sequence_completed"); + self.enemy = activator; + multi_trigger (); +}; + +/*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 = +{ + self.wait = -1; + if (!self.count) + self.count = 2; + + self.use = counter_use; +}; + + +/* +============================================================================== + +TELEPORT TRIGGERS + +============================================================================== +*/ + +float PLAYER_ONLY = 1; +float SILENT = 2; + +void() play_teleport = +{ + local float v; + local string tmpstr; + + v = random() * 5; + if (v < 1) + tmpstr = "misc/r_tele1.wav"; + else if (v < 2) + tmpstr = "misc/r_tele2.wav"; + else if (v < 3) + tmpstr = "misc/r_tele3.wav"; + else if (v < 4) + tmpstr = "misc/r_tele4.wav"; + else + tmpstr = "misc/r_tele5.wav"; + + sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM); + remove (self); +}; + +void(vector org) spawn_tfog = +{ + s = spawn (); + s.origin = org; + s.nextthink = time + 0.2; + s.think = play_teleport; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); +}; + + +void() tdeath_touch = +{ + if (other == self.owner) + return; + +// frag anyone who teleports in on top of an invincible player + if (other.classname == "player") + { + if (other.invincible_finished > time) + self.classname = "teledeath2"; + if (self.owner.classname != "player") + { // other monsters explode themselves + T_Damage (self.owner, self, self, 50000); + return; + } + + } + + if (other.health) + { + T_Damage (other, self, self, 50000); + } +}; + + +void(vector org, entity death_owner) spawn_tdeath = +{ +local entity death; + + death = spawn(); + death.classname = "teledeath"; + death.movetype = MOVETYPE_NONE; + death.solid = SOLID_TRIGGER; + death.angles = '0 0 0'; + setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1'); + setorigin (death, org); + death.touch = tdeath_touch; + death.nextthink = time + 0.2; + death.think = SUB_Remove; + death.owner = death_owner; + + force_retouch = 2; // make sure even still objects get hit +}; + +void() teleport_touch = +{ +local entity t; +local vector org; + + if (self.targetname) + { + if (self.nextthink < time) + { + return; // not fired yet + } + } + + if (self.spawnflags & PLAYER_ONLY) + { + if (other.classname != "player") + return; + } + +// only teleport living creatures + if (other.health <= 0 || other.solid != SOLID_SLIDEBOX) + return; + + SUB_UseTargets (); + +// put a tfog where the player was + spawn_tfog (other.origin); + + t = find (world, targetname, self.target); + if (!t) + objerror ("couldn't find target"); + +// spawn a tfog flash in front of the destination + makevectors (t.mangle); + org = t.origin + 32 * v_forward; + + spawn_tfog (org); + spawn_tdeath(t.origin, other); + +// move the player and lock him down for a little while + if (!other.health) + { + other.origin = t.origin; + other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y); + return; + } + + setorigin (other, t.origin); + other.angles = t.mangle; + if (other.classname == "player") + { + other.fixangle = 1; // turn this way immediately + other.teleport_time = time + 0.7; + if (other.flags & FL_ONGROUND) + other.flags = other.flags - FL_ONGROUND; + other.velocity = v_forward * 300; + } + other.flags = other.flags - other.flags & FL_ONGROUND; +}; + +/*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32) +This is the destination marker for a teleporter. It should have a "targetname" field with the same value as a teleporter's "target" field. +*/ +void() info_teleport_destination = +{ +// this does nothing, just serves as a target spot + self.mangle = self.angles; + self.angles = '0 0 0'; + self.model = ""; + self.origin = self.origin + '0 0 27'; + if (!self.targetname) + objerror ("no targetname"); +}; + +void() teleport_use = +{ + self.nextthink = time + 0.2; + force_retouch = 2; // make sure even still objects get hit + self.think = SUB_Null; +}; + +/*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT +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. +*/ +void() trigger_teleport = +{ + local vector o; + + InitTrigger (); + self.touch = teleport_touch; + // find the destination + if (!self.target) + objerror ("no target"); + self.use = teleport_use; + + if (!(self.spawnflags & SILENT)) + { + precache_sound ("ambience/hum1.wav"); + o = (self.mins + self.maxs)*0.5; + ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC); + } +}; + +/* +============================================================================== + +trigger_setskill + +============================================================================== +*/ + +void() trigger_skill_touch = +{ + if (other.classname != "player") + return; + + cvar_set ("skill", self.message); +}; + +/*QUAKED trigger_setskill (.5 .5 .5) ? +sets skill level to the value of "message". +Only used on start map. +*/ +void() trigger_setskill = +{ + InitTrigger (); + self.touch = trigger_skill_touch; +}; + + +/* +============================================================================== + +ONLY REGISTERED TRIGGERS + +============================================================================== +*/ + +void() trigger_onlyregistered_touch = +{ + if (other.classname != "player") + return; + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + if (cvar("registered")) + { + self.message = ""; + SUB_UseTargets (); + remove (self); + } + else + { + if (self.message != "") + { + centerprint (other, self.message); + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } + } +}; + +/*QUAKED trigger_onlyregistered (.5 .5 .5) ? +Only fires if playing the registered version, otherwise prints the message +*/ +void() trigger_onlyregistered = +{ + precache_sound ("misc/talk.wav"); + InitTrigger (); + self.touch = trigger_onlyregistered_touch; +}; + +//============================================================================ + +//JIM +void( entity ent, float amount ) hurt_setdamage = + { + ent.dmg = amount; + if ( !amount ) + { + ent.solid = SOLID_NOT; + } + else + { + ent.solid = SOLID_TRIGGER; + } + ent.nextthink = -1; + }; + +void() hurt_on = +{ + self.solid = SOLID_TRIGGER; + self.nextthink = -1; +}; + +void() hurt_touch = +{ + if (other.takedamage) + { + self.solid = SOLID_NOT; + T_Damage (other, self, self, self.dmg); + self.think = hurt_on; + self.nextthink = time + 1; +//MED 12/01/96 added count stuff + if (self.cnt > 0) + { + self.cnt = self.cnt - 1; + if (self.cnt == 0) + { + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + } + } + } + + return; +}; + +//MED 12/01/96 added count field +/*QUAKED trigger_hurt (.5 .5 .5) ? +Any object touching this will be hurt +set dmg to damage amount +defalt dmg = 5 +"cnt" default infinite, how many times to trigger +*/ +void() trigger_hurt = +{ + InitTrigger (); + self.touch = hurt_touch; + if (!self.dmg) + self.dmg = 5; +//MED 12/01/96 added count stuff + if (self.cnt == 0) + self.cnt = -1; +}; + +//============================================================================ + +float PUSH_ONCE = 1; + +void() trigger_push_touch = +{ + if (other.classname == "grenade") + other.velocity = self.speed * self.movedir * 10; + else if (other.health > 0) + { + other.velocity = self.speed * self.movedir * 10; + if (other.classname == "player") + { + if (other.fly_sound < time) + { + other.fly_sound = time + 1.5; + sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); + } + } + } + if (self.spawnflags & PUSH_ONCE) + remove(self); +}; + + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE +Pushes the player +*/ +void() trigger_push = +{ + InitTrigger (); + precache_sound ("ambience/windfly.wav"); + self.touch = trigger_push_touch; + if (!self.speed) + self.speed = 1000; +}; + +//============================================================================ + +void() trigger_monsterjump_touch = +{ + if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER ) + return; + +// set XY even if not on ground, so the jump will clear lips + other.velocity_x = self.movedir_x * self.speed; + other.velocity_y = self.movedir_y * self.speed; + + if ( !(other.flags & FL_ONGROUND) ) + return; + + other.flags = other.flags - FL_ONGROUND; + + other.velocity_z = self.height; +//MED 12/01/96 added count stuff + if (self.cnt>0) + { + self.cnt = self.cnt - 1; + if (self.cnt == 0) + { + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + } + } +}; + +//MED 12/01/96 added count field +/*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 +"cnt" default infinite, how many times to trigger +*/ +void() trigger_monsterjump = +{ + if (!self.speed) + self.speed = 200; + if (!self.height) + self.height = 200; + if (self.angles == '0 0 0') + self.angles = '0 360 0'; +//MED 12/01/96 added count stuff + if (self.cnt == 0) + self.cnt = -1; + InitTrigger (); + self.touch = trigger_monsterjump_touch; +}; diff --git a/quakec_hipnotic/weapons.qc b/quakec_hipnotic/weapons.qc new file mode 100644 index 0000000..66e77bf --- /dev/null +++ b/quakec_hipnotic/weapons.qc @@ -0,0 +1,2331 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +*/ +//JIM +void( vector pos ) placebullethole; +void (entity targ, entity inflictor, entity attacker, float damage) T_Damage; +void () player_run; +void(entity bomb, entity attacker, float rad, entity ignore) T_RadiusDamage; +void(vector org, vector vel, float damage) SpawnBlood; +void() SuperDamageSound; + +//MED 10/18/96 +void(float damage) spawn_touchblood; +/* +================ +HIPNOTIC WEAPONS +================ +*/ +void() HIP_LaserTouch = +{ + local vector org; + local vector spot1,spot2; + local vector oldvel; + local float mag; + local float r; + + self.owner = world; + self.cnt = self.cnt + 1; + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + oldvel = normalize(self.old_velocity); + spot1 = self.origin - (16*oldvel); + spot2 = self.origin + (16*oldvel); + traceline (spot1, spot2, FALSE, self); // see through other monsters + self.origin = trace_endpos; + + org = self.origin; + + if (other.health) + { + if (self.lastvictim == other) + { + self.dmg = self.dmg / 2; + } + spawn_touchblood (self.dmg); + T_Damage (other, self, self.lastvictim, self.dmg); + } + else if ((self.cnt == 3) || (random()<0.15)) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } + else + { +// self.dmg = 0.66 * self.dmg; + self.dmg = 0.9 * self.dmg; +// self.speed = 0.95 * self.speed; + self.velocity = oldvel+(2*trace_plane_normal); + self.velocity = normalize(self.velocity); + self.velocity = self.speed * self.velocity; + self.old_velocity = self.velocity; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; + r = random(); + sound (self, CHAN_WEAPON, "hipweap/laserric.wav", 1, ATTN_STATIC); +/* + if (r<0.33) + sound (self, CHAN_WEAPON, "weapons/ric1.wav", 1, ATTN_STATIC); + else if (r<0.66) + sound (self, CHAN_WEAPON, "weapons/ric2.wav", 1, ATTN_STATIC); + else + sound (self, CHAN_WEAPON, "weapons/ric3.wav", 1, ATTN_STATIC); +*/ + return; + } + sound (self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); + remove(self); + +}; +void() HIP_LaserThink = + { + local float delta; + + + if (time>self.attack_finished) + { + remove(self); + return; + } + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; + self.velocity = self.old_velocity; + self.angles = vectoangles(self.velocity); + self.nextthink = time+0.1; + }; + +void(vector org, vector vec, float light) HIP_LaunchLaser = +{ +// sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM); + sound (self ,CHAN_WEAPON, "hipweap/laserg.wav", 1, ATTN_NORM); + + vec = normalize(vec); + + newmis = spawn(); + newmis.owner = self; + newmis.classname = "hiplaser"; + newmis.lastvictim = self; + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + if (light) + newmis.effects = EF_DIMLIGHT; + + setmodel (newmis, "progs/lasrspik.mdl"); + setsize (newmis, '0 0 0', '0 0 0'); + + setorigin (newmis, org); + + newmis.speed = 1000; + newmis.dmg = 18; + newmis.velocity = vec * newmis.speed; + newmis.old_velocity = newmis.velocity; + newmis.angles = vectoangles(newmis.velocity); + newmis.avelocity = '0 0 400'; + + newmis.nextthink = time; + newmis.attack_finished = time + 5; + newmis.think = HIP_LaserThink; + newmis.touch = HIP_LaserTouch; + newmis.count = 0; + +}; + +/* +================= +HIP_FireLaser +================= +*/ +void(float stat) HIP_FireLaser = + { + local vector org; + local vector dir; + local vector out; + local float ofs; + local float aofs; + + if (!self.button0) + { + player_run (); + return; + } + if (self.ammo_cells < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + SuperDamageSound(); + self.effects = self.effects | EF_MUZZLEFLASH; + makevectors (self.v_angle); + + ofs = 6; + out = v_forward; + out_z = 0; + out = normalize(out); + org = self.origin + ((12-ofs) * v_up) + (12*out); +// org = self.origin + (1*v_forward); + dir = aim (self, 1000); + aofs = ofs * 0.707; + if (stat == 0) + { + self.currentammo = self.ammo_cells = self.ammo_cells - 1; + org = org + (aofs*v_right); + org = org - (aofs*v_up); + HIP_LaunchLaser(org, dir, 0); + org = org - (2*aofs*v_right); + HIP_LaunchLaser(org, dir, 0); + } + else if (stat == 1) + { + self.currentammo = self.ammo_cells = self.ammo_cells - 1; + org = org + (ofs*v_up); + if (random()<0.1) + { + HIP_LaunchLaser(org, dir, 1); + newmis.dmg = 25; + } + else + HIP_LaunchLaser(org, dir, 0); + } + self.punchangle_x = -1; + }; + +/* +================= +HIP_HammerDamage +================= +*/ +void(vector p1, vector p2, entity from, float damage) HIP_HammerDamage = +{ + local entity e1, e2; + local vector f; + + f = p2 - p1; + normalize (f); + f_x = 0 - f_y; + f_y = f_x; + f_z = 0; + f = f*16; + + e1 = e2 = world; + + traceline (p1, p2, FALSE, self); +//JIM + if (trace_ent.takedamage && !trace_ent.wetsuit_finished ) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + if (self.classname == "player") + { + if (other.classname == "player") + trace_ent.velocity_z = trace_ent.velocity_z + 400; + } + } + e1 = trace_ent; + + traceline (p1 + f, p2 + f, FALSE, self); +//JIM + if (trace_ent != e1 && trace_ent.takedamage && + !trace_ent.wetsuit_finished ) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } + e2 = trace_ent; + + traceline (p1 - f, p2 - f, FALSE, self); +//JIM + if (trace_ent != e1 && trace_ent != e2 && trace_ent.takedamage && + !trace_ent.wetsuit_finished ) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } +}; + +/* +void() HIP_DiversionLightningThink = + { + local vector org,end; + + if (time > self.delay) + { + remove(self); + return; + } + + makevectors(self.v_angle); + org = self.origin; + end = org + v_forward*200; +// end = end + (((200*random()) - 100) * v_up); + end = end + (((400*random()) - 200) * v_right); + + traceline (org, end, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + self.nextthink = time + 0.1; + }; +*/ + +//void(entity prev, entity owner, float dst) HIP_SpawnMjolnirLightning; +void() HIP_LightningThink = + { + local vector org; + local vector dst; + local entity head; + local entity selected; + local float cur_dist; + local float head_dist; + local vector vec; + local float dot; + local float oldstate; + local float dam; + + + if (time > self.delay) + { + if (self.enemy != world) + self.enemy.struck_by_mjolnir = 0; + remove(self); + return; + } + oldstate = self.state; + if (self.state==0) + { + // look in our immediate vicinity + self.enemy = world; + selected = world; + cur_dist = self.distance; + head = findradius(self.owner.origin, self.distance); + while(head) + { + if(!(head.flags & FL_NOTARGET) && ((head.flags & FL_MONSTER) || (head.flags & FL_CLIENT))) + { +// if (visible(head) && (head!=self.owner.owner) && (head.health>0)) + if ((visible(head)) && (head!=self.owner.owner) && (head.health>0)) + { + head_dist = vlen(head.origin - self.lastvictim.origin); + if ((head_dist0.3) + HIP_HammerDamage (org, trace_endpos, self, dam); + else + HIP_HammerDamage (org, trace_endpos, self, dam*0.5); + self = head; + + self.nextthink = time + 0.2; + }; + +void(entity prev, entity own, float dst) HIP_SpawnMjolnirLightning = + { + local entity light; + // spawn actual lightning + light = spawn(); + light.delay = time + 0.8; + light.state = 0; + light.lastvictim = prev; + light.distance = dst; + light.owner = own; + light.v_angle = self.angles; + light.v_angle_x = 0; + light.v_angle_z = 0; + light.origin = own.origin; + light.think = HIP_LightningThink; + light.nextthink = time; + }; + +void() HIP_SpawnMjolnirBase = + { + local entity light; + // spawn lightning base + light = spawn(); +// light.origin = self.origin - '0 0 24' + (32*v_forward); + light.origin = trace_endpos; + light.flags = 0; + light.owner = self; + light.struck_by_mjolnir = 1; + light.think = SUB_Remove; + light.nextthink = time + 1; + sound (light, CHAN_AUTO, "hipweap/mjolslap.wav", 1, ATTN_NORM); + sound (light, CHAN_WEAPON, "hipweap/mjolhit.wav", 1, ATTN_NORM); + makevectors(self.v_angle); + light.movedir = v_forward; + HIP_SpawnMjolnirLightning( light, light, 350 ); + HIP_SpawnMjolnirLightning( light, light, 350 ); + HIP_SpawnMjolnirLightning( light, light, 350 ); + HIP_SpawnMjolnirLightning( light, light, 350 ); + }; + +void() HIP_FireMjolnirLightning = + { + local vector org; + local float cells; + +// explode if under water + if (self.waterlevel > 1) + { + cells = self.ammo_cells; + self.ammo_cells = 0; + discharged = 1; + T_RadiusDamage (self, self, 35*cells, world); + discharged = 0; + W_SetCurrentAmmo (); + return; + } + +// if (self.t_width < time) +// { +// sound (self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); +// self.t_width = time + 0.6; +// } +// self.punchangle_x = 12; + + self.currentammo = self.ammo_cells = self.ammo_cells - 15; + + HIP_SpawnMjolnirBase(); + }; + +void() HIP_FireMjolnir = + { + local vector org; + local float cells; + local float damage; + local vector source; + local vector ang; + + + self.attack_finished = time + 0.4; + source = self.origin + '0 0 16'; +// ang = self.angles; +// ang_x = 0; +// ang_y = 0; + makevectors(self.v_angle); + traceline (source, source + v_forward*32, FALSE, self); + if (trace_fraction == 1.0 && (self.ammo_cells >= 15)) + { + source = source + v_forward*32; + traceline (source , source - v_up*50, FALSE, self); +// dprint("fraction = "); +// dprint(ftos(trace_fraction)); +// dprint("\n"); + if (trace_fraction > 0.3 && trace_fraction< 1.0) + { + HIP_FireMjolnirLightning(); + self.attack_finished = time + 1.5; + return; + } + } + org = trace_endpos - v_forward*4; + + if (trace_ent.takedamage) + { + damage = 50; + if (trace_ent.classname == "monster_zombie") + damage = 70; + trace_ent.axhitme = 1; + SpawnBlood (org, v_forward, damage); + T_Damage (trace_ent, self, self, damage); + } + else + { // hit wall + if (trace_fraction != 1.0) + { +// sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + sound (self, CHAN_WEAPON, "hipweap/mjoltink.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } + else + { + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); + } + } + self.attack_finished = time + 0.4; + + }; + +// called by worldspawn +void() W_Precache = +{ + precache_sound ("weapons/r_exp3.wav"); // new rocket explosion + precache_sound ("weapons/rocket1i.wav"); // spike gun + precache_sound ("weapons/sgun1.wav"); + precache_sound ("weapons/guncock.wav"); // player shotgun + precache_sound ("weapons/ric1.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric2.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric3.wav"); // ricochet (used in c code) + precache_sound ("weapons/spike2.wav"); // super spikes + precache_sound ("weapons/tink1.wav"); // spikes tink (used in c code) + precache_sound ("weapons/grenade.wav"); // grenade launcher + precache_sound ("weapons/bounce.wav"); // grenade bounce + precache_sound ("weapons/shotgn2.wav"); // super shotgun +//MED + precache_sound ("enforcer/enfstop.wav"); // laser cannon +//MED 11/06/96 + precache_sound ("knight/sword1.wav"); // laser cannon + precache_sound ("hipweap/laserg.wav"); // laser cannon + precache_sound ("hipweap/laserric.wav"); // laser cannon ricochet + precache_sound ("hipweap/proxwarn.wav"); // proximity bomb + precache_sound ("hipweap/proxbomb.wav"); // proximity bomb +//MED 11/19/96 + precache_sound ("hipweap/mjolhit.wav"); // mjolnir + precache_sound ("hipweap/mjolslap.wav"); // mjolnir + precache_sound ("hipweap/mjoltink.wav"); // mjolnir +}; + +float() crandom = +{ + return 2*(random() - 0.5); +}; + +/* +================ +W_FireAxe +================ +*/ +void() W_FireAxe = +{ + local vector source; + local vector org; + + makevectors (self.v_angle); + source = self.origin + '0 0 16'; + traceline (source, source + v_forward*64, FALSE, self); + if (trace_fraction == 1.0) + return; + + org = trace_endpos - v_forward*4; + + if (trace_ent.takedamage) + { + trace_ent.axhitme = 1; + SpawnBlood (org, '0 0 0', 20); + T_Damage (trace_ent, self, self, 20); + } + else + { // hit wall + sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + + +//============================================================================ + + +vector() wall_velocity = +{ + local vector vel; + + vel = normalize (self.velocity); + vel = normalize(vel + v_up*(random()- 0.5) + v_right*(random()- 0.5)); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + return vel; +}; + + +/* +================ +SpawnMeatSpray +================ +*/ +void(vector org, vector vel) SpawnMeatSpray = +{ + local entity missile, mpuff; + local vector org; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_NOT; + + makevectors (self.angles); + + missile.velocity = vel; + missile.velocity_z = missile.velocity_z + 250 + 50*random(); + + missile.avelocity = '3000 1000 2000'; + +// set missile duration + missile.nextthink = time + 1; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + +/* +================ +SpawnBlood +================ +*/ +void(vector org, vector vel, float damage) SpawnBlood = +{ + particle (org, vel*0.1, 73, damage*2); +}; + +/* +================ +spawn_touchblood +================ +*/ +void(float damage) spawn_touchblood = +{ + local vector vel; + + vel = wall_velocity () * 0.2; + SpawnBlood (self.origin + vel*0.01, vel, damage); +}; + + +/* +================ +SpawnChunk +================ +*/ +void(vector org, vector vel) SpawnChunk = +{ + particle (org, vel*0.02, 0, 10); +}; + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +entity multi_ent; +float multi_damage; + +void() ClearMultiDamage = +{ + multi_ent = world; + multi_damage = 0; +}; + +void() ApplyMultiDamage = +{ + if (!multi_ent) + return; + T_Damage (multi_ent, self, self, multi_damage); +}; + +void(entity hit, float damage) AddMultiDamage = +{ + if (!hit) + return; + + if (hit != multi_ent) + { + ApplyMultiDamage (); + multi_damage = damage; + multi_ent = hit; + } + else + multi_damage = multi_damage + damage; +}; + +/* +============================================================================== + +BULLETS + +============================================================================== +*/ + +/* +================ +TraceAttack +================ +*/ +void(float damage, vector dir) TraceAttack = +{ + local vector vel, org; + + vel = normalize(dir + v_up*crandom() + v_right*crandom()); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + org = trace_endpos - dir*4; + + if (trace_ent.takedamage) + { + SpawnBlood (org, vel*0.2, damage); + AddMultiDamage (trace_ent, damage); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + +/* +================ +FireBullets + +Used by shotgun, super shotgun, and enemy soldier firing +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void(float shotcount, vector dir, vector spread) FireBullets = +{ + local vector direction; + local vector src; + //JIM + local float bullet; + bullet = 0; + + makevectors(self.v_angle); + + src = self.origin + v_forward*10; + src_z = self.absmin_z + self.size_z * 0.7; + + ClearMultiDamage (); + while (shotcount > 0) + { + direction = dir + crandom()*spread_x*v_right + crandom()*spread_y*v_up; + + traceline (src, src + direction*2048, FALSE, self); + if (trace_fraction != 1.0) + //JIM + { + TraceAttack (4, direction); + if ( ( !bullet ) && ( trace_ent == world ) ) + { + placebullethole( trace_endpos ); + bullet = 1; + } + } + + shotcount = shotcount - 1; + } + ApplyMultiDamage (); +}; + +/* +================ +W_FireShotgun +================ +*/ +void() W_FireShotgun = +{ + local vector dir; + + sound (self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + self.currentammo = self.ammo_shells = self.ammo_shells - 1; + dir = aim (self, 100000); + FireBullets (6, dir, '0.04 0.04 0'); +}; + + +/* +================ +W_FireSuperShotgun +================ +*/ +void() W_FireSuperShotgun = +{ + local vector dir; + + if (self.currentammo == 1) + { + W_FireShotgun (); + return; + } + + sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM); + + self.punchangle_x = -4; + + self.currentammo = self.ammo_shells = self.ammo_shells - 2; + dir = aim (self, 100000); + FireBullets (14, dir, '0.14 0.08 0'); +}; + + +/* +============================================================================== + +ROCKETS + +============================================================================== +*/ + +void() s_explode1 = [0, s_explode2] {}; +void() s_explode2 = [1, s_explode3] {}; +void() s_explode3 = [2, s_explode4] {}; +void() s_explode4 = [3, s_explode5] {}; +void() s_explode5 = [4, s_explode6] {}; +void() s_explode6 = [5, SUB_Remove] {}; + +void() BecomeExplosion = +{ + self.movetype = MOVETYPE_NONE; + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +void() T_MissileTouch = +{ + local float damg; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + damg = 100 + random()*20; + + if (other.health) + { + if (other.classname == "monster_shambler") + damg = damg * 0.5; // mostly immune + T_Damage (other, self, self.owner, damg ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact + T_RadiusDamage (self, self.owner, 120, other); + +// sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + + + +/* +================ +W_FireRocket +================ +*/ +void() W_FireRocket = +{ + local entity missile, mpuff; + + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.solid = SOLID_BBOX; + missile.classname = "missile"; + +// set missile speed + + makevectors (self.v_angle); + missile.velocity = aim(self, 1000); + missile.velocity = missile.velocity * 1000; + missile.angles = vectoangles(missile.velocity); + + missile.touch = T_MissileTouch; + +// set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/missile.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + v_forward*8 + '0 0 16'); +//MED 11/09/96 + newmis = missile; +}; + +/* +=============================================================================== + +LIGHTNING + +=============================================================================== +*/ + +/* +================= +LightningDamage +================= +*/ +void(vector p1, vector p2, entity from, float damage) LightningDamage = +{ + local entity e1, e2; + local vector f; + + f = p2 - p1; + normalize (f); + f_x = 0 - f_y; + f_y = f_x; + f_z = 0; + f = f*16; + + e1 = e2 = world; + + traceline (p1, p2, FALSE, self); +//JIM + if (trace_ent.takedamage && !trace_ent.wetsuit_finished ) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + if (self.classname == "player") + { + if (other.classname == "player") + trace_ent.velocity_z = trace_ent.velocity_z + 400; + } + } + e1 = trace_ent; + + traceline (p1 + f, p2 + f, FALSE, self); +//JIM + if (trace_ent != e1 && trace_ent.takedamage && + !trace_ent.wetsuit_finished ) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } + e2 = trace_ent; + + traceline (p1 - f, p2 - f, FALSE, self); +//JIM + if (trace_ent != e1 && trace_ent != e2 && trace_ent.takedamage && + !trace_ent.wetsuit_finished ) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } +}; + + +void() W_FireLightning = +{ + local vector org; + local float cells; + + if (self.ammo_cells < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + +// explode if under water + if (self.waterlevel > 1) + { + cells = self.ammo_cells; + self.ammo_cells = 0; + //MED 01/05/97 added discharge flag + discharged = 1; + T_RadiusDamage (self, self, 35*cells, world); + discharged = 0; + W_SetCurrentAmmo (); + return; + } + + if (self.t_width < time) + { + sound (self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + self.t_width = time + 0.6; + } + self.punchangle_x = -2; + + self.currentammo = self.ammo_cells = self.ammo_cells - 1; + + org = self.origin + '0 0 16'; + + traceline (org, org + v_forward*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + LightningDamage (self.origin, trace_endpos + v_forward*4, self, 30); +}; + + +//============================================================================= + + +void() GrenadeExplode = +{ + T_RadiusDamage (self, self.owner, 120, world); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +void() GrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage == DAMAGE_AIM) + { + GrenadeExplode(); + return; + } + sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +/* +================ +W_FireGrenade +================ +*/ +void() W_FireGrenade = +{ + local entity missile, mpuff; + + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + missile.classname = "grenade"; + +// set missile speed + + makevectors (self.v_angle); + + if (self.v_angle_x) + missile.velocity = v_forward*600 + v_up * 200 + crandom()*v_right*10 + crandom()*v_up*10; + else + { + missile.velocity = aim(self, 10000); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + } + + missile.avelocity = '300 300 300'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = GrenadeTouch; + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = GrenadeExplode; + + setmodel (missile, "progs/grenade.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); +}; + +void() ProximityExplode = +{ + T_RadiusDamage (self, self.owner, 95, world); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +//MED 10/31/96 +float NumProximityGrenades; +/* +================ +ProximityGrenadeExplode +================ +*/ +void() ProximityGrenadeExplode = + { + self.takedamage = DAMAGE_NO; + NumProximityGrenades = NumProximityGrenades - 1; + self.deathtype = "exploding"; + self.nextthink = time + 0.1; + self.owner = self.lastvictim; + self.think = ProximityExplode; + }; + +/* +================ +ProximityGrenadeTouch +================ +*/ +void() ProximityGrenadeTouch = + { + if (other == self) + return; + if (other.classname == self.classname) + return; + self.movetype = MOVETYPE_TOSS; + if (self.state == 1) + return; + if (vlen(other.velocity) > 0) + { + ProximityGrenadeExplode(); + self.think(); + return; + } + if (other.takedamage == DAMAGE_AIM) + { + ProximityGrenadeExplode(); + self.think(); + return; + } + sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + self.movetype = MOVETYPE_NONE; + setsize (self, '-8 -8 -8', '8 8 8'); + self.state = 1; + self.spawnmaster = other; + }; + +/* +================ +ProximityBomb +================ +*/ +void() ProximityBomb = + { + local entity head; + local float blowup; + + if ((time > self.delay) || (NumProximityGrenades > 15) || (vlen(self.spawnmaster.velocity)>0) ) + { + ProximityGrenadeExplode(); + self.think(); + return; + } + self.owner = world; + self.takedamage = DAMAGE_YES; + head = findradius(self.origin, 140); + blowup = 0; + + while (head) + { + if ((head != self) && (head.health > 0) && (head.flags & (FL_CLIENT|FL_MONSTER)) && (head.classname!=self.classname)) + blowup = 1; + if ((head.classname == self.classname) && (head.state==0)) + blowup = 1; + traceline(self.origin,head.origin,TRUE,self); + if (trace_fraction != 1.0) + blowup = 0; + if (blowup==1) + { + sound (self, CHAN_WEAPON, "hipweap/proxwarn.wav", 1, ATTN_NORM); + ProximityGrenadeExplode(); + self.nextthink = time + 0.5; + return; + } + head = head.chain; + } + self.nextthink = time + 0.25; + }; + +/* +================ +W_FireProximityGrenade +================ +*/ +void() W_FireProximityGrenade = + { + local entity missile, mpuff; + + NumProximityGrenades = NumProximityGrenades + 1; + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "hipweap/proxbomb.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.lastvictim = self; + missile.movetype = MOVETYPE_TOSS; + missile.solid = SOLID_BBOX; + missile.classname = "proximity_grenade"; + missile.takedamage = DAMAGE_NO; + missile.health = 5; + missile.state = 0; + +// set missile speed + + makevectors (self.v_angle); + + if (self.v_angle_x) + missile.velocity = v_forward*600 + v_up * 200 + crandom()*v_right*10 + crandom()*v_up*10; + else + { + missile.velocity = aim(self, 10000); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + } + + missile.avelocity = '100 600 100'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = ProximityGrenadeTouch; + +// set missile duration + missile.nextthink = time + 2; + missile.delay = time + 15 + (10*random()); + missile.think = ProximityBomb; + missile.th_die = ProximityGrenadeExplode; + + setmodel (missile, "progs/proxbomb.mdl"); + setorigin (missile, self.origin); + setsize (missile, '-1 -1 -1', '1 1 1'); + }; + +//============================================================================= + +void() spike_touch; +void() superspike_touch; + + +/* +=============== +launch_spike + +Used for both the player and the ogre +=============== +*/ +void(vector org, vector dir) launch_spike = +{ + newmis = spawn (); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + + newmis.angles = vectoangles(dir); + + newmis.touch = spike_touch; + newmis.classname = "spike"; + newmis.think = SUB_Remove; + newmis.nextthink = time + 6; + setmodel (newmis, "progs/spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + setorigin (newmis, org); + + newmis.velocity = dir * 1000; +}; + +void() W_FireSuperSpikes = +{ + local vector dir; + local entity old; + + sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_nails = self.ammo_nails - 2; + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16', dir); + newmis.touch = superspike_touch; + setmodel (newmis, "progs/s_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + self.punchangle_x = -2; +}; + +void(float ox) W_FireSpikes = +{ + local vector dir; + local entity old; + + makevectors (self.v_angle); + + if (self.ammo_nails >= 2 && self.weapon == IT_SUPER_NAILGUN) + { + W_FireSuperSpikes (); + return; + } + + if (self.ammo_nails < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_nails = self.ammo_nails - 1; + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16' + v_right*ox, dir); + + self.punchangle_x = -2; +}; + + + +.float hit_z; +void() spike_touch = +{ +local float rand; + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (9); + T_Damage (other, self, self.owner, 9); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + + if (self.classname == "wizspike") + WriteByte (MSG_BROADCAST, TE_WIZSPIKE); + else if (self.classname == "knightspike") + WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE); + else + WriteByte (MSG_BROADCAST, TE_SPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + +void() superspike_touch = +{ +local float rand; + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (18); + T_Damage (other, self, self.owner, 18); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_SUPERSPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + + +/* +=============================================================================== + +PLAYER WEAPON USE + +=============================================================================== +*/ + +//MED 10/18/96 added HIPWEAPONS +void() W_SetCurrentAmmo = +{ + player_run (); // get out of any weapon firing states + + self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS) ); + + if (self.weapon == IT_AXE) + { + self.currentammo = 0; + self.weaponmodel = "progs/v_axe.mdl"; + self.weaponframe = 0; + } + else if (self.weapon == IT_SHOTGUN) + { + self.currentammo = self.ammo_shells; + self.weaponmodel = "progs/v_shot.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.currentammo = self.ammo_shells; + self.weaponmodel = "progs/v_shot2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_NAILGUN) + { + self.currentammo = self.ammo_nails; + self.weaponmodel = "progs/v_nail.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_NAILS; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.currentammo = self.ammo_nails; + self.weaponmodel = "progs/v_nail2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_NAILS; + } +//MED + else if (self.weapon == IT_LASER_CANNON) + { + self.currentammo = self.ammo_cells; + self.weaponmodel = "progs/v_laserg.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_CELLS; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.currentammo = self.ammo_rockets; + self.weaponmodel = "progs/v_rock.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.currentammo = self.ammo_rockets; + self.weaponmodel = "progs/v_rock2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_LIGHTNING) + { + self.currentammo = self.ammo_cells; + self.weaponmodel = "progs/v_light.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_CELLS; + } +//MED + else if (self.weapon == IT_MJOLNIR) + { + self.currentammo = self.ammo_cells; + self.weaponmodel = "progs/v_hammer.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_CELLS; + } +//MED + else if (self.weapon == IT_PROXIMITY_GUN) + { + self.currentammo = self.ammo_rockets; + self.weaponmodel = "progs/v_prox.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_ROCKETS; + } + else + { + self.currentammo = 0; + self.weaponmodel = ""; + self.weaponframe = 0; + } +}; + +float() W_BestWeapon = +{ + local float it; + + it = self.items; + + if (self.waterlevel <= 1 && self.ammo_cells >= 1 && (it & IT_LIGHTNING) ) + return IT_LIGHTNING; +//MED + else if(self.ammo_cells >= 1 && (it & IT_LASER_CANNON) ) + return IT_LASER_CANNON; + else if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) ) + return IT_SUPER_NAILGUN; + else if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) ) + return IT_SUPER_SHOTGUN; + else if(self.ammo_nails >= 1 && (it & IT_NAILGUN) ) + return IT_NAILGUN; + else if(self.ammo_shells >= 1 && (it & IT_SHOTGUN) ) + return IT_SHOTGUN; +//MED + else if( it & IT_MJOLNIR ) + return IT_MJOLNIR; + +/* + if(self.ammo_rockets >= 1 && (it & IT_ROCKET_LAUNCHER) ) + return IT_ROCKET_LAUNCHER; + else if(self.ammo_rockets >= 1 && (it & IT_GRENADE_LAUNCHER) ) + return IT_GRENADE_LAUNCHER; + +*/ + + return IT_AXE; +}; + +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon = +{ + float playerFlags = CheckPlayerEXFlags(playerEnt); + if((playerFlags & PEF_CHANGENEVER) != 0) + { + return 0; + } + + if(((playerFlags & PEF_CHANGEONLYNEW) != 0) && old == new) + { + return 0; + } + + return 1; +}; + +float() W_CheckNoAmmo = +{ + if (self.currentammo > 0) + return TRUE; + + if (self.weapon == IT_AXE) + return TRUE; + +//MED + if (self.weapon == IT_MJOLNIR) + { + return TRUE; + } + + self.weapon = W_BestWeapon (); + + W_SetCurrentAmmo (); + +// drop the weapon down + return FALSE; +}; + +/* +============ +W_Attack + +An attack impulse can be triggered now +============ +*/ +void() player_axe1; +void() player_axeb1; +void() player_axec1; +void() player_axed1; +void() player_shot1; +void() player_nail1; +void() player_light1; +void() player_rocket1; +//MED +void() player_laser1; +void() player_hammer1; +void() player_mjolnir1; + + +void() W_Attack = +{ + local float r; + + if (!W_CheckNoAmmo ()) + return; + + makevectors (self.v_angle); // calculate forward angle for velocity + self.show_hostile = time + 1; // wake monsters up + + if (self.weapon == IT_AXE) + { + sound (self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); + r = random(); + if (r < 0.25) + player_axe1 (); + else if (r<0.5) + player_axeb1 (); + else if (r<0.75) + player_axec1 (); + else + player_axed1 (); + self.attack_finished = time + 0.5; + } + else if (self.weapon == IT_SHOTGUN) + { + player_shot1 (); + W_FireShotgun (); + self.attack_finished = time + 0.5; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + player_shot1 (); + W_FireSuperShotgun (); + self.attack_finished = time + 0.7; + } + else if (self.weapon == IT_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + player_rocket1(); + W_FireGrenade(); + self.attack_finished = time + 0.6; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + player_rocket1(); + W_FireRocket(); + self.attack_finished = time + 0.8; + } + else if (self.weapon == IT_LIGHTNING) + { + player_light1(); + self.attack_finished = time + 0.1; + sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + } +//MED + else if (self.weapon == IT_LASER_CANNON) + { + player_laser1(); + } +//MED + else if (self.weapon == IT_MJOLNIR) + { + if (self.ammo_cells < 30) + player_hammer1(); + else + player_mjolnir1(); + self.attack_finished = time + 0.8; + } +//MED + else if (self.weapon == IT_PROXIMITY_GUN) + { + player_rocket1(); + W_FireProximityGrenade(); + self.attack_finished = time + 0.6; + } +}; + +/* +============ +W_ChangeWeapon + +============ +*/ +//MED 10/18/96 added hipweapons +void() W_ChangeWeapon = +{ + local float it, am, fl; + local float oldimpulse; + + it = self.items; + am = 0; + +//MED + if (self.impulse == 1) + { + fl = IT_AXE; + } + else if (self.impulse == 2) + { + fl = IT_SHOTGUN; + if (self.ammo_shells < 1) + am = 1; + } + else if (self.impulse == 3) + { + fl = IT_SUPER_SHOTGUN; + if (self.ammo_shells < 2) + am = 1; + } + else if (self.impulse == 4) + { + fl = IT_NAILGUN; + if (self.ammo_nails < 1) + am = 1; + } +//MED + else if (self.impulse == 5) + { + fl = IT_SUPER_NAILGUN; + if (self.ammo_nails < 2) + am = 1; + + } +//MED + else if (self.impulse == 6) + { + if (self.weapon == IT_GRENADE_LAUNCHER) + { + fl = IT_PROXIMITY_GUN; + } + else + { + fl = IT_GRENADE_LAUNCHER; + } + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.impulse == 7) + { + fl = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } +//MED + else if (self.impulse == 8) + { + fl = IT_LIGHTNING; + if (self.ammo_cells < 1) + am = 1; + } + else if (self.impulse == 225) + { + fl = IT_LASER_CANNON; + if (self.ammo_cells < 1) + am = 1; + } + else if (self.impulse == 226) + { + fl = IT_MJOLNIR; + } + else if (self.impulse == 227) + { + fl = IT_PROXIMITY_GUN; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.impulse == 228) + { + fl = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + +//MED + oldimpulse = self.impulse; + self.impulse = 0; + + if (!(self.items & fl)) + { +//MED + if (fl == IT_GRENADE_LAUNCHER) + { + fl = IT_PROXIMITY_GUN; + if (!(self.items & fl)) + { + sprint (self, "$qc_no_weapon"); + return; + } + if (self.ammo_rockets < 1) + am = 1; + else + am = 0; + } + else + { + sprint (self, "$qc_no_weapon"); + return; + } + } + + if (am) + { // don't have the ammo + sprint (self, "$qc_not_enough_ammo"); + return; + } + +// +// set weapon, set ammo +// + self.weapon = fl; + W_SetCurrentAmmo (); +}; + +/* +============ +CheatCommand +============ +*/ +void() CheatCommand = +{ + if ( ( deathmatch || coop ) && cheats_allowed == 0 ) { // mal: allow impulse 9 IF cheats allowed + return; + } + + self.ammo_rockets = 100; + self.ammo_nails = 200; + self.ammo_shells = 100; + self.items = self.items | + IT_AXE | + IT_SHOTGUN | + IT_SUPER_SHOTGUN | + IT_NAILGUN | + IT_SUPER_NAILGUN | + IT_GRENADE_LAUNCHER | + IT_ROCKET_LAUNCHER | + IT_KEY1 | IT_KEY2; + + self.ammo_cells = 200; + self.items = self.items | IT_LIGHTNING; +//MED + self.items = self.items | IT_LASER_CANNON; + self.items = self.items | IT_MJOLNIR; + self.items = self.items | IT_PROXIMITY_GUN; + + self.weapon = IT_ROCKET_LAUNCHER; + self.impulse = 0; + W_SetCurrentAmmo (); +}; + +/* +============ +CycleWeaponCommand + +Go to the next weapon with ammo +============ +*/ +void() CycleWeaponCommand = +{ + local float it, am; + + it = self.items; + self.impulse = 0; + + while (1) + { + am = 0; + +//MED + if (self.weapon == IT_MJOLNIR) + { + self.weapon = IT_AXE; + } + else if (self.weapon == IT_AXE) + { + self.weapon = IT_SHOTGUN; + if (self.ammo_shells < 1) + am = 1; + } + else if (self.weapon == IT_SHOTGUN) + { + self.weapon = IT_SUPER_SHOTGUN; + if (self.ammo_shells < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.weapon = IT_NAILGUN; + if (self.ammo_nails < 1) + am = 1; + } + else if (self.weapon == IT_NAILGUN) + { + self.weapon = IT_SUPER_NAILGUN; + if (self.ammo_nails < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.weapon = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } +//MED + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.weapon = IT_PROXIMITY_GUN; + if (self.ammo_rockets < 1) + am = 1; + } +//MED + else if (self.weapon == IT_PROXIMITY_GUN) + { + self.weapon = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.weapon = IT_LIGHTNING; + if (self.ammo_cells < 1) + am = 1; + } + else if (self.weapon == IT_LIGHTNING) + { + self.weapon = IT_LASER_CANNON; + if (self.ammo_cells < 1) + am = 1; + } + else if (self.weapon == IT_LASER_CANNON) + { + self.weapon = IT_MJOLNIR; + } + + if ( (self.items & self.weapon) && am == 0) + { + W_SetCurrentAmmo (); + return; + } + } + +}; + +/* +============ +CycleWeaponReverseCommand + +Go to the prev weapon with ammo +============ +*/ +void() CycleWeaponReverseCommand = +{ + local float it, am; + + it = self.items; + self.impulse = 0; + + while (1) + { + am = 0; + +//MED + if (self.weapon == IT_MJOLNIR) + { + self.weapon = IT_LASER_CANNON; + if (self.ammo_cells < 1) + am = 1; + } +//MED + else if (self.weapon == IT_LASER_CANNON) + { + self.weapon = IT_LIGHTNING; + if (self.ammo_cells < 1) + am = 1; + } + else if (self.weapon == IT_LIGHTNING) + { + self.weapon = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.weapon = IT_PROXIMITY_GUN; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.weapon == IT_PROXIMITY_GUN) + { + self.weapon = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.weapon = IT_SUPER_NAILGUN; + if (self.ammo_nails < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.weapon = IT_NAILGUN; + if (self.ammo_nails < 1) + am = 1; + } + else if (self.weapon == IT_NAILGUN) + { + self.weapon = IT_SUPER_SHOTGUN; + if (self.ammo_shells < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.weapon = IT_SHOTGUN; + if (self.ammo_shells < 1) + am = 1; + } + else if (self.weapon == IT_SHOTGUN) + { + self.weapon = IT_AXE; + } + else if (self.weapon == IT_AXE) + { + self.weapon = IT_MJOLNIR; + } + + if ( (it & self.weapon) && am == 0) + { + W_SetCurrentAmmo (); + return; + } + } + +}; + +/* +============ +ServerflagsCommand + +Just for development +============ +*/ +void() ServerflagsCommand = +{ + serverflags = serverflags * 2 + 1; +}; + +void() QuadCheat = +{ + if (deathmatch || coop) + return; + self.super_time = 1; + self.super_damage_finished = time + 30; + self.items = self.items | IT_QUAD; + bprint ("$qc_quad_cheat"); +}; + +//MED +void() WetsuitCheat = + { + if (deathmatch || coop) + return; + self.items2 = self.items2 | HIP_IT_WETSUIT; + self.wetsuit_time = 1; + self.wetsuit_finished = time + 30; + bprint( "$qc_wetsuit_cheat" ); + }; + +//MED +void() EmpathyShieldsCheat = + { + if (deathmatch || coop) + return; + self.items2 = self.items2 | HIP_IT_EMPATHY_SHIELDS; + self.empathy_time = 1; + self.empathy_finished = time + 30; + bprint( "$qc_empathy_cheat" ); + }; + +void() DumpEntities = + { + local entity head; + local float i; + + i = 1; + head = nextent(world); + while (head!=world) + { + dprint(ftos(i)); + dprint(" "); + dprint(head.classname); + dprint("\n"); + head = nextent(head); + i = i + 1; + } + }; + +void() DumpLiveEntities = + { + local entity head; + local float i; + + i = 1; + head = nextent(world); + while (head!=world) + { + if (head.health > 0) + { + dprint(ftos(i)); + dprint(" "); + dprint(head.classname); + dprint(" "); + dprint(vtos(head.origin)); + dprint("\n"); + dprint("--------------------\n"); + } + head = nextent(head); + i = i + 1; + } + }; + +void() Genocide = + { + local entity head; + + if (deathmatch || coop) + return; + bprint("$qc_genocide_cheat"); + head = nextent(world); + while (head!=world) + { + if ((head.health > 0) && (head.flags & FL_MONSTER)) + { + T_Damage(head,world,world,head.health+10); + } + head = nextent(head); + } + }; + +float dump_coord; +void () ToggleDump = + { + dump_coord = 1 - dump_coord; + if (dump_coord == 1) + bprint("$qc_dump_player_loc"); + }; +void () DumpCoordinates = + { + local entity pl; + pl = checkclient(); + if (pl) + { + bprint("Player: "); + bprint(vtos(pl.origin)); + bprint("\n"); + } + }; +/* +============ +ImpulseCommands + +============ +*/ +void() ImpulseCommands = +{ + if (self.impulse >= 1 && self.impulse <= 8) + W_ChangeWeapon (); + + if (self.impulse >= 225 && self.impulse <= 228) + W_ChangeWeapon (); + + if (self.impulse == 9) + CheatCommand (); + if (self.impulse == 10) + CycleWeaponCommand (); + if (self.impulse == 11) + ServerflagsCommand (); + if (self.impulse == 12) + CycleWeaponReverseCommand (); +//JIM + if ( self.impulse == 200 ) + WetsuitCheat(); +//MED + if ( self.impulse == 201 ) + EmpathyShieldsCheat(); + if ( self.impulse == 205 ) + Genocide(); + if ( self.impulse == 206 ) + ToggleDump(); + if ( self.impulse == 202 ) + DumpEntities(); + if ( self.impulse == 203 ) + DumpLiveEntities(); + if (self.impulse == 255) + QuadCheat (); + if (dump_coord == 1) + DumpCoordinates(); + + self.impulse = 0; +}; + +/* +============ +W_WeaponFrame + +Called every frame so impulse events can be handled as well as possible +============ +*/ +void() W_WeaponFrame = +{ + if (time < self.attack_finished) + return; + + ImpulseCommands (); + +// check for attack + if (self.button0) + { + SuperDamageSound (); + W_Attack (); + } +}; + +/* +======== +SuperDamageSound + +Plays sound if needed +======== +*/ +void() SuperDamageSound = +{ + if (self.super_damage_finished > time) + { + if (self.super_sound < time) + { + self.super_sound = time + 1; + sound (self, CHAN_BODY, "items/damage3.wav", 1, ATTN_NORM); + } + } + return; +}; diff --git a/quakec_hipnotic/world.qc b/quakec_hipnotic/world.qc new file mode 100644 index 0000000..546b2f9 --- /dev/null +++ b/quakec_hipnotic/world.qc @@ -0,0 +1,481 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +void() InitBodyQue; +//JIM +void() InitBulletHoles; + + +void() main = +{ + dprint ("main function\n"); + +// these are just commands the the prog compiler to copy these files + + precache_file ("progs.dat"); + precache_file ("gfx.wad"); + precache_file ("quake.rc"); + precache_file ("default.cfg"); + + precache_file ("end1.bin"); + precache_file2 ("end2.bin"); + + precache_file ("demo1.dem"); + precache_file ("demo2.dem"); + precache_file ("demo3.dem"); + +// +// these are all of the lumps from the cached.ls files +// + precache_file ("gfx/palette.lmp"); + precache_file ("gfx/colormap.lmp"); + + precache_file2 ("gfx/pop.lmp"); + + precache_file ("gfx/complete.lmp"); + precache_file ("gfx/inter.lmp"); + + precache_file ("gfx/ranking.lmp"); + precache_file ("gfx/vidmodes.lmp"); + precache_file ("gfx/finale.lmp"); + precache_file ("gfx/conback.lmp"); + precache_file ("gfx/qplaque.lmp"); + + precache_file ("gfx/menudot1.lmp"); + precache_file ("gfx/menudot2.lmp"); + precache_file ("gfx/menudot3.lmp"); + precache_file ("gfx/menudot4.lmp"); + precache_file ("gfx/menudot5.lmp"); + precache_file ("gfx/menudot6.lmp"); + + precache_file ("gfx/menuplyr.lmp"); + precache_file ("gfx/bigbox.lmp"); + precache_file ("gfx/dim_modm.lmp"); + precache_file ("gfx/dim_drct.lmp"); + precache_file ("gfx/dim_ipx.lmp"); + precache_file ("gfx/dim_tcp.lmp"); + precache_file ("gfx/dim_mult.lmp"); + precache_file ("gfx/mainmenu.lmp"); + + precache_file ("gfx/box_tl.lmp"); + precache_file ("gfx/box_tm.lmp"); + precache_file ("gfx/box_tr.lmp"); + + precache_file ("gfx/box_ml.lmp"); + precache_file ("gfx/box_mm.lmp"); + precache_file ("gfx/box_mm2.lmp"); + precache_file ("gfx/box_mr.lmp"); + + precache_file ("gfx/box_bl.lmp"); + precache_file ("gfx/box_bm.lmp"); + precache_file ("gfx/box_br.lmp"); + + precache_file ("gfx/sp_menu.lmp"); + precache_file ("gfx/ttl_sgl.lmp"); + precache_file ("gfx/ttl_main.lmp"); + precache_file ("gfx/ttl_cstm.lmp"); + + precache_file ("gfx/mp_menu.lmp"); + + precache_file ("gfx/netmen1.lmp"); + precache_file ("gfx/netmen2.lmp"); + precache_file ("gfx/netmen3.lmp"); + precache_file ("gfx/netmen4.lmp"); + precache_file ("gfx/netmen5.lmp"); + + precache_file ("gfx/sell.lmp"); + + precache_file ("gfx/help0.lmp"); + precache_file ("gfx/help1.lmp"); + precache_file ("gfx/help2.lmp"); + precache_file ("gfx/help3.lmp"); + precache_file ("gfx/help4.lmp"); + precache_file ("gfx/help5.lmp"); + + precache_file ("gfx/pause.lmp"); + precache_file ("gfx/loading.lmp"); + + precache_file ("gfx/p_option.lmp"); + precache_file ("gfx/p_load.lmp"); + precache_file ("gfx/p_save.lmp"); + precache_file ("gfx/p_multi.lmp"); + +// sounds loaded by C code + precache_sound ("misc/menu1.wav"); + precache_sound ("misc/menu2.wav"); + precache_sound ("misc/menu3.wav"); + + precache_sound ("ambience/water1.wav"); + precache_sound ("ambience/wind2.wav"); + +//MED +//Added Hipnotic Pack +/* +// shareware + precache_file ("maps/start.bsp"); + + precache_file ("maps/e1m1.bsp"); + precache_file ("maps/e1m2.bsp"); + precache_file ("maps/e1m3.bsp"); + precache_file ("maps/e1m4.bsp"); + precache_file ("maps/e1m5.bsp"); + precache_file ("maps/e1m6.bsp"); + precache_file ("maps/e1m7.bsp"); + precache_file ("maps/e1m8.bsp"); + +// registered + precache_file2 ("gfx/pop.lmp"); + + precache_file2 ("maps/e2m1.bsp"); + precache_file2 ("maps/e2m2.bsp"); + precache_file2 ("maps/e2m3.bsp"); + precache_file2 ("maps/e2m4.bsp"); + precache_file2 ("maps/e2m5.bsp"); + precache_file2 ("maps/e2m6.bsp"); + precache_file2 ("maps/e2m7.bsp"); + + precache_file2 ("maps/e3m1.bsp"); + precache_file2 ("maps/e3m2.bsp"); + precache_file2 ("maps/e3m3.bsp"); + precache_file2 ("maps/e3m4.bsp"); + precache_file2 ("maps/e3m5.bsp"); + precache_file2 ("maps/e3m6.bsp"); + precache_file2 ("maps/e3m7.bsp"); + + precache_file2 ("maps/e4m1.bsp"); + precache_file2 ("maps/e4m2.bsp"); + precache_file2 ("maps/e4m3.bsp"); + precache_file2 ("maps/e4m4.bsp"); + precache_file2 ("maps/e4m5.bsp"); + precache_file2 ("maps/e4m6.bsp"); + precache_file2 ("maps/e4m7.bsp"); + precache_file2 ("maps/e4m8.bsp"); + + precache_file2 ("maps/end.bsp"); + + precache_file2 ("maps/dm1.bsp"); + precache_file2 ("maps/dm2.bsp"); + precache_file2 ("maps/dm3.bsp"); + precache_file2 ("maps/dm4.bsp"); + precache_file2 ("maps/dm5.bsp"); + precache_file2 ("maps/dm6.bsp"); +*/ + precache_file ("maps/start.bsp"); + + precache_file ("maps/hip1m1.bsp"); + precache_file ("maps/hip1m2.bsp"); + precache_file ("maps/hip1m3.bsp"); + precache_file ("maps/hip1m4.bsp"); + precache_file ("maps/hip1m5.bsp"); + precache_file ("maps/hip2m1.bsp"); + precache_file ("maps/hip2m2.bsp"); + precache_file ("maps/hip2m3.bsp"); + precache_file ("maps/hip2m4.bsp"); + precache_file ("maps/hip2m5.bsp"); + precache_file ("maps/hip2m6.bsp"); + precache_file ("maps/hip3m1.bsp"); + precache_file ("maps/hip3m2.bsp"); + precache_file ("maps/hip3m3.bsp"); + precache_file ("maps/hip3m4.bsp"); + precache_file ("maps/hipdm1.bsp"); + precache_file ("maps/hipend.bsp"); + precache_file ("maps/zoo.bsp"); + precache_file ("maps/mark.bsp"); +}; + + +entity lastspawn; + +//======================= +/*QUAKED worldspawn (0 0 0) ? +Only used for the world entity. +Set message to the level name. +Set sounds to the cd track to play. + +World Types: +0: medieval +1: metal +2: base +*/ +//======================= + +void() worldspawn = +{ + lastspawn = world; + InitBodyQue (); + +//JIM + InitBulletHoles(); + +// custom map attributes + if (self.model == "maps/e1m8.bsp") + cvar_set ("sv_gravity", "100"); + else + cvar_set ("sv_gravity", "800"); + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + +// sounds used from C physics code + precache_sound ("demon/dland2.wav"); // landing thud + precache_sound ("misc/h2ohit1.wav"); // landing splash + +// setup precaches allways needed + precache_sound ("items/itembk2.wav"); // item respawn sound + precache_sound ("player/plyrjmp8.wav"); // player jump + precache_sound ("player/land.wav"); // player landing + precache_sound ("player/land2.wav"); // player hurt landing + precache_sound ("player/drown1.wav"); // drowning pain + precache_sound ("player/drown2.wav"); // drowning pain + precache_sound ("player/gasp1.wav"); // gasping for air + precache_sound ("player/gasp2.wav"); // taking breath + precache_sound ("player/h2odeath.wav"); // drowning death + + precache_sound ("misc/talk.wav"); // talk + precache_sound ("player/teledth1.wav"); // telefrag + precache_sound ("misc/r_tele1.wav"); // teleport sounds + precache_sound ("misc/r_tele2.wav"); + precache_sound ("misc/r_tele3.wav"); + precache_sound ("misc/r_tele4.wav"); + precache_sound ("misc/r_tele5.wav"); + precache_sound ("weapons/lock4.wav"); // ammo pick up + precache_sound ("weapons/pkup.wav"); // weapon up + precache_sound ("items/armor1.wav"); // armor up + precache_sound ("weapons/lhit.wav"); //lightning + precache_sound ("weapons/lstart.wav"); //lightning start + precache_sound ("items/damage3.wav"); + + precache_sound ("misc/power.wav"); //lightning for boss + +// player gib sounds + precache_sound ("player/gib.wav"); // player gib sound + precache_sound ("player/udeath.wav"); // player gib sound + precache_sound ("player/tornoff2.wav"); // gib sound + +// player pain sounds + + precache_sound ("player/pain1.wav"); + precache_sound ("player/pain2.wav"); + precache_sound ("player/pain3.wav"); + precache_sound ("player/pain4.wav"); + precache_sound ("player/pain5.wav"); + precache_sound ("player/pain6.wav"); + +// player death sounds + precache_sound ("player/death1.wav"); + precache_sound ("player/death2.wav"); + precache_sound ("player/death3.wav"); + precache_sound ("player/death4.wav"); + precache_sound ("player/death5.wav"); + +// ax sounds + precache_sound ("weapons/ax1.wav"); // ax swoosh + precache_sound ("player/axhit1.wav"); // ax hit meat + precache_sound ("player/axhit2.wav"); // ax hit world + + precache_sound ("player/h2ojump.wav"); // player jumping into water + precache_sound ("player/slimbrn2.wav"); // player enter slime + precache_sound ("player/inh2o.wav"); // player enter water + precache_sound ("player/inlava.wav"); // player enter lava + precache_sound ("misc/outwater.wav"); // leaving water sound + + precache_sound ("player/lburn1.wav"); // lava burn + precache_sound ("player/lburn2.wav"); // lava burn + + precache_sound ("misc/water1.wav"); // swimming + precache_sound ("misc/water2.wav"); // swimming + +//MED 10/21/96 added flies sound + precache_sound ("misc/flys.wav"); +//MED 01/13/97 added explosion sounds + precache_sound ("misc/shortexp.wav"); + precache_sound ("misc/longexpl.wav"); + precache_sound ("misc/foot1.wav"); + precache_sound ("misc/foot2.wav"); + precache_sound ("misc/foot3.wav"); + precache_sound ("misc/foot4.wav"); + precache_sound ("misc/foot5.wav"); + precache_sound ("misc/foot6.wav"); + precache_sound ("misc/foot7.wav"); + + precache_model ("progs/player.mdl"); + precache_model ("progs/eyes.mdl"); + precache_model ("progs/h_player.mdl"); +//MED 12/03/96 added player with hammer + precache_model ("progs/playham.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_model ("progs/s_bubble.spr"); // drowning bubbles + precache_model ("progs/s_explod.spr"); // sprite explosion + + precache_model ("progs/v_axe.mdl"); + precache_model ("progs/v_shot.mdl"); + precache_model ("progs/v_nail.mdl"); + precache_model ("progs/v_rock.mdl"); + precache_model ("progs/v_shot2.mdl"); + precache_model ("progs/v_nail2.mdl"); + precache_model ("progs/v_rock2.mdl"); +//MED 10/31/96 added precache for hip view models + precache_model ("progs/v_laserg.mdl"); + precache_model ("progs/v_hammer.mdl"); + precache_model ("progs/v_prox.mdl"); + precache_model ("progs/proxbomb.mdl"); + + precache_model ("progs/bolt.mdl"); // for lightning gun + precache_model ("progs/bolt2.mdl"); // for lightning gun + precache_model ("progs/bolt3.mdl"); // for boss shock + precache_model ("progs/lavaball.mdl"); // for testing +//MED 10/18/96 added precache for laser cannon + precache_model ("progs/lasrspik.mdl"); + + precache_model ("progs/missile.mdl"); + precache_model ("progs/grenade.mdl"); + precache_model ("progs/spike.mdl"); + precache_model ("progs/s_spike.mdl"); + + precache_model ("progs/backpack.mdl"); + + precache_model ("progs/zom_gib.mdl"); + + precache_model ("progs/v_light.mdl"); + + +// +// Setup light animation tables. 'a' is total darkness, 'z' is maxbright. +// + + // 0 normal + lightstyle(0, "m"); + + // 1 FLICKER (first variety) + lightstyle(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + lightstyle(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + lightstyle(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + lightstyle(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + lightstyle(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + lightstyle(63, "a"); + +//JIM + StopEarthQuake(); +//MED + if (cvar("crosshair") == 2) + footsteps = 1; + else + footsteps = 0; + }; + +void() StartFrame = +{ + teamplay = cvar("teamplay"); + skill = cvar("skill"); + cheats_allowed = cvar("sv_cheats"); + + // save off a global here so it gets included in savegames + if (!campaign_valid) { + campaign_valid = TRUE; + campaign = cvar("campaign"); + } else { + cvar_set("campaign", ftos(campaign)); + } + + framecount = framecount + 1; +}; + +/* +============================================================================== + +BODY QUE + +============================================================================== +*/ + +entity bodyque_head; + +void() bodyque = +{ // just here so spawn functions don't complain after the world + // creates bodyques +}; + +void() InitBodyQue = +{ + local entity e; + + bodyque_head = spawn(); + bodyque_head.classname = "bodyque"; + bodyque_head.owner = spawn(); + bodyque_head.owner.classname = "bodyque"; + bodyque_head.owner.owner = spawn(); + bodyque_head.owner.owner.classname = "bodyque"; + bodyque_head.owner.owner.owner = spawn(); + bodyque_head.owner.owner.owner.classname = "bodyque"; + bodyque_head.owner.owner.owner.owner = bodyque_head; +}; + + +// make a body que entry for the given ent so the ent can be +// respawned elsewhere +void(entity ent) CopyToBodyQue = +{ + bodyque_head.angles = ent.angles; + bodyque_head.model = ent.model; + bodyque_head.modelindex = ent.modelindex; + bodyque_head.frame = ent.frame; + bodyque_head.colormap = ent.colormap; + bodyque_head.movetype = ent.movetype; + bodyque_head.velocity = ent.velocity; + bodyque_head.flags = 0; + setorigin (bodyque_head, ent.origin); + setsize (bodyque_head, ent.mins, ent.maxs); + bodyque_head = bodyque_head.owner; +}; diff --git a/quakec_mg1/ai.qc b/quakec_mg1/ai.qc new file mode 100644 index 0000000..90816d7 --- /dev/null +++ b/quakec_mg1/ai.qc @@ -0,0 +1,897 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() t_movetarget; +void() knight_walk1; +void() knight_bow6; +void() knight_bow1; +void(entity etemp, entity stemp, entity stemp, float dmg) T_Damage; +float CanTakedamage(entity e) = { return e.takedamage > 0; } +/* + +.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. + +.huntt_ime +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 +*/ + + +// +// globals +// + +// +// when a monster becomes angry at a player, that monster will be used +// as the sight target the next frame so that monsters near that one +// will wake up even if they wouldn't have noticed the player +// +entity sight_entity; +float sight_entity_time; + +float(float v) anglemod = +{ + while (v >= 360) + v = v - 360; + while (v < 0) + v = v + 360; + return v; +}; + +/* +============================================================================== + +MOVETARGET CODE + +The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target. + +targetname +must be present. The name of this movetarget. + +target +the next spot to move to. If not present, stop here for good. + +pausetime +The number of seconds to spend standing or bowing for path_stand or path_bow + +============================================================================== +*/ + + +/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8) +Monsters will continue walking towards the next target corner. +*/ +void() path_corner = +{ + if (!self.targetname) + objerror ("path_corner with no targetname.\n"); + + if (self.wait < 0) self.wait = 999999; + + self.solid = SOLID_TRIGGER; + self.touch = t_movetarget; + setsize (self, '-8 -8 -8', '8 8 8'); +}; + + +/* +============= +t_movetarget + +Something has bumped into a movetarget. If it is a monster +moving towards it, change the next destination and continue. +============== +*/ +void() t_movetarget = +{ + local entity temp; + + if (other.movetarget != self) + return; + + if (other.enemy) + return; // fighting, not following a path + + self.owner = other; //We have a valid visitor! + if(other.dmg_inflictor && other.dmg_inflictor.classname == "path_corner") + { + //Clear the previous path corner from owning our new visitor + if(other.dmg_inflictor.owner == other.dmg_inflictor) + { + other.dmg_inflictor.owner = world; + } + } + other.dmg_inflictor = self; + + if(other.pausetime > time) + return; // Waiting to walk... + + temp = self; + self = other; + other = temp; + + if (self.classname == "monster_ogre") + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound + + self.goalentity = self.movetarget = find (world, targetname, other.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + if (!self.movetarget) + { + self.pausetime = time + 999999; + self.th_stand (); + } + else if(other.wait) + { + self.pausetime = time + other.wait; + self.th_stand (); + } +}; + +//============================================================================ +// Functions for changing how path_corners work. +//============================================================================ +void monster_use(); + +void target_cancelpause_use() +{ + entity mon = find(world, targetname, self.target); + while(mon) + { + if(mon.flags & FL_MONSTER) + { + mon.pausetime = 0; + mon.use = monster_use; + } + mon = find(mon, targetname, self.target); + } +} + +/*QUAKED target_cancelpause (0.5 0.3 0) (-8 -8 -8) (8 8 8) +When activated, will zero out the pausetime for monsters, causing them to continue walking their paths. +*/ +void target_cancelpause() +{ + if(!self.target) objerror("target_cancelpause with no target given.\n"); + if(!self.targetname) objerror("target_cancelpause with no targetname given.\n"); + self.use = target_cancelpause_use; +} + +//============================================================================ + +void target_switchpath_use() +{ + entity e = find(world, targetname, self.target); + while(e) + { + if(e.classname == "path_corner") + { + string oldTarget = e.target; + // Just switch the target + e.target = self.netname; + + // Has this corner been visited by a monster lately? + if(e.owner) + { + entity o = e.owner; + entity oldTargetEntity = find(world, targetname, oldTarget); + if(o.movetarget == oldTargetEntity) + { + // Switch that monster over to walk to the new target instead + o.goalentity = o.movetarget = find(world, targetname, e.target); + o.ideal_yaw = vectoyaw(o.goalentity.origin - o.origin); + } + } + } + e = find(e, targetname, self.target); + } +} + +/*QUAKED target_switchpath (0.5 0.3 0) (-8 -8 -8) (8 8 8) +When activated, will switch out the target . +*/ +void target_switchpath() +{ + if(!self.target) objerror("target_switchtarget with no target given.\n"); + if(!self.targetname) objerror("target_switchtarget with no targetname given.\n"); + if(!self.netname) objerror("target_switchtarget with no netname given.\n"); + self.use = target_switchpath_use; +} + +#ifdef MONSTERS_AWARE_OF_CONTENTS +#define RETURN_IF_DIED_FROM_CONTENTS_DAMAGE if(CheckContentsDamage()) return; + +//============================================================================ +// CheckContentsDamage +// Returns TRUE if the monster died and shouldn't proceed with its current think +//============================================================================ + +.float dmgtime; +float CheckContentsDamage() +{ + if(self.waterlevel == 0) return FALSE; //Not in any liquid + if(self.health <= 0) return FALSE; //Dead or dying + if(time < self.dmgtime) return FALSE; //Still processing damage from last time + if(self.spawnflags & SPAWNFLAG_NO_CONTENTS_DAMAGE) return FALSE; //We're immune + + if(self.watertype == CONTENT_SLIME) + { + self.dmgtime = time + 1; + T_Damage (self, world, world, 4 * self.waterlevel); + // self.pain_finished = 0; //Enable for spasming monsters + } + else if(self.watertype == CONTENT_LAVA) + { + self.dmgtime = time + 0.2; + if(self.classname == "monster_zombie") + { + T_Damage (self, world, world, 120); // GIB! + return TRUE; + } + T_Damage (self, world, world, 30 * self.waterlevel); + // self.pain_finished = 0; //Enable for spasming monsters + } + + return self.health <= 0; +} +#else +#define RETURN_IF_DIED_FROM_CONTENTS_DAMAGE ; +#endif + +//============================================================================ + +const vector RANGES_NORMAL = '120 500 1000'; +const vector RANGES_NEARSIGHTED = '120 300 340'; + +const float VISIONCONE_NORMAL = 0.3; +const float VISIONCONE_NEARSIGHTED = 0.866; // ~30 degrees +/* +============= +range + +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 +============= +*/ + + +float(entity targ) range = +{ + vector spot1 = self.origin + self.view_ofs; + vector spot2 = targ.origin + targ.view_ofs; + float r = vlen (spot1 - spot2); + + vector ranges = (self.spawnflags & SPAWNFLAG_NEARSIGHTED) ? ((self.enemy) ? RANGES_NORMAL * 0.8 : RANGES_NEARSIGHTED) : RANGES_NORMAL; + + if (r < ranges_x) + return RANGE_MELEE; + + if (r < ranges_y) + return RANGE_NEAR; + + if (r < ranges_z) + return RANGE_MID; + + return RANGE_FAR; +}; + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +float (entity targ) visible = +{ + local vector spot1, spot2; + + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + traceline (spot1, spot2, TRUE, self); // see through other monsters + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_fraction == 1) + return TRUE; + return FALSE; +}; + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ + +float(entity targ) infront = +{ + local vector vec; + local float dot, dottarget; + + makevectors (self.angles); + vec = normalize (targ.origin - self.origin); + dot = vec * v_forward; + + dottarget = (self.spawnflags & SPAWNFLAG_NEARSIGHTED) ? VISIONCONE_NEARSIGHTED : VISIONCONE_NORMAL; + if ( dot > dottarget) + { + return TRUE; + } + return FALSE; +}; + + +//============================================================================ + +void() HuntTarget = +{ + self.goalentity = self.enemy; + self.think = self.th_run; + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + self.nextthink = time + 0.1; + SUB_AttackFinished (1); // wait a while before first attack +}; + +void() SightSound = +{ +local float rsnd; + + if (self.classname == "monster_ogre") + sound (self, CHAN_VOICE, "ogre/ogwake.wav", 1, ATTN_NORM); + else if (self.classname == "monster_knight") + sound (self, CHAN_VOICE, "knight/ksight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_shambler") + sound (self, CHAN_VOICE, "shambler/ssight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_demon1") + sound (self, CHAN_VOICE, "demon/sight2.wav", 1, ATTN_NORM); + else if (self.classname == "monster_wizard") + sound (self, CHAN_VOICE, "wizard/wsight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_zombie") + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_NORM); + else if (self.classname == "monster_dog") + sound (self, CHAN_VOICE, "dog/dsight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_hell_knight") + sound (self, CHAN_VOICE, "hknight/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_tarbaby") + sound (self, CHAN_VOICE, "blob/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_enforcer") + { + rsnd = rint(random() * 3); + if (rsnd == 1) + sound (self, CHAN_VOICE, "enforcer/sight1.wav", 1, ATTN_NORM); + else if (rsnd == 2) + sound (self, CHAN_VOICE, "enforcer/sight2.wav", 1, ATTN_NORM); + else if (rsnd == 0) + sound (self, CHAN_VOICE, "enforcer/sight3.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "enforcer/sight4.wav", 1, ATTN_NORM); + } + else if (self.classname == "monster_army") + sound (self, CHAN_VOICE, "soldier/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_shalrath") + sound (self, CHAN_VOICE, "shalrath/sight.wav", 1, ATTN_NORM); +}; + +void() FoundTarget = +{ + if (self.enemy.classname == "player") + { // let other monsters see this monster for a while + sight_entity = self; + sight_entity_time = time; + } + + self.show_hostile = time + 1; // wake up other monsters + + SightSound (); + HuntTarget (); +}; + +/* +=========== +FindTarget + +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. +============ +*/ +float() FindTarget = +{ + local entity client; + local float r; + + //First, if we are attacking friends, try to find another friend + if(self.spawnflags & MONSTER_ATTACK_FRIEND) + { + entity f = SUB_RandomTarget(self, CanTakedamage); + if(f) + { + self.enemy = f; + FoundTarget (); + return TRUE; + } + } + + + // if the first spawnflag bit is set, the monster will only wake up on + // really seeing the player, not another monster getting angry + + // spawnflags & 3 is a big hack, because zombie crucified used the first + // spawn flag prior to the ambush flag, and I forgot about it, so the second + // spawn flag works as well + if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) ) + { + client = sight_entity; + if (client.enemy == self.enemy) + return FALSE; + } + else + { + client = checkclient (); + if (!client) + return FALSE; // current check entity isn't in PVS + } + + if (client == self.enemy) + return FALSE; + + if (client.flags & FL_NOTARGET) + return FALSE; + if (client.items & IT_INVISIBILITY) + return FALSE; + + r = range (client); + if (r == RANGE_FAR) + { + return FALSE; + } + + if (!visible (client)) + { + return FALSE; + } + + if (r == RANGE_NEAR) + { + if (client.show_hostile < time && !infront (client)) + { + return FALSE; + } + } + else if (r == RANGE_MID) + { + if ( !infront (client)) + { + return FALSE; + } + } + +// +// got one +// + self.enemy = client; + if (self.enemy.classname != "player") + { + self.enemy = self.enemy.enemy; + if (self.enemy.classname != "player") + { + self.enemy = world; + return FALSE; + } + } + + FoundTarget (); + return TRUE; +}; + + +//============================================================================= + +void(float dist) ai_forward = +{ + walkmove (self.angles_y, dist); +}; + +void(float dist) ai_back = +{ + walkmove ( (self.angles_y+180), dist); +}; + + +/* +============= +ai_pain + +stagger back a bit +============= +*/ +void(float dist) ai_pain = +{ + ai_back (dist); +/* + local float away; + + away = anglemod (vectoyaw (self.origin - self.enemy.origin) + + 180*(random()- 0.5) ); + + walkmove (away, dist); +*/ +}; + +/* +============= +ai_painforward + +stagger back a bit +============= +*/ +void(float dist) ai_painforward = +{ + walkmove (self.ideal_yaw, dist); +}; + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void(float dist) ai_walk = +{ + RETURN_IF_DIED_FROM_CONTENTS_DAMAGE + + movedist = dist; + + // check for noticing a player + if (FindTarget ()) + return; + + movetogoal (dist); +}; + + +/* +============= +ai_stand + +The monster is staying in one place for a while, with slight angle turns +============= +*/ +void() ai_stand = +{ + RETURN_IF_DIED_FROM_CONTENTS_DAMAGE + + if (FindTarget ()) + return; + + if (time > self.pausetime) + { + self.th_walk (); + return; + } +}; + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +============= +*/ +void() ai_turn = +{ + if (FindTarget ()) + return; + + ChangeYaw (); +}; + +//============================================================================= + +/* +============= +ChooseTurn +============= +*/ +void(vector dest3) ChooseTurn = +{ + local vector dir, newdir; + + dir = self.origin - dest3; + + newdir_x = trace_plane_normal_y; + newdir_y = 0 - trace_plane_normal_x; + newdir_z = 0; + + if (dir * newdir > 0) + { + dir_x = 0 - trace_plane_normal_y; + dir_y = trace_plane_normal_x; + } + else + { + dir_x = trace_plane_normal_y; + dir_y = 0 - trace_plane_normal_x; + } + + dir_z = 0; + self.ideal_yaw = vectoyaw(dir); +}; + +/* +============ +FacingIdeal + +============ +*/ +float() FacingIdeal = +{ + local float delta; + + delta = anglemod(self.angles_y - self.ideal_yaw); + if (delta > 45 && delta < 315) + return FALSE; + return TRUE; +}; + + +//============================================================================= + +float() WizardCheckAttack; +float() DogCheckAttack; + +float() CheckAnyAttack = +{ + if (!enemy_visible) + return FALSE; + if (self.classname == "monster_army") + return SoldierCheckAttack (); + if (self.classname == "monster_ogre") + return OgreCheckAttack (); + if (self.classname == "monster_shambler") + return ShamCheckAttack (); + if (self.classname == "monster_demon1") + return DemonCheckAttack (); + if (self.classname == "monster_dog") + return DogCheckAttack (); + if (self.classname == "monster_wizard") + return WizardCheckAttack (); + return CheckAttack (); +}; + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void() ai_run_melee = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + + if (FacingIdeal()) + { + self.th_melee (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void() ai_run_missile = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (FacingIdeal()) + { + self.th_missile (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void() ai_run_slide = +{ + local float ofs; + + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (self.lefty) + ofs = 90; + else + ofs = -90; + + if (walkmove (self.ideal_yaw + ofs, movedist)) + return; + + self.lefty = 1 - self.lefty; + + walkmove (self.ideal_yaw - ofs, movedist); +}; + +/* +============= +ai_pathtogoal + +Advanced movement code that use the bots pathfinder if allowed and conditions are right. +Feel free to add any other conditions needed. +============= +*/ +void ai_pathtogoal( float dist ) { + if ( self.allowPathFind == FALSE ) { + movetogoal( dist ); // can't use pathfinding, so use normal Quake movement behavior. + return; + } + + if ( enemy_visible ) { + if ( self.combat_style == CS_RANGED ) { + // do the normal "shoot, walk, shoot" behavior... + movetogoal( dist ); + return; + } else if ( self.combat_style == CS_MELEE ) { + // path pretty close to the enemy, then let normal Quake movement take over. + if ( enemy_range > RANGE_NEAR ) { + if ( walkpathtogoal( dist, self.enemy.origin ) == PATH_IN_PROGRESS ) { + return; + } + } + } else if ( self.combat_style == CS_MIXED ) { + // most mixed combat AI have fairly short range attacks, so try to path within mid range. + if ( enemy_range > RANGE_MID ) { + if ( walkpathtogoal( dist, self.enemy.origin ) == PATH_IN_PROGRESS ) { + return; + } + } + } + } else { + // we can't see our enemy, let's see if we can path to them + if ( walkpathtogoal( dist, self.enemy.origin ) == PATH_IN_PROGRESS ) { + return; + } + } + + movetogoal( dist ); // fall back to normal Quake movement behavior. +} + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void(float dist) ai_run = +{ + RETURN_IF_DIED_FROM_CONTENTS_DAMAGE + + movedist = dist; +// see if the enemy is dead + if (self.enemy.health <= 0) + { + self.enemy = world; + if (self.oldenemy.health > 0) + { + self.enemy = self.oldenemy; + HuntTarget (); + } + else + { + if (self.movetarget) + self.th_walk (); + else + self.th_stand (); + return; + } + } + + self.show_hostile = time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_visible = visible(self.enemy); + if (enemy_visible) + self.search_time = time + 5; + +// look for other coop players + if (coop && self.search_time < time) + { + if (FindTarget ()) + return; + } + + enemy_range = range(self.enemy); + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + + if (self.attack_state == AS_MISSILE) + { +//dprint ("ai_run_missile\n"); + ai_run_missile (); + return; + } + if (self.attack_state == AS_MELEE) + { +//dprint ("ai_run_melee\n"); + ai_run_melee (); + return; + } + + if (CheckAnyAttack ()) + return; // beginning an attack + + if (self.attack_state == AS_SLIDING) + { + ai_run_slide (); + return; + } + + if ( isHordeMode ) { + ai_pathtogoal( dist ); + } else { + // head straight in + movetogoal( dist ); // done in C code... + } +}; + diff --git a/quakec_mg1/buttons.qc b/quakec_mg1/buttons.qc new file mode 100644 index 0000000..63ecfe7 --- /dev/null +++ b/quakec_mg1/buttons.qc @@ -0,0 +1,266 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +//Yoder Sept24 Horde Merge +void(float key_item)horde_key_spend; + +// button and multiple button + +float BUTTON_KEY_ALWAYS_REQUIRED = 32; + +void() button_wait; +void() button_return; + +void() button_wait = +{ + self.state = STATE_TOP; + self.nextthink = self.ltime + self.wait; + self.think = button_return; + activator = self.enemy; + SUB_UseTargets(); + self.frame = 1; // use alternate textures +}; + +void() button_done = +{ + self.state = STATE_BOTTOM; +}; + +void() button_return = +{ + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, button_done); + self.frame = 0; // use normal textures + if (self.health) + self.takedamage = DAMAGE_YES; // can be shot again +}; + + +void() button_blocked = +{ // do nothing, just don't ome all the way back out +}; + + +void() button_fire = +{ + if (self.state == STATE_UP || self.state == STATE_TOP) + return; + + if(self.items) + { + sound (self, CHAN_ITEM, self.noise4, 1, ATTN_NORM); + if(!(self.spawnflags & BUTTON_KEY_ALWAYS_REQUIRED)) + { + self.items = 0; + } + } + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, button_wait); +}; + + +void() button_use = +{ + self.enemy = activator; + button_fire (); +}; + +void() button_touch = +{ + if (other.classname != "player") + { + return; + } + + if(time < self.attack_finished) + { + return; + } + self.attack_finished = time + 2; + + if ( (self.items & other.items) != self.items ) + { + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + if (self.items == IT_KEY1) + { + if (self.worldtype == WORLDTYPE_BASE) + { + centerprint (other, "$qc_need_silver_keycard"); + } + else if (self.worldtype == WORLDTYPE_METAL) + { + centerprint (other, "$qc_need_silver_runekey"); + } + else if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + centerprint (other, "$qc_need_silver_key"); + } + } + else if (self.items == IT_KEY2) + { + if (self.worldtype == WORLDTYPE_BASE) + { + centerprint (other, "$qc_need_gold_keycard"); + } + else if (self.worldtype == WORLDTYPE_METAL) + { + centerprint (other, "$qc_need_gold_runekey"); + } + else if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + centerprint (other, "$qc_need_gold_key"); + } + } + return; + } + else + { + // Yoder Sept24, 2021 Horde merge + if (horde_ent) + { + horde_key_spend(self.items); + self.touch = SUB_Null; + } + else // standard behavior + other.items = other.items - self.items; + } + + self.enemy = other; + button_fire (); +}; + +void() button_killed = +{ + self.enemy = damage_attacker; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + button_fire (); +}; + + +/*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" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +void() func_button = +{ + self.netname = "func_button"; // for bot nav support. + SUB_SetWorldtype(); + if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + precache_sound ("doors/medtry.wav"); + precache_sound ("doors/meduse.wav"); + self.noise3 = "doors/medtry.wav"; + self.noise4 = "doors/meduse.wav"; + } + else if (self.worldtype == WORLDTYPE_METAL) + { + precache_sound ("doors/runetry.wav"); + precache_sound ("doors/runeuse.wav"); + self.noise3 = "doors/runetry.wav"; + self.noise4 = "doors/runeuse.wav"; + } + else if (self.worldtype == WORLDTYPE_BASE) + { + precache_sound ("doors/basetry.wav"); + precache_sound ("doors/baseuse.wav"); + self.noise3 = "doors/basetry.wav"; + self.noise4 = "doors/baseuse.wav"; + } + + if (self.sounds == 0) + { + precache_sound ("buttons/airbut1.wav"); + self.noise = "buttons/airbut1.wav"; + } + if (self.sounds == 1) + { + precache_sound ("buttons/switch21.wav"); + self.noise = "buttons/switch21.wav"; + } + if (self.sounds == 2) + { + precache_sound ("buttons/switch02.wav"); + self.noise = "buttons/switch02.wav"; + } + if (self.sounds == 3) + { + precache_sound ("buttons/switch04.wav"); + self.noise = "buttons/switch04.wav"; + } + + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + setmodel (self, self.model); + + self.blocked = button_blocked; + self.use = button_use; + + if (self.spawnflags & DOOR_SILVER_KEY) + self.items = IT_KEY1; + if (self.spawnflags & DOOR_GOLD_KEY) + self.items = IT_KEY2; + + if (self.health) + { + self.max_health = self.health; + self.th_die = button_killed; + self.takedamage = DAMAGE_YES; + } + else + self.touch = button_touch; + + if (!self.speed) + self.speed = 40; + if (!self.wait) + self.wait = 1; + if (!self.lip) + self.lip = 4; + + self.state = STATE_BOTTOM; + + if(self.movedir) + { + self.pos1 = self.origin; + self.pos2 = self.pos1 + self.movedir; + self.angles = '0 0 0'; + } + else + { + SetMovedir(); + self.pos1 = self.origin; + self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip); + } + +}; + diff --git a/quakec_mg1/client.qc b/quakec_mg1/client.qc new file mode 100644 index 0000000..c16472b --- /dev/null +++ b/quakec_mg1/client.qc @@ -0,0 +1,2064 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void(entity temp_player) horde_set_keys; // yoder sept24 +void() GibMonster; +float() HordeGetPlayersAlive; + +// prototypes +void () W_WeaponFrame; +void() W_SetCurrentAmmo; +void(entity attacker, float damage) player_pain; +void() player_stand1; +void (vector org) spawn_tfog; +void (vector org, entity death_owner) spawn_tdeath; + +float modelindex_eyes, modelindex_player; + +const float NO_INTERMISSION = 1; + +/* +============================================================================= + + LEVEL CHANGING / INTERMISSION + +============================================================================= +*/ + +float intermission_running; +float intermission_exittime; + +/*QUAKED info_intermission (1 0.5 0.5) (-16 -16 -16) (16 16 16) +This is the camera point for the intermission. +Use mangle instead of angle, so you can set pitch or roll as well as yaw. 'pitch roll yaw' +*/ +void() info_intermission = +{ +}; + + +void intermission_clear_powerups(entity e) +{ + e.items (-) IT_QUAD | IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT; + e.super_damage_finished = 0; + e.super_time = 0; + e.invisible_finished = 0; + e.invisible_time = 0; + e.invincible_finished = 0; + e.invincible_time = 0; + e.radsuit_finished = 0; + e.rad_time = 0; +} + + +float reset_flag; + +void() SetChangeParms = +{ + if(reset_flag) + { + setspawnparms(self); + return; + } + + if (self.health <= 0 || deathmatch) + { + SetNewParms (); + return; + } + + if (world.worldtype == WORLDTYPE_HUB) + { + SetNewParms (); + return; + } + +// remove items + self.items = self.items - (self.items & + (IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD) ); + + // cap super health + if (self.health > self.max_health) + self.health = self.max_health; + if (self.health < self.max_health / 2) + self.health = self.max_health / 2; + // replenish ammo + if (self.ammo_shells < 25) + { + self.ammo_shells = 25; + } + parm1 = self.items; + parm2 = self.health; + parm3 = self.armorvalue; + parm4 = self.ammo_shells; + parm5 = self.ammo_nails; + parm6 = self.ammo_rockets; + parm7 = self.ammo_cells; + parm8 = self.weapon; + parm9 = self.armortype * 100; +}; + +void() SetNewParms = +{ + parm1 = IT_SHOTGUN | IT_AXE; + if (skill == 3 && !deathmatch) + parm2 = 50; + else + parm2 = 100; + parm3 = 0; + parm4 = 25; + parm5 = 0; + parm6 = 0; + parm7 = 0; + parm8 = 1; + parm9 = 0; +}; + +void() DecodeLevelParms = +{ + if (world.model == "maps/start.bsp" || world.worldtype == WORLDTYPE_HUB) + { + SetNewParms (); // take away all stuff on starting new episode + } + + // Yoder Oct25, reset weapons between map loads + if (cvar("horde")) + SetNewParms (); + + self.items = parm1; + self.health = parm2; + self.armorvalue = parm3; + self.ammo_shells = parm4; + self.ammo_nails = parm5; + self.ammo_rockets = parm6; + self.ammo_cells = parm7; + self.weapon = parm8; + self.armortype = parm9 * 0.01; +}; + +/* +============ +FindIntermission + +Returns the entity to view from +============ +*/ +entity() FindIntermission = +{ + local entity spot; + local float cyc; + +// look for info_intermission first + spot = find (world, classname, "info_intermission"); + if (spot) + { // pick a random one + cyc = random() * 4; + while (cyc > 1) + { + spot = find (spot, classname, "info_intermission"); + if (!spot) + spot = find (spot, classname, "info_intermission"); + cyc = cyc - 1; + } + return spot; + } + +// then look for the start position + spot = find (world, classname, "info_player_start"); + if (spot) + return spot; + +// testinfo_player_start is only found in regioned levels + spot = find (world, classname, "testplayerstart"); + if (spot) + return spot; + + objerror ("FindIntermission: no spot"); + return world; +}; + +/* +============ +HORDE random map pick + +added Aug31, 2021 +============ +*/ +string() HordeRandomMap = +{ + /*local float r, loops; + local string newmap; + + + loops = 0; + + while(loops < 4) + { + r = random() * 4; + if (r <= 1) + newmap = "horde1"; + else if (r <= 2) + newmap = "horde2"; + else if (r <= 3) + newmap = "horde3"; + else + newmap = "horde4"; + + if !(mapname == newmap) + return newmap; + else + loops += 1; + } + */ + + //dprint("go with next map in progression\n"); + if (mapname == "horde1") + return "horde2"; + else if (mapname == "horde2") + return "horde3"; + else if (mapname == "horde3") + return "horde4"; + else + return "horde1"; +}; + + +string nextmap; +void() GotoNextMap = +{ + if (cvar("samelevel")) // if samelevel is set, stay on same level + changelevel (mapname); + else + { + if (cvar("horde")) + changelevel(HordeRandomMap()); + else + changelevel (nextmap); + } +}; + + +string intermissiontext; +void() ExitIntermission = +{ +// skip any text in deathmatch + if (deathmatch) + { + GotoNextMap (); + return; + } + + intermission_exittime = time + 1; + intermission_running = intermission_running + 1; + +// +// run some text if at the end of an episode +// + if (intermission_running == 2) + { + if (world.model == "maps/e1m7.bsp") + { + if (!cvar("registered")) + { + intermissiontext = "$qc_finale_e1_shareware"; + } + else + { + intermissiontext = "$qc_finale_e1"; + } + } + else if (world.model == "maps/e2m6.bsp") + { + intermissiontext = "$qc_finale_e2"; + } + else if (world.model == "maps/e3m6.bsp") + { + intermissiontext = "$qc_finale_e3"; + } + else if (world.model == "maps/e4m7.bsp") + { + intermissiontext = "$qc_finale_e4"; + } + else if (world.endtext) + { + intermissiontext = world.endtext; + } + + if (intermissiontext) + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, intermissiontext); + intermissiontext = string_null; + return; + } + + if ( nextmap == "start" && !coop && !cvar("horde")) // Yoder modify Oct25 to check against horde + { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + } + else + { + GotoNextMap(); + } + + } + + if (intermission_running == 3) + { + if (!cvar("registered")) + { // shareware episode has been completed, go to sell screen + WriteByte (MSG_ALL, SVC_SELLSCREEN); + return; + } + + if ( (serverflags & SIGIL_ALL) == SIGIL_ALL) + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_mg1_endtext_all_runes"); + return; + } + + } + + if ( nextmap == "start" && !coop && !cvar("horde")) // Yoder modify Oct25 to check against horde + { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + } + else + { + GotoNextMap(); + } +}; + +/* +============ +IntermissionThink + +When the player presses attack or jump, change to the next level +============ +*/ +void() IntermissionThink = +{ + if (time < intermission_exittime) + return; + + if (!self.button0 && !self.button1 && !self.button2) + return; + + ExitIntermission (); +}; + +void() execute_changelevel = +{ + local entity pos; + + intermission_running = 1; + +// enforce a wait time before allowing changelevel + if (deathmatch) + intermission_exittime = time + 5; + else + intermission_exittime = time + 2; + + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + + pos = FindIntermission (); + + other = find (world, classname, "player"); + while (other != world) + { + other.view_ofs = '0 0 0'; + other.angles = other.v_angle = pos.mangle; + other.fixangle = TRUE; // turn this way immediately + other.nextthink = time + 0.5; + other.takedamage = DAMAGE_NO; + other.solid = SOLID_NOT; + other.movetype = MOVETYPE_NONE; + other.modelindex = 0; + intermission_clear_powerups(other); + setorigin (other, pos.origin); + FogPushSettingsFrom(other, pos, 0); + + other = find (other, classname, "player"); + } + + WriteByte (MSG_ALL, SVC_INTERMISSION); + + if (campaign && world.model == "maps/e5end.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_E5END"); + if (skill == 3) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_E5END_NIGHTMARE"); + } + } + else if (campaign && world.model == "maps/mgend.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_MGEND"); + if (skill == 3) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_MGEND_NIGHTMARE"); + } + } + + if (world.model == "maps/e5m6.bsp" && nextmap == "e5sm2") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_E5M8"); + } + if (world.model == "maps/mge1m1.bsp" && nextmap == "mge1m3") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FIND_MGE1M3"); + } + + // Yoder merge Horde sept24 2021 + if (horde_ent) + { + horde_ent.think = SUB_Null; + horde_ent.nextthink = -1; + + other = find (world, category, CATEGORY_MONSTER); + while (other != world) + { + //void(entity targ, entity inflictor, entity attacker, float damage) + //T_Damage(other, world, world, 4000); + other.think = GibMonster; + other.nextthink = time + 0.2 + random() * 1.8; + other = find(other, category, CATEGORY_MONSTER); + } + } + + if(self.spawnflags & NO_INTERMISSION) + { + //Go directly to intermission text. + ExitIntermission(); + } +}; + + +void() changelevel_touch = +{ + if (other.classname != "player") + return; + + if ((cvar("noexit") == 1) || ((cvar("noexit") == 2) && (mapname != "start"))) + { + T_Damage (other, self, self, 50000); + return; + } + + if (coop || deathmatch) + { + bprint("$qc_exited", other.netname); + } + + nextmap = self.map; + intermissiontext = self.endtext; + + if(world.model == "maps/mgend.bsp") + { + //Finished the whole pack, clear the runes. + serverflags = 0; + } + + SUB_UseTargets (); + + if ( (self.spawnflags & NO_INTERMISSION) && (deathmatch == 0) && (self.endtext == string_null)) + { + GotoNextMap(); + return; + } + + self.touch = SUB_Null; + +// we can't move people right now, because touch functions are called +// in the middle of C movement code, so set a think time to do it + self.think = execute_changelevel; + self.nextthink = time + 0.1; +}; + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ +void() trigger_changelevel = +{ + if (!self.map) + objerror ("changelevel trigger doesn't have map"); + + InitTrigger (); + self.touch = changelevel_touch; +}; + + +/* +============================================================================= + + PLAYER GAME EDGE FUNCTIONS + +============================================================================= +*/ + +void LoadGame() +{ + dprint("\n\nLoadGame: self.classname = "); + dprint(self.classname); + dprint("\n\n"); + + //For each player... + entity cl = find(world, classname, "player"); + while(cl) + { + if(cl.fog_density) + { + //Restore fog + SetFog(cl, cl.fog_density, cl.fog_color, 0.0); + } + cl = find(cl, classname, "player"); + } +} + +void() set_suicide_frame; + +// called by ClientKill and DeadThink +void() respawn = +{ + self.velocity = '0 0 0'; + if (coop) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue (self); + // get the spawn parms as they were at level start + setspawnparms (self); + // respawn + PutClientInServer (); + } + else if (deathmatch) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue (self); + // set default spawn parms + SetNewParms (); + // respawn + PutClientInServer (); + } + else + { // restart the entire server + // put serverflags back to map's initial setting + serverflags = startingserverflags; + // request a reset to the parms + reset_flag = TRUE; + // "change" to the same level + localcmd ("changelevel "); + localcmd(mapname); + localcmd("\n"); + } +}; + +// Yoder, Sept24, 2021 +// when in horde coop, respawn a dead teammate with this: +void() horde_respawn_teammate = +{ + CopyToBodyQue (self); + //setspawnparms (self); + //SetNewParms(); + PutClientInServer (); +}; + +// Yoder, Sept24, 2021 +// when in horde (coop or solo), respawn all by restarting the server: +void() horde_respawn_all = +{ + // restart the entire server + // put serverflags back to map's initial setting + serverflags = startingserverflags; + // request a reset to the parms + reset_flag = TRUE; + // "change" to the same level + localcmd ("changelevel "); + localcmd(mapname); + localcmd("\n"); +}; + +void PlayerDie(); +/* +============ +ClientKill + +Player entered the suicide command +============ +*/ +void() ClientKill = +{ + bprint("$qc_suicides", self.netname); + if (cvar("horde")) // horde mode behavior + { + PlayerDie(); + self.health = 0; + } + else // standard behavior + { + set_suicide_frame (); + self.modelindex = modelindex_player; + self.frags = self.frags - 2; // extra penalty + respawn (); + } +}; + +float(vector v) CheckSpawnPoint = +{ + return FALSE; +}; + +/* +============ +PlayerVisibleToSpawnPoint + +Returns true if player can see this point +============ +*/ +float PlayerVisibleToSpawnPoint( entity point ) { + local vector spot1, spot2; + local entity player = find( world, classname, "player" ); + while ( player ) { + if ( player.health > 0 ) { + spot1 = point.origin + player.view_ofs; + spot2 = player.origin + player.view_ofs; + + traceline( spot1, spot2, TRUE, point ); + if ( trace_fraction >= 1.0f ) { + return TRUE; + } + } + + player = find( player, classname, "player" ); + } + + return FALSE; +} + +float IDEAL_DIST_FROM_DM_SPAWN_POINT = 384; +float MIN_DIST_FROM_DM_SPAWN_POINT = 84; +float MIN_DIST_FROM_HORDE_SPAWN_POINT = 72; + +float predicate_is_active_spawnpoint(entity e) +{ + return (e.state == COOP_SPAWN_ACTIVE); +} + +/* +============ +SelectSpawnPoint + +Returns the entity to spawn at +============ +*/ +entity SelectSpawnPoint(float forceSpawn) { + local entity spot, thing; + local float numspots, totalspots; + local float pcount; + local entity spots; + local entity hordeSpawn; + + numspots = 0; + totalspots = 0; + + // testinfo_player_start is only found in regioned levels + spot = find( world, classname, "testplayerstart" ); + if ( spot ) + return spot; + + if ( cvar( "horde" ) ) { // run simpler logic for horde - just need a spot with noone on top of it... + hordeSpawn = find( world, classname, "info_player_coop" ); + + while( hordeSpawn ) { + thing = findradius( hordeSpawn.origin, MIN_DIST_FROM_HORDE_SPAWN_POINT ); + + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount++; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { + return hordeSpawn; + } + + // Get the next spot in the chain + hordeSpawn = find( hordeSpawn, classname, "info_player_coop" ); + } + + if (coop) // fix for singleplayer + return world; // didn't find anything.... + } else if ( coop ) { + // choose a info_player_coop point that is active + entity startedAt = lastspawn; + lastspawn = find( lastspawn, classname, "info_player_coop" ); + while( lastspawn.state != COOP_SPAWN_ACTIVE ) { + if ( lastspawn == startedAt ) { + break; + } + lastspawn = find(lastspawn, classname, "info_player_coop"); + } + + if ( lastspawn != world ) { + return lastspawn; + } + } else if ( deathmatch ) { + + // find all spots that don't have visible players nearby + spots = world; + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + totalspots = totalspots + 1; + + thing = findradius( spot.origin, IDEAL_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { + if ( PlayerVisibleToSpawnPoint( spot ) ) { + pcount = pcount + 1; + } + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + + totalspots = totalspots - 1; + + // on small maps with few spawn points, our "ideal" spawn conditions may not be possible to meet + // so fallback to just trying to pick a point without a player on top of it, so we don't start + // a spawn frag loop + if ( numspots == 0 ) { + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + thing = findradius( spot.origin, MIN_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + } + + // uncomment to force a deferred spawn + // if (forceSpawn == FALSE) return world; + + if ( !numspots ) { + if (forceSpawn == FALSE) { + return world; + } + + // no spots available so just pick one at random + totalspots = rint( ( random() * totalspots ) ); + spot = find( world, classname, "info_player_deathmatch" ); + + while( totalspots > 0 ) { + totalspots = totalspots - 1; + spot = find( spot, classname, "info_player_deathmatch" ); + } + return spot; + } + + // Generate a random number between 1 and numspots + numspots = numspots - 1; + + numspots = rint( ( random() * numspots ) ); + + spot = spots; + while( numspots > 0 ) { + spot = spot.goalentity; + numspots = numspots - 1; + } + + return spot; + } + + if (serverflags) + { // return with a rune to start + float lastPickup = sigil_getLastPickup(); + if(lastPickup) + { + string spname = string_null; + if(lastPickup & SIGIL_E1) spname = "start_1"; + else if(lastPickup & SIGIL_E2) spname = "start_2"; + else if(lastPickup & SIGIL_E3) spname = "start_3"; + else if(lastPickup & SIGIL_E4) spname = "start_4"; + else if(lastPickup & SIGIL_E5) spname = "start_5"; + else if(lastPickup & SIGIL_E6) spname = "start_6"; + if(spname) + { + spot = find (world, netname, spname); + if(spot) + { + if(spot.classname == "info_player_start_hub") + { + sigil_clearLastPickup(); + return spot; + } + } + } + } + spot = find (world, classname, "info_player_start2"); + if (spot) + return spot; + } + + spot = find (world, classname, "info_player_start"); + if (!spot) + error ("PutClientInServer: no info_player_start on level"); + + return spot; +}; + +/* +=========== +PutClientInServer + +called each time a player is spawned +============ +*/ +void() DecodeLevelParms; +void() PlayerDie; + + +void RunPostPutClientInServer() +{ + entity oself = self; + self = self.owner; + entity spot = oself.enemy; + + // Yoder Sept24, 2021 + horde_set_keys(self); // reset player's key count accordingly + + // Fog settings sometimes (?) need to be set a frame after the client has spawned. + // Not sure why, or why it sometimes works on the same frame. + if(!FogPushSettingsFrom(self, spot, 0)) + { + FogPushSettingsFrom(self, world, 0); + } + + remove(oself); +} + +void() PutClientInServer = +{ + local entity spot; + + self.classname = "player"; + if (skill == 3 && !deathmatch) + self.health = 50; + else + self.health = 100; + self.takedamage = DAMAGE_AIM; + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_WALK; + self.show_hostile = 0; + if (skill == 3 && !deathmatch) + self.max_health = 50; + else + self.max_health = 100; + self.flags = FL_CLIENT; + self.air_finished = time + 12; + self.dmg = 2; // initial water damage + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.invisible_finished = 0; + self.invincible_finished = 0; + self.effects = 0; + self.invincible_time = 0; + + if ( coop ) { + self.team = TEAM_HUMANS; + } + + DecodeLevelParms (); + + W_SetCurrentAmmo (); + + self.attack_finished = time; + self.th_pain = player_pain; + self.th_die = PlayerDie; + + self.deadflag = DEAD_NO; +// paustime is set by teleporters to keep the player from moving a while + self.pausetime = 0; + + + local float shouldTelefrag; + + if (self.spawn_deferred > 0 && time >= self.spawn_deferred) { + dprint("forcing telefrag on this spawn\n"); + shouldTelefrag = TRUE; + } else { + shouldTelefrag = FALSE; + } + spot = SelectSpawnPoint(shouldTelefrag); + if (spot == world) { + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + self.deadflag = DEAD_DEAD; + setmodel(self, ""); + self.view_ofs = '0 0 1'; // not 0 because PlayerPreThink would return out + self.velocity = '0 0 0'; + + if (self.spawn_deferred == 0) { + dprint("no spawns available! deferring\n"); + self.spawn_deferred = time + 5; + } + + spot = FindIntermission(); + self.angles = self.v_angle = spot.mangle; + self.fixangle = TRUE; + self.origin = spot.origin; + self.weaponmodel = ""; + self.weaponframe = 0; + self.weapon = 0; + + return; + } + + self.spawn_deferred = 0; + + self.origin = spot.origin + '0 0 1'; + self.angles = spot.angles; + self.fixangle = TRUE; // turn this way immediately + +#ifdef COOP_RESPAWN_KEEP_WEAPONS + if(coop) + { + // In coop we want to give back the players the weapons they progressed with + self.items |= (spot.items & IT_ALL_WEAPONS); + if(self.items & (IT_NAILGUN | IT_SUPER_NAILGUN)) self.ammo_nails = 30; + if(self.items & (IT_GRENADE_LAUNCHER | IT_ROCKET_LAUNCHER)) self.ammo_rockets = 4; + if(self.items & IT_LIGHTNING) self.ammo_cells = 12; + } +#endif + + // oh, this is a hack! + setmodel (self, "progs/eyes.mdl"); + modelindex_eyes = self.modelindex; + + setmodel (self, "progs/player.mdl"); + modelindex_player = self.modelindex; + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + + self.view_ofs = '0 0 22'; + + player_stand1 (); + + if (deathmatch || coop) + { + makevectors(self.angles); + spawn_tfog (self.origin + v_forward*20); + } + + spawn_tdeath (self.origin, self); + + entity postSpawn = spawn(); + postSpawn.owner = self; + postSpawn.enemy = spot; + postSpawn.think = RunPostPutClientInServer; + postSpawn.nextthink = time + 0.05; + + stuffcmd(self, "-attack\n"); // prevent shooting after respawning +}; + + +/* +============================================================================= + + QUAKED FUNCTIONS + +============================================================================= +*/ + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 24) +The normal starting point for a level. +*/ +void() info_player_start = +{ + self.netname = "info_player_start"; // for bot nav support. +}; + + +/*QUAKED info_player_start2 (1 0 0) (-16 -16 -24) (16 16 24) +Only used on start map for the return point from an episode. +*/ +void() info_player_start2 = +{ + self.netname = "info_player_start2"; // for bot nav support. +}; + +/*QUAKED info_player_start_hub (1 0 0) (-16 -16 -24) (16 16 24) +Only used on start map for the return point from an episode. +*/ +void() info_player_start_hub = +{ +}; + + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for deathmatch games +*/ +void() info_player_deathmatch = +{ + if(!deathmatch) + { + remove(self); + return; + } + self.netname = "info_player_deathmatch"; // for bot nav support. +}; + +////////////////////////////////////////////////////////////////////////// + +void trigger_activate_coop_spawns_use() +{ + float it = 0; + +#ifdef COOP_RESPAWN_KEEP_WEAPONS + entity p = find (world, classname, "player"); + while (p != world) + { + it |= p.items; + p = find (p, classname, "player"); + } + it &= IT_ALL_WEAPONS; + + dprint("Activating coop spawn \""); + dprint(self.target); + dprint("\": Collected "); + dprint(ftos(it)); + dprint(" from players.\n"); +#endif + + entity s = find(world, classname, "info_player_coop"); + while(s) + { + if(s.targetname == self.target) + { + s.state = COOP_SPAWN_ACTIVE; + s.items = it; + } + else + { + s.state = 0; + s.items = 0; + } + s = find(s, classname, "info_player_coop"); + } +} + +/*QUAKED trigger_activate_coop_spawns (1 0 1) (-8 -8 -8) (8 8 8) +Activates a set of coop spawnpoints. +*/ +void trigger_activate_coop_spawns() +{ + if(!coop) + { + remove(self); + return; + } + self.use = trigger_activate_coop_spawns_use; +} + +const float COOP_SPAWN_START_ACTIVE = 1; +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for coop games +*/ +void() info_player_coop = +{ + if(!coop) + { + remove(self); + return; + } + self.netname = "info_player_coop"; // for bot nav support. + if(!self.targetname) self.state = COOP_SPAWN_ACTIVE; + if(self.spawnflags & COOP_SPAWN_START_ACTIVE) self.state = COOP_SPAWN_ACTIVE; +}; + +/* +=============================================================================== + +RULES + +=============================================================================== +*/ + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void() NextLevel = +{ + local entity o; + + if (mapname == "start") + { + if (!cvar("registered")) + { + mapname = "e1m1"; + } + else if (!(serverflags & 1)) + { + mapname = "e1m1"; + serverflags = serverflags | 1; + } + else if (!(serverflags & 2)) + { + mapname = "e2m1"; + serverflags = serverflags | 2; + } + else if (!(serverflags & 4)) + { + mapname = "e3m1"; + serverflags = serverflags | 4; + } + else if (!(serverflags & 8)) + { + mapname = "e4m1"; + serverflags = serverflags - 7; + } + + o = spawn(); + o.map = mapname; + } + else if (world.model == "maps/mgdm1.bsp") + { + o = spawn(); + o.map = "mgdm2"; + } + else if (world.model == "maps/mgdm2.bsp") + { + o = spawn(); + o.map = "mgdm3"; + } + else if (world.model == "maps/mgdm3.bsp") + { + o = spawn(); + o.map = "mgdm4"; + } + else if (world.model == "maps/mgdm4.bsp") + { + o = spawn(); + o.map = "mgdm1"; + } + else + { + // find a trigger changelevel + o = find(world, classname, "trigger_changelevel"); + + // Stay on same level if no changelevel is found + if (!o) + { + o = spawn(); + o.map = mapname; + } + } + + nextmap = o.map; + gameover = TRUE; + + if (o.nextthink < time) + { + o.think = execute_changelevel; + o.nextthink = time + 0.1; + } +}; + +/* +============ +CheckRules + +Exit deathmatch games upon conditions +============ +*/ +void() CheckRules = +{ + local float timelimit; + local float fraglimit; + + if (gameover) // someone else quit the game already + return; + + timelimit = cvar("timelimit") * 60; + fraglimit = cvar("fraglimit"); + + if (timelimit && time >= timelimit) + { + NextLevel (); + return; + } + + if (fraglimit && self.frags >= fraglimit) + { + NextLevel (); + return; + } +}; + +//============================================================================ + +void() PlayerDeathThink = +{ + local float forward; + + if ((self.flags & FL_ONGROUND)) + { + forward = vlen (self.velocity); + forward = forward - 20; + if (forward <= 0) + self.velocity = '0 0 0'; + else + self.velocity = forward * normalize(self.velocity); + } + + if (self.spawn_deferred) + { + local entity spot; + + spot = SelectSpawnPoint(FALSE); + //dprint("time {} >= self.spawn_deferred {}\n", ftos(time), ftos(self.spawn_deferred)); + if (spot != world || time >= self.spawn_deferred) { + respawn(); + } + + return; + } + + +// wait for all buttons released + if (self.deadflag == DEAD_DEAD) + { + if (self.button2 || self.button1 || self.button0) + return; + self.deadflag = DEAD_RESPAWNABLE; + return; + } + +// wait for any button down + if (!self.button2 && !self.button1 && !self.button0) + return; + + self.button0 = 0; + self.button1 = 0; + self.button2 = 0; + + // Yoder Sept24, 2021 + if (horde_ent) + { + if ((coop && HordeGetPlayersAlive() <= 0) || (!coop)) + { + horde_respawn_all(); + } + return; + } + + respawn(); +}; + + +void() PlayerJump = +{ + if (self.flags & FL_WATERJUMP) + return; + + if (self.waterlevel >= 2) + { + if (self.watertype == CONTENT_WATER) + self.velocity_z = 100; + else if (self.watertype == CONTENT_SLIME) + self.velocity_z = 80; + else + self.velocity_z = 50; + +// play swiming sound + if (self.swim_flag < time) + { + self.swim_flag = time + 1; + if (random() < 0.5) + sound (self, CHAN_BODY, "misc/water1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_BODY, "misc/water2.wav", 1, ATTN_NORM); + } + + return; + } + + if (!(self.flags & FL_ONGROUND)) + return; + + if ( !(self.flags & FL_JUMPRELEASED) ) + return; // don't pogo stick + + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + + self.flags = self.flags - FL_ONGROUND; // don't stairwalk + + self.button2 = 0; +// player jumping sound + sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM); + self.velocity_z = self.velocity_z + 270; +}; + + +/* +=========== +WaterMove + +============ +*/ +.float dmgtime; + +void() WaterMove = +{ +//dprint (ftos(self.waterlevel)); + if (self.movetype == MOVETYPE_NOCLIP) + return; + if (self.health < 0) + return; + + if (self.waterlevel != 3) + { + if (self.air_finished < time) + sound (self, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM); + else if (self.air_finished < time + 9) + sound (self, CHAN_VOICE, "player/gasp1.wav", 1, ATTN_NORM); + self.air_finished = time + 12; + self.dmg = 2; + } + else if (self.air_finished < time) + { // drown! + if (self.pain_finished < time) + { + self.dmg = self.dmg + 2; + if (self.dmg > 15) + self.dmg = 10; + T_Damage (self, world, world, self.dmg); + self.pain_finished = time + 1; + } + } + + if (!self.waterlevel) + { + if (self.flags & FL_INWATER) + { + // play leave water sound + sound (self, CHAN_BODY, "misc/outwater.wav", 1, ATTN_NORM); + self.flags = self.flags - FL_INWATER; + } + return; + } + + if (self.watertype == CONTENT_LAVA) + { // do damage + if (self.dmgtime < time) + { + if (self.radsuit_finished > time) + self.dmgtime = time + 1; + else + self.dmgtime = time + 0.2; + + T_Damage (self, world, world, 10*self.waterlevel); + } + } + else if (self.watertype == CONTENT_SLIME) + { // do damage + if (self.dmgtime < time && self.radsuit_finished < time) + { + self.dmgtime = time + 1; + T_Damage (self, world, world, 4*self.waterlevel); + } + } + + if ( !(self.flags & FL_INWATER) ) + { + +// player enter water sound + + if (self.watertype == CONTENT_LAVA) + sound (self, CHAN_BODY, "player/inlava.wav", 1, ATTN_NORM); + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/inh2o.wav", 1, ATTN_NORM); + if (self.watertype == CONTENT_SLIME) + sound (self, CHAN_BODY, "player/slimbrn2.wav", 1, ATTN_NORM); + + self.flags = self.flags + FL_INWATER; + self.dmgtime = 0; + } + + if (! (self.flags & FL_WATERJUMP) ) + self.velocity = self.velocity - 0.8*self.waterlevel*frametime*self.velocity; +}; + +void() CheckWaterJump = +{ + local vector start, end; + +// check for a jump-out-of-water + makevectors (self.angles); + start = self.origin; + start_z = start_z + 8; + v_forward_z = 0; + normalize(v_forward); + end = start + v_forward*24; + traceline (start, end, TRUE, self); + if (trace_fraction < 1) + { // solid at waist + start_z = start_z + self.maxs_z - 8; + end = start + v_forward*24; + self.movedir = trace_plane_normal * -50; + traceline (start, end, TRUE, self); + if (trace_fraction == 1) + { // open at eye level + self.flags = self.flags | FL_WATERJUMP; + self.velocity_z = 225; + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + self.teleport_time = time + 2; // safety net + return; + } + } +}; + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void() PlayerPreThink = +{ + if (intermission_running) + { + IntermissionThink (); // otherwise a button could be missed between + return; // the think tics + } + + if (self.view_ofs == '0 0 0') + return; // intermission or finale + + makevectors (self.v_angle); // is this still used + + CheckRules (); + WaterMove (); + + if (self.waterlevel == 2) + CheckWaterJump (); + + if (self.deadflag >= DEAD_DEAD) + { + PlayerDeathThink (); + return; + } + + if (self.deadflag == DEAD_DYING) + return; // dying, so do nothing + + if (self.button2) + { + PlayerJump (); + } + else + self.flags = self.flags | FL_JUMPRELEASED; + +// teleporters can force a non-moving pause time + if (time < self.pausetime) + self.velocity = '0 0 0'; + + if(time > self.attack_finished && self.currentammo == 0 && self.weapon != IT_AXE) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + } +}; + +/* +================ +CheckPowerups + +Check for turning off powerups +================ +*/ +void() CheckPowerups = +{ + if (self.health <= 0) + return; + +// invisibility + if (self.invisible_finished) + { +// sound and screen flash when items starts to run out + if (self.invisible_sound < time) + { + sound (self, CHAN_AUTO, "items/inv3.wav", 0.5, ATTN_IDLE); + self.invisible_sound = time + ((random() * 3) + 1); + } + + + if (self.invisible_finished < time + 3) + { + if (self.invisible_time == 1) + { + sprint(self, "$qc_ring_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/inv2.wav", 1, ATTN_NORM); + self.invisible_time = time + 1; + } + + if (self.invisible_time < time) + { + self.invisible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invisible_finished < time) + { // just stopped + self.items = self.items - IT_INVISIBILITY; + self.invisible_finished = 0; + self.invisible_time = 0; + } + + // use the eyes + self.frame = 0; + self.modelindex = modelindex_eyes; + } + else + self.modelindex = modelindex_player; // don't use eyes + +// invincibility + if (self.invincible_finished) + { +// sound and screen flash when items starts to run out + if (self.invincible_finished < time + 3) + { + if (self.invincible_time == 1) + { + sprint (self, "$qc_protection_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/protect2.wav", 1, ATTN_NORM); + self.invincible_time = time + 1; + } + + if (self.invincible_time < time) + { + self.invincible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invincible_finished < time) + { // just stopped + self.items = self.items - IT_INVULNERABILITY; + self.invincible_time = 0; + self.invincible_finished = 0; + } + if (self.invincible_finished > time) + self.effects = self.effects | EF_PENTLIGHT; + else + self.effects = self.effects - (self.effects & EF_PENTLIGHT); + } + +// super damage + if (self.super_damage_finished) + { + +// sound and screen flash when items starts to run out + + if (self.super_damage_finished < time + 3) + { + if (self.super_time == 1) + { + sprint (self, "$qc_quad_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/damage2.wav", 1, ATTN_NORM); + self.super_time = time + 1; + } + + if (self.super_time < time) + { + self.super_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.super_damage_finished < time) + { // just stopped + self.items = self.items - IT_QUAD; + self.super_damage_finished = 0; + self.super_time = 0; + } + if (self.super_damage_finished > time) + self.effects = self.effects | EF_QUADLIGHT; + else + self.effects = self.effects - (self.effects & EF_QUADLIGHT); + } + +// suit + if (self.radsuit_finished) + { + self.air_finished = time + 12; // don't drown + +// sound and screen flash when items starts to run out + if (self.radsuit_finished < time + 3) + { + if (self.rad_time == 1) + { + sprint (self, "$qc_biosuit_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/suit2.wav", 1, ATTN_NORM); + self.rad_time = time + 1; + } + + if (self.rad_time < time) + { + self.rad_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.radsuit_finished < time) + { // just stopped + self.items = self.items - IT_SUIT; + self.rad_time = 0; + self.radsuit_finished = 0; + } + } + +}; + + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void() PlayerPostThink = +{ + if (self.view_ofs == '0 0 0') + return; // intermission or finale + if (self.deadflag) + return; + +// do weapon stuff + + W_WeaponFrame (); + +// check to see if player landed and play landing sound + if ((self.jump_flag < -300) && (self.flags & FL_ONGROUND) && (self.health > 0)) + { + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM); + else if (self.jump_flag < -650) + { + T_Damage (self, world, world, 5); + sound (self, CHAN_VOICE, "player/land2.wav", 1, ATTN_NORM); + self.deathtype = "falling"; + } + else + sound (self, CHAN_VOICE, "player/land.wav", 1, ATTN_NORM); + + self.jump_flag = 0; + } + + if (!(self.flags & FL_ONGROUND)) + self.jump_flag = self.velocity_z; + + // AY 11 Nov 2021, check killspree end + local float score; + if ((self.killspree > 0) && (time > self.killtime)) + { + dprint("killspree ended.\n"); + + //score = (self.killspree + (self.killspree - 1) -1) * 5; // previously was "attacker.killspree - 1" + if (self.killspree > 1) + { + score = ceil((self.killspree * self.killspree)/2); + sprint(self, "$qc_horde_streak_ended", ftos(score)); + self.frags += score; + } + + self.killspree = 0; // reset killspree + } + + CheckPowerups (); +}; + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +void() ClientConnect = +{ + bprint("$qc_entered", self.netname); + + num_players++; + + // a client connecting during an intermission can cause problems + if (intermission_running) + ExitIntermission (); +}; + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server +============ +*/ +void() ClientDisconnect = +{ + // if the level end trigger has been activated, just return + // since they aren't *really* leaving + if (gameover) + { + return; + } + + num_players--; + + // yoder oct13, horde fix for dc-ing players + if(cvar("horde")) + { + dprint("horde mode, player disconnect\n"); + self.health = 0; + } + + // let everyone else know + bprint("$qc_left_game", self.netname, ftos(self.frags)); + sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE); + set_suicide_frame (); +}; + +/* +=========== +ClientObituary + +called when a player dies +============ +*/ +void(entity targ, entity attacker) ClientObituary = +{ + local float rnum; + rnum = random(); + + if (targ.classname == "player") + { + if (attacker.classname == "teledeath") + { + bprint("$qc_telefragged", targ.netname, attacker.owner.netname); + + attacker.owner.frags = attacker.owner.frags + 1; + return; + } + + if (attacker.classname == "teledeath2") + { + bprint("$qc_satans_power", targ.netname); + + targ.frags = targ.frags - 1; + return; + } + + if (attacker.classname == "player") + { + if (targ == attacker) + { + // killed self + attacker.frags = attacker.frags - 1; + + if (targ.weapon == 64 && targ.waterlevel > 1) + { + if (targ.watertype == CONTENT_SLIME) + bprint("$qc_discharge_slime", targ.netname); + + else if (targ.watertype == CONTENT_LAVA) + bprint("$qc_discharge_lava", targ.netname); + + else + bprint("$qc_discharge_water", targ.netname); + + return; + } + if (targ.weapon == IT_GRENADE_LAUNCHER) + { + bprint("$qc_suicide_pin", targ.netname); + } + else + { + bprint("$qc_suicide_bored", targ.netname); + } + return; + } + else if ( (teamplay == 2) && (targ.team > 0)&&(targ.team == attacker.team) ) + { + if (rnum < 0.25) + bprint("$qc_ff_teammate", attacker.netname); + else if (rnum < 0.50) + bprint("$qc_ff_glasses", attacker.netname); + else if (rnum < 0.75) + bprint("$qc_ff_otherteam", attacker.netname); + else + bprint("$qc_ff_friend", attacker.netname); + attacker.frags = attacker.frags - 1; + return; + } + else + { + attacker.frags = attacker.frags + 1; + + rnum = attacker.weapon; + if (rnum == IT_AXE) + { + bprint("$qc_death_ax", targ.netname, attacker.netname); + return; + } + if (rnum == IT_SHOTGUN) + { + bprint("$qc_death_sg", targ.netname, attacker.netname); + return; + } + if (rnum == IT_SUPER_SHOTGUN) + { + bprint("$qc_death_dbl", targ.netname, attacker.netname); + return; + } + if (rnum == IT_NAILGUN) + { + bprint("$qc_death_nail", targ.netname, attacker.netname); + return; + } + if (rnum == IT_SUPER_NAILGUN) + { + bprint("$qc_death_sng", targ.netname, attacker.netname); + return; + } + if (rnum == IT_GRENADE_LAUNCHER) + { + if (targ.health < -40) + { + bprint("$qc_death_gl1", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_gl2", targ.netname, attacker.netname); + return; + } + } + if (rnum == IT_ROCKET_LAUNCHER) + { + if (targ.health < -40) + { + bprint("$qc_death_rl2", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_rl3", targ.netname, attacker.netname); + return; + } + } + if (rnum == IT_LIGHTNING) + { + if (attacker.waterlevel > 1) + { + bprint("$qc_death_lg1", targ.netname, attacker.netname); + if (attacker.invincible_finished) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SURVIVE_DISCHARGE"); + } + } + else + bprint("$qc_death_lg2", targ.netname, attacker.netname); + return; + } + } + return; + } + else + { + targ.frags = targ.frags - 1; + + // killed by a montser? + if (attacker.flags & FL_MONSTER) + { + if (attacker.classname == "monster_army") + bprint ("$qc_ks_grunt", targ.netname); + if (attacker.classname == "monster_demon1") + bprint ("$qc_ks_fiend", targ.netname); + if (attacker.classname == "monster_dog") + bprint ("$qc_ks_rottweiler", targ.netname); + if (attacker.classname == "monster_enforcer") + bprint ("$qc_ks_enforcer", targ.netname); + if (attacker.classname == "monster_fish") + bprint ("$qc_ks_rotfish", targ.netname); + if (attacker.classname == "monster_hell_knight") + bprint ("$qc_ks_deathknight", targ.netname); + if (attacker.classname == "monster_knight") + bprint ("$qc_ks_knight", targ.netname); + if (attacker.classname == "monster_ogre") + bprint ("$qc_ks_ogre", targ.netname); + if (attacker.classname == "monster_shalrath") + bprint ("$qc_ks_vore", targ.netname); + if (attacker.classname == "monster_shambler") + bprint ("$qc_ks_shambler", targ.netname); + if (attacker.classname == "monster_tarbaby") + bprint ("$qc_ks_spawn", targ.netname); + if (attacker.classname == "monster_wizard") + bprint ("$qc_ks_scrag", targ.netname); + if (attacker.classname == "monster_zombie") + bprint ("$qc_ks_zombie", targ.netname); + + return; + } + + // tricks and traps + if (attacker.classname == "explo_box") + { + + bprint ("$qc_ks_blew_up", targ.netname); + return; + } + if (attacker.solid == SOLID_BSP && attacker != world) + { + bprint ("$qc_death_squish", targ.netname); + return; + } + if (attacker.classname == "trap_shooter" || attacker.classname == "trap_spikeshooter") + { + bprint ("$qc_ks_spiked", targ.netname); + return; + } + if (attacker.classname == "fireball") + { + bprint ("$qc_ks_lavaball", targ.netname); + return; + } + if (attacker.classname == "trigger_changelevel") + { + bprint ("$qc_ks_tried_leave", targ.netname); + return; + } + + // in-water deaths + rnum = targ.watertype; + if (rnum == -3) + { + if (random() < 0.5) + bprint("$qc_death_drown1", targ.netname); + else + bprint("$qc_death_drown2", targ.netname); + return; + } + else if (rnum == -4) + { + if (random() < 0.5) + bprint("$qc_death_slime1", targ.netname); + else + bprint("$qc_death_slime2", targ.netname); + return; + } + else if (rnum == -5) + { + if (targ.health < -15) + { + bprint("$qc_death_lava1", targ.netname); + return; + } + if (random() < 0.5) + bprint("$qc_death_lava2", targ.netname); + else + bprint("$qc_death_lava3", targ.netname); + return; + } + + // fell to their death? + if (targ.deathtype == "falling") + { + bprint("$qc_death_fall", targ.netname); + return; + } + + // hell if I know; he's just dead!!! + bprint("$qc_death_died", targ.netname); + } + } +}; diff --git a/quakec_mg1/combat.qc b/quakec_mg1/combat.qc new file mode 100644 index 0000000..0187163 --- /dev/null +++ b/quakec_mg1/combat.qc @@ -0,0 +1,458 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Yoder Sept24 2021 Horde Merge +void() horde_spawn_powerup; + +void() T_MissileTouch; +void() info_player_start; +void(entity targ, entity attacker) ClientObituary; + +void() monster_death_use; + +//============================================================================ + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +float(entity targ, entity inflictor) CanDamage = +{ +// bmodels need special checking because their origin is 0,0,0 + if (targ.movetype == MOVETYPE_PUSH) + { + traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self); + if (trace_fraction == 1) + return TRUE; + if (trace_ent == targ) + return TRUE; + return FALSE; + } + + traceline(inflictor.origin, targ.origin, TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '15 -15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + + return FALSE; +}; + + +/* +============ +Killed +============ +*/ +void(entity targ, entity attacker) Killed = +{ + local entity oself; + + oself = self; + self = targ; + + if (self.health < -99) + self.health = -99; // don't let sbar look bad if a player + + if (self.movetype == MOVETYPE_PUSH || self.movetype == MOVETYPE_NONE) + { // doors, triggers, etc + if (self.classname == "misc_explobox" || self.classname == "misc_explobox2") // yoder add, 27/09/202 to let the SUB_UseTarges work + self.enemy = attacker; + self.th_die (); + self = oself; + return; + } + + self.enemy = attacker; + + // Yoder March04 2022, punish team killspree + if (cvar("horde") && (attacker.classname) == "player" && (targ.classname == "player")) + { + dprint("punish teamkill\n"); + attacker.killtime = 0; + attacker.frags -= 2; + } + +// bump the monster counter + if (self.flags & FL_MONSTER) + { + // Yoder Sept24, 2021 Horde Merge + if (horde_ent) + { + if (self.classname != "monster_zombie") // zombies don't count + { + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); + } + + // check kill spree + if (attacker.classname == "player") + { + attacker.killspree++; + attacker.killtime = time + KILLSPREE_GAP; + + if (time < attacker.killtime) + { + if (attacker.killspree >= 14) + sprint(attacker, "$qc_horde_streak_generic", ftos(attacker.killspree)); + else if (attacker.killspree == 13) + sprint(attacker, "$qc_horde_streak_13"); + else if (attacker.killspree == 12) + sprint(attacker, "$qc_horde_streak_12"); + else if (attacker.killspree == 11) + sprint(attacker, "$qc_horde_streak_11"); + else if (attacker.killspree == 10) + sprint(attacker, "$qc_horde_streak_10"); + else if (attacker.killspree == 9) + sprint(attacker, "$qc_horde_streak_9"); + else if (attacker.killspree == 8) + sprint(attacker, "$qc_horde_streak_8"); + else if (attacker.killspree == 7) + sprint(attacker, "$qc_horde_streak_7"); + else if (attacker.killspree == 6) + sprint(attacker, "$qc_horde_streak_6"); + else if (attacker.killspree == 5) + sprint(attacker, "$qc_horde_streak_5"); + else if (attacker.killspree == 4) + sprint(attacker, "$qc_horde_streak_4"); + else if (attacker.killspree == 3) + sprint(attacker, "$qc_horde_streak_3"); + else if (attacker.killspree == 2) + sprint(attacker, "$qc_horde_streak_2"); + } + } + + // check to see about spawning a powerup + horde_spawn_powerup(); + } + else // standard behavior + { + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); + } + if (attacker.classname == "player") + attacker.frags = attacker.frags + 1; + + if (attacker != self && attacker.flags & FL_MONSTER) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FRIENDLY_FIRE"); + } + } + + ClientObituary(self, attacker); + + self.takedamage = DAMAGE_NO; + self.touch = SUB_Null; + + monster_death_use(); + self.th_die (); + + self = oself; +}; + + +/* +============ +T_Damage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +============ +*/ +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage= +{ + local vector dir; + local entity oldself; + local float save; + local float take; + + if (!targ.takedamage) + return; + + // Yoder add, September 9 2020 + if (targ.is_frozen) + return; + +// used by buttons and triggers to set activator for target firing + damage_attacker = attacker; + +// check for quad damage powerup on the attacker + if (attacker.super_damage_finished > time) + damage = damage * 4; + +// save damage based on the target's armor level + + save = ceil(targ.armortype*damage); + if (save >= targ.armorvalue) + { + save = targ.armorvalue; + targ.armortype = 0; // lost all armor + targ.items = targ.items - (targ.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)); + } + + targ.armorvalue = targ.armorvalue - save; + take = ceil(damage-save); + +// add to the damage total for clients, which will be sent as a single +// message at the end of the frame + if (targ.flags & FL_CLIENT) + { + targ.dmg_take = targ.dmg_take + take; + targ.dmg_save = targ.dmg_save + save; + targ.dmg_inflictor = inflictor; + } + +// figure momentum add + if ( (inflictor != world) && (targ.movetype == MOVETYPE_WALK) ) + { + dir = targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; + dir = normalize(dir); + targ.velocity = targ.velocity + dir*damage*8; + } + +// check for godmode or invincibility + if (targ.flags & FL_GODMODE) + return; + if (targ.invincible_finished >= time) + { + if (self.invincible_sound < time) + { + sound (targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + self.invincible_sound = time + 2; + } + return; + } + +// team play damage avoidance + if ( (teamplay == 1 && targ != attacker) && (targ.team > 0)&&(targ.team == attacker.team) ) + return; + + // special case for killable chthon (yoder, december 16 2020) + if ((targ.classname == "monster_boss") && (targ.spawnflags & 2)) // is killable chthon + { + + if (attacker.weapon == IT_LIGHTNING) + { + // TODO: Spawn blood + dprint("Player has LG! Damage Chthon!\n"); + } + else + { + dprint("player attacked with non-lightning weapon. Do nothing.\n"); + return; + } + } + +// do the damage + targ.health = targ.health - take; + + if (targ.health <= 0) + { + Killed (targ, attacker); + return; + } + +// react to the damage + oldself = self; + self = targ; + + if ( (self.flags & FL_MONSTER) && attacker != world) + { + // get mad unless of the same class (except for soldiers) + if (self != attacker && attacker != self.enemy) + { + if ( (self.classname != attacker.classname) + || (self.classname == "monster_army" ) ) + { + // yoder Sept24, 2021 + // if in horde mode, have enemies obey a reaggro time + // TODO: Evaluate if we want this as default in co-op? + if (cvar("horde")) + { + if ((self.enemy.classname == "player") && // current and new enemy are both players, check reaggro times + (attacker.classname == "player")) + { + // check reaggro times + if ((!self.aggro_time) || + (self.aggro_time + AGGRO_MIN + (random() * AGGRO_ADD < time))) + { + self.oldenemy = self.enemy; + self.enemy = attacker; + self.aggro_time = time; + FoundTarget (); + } + else + { + // ignore new aggro from this hit + dprint("ignore new aggro\n"); + } + } + else // immediately aggro, store the player if previous was player + { + if (self.enemy.classname == "player") + self.oldenemy = self.enemy; + self.enemy = attacker; + FoundTarget (); + } + } + else // original behavior + { + if (self.enemy.classname == "player") + self.oldenemy = self.enemy; + self.enemy = attacker; + FoundTarget (); + } + } + } + } + + if (self.th_pain) + { + self.th_pain (attacker, take); + // nightmare mode monsters don't go into pain frames often + if (skill == 3) + self.pain_finished = time + 5; + } + + self = oldself; +}; + +/* +============ +T_RadiusDamage +============ +*/ +void(entity inflictor, entity attacker, float damage, entity ignore) T_RadiusDamage = +{ + local float points; + local entity head; + local vector org; + + #ifdef GIB_DOWNED_ZOMBIES + //Look for downed zombies and gib them if possible + if(damage >= 60) + { + head = find(world, classname, "monster_zombie"); + while(head) + { + if(head.solid == SOLID_NOT) + { + org = head.origin + (head.mins + head.maxs)*0.5; + points = 0.5*vlen (inflictor.origin - org); + if (points < 0) + { + points = 0; + } + points = damage - points; + if (points > 60) + { + if (CanDamage (head, inflictor)) + { + Killed (head, attacker); + } + } + } + head = find(head, classname, "monster_zombie"); + } + } + #endif + + head = findradius(inflictor.origin, damage+40); + + while (head) + { + if (head != ignore) + { + if (head.takedamage) + { + org = head.origin + (head.mins + head.maxs)*0.5; + points = 0.5*vlen (inflictor.origin - org); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (head, inflictor)) + { // shambler takes half damage from all explosions + if (head.classname == "monster_shambler") + T_Damage (head, inflictor, attacker, points*0.5); + else + T_Damage (head, inflictor, attacker, points); + } + } + } + } + head = head.chain; + } +}; + +/* +============ +T_BeamDamage +============ +*/ +void(entity attacker, float damage) T_BeamDamage = +{ + local float points; + local entity head; + + head = findradius(attacker.origin, damage+40); + + while (head) + { + if (head.takedamage) + { + points = 0.5*vlen (attacker.origin - head.origin); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (head, attacker)) + { + if (head.classname == "monster_shambler") + T_Damage (head, attacker, attacker, points*0.5); + else + T_Damage (head, attacker, attacker, points); + } + } + } + head = head.chain; + } +}; + diff --git a/quakec_mg1/defs.qc b/quakec_mg1/defs.qc new file mode 100644 index 0000000..0ee2a65 --- /dev/null +++ b/quakec_mg1/defs.qc @@ -0,0 +1,890 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* +============================================================================== + + SOURCE FOR GLOBALVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system globals +// +entity self; +entity other; +entity world; +float time; +float frametime; + +float force_retouch; // force all entities to touch triggers + // next frame. this is needed because + // non-moving things don't normally scan + // for triggers, and when a trigger is + // created (like a teleport trigger), it + // needs to catch everything. + // decremented each frame, so set to 2 + // to guarantee everything is touched +string mapname; + +float deathmatch; +float coop; +float teamplay; + +float serverflags; // propagated from level to level, used to + // keep track of completed episodes + +float total_secrets; +float total_monsters; + +float found_secrets; // number of secrets found +float killed_monsters; // number of monsters killed + + +// spawnparms are used to encode information about clients across server +// level changes +float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16; + +// +// global variables set by built in functions +// +vector v_forward, v_up, v_right; // set by makevectors() + +// set by traceline / tracebox +float trace_allsolid; +float trace_startsolid; +float trace_fraction; +vector trace_endpos; +vector trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; + +entity msg_entity; // destination of single entity writes + +// +// required prog functions +// +void() main; // only for testing + +void() StartFrame; + +void() PlayerPreThink; +void() PlayerPostThink; + +void() ClientKill; +void() ClientConnect; +void() PutClientInServer; // call after setting the parm1... parms +void() ClientDisconnect; + +void() SetNewParms; // called when a client first connects to + // a server. sets parms so they can be + // saved off for restarts + +void() SetChangeParms; // call to set parms for self so they can + // be saved for a level transition + + +//================================================ +void end_sys_globals; // flag for structure dumping +//================================================ + +/* +============================================================================== + + SOURCE FOR ENTVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system fields (*** = do not set in prog code, maintained by C code) +// +.float modelindex; // *** model index in the precached list +.vector absmin, absmax; // *** origin + mins / maxs + +.float ltime; // local time for entity +.float movetype; +.float solid; + +.vector origin; // *** +.vector oldorigin; // *** +.vector velocity; +.vector angles; +.vector avelocity; + +.vector punchangle; // temp angle adjust from damage or recoil + +.string classname; // spawn function +.string model; +.float frame; +.float skin; +.float effects; + +.vector mins, maxs; // bounding box extents reletive to origin +.vector size; // maxs - mins + +.void() touch; +.void() use; +.void() think; +.void() blocked; // for doors or plats, called when can't push other + +.float nextthink; +.entity groundentity; + +// stats +.float health; +.float frags; +.float weapon; // one of the IT_SHOTGUN, etc flags +.string weaponmodel; +.float weaponframe; +.float currentammo; +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; + +.float items; // bit flags + +.float takedamage; +.entity chain; +.float deadflag; + +.vector view_ofs; // add to origin to get eye point + + +.float button0; // fire +.float button1; // use +.float button2; // jump + +.float impulse; // weapon changes + +.float fixangle; +.vector v_angle; // view / targeting angle for players +.float idealpitch; // calculated pitch angle for lookup up slopes + + +.string netname; + +.entity enemy; + +.float flags; + +.float colormap; +.float team; + +.float max_health; // players maximum health is stored here + +.float teleport_time; // don't back up + +.float armortype; // save this fraction of incoming damage +.float armorvalue; + +.float waterlevel; // 0 = not in, 1 = feet, 2 = wast, 3 = eyes +.float watertype; // a contents value + +.float ideal_yaw; +.float yaw_speed; + +.entity aiment; + +.entity goalentity; // a movetarget or an enemy + +.float spawnflags; + +.string target; +.string targetname; + +// damage is accumulated through a frame. and sent as one single +// message, so the super shotgun doesn't generate huge messages +.float dmg_take; +.float dmg_save; +.entity dmg_inflictor; + +.entity owner; // who launched a missile +.vector movedir; // mostly for doors, but also used for waterjump + +.string message; // trigger messages + +.float sounds; // either a cd track number or sound number + +.string noise, noise1, noise2, noise3; // contains names of wavs to play + +//================================================ +void end_sys_fields; // flag for structure dumping +//================================================ + +/* +============================================================================== + + VARS NOT REFERENCED BY C CODE + +============================================================================== +*/ + + +// +// constants +// + +float FALSE = 0; +float TRUE = 1; + +// edict.flags +float FL_FLY = 1; +float FL_SWIM = 2; +float FL_CLIENT = 8; // set for all client edicts +float FL_INWATER = 16; // for enter / leave water splash +float FL_MONSTER = 32; +float FL_GODMODE = 64; // player cheat +float FL_NOTARGET = 128; // player cheat +float FL_ITEM = 256; // extra wide size for bonus items +float FL_ONGROUND = 512; // standing on something +float FL_PARTIALGROUND = 1024; // not all corners are valid +float FL_WATERJUMP = 2048; // player jumping out of water +float FL_JUMPRELEASED = 4096; // for jump debouncing +float FL_ISBOT = 8192; // marks a player as a bot +float FL_FUTUREMONSTER = 16384;// for marking unspawned monsters +float FL_INSHELTER = 32768;// for marking entities as sheltered from trigger_push + +// edict.movetype values +float MOVETYPE_NONE = 0; // never moves +//float MOVETYPE_ANGLENOCLIP = 1; +//float MOVETYPE_ANGLECLIP = 2; +float MOVETYPE_WALK = 3; // players only +float MOVETYPE_STEP = 4; // discrete, not real time unless fall +float MOVETYPE_FLY = 5; +float MOVETYPE_TOSS = 6; // gravity +float MOVETYPE_PUSH = 7; // no clip to world, push and crush +float MOVETYPE_NOCLIP = 8; +float MOVETYPE_FLYMISSILE = 9; // fly with extra size against monsters +float MOVETYPE_BOUNCE = 10; +float MOVETYPE_BOUNCEMISSILE = 11; // bounce with extra size + +float DROPTOFLOOR_DISABLE = 65536; + +// edict.solid values +float SOLID_NOT = 0; // no interaction with other objects +float SOLID_TRIGGER = 1; // touch on edge, but not blocking +float SOLID_BBOX = 2; // touch on edge, block +float SOLID_SLIDEBOX = 3; // touch on edge, but not an onground +float SOLID_BSP = 4; // bsp clip, touch on edge, block + +// range values +float RANGE_MELEE = 0; +float RANGE_NEAR = 1; +float RANGE_MID = 2; +float RANGE_FAR = 3; + +// deadflag values + +float DEAD_NO = 0; +float DEAD_DYING = 1; +float DEAD_DEAD = 2; +float DEAD_RESPAWNABLE = 3; + +// takedamage values + +float DAMAGE_NO = 0; +float DAMAGE_YES = 1; +float DAMAGE_AIM = 2; + +// items +float IT_AXE = 4096; +float IT_SHOTGUN = 1; +float IT_SUPER_SHOTGUN = 2; +float IT_NAILGUN = 4; +float IT_SUPER_NAILGUN = 8; +float IT_GRENADE_LAUNCHER = 16; +float IT_ROCKET_LAUNCHER = 32; +float IT_LIGHTNING = 64; +float IT_EXTRA_WEAPON = 128; + +float IT_ALL_WEAPONS = IT_AXE | IT_SHOTGUN | IT_SUPER_SHOTGUN | IT_NAILGUN | IT_SUPER_NAILGUN | IT_GRENADE_LAUNCHER | IT_ROCKET_LAUNCHER | IT_LIGHTNING | IT_EXTRA_WEAPON ; + +float IT_SHELLS = 256; +float IT_NAILS = 512; +float IT_ROCKETS = 1024; +float IT_CELLS = 2048; + +float IT_ARMOR1 = 8192; +float IT_ARMOR2 = 16384; +float IT_ARMOR3 = 32768; +float IT_SUPERHEALTH = 65536; + +float IT_KEY1 = 131072; +float IT_KEY2 = 262144; + +float IT_INVISIBILITY = 524288; +float IT_INVULNERABILITY = 1048576; +float IT_SUIT = 2097152; +float IT_QUAD = 4194304; + +// point content values + +float CONTENT_EMPTY = -1; +float CONTENT_SOLID = -2; +float CONTENT_WATER = -3; +float CONTENT_SLIME = -4; +float CONTENT_LAVA = -5; +float CONTENT_SKY = -6; + +float STATE_TOP = 0; +float STATE_BOTTOM = 1; +float STATE_UP = 2; +float STATE_DOWN = 3; + +vector VEC_ORIGIN = '0 0 0'; +vector VEC_HULL_MIN = '-16 -16 -24'; +vector VEC_HULL_MAX = '16 16 32'; + +vector VEC_HULL2_MIN = '-32 -32 -24'; +vector VEC_HULL2_MAX = '32 32 64'; + +// protocol bytes +float SVC_UPDATESTAT = 3; // Yoder add, 23/09/2020 +float SVC_TEMPENTITY = 23; +float SVC_CENTERPRINT = 26; // Yoder add, 23/09/2020 +float SVC_KILLEDMONSTER = 27; +float SVC_FOUNDSECRET = 28; +float SVC_INTERMISSION = 30; +float SVC_FINALE = 31; +float SVC_CDTRACK = 32; +float SVC_SELLSCREEN = 33; +float SVC_ACHIEVEMENT = 52; + + +float TE_SPIKE = 0; +float TE_SUPERSPIKE = 1; +float TE_GUNSHOT = 2; +float TE_EXPLOSION = 3; +float TE_TAREXPLOSION = 4; +float TE_LIGHTNING1 = 5; +float TE_LIGHTNING2 = 6; +float TE_WIZSPIKE = 7; +float TE_KNIGHTSPIKE = 8; +float TE_LIGHTNING3 = 9; +float TE_LAVASPLASH = 10; +float TE_TELEPORT = 11; +float TE_BEAM = 13; + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +float CHAN_AUTO = 0; +float CHAN_WEAPON = 1; +float CHAN_VOICE = 2; +float CHAN_ITEM = 3; +float CHAN_BODY = 4; + +float ATTN_NONE = 0; +float ATTN_NORM = 1; +float ATTN_IDLE = 2; +float ATTN_STATIC = 3; + +// update types + +float UPDATE_GENERAL = 0; +float UPDATE_STATIC = 1; +float UPDATE_BINARY = 2; +float UPDATE_TEMP = 3; + +// entity effects + +float EF_BRIGHTFIELD = 1; +float EF_MUZZLEFLASH = 2; +float EF_BRIGHTLIGHT = 4; +float EF_DIMLIGHT = 8; +float EF_QUADLIGHT = 16; +float EF_PENTLIGHT = 32; +float EF_CANDLELIGHT = 64; + + +// messages +float MSG_BROADCAST = 0; // unreliable to all +float MSG_ONE = 1; // reliable to one (msg_entity) +float MSG_ALL = 2; // reliable to all +float MSG_INIT = 3; // write to the init string + +// team numbers for Coop +float TEAM_MONSTERS = 0; +float TEAM_HUMANS = 1; + +// player expanded settings flags +float PEF_CHANGEONLYNEW = 1; +float PEF_CHANGENEVER = 2; + +// mal: codes that the QuakeC built-ins "bot_movetopoint" and "bot_followentity" will return each time they're called +float BOT_GOAL_ERROR = 0; // can't do this goal for some reason. +float BOT_GOAL_SUCCESS = 1; // goal as requested is complete. +float BOT_GOAL_IN_PROGRESS = 2; // goal is in progress + +// mal: codes that the QuakeC built-in "walkpathtogoal" will return each time it's called +float PATH_ERROR = 0; // something bad happened ( no nav nodes, no nearby nodes, no path, etc. ). +float PATH_REACHED_GOAL = 1; // reached whatever we were trying to get to. +float PATH_REACHED_PATH_END = 2; // reached the end of the path - can now move directly to target. +float PATH_MOVE_BLOCKED = 3; // something ( or someone ) is in our way. +float PATH_IN_PROGRESS = 4; // path was found, and we're following it. + +//================================================ + +// +// globals +// + +nosave float load_flag; + +float movedist; +float gameover; // set when a rule exits + +string string_null; // null string, nothing should be held here + +entity newmis; // launch_spike sets this after spawning it + +entity activator; // the entity that activated a trigger or brush + +entity damage_attacker; // set by T_Damage +float framecount; + +float skill; +float campaign_valid; +float campaign; +float isHordeMode; // mal: cache off horde cvar +float cheats_allowed; // mal: let's us run cheat commands in DM/Coop games. + +//================================================ + +// +// world fields (FIXME: make globals) +// +.string wad; +.string map; +.string endtext; + +const float WORLDTYPE_MEDIEVAL = 0; +const float WORLDTYPE_METAL = 1; +const float WORLDTYPE_BASE = 2; +const float WORLDTYPE_HUB = 3; +.float worldtype; + +//================================================ + +.float alpha; +.string killtarget; + +.void(float deltaTime) tick; + +// +// quakeed fields +// +.float light_lev; // not used by game, but parsed by light util +.float style; + +float SPAWNFLAG_NOMESSAGE = 1; +float SPAWNFLAG_NOTOUCH = 1; +float SPAWNFLAG_TRIGGER_FIRST = 2; +float SPAWNFLAG_NEARSIGHTED = 8192; + +float MONSTER_ATTACK_FRIEND = 32; +#ifdef MONSTERS_AWARE_OF_CONTENTS +float SPAWNFLAG_NO_CONTENTS_DAMAGE = 16384; +#endif + + +// +// monster ai +// +.void() th_stand; +.void() th_walk; +.void() th_run; +.void() th_missile; +.void() th_melee; +.void(entity attacker, float damage) th_pain; +.void() th_die; + +.entity oldenemy; // mad at this player before taking damage + +.float speed; +.float speed2; // Yoder add, July 10 2020, used by func_doors' "door_go_down" + +.float lefty; + +.float search_time; + +// Attack state ( not really used in Vanilla ) +.float attack_state; +float AS_STRAIGHT = 1; +float AS_SLIDING = 2; +float AS_MELEE = 3; +float AS_MISSILE = 4; + +.float allowPathFind; // whether or not AI can used advanced pathfinder. + +// Combat style +.float combat_style; +float CS_NONE = 0; // default - for bosses or other monsters that don't move. +float CS_RANGED = 1; +float CS_MELEE = 2; +float CS_MIXED = 3; // can do ranged or melee equally. + +// +// player only fields +// +.float walkframe; + +.float attack_finished; +.float pain_finished; + +.float invincible_finished; +.float invisible_finished; +.float super_damage_finished; +.float radsuit_finished; + +.float invincible_time, invincible_sound; +.float invisible_time, invisible_sound; +.float super_time, super_sound; +.float rad_time; +.float fly_sound; + +.float axhitme; + +.float show_hostile; // set to time+0.2 whenever a client fires a + // weapon or takes damage. Used to alert + // monsters that otherwise would let the player go +.float jump_flag; // player jump flag +.float swim_flag; // player swimming sound flag +.float air_finished; // when time > air_finished, start drowning +.float bubble_count; // keeps track of the number of bubbles +.string deathtype; // keeps track of how the player died + +// +// object stuff +// +.string mdl; +.vector mangle; // angle at start + +.vector oldorigin; // only used by secret door + +.float t_length, t_width; + +float START_OFF = 1; +// +// doors, etc +// +.vector dest, dest1, dest2; +.float wait; // time from firing to restarting +.float delay; // time from activation to firing +.string noise4; + +// +// monsters +// +.float pausetime; +.entity movetarget; + + +// +// doors +// +.float aflag; +.float dmg; // damage done by door when hit + +// +// misc +// +.float cnt; // misc flag + +// +// subs +// +.void() think1; +.vector finaldest, finalangle; + +// +// triggers +// +.float count; // for counting triggers + + +// +// plats / doors / buttons +// +.float lip; +.float state; +.vector pos1, pos2; // top and bottom positions +.float height; + +// +// sounds +// +.float waitmin, waitmax; +.float distance; +.float volume; + +.float spawn_deferred; + + +//=========================================================================== + + +// +// builtin functions +// + +void(vector ang) makevectors = #1; // sets v_forward, etc globals +void(entity e, vector o) setorigin = #2; +void(entity e, string m) setmodel = #3; // set movetype and solid first +void(entity e, vector min, vector max) setsize = #4; +// #5 was removed +void() break = #6; +float() random = #7; // returns 0 - 1 +void(entity e, float chan, string samp, float vol, float atten) sound = #8; +vector(vector v) normalize = #9; +void(string e) error = #10; +void(string e) objerror = #11; +float(vector v) vlen = #12; +float(vector v) vectoyaw = #13; +entity() spawn = #14; + +void RemoveFrameTickEntity(entity rem); +void(entity e) remove_real = #15; +void(entity e) remove +{ + if(e.tick) RemoveFrameTickEntity(e); + remove_real(e); +} + +// sets trace_* globals +// nomonsters can be: +// An entity will also be ignored for testing if forent == test, +// forent->owner == test, or test->owner == forent +// a forent of world is ignored +void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16; + +entity() checkclient = #17; // returns a client to look for +entity(entity start, .string fld, string match) find = #18; +string(string s) precache_sound = #19; +string(string s) precache_model = #20; +void(entity client, string s)stuffcmd = #21; +entity(vector org, float rad) findradius = #22; +void(string s, ...) bprint = #0:ex_bprint; +void(entity client, string s, ...) sprint = #0:ex_sprint; +void(string s) dprint = #25; +string(float f) ftos = #26; +string(vector v) vtos = #27; +void() coredump = #28; // prints all edicts +void() traceon = #29; // turns statment trace on +void() traceoff = #30; +void(entity e) eprint = #31; // prints an entire edict +float(float yaw, float dist) walkmove = #32; // returns TRUE or FALSE +// #33 was removed +float(float yaw, float dist) droptofloor_real = #34; // TRUE if landed on floor +float() droptofloor = +{ + if(self.spawnflags & DROPTOFLOOR_DISABLE) return TRUE; + return droptofloor_real(0 ,0 ); +} +void(float style, string value) lightstyle = #35; +float(float v) rint = #36; // round to nearest int +float(float v) floor = #37; // largest integer <= v +float(float v) ceil = #38; // smallest integer >= v +// #39 was removed +float(entity e) checkbottom = #40; // true if self is on ground +float(vector v) pointcontents = #41; // returns a CONTENT_* +// #42 was removed +float(float f) fabs = #43; +vector(entity e, float speed) aim = #44; // returns the shooting vector +float(string s) cvar = #45; // return cvar.value +void(string s) localcmd = #46; // put string into local que +entity(entity e) nextent = #47; // for looping through all ents +void(vector o, vector d, float color, float count) particle = #48;// start a particle effect +void() ChangeYaw = #49; // turn towards self.ideal_yaw + // at self.yaw_speed +// #50 was removed +vector(vector v) vectoangles = #51; + +// +// direct client message generation +// +void(float to, float f) WriteByte = #52; +void(float to, float f) WriteChar = #53; +void(float to, float f) WriteShort = #54; +void(float to, float f) WriteLong = #55; +void(float to, float f) WriteCoord = #56; +void(float to, float f) WriteAngle = #57; +void(float to, string s) WriteString = #58; +void(float to, entity s) WriteEntity = #59; + + +void(float step) movetogoal = #67; + +string(string s) precache_file = #68; // no effect except for -copy +void(entity e) makestatic = #69; +void(string s) changelevel = #70; + +//#71 was removed + +void(string var, string val) cvar_set = #72; // sets cvar.value + +void(entity client, string s, ...) centerprint = #0:ex_centerprint; // sprint, but in middle +void centerprint_all(string s) +{ + entity e = find(world, classname, "player"); + while(e) + { + centerprint(e, s); + e = find(e, classname, "player"); + } + /* + entity e = nextent(world); + while(e) + { + if(e.flags & FL_CLIENT) + { + centerprint(e, s); + } + e = nextent(e); + } + */ +} + +void(vector pos, string samp, float vol, float atten) ambientsound = #74; + +string(string s) precache_model2 = #75; // registered version only +string(string s) precache_sound2 = #76; // registered version only +string(string s) precache_file2 = #77; // registered version only + +void(entity e) setspawnparms = #78; // set parm1... to the + // values at level start + // for coop respawn + +float() finaleFinished = #0:ex_finaleFinished; + +// Plays a sound that only "client" can hear. Useful for announcers... +void localsound( entity client, string sample ) = #0:ex_localsound; + +// Debug rendering functions. +void draw_point( vector point, float colormap, float lifetime, float depthtest ) = #0:ex_draw_point; +void draw_line( vector start, vector end, float colormap, float lifetime, float depthtest ) = #0:ex_draw_line; +void draw_arrow( vector start, vector end, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_arrow; +void draw_ray( vector start, vector direction, float length, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_ray; +void draw_circle( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_circle; +void draw_bounds( vector min, vector max, float colormap, float lifetime, float depthtest ) = #0:ex_draw_bounds; +void draw_worldtext( string s, vector origin, float size, float lifetime, float depthtest ) = #0:ex_draw_worldtext; +void draw_sphere( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_sphere; +void draw_cylinder( vector origin, float halfHeight, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_cylinder; + +// Bot functions that return a value based on "BOT_GOAL_", depending on the status of the goal. +float bot_movetopoint( entity bot, vector point ) = #0:ex_bot_movetopoint; +float bot_followentity( entity bot, entity goal ) = #0:ex_bot_followentity; + +// Function for checking new player configured flags, such as weapon swap behaviour +float CheckPlayerEXFlags( entity playerEnt ) = #0:ex_CheckPlayerEXFlags; + +// Path monsters to the goal using any available nav nodes on the map +float walkpathtogoal( float movedist, vector goal ) = #0:ex_walkpathtogoal; + +float checkextension( string s ) = #99; + +//============================================================================ + +// +// subs.qc +// +void(vector tdest, float tspeed, void() func) SUB_CalcMove; +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt; +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove; +void() SUB_CalcMoveDone; +void() SUB_CalcAngleMoveDone; +void() SUB_Null; +void() SUB_UseTargets; +void() SUB_Remove; + +// +// combat.qc +// +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage; + + +float (entity e, float healamount, float ignore) T_Heal; // health function + +float(entity targ, entity inflictor) CanDamage; + +// Yoder add, September 9 2020 +.float is_frozen; // defines whether an entity is frozen +const float COOP_ONLY = 32768; // yoder add, Jan 16 2021; Only spawn in coop. Used by items and monsters +const float COOP_SPAWN_ACTIVE = 73; + +const float NOT_IN_COOP = 131072; +#define INHIBIT_COOP if(coop && self.spawnflags & NOT_IN_COOP) {remove(self); return;} + +var float num_players = 0; + +// +// horde.qc stuff +// +// Yoder, September 24 2021, merging Horde back into mg1 code +.float is_frozen; // defines whether an entity is frozen +float FADETIME = 5; // STATIC GLOBAL, how long after death until monsters fade out + +float DEFAULT_POWERUP_CHANCE = 0.025; +float POWERUP_CHANCE_GAIN = 0.025; +float powerup_chance; + +entity horde_ent; // global horde manager so code can reference without lookup +void() remote_wavecheck; // trigger wavecheck from outside the base entity + +float keys_silver; // number of silver keys the players are carrying +float keys_gold; // number of gold keys the players are carrying + +.float killtime; // game time at which player got their last killed +.float killspree; // player's current killing spree +float KILLSPREE_GAP = 2; // amount of time that can lapse for a kill to no longer count as part of a spree + +.string category; // to do find operations by type +string CATEGORY_MONSTER = "monster"; + +.vector old_velocity; + +float MONSTER_IGNORE_DAMAGE_LAVA = 1; +float MONSTER_IGNORE_DAMAGE_SLIME = 2; + +.float aggro_time; // when a monster is aggro'd, set this value to the time. Future aggro's check against this. +float AGGRO_MIN = 1; // how long to wait before allowing a new Aggro +float AGGRO_ADD = 1; // random additional wait + +float HORDE_START_DELAY = 10; // how long to wait before starting +float HORDE_AMMO_RESPAWN_DELAY = 20; // how long between horde ammo spawns +//float horde; +.float hunger_time; +float HUNGER_MAX = 10; // max delay between hunger pangs +float HUNGER_MIN = 2.5; // min delay between hunger pangs + +.float axe_hit_chain; // number of consecutive axe hits +.float axe_hit_chain_time; // time of last axe hit + combo time +float AXE_COMBO_TIME = 0.7; // max time before combo resets + +// AY Dec15 2021, moving from item_runes to defs since these get used by horde now +float SIGIL_E1 = 1; +float SIGIL_E2 = 2; +float SIGIL_E3 = 4; +float SIGIL_E4 = 8; +float SIGIL_E5 = 16; +float SIGIL_E6 = 32; +float SIGIL_NUMBITS = 6; \ No newline at end of file diff --git a/quakec_mg1/doors.qc b/quakec_mg1/doors.qc new file mode 100644 index 0000000..20b6c5e --- /dev/null +++ b/quakec_mg1/doors.qc @@ -0,0 +1,808 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +//Yoder Sept24 Horde Merge +void(float key_item)horde_key_spend; + +float DOOR_START_OPEN = 1; +float DOOR_DONT_LINK = 4; +float DOOR_GOLD_KEY = 8; +float DOOR_SILVER_KEY = 16; +float DOOR_TOGGLE = 32; +float DOOR_CRUSHER = 64; +/* + +Doors are similar to buttons, but can spawn a fat trigger field around them +to open without a touch, and they link together to form simultanious +double/quad doors. + +Door.owner is the master door. If there is only one door, it points to itself. +If multiple doors, all will point to a single one. + +Door.enemy chains from the master door through all doors linked in the chain. + +*/ + +/* +============================================================================= + +THINK FUNCTIONS + +============================================================================= +*/ + +void() door_go_down; +void() door_go_up; + +void() door_blocked = +{ + T_Damage (other, self, self, self.dmg); + +// 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.spawnflags & DOOR_CRUSHER) + return; + if (self.wait >= 0) + { + if (self.state == STATE_DOWN) + door_go_up (); + else + door_go_down (); + } +}; + + +void() door_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + if (self.spawnflags & DOOR_TOGGLE) + return; // don't come down automatically + self.think = door_go_down; + self.nextthink = self.ltime + self.wait; +}; + +void() door_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() door_go_down = +{ + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + if (self.max_health) + { + self.takedamage = DAMAGE_YES; + self.health = self.max_health; + } + + self.state = STATE_DOWN; + if (self.speed2) + SUB_CalcMove (self.pos1, self.speed2, door_hit_bottom); + else + SUB_CalcMove (self.pos1, self.speed, door_hit_bottom); +}; + +void() door_go_up = +{ + if (self.state == STATE_UP) + return; // allready going up + + if (self.state == STATE_TOP) + { // reset top wait time + self.nextthink = self.ltime + self.wait; + return; + } + + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, door_hit_top); + + SUB_UseTargets(); +}; + + +/* +============================================================================= + +ACTIVATION FUNCTIONS + +============================================================================= +*/ + +void() door_fire = +{ + local entity oself; + local entity starte; + + if (self.owner != self) + objerror ("door_fire: self.owner != self"); + + + // play use key sound + if (self.items) + sound (self, CHAN_ITEM, self.noise4, 1, ATTN_NORM); + + self.message = string_null; // no more message + oself = self; + + if (self.spawnflags & DOOR_TOGGLE) + { + if (self.state == STATE_UP || self.state == STATE_TOP) + { + starte = self; + do + { + door_go_down (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + self = oself; + return; + } + } + +// trigger all paired doors + starte = self; + do + { + door_go_up (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + self = oself; +}; + + +void() door_use = +{ + local entity oself; + + self.message = ""; // door message are for touch only + self.owner.message = ""; + self.enemy.message = ""; + oself = self; + self = self.owner; + door_fire (); + self = oself; +}; + + +void() door_trigger_touch = +{ + if (other.health <= 0) + return; + + if (time < self.attack_finished) + return; + self.attack_finished = time + 1; + + activator = other; + + self = self.owner; + door_use (); +}; + + +void() door_killed = +{ + local entity oself; + + oself = self; + self = self.owner; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + door_use (); + self = oself; +}; + + +/* +================ +door_touch + +Prints messages and opens key doors +================ +*/ +void() door_touch = +{ + if (other.classname != "player") + return; + if (self.owner.attack_finished > time) + return; + + self.owner.attack_finished = time + 2; + + if (self.owner.message != "") + { + centerprint (other, self.owner.message); // Ingame message, localized + sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + +// key door stuff + if (!self.items) + return; + + if ( (self.items & other.items) != self.items ) + { + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + if (self.owner.items == IT_KEY1) + { + if (self.worldtype == WORLDTYPE_BASE) + { + centerprint (other, "$qc_need_silver_keycard"); + } + else if (self.worldtype == WORLDTYPE_METAL) + { + centerprint (other, "$qc_need_silver_runekey"); + } + else if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + centerprint (other, "$qc_need_silver_key"); + } + } + else if (self.owner.items == IT_KEY2) + { + if (self.worldtype == WORLDTYPE_BASE) + { + centerprint (other, "$qc_need_gold_keycard"); + } + else if (self.worldtype == WORLDTYPE_METAL) + { + centerprint (other, "$qc_need_gold_runekey"); + } + else if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + centerprint (other, "$qc_need_gold_key"); + } + } + return; + } + + // Yoder sept24, 2021, horde merge + if (horde_ent) + horde_key_spend(self.items); + else // standard behavior + other.items = other.items - self.items; + self.touch = SUB_Null; + if (self.enemy) + self.enemy.touch = SUB_Null; // get paired door + door_use (); +}; + +/* +============================================================================= + +SPAWNING FUNCTIONS + +============================================================================= +*/ + + +void(vector fmins, vector fmaxs) spawn_field = +{ + local entity trigger; + local vector t1, t2; + + trigger = spawn(); + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.owner = self; + trigger.touch = door_trigger_touch; + + t1 = fmins; + t2 = fmaxs; + setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); +}; + + +float (entity e1, entity e2) EntitiesTouching = +{ + if (e1.mins_x > e2.maxs_x) + return FALSE; + if (e1.mins_y > e2.maxs_y) + return FALSE; + if (e1.mins_z > e2.maxs_z) + return FALSE; + if (e1.maxs_x < e2.mins_x) + return FALSE; + if (e1.maxs_y < e2.mins_y) + return FALSE; + if (e1.maxs_z < e2.mins_z) + return FALSE; + return TRUE; +}; + + +/* +============= +LinkDoors + + +============= +*/ +void() LinkDoors = +{ + local entity t, starte; + local vector cmins, cmaxs; + + if (self.enemy) + return; // already linked by another door + if (self.spawnflags & 4) + { + self.owner = self.enemy = self; + return; // don't want to link this door + } + + cmins = self.mins; + cmaxs = self.maxs; + + starte = self; + t = self; + + do + { + self.owner = starte; // master door + + if (self.health) + starte.health = self.health; + if (self.targetname) + starte.targetname = self.targetname; + if (self.message != "") + starte.message = self.message; + + t = find (t, classname, self.classname); + if (!t) + { + self.enemy = starte; // make the chain a loop + + // shootable, fired, or key doors just needed the owner/enemy links, + // they don't spawn a field + + self = self.owner; + + if (self.health) + return; + if (self.targetname) + return; + if (self.items) + return; + + spawn_field(cmins, cmaxs); + + return; + } + + if (EntitiesTouching(self,t)) + { + if (t.enemy) + objerror ("cross connected doors"); + + self.enemy = t; + self = t; + + if (t.mins_x < cmins_x) + cmins_x = t.mins_x; + if (t.mins_y < cmins_y) + cmins_y = t.mins_y; + if (t.mins_z < cmins_z) + cmins_z = t.mins_z; + if (t.maxs_x > cmaxs_x) + cmaxs_x = t.maxs_x; + if (t.maxs_y > cmaxs_y) + cmaxs_y = t.maxs_y; + if (t.maxs_z > cmaxs_z) + cmaxs_z = t.maxs_z; + } + } while (1 ); + +}; + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). + +Key doors are allways wait -1. + +"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" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +void() func_door = +{ + self.netname = "func_door"; // for bot nav support. + SUB_SetWorldtype(); + if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + precache_sound ("doors/medtry.wav"); + precache_sound ("doors/meduse.wav"); + self.noise3 = "doors/medtry.wav"; + self.noise4 = "doors/meduse.wav"; + } + else if (self.worldtype == WORLDTYPE_METAL) + { + precache_sound ("doors/runetry.wav"); + precache_sound ("doors/runeuse.wav"); + self.noise3 = "doors/runetry.wav"; + self.noise4 = "doors/runeuse.wav"; + } + else if (self.worldtype == WORLDTYPE_BASE) + { + precache_sound ("doors/basetry.wav"); + precache_sound ("doors/baseuse.wav"); + self.noise3 = "doors/basetry.wav"; + self.noise4 = "doors/baseuse.wav"; + } + + if (self.sounds == 0) + { + precache_sound ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = "misc/null.wav"; + self.noise2 = "misc/null.wav"; + } + if (self.sounds == 1) + { + precache_sound ("doors/drclos4.wav"); + precache_sound ("doors/doormv1.wav"); + self.noise1 = "doors/drclos4.wav"; + self.noise2 = "doors/doormv1.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/hydro1.wav"); + precache_sound ("doors/hydro2.wav"); + self.noise2 = "doors/hydro1.wav"; + self.noise1 = "doors/hydro2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/stndr1.wav"); + precache_sound ("doors/stndr2.wav"); + self.noise2 = "doors/stndr1.wav"; + self.noise1 = "doors/stndr2.wav"; + } + if (self.sounds == 4) + { + precache_sound ("doors/ddoor1.wav"); + precache_sound ("doors/ddoor2.wav"); + self.noise1 = "doors/ddoor2.wav"; + self.noise2 = "doors/ddoor1.wav"; + } + + + self.max_health = self.health; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + // self.classname = "door"; //Removed, why did it ever do this? + self.blocked = door_blocked; + self.use = door_use; + + if (self.spawnflags & DOOR_SILVER_KEY) + self.items = IT_KEY1; + if (self.spawnflags & DOOR_GOLD_KEY) + self.items = IT_KEY2; + + if (!self.speed) + self.speed = 100; + if (!self.wait) + self.wait = 3; + if (!self.lip) + self.lip = 8; + if (!self.dmg) + self.dmg = 2; + + if(self.movedir) + { + self.pos1 = self.origin; + self.pos2 = self.pos1 + self.movedir; + self.angles = '0 0 0'; + } + else + { + SetMovedir(); + self.pos1 = self.origin; + self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip); + } + + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if (self.spawnflags & DOOR_START_OPEN) + { + setorigin (self, self.pos2); + self.pos2 = self.pos1; + self.pos1 = self.origin; + } + + self.state = STATE_BOTTOM; + + if (self.health) + { + self.takedamage = DAMAGE_YES; + self.th_die = door_killed; + } + + if (self.items) + self.wait = -1; + + self.touch = door_touch; + +// LinkDoors can't be done until all of the doors have been spawned, so +// the sizes can be detected properly. + self.think = LinkDoors; + self.nextthink = self.ltime + 0.1; +}; + +/* +============================================================================= + +SECRET DOORS + +============================================================================= +*/ + +void() fd_secret_move1; +void() fd_secret_move2; +void() fd_secret_move3; +void() fd_secret_move4; +void() fd_secret_move5; +void() fd_secret_move6; +void() fd_secret_done; + +float SECRET_OPEN_ONCE = 1; // stays open +float SECRET_1ST_LEFT = 2; // 1st move is left of arrow +float SECRET_1ST_DOWN = 4; // 1st move is down from arrow +float SECRET_NO_SHOOT = 8; // only opened by trigger +float SECRET_YES_SHOOT = 16; // shootable even if targeted + + +void () fd_secret_use = +{ + local float temp; + + self.health = 10000; + + // exit if still moving around... + if (self.origin != self.oldorigin) + return; + + self.message = string_null; // no more message + + SUB_UseTargets(); // fire all targets / killtargets + + if (!(self.spawnflags & SECRET_NO_SHOOT)) + { + self.th_pain = SUB_NullPain; + self.takedamage = DAMAGE_NO; + } + self.velocity = '0 0 0'; + + // Make a sound, wait a little... + + sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.nextthink = self.ltime + 0.1; + + temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1 + makevectors(self.mangle); + + if (!self.t_width) + { + if (self.spawnflags & SECRET_1ST_DOWN) + self. t_width = fabs(v_up * self.size); + else + self. t_width = fabs(v_right * self.size); + } + + if (!self.t_length) + self. t_length = fabs(v_forward * self.size); + + if (self.spawnflags & SECRET_1ST_DOWN) + self.dest1 = self.origin - v_up * self.t_width; + else + self.dest1 = self.origin + v_right * (self.t_width * temp); + + self.dest2 = self.dest1 + v_forward * self.t_length; + SUB_CalcMove(self.dest1, self.speed, fd_secret_move1); + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); +}; + +void (entity attacker, float damage) fd_secret_use_pain = { fd_secret_use(); } + +// Wait after first movement... +void () fd_secret_move1 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move2; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +// Start moving sideways w/sound... +void () fd_secret_move2 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest2, self.speed, fd_secret_move3); +}; + +// Wait here until time to go back... +void () fd_secret_move3 = +{ + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + if (!(self.spawnflags & SECRET_OPEN_ONCE)) + { + self.nextthink = self.ltime + self.wait; + self.think = fd_secret_move4; + } +}; + +// Move backward... +void () fd_secret_move4 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest1, self.speed, fd_secret_move5); +}; + +// Wait 1 second... +void () fd_secret_move5 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move6; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () fd_secret_move6 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done); +}; + +void () fd_secret_done = +{ + if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use_pain; + } + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () secret_blocked = +{ + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; + +/* +================ +secret_touch + +Prints messages +================ +*/ +void() secret_touch = +{ + if (other.classname != "player") + return; + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + + if (self.message) + { + centerprint (other, self.message); // Ingame message, localized + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } +}; + + +/*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot +Basic secret door. Slides back, then to the side. Angle determines direction. +wait = # of seconds before coming back +1st_left = 1st move is left of arrow +1st_down = 1st move is down from arrow +always_shoot = even if targeted, keep shootable +t_width = override WIDTH to move back (or height if going down) +t_length = override LENGTH to move sideways +"dmg" damage to inflict when blocked (2 default) + +If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. +"sounds" +1) medieval +2) metal +3) base +*/ + +void () func_door_secret = +{ + if (self.sounds == 0) + self.sounds = 3; + if (self.sounds == 1) + { + precache_sound ("doors/latch2.wav"); + precache_sound ("doors/winch2.wav"); + precache_sound ("doors/drclos4.wav"); + self.noise1 = "doors/latch2.wav"; + self.noise2 = "doors/winch2.wav"; + self.noise3 = "doors/drclos4.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/airdoor1.wav"); + precache_sound ("doors/airdoor2.wav"); + self.noise2 = "doors/airdoor1.wav"; + self.noise1 = "doors/airdoor2.wav"; + self.noise3 = "doors/airdoor2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/basesec1.wav"); + precache_sound ("doors/basesec2.wav"); + self.noise2 = "doors/basesec1.wav"; + self.noise1 = "doors/basesec2.wav"; + self.noise3 = "doors/basesec2.wav"; + } + + if (!self.dmg) + self.dmg = 2; + + // Magic formula... + self.mangle = self.angles; + self.angles = '0 0 0'; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + // self.classname = "door"; //Removed, why did it ever do this? + setmodel (self, self.model); + setorigin (self, self.origin); + + self.touch = secret_touch; + self.blocked = secret_blocked; + self.speed = 50; + self.use = fd_secret_use; + if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use_pain; + self.th_die = fd_secret_use; + } + self.oldorigin = self.origin; + if (!self.wait) + self.wait = 5; // 5 seconds before closing +}; diff --git a/quakec_mg1/fight.qc b/quakec_mg1/fight.qc new file mode 100644 index 0000000..a610f59 --- /dev/null +++ b/quakec_mg1/fight.qc @@ -0,0 +1,426 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* + +A monster is in fight mode if it thinks it can effectively attack its +enemy. + +When it decides it can't attack, it goes into hunt mode. + +*/ + +float(float v) anglemod; + +void() knight_atk1; +void() knight_runatk1; +void() ogre_smash1; +void() ogre_swing1; + +void() sham_smash1; +void() sham_swingr1; +void() sham_swingl1; + +float() DemonCheckAttack; +void(float side) Demon_Melee; + +void(vector dest) ChooseTurn; + +void() ai_face; + + +float enemy_visible, enemy_range; +float enemy_yaw; + + +void() knight_attack = +{ + local float len; + +// decide if now is a good swing time + len = vlen(self.enemy.origin+self.enemy.view_ofs - (self.origin+self.view_ofs)); + + if (len<80) + knight_atk1 (); + else + knight_runatk1 (); +}; + +//============================================================================= + +/* +=========== +CheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() CheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != targ) + return FALSE; // don't have a clear shot + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (enemy_range == RANGE_MELEE) + { // melee attack + if (self.th_melee) + { + if (self.classname == "monster_knight") + knight_attack (); + else + self.th_melee (); + return TRUE; + } + } + +// missile attack + if (!self.th_missile) + return FALSE; + + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + { + chance = 0.9; + self.attack_finished = 0; + } + else if (enemy_range == RANGE_NEAR) + { + if (self.th_melee) + chance = 0.2; + else + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + if (self.th_melee) + chance = 0.05; + else + chance = 0.1; + } + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (2*random()); + return TRUE; + } + + return FALSE; +}; + + +/* +============= +ai_face + +Stay facing the enemy +============= +*/ +void() ai_face = +{ + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); +}; + +/* +============= +ai_charge + +The monster is in a melee attack, so get as close as possible to .enemy +============= +*/ +float (entity targ) visible; +float(entity targ) infront; +float(entity targ) range; + +void(float d) ai_charge = +{ + ai_face (); + movetogoal (d); // done in C code... +}; + +void() ai_charge_side = +{ + local vector dtemp; + local float heading; + +// aim to the left of the enemy for a flyby + + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); + + makevectors (self.angles); + dtemp = self.enemy.origin - 30*v_right; + heading = vectoyaw(dtemp - self.origin); + + walkmove(heading, 20); +}; + + +/* +============= +ai_melee + +============= +*/ +void() ai_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +void() ai_melee_side = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + ai_charge_side(); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + if (!CanDamage (self.enemy, self)) + return; + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +//============================================================================= + +/* +=========== +SoldierCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() SoldierCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + return FALSE; // don't have a clear shot + + +// missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.4; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (1 + random()); + if (random() < 0.3) + self.lefty = !self.lefty; + + return TRUE; + } + + return FALSE; +}; +//============================================================================= + +/* +=========== +ShamCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() ShamCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_visible) + return FALSE; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + if (vlen(spot1 - spot2) > 600) + return FALSE; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + +// missile attack + if (enemy_range == RANGE_FAR) + return FALSE; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (2 + 2*random()); + return TRUE; +}; + +//============================================================================ + +/* +=========== +OgreCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() OgreCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_visible) + return FALSE; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + +// missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + else if (enemy_range == RANGE_NEAR) + chance = 0.10; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (1 + 2*random()); + return TRUE; +}; + diff --git a/quakec_mg1/fog.qc b/quakec_mg1/fog.qc new file mode 100644 index 0000000..6b502d0 --- /dev/null +++ b/quakec_mg1/fog.qc @@ -0,0 +1,296 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +const float FORCE_SET_FOG = 1; + + +void stuffcmd_digit( entity client, float f) = +{ + float d; + d = floor(f); + d = mod(d, 10); + + if(d == 0) + stuffcmd(client, "0"); + else if(d == 1) + stuffcmd(client, "1"); + else if(d == 2) + stuffcmd(client, "2"); + else if(d == 3) + stuffcmd(client, "3"); + else if(d == 4) + stuffcmd(client, "4"); + else if(d == 5) + stuffcmd(client, "5"); + else if(d == 6) + stuffcmd(client, "6"); + else if(d == 7) + stuffcmd(client, "7"); + else if(d == 8) + stuffcmd(client, "8"); + else if(d == 9) + stuffcmd(client, "9"); +} + +void stuffcmd_int( entity client, float f, float numdigits) = +{ + + float tmp; + + + if(f == 0){ + stuffcmd( client, "0"); + return; + } + + if(f < 0){ + // Yeah sure. + stuffcmd( client, "-"); + f = fabs(f); + } + + if(numdigits <= 0){ + tmp = f; + numdigits = 1; + while(tmp >= 1){ + tmp = tmp / 10; + numdigits = numdigits * 10; + } + } + //I need to do this to get zero-padding to work. + + while( numdigits > 1 ){ + + numdigits = numdigits / 10; + tmp = f / numdigits; + stuffcmd_digit( client, tmp); + + } + + +} + +void stuffcmd_float( entity client, float f) = +{ + float intpart, decpart, isNegative; + + isNegative = FALSE; + + if(f == 0){ + stuffcmd( client, "0"); + return; + } + + if(f < 0){ + // easier this way + isNegative = TRUE; + f = fabs(f); + } + + + // 1: stuff the sign. + if(isNegative) + stuffcmd( client, "-"); + + // 2: stuff the integer part. + intpart = floor(f); + stuffcmd_int( client, intpart, 0); + + + // 3: stuff the decimal point. + stuffcmd( client, "."); + + + // 4: stuff the decimal part. + decpart = mod( f, 1); + decpart = decpart * 10000; + stuffcmd_int( client, decpart, 10000); + +} + +.float fog_density; +.vector fog_color; +.entity fog_active_fog_trigger; +.string fog_info_entity; + +void SetFog(entity client, float density, vector color, float transitionTime = 0) +{ + client.fog_density = density; + client.fog_color = color; + stuffcmd(client, "\nfog "); + stuffcmd_float(client, density); + stuffcmd(client, " "); + stuffcmd_float(client, color_x); + stuffcmd(client, " "); + stuffcmd_float(client, color_y); + stuffcmd(client, " "); + stuffcmd_float(client, color_z); + if(transitionTime > 0) + { + stuffcmd(client, " "); + stuffcmd_float(client, transitionTime); + } + stuffcmd(client, "\n"); +} + +entity GetFogInfoEntity(entity e, .string field = fog_info_entity) +{ + if(e.field) + { + e = find(world, targetname, e.field); + if(e.classname == "info_fog") + { + return e; + } + } + return world; +} + +float FogPushSettingsFrom(entity client, entity source, float transitionTime) +{ + if(client.classname != "player") return FALSE; + source = GetFogInfoEntity(source); + if(!source) return FALSE; + if(source.fog_density || source.fog_color || (source.spawnflags & FORCE_SET_FOG)) + { + SetFog(client, source.fog_density, source.fog_color, transitionTime); + return TRUE; + } + return FALSE; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////// + +/*QUAKED info_fog (0.5 .5 .8) (-8 -8 -8) (8 8 8) +Fog value definition +wait: fog density +dest: fog color +*/ + +void info_fog() +{ + if(!self.fog_density) self.fog_density = 0.05; + else if(self.fog_color_x > 1.0 || self.fog_color_y > 1.0 || self.fog_color_z > 1.0) //Not in 0..1 range? + { + self.fog_color = self.fog_color * (1.0 / 255); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////// + +void trigger_fog_activate() +{ + if(self == other.fog_active_fog_trigger) return; + if (other.classname != "player") // Yoder add, 27/09/2020 to fix crash + return; + other.fog_active_fog_trigger = self; + FogPushSettingsFrom(other, self, self.delay); +} + +/*QUAKED trigger_fog (0.5 .5 .8) (? ? ?) (? ? ?) +Trigger to transition fog +fog_info_entity: info_fog that contains values this trigger should use. +delay: time to fade the transition over +*/ +void trigger_fog() +{ + // Fog is set on the server, so transitions and triggers work really bad in multiplayer. + // Just remove ourself if we're in multiplayer. + if(coop || deathmatch) + { + remove(self); + return; + } + + if(!self.delay) self.delay = 0.5; + self.use = trigger_fog_activate; + if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) ) + { + self.touch = trigger_fog_activate; + } + InitTrigger (); +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////// + +float trigger_fog_transition_touch_get_tween(entity pl) +{ + vector t = pl.origin - self.mins; + vector b = self.size; + float tween; + + if(self.style == 0) + { + tween = t_x / b_x; + } + else if(self.style == 1) + { + tween = t_y / b_y; + } + else if(self.style == 2) + { + tween = t_z / b_z; + } + + if(tween < 0) tween = 0; + if(tween > 1) tween = 1; + + return tween; +} + +void trigger_fog_transition_touch() +{ + if(other.classname != "player") return; + + float tween = trigger_fog_transition_touch_get_tween(other); + + entity fog1 = GetFogInfoEntity(self); + entity fog2 = GetFogInfoEntity(self, target); + + float density = ((1 - tween) * fog1.fog_density) + (tween * fog2.fog_density); + vector color = ((1 - tween) * fog1.fog_color) + (tween * fog2.fog_color); + + SetFog(other, density, color); +} + +/*QUAKED trigger_fog_transition (0.5 .5 .8) (? ? ?) (? ? ?) +Trigger to transition fog between two sides. +fog_info_entity: info_fog that contains values this trigger should use on the 'left' side. +target: info_fog that contains values this trigger should use on the 'right' side. +style: Which axis this trigger fades across. 0 = X, 1 = Y, 2 = Z +*/ +void trigger_fog_transition() +{ + // Fog is set on the server, so transitions and triggers work really bad in multiplayer. + // Just remove ourself if we're in multiplayer. + if(coop || deathmatch) + { + remove(self); + return; + } + + if(self.style < 0 || self.style > 2) + { + objerror("Invalid style for trigger_fog_transition. Needs to be 0, 1 or 2."); + } + self.touch = trigger_fog_transition_touch; + InitTrigger (); +} + diff --git a/quakec_mg1/frametick.qc b/quakec_mg1/frametick.qc new file mode 100644 index 0000000..d9fa42c --- /dev/null +++ b/quakec_mg1/frametick.qc @@ -0,0 +1,71 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +entity frametick_head; +.entity frametick_next; + +void RegisterFrameTickEntity(entity ent) +{ + if(!ent.tick) + { + entity oself = self; + self = ent; + objerror("Registered frame tick entity doesn't have tick function.\n"); + self = oself; + } + ent.frametick_next = frametick_head; + frametick_head = ent; +} + +void RemoveFrameTickEntity(entity rem) +{ + if(!rem.tick) return; + + if(rem == frametick_head){ + frametick_head = rem.frametick_next; + rem.frametick_next = world; + return; + } + + entity ent = frametick_head; + while(ent) + { + if(ent.frametick_next == rem) + { + ent.frametick_next = rem.frametick_next; + rem.frametick_next = world; + return; + } + ent = ent.frametick_next; + } +} + +void RunFrameTickEntities(float deltaTime) +{ + entity oself = self; + entity ent = frametick_head; + while(ent) + { + self = ent; + ent = self.frametick_next; + self.tick(deltaTime); + } + self = oself; +} diff --git a/quakec_mg1/func_bob.qc b/quakec_mg1/func_bob.qc new file mode 100644 index 0000000..188a6d3 --- /dev/null +++ b/quakec_mg1/func_bob.qc @@ -0,0 +1,134 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +const float FUNC_BOB_NONSOLID = 1; +const float FUNC_BOB_START_ON = 2; + +const float FUNC_BOB_THINKINTERVAL = 0.05; + +vector func_bob_GetPositionForAngle(float an) +{ + self.count += (an); + self.count = mod(self.count, 360); + + float dd = sin(self.count); + vector offs = self.dest * dd; + if(self.dest2) + { + float ddc = cos(self.count); + offs+= (self.dest2 * ddc); + } + + return offs; +} + +void func_bob_blocked() +{ + if(self.attack_finished > time) return; + T_Damage(other, self, self, self.dmg); + self.attack_finished = time + 0.5; +} + +/* + We have two different strategies for movement here. + For solid bobbers, we use the think function and set a velocity. This allows us to stand smoothly on the bobber and be blocked by it. + For non solid bobbers, the engine does not like having something nonsolid that moves apparently, so instead we just set its position each frame. +*/ + +// Used by non-solid bobbers +void func_bob_tick(float dt) +{ + vector offs = func_bob_GetPositionForAngle(self.avelocity_x * dt); + setorigin(self, offs); +} + +// Used by solid bobbers +void func_bob_think() +{ + vector offs = func_bob_GetPositionForAngle(self.avelocity_x * FUNC_BOB_THINKINTERVAL); + + vector diff = offs - self.origin; + self.velocity = diff * (1/FUNC_BOB_THINKINTERVAL); + + self.nextthink = self.ltime + FUNC_BOB_THINKINTERVAL; +} + +void func_bob_use() +{ + if(self.state) + { + if(self.solid) + { + self.velocity = '0 0 0'; + self.nextthink = -1; + } + else + { + RemoveFrameTickEntity(self); + } + } + else + { + if(self.solid) + { + func_bob_think(); + } + else + { + RegisterFrameTickEntity(self); + } + } + + self.state = 1 - self.state; +} + +void func_bob() +{ + setmodel(self, self.model); + setorigin(self, self.origin); + + if(!self.dest) self.dest = '0 0 64'; + if(!self.wait) self.wait = 10; + if(!self.dmg) self.dmg = 1; + + self.avelocity_x = 360 / self.wait; + self.count = 360 * self.delay; + + self.use = func_bob_use; + self.tick = func_bob_tick; + self.think = func_bob_think; + self.blocked = func_bob_blocked; + + + if(self.spawnflags & FUNC_BOB_NONSOLID) + { + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + } + else + { + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + } + + if(self.spawnflags & FUNC_BOB_START_ON) + { + func_bob_use(); + } +} \ No newline at end of file diff --git a/quakec_mg1/func_fade.qc b/quakec_mg1/func_fade.qc new file mode 100644 index 0000000..1cc86fc --- /dev/null +++ b/quakec_mg1/func_fade.qc @@ -0,0 +1,121 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +const float FUNC_FADE_STATE_HIDDEN = 0; +const float FUNC_FADE_STATE_VISIBLE = 1; + +const float FUNC_FADE_DISTANCE_MAXSPEED = 320; + +float func_fade_get_shortest_playerdistance() +{ + float dist = 32767; + float d; + + vector vpos; + + entity pl = find (world, classname, "player"); + while (pl != world) + { + vpos = pl.origin + pl.view_ofs; + d = vlen(self.oldorigin - vpos); + if(d < dist) dist = d; + pl = find (pl, classname, "player"); + } + return dist; +} + +void func_fade_set_nextthink_for_distance(float dist) +{ + self.nextthink = self.ltime + Clamp(dist / FUNC_FADE_DISTANCE_MAXSPEED, 0.05, 2.0); +} + +void func_fade_set_alpha(float a, float force=FALSE) +{ + a = Clamp(a, 0, 1); + if( !force && fabs(a - self.alpha) < 0.001) return; + if (self.state == FUNC_FADE_STATE_HIDDEN && a > 0) + { + self.state = FUNC_FADE_STATE_VISIBLE; + self.model = self.mdl; + } + else if (self.state == FUNC_FADE_STATE_VISIBLE && a == 0) + { + self.state = FUNC_FADE_STATE_HIDDEN; + self.model = string_null; + } + self.alpha = a; +} + +void func_fade_think() +{ + float dist = func_fade_get_shortest_playerdistance(); + float diff = dist - self.distance; + + if (diff < 0) + { + //Outside the transition zone, inside the circle + func_fade_set_alpha(1); + dist = -diff; + func_fade_set_nextthink_for_distance(dist); + } + else if(diff > self.lip) + { + //Outside the transition zone, outside the circle + func_fade_set_alpha(0); + dist = diff - self.lip; + func_fade_set_nextthink_for_distance(dist); + } + else + { + //In the transition zone + diff/= self.lip; + func_fade_set_alpha(1 - diff); + self.nextthink = self.ltime + 0.05; + } +} + +void func_fade() +{ + if(TRUE) + { + dprint("WARNING: A func_fade was spawned. This entity is obsolete, and was converted to a func_wall instead.\n") + func_wall(); + return; + } + + if(!self.distance) self.distance = 2048; //Radius of fade circle + if(!self.lip) self.lip = 256; // Lip around fade circle + + self.waitmax = self.distance + self.lip; + + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + + setmodel(self, self.model); + self.mdl = self.model; + self.oldorigin = self.mins + ((self.maxs - self.mins) * 0.5); + + self.state = FUNC_FADE_STATE_VISIBLE; + + self.think = func_fade_think; + self.nextthink = self.ltime + 0.2 + random() * 0.2; + + func_fade_set_alpha(0, TRUE); +} diff --git a/quakec_mg1/func_toss.qc b/quakec_mg1/func_toss.qc new file mode 100644 index 0000000..776707c --- /dev/null +++ b/quakec_mg1/func_toss.qc @@ -0,0 +1,265 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +const float TOSS_WAVE_ORIGIN = 1; +const float TOSS_REMOVE_ON_STOP = 2; +const float TOSS_NO_BOUNCE = 4; +const float TOSS_ONLY_REMOVE_IF_FAR_FALL = 8; +const string TOSS_WAVE_NETNAME = "_toss_origin"; + +void func_toss_think() +{ + if(vlen(self.velocity) < 0.01) + { + if(self.spawnflags & TOSS_REMOVE_ON_STOP) + { + if(self.spawnflags & TOSS_ONLY_REMOVE_IF_FAR_FALL) + { + if(vlen(self.oldorigin - self.origin) > 64) + { + remove(self); + return; + } + } + else + { + remove(self); + return; + } + } + self.velocity = '0 0 0'; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + return; + } + self.nextthink = time + 0.5; +} + +void func_toss_toss() +{ + vector min = self.mins + '4 4 0'; + vector max = self.maxs - self.dest2; + + self.solid = SOLID_BBOX; + if(self.spawnflags & TOSS_NO_BOUNCE) + self.movetype = MOVETYPE_TOSS; + else + self.movetype = MOVETYPE_BOUNCE; + self.size = self.size - '8 8 0'; + self.mins = min; + + self.velocity = self.movedir; + + if(self.noise) + { + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + + self.think = func_toss_think; + self.nextthink = time + 0.25; +} + + + +void func_toss_use() +{ + self.use = SUB_Null; + if(self.delay) + { + self.think = func_toss_toss; + self.nextthink = self.ltime + self.delay; + } + else + { + func_toss_toss(); + } +} + +void func_toss_linkcascade() +{ + entity o = find(world, netname, TOSS_WAVE_NETNAME); + float dist, mindist = 16384; + while(o) + { + if(o.targetname == self.targetname) + { + vector p = self.mins + self.size * 0.5; + p -= o.mins + o.size * 0.5; + dist = vlen(p) / o.speed; + if(dist < mindist) mindist = dist; + } + o = find(o, netname, TOSS_WAVE_NETNAME); + } + if(mindist < 16384) + self.delay = mindist; +} + +void func_toss() + { + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setmodel (self, self.model); + setorigin (self, self.origin); + self.oldorigin = self.origin; + + self.use = func_toss_use; + + if(!self.movedir) + { + self.movedir = '0 0 200'; + } + + if(self.dest) + { + self.movedir_x += crandom() * self.dest_x; + self.movedir_y += crandom() * self.dest_y; + self.movedir_z += crandom() * self.dest_z; + } + + if(self.spawnflags & TOSS_WAVE_ORIGIN) + { + self.netname = TOSS_WAVE_NETNAME; + if(!self.speed) self.speed = 200; + } + else if(!self.delay) + { + self.think = func_toss_linkcascade; + self.nextthink = self.ltime + 0.2; + } + + if(self.noise) + { + precache_sound (self.noise); + } + } + +// Shatter variant +void func_shatter_use() +{ + self.use = SUB_Null; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_TOSS; + self.velocity = self.movedir; +}; +void func_shatter() +{ + local float dist; // distance between origin and pos2 + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setmodel (self, self.model); + setorigin (self, self.origin); + + self.pos1 = (self.absmax - (self.size/2)); + + if(!self.pos2) + { + dprint("No pos2 provided for func_shatter\n"); + self.pos2 = self.origin - '0 0 100'; + } + + dist = vlen(self.pos1 - self.pos2); + dist = dist * dist; + + if(!self.speed) + { + self.speed = 200; + } + + self.speed = self.speed * 10000/dist; + + if(!self.wait) // use for random variance + self.wait = 10; + + // calculate velocity in advance + self.movedir = normalize(self.pos1 - self.pos2) * self.speed; + self.movedir_x = self.movedir_x + crandom() * self.wait; + self.movedir_y = self.movedir_y + crandom() * self.wait; + self.movedir_z = self.movedir_z + crandom() * self.wait; + + self.use = func_shatter_use; + + /* + if(self.noise) + { + precache_sound (self.noise); + } + */ + +} + +// debris variant +void() func_debris_fade = +{ + self.alpha = self.alpha - (1/self.wait) * frametime; + if (self.alpha < 0) + { + self.alpha = 1; + setorigin(self, self.oldorigin); + self.velocity = '0 0 0'; + self.velocity_x = crandom() * 8; + self.velocity_y = crandom() * 8; + self.movetype = MOVETYPE_TOSS; + self.nextthink = time + self.delay; + return; + } + else + self.nextthink = time + 0.01; // check again next frame +}; + +void() func_debris_wakeup = +{ + self.use = SUB_Null; + self.solid = SOLID_NOT; + self.velocity_x = crandom() * 8; + self.velocity_y = crandom() * 8; + self.movetype = MOVETYPE_TOSS; + self.think = func_debris_fade; + self.alpha = 1; + self.nextthink = time + self.delay; +}; + +void() func_debris_wakeup_wait = +{ + self.think = func_debris_wakeup; + self.nextthink = self.ltime + random(); +}; + +void() func_debris = +{ + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setmodel (self, self.model); + setorigin (self, self.origin); + self.oldorigin = self.origin; + self.use = func_debris_wakeup_wait; + + self.movedir = ' 0 0 200'; + + if (!self.delay) // how long the debris lives before fading + self.delay = 1.5; + + if (!self.wait) // amount of time (s) to fade out + self.wait = 0.1; + + self.alpha = 1; +}; + + + + diff --git a/quakec_mg1/horde.qc b/quakec_mg1/horde.qc new file mode 100644 index 0000000..d596413 --- /dev/null +++ b/quakec_mg1/horde.qc @@ -0,0 +1,2074 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +.float storednextthink; +.void() storedthink; +void() Countdown; +void() Wavecheck; +void() SpawnAmmo; +.float wave; // what wave of the horde mode we're on +.float fodder; // how many fodder squads to spawn +.float elites; // how many elite squads to spawn +.float bosses; // how many bosses to spawn +.float spawncount; // number of monster spawns +.float army; // Spawn Army? True/fallse +float(entity spawnpoint) CheckBlockedSpawn; + +float key_spawned; // TRUE/FALSE, determine if key has already been spawned, prevent dupes. + +float SPAWN_RESET_TIME = 5; // amount of time that a spawn point is considered occupied and not valid for use + +/* +================ +HordeGetPlayersAlive + +added Aug31 2021 +Returns a float for the number of living players +================ +*/ +float() HordeGetPlayersAlive = +{ + local float playercount; + local entity e; + + // count entities + e = find(world, classname, "player"); + while(e) + { + if (e.health > 0) + { + if (e.flags & FL_CLIENT) + playercount++; + else + dprint("Player is no longer a client!\n"); + } + e = find(e, classname, "player"); + } + dprint("there are "); + dprint(ftos(playercount)); + dprint(" players alive.\n"); + + return playercount; +}; + +/* +================ +HordeGetMonstersAlive + +added Oct28 2021 +Manually counts all living monsters +================ +*/ +float() HordeGetMonstersAlive = +{ + local float monstercount; + local entity e; + + // count monster entities + e = find(world, category, "monster"); + while(e) + { + if ((e.health > 0) && (e.classname != "monster_zombie")) + monstercount++; + + e = find(e, category, "monster"); + } + dprint("verification count. There are "); + dprint(ftos(monstercount)); + dprint(" monsters alive.\n"); + + return monstercount; +}; +/* +================ +HordeFindTarget + +added Aug31 2021 +Returns a random living player +================ +*/ +entity() HordeFindTarget = +{ + local float playercount = HordeGetPlayersAlive(); + + if (playercount == 0) + return world; + + local entity e; + local float rcount; // random value [0,count] + rcount = random() * playercount; + + playercount = 0; // reset for new count + e = find(world, classname, "player"); + while(e) + { + if (e.health > 0) + playercount++; + + if ((rcount <= playercount) && !(e.flags & FL_NOTARGET)) + return e; + else + e = find(e, classname, "player"); + } + return world; +}; + + +/*QUAKED info_monster_start START_OFF +if targeted, it will toggle between on or off, like lights +*/ +float SKIP_BLOCK_CHECK = 2; +void() monster_start_use = +{ + if (self.spawnflags & START_OFF) + self.spawnflags = self.spawnflags - START_OFF; + else + self.spawnflags = self.spawnflags + START_OFF; +}; + +void() info_monster_start = +{ + self.wait = FALSE; // used for checking if this is a valid spawn + self.use = monster_start_use; + setorigin (self, self.origin); + setsize(self, '-80 -80 0', '80 80 128'); +}; + +void() info_monster_start_ranged = // used for ogres and enforcers +{ + self.wait = FALSE; // used for checking if this is a valid spawn + self.use = monster_start_use; + setorigin (self, self.origin); + setsize(self, '-44 -44 0', '44 44 128'); +}; + +void() info_monster_start_flying = // used exclusively for scrags (wizards) +{ + self.wait = FALSE; // used for checking if this is a valid spawn + self.use = monster_start_use; + setorigin (self, self.origin); + setsize(self, '-80 -80 0', '80 80 128'); +}; + +void() info_monster_start_boss = // used for shalraths and shamblers +{ + self.wait = FALSE; // used for checking if this is a valid spawn + self.use = monster_start_use; + setorigin (self, self.origin); + setsize(self, '-44 -44 0', '44 44 128'); +}; + +// ENTITY +void() info_horde_ammo = +{ + //self.wait = FALSE; + self.think = SpawnAmmo; + self.nextthink = time + HORDE_START_DELAY + random() * 3; +}; + +// ENTITY +void() info_horde_item = +{ + self.wait = FALSE; +}; + +float KEY_FIRST = 1; +float KEY_SECOND = 2; +float KEY_THIRD = 4; +float KEY_FOURTH_PLUS = 8; + +// ENTITY +void() info_horde_key = +{ + self.wait = FALSE; +}; +//============================================================================ +void() PrecacheMonsters = +{ + // knight + precache_model ("progs/knight.mdl"); + precache_model ("progs/h_knight.mdl"); + + precache_sound ("knight/kdeath.wav"); + precache_sound ("knight/khurt.wav"); + precache_sound ("knight/ksight.wav"); + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + precache_sound ("knight/idle.wav"); + + // hellknight + precache_model2 ("progs/hknight.mdl"); + precache_model2 ("progs/k_spike.mdl"); + precache_model2 ("progs/h_hellkn.mdl"); + + + precache_sound2 ("hknight/attack1.wav"); + precache_sound2 ("hknight/death1.wav"); + precache_sound2 ("hknight/pain1.wav"); + precache_sound2 ("hknight/sight1.wav"); + precache_sound ("hknight/hit.wav"); // used by C code, so don't sound2 + precache_sound2 ("hknight/slash1.wav"); + precache_sound2 ("hknight/idle.wav"); + precache_sound2 ("hknight/grunt.wav"); + + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + + // dog + precache_model ("progs/h_dog.mdl"); + precache_model ("progs/dog.mdl"); + + precache_sound ("dog/dattack1.wav"); + precache_sound ("dog/ddeath.wav"); + precache_sound ("dog/dpain1.wav"); + precache_sound ("dog/dsight.wav"); + precache_sound ("dog/idle.wav"); + + // demon + precache_model ("progs/demon.mdl"); + precache_model ("progs/h_demon.mdl"); + + precache_sound ("demon/ddeath.wav"); + precache_sound ("demon/dhit2.wav"); + precache_sound ("demon/djump.wav"); + precache_sound ("demon/dpain1.wav"); + precache_sound ("demon/idle1.wav"); + precache_sound ("demon/sight2.wav"); + + // ogre + precache_model ("progs/ogre.mdl"); + precache_model ("progs/h_ogre.mdl"); + precache_model ("progs/grenade.mdl"); + + precache_sound ("ogre/ogdrag.wav"); + precache_sound ("ogre/ogdth.wav"); + precache_sound ("ogre/ogidle.wav"); + precache_sound ("ogre/ogidle2.wav"); + precache_sound ("ogre/ogpain1.wav"); + precache_sound ("ogre/ogsawatk.wav"); + precache_sound ("ogre/ogwake.wav"); + + // grunt + precache_model ("progs/soldier.mdl"); + precache_model ("progs/h_guard.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_sound ("soldier/death1.wav"); + precache_sound ("soldier/idle.wav"); + precache_sound ("soldier/pain1.wav"); + precache_sound ("soldier/pain2.wav"); + precache_sound ("soldier/sattck1.wav"); + precache_sound ("soldier/sight1.wav"); + + precache_sound ("player/udeath.wav"); // gib death + + // enforcer + precache_model2 ("progs/enforcer.mdl"); + precache_model2 ("progs/h_mega.mdl"); + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/idle1.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + + // shambler + precache_model ("progs/shambler.mdl"); + precache_model ("progs/s_light.mdl"); + precache_model ("progs/h_shams.mdl"); + precache_model ("progs/bolt.mdl"); + + precache_sound ("shambler/sattck1.wav"); + precache_sound ("shambler/sboom.wav"); + precache_sound ("shambler/sdeath.wav"); + precache_sound ("shambler/shurt2.wav"); + precache_sound ("shambler/sidle.wav"); + precache_sound ("shambler/ssight.wav"); + precache_sound ("shambler/melee1.wav"); + precache_sound ("shambler/melee2.wav"); + precache_sound ("shambler/smack.wav"); + + // shalrath + precache_model2 ("progs/shalrath.mdl"); + precache_model2 ("progs/h_shal.mdl"); + precache_model2 ("progs/v_spike.mdl"); + + precache_sound2 ("shalrath/attack.wav"); + precache_sound2 ("shalrath/attack2.wav"); + precache_sound2 ("shalrath/death.wav"); + precache_sound2 ("shalrath/idle.wav"); + precache_sound2 ("shalrath/pain.wav"); + precache_sound2 ("shalrath/sight.wav"); + + // wizard + precache_model ("progs/wizard.mdl"); + precache_model ("progs/h_wizard.mdl"); + precache_model ("progs/w_spike.mdl"); + + precache_sound ("wizard/hit.wav"); // used by c code + precache_sound ("wizard/wattack.wav"); + precache_sound ("wizard/wdeath.wav"); + precache_sound ("wizard/widle1.wav"); + precache_sound ("wizard/widle2.wav"); + precache_sound ("wizard/wpain.wav"); + precache_sound ("wizard/wsight.wav"); + + // zombies + precache_model ("progs/zombie.mdl"); + precache_model ("progs/h_zombie.mdl"); + precache_model ("progs/zom_gib.mdl"); + + precache_sound ("zombie/z_idle.wav"); + precache_sound ("zombie/z_idle1.wav"); + precache_sound ("zombie/z_shot1.wav"); + precache_sound ("zombie/z_gib.wav"); + precache_sound ("zombie/z_pain.wav"); + precache_sound ("zombie/z_pain1.wav"); + precache_sound ("zombie/z_fall.wav"); + precache_sound ("zombie/z_miss.wav"); + precache_sound ("zombie/z_hit.wav"); + precache_sound ("zombie/idle_w2.wav"); + + + // ammo + precache_model ("maps/b_shell1.bsp"); + precache_model ("maps/b_shell0.bsp"); + precache_model ("maps/b_nail1.bsp"); + precache_model ("maps/b_nail0.bsp"); + precache_model ("maps/b_rock1.bsp"); + precache_model ("maps/b_rock0.bsp"); + precache_model ("maps/b_batt1.bsp"); + precache_model ("maps/b_batt0.bsp"); + + // items + precache_model("maps/b_bh10.bsp"); // rotten health + precache_sound("items/r_item1.wav"); + precache_model("maps/b_bh25.bsp"); // regular health + precache_sound("items/health1.wav"); + precache_model ("progs/armor.mdl"); // regular armor + + // quad damage + precache_model ("progs/quaddama.mdl"); + precache_sound ("items/damage.wav"); + precache_sound ("items/damage2.wav"); + precache_sound ("items/damage3.wav"); + + // pentagram (invulnerability) + precache_model ("progs/invulner.mdl"); + precache_sound ("items/protect.wav"); + precache_sound ("items/protect2.wav"); + precache_sound ("items/protect3.wav"); + + // new horde sounds + precache_sound("horde/nyom.wav"); + + // precache keys + if (world.worldtype == WORLDTYPE_MEDIEVAL || world.worldtype == WORLDTYPE_HUB) + { + precache_sound ("misc/medkey.wav"); + precache_model("progs/w_g_key.mdl"); // gold key + precache_model("progs/w_s_key.mdl"); // silver key + } + if (world.worldtype == WORLDTYPE_METAL) + { + precache_sound ("misc/runekey.wav"); + precache_model ("progs/m_g_key.mdl"); + precache_model ("progs/m_s_key.mdl"); + } + if (world.worldtype == WORLDTYPE_BASE) + { + precache_sound2 ("misc/basekey.wav"); + precache_model2 ("progs/b_g_key.mdl"); + precache_model2 ("progs/b_s_key.mdl"); + } +}; +// ================================================================= +void() GibMonster = +{ + T_Damage(self, world, world, 4000); +}; +// ================================================================= +/* +================ +SpawnMonster +================ +*/ +entity(string class, vector org, vector temp_angles) SpawnMonster = +{ + local entity monster; + monster = spawn(); + + monster.angles = temp_angles; + monster.solid = SOLID_SLIDEBOX; + monster.movetype = MOVETYPE_STEP; + //monster.target = "horde_manager"; + monster.flags = monster.flags | FL_MONSTER; + //monster.alpha = 1; // only set monster alpha after they're a corpse + + local entity oself; // old self + + // perform enemy-type specific loading + if (class == "knight") + { + setmodel (monster, "progs/knight.mdl"); + setsize (monster, '-16 -16 -24', '16 16 40'); + org = org + '0 0 24'; // offset based on size + monster.classname = "monster_knight"; + monster.health = 75; + monster.th_stand = knight_stand1; + monster.th_walk = knight_walk1; + monster.th_run = knight_run1; + monster.th_melee = knight_atk1; + monster.th_pain = knight_pain; + monster.th_die = knight_die; + } + else if (class == "hellknight") + { + setmodel (monster, "progs/hknight.mdl"); + setsize (monster, '-16 -16 -24', '16 16 40'); + org = org + '0 0 24'; + monster.classname = "monster_hell_knight"; + monster.health = 250; + monster.th_stand = hknight_stand1; + monster.th_walk = hknight_walk1; + monster.th_run = hknight_run1; + monster.th_melee = hknight_melee; + monster.th_missile = hknight_magicc1; + monster.th_pain = hknight_pain; + monster.th_die = hknight_die; + } + else if (class == "dog") + { + setmodel (monster, "progs/dog.mdl"); + setsize (monster, '-32 -32 -24', '32 32 40'); + org = org + '0 0 24'; + monster.classname = "monster_dog"; + monster.health = 25; + monster.th_stand = dog_stand1; + monster.th_walk = dog_walk1; + monster.th_run = dog_run1; + monster.th_pain = dog_pain; + monster.th_die = dog_die; + monster.th_melee = dog_atta1; + monster.th_missile = dog_leap1; + } + else if (class == "demon") + { + setmodel (monster, "progs/demon.mdl"); + + setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX); + monster.classname = "monster_demon1"; + monster.health = 300; + org = org + '0 0 48'; + monster.th_stand = demon1_stand1; + monster.th_walk = demon1_walk1; + monster.th_run = demon1_run1; + monster.th_die = demon_die; + monster.th_melee = Demon_MeleeAttack; + monster.th_missile = demon1_jump1; + monster.th_pain = demon1_pain; + } + else if (class == "ogre") + { + setmodel (monster, "progs/ogre.mdl"); + setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX); + monster.classname = "monster_ogre"; + monster.health = 200; + org = org + '0 0 32'; + monster.th_stand = ogre_stand1; + monster.th_walk = ogre_walk1; + monster.th_run = ogre_run1; + monster.th_die = ogre_die; + monster.th_melee = ogre_melee; + monster.th_missile = ogre_nail1; + monster.th_pain = ogre_pain; + } + else if (class == "grunt") + { + setmodel (monster, "progs/soldier.mdl"); + setsize (monster, '-16 -16 -24', '16 16 40'); + monster.classname = "monster_army"; + monster.health = 30; + org = org + '0 0 24'; + monster.th_stand = army_stand1; + monster.th_walk = army_walk1; + monster.th_run = army_run1; + monster.th_missile = army_atk1; + monster.th_pain = army_pain; + monster.th_die = army_die; + } + else if (class == "enforcer") + { + setmodel (monster, "progs/enforcer.mdl"); + setsize (monster, '-16 -16 -24', '16 16 40'); + monster.classname = "monster_enforcer"; + monster.health = 80; + org = org + '0 0 24'; + monster.th_stand = enf_stand1; + monster.th_walk = enf_walk1; + monster.th_run = enf_run1; + monster.th_pain = enf_pain; + monster.th_die = enf_die; + monster.th_missile = enf_atk1; + } + else if (class == "shambler") + { + setmodel (monster, "progs/shambler.mdl"); + setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX); + monster.classname = "monster_shambler"; + monster.health = 600; + org = org + '0 0 32'; + monster.th_stand = sham_stand1; + monster.th_walk = sham_walk1; + monster.th_run = sham_run1; + monster.th_die = sham_die; + monster.th_melee = sham_melee; + monster.th_missile = sham_magic1; + monster.th_pain = sham_pain; + } + else if (class == "shalrath") + { + setmodel (monster, "progs/shalrath.mdl"); + setsize (monster, VEC_HULL2_MIN, VEC_HULL2_MAX); + monster.classname = "monster_shalrath"; + org = org + '0 0 32'; + monster.health = 400; + monster.th_stand = shal_stand; + monster.th_walk = shal_walk1; + monster.th_run = shal_run1; + monster.th_die = shalrath_die; + monster.th_pain = shalrath_pain; + monster.th_missile = shal_attack1; + } + else if (class == "wizard") + { + setmodel (monster, "progs/wizard.mdl"); + monster.flags = monster.flags | FL_FLY; // special behavior + monster.classname = "monster_wizard"; + setsize (monster, VEC_HULL_MIN, VEC_HULL_MAX); + org = org + '0 0 32'; + monster.health = 80; + monster.th_stand = wiz_stand1; + monster.th_walk = wiz_walk1; + monster.th_run = wiz_run1; + monster.th_missile = Wiz_Missile; + monster.th_pain = Wiz_Pain; + monster.th_die = wiz_die; + } + else if (class == "zombie") + { + setmodel (monster, "progs/zombie.mdl"); + setsize (monster, '-16 -16 -24', '16 16 40'); + monster.classname = "monster_zombie"; + org = org + '0 0 32'; + monster.health = 60; + monster.th_stand = zombie_stand1; + monster.th_walk = zombie_walk1; + monster.th_run = zombie_run1; + monster.th_pain = zombie_pain; + monster.th_die = zombie_die; + monster.th_missile = zombie_missile; + monster.target = string_null; // don't target anything! + } + else + { + dprint("ERROR: No monster provided"); + } + // finish the shared settings + setorigin (monster, org); + monster.origin_z = monster.origin_z + 1; // raise off floor a bit + + // temporarily swap self for next funcs + oself = self; + self = monster; + + if (class != "wizard") + droptofloor(); + + if (!walkmove(0,0)) + { + dprint ("walkmonster in wall at: "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + // resume normal self behavior + self = oself; + + monster.category = CATEGORY_MONSTER; + monster.takedamage = DAMAGE_AIM; + monster.ideal_yaw = monster.angles * '0 1 0'; + if (!monster.yaw_speed) + monster.yaw_speed = 20; + monster.view_ofs = '0 0 25'; + monster.use = monster_use; + + monster.flags = monster.flags | FL_MONSTER; + + monster.enemy = HordeFindTarget(); // find a random living player + //monster.enemy = find (world, classname, "player"); + monster.think = FoundTarget; + + //monster.nextthink = monster.nextthink + 0.1; + monster.nextthink = time + 0.1; + + spawn_tdeath_fast (monster.origin, monster); + //spawn_tfog(monster.origin); + + monster.owner = self; + if (monster.classname != "monster_zombie") // don't count zombies toward total goal + total_monsters = total_monsters + 1; + + + return monster; +}; + +void() horde_ammo_touch = +{ + local entity temp_player, t; + local float best_weapon; + + // early exit touch if not a player or not alive + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + // if the player was using their best weapon, change up to the new one if better + t = self; // hang on to the ammo entity for a moment + self = other; + best_weapon = W_BestWeapon(); + self = t; // set self back to ammo entity + + // if player is at max ammo for ammo's type, return + if (((self.weapon == 1) && (other.ammo_shells >= 100)) || // shotgun + ((self.weapon == 2) && (other.ammo_nails >= 200)) || // nailgun + ((self.weapon == 3) && (other.ammo_rockets >= 100)) || // rockets + ((self.weapon == 4) && (other.ammo_cells >= 100))) // cells + return; + + sprint(other, "$qc_got_item", self.netname); + + // ammo touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + // change to a better weapon if appropriate + if ( other.weapon == best_weapon ) + { + t = self; + self = other; + self.weapon = W_BestWeapon(); + W_SetCurrentAmmo (); + self = t; + } + + // loop through all players and give ammo + temp_player = find(world, classname, "player"); + while(temp_player) + { + if (temp_player.health > 0) // only give ammo to living players + { + if (self.weapon == 1) // shotgun + temp_player.ammo_shells += self.aflag; + else if (self.weapon == 2) // spikes + temp_player.ammo_nails += self.aflag; + else if (self.weapon == 3) // rockets + temp_player.ammo_rockets += self.aflag; + else if (self.weapon == 4) // cells + temp_player.ammo_cells += self.aflag; + + // temp swap and set bounds + t = other; + other = temp_player; + bound_other_ammo(); + other = t; + + // temp swap again and set current ammo + t = self; + self = temp_player; + W_SetCurrentAmmo(); + self = t; + } + temp_player = find(temp_player, classname, "player"); + } + + // remove + self.model = string_null; + self.solid = SOLID_NOT; + // AY dec06 2021 + //self.owner.wait = FALSE; The Old Way + self.owner.think = SpawnAmmo; + self.owner.nextthink = time + HORDE_AMMO_RESPAWN_DELAY; + // end AY + remove(self); +}; + +void() horde_health_touch = +{ + if (other.classname != "player") + return; + + if (!T_Heal(other, self.healamount, 0)) + return; + + sprint(other, "$qc_item_health", ftos(self.healamount)); + + // health touch sound + sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + self.model = string_null; + self.solid = SOLID_NOT; + self.owner.wait = FALSE; + remove(self); +}; + +void() horde_armor_touch = +{ + local float type, value, bit; + + if (other.health <= 0) + return; + if (other.classname != "player") + return; + + if (self.classname == "item_armor1") + { + type = 0.3; + value = 100; + bit = IT_ARMOR1; + } + if (self.classname == "item_armor2") + { + type = 0.6; + value = 150; + bit = IT_ARMOR2; + } + if (self.classname == "item_armorInv") + { + type = 0.8; + value = 200; + bit = IT_ARMOR3; + } + if (other.armortype*other.armorvalue >= type*value) + return; + + other.armortype = type; + other.armorvalue = value; + other.items = other.items - (other.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + bit; + + self.solid = SOLID_NOT; + self.model = string_null; + sprint(other, "$qc_item_armor"); +// armor touch sound + sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.owner.wait = FALSE; + remove(self); +}; + +void() horde_print_keys = +{ + dprint(ftos(keys_silver)); + dprint(" silver keys : "); + dprint(ftos(keys_gold)); + dprint(" gold keys\n"); +}; + +void(entity temp_player) horde_set_keys = +{ + // player has just respawned, reset their keys based on global key values + if (keys_silver > 0) + temp_player.items+= IT_KEY1; + + if (keys_gold > 0) + temp_player.items+= IT_KEY2; +}; + + +void(float key_item) horde_key_give_all = +{ + local entity temp_player; + temp_player = find(world, classname, "player"); + + while (temp_player) + { + temp_player.items = temp_player.items + key_item; + temp_player = find(temp_player, classname, "player"); + } + + horde_print_keys(); +}; + +void(float key_item) horde_key_remove_all = +{ + local entity temp_player; + temp_player = find(world, classname, "player"); + + while (temp_player) + { + temp_player.items = temp_player.items - key_item; + temp_player = find(temp_player, classname, "player"); + } + + horde_print_keys(); +}; + +void(float key_item)horde_key_give = +{ + if (key_item & IT_KEY1) + { + keys_silver++; + if (keys_silver == 1) + horde_key_give_all(IT_KEY1); + } + else if (key_item & IT_KEY2) + { + keys_gold++; + if (keys_gold == 1) + horde_key_give_all(IT_KEY2); + } + horde_print_keys(); +} + +void(float key_item)horde_key_spend = +{ + if (key_item & IT_KEY1) + { + keys_silver--; + if (keys_silver == 0) + horde_key_remove_all(IT_KEY1); + } + else if (key_item & IT_KEY2) + { + keys_gold--; + if (keys_gold == 0) + horde_key_remove_all(IT_KEY2); + } + horde_print_keys(); +} + +void() horde_key_touch = +{ + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (other.flags & FL_ISBOT) + return; + + sprint(other, "$qc_got_item", self.netname); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + horde_key_give(self.items); + + // find the horde manager and trigger the next wave + if (key_spawned) + { + dprint("key triggers next wave\n"); + horde_ent.wait = TRUE; + horde_ent.think = Countdown; + horde_ent.nextthink = time; + } + else + dprint("key shouldn't trigger next wave!\n"); + /* + local entity t = find(world, classname, "horde_manager"); + if (t != world) + { + t.wait = TRUE; + t.think = Countdown; + t.nextthink = time; + } + */ + remove(self); +} + +void() SpawnAmmo = +{ + local float r, is_big; + local entity item; + local vector pos; + pos = self.origin; + item = spawn(); + + // roll a d4, a 1 means big ammo. + is_big = (random() * 4) <= 1; + + if (is_big) + pos = pos - '16 16 0'; + else + pos = pos - '12 12 0'; + + // NEW: roll a d20, 1-8 is shells, 9-14 is spikes, 14-17 is rockets, 18-20 is cells + // OLD: roll a d12, 1-5 is shells, 6-10 is spikes, 11 is rockets, 12 is cells + /* + ITEM New % Old % + shells 40% 41.66% + spikes 30% 33.33% + rockets 20% 8.33 % + cells 10% 8.33 % + */ + r = random() * 20; + if (r <= 7) // shells + { + item.classname = "item_shells"; + if (is_big) + { + setmodel (item, "maps/b_shell1.bsp"); + item.aflag = 40; + } + else + { + setmodel (item, "maps/b_shell0.bsp"); + item.aflag = 20; + } + item.weapon = 1; + item.netname = "$qc_shells"; + } + else if (r <= 14) // spikes + { + item.classname = "item_spikes"; + if (is_big) + { + setmodel (item, "maps/b_nail1.bsp"); + item.aflag = 50; + } + else + { + setmodel (item, "maps/b_nail0.bsp"); + item.aflag = 25; + } + item.weapon = 2; + item.netname = "$qc_nails"; + } + else if (r <= 17) // rockets + { + item.classname = "item_rockets"; + if (is_big) + { + setmodel (item, "maps/b_rock1.bsp"); + item.aflag = 10; + } + else + { + setmodel (item, "maps/b_rock0.bsp"); + item.aflag = 5; + } + item.weapon = 3; + item.netname = "$qc_rockets"; + } + else // cells + { + item.classname = "item_cells"; + if (is_big) + { + setmodel (item, "maps/b_batt1.bsp"); + item.aflag = 12; + } + else + { + setmodel (item, "maps/b_batt0.bsp"); + item.aflag = 6; + } + item.weapon = 4; + item.netname = "$qc_cells"; + } + setorigin (item, pos + '0 0 1'); + setsize (item, '0 0 0', '32 32 56'); + item.owner = self; + self.wait = TRUE; + item.movetype = MOVETYPE_TOSS; + item.solid = SOLID_TRIGGER; + item.flags = FL_ITEM; + item.touch = horde_ammo_touch; + + spawn_tfog_silent(pos + '0 0 8'); // added aug30 + + //item.think = PlaceItem; + //item.nextthink = time + 0.1; +} +void() SpawnItem = +{ + local float r; + local entity item; + local vector pos; + item = spawn(); + + pos = self.origin; // set to origin, then offset as needed + + r = random() * 6; + if (r < 3) // rotten health + { + item.classname = "item_health"; + item.touch = horde_health_touch; + setmodel (item, "maps/b_bh10.bsp"); + item.noise = "items/r_item1.wav"; + item.healamount = 15; + item.healtype = 0; + setsize (item, '0 0 0', '32 32 56'); + //pos = pos - '16 16 0'; + } + if (r < 5) // regular health + { + item.classname = "item_health"; + item.touch = horde_health_touch; + setmodel (item, "maps/b_bh25.bsp"); + item.noise = "items/health1.wav"; + item.healamount = 25; + item.healtype = 1; + setsize (item, '0 0 0', '32 32 56'); + pos = pos - '16 16 0'; + } + else // armor + { + item.classname = "item_armor1"; + item.touch = horde_armor_touch; + item.armortype = 0.3; + item.armorvalue = 100; + setmodel (item, "progs/armor.mdl"); + item.skin = 0; + setsize (item, '-16 -16 0', '16 16 56'); + } + + setorigin (item, pos + '0 0 1'); + item.owner = self; + self.wait = TRUE; + item.movetype = MOVETYPE_TOSS; + item.solid = SOLID_TRIGGER; + item.flags = FL_ITEM; + //item.think = PlaceItem; + //item.nextthink = time + 0.1; + spawn_tfog_silent(pos + '0 0 8'); // added aug30 +}; + +/* Spawn Key +A key should spawn after every boss wave. + +*/ +void(float is_gold) SpawnKey = +{ + local entity item; + item = spawn(); + + if (is_gold) + { + item.items = IT_KEY2; + + // set model and sound based on worldtype + if (world.worldtype == WORLDTYPE_METAL) + { + item.netname = "$qc_gold_runekey"; + item.noise = "misc/runekey.wav"; + setmodel (item, "progs/m_g_key.mdl"); + centerprint_all("$qc_horde_gold_runekey_appears"); + } + else if (world.worldtype == WORLDTYPE_BASE) + { + item.netname = "$qc_gold_keycard"; + item.noise = "misc/basekey.wav"; + setmodel (item, "progs/b_g_key.mdl"); + centerprint_all("$qc_horde_gold_keycard_appears"); + } + else // assume medieval + { + item.netname = "$qc_gold_key"; + item.noise = "misc/medkey.wav"; + setmodel (item, "progs/w_g_key.mdl"); + centerprint_all("$qc_horde_gold_key_appears"); + } + } + else + { + item.items = IT_KEY1; + + // set model and sound based on worldtype + if (world.worldtype == WORLDTYPE_METAL) + { + item.netname = "$qc_silver_runekey"; + item.noise = "misc/runekey.wav"; + setmodel (item, "progs/m_s_key.mdl"); + centerprint_all("$qc_horde_silver_runekey_appears"); + } + else if (world.worldtype == WORLDTYPE_BASE) + { + item.netname = "$qc_silver_keycard"; + item.noise = "misc/basekey.wav"; + setmodel (item, "progs/b_s_key.mdl"); + centerprint_all("$qc_horde_silver_keycard_appears"); + } + else // assume medieval + { + item.netname = "$qc_silver_key"; + item.noise = "misc/medkey.wav"; + setmodel (item, "progs/w_s_key.mdl"); + centerprint_all("$qc_horde_silver_key_appears"); + } + + } + //item.noise = "misc/medkey.wav"; + setsize (item, '-16 -16 -25', '16 16 32'); + item.touch = horde_key_touch; + //item.think = PlaceItem; + //item.nextthink = time + 0.2; + item.target = "horde_manager"; // when used, retrigger the horde manager + item.flags = FL_ITEM; // make extra wide + //item.flags = item.flags & FL_NOBOTS; // AY 11 Nov 2021 so bots can't get keys + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + setorigin (item, self.origin + '0 0 32'); + item.velocity = '0 0 255'; + + // AY Feb22, 2022, rune keys shouldn't have light effects on them + if (world.worldtype != WORLDTYPE_METAL) + item.effects = EF_BRIGHTLIGHT; + + //dprint(" spawned key\n"); + spawn_tfog(item.origin); + key_spawned = 1; +}; + +void() SpawnGoldKey = +{ + SpawnKey(TRUE); +}; + +void() SpawnSilverKey = +{ + SpawnKey(FALSE); +}; + +// Function get a key given the current wave +// Wave 3 should return a key with spawnflag "KEY_FIRST" +// Wave 6 should return a key with spawnflag "KEY_SECOND" +// Wave 9 should return a key with spawnflag "KEY_THIRD" +// Wave 9+ should return a key with spawnflag "KEY_FOURTH_PLUS" +// If a key with the correct spawnflag isn't found, return any key +void() GetKey = +{ + local entity t, l; + t = find(world, classname, "info_horde_key"); + l = t; // save ref for later + + // check if any keys exist + if (t == world) + { + dprint("ERROR: No info_horde_key!\n"); + // continue as if player got the key + self.think = Countdown; + self.nextthink = time + 4; + return; + } + + while(t != world) + { + if (self.wave <= 3) // looking for KEY_FIRST + { + if (t.spawnflags & KEY_FIRST) + { + t.think = SpawnSilverKey; + t.nextthink = time; + return; + } + } + else if (self.wave <= 6) // looking for KEY_SECOND + { + if (t.spawnflags & KEY_SECOND) + { + t.think = SpawnSilverKey; + t.nextthink = time; + return; + } + } + else if (self.wave <= 9) // looking for KEY_THIRD + { + if (t.spawnflags & KEY_THIRD) + { + t.think = SpawnGoldKey; + t.nextthink = time; + return; + } + } + else // looking for KEY_FOURTH_PLUS + { + // check next key + if (t.spawnflags & KEY_FOURTH_PLUS) + { + t.think = SpawnSilverKey; + t.nextthink = time; + return; + } + } + t = find(t, classname, "info_horde_key"); + } + + // didn't find key with matching spawnflag, return any key + dprint("didn't find key with matching spawnflag. Return last: "); + dprint(l.classname); + + if (self.wave == 9) + l.think = SpawnGoldKey; + else + l.think = SpawnSilverKey; + l.nextthink = time; +}; + + +//============================================================================ +/* +SpawnSquad2 + +Yoder FEB02 2022 +*/ +void(string name, vector org, vector temp_angles) SpawnSquad2 = +{ + if (name == "3 grunts") + { + if (skill > 0) + { + SpawnMonster("grunt", org + '0 -40 0', temp_angles); + SpawnMonster("grunt", org + '40 40 0', temp_angles); + SpawnMonster("grunt", org + '-40 40 0', temp_angles); + } + else + { + SpawnMonster("grunt", org + '-40 0 0', temp_angles); + SpawnMonster("grunt", org + '40 0 0', temp_angles); + } + } + else if (name == "2 grunts, 1 dog") + { + SpawnMonster("dog", org + '44 0 0', temp_angles); + SpawnMonster("grunt", org + '-40 -40 0', temp_angles); + SpawnMonster("grunt", org + '-40 40 0', temp_angles); + } + else if (name == "2 dogs") + { + if (skill > 0) + { + SpawnMonster("dog", org + '-0 -44 0', temp_angles); + SpawnMonster("dog", org + '0 44 0', temp_angles); + } + else + SpawnMonster("dog", org + '-0 -44 0', temp_angles); + } + else if (name == "1 enforcer") + { + SpawnMonster("enforcer", org + '0 0 0', temp_angles); + } + else if (name == "2 enforcers") + { + if (skill > 0) + { + SpawnMonster("enforcer", org + '40 0 0', temp_angles); + SpawnMonster("enforcer", org + '-40 0 0', temp_angles); + } + else + SpawnMonster("enforcer", org + '0 0 0', temp_angles); + } + else if (name == "1 ogre") + { + SpawnMonster("ogre", org + '0 0 0', temp_angles); + } + else if (name == "2 knights") + { + if (skill > 0) + { + SpawnMonster("knight", org + '40 0 0', temp_angles); + SpawnMonster("knight", org + '-40 0 0', temp_angles); + } + else + { + SpawnMonster("knight", org + '40 0 0', temp_angles); + } + + } + else if (name == "2 zombies") + { + SpawnMonster("zombie", org + '40 0 0', temp_angles); + SpawnMonster("zombie", org + '-40 0 0', temp_angles); + } + else if (name == "1 wizard") + SpawnMonster("wizard", org + '0 0 0', temp_angles); + else if (name == "2 hellknights") + { + if (skill > 0) + { + SpawnMonster("hellknight", org + '0 40 0', temp_angles); + SpawnMonster("hellknight", org + '0 -40 0', temp_angles); + } + else + SpawnMonster("hellknight", org + '0 40 0', temp_angles); + } + else if (name == "2 knights, 1 hellknight") + { + SpawnMonster("hellknight", org + '40 0 0', temp_angles); + SpawnMonster("knight", org + '-40 40 0', temp_angles); + SpawnMonster("knight", org + '-40 -40 0', temp_angles); + } + else if (name == "3 wizards") + { + if (skill > 0) + { + SpawnMonster("wizard", org + '40 40 40', temp_angles); + SpawnMonster("wizard", org + '-40 40 40', temp_angles); + SpawnMonster("wizard", org + '-40 -40 40', temp_angles); + } + else + { + SpawnMonster("wizard", org + '40 40 40', temp_angles); + SpawnMonster("wizard", org + '-40 40 40', temp_angles); + } + } + else if (name == "shambler") + SpawnMonster("shambler", org, temp_angles); + else if (name == "double demon") + { + if (skill >= 3 && random() > 0.8) + { + // double shambler + SpawnMonster("shambler", org + '40 40 0', temp_angles); + SpawnMonster("shambler", org + '-40 -40 0', temp_angles); + } + else if (skill >= 1) + { + SpawnMonster("demon", org + '40 40 0', temp_angles); + SpawnMonster("demon", org + '-40 -40 0', temp_angles); + } + else + SpawnMonster("demon", org, temp_angles); + } + else if (name == "shalrath") + SpawnMonster("shalrath", org, temp_angles); + else + dprint("ERROR: unrecognized squad name!\n"); + + spawn_tfog(org); +}; + +/* +HordeFindSpawnpoint + +Yoder February 2nd 2022 +*/ +float HORDE_SQUAD_TYPE_NORMAL = 0; +float HORDE_SQUAD_TYPE_RANGED = 1; +float HORDE_SQUAD_TYPE_FLYING = 2; +float HORDE_SQUAD_TYPE_BOSS = 3; + +float HORDE_SQUAD_CAT_ERROR = -1; +float HORDE_SQUAD_CAT_FODDER = 0; +float HORDE_SQUAD_CAT_ELITE = 1; +float HORDE_SQUAD_CAT_BOSS = 2; + +entity(float squad_type)HordeFindSpawnpoint = +{ + local float spawnpoint, temp_spawncount, loopcount, randomspawn; + local float temp_spawncount_valid; // how many of the spawn points are valid + local entity t; + spawnpoint = 0; + loopcount = 0; + string squad_class; + + if (squad_type == HORDE_SQUAD_TYPE_BOSS) + squad_class = "info_monster_start_boss"; + else if (squad_type == HORDE_SQUAD_TYPE_FLYING) + squad_class = "info_monster_start_flying"; + else if (squad_type == HORDE_SQUAD_TYPE_RANGED) + squad_class = "info_monster_start_ranged"; + else + squad_class = "info_monster_start"; + + // STEP 1: count all spawns of spawn type + temp_spawncount = 0; + temp_spawncount_valid = 0; + + // count the spawnpoints for squad_class + t = find(world, classname, squad_class); + while (t) + { + temp_spawncount++; + if ((time > t.wait) && (!(t.spawnflags & START_OFF)) && + (!CheckBlockedSpawn(t))) + temp_spawncount_valid++; + t = find(t, classname, squad_class); + } + + // verify against 0 valid spawns + if (temp_spawncount_valid <= 0) + { + if (squad_type == HORDE_SQUAD_TYPE_NORMAL) + { + dprint("HordeFindSpawnPoint: FOUND 0 Valid spawns\n"); + return world; // already on fallback option, oops + } + else + { + squad_type = HORDE_SQUAD_TYPE_NORMAL; + squad_class = "info_monster_start"; + t = world; + loopcount = 1; + } + } + + // count again, but for Normal spawn points? + if (loopcount) + { + temp_spawncount = 0; + temp_spawncount_valid = 0; + + t = find(world, classname, squad_class); + while (t) + { + temp_spawncount++; + if ((time > t.wait) && (!(t.spawnflags & START_OFF)) && + (!CheckBlockedSpawn(t))) + temp_spawncount_valid++; + t = find(t, classname, squad_class); + } + } + + // verify against 0 valid spawns + if (temp_spawncount_valid <= 0) + { + dprint("HordeFindSpawnPoint: FOUND 0 Valid spawns after recheck\n"); + return world; // already on fallback option, oops + } + + // pick random spawncount + loopcount = 0; + randomspawn = temp_spawncount * random(); + dprint("randomspawn: ");dprint(ftos(randomspawn)); + dprint(" | whole count: ");dprint(ftos(temp_spawncount)); + dprint(" | valid count: ");dprint(ftos(temp_spawncount_valid));dprint("\n"); + t = find (world, classname, squad_class); + while(t) + { + spawnpoint++; + if (spawnpoint >= randomspawn) + { + if (!(t.spawnflags & START_OFF) && (time > t.wait) && (!(CheckBlockedSpawn(t)))) + { + dprint("picked spawnpoint: ");dprint(ftos(spawnpoint));dprint("\n"); + return t; + } + } + t = find(t, classname, squad_class); + + if (!t) + { + if (loopcount) + dprint("something has gone horribly wrong!\n"); + else + { + loopcount = 1; + + t = find(t, classname, squad_class); + spawnpoint = 0; + randomspawn = 0; // allow any valid spawn + } + } + } + return world; +}; +/* +SpawnWave2 + +Instead of finding a spawn point and then spawning a squad, +decide a squad, and then find a spawn point. +Yoder February 2nd 2022 + +*/ + +void() SpawnWave2 = +{ + local entity t, oself; // t is the temp spawn, oself is for temp storage of self + local float squad_type; + local string squad_name; // specific squad name + local float r; // random roll + local float squad_cat; // + + //STEP 1: determine what kind of monster is getting spawned + if (self.army) + { + + if (self.fodder > 0) + { + r = random() * 4; + if (r < 1) + squad_name = "3 grunts"; + else if (r < 2) + squad_name = "2 grunts, 1 dog"; + else if (r < 3.5) + squad_name = "2 dogs"; + else + { + squad_name = "1 enforcer"; + squad_type = HORDE_SQUAD_TYPE_RANGED; + } + + squad_cat = HORDE_SQUAD_CAT_FODDER; + } + else if (self.elites > 0) + { + squad_type = HORDE_SQUAD_TYPE_RANGED; + r = random() * 2; + if (r < 1.5) + squad_name = "2 enforcers"; + else + squad_name = "1 ogre"; + + squad_cat = HORDE_SQUAD_CAT_ELITE; + } + else if (self.bosses > 0) + { + dprint("ERROR: should not spawn boss squad in army wave\n"); + self.bosses = 0; + squad_cat = HORDE_SQUAD_CAT_ERROR; + } + } + else // NON-ARMY + { + if (self.fodder > 0) + { + r = random() * 4; + if (r < 2) + squad_name = "2 knights"; + else if (r < 3) + squad_name = "2 zombies"; + else + { + squad_name = "1 wizard"; + squad_type = HORDE_SQUAD_TYPE_FLYING; + } + + squad_cat = HORDE_SQUAD_CAT_FODDER; + } + else if (self.elites > 0) + { + r = random() * 4; + if (r < 1) + squad_name = "2 hellknights"; + else if (r < 2) + squad_name = "2 knights, 1 hellknight"; + else if (r < 3) + { + squad_name = "1 ogre"; + squad_type = HORDE_SQUAD_TYPE_RANGED; + } + else + { + squad_name = "3 wizards"; + squad_type = HORDE_SQUAD_TYPE_FLYING; + } + + squad_cat = HORDE_SQUAD_CAT_ELITE; + } + else if (self.bosses > 0) + { + squad_type = HORDE_SQUAD_TYPE_BOSS; + r = random() * 3; + if (r < 1) + squad_name = "shambler"; + else if (r < 2.5) + { + squad_name = "double demon"; + squad_type = HORDE_SQUAD_TYPE_NORMAL; + } + else + squad_name = "shalrath"; + + squad_cat = HORDE_SQUAD_CAT_BOSS; + } + } + + + //STEP 2: find a spawn point for the squad + t = HordeFindSpawnpoint(squad_type); + dprint("spawnpoint found was: ");dprint(t.classname);dprint("\n"); + if (t && (squad_cat != HORDE_SQUAD_CAT_ERROR)) + { + t.wait = time + SPAWN_RESET_TIME; // block spawnpoint from reuse for a duration + + // use targets + oself = self; + self = t; + SUB_UseTargets(); + self = oself; + + // set angles, just in case + if (!t.angles) + t.angles = '0 0 0'; + + SpawnSquad2(squad_name, t.origin, t.angles); + WriteByte(MSG_ALL, SVC_UPDATESTAT); + WriteByte(MSG_ALL, 12); // 12 = STAT_TOTALMONSTERS + WriteLong(MSG_ALL, total_monsters); + + if (squad_cat == HORDE_SQUAD_CAT_FODDER) + self.fodder--; + else if (squad_cat == HORDE_SQUAD_CAT_ELITE) + self.elites--; + else if (squad_cat == HORDE_SQUAD_CAT_BOSS) + self.bosses--; + + if ((self.fodder + self.elites + self.bosses) <= 0) // max spawns hit + { + dprint("wave spawn completed!\n"); + self.wait = FALSE; + self.think = Wavecheck; + self.nextthink = time + 30; + } + else + { + // normal spawnwave wait time + self.think = SpawnWave2; + self.nextthink = time + 1 + random() + 1; + } + } + else + { + dprint("no valid spawns, wait a moment\n"); + self.think = SpawnWave2; + self.nextthink = time + 1; + } +}; + +/* SpawnWavePrep +called once at the countdown starts +determines the number of squads by type +also spawns ammo and items +*/ +float() SpawnWavePrep = +{ + local entity p; + local float playercount, playerscalar; + // Reset Key Spawn + key_spawned = FALSE; + + // Respawn dead players! + if (HordeGetPlayersAlive() < 1) + { + dprint("WARNING: Last player died at round end, don't start new round!\n"); + return 0; + } + + // Items + p = find (world, classname, "info_horde_item"); + while (p) + { + if (!p.wait) + { + p.think = SpawnItem; + p.nextthink = time + random() * 2; + } + p = find (p, classname, "info_horde_item"); + } + + self.wave++; + dprint("wave "); + dprint(ftos(self.wave)); + dprint("\n"); + + // See spreadsheet for notes on squad scaling + + // AY FEB07 scaling start wave based on difficulty + local float temp_wave; + if (skill >= 3) + temp_wave = self.wave + 6; + else if (skill >= 2) + temp_wave = self.wave + 3; + else + temp_wave = self.wave; + + // Determine if Army wave + if ((temp_wave + 2) % 3 == 0) + self.army = TRUE; + else + self.army = FALSE; + + // player scalar + playercount = HordeGetPlayersAlive(); + if (playercount >= 4) + playerscalar = 2; + else if (playercount >= 3) + playerscalar = 1.5; + else if (playercount >= 2) + playerscalar = 1.25; + else + playerscalar = 1; + + // Boss Count + if (temp_wave % 3 == 0) + { + self.bosses = floor((temp_wave+1)/4); + } + else if ((skill > 1) && (!self.army) && (temp_wave > 9)) + { + //AY feb07, some extra spice + self.bosses = floor((temp_wave+1)/8); + } + + // Elite Count + self.elites = ceil((temp_wave - 1)/3) - floor(self.bosses/2); + self.elites = floor(self.elites * playerscalar); + + // Fodder Count + self.fodder = (temp_wave + 2) - (self.bosses * 2 + self.elites); + self.fodder = floor(self.fodder * playerscalar); + + // Trigger map entities + local entity stemp; + p = find (world, targetname, self.target); + while(p) + { + stemp = self; + self = p; + if (self.use != SUB_Null) + { + if (self.use) + self.use (); + } + self = stemp; + p = find (p, targetname, self.target); + } + + self.wait = TRUE; + return 1; +} + +void() Countdown4 = +{ + if (self.wave % 3 == 0) + centerprint_all("$qc_horde_boss_wave"); + else + centerprint_all("$qc_horde_fight"); + SpawnWave2(); + sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE); +}; +void() Countdown3 = +{ + centerprint_all("1"); + self.think = Countdown4; + self.nextthink = time + 1; + sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE); +}; +void() Countdown2 = +{ + centerprint_all("2"); + self.think = Countdown3; + self.nextthink = time + 1; + sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE); +}; +void() Countdown = +{ + if (!SpawnWavePrep()) + return; + + centerprint_all("3"); + self.think = Countdown2; + self.nextthink = time + 1; + + sound(self, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NONE); +}; + +void() RespawnAllPlayers = +{ + local entity p, oself; + p = find (world, classname, "player"); + while (p) + { + if (p.deadflag > 0) + { + oself = self; + self = p; + horde_respawn_teammate(); + self = oself; + //p.think = horde_respawn_teammate; + //p.nextthink = time; // next frame + } + p = find (p, classname, "player"); + } +}; + +/* +When each enemy spawned by the horde manager dies, they trigger self.owner.use, which is wavecheck. +Wavecheck sees if all spawned monsters are dead, so it can determine if it needs to start a new wave. +*/ +void() Wavecheck = +{ + if (self.wait) // still in the process of spawning monsters, don't wavecheck + return; + + self.think = Wavecheck; + self.nextthink = time + 10; + + dprint("\n===============\n"); + dprint("checking kills: "); + dprint(ftos(killed_monsters)); + dprint(" of "); + dprint(ftos(total_monsters)); + dprint("\n"); + + // debug checking last monster position + if (killed_monsters + 3 >= total_monsters) + { + local entity t; + t = find(world, category, CATEGORY_MONSTER); + while(t) + { + if (t.health > 0) + { + dprint("Position of monster: "); + dprint(t.classname); + dprint(" : "); + dprint(vtos(t.origin)); + dprint("\n"); + } + else + t.category = string_null; + t = find(t, category, CATEGORY_MONSTER); + } + } + + // Early exit for kill count + if ((self.wave % 3 == 0) || (self.wave < 3)) // Be exact? + { + dprint("check monsters killed as boss wave\n"); + if (HordeGetMonstersAlive() > 0) // testing the new way + return; + //if (killed_monsters < total_monsters) + // return; + } + else + { + dprint("check monsters killed as any other wave\n"); + if (HordeGetMonstersAlive() > 5) + return; + //if (killed_monsters < (total_monsters -5)) + // return; + } + + // Made it this far, means wave completed successfully + dprint("wavecheck looks good! Respawning players\n"); + RespawnAllPlayers(); + + self.wait = 1; + self.think = Countdown; + + if (self.wave % 3 == 0) // it was a boss wave + { + if(!key_spawned) // key was already spawned, don't spawn another yet! + GetKey(); + + self.nextthink = time + 20; // in case the players are slow to find the key + } + else + { + self.nextthink = time; + } + dprint("wavecheck now completed.\n"); +}; + +// setup horde rules +void() SetHorde = +{ + dprint("TEST: It is September 23, 2021!\n"); // yoder sanity test + self.think = Countdown; + self.nextthink = time + 1; +} + +// define entity +void() horde_manager = +{ + // AY, Dec06 2021, auto set horde cvar + if (!cvar("horde") && !deathmatch) + cvar_set("horde", "1"); + // end AY + + PrecacheMonsters(); + + horde_ent = self; // set global reference to the horde manager + + if (!self.target) + self.target = "horde_event"; // default name + + self.targetname = "horde_manager"; + self.think = SetHorde; + self.nextthink = time + HORDE_START_DELAY; + self.use = Wavecheck; + + self.wait = 1; // waiting before next wave? + self.delay = 9; // max number of squads to spawn in a wave + self.wave = 0; + + //if (!horde) + // horde = 1; // in case CVAR isn't already set + +}; + +void() PowerupFade = +{ + if (self.alpha > 0) + { + self.alpha = self.alpha - 0.25 * frametime; + self.nextthink = time; // think next frame + } + else + remove(self); +} + +void() horde_powerup_think = +{ + if (self.velocity_z < 0) + { + dprint("WARNING: Powerup fell out of world. Remove\n"); + remove(self); + } + else + { + self.alpha = 1; + self.think = PowerupFade; + self.nextthink = time; + } +}; + +void() horde_spawn_powerup = +{ + if (!powerup_chance) + powerup_chance = DEFAULT_POWERUP_CHANCE; + + if (random() < powerup_chance) // "if(1)" to guarantee powerup drop + { + dprint("powerup chance was: "); + dprint(ftos(powerup_chance)); + dprint("\n"); + + powerup_chance = DEFAULT_POWERUP_CHANCE; + + local entity powerup = spawn(); + setorigin (powerup, self.origin + '0 0 0'); + + //setsize (powerup, '-16 -16 -24', '16 16 32'); + powerup.flags = FL_ITEM; + powerup.solid = SOLID_TRIGGER; + powerup.movetype = MOVETYPE_BOUNCE; + powerup.velocity = '0 0 300'; + + //powerup.velocity_x = crandom() * 64; + //powerup.velocity_y = crandom() * 64; + powerup.touch = powerup_touch; + powerup.think = horde_powerup_think; + powerup.nextthink = time + 10; + + if (random() < 0.25) // spawn pentagram (invulnerability) + { + powerup.noise = "items/protect.wav"; + setmodel(powerup, "progs/invulner.mdl"); + powerup.netname = "$qc_pentagram_of_protection"; + powerup.items = IT_INVULNERABILITY; + powerup.classname = "item_artifact_invulnerability"; + } + else // spawn quad damage + { + powerup.noise = "items/damage.wav"; + setmodel(powerup, "progs/quaddama.mdl"); + powerup.netname = "$qc_quad_damage"; + powerup.items = IT_QUAD; + powerup.classname = "item_artifact_super_damage"; + } + //powerup.effects = EF_DIMLIGHT; + + setsize (powerup, '-12 -12 -12', '12 12 12'); + } + else + powerup_chance = powerup_chance + POWERUP_CHANCE_GAIN; +}; + +// remote wavecheck +// this is to trigger a wavecheck from outside of horde mode +void() remote_wavecheck = +{ + if(!horde_ent || (intermission_running)) + { + dprint("no wavecheck."); + if (!horde_ent) + dprint(" no horde ent found."); + if (intermission_running) + dprint(" intermission running."); + dprint("\n\n"); + return; + } + dprint("remote wavecheck from: "); + dprint(self.classname); + dprint("\n"); + local entity stemp = self; // the entity that is triggering the remote wavecheck + self = horde_ent; + self.use(); + + self = stemp; // set self back to whatever it was +}; + +/* CheckBlockedSpawn +AY Feb24, 2022 +Checks the horde spawn against all living players +Returns true/false if spawn is blocked. +*/ +float(entity spawnpoint) CheckBlockedSpawn = +{ + local float blocked = FALSE; + local entity p; + local vector p_mins, p_maxs, s_mins, s_maxs; + + if (spawnpoint.spawnflags & SKIP_BLOCK_CHECK) + return FALSE; + + p = find(world, classname, "player"); + while(p && !blocked) + { + if ((p.health > 0) && (p.deadflag <= 0)) + { + p_mins = p.origin + p.mins; + p_maxs = p.origin + p.maxs; + s_mins = spawnpoint.origin + spawnpoint.mins; + s_maxs = spawnpoint.origin + spawnpoint.maxs; + + if (((p_maxs_x > s_mins_x) && (p_mins_x < s_maxs_x)) && + ((p_maxs_y > s_mins_y) && (p_mins_y < s_maxs_y)) && + ((p_maxs_y > s_mins_y) && (p_mins_y < s_maxs_y))) + { + dprint("player blocking spawn\n"); + blocked = TRUE; + } + } + p = find(p, classname, "player"); + } + return blocked; +}; \ No newline at end of file diff --git a/quakec_mg1/items.qc b/quakec_mg1/items.qc new file mode 100644 index 0000000..5854712 --- /dev/null +++ b/quakec_mg1/items.qc @@ -0,0 +1,1426 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() W_SetCurrentAmmo; +/* ALL LIGHTS SHOULD BE 0 1 0 IN COLOR ALL OTHER ITEMS SHOULD +BE .8 .3 .4 IN COLOR */ + + +void() SUB_regen = +{ + self.model = self.mdl; // restore original model + self.solid = SOLID_TRIGGER; // allow it to be touched again + sound (self, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); // play respawn sound + setorigin (self, self.origin); +}; + + +/*QUAKED noclass (0 0 0) (-8 -8 -8) (8 8 8) +prints a warning message when spawned +*/ +void() noclass = +{ + dprint ("noclass spawned at"); + dprint (vtos(self.origin)); + dprint ("\n"); + remove (self); +}; + + + +/* +============ +PlaceItem + +plants the object on the floor +============ +*/ +void() PlaceItem = +{ + local float oldz; + + self.mdl = self.model; // so it can be restored on respawn + self.flags = FL_ITEM; // make extra wide + self.solid = SOLID_TRIGGER; + if(self.spawnflags & DROPTOFLOOR_DISABLE) + { + self.movetype = MOVETYPE_NONE; + } + else + { + self.movetype = MOVETYPE_TOSS; + } + self.velocity = '0 0 0'; + self.origin_z = self.origin_z + 6; + oldz = self.origin_z; + if (!droptofloor()) + { + dprint ("Bonus item fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + return; + } +}; + +/* +============ +StartItem + +Sets the clipping size and plants the object on the floor +============ +*/ +void() StartItem = +{ + INHIBIT_COOP + + if (RemovedOutsideCoop()) return; + + self.nextthink = time + 0.2; // items start after other solids + self.think = PlaceItem; +}; + +/* +========================================================================= + +HEALTH BOX + +========================================================================= +*/ +// +// T_Heal: add health to an entity, limiting health to max_health +// "ignore" will ignore max_health limit +// +float (entity e, float healamount, float ignore) T_Heal = +{ + if (e.health <= 0) + return 0; + if ((!ignore) && (e.health >= other.max_health)) + return 0; + healamount = ceil(healamount); + + // AY Dec13 2021, horde rune of hunger + if (cvar("horde") && (serverflags & 2)) + e.hunger_time = time + HUNGER_MAX; + + e.health = e.health + healamount; + if ((!ignore) && (e.health >= other.max_health)) + e.health = other.max_health; + + if (e.health > 250) + e.health = 250; + return 1; +}; + +/*QUAKED item_health (.3 .3 1) (0 0 0) (32 32 32) rotten megahealth +Health box. Normally gives 25 points. +Rotten box heals 5-10 points, +megahealth will add 100 health, then +rot you down to your maximum health limit, +one point per second. +*/ + +float H_ROTTEN = 1; +float H_MEGA = 2; +.float healamount, healtype; +void() health_touch; +void() item_megahealth_rot; + +void() item_health = +{ + self.touch = health_touch; + + if (self.spawnflags & H_ROTTEN) + { + precache_model("maps/b_bh10.bsp"); + + precache_sound("items/r_item1.wav"); + setmodel(self, "maps/b_bh10.bsp"); + self.noise = "items/r_item1.wav"; + self.healamount = 15; + self.healtype = 0; + } + else + if (self.spawnflags & H_MEGA) + { + precache_model("maps/b_bh100.bsp"); + precache_sound("items/r_item2.wav"); + setmodel(self, "maps/b_bh100.bsp"); + self.noise = "items/r_item2.wav"; + self.healamount = 100; + self.healtype = 2; + } + else + { + precache_model("maps/b_bh25.bsp"); + precache_sound("items/health1.wav"); + setmodel(self, "maps/b_bh25.bsp"); + self.noise = "items/health1.wav"; + self.healamount = 25; + self.healtype = 1; + } + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +void() health_touch = +{ + if (other.classname != "player") + return; + +#ifdef COOP_RESPAWN_ITEMS_FOR_PLAYERS + if (coop && other == self.enemy && self.wait <= 0) //In coop, don't allow the same player to pick up all the respawns. + return; + self.enemy = other; // Store this player for coop +#endif + + if (self.healtype == 2) // Megahealth? Ignore max_health... + { + if (other.health >= 250) + return; + if (!T_Heal(other, self.healamount, 1)) + return; + } + else + { + if (!T_Heal(other, self.healamount, 0)) + return; + } + + sprint(other, "$qc_item_health", ftos(self.healamount)); + +// health touch sound + sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + + stuffcmd (other, "bf\n"); + + self.model = string_null; + self.solid = SOLID_NOT; + + // Megahealth = rot down the player's super health + if (self.healtype == 2) + { + other.items = other.items | IT_SUPERHEALTH; + self.nextthink = time + 5; + self.think = item_megahealth_rot; + self.owner = other; + } + else + { + if (deathmatch != 2) // deathmatch 2 is the silly old rules + { + if (deathmatch) + { + self.nextthink = time + 20; + self.think = SUB_regen; + } + } + +#ifdef COOP_RESPAWN_ITEMS_FOR_PLAYERS + if(coop && self.wait <= 0) + { + self.cnt++; + if(self.cnt < num_players) //Not been picked up by all players yet. + { + self.nextthink = time + 1; + self.think = SUB_regen; // Respawn almost immediately + } + else + { + self.enemy = world; + self.cnt = 0; + self.nextthink = time + 6 * 60; + self.think = SUB_regen; // Respawn after 6 mins in case the players get stuck on something hard. + } + } + else +#endif + + if (self.wait) + { + self.nextthink = time + self.wait; + self.think = SUB_regen; + self.enemy = world; + } + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + +void() item_megahealth_rot = +{ + other = self.owner; + + if (other.health > other.max_health) + { + other.health = other.health - 1; + self.nextthink = time + 1; + return; + } + +// it is possible for a player to die and respawn between rots, so don't +// just blindly subtract the flag off + other.items = other.items - (other.items & IT_SUPERHEALTH); + + if (deathmatch && deathmatch != 2) // deathmatch 2 is silly old rules + { + self.nextthink = time + 20; + self.think = SUB_regen; + } +}; + +/* +=============================================================================== + +ARMOR + +=============================================================================== +*/ + +void() armor_touch = +{ + local float type, value, bit; + + if (other.health <= 0) + return; + if (other.classname != "player") + return; + +#ifdef COOP_RESPAWN_ITEMS_FOR_PLAYERS + if (coop && other == self.enemy && self.wait <= 0) //In coop, don't allow the same player to pick up all the respawns. + return; + self.enemy = other; // Store this player for coop +#endif + + if (self.classname == "item_armor1") + { + type = 0.3; + value = 100; + bit = IT_ARMOR1; + } + if (self.classname == "item_armor2") + { + type = 0.6; + value = 150; + bit = IT_ARMOR2; + } + if (self.classname == "item_armorInv") + { + type = 0.8; + value = 200; + bit = IT_ARMOR3; + } + if (other.armortype*other.armorvalue >= type*value) + return; + + other.armortype = type; + other.armorvalue = value; + other.items = other.items - (other.items & (IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + bit; + + self.solid = SOLID_NOT; + self.model = string_null; + +#ifdef COOP_RESPAWN_ITEMS_FOR_PLAYERS + if(coop && self.wait <= 0) + { + self.cnt++; + if(self.cnt < num_players) //Not been picked up by all players yet. + { + self.nextthink = time + 1; + self.think = SUB_regen; // Respawn almost immediately + } + else + { + self.enemy = world; + self.cnt = 0; + self.nextthink = time + 8 * 60; + self.think = SUB_regen; // Respawn after 8 mins in case the players get stuck on something hard. + } + } + else +#endif + + if (deathmatch && deathmatch != 2) + { + self.nextthink = time + 20; + self.think = SUB_regen; + } + else if (self.wait) + { + self.enemy = world; + self.nextthink = time + self.wait; + self.think = SUB_regen; + } + + + sprint(other, "$qc_item_armor"); + // armor touch sound + sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_armor1 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armor1 = +{ + self.touch = armor_touch; + self.armortype = 0.3; + self.armorvalue = 100; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 0; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED item_armor2 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armor2 = +{ + self.touch = armor_touch; + self.armortype = 0.6; + self.armorvalue = 150; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 1; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED item_armorInv (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armorInv = +{ + self.touch = armor_touch; + self.armortype = 0.8; + self.armorvalue = 200; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 2; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/* +=============================================================================== + +WEAPONS + +=============================================================================== +*/ +void() bound_other_ammo = +{ + if (other.ammo_shells > 100) + other.ammo_shells = 100; + if (other.ammo_nails > 200) + other.ammo_nails = 200; + if (other.ammo_rockets > 100) + other.ammo_rockets = 100; + if (other.ammo_cells > 100) + other.ammo_cells = 100; +}; + + +float(float w) RankForWeapon = +{ + if (w == IT_LIGHTNING) + return 1; + if (w == IT_ROCKET_LAUNCHER) + return 2; + if (w == IT_SUPER_NAILGUN) + return 3; + if (w == IT_GRENADE_LAUNCHER) + return 4; + if (w == IT_SUPER_SHOTGUN) + return 5; + if (w == IT_NAILGUN) + return 6; + return 7; +}; + +/* +============= +Deathmatch_Weapon + +Deathmatch weapon change rules for picking up a weapon + +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; +============= +*/ +void(float old, float new) Deathmatch_Weapon = +{ + local float or, nr; + +// change self.weapon if desired + or = RankForWeapon (self.weapon); + nr = RankForWeapon (new); + if ( nr < or ) + self.weapon = new; +}; + +/* +============= +weapon_touch +============= +*/ +float() W_BestWeapon; +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon; + +void() weapon_touch = +{ + local float hadammo, best, new, old; + local entity stemp; + local float leave; + + if (!(other.flags & FL_CLIENT)) + return; + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + if (coop || deathmatch == 2 || deathmatch == 3 || deathmatch == 5) + leave = 1; + else + leave = 0; + + if (self.classname == "weapon_nailgun") + { + if (leave && (other.items & IT_NAILGUN) ) + return; + hadammo = other.ammo_nails; + new = IT_NAILGUN; + other.ammo_nails = other.ammo_nails + 30; + } + else if (self.classname == "weapon_supernailgun") + { + if (leave && (other.items & IT_SUPER_NAILGUN) ) + return; + hadammo = other.ammo_rockets; + new = IT_SUPER_NAILGUN; + other.ammo_nails = other.ammo_nails + 30; + } + else if (self.classname == "weapon_supershotgun") + { + if (leave && (other.items & IT_SUPER_SHOTGUN) ) + return; + hadammo = other.ammo_rockets; + new = IT_SUPER_SHOTGUN; + other.ammo_shells = other.ammo_shells + 5; + } + else if (self.classname == "weapon_rocketlauncher") + { + if (leave && (other.items & IT_ROCKET_LAUNCHER) ) + return; + hadammo = other.ammo_rockets; + new = IT_ROCKET_LAUNCHER; + other.ammo_rockets = other.ammo_rockets + 5; + } + else if (self.classname == "weapon_grenadelauncher") + { + if (leave && (other.items & IT_GRENADE_LAUNCHER) ) + return; + hadammo = other.ammo_rockets; + new = IT_GRENADE_LAUNCHER; + other.ammo_rockets = other.ammo_rockets + 5; + } + else if (self.classname == "weapon_lightning") + { + if (leave && (other.items & IT_LIGHTNING) ) + return; + hadammo = other.ammo_rockets; + new = IT_LIGHTNING; + other.ammo_cells = other.ammo_cells + 15; + } + else + objerror ("weapon_touch: unknown classname"); + + sprint(other, "$qc_got_item", self.netname); + // weapon touch sound + sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + bound_other_ammo (); + +// change to the weapon + old = other.items; + other.items = other.items | new; + + stemp = self; + self = other; + + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (!deathmatch) + self.weapon = new; + else + Deathmatch_Weapon (old, new); + } + + W_SetCurrentAmmo(); + + self = stemp; + + // Moved up so the targets are fired even in coop, but only once. + activator = other; + SUB_UseTargets(); // fire all targets / killtargets + + if (leave) + { + self.target = string_null; + return; + } + +// remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + if (deathmatch && deathmatch != 2) + { + self.nextthink = time + 30; + self.think = SUB_regen; + } + else if (self.wait) + { + self.nextthink = time + self.wait; + self.think = SUB_regen; + } + +}; + + +/*QUAKED weapon_supershotgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_supershotgun = +{ + precache_model ("progs/g_shot.mdl"); + setmodel (self, "progs/g_shot.mdl"); + self.weapon = IT_SUPER_SHOTGUN; + self.netname = "$qc_double_shotgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_nailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_nailgun = +{ + precache_model ("progs/g_nail.mdl"); + setmodel (self, "progs/g_nail.mdl"); + self.weapon = IT_NAILGUN; + self.netname = "$qc_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_supernailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_supernailgun = +{ + precache_model ("progs/g_nail2.mdl"); + setmodel (self, "progs/g_nail2.mdl"); + self.weapon = IT_SUPER_NAILGUN; + self.netname = "$qc_super_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_grenadelauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_grenadelauncher = +{ + precache_model ("progs/g_rock.mdl"); + setmodel (self, "progs/g_rock.mdl"); + self.weapon = IT_GRENADE_LAUNCHER; + self.netname = "$qc_grenade_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_rocketlauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_rocketlauncher = +{ + precache_model ("progs/g_rock2.mdl"); + setmodel (self, "progs/g_rock2.mdl"); + self.weapon = IT_ROCKET_LAUNCHER; + self.netname = "$qc_rocket_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/*QUAKED weapon_lightning (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_lightning = +{ + precache_model ("progs/g_light.mdl"); + setmodel (self, "progs/g_light.mdl"); + self.weapon = IT_LIGHTNING; + self.netname = "$qc_thunderbolt"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/* +=============================================================================== + +AMMO + +=============================================================================== +*/ + +void() ammo_touch = +{ + entity stemp; + float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + +#ifdef COOP_RESPAWN_ITEMS_FOR_PLAYERS + if (coop && other == self.enemy && self.wait <= 0) //In coop, don't allow the same player to pick up all the respawns. + return; + self.enemy = other; // Store this player for coop +#endif + + // if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + // shotgun + if (self.weapon == 1) + { + if (other.ammo_shells >= 100) + return; + other.ammo_shells = other.ammo_shells + self.aflag; + } + + // spikes + if (self.weapon == 2) + { + if (other.ammo_nails >= 200) + return; + other.ammo_nails = other.ammo_nails + self.aflag; + } + + // rockets + if (self.weapon == 3) + { + if (other.ammo_rockets >= 100) + return; + other.ammo_rockets = other.ammo_rockets + self.aflag; + } + + // cells + if (self.weapon == 4) + { + if (other.ammo_cells >= 100) + return; + other.ammo_cells = other.ammo_cells + self.aflag; + } + + bound_other_ammo (); + + sprint(other, "$qc_got_item", self.netname); + // ammo touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + // change to a better weapon if appropriate + + if ( other.weapon == best && (W_WantsToChangeWeapon(other, 0, 1) == 1)) + { + stemp = self; + self = other; + self.weapon = W_BestWeapon(); + W_SetCurrentAmmo (); + self = stemp; + } + + // if changed current ammo, update it + stemp = self; + self = other; + W_SetCurrentAmmo(); + self = stemp; + + + // remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + + self.think = SUB_regen; +#ifdef COOP_RESPAWN_ITEMS_FOR_PLAYERS + if(coop && self.wait <= 0) //Wait overrides coop respawn behaviour + { + self.cnt++; + if(self.cnt < num_players) //Not been picked up by all players yet. + { + self.nextthink = time + 1; + self.think = SUB_regen; // Respawn almost immediately + } + else + { + self.enemy = world; + self.cnt = 0; + self.nextthink = time + 4 * 60; + self.think = SUB_regen; // Respawn after 4 mins in case the players get stuck on something hard. + } + } + else +#endif + if (deathmatch) + { + if (deathmatch == 3 || deathmatch == 5) + { + self.nextthink = time + 15; + } + else if (deathmatch != 2) + { + self.nextthink = time + 30; + } + } + else if (self.wait) + { + self.enemy = world; + self.nextthink = time + self.wait; + } + + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + + + +float WEAPON_BIG2 = 1; + +/*QUAKED item_shells (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_shells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_shell1.bsp"); + setmodel (self, "maps/b_shell1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_shell0.bsp"); + setmodel (self, "maps/b_shell0.bsp"); + self.aflag = 20; + } + self.weapon = 1; + self.netname = "$qc_shells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_spikes (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_spikes = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_nail1.bsp"); + setmodel (self, "maps/b_nail1.bsp"); + self.aflag = 50; + } + else + { + precache_model ("maps/b_nail0.bsp"); + setmodel (self, "maps/b_nail0.bsp"); + self.aflag = 25; + } + self.weapon = 2; + self.netname = "$qc_nails"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_rockets (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_rockets = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_rock1.bsp"); + setmodel (self, "maps/b_rock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_rock0.bsp"); + setmodel (self, "maps/b_rock0.bsp"); + self.aflag = 5; + } + self.weapon = 3; + self.netname = "$qc_rockets"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/*QUAKED item_cells (0 .5 .8) (0 0 0) (32 32 32) big +*/ + +void() item_cells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_batt1.bsp"); + setmodel (self, "maps/b_batt1.bsp"); + self.aflag = 12; + } + else + { + precache_model ("maps/b_batt0.bsp"); + setmodel (self, "maps/b_batt0.bsp"); + self.aflag = 6; + } + self.weapon = 4; + self.netname = "$qc_cells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/* +=============================================================================== + +KEYS + +=============================================================================== +*/ + +void() key_touch = +{ + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (other.items & self.items) + return; + + sprint(other, "$qc_got_item", self.netname); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + other.items = other.items | self.items; + + if (!coop) + { + self.solid = SOLID_NOT; + self.model = string_null; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets + self.target = string_null; //Only fire the trigger once though. +}; + + +void() key_setsounds = +{ + // yoder mod jan 13, 2021 + if (self.worldtype == WORLDTYPE_MEDIEVAL) + { + precache_sound ("misc/medkey.wav"); + self.noise = "misc/medkey.wav"; + } + else if (self.worldtype == WORLDTYPE_METAL) + { + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + } + else if (self.worldtype == WORLDTYPE_BASE) + { + precache_sound2 ("misc/basekey.wav"); + self.noise = "misc/basekey.wav"; + } +}; + +/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) +SILVER key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key1 = +{ + SUB_SetWorldtype(); + + if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + self.netname = "$qc_silver_key"; + precache_model ("progs/w_s_key.mdl"); + setmodel (self, "progs/w_s_key.mdl"); + } + else if (self.worldtype == WORLDTYPE_METAL) + { + self.netname = "$qc_silver_runekey"; + precache_model ("progs/m_s_key.mdl"); + setmodel (self, "progs/m_s_key.mdl"); + } + else if (self.worldtype == WORLDTYPE_BASE) + { + self.netname = "$qc_silver_keycard"; + precache_model2 ("progs/b_s_key.mdl"); + setmodel (self, "progs/b_s_key.mdl"); + } + + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY1; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) +GOLD key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key2 = +{ + SUB_SetWorldtype(); + + if (self.worldtype == WORLDTYPE_MEDIEVAL || self.worldtype == WORLDTYPE_HUB) + { + precache_model ("progs/w_g_key.mdl"); + setmodel (self, "progs/w_g_key.mdl"); + self.netname = "$qc_gold_key"; + } + else if (self.worldtype == WORLDTYPE_METAL) + { + precache_model ("progs/m_g_key.mdl"); + setmodel (self, "progs/m_g_key.mdl"); + self.netname = "$qc_gold_runekey"; + } + else if (self.worldtype == WORLDTYPE_BASE) + { + precache_model2 ("progs/b_g_key.mdl"); + setmodel (self, "progs/b_g_key.mdl"); + self.netname = "$qc_gold_keycard"; + } + + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY2; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + + +/* +=============================================================================== + +POWERUPS + +=============================================================================== +*/ + +void() powerup_touch; + + +void() powerup_touch = +{ + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + sprint(other, "$qc_got_item", self.netname); + + if (deathmatch) + { + self.mdl = self.model; + + if ((self.classname == "item_artifact_invulnerability") || + (self.classname == "item_artifact_invisibility")) + self.nextthink = time + 60*5; + else + self.nextthink = time + 60; + + self.think = SUB_regen; + } + else if (self.wait) + { + self.nextthink = time + self.wait; + self.think = SUB_regen; + } + + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | self.items; + self.model = string_null; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets + + if (self.classname == "item_artifact_envirosuit") + // do the apropriate action + { + other.rad_time = 1; + other.radsuit_finished = time + 30; + if(coop) //Respawn biosuit in coop, since it might be required for progression + { + self.target = string_null; + self.think = SUB_regen; + self.nextthink = time + 2.5; + } + } + if (self.classname == "item_artifact_invulnerability") + { + other.invincible_time = 1; + // Yoder Sept24 2021 Horde Merge + if (horde_ent) + other.invincible_finished = time + 5; + else + other.invincible_finished = time + 30; + } + + if (self.classname == "item_artifact_invisibility") + { + other.invisible_time = 1; + other.invisible_finished = time + 30; + } + + if (self.classname == "item_artifact_super_damage") + { + other.super_time = 1; + // Yoder Sept24 2021 Horde Merge + if (horde_ent) + other.super_damage_finished = time + 5; + else + other.super_damage_finished = time + 30; + } + +}; + + + +/*QUAKED item_artifact_invulnerability (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invulnerable for 30 seconds +*/ +void() item_artifact_invulnerability = +{ + self.touch = powerup_touch; + + precache_model ("progs/invulner.mdl"); + precache_sound ("items/protect.wav"); + precache_sound ("items/protect2.wav"); + precache_sound ("items/protect3.wav"); + self.noise = "items/protect.wav"; + setmodel (self, "progs/invulner.mdl"); + self.netname = "$qc_pentagram_of_protection"; + self.items = IT_INVULNERABILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_artifact_envirosuit (0 .5 .8) (-16 -16 -24) (16 16 32) +Player takes no damage from water or slime for 30 seconds +*/ +void() item_artifact_envirosuit = +{ + self.touch = powerup_touch; + + precache_model ("progs/suit.mdl"); + precache_sound ("items/suit.wav"); + precache_sound ("items/suit2.wav"); + self.noise = "items/suit.wav"; + setmodel (self, "progs/suit.mdl"); + self.netname = "$qc_biosuit"; + self.items = IT_SUIT; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_invisibility (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invisible for 30 seconds +*/ +void() item_artifact_invisibility = +{ + self.touch = powerup_touch; + + precache_model ("progs/invisibl.mdl"); + precache_sound ("items/inv1.wav"); + precache_sound ("items/inv2.wav"); + precache_sound ("items/inv3.wav"); + self.noise = "items/inv1.wav"; + setmodel (self, "progs/invisibl.mdl"); + self.netname = "$qc_ring_of_shadows"; + self.items = IT_INVISIBILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_super_damage (0 .5 .8) (-16 -16 -24) (16 16 32) +The next attack from the player will do 4x damage +*/ +void() item_artifact_super_damage = +{ + self.touch = powerup_touch; + + precache_model ("progs/quaddama.mdl"); + precache_sound ("items/damage.wav"); + precache_sound ("items/damage2.wav"); + precache_sound ("items/damage3.wav"); + self.noise = "items/damage.wav"; + setmodel (self, "progs/quaddama.mdl"); + self.netname = "$qc_quad_damage"; + self.items = IT_QUAD; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +PLAYER BACKPACKS + +=============================================================================== +*/ + +void() BackpackTouch = +{ + local float best, old, new; + local entity stemp; + local float acount; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + acount = 0; + sprint(other, "$qc_backpack_got"); + + if (self.items) + if ((other.items & self.items) == 0 && self.netname) + { + acount = 1; + sprint(other, "{}", self.netname); // No need to localize + } + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + +// change weapons + other.ammo_shells = other.ammo_shells + self.ammo_shells; + other.ammo_nails = other.ammo_nails + self.ammo_nails; + other.ammo_rockets = other.ammo_rockets + self.ammo_rockets; + other.ammo_cells = other.ammo_cells + self.ammo_cells; + + new = self.items; + if (!new) + new = other.weapon; + old = other.items; + other.items = other.items | new; + + bound_other_ammo (); + + if (self.ammo_shells) + { + if (acount) + sprint(other, ", "); // No need to localize + acount = 1; + sprint(other, "$qc_backpack_shells", ftos(self.ammo_shells)); + } + if (self.ammo_nails) + { + if (acount) + sprint(other, ", "); // No need to localize + acount = 1; + sprint(other, "$qc_backpack_nails", ftos(self.ammo_nails)); + } + if (self.ammo_rockets) + { + if (acount) + sprint(other, ", "); // No need to localize + acount = 1; + sprint(other, "$qc_backpack_rockets", ftos(self.ammo_rockets)); + } + if (self.ammo_cells) + { + if (acount) + sprint(other, ", "); // No need to localize + acount = 1; + sprint(other, "$qc_backpack_cells", ftos(self.ammo_cells)); + } + + sprint (other, "\n"); // No need to localize +// backpack touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + +// remove the backpack, change self to the player + remove(self); + self = other; + +// change to the weapon + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (!deathmatch) + self.weapon = new; + else + Deathmatch_Weapon (old, new); + } + + W_SetCurrentAmmo (); +}; + +/* +=============== +DropBackpack +=============== +*/ +void() DropBackpack = +{ + local entity item; + + // Yoder Sept24 2021 Horde Merge + // don't drop backpacks in horde mode + if (horde_ent) + return; + if (!(self.ammo_shells + self.ammo_nails + self.ammo_rockets + self.ammo_cells)) + return; // nothing in it + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.items = self.weapon; + if (item.items == IT_AXE) + item.netname = "$qc_axe"; + else if (item.items == IT_SHOTGUN) + item.netname = "$qc_shotgun"; + else if (item.items == IT_SUPER_SHOTGUN) + item.netname = "$qc_double_shotgun"; + else if (item.items == IT_NAILGUN) + item.netname = "$qc_nailgun"; + else if (item.items == IT_SUPER_NAILGUN) + item.netname = "$qc_super_nailgun"; + else if (item.items == IT_GRENADE_LAUNCHER) + item.netname = "$qc_grenade_launcher"; + else if (item.items == IT_ROCKET_LAUNCHER) + item.netname = "$qc_rocket_launcher"; + else if (item.items == IT_LIGHTNING) + item.netname = "$qc_thunderbolt"; + else + item.netname = string_null; + + item.ammo_shells = self.ammo_shells; + item.ammo_nails = self.ammo_nails; + item.ammo_rockets = self.ammo_rockets; + item.ammo_cells = self.ammo_cells; + + // include some ammo in the backpack if the weapon is being given + if (item.ammo_shells < 5 && (item.items == IT_SHOTGUN || item.items == IT_SUPER_SHOTGUN)) { + item.ammo_shells = 5; + } + + if (item.ammo_nails < 20 && (item.items == IT_NAILGUN || item.items == IT_SUPER_NAILGUN)) { + item.ammo_nails = 20; + } + + if (item.ammo_rockets < 5 && (item.items == IT_GRENADE_LAUNCHER || item.items == IT_ROCKET_LAUNCHER)) { + item.ammo_rockets = 5; + } + + if (item.ammo_cells < 15 && item.items == IT_LIGHTNING) { + item.ammo_cells = 15; + } + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + setmodel (item, "progs/backpack.mdl"); + setsize (item, '-16 -16 0', '16 16 56'); + item.touch = BackpackTouch; + + item.nextthink = time + 120; // remove after 2 minutes + item.think = SUB_Remove; +}; diff --git a/quakec_mg1/items_runes.qc b/quakec_mg1/items_runes.qc new file mode 100644 index 0000000..ed7666c --- /dev/null +++ b/quakec_mg1/items_runes.qc @@ -0,0 +1,219 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +=============================================================================== + +END OF LEVEL RUNES + +=============================================================================== +*/ + +float SIGIL_E1 = 1; +float SIGIL_E2 = 2; +float SIGIL_E3 = 4; +float SIGIL_E4 = 8; +float SIGIL_E5 = 16; +float SIGIL_E6 = 32; +float SIGIL_NUMBITS = 6; + +float SIGIL_ALL = SIGIL_E1 | SIGIL_E2 | SIGIL_E3 | SIGIL_E4 | SIGIL_E5; +float SIGIL_ALL_ALL_ALLLL = SIGIL_E1 | SIGIL_E2 | SIGIL_E3 | SIGIL_E4 | SIGIL_E5 | SIGIL_E6; + + +void() sigil_touch = +{ + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + centerprint_all (self.netname); // Netname is localized + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + self.model = string_null; + float lastPickup = SUB_LeftShift(self.spawnflags & SIGIL_ALL, SIGIL_NUMBITS); + serverflags = serverflags | (self.spawnflags & SIGIL_ALL) | lastPickup; + + if (cvar("horde") && (self.spawnflags & SIGIL_E2)) // Hunger + { + local entity e; + e = find(world, classname, "player"); + while (e) + { + e.hunger_time = time + HUNGER_MAX; + dprint("hunger time is: "); + dprint(ftos(e.hunger_time)); + dprint("\n"); + e = find(e, classname, "player"); + } + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + +float sigil_getLastPickup() +{ + float lastPickup = SUB_RightShift(serverflags, SIGIL_NUMBITS) & SIGIL_ALL; + return lastPickup; +} + +void sigil_clearLastPickup() +{ + serverflags = serverflags & ~SUB_LeftShift(SIGIL_ALL, SIGIL_NUMBITS); +} + + +/*QUAKED item_sigil (0 .5 .8) (-16 -16 -24) (16 16 32) E1 E2 E3 E4 E5 E6 +End of level sigil, pick up to end episode and return to jrstart. +*/ + +void() item_sigil = +{ + dprint("SPAWNING SIGIL: "); + dprint(ftos(self.spawnflags)); + dprint("\n"); + if (!self.spawnflags) + self.spawnflags|= SIGIL_E1; + + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + + string tempmdl = string_null; + + if (self.spawnflags & SIGIL_E1) + { + self.spawnflags = SIGIL_E1; + tempmdl = "progs/mg1_rune1.mdl"; + self.netname = "$qc_mg1_pickup_rune1"; + } + else if (self.spawnflags & SIGIL_E2) + { + self.spawnflags = SIGIL_E2; + tempmdl = "progs/mg1_rune2.mdl"; + self.netname = "$qc_mg1_pickup_rune2"; + } + else if (self.spawnflags & SIGIL_E3) + { + self.spawnflags = SIGIL_E3; + tempmdl = "progs/mg1_rune3.mdl"; + self.netname = "$qc_mg1_pickup_rune3"; + } + else if (self.spawnflags & SIGIL_E4) + { + self.spawnflags = SIGIL_E4; + tempmdl = "progs/mg1_rune4.mdl"; + self.netname = "$qc_mg1_pickup_rune4"; + } + else if (self.spawnflags & SIGIL_E5) + { + self.spawnflags = SIGIL_E5; + tempmdl = "progs/mg1_rune5.mdl"; + self.netname = "$qc_mg1_pickup_rune5"; + } + else if (self.spawnflags & SIGIL_E6) + { + self.spawnflags = SIGIL_E6; + tempmdl = "progs/mg1_rune6.mdl"; + self.netname = "$qc_mg1_pickup_rune6"; + } + + + dprint("PREP SIGIL\n"); + + precache_model (tempmdl); + setmodel (self, tempmdl); + + self.touch = sigil_touch; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +// =============================================================================== + +const float RUNE_INDICATOR_ACTIVE = 64; + +void misc_rune_indicator_use() +{ + self.alpha = 1.0; + SUB_UseTargets(); +} + +void misc_rune_indicator() +{ + float active = self.spawnflags & RUNE_INDICATOR_ACTIVE ? TRUE : FALSE; + self.spawnflags (-) RUNE_INDICATOR_ACTIVE; + if (!self.spawnflags) + { + self.spawnflags|= SIGIL_E1; + } + self.spawnflags &= SIGIL_ALL_ALL_ALLLL; + + string mdl = string_null; + + if (self.spawnflags & SIGIL_E1) + { + self.spawnflags = SIGIL_E1; + mdl = "progs/mg1_rune1.mdl"; + } + else if (self.spawnflags & SIGIL_E2) + { + self.spawnflags = SIGIL_E2; + mdl = "progs/mg1_rune2.mdl"; + } + else if (self.spawnflags & SIGIL_E3) + { + self.spawnflags = SIGIL_E3; + mdl = "progs/mg1_rune3.mdl"; + } + else if (self.spawnflags & SIGIL_E4) + { + self.spawnflags = SIGIL_E4; + mdl = "progs/mg1_rune4.mdl"; + } + else if (self.spawnflags & SIGIL_E5) + { + self.spawnflags = SIGIL_E5; + mdl = "progs/mg1_rune5.mdl"; + } + else if (self.spawnflags & SIGIL_E6) + { + self.spawnflags = SIGIL_E6; + mdl = "progs/mg1_rune6.mdl"; + } + + precache_model (mdl); + setmodel (self, mdl); + + self.use = misc_rune_indicator_use; + + if(((self.spawnflags & serverflags) == self.spawnflags) || active ) + { + self.think = SUB_UseTargets; + self.nextthink = time + 0.2; + } + else + { + //Show a ghost of the rune before you collect it. + self.alpha = 0.2; + } +} \ No newline at end of file diff --git a/quakec_mg1/lights.qc b/quakec_mg1/lights.qc new file mode 100644 index 0000000..3e69ffe --- /dev/null +++ b/quakec_mg1/lights.qc @@ -0,0 +1,408 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +const string LightStyles[] = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}; +const float LightStyles_MAX = LightStyles.length - 1; +const string LightStylesHalf[] = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"}; +const float LightStylesHalf_MAX = LightStylesHalf.length - 1; + + +//============================================================================ +// No special functionality, just to hold dynamic shadowcasting lights for KEX +const float DYNAMICLIGHT_NOT_IN_COOP = 1; +void dynamiclight() +{ + if(coop && self.spawnflags & DYNAMICLIGHT_NOT_IN_COOP) + { + remove(self); + } +} + +//============================================================================ + +void InitLightStyles() +{ + // + // Setup light animation tables. 'a' is total darkness, 'z' is maxbright. + // + + // 0 normal + lightstyle(0, "m"); + + // 1 FLICKER (first variety) + lightstyle(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + lightstyle(4, "ma"); + + // 5 GENTLE PULSE 1 + lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + lightstyle(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + lightstyle(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + lightstyle(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // New styles under here if needed: + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + lightstyle(63, "a"); +} + +//============================================================================ + +string LightFractionToStyle(float frac) +{ + if(frac <= 0) return LightStyles[0]; + if(frac >= 1) return LightStyles[LightStyles_MAX]; + frac*= LightStyles.length; + frac = floor(frac); + return LightStyles[frac]; +} + +string LightFractionToStyleHalf(float frac) +{ + if(frac <= 0) return LightStylesHalf[0]; + if(frac >= 1) return LightStylesHalf[LightStylesHalf_MAX]; + frac*= LightStylesHalf.length; + frac = floor(frac); + return LightStylesHalf[frac]; +} + +enum : int +{ + LightRampStateOff, + LightRampStateFadeUp, + LightRampStateOn, + LightRampStateFadeDown +}; + +float LIGHTRAMP_FULLRANGE = 1; + +void target_lightramp_setlight(float style, float frac) +{ + if(self.spawnflags & LIGHTRAMP_FULLRANGE) + lightstyle(style, LightFractionToStyle(frac)); + else + lightstyle(style, LightFractionToStyleHalf(frac)); +} + +void target_lightramp_tick(float dt) +{ + float diff = dt * self.delay; + switch(self.state) + { + case LightRampStateFadeDown: + self.cnt-= diff; + break; + + case LightRampStateFadeUp: + self.cnt+= diff; + break; + } + target_lightramp_setlight(self.owner.style, self.cnt); + if(self.cnt >= 1) + { + self.cnt = 1; + self.state = LightRampStateOn; + RemoveFrameTickEntity(self); + } + if(self.cnt <= 0) + { + self.cnt = 0; + self.state = LightRampStateOff; + RemoveFrameTickEntity(self); + } + +} + +void target_lightramp_use() +{ + switch(self.state) + { + case LightRampStateFadeDown: + self.state = LightRampStateFadeUp; + return; + + case LightRampStateOff: + self.state = LightRampStateFadeUp; + RegisterFrameTickEntity(self); + return; + + case LightRampStateOn: + self.state = LightRampStateFadeDown; + RegisterFrameTickEntity(self); + return; + + case LightRampStateFadeUp: + self.state = LightRampStateFadeDown; + return; + } +} +void target_lightramp_init() +{ + self.think = SUB_Null; + if(!self.target) objerror("\n\ntarget_lightramp with no target.\n\n"); + if(!self.targetname) objerror("\n\ntarget_lightramp with no targetname.\n\n"); + + self.owner = find(world, targetname, self.target); + if(!self.owner) objerror("\n\ntarget_lightramp with unmatched target.\n\n"); + + if(self.owner.spawnflags & START_OFF) + { + self.state = LightRampStateOff; + self.cnt = 0; + } + else + { + self.state = LightRampStateOn; + self.cnt = 1; + } + + self.use = target_lightramp_use; + self.tick = target_lightramp_tick; +} + +void target_lightramp() +{ + + if(!self.delay) self.delay = 1; + self.delay = 1 / self.delay; + + self.think = target_lightramp_init; + self.nextthink = time + 0.1; +} + +//============================================================================ + +const float LIGHT_TARGETNAME_IS_STYLE = 2; + +void() light_use = +{ + if (self.spawnflags & START_OFF) + { + lightstyle(self.style, "m"); + self.spawnflags = self.spawnflags - START_OFF; + } + else + { + lightstyle(self.style, "a"); + self.spawnflags = self.spawnflags + START_OFF; + } +}; + +/*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, it will toggle between on or off. +*/ +void() light = +{ + if (!self.targetname) + { // inert light + remove(self); + return; + } + + if (self.style >= 32) + { + if(self.spawnflags & LIGHT_TARGETNAME_IS_STYLE) + { + lightstyle(self.style, self.targetname); + remove(self); + return; + } + self.use = light_use; + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } +}; + +/*QUAKED light_fluoro (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, it will toggle between on or off. +Makes steady fluorescent humming sound +*/ +void() light_fluoro = +{ + if (self.style >= 32) + { + self.use = light_use; + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } + + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_fluorospark (0 1 0) (-8 -8 -8) (8 8 8) +Non-displayed light. +Default light value is 300 +Default style is 10 +Makes sparking, broken fluorescent sound +*/ +void() light_fluorospark = +{ + if (!self.style) + self.style = 10; + + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_globe (0 1 0) (-8 -8 -8) (8 8 8) +Sphere globe light. +Default light value is 300 +Default style is 0 +*/ +void() light_globe = +{ + precache_model ("progs/s_light.spr"); + setmodel (self, "progs/s_light.spr"); + makestatic (self); +}; + +void() FireAmbient = +{ + precache_sound ("ambience/fire1.wav"); +// attenuate fast + ambientsound (self.origin, "ambience/fire1.wav", 0.5, ATTN_STATIC); +}; + +const float UPSIDEDOWN = 4; + +/*QUAKED light_torch_small_walltorch (0 .5 0) (-10 -10 -20) (10 10 20) +Short wall torch +Default light value is 200 +Default style is 0 +*/ +void() light_torch_small_walltorch = +{ + precache_model ("progs/flame.mdl"); + setmodel (self, "progs/flame.mdl"); + + if (self.spawnflags & UPSIDEDOWN) + self.angles = '180 0 0'; + + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_large_yellow (0 1 0) (-10 -10 -12) (12 12 18) +Large yellow flame ball +*/ +void() light_flame_large_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + self.frame = 1; + + if (self.spawnflags & UPSIDEDOWN) + self.angles = '180 0 0'; + + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_yellow (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Small yellow flame ball +*/ +void() light_flame_small_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + + if (self.spawnflags & UPSIDEDOWN) + self.angles = '180 0 0'; + + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_white (0 1 0) (-10 -10 -40) (10 10 40) START_OFF +Small white flame ball +*/ +void() light_flame_small_white = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + + if (self.spawnflags & UPSIDEDOWN) + self.angles = '180 0 0'; + + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_gas (0 1 0) (-10 -10 -40) (10 10 40) START_OFF +Gas flare (spawns two entites) +*/ +void() light_flame_gas = +{ + precache_model ("progs/flame3.mdl"); + setmodel (self, "progs/flame3.mdl"); + self.alpha = 0.6; + + entity s = spawn(); + setorigin(s, self.origin); + setmodel (s, "progs/flame3.mdl"); + s.alpha = 0.4; + s.frame = 1; +}; + +/*QUAKED light_candle (0 .5 0) (-4 -4 -10) (4 4 10) +Candle +Default light value is 200 +Default style is 0 +*/ +void() light_candle = +{ + precache_model ("progs/candle.mdl"); + setmodel (self, "progs/candle.mdl"); + makestatic (self); +}; diff --git a/quakec_mg1/map_specific/hub.qc b/quakec_mg1/map_specific/hub.qc new file mode 100644 index 0000000..d959b41 --- /dev/null +++ b/quakec_mg1/map_specific/hub.qc @@ -0,0 +1,36 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +void hub_trigger_changelevel() +{ + if((serverflags & SIGIL_ALL) !=SIGIL_ALL) + { + remove(self); + return; + } + + trigger_changelevel(); +} + + + + + + diff --git a/quakec_mg1/map_specific/mge2m2.qc b/quakec_mg1/map_specific/mge2m2.qc new file mode 100644 index 0000000..96287a0 --- /dev/null +++ b/quakec_mg1/map_specific/mge2m2.qc @@ -0,0 +1,112 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +//Electrodes + +void mge2m2_electrode_target() +{ +} + +// Electrode buttons + +float mge2m2_match_cnt(entity e) { return self.cnt == e.cnt; } + +void mge2m2_electrode_button_touch() +{ + if(other.classname != "player") return; + if(other.health <= 0) return; + + button_touch(); + self.touch = SUB_Null; + + entity t = SUB_FindWithPredicate(world, classname, "mge2m2_electrode_target", mge2m2_match_cnt); + while(t) + { + t.think = SUB_Remove; + t.nextthink = time + 0.1; + t = SUB_FindWithPredicate(t, classname, "mge2m2_electrode_target", mge2m2_match_cnt); + } +} + +void mge2m2_electrode_button() +{ + func_button(); + self.touch = mge2m2_electrode_button_touch; +} + +// Opening the rune "egg" + +void mge2m2_rune_egg_opener_use() +{ + entity t, oself; + oself = self; + t = find(world, targetname, self.target); + while(t) + { + if(t.classname == "func_door") + { + self = t; + self.movedir = self.dest2; + self.pos1 = self.origin; + self.pos2 = self.pos1 + self.movedir; + self.state = STATE_BOTTOM; + self.speed = 500; + self.noise1 = "doors/drclos4.wav"; + self.noise2 = "doors/doormv1.wav"; + door_go_up(); + } + t = find(t, targetname, oself.target); + } + self = oself; +} + +void mge2m2_rune_egg_opener() +{ + self.use = mge2m2_rune_egg_opener_use; +} + +// Rune pickupability + +void mge2m2_rune_pickup_fixer_use() +{ + void() tutsh; + if(self.state) tutsh = sigil_touch; + else + { + self.state = 1; + tutsh = SUB_Null; + } + entity ru = find(world, classname, "item_sigil"); + while(ru) + { + ru.touch = tutsh; + ru = find(ru, classname, "item_sigil"); + } +} + +void mge2m2_rune_pickup_fixer() +{ + self.use = mge2m2_rune_pickup_fixer_use; + self.think = mge2m2_rune_pickup_fixer_use; + self.nextthink = time + 0.8; +} + +// Obsolete, replace with trigger_cleanup_corpses +void mge2m2_cleanup_corpses() { dprint("WARNING: entity 'mge2m2_cleanup_corpses' spawned. Please replace with 'trigger_cleanup_corpses'.\n"); trigger_cleanup_corpses();} \ No newline at end of file diff --git a/quakec_mg1/math.qc b/quakec_mg1/math.qc new file mode 100644 index 0000000..680e8da --- /dev/null +++ b/quakec_mg1/math.qc @@ -0,0 +1,86 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/////////////////////////////////////////////////////////// +// Math related functions +/////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////// +// Modulus + +float mod(float v, float m) +{ + float r; + + if(m == 0) objerror("Illegal op: mod(x, 0)!"); + if(fabs(v) < m) return v; + if(v == m) return 0; + + r = floor(v / m) * m; + + return v - r; +} + +/////////////////////////////////////////////////////////// +// Sine + +float sin(float angle) +{ + vector a = {0, angle, 0}; + makevectors(a); + return v_forward_y; +} + +/////////////////////////////////////////////////////////// +// Cosine + +float cos(float angle) +{ + vector a = {0, angle, 0}; + makevectors(a); + return v_forward_x; +} + +/* +================ +Bitshift emulation +================ +*/ + +float SUB_LeftShift(float num, float bits) +{ + bits = floor(bits); + while(bits > 0) + { + num*= 2; + bits--; + } + return num; +} + +float SUB_RightShift(float num, float bits) +{ + bits = floor(bits); + while(bits > 0) + { + num/= 2; + bits--; + } + return num; +} diff --git a/quakec_mg1/misc.qc b/quakec_mg1/misc.qc new file mode 100644 index 0000000..5fbd56b --- /dev/null +++ b/quakec_mg1/misc.qc @@ -0,0 +1,721 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void() info_null = +{ + remove(self); +}; + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void() info_notnull = +{ +}; + +//============================================================================ + + +/*QUAKED misc_fireball (0 .5 .8) (-8 -8 -8) (8 8 8) +Lava Balls +*/ + +void() fire_fly; +void() fire_touch; +void() misc_fireball = +{ + if (!self.speed) + { + self.speed = 1000; + } + if(self.noise) + { + precache_sound(self.noise); + } + if(self.noise2) + { + precache_sound(self.noise2); + } + + precache_model ("progs/lavaball.mdl"); + self.classname = "fireball"; + + if(self.targetname) + { + self.use = fire_fly; + } + else + { + self.nextthink = time + (random() * 5); + self.think = fire_fly; + } +}; + +void() fire_fly = +{ +local entity fireball; + + fireball = spawn(); + fireball.solid = SOLID_TRIGGER; + fireball.movetype = MOVETYPE_TOSS; + if(self.movedir) + { + fireball.velocity = self.movedir; + fireball.velocity_x += crandom() * 20; + fireball.velocity_y += crandom() * 20; + fireball.velocity_z += crandom() * 20; + } + else + { + fireball.velocity_x = (random() * 100) - 50; + fireball.velocity_y = (random() * 100) - 50; + fireball.velocity_z = self.speed + (random() * 200); + } + fireball.classname = "fireball"; + setmodel (fireball, "progs/lavaball.mdl"); + setsize (fireball, '0 0 0', '0 0 0'); + setorigin (fireball, self.origin); + fireball.think = SUB_Remove; + fireball.touch = fire_touch; + + if(self.noise) + { + sound (self, CHAN_VOICE, self.noise, 1.0, ATTN_NORM); + } + fireball.noise2 = self.noise2; + + self.nextthink = time + (random() * 5) + 3; + self.think = fire_fly; +}; + + +void() fire_touch = +{ + T_Damage (other, self, self, 20); + if(self.noise2) + { + sound (self, CHAN_VOICE, self.noise2, 0.8, ATTN_NORM); + } + remove(self); +}; + +//============================================================================ + + +void() barrel_explode = +{ + self.origin_z = self.origin_z + 32; + + // did say self.owner + T_RadiusDamage (self, self, 160, world); + sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM); + particle (self.origin, '0 0 0', 75, 255); + + + // yoder add, 27/09/2020 to make barrels fire targets on explode + activator = self.enemy; + dprint("enemy name: "); + dprint(self.enemy.classname); + dprint("\n"); + SUB_UseTargets (); + + BecomeExplosion (); +}; + +void() barrel_detonate = +{ + self.takedamage = DAMAGE_NO; + self.think = barrel_explode; + self.nextthink = self.ltime + 0.15; +} + + +/*QUAKED misc_explobox (0 .5 .8) (0 0 0) (32 32 64) + +*/ + +void() misc_explobox = +{ + local float oldz; + + if(!self.mdl) self.mdl = "maps/b_explob.bsp"; + + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + precache_model (self.mdl); + setmodel (self, self.mdl); + precache_sound ("weapons/r_exp3.wav"); + self.health = 20; + self.th_die = barrel_detonate; + self.takedamage = DAMAGE_AIM; + + self.origin_z = self.origin_z + 2; + oldz = self.origin_z; + droptofloor(); + if (oldz - self.origin_z > 250) + { + dprint ("explobox fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + } + self.classname = "explo_box"; // Used by ClientObituary to print death message +}; + + + + +/*QUAKED misc_explobox2 (0 .5 .8) (0 0 0) (32 32 64) +Smaller exploding box, REGISTERED ONLY +*/ + +void() misc_explobox2 = +{ + self.mdl = "maps/b_exbox2.bsp"; + misc_explobox(); +}; + +//============================================================================ + +void func_explode_detonate() +{ + vector pos = self.mins + (self.size * 0.5); + pos_z -= 32; + setmodel(self, string_null); + setorigin(self, pos); + + barrel_explode(); +} + +void() func_explode_die = +{ + self.takedamage = DAMAGE_NO; + self.think = func_explode_detonate; + self.nextthink = self.ltime + 0.15; +} + + +/*QUAKED func_explode (0 .5 .8) (0 0 0) (32 32 64) +Custom exploding box +*/ + +void func_explode() = +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + setmodel (self, self.model); + + precache_sound ("weapons/r_exp3.wav"); + self.health = 20; + self.th_die = func_explode_die; + self.takedamage = DAMAGE_AIM; + + +}; + +//============================================================================ + +float SPAWNFLAG_SUPERSPIKE = 1; +float SPAWNFLAG_LASER = 2; + +void(vector org, vector vec) LaunchLaser; + +void() spikeshooter_use = +{ + if (self.spawnflags & SPAWNFLAG_LASER) + { + sound (self, CHAN_VOICE, "enforcer/enfire.wav", 1, ATTN_NORM); + LaunchLaser (self.origin, self.movedir); + } + else + { + sound (self, CHAN_VOICE, "weapons/spike2.wav", 1, ATTN_NORM); + launch_spike (self.origin, self.movedir); + newmis.velocity = self.movedir * 500; + if (self.spawnflags & SPAWNFLAG_SUPERSPIKE) + newmis.touch = superspike_touch; + } +}; + +void() shooter_think = +{ + spikeshooter_use (); + self.nextthink = time + self.wait; + newmis.velocity = self.movedir * 500; +}; + + +/*QUAKED trap_spikeshooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser +When triggered, fires a spike in the direction set in QuakeEd. +Laser is only for REGISTERED. +*/ + +void() trap_spikeshooter = +{ + SetMovedir (); + self.use = spikeshooter_use; + if (self.spawnflags & SPAWNFLAG_LASER) + { + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + } + else + precache_sound ("weapons/spike2.wav"); +}; + + +/*QUAKED trap_shooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser +Continuously fires spikes. +"wait" time between spike (1.0 default) +"nextthink" delay before firing first spike, so multiple shooters can be stagered. +*/ +void() trap_shooter = +{ + trap_spikeshooter (); + + if (self.wait == 0) + self.wait = 1; + self.nextthink = self.nextthink + self.wait + self.ltime; + self.think = shooter_think; +}; + + + +/* +=============================================================================== + + +=============================================================================== +*/ + + +void() make_bubbles; +void() bubble_remove; +void() bubble_bob; + +/*QUAKED air_bubbles (0 .5 .8) (-8 -8 -8) (8 8 8) + +testing air bubbles +*/ + +void() air_bubbles = + +{ + if (deathmatch) + { + remove (self); + return; + } + precache_model ("progs/s_bubble.spr"); + self.nextthink = time + 1; + self.think = make_bubbles; +}; + +void() make_bubbles = +{ +local entity bubble; + + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + random() + 0.5; + self.think = make_bubbles; +}; + +void() bubble_split = +{ +local entity bubble; + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = self.velocity; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 1; + bubble.cnt = 10; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.frame = 1; + self.cnt = 10; + if (self.waterlevel != 3) + remove (self); +}; + +void() bubble_remove = +{ + if (other.classname == self.classname) + { +// dprint ("bump"); + return; + } + remove(self); +}; + +void() bubble_bob = +{ +local float rnd1, rnd2, rnd3; + + self.cnt = self.cnt + 1; + if (self.cnt == 4) + bubble_split(); + if (self.cnt == 20) + remove(self); + + rnd1 = self.velocity_x + (-10 + (random() * 20)); + rnd2 = self.velocity_y + (-10 + (random() * 20)); + rnd3 = self.velocity_z + 10 + random() * 10; + + if (rnd1 > 10) + rnd1 = 5; + if (rnd1 < -10) + rnd1 = -5; + + if (rnd2 > 10) + rnd2 = 5; + if (rnd2 < -10) + rnd2 = -5; + + if (rnd3 < 10) + rnd3 = 15; + if (rnd3 > 30) + rnd3 = 25; + + self.velocity_x = rnd1; + self.velocity_y = rnd2; + self.velocity_z = rnd3; + + self.nextthink = time + 0.5; + self.think = bubble_bob; +}; + +/*~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~> +~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~*/ + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) + +Just for the debugging level. Don't use +*/ + +void() viewthing = + +{ + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + precache_model ("progs/player.mdl"); + setmodel (self, "progs/player.mdl"); +}; + + +/* +============================================================================== + +SIMPLE BMODELS + +============================================================================== +*/ + +void() func_wall_use = +{ // change to alternate textures + self.frame = 1 - self.frame; +}; + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibitted +*/ +void() func_wall = +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +void() func_illusionary = + +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + setmodel (self, self.model); + makestatic (self); +}; + +/*QUAKED func_episodegate (0 .5 .8) ? E1 E2 E3 E4 +This bmodel will appear if the episode has allready been completed, so players can't reenter it. +*/ +void() func_episodegate = + +{ + if (!(serverflags & self.spawnflags)) + return; // can still enter episode + + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + +/*QUAKED func_bossgate (0 .5 .8) ? +This bmodel appears unless players have all of the episode sigils. +*/ +const float BOSSGATE_INVERSE = 64; +void() func_bossgate = +{ + float inverse = self.spawnflags & BOSSGATE_INVERSE ? TRUE : FALSE; + self.spawnflags (-) BOSSGATE_INVERSE; + if(!self.spawnflags) + { + self.spawnflags = SIGIL_E1 | SIGIL_E2 | SIGIL_E3 | SIGIL_E4; + } + self.spawnflags&= SIGIL_ALL; + + if ( (serverflags & self.spawnflags) == self.spawnflags) //All runes collected + { + if(!inverse) + { + remove(self); + return; + } + } + else //Still missing some runes + { + if(inverse) + { + remove(self); + return; + } + } + + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); + + if(self.target || self.killtarget) + { + self.think = SUB_UseTargets; + self.nextthink = time + 0.2; + } +}; + +//============================================================================ + +const float FUNC_HURT_START_ON = 1; + +void func_hurt_touch() +{ + if(!self.state) return; + if(other.health <= 0) return; + if(!other.takedamage) return; + if(!(other.flags & (FL_CLIENT | FL_MONSTER))) return; + if(self.attack_finished > time) return; + + T_Damage(other, self, world, self.dmg); + self.attack_finished = time + self.wait; +} + +void func_hurt_use() +{ + self.state = 1 - self.state; + self.attack_finished = 0; +} + +/*QUAKED func_hurt (0 .5 .8) ? +Hurts alive things when they touch it +*/ +void func_hurt() +{ + setmodel(self, self.model); + setorigin(self, self.origin); + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.touch = func_hurt_touch; + self.use = func_hurt_use; + + if(!self.dmg) self.dmg = 10; + if(!self.wait) self.wait = 0.2; + + if(self.spawnflags & FUNC_HURT_START_ON) + { + self.state = 1; + } +} + +//============================================================================ + +void trigger_changetarget_use() +{ + SUB_SwitchTargets(target, self.target, self.killtarget); +} + +void trigger_changetarget() +{ + if(!self.target) objerror("Missing target."); + if(!self.killtarget) objerror("Missing target to change to."); + self.use = trigger_changetarget_use; +} + +//============================================================================ +// Cleanup +void trigger_cleanup_corpses_use() +{ + entity e = nextent(world); + while(e) + { + if(e.flags & FL_MONSTER) + { + if(e.health <= 0) + { + remove(e); + } + } + e = nextent(e); + } + remove(self); +} + +void trigger_cleanup_corpses() +{ + if(coop == 0) { remove(self); return; } + self.use = trigger_cleanup_corpses_use; +} + +//============================================================================ +/*QUAKED ambient_suck_wind (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_suck_wind = +{ + precache_sound ("ambience/suck1.wav"); + ambientsound (self.origin, "ambience/suck1.wav", 1, ATTN_STATIC); + makestatic(self); +}; + +/*QUAKED ambient_drone (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drone = +{ + precache_sound ("ambience/drone6.wav"); + ambientsound (self.origin, "ambience/drone6.wav", 0.5, ATTN_STATIC); + makestatic(self); +}; + +/*QUAKED ambient_flouro_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_flouro_buzz = +{ + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 1, ATTN_STATIC); + makestatic(self); +}; +/*QUAKED ambient_drip (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drip = +{ + precache_sound ("ambience/drip1.wav"); + ambientsound (self.origin, "ambience/drip1.wav", 0.5, ATTN_STATIC); + makestatic(self); +}; +/*QUAKED ambient_comp_hum (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_comp_hum = +{ + precache_sound ("ambience/comp1.wav"); + ambientsound (self.origin, "ambience/comp1.wav", 1, ATTN_STATIC); + makestatic(self); +}; +/*QUAKED ambient_thunder (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_thunder = +{ + precache_sound ("ambience/thunder1.wav"); + ambientsound (self.origin, "ambience/thunder1.wav", 0.5, ATTN_STATIC); + makestatic(self); +}; +/*QUAKED ambient_light_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_light_buzz = +{ + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); + makestatic(self); +}; +/*QUAKED ambient_swamp1 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp1 = +{ + precache_sound ("ambience/swamp1.wav"); + ambientsound (self.origin, "ambience/swamp1.wav", 0.5, ATTN_STATIC); + makestatic(self); +}; +/*QUAKED ambient_swamp2 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp2 = +{ + precache_sound ("ambience/swamp2.wav"); + ambientsound (self.origin, "ambience/swamp2.wav", 0.5, ATTN_STATIC); + makestatic(self); +}; + +/*QUAKED ambient_generic (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_generic = +{ + if(!self.noise) + { + dprint("ambient_generic with no noise. Removing.\n"); + remove(self); + } + if(!self.volume) self.volume = 0.5; + if(!self.delay) self.delay = 3; + + precache_sound (self.noise); + ambientsound (self.origin, self.noise, self.volume, self.delay); + makestatic(self); +}; + +//============================================================================ + + diff --git a/quakec_mg1/misc_corpses.qc b/quakec_mg1/misc_corpses.qc new file mode 100644 index 0000000..02e8c62 --- /dev/null +++ b/quakec_mg1/misc_corpses.qc @@ -0,0 +1,106 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +//============================================================================ + +typedef struct +{ + string mdl; + float frame; +}poseInfo; + +#pragma warning disable F317 //Use of stale framemacros +poseInfo poses[] = +{ + { "progs/demon.mdl", $CORPSEFRAME_DEMON_1 }, + { "progs/dog.mdl", $CORPSEFRAME_DOG_1 }, + { "progs/dog.mdl", $CORPSEFRAME_DOG_2 }, + { "progs/enforcer.mdl", $CORPSEFRAME_ENFORCER_1 }, + { "progs/enforcer.mdl", $CORPSEFRAME_ENFORCER_2 }, + { "progs/fish.mdl", $CORPSEFRAME_FISH_1 }, + { "progs/hknight.mdl", $CORPSEFRAME_HKNIGHT_1 }, + { "progs/hknight.mdl", $CORPSEFRAME_HKNIGHT_2 }, + { "progs/knight.mdl", $CORPSEFRAME_KNIGHT_1 }, + { "progs/knight.mdl", $CORPSEFRAME_KNIGHT_2 }, + { "progs/ogre.mdl", $CORPSEFRAME_OGRE_1 }, + { "progs/ogre.mdl", $CORPSEFRAME_OGRE_2 }, + { "progs/shalrath.mdl", $CORPSEFRAME_SHALRATH_1 }, + { "progs/shambler.mdl", $CORPSEFRAME_SHAMBLER_1 }, + { "progs/soldier.mdl", $CORPSEFRAME_SOLDIER_1 }, + { "progs/soldier.mdl", $CORPSEFRAME_SOLDIER_2 }, + { "progs/wizard.mdl", $CORPSEFRAME_WIZARD_1 }, + { "progs/player.mdl", $CORPSEFRAME_PLAYER_1 }, + { "progs/player.mdl", $CORPSEFRAME_PLAYER_2 }, + { "progs/player.mdl", $CORPSEFRAME_PLAYER_3 }, + { "progs/player.mdl", $CORPSEFRAME_PLAYER_4 }, + { "progs/player.mdl", $CORPSEFRAME_PLAYER_5 }, + { "progs/player.mdl", $CORPSEFRAME_PLAYER_6 }, + // Heads + { "progs/h_demon.mdl", 0 }, + { "progs/h_dog.mdl", 0 }, + { "progs/h_guard.mdl", 0 }, + { "progs/h_hellkn.mdl", 0 }, + { "progs/h_knight.mdl", 0 }, + { "progs/h_mega.mdl", 0 }, + { "progs/h_ogre.mdl", 0 }, + { "progs/h_player.mdl", 0 }, + { "progs/h_shal.mdl", 0 }, + { "progs/h_shams.mdl", 0 }, + { "progs/h_wizard.mdl", 0 }, + { "progs/h_zombie.mdl", 0 }, + // Gibs + { "progs/gib1.mdl", 0 }, + { "progs/gib2.mdl", 0 }, + { "progs/gib3.mdl", 0 }, +}; +#pragma warning enable F317 + +void misc_corpse() +{ + if(self.style < 0 || self.style >= poses.length) + { + objerror("misc_corpse with invalid style."); + return; + } + poseInfo pose = poses[self.style]; + precache_model(pose.mdl); + self.frame = pose.frame; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + setmodel (self, pose.mdl); +} + +void printCorpseFGDStuff() +{ + dprint("Corpseframes:\n"); + for(float i = 0; i < poses.length; i++) + { + //style == 0 -> { path: "progs/demon.mdl" , frame: 53 }, + dprint("style == "); + dprint(ftos(i)); + dprint(" -> { path: \""); + dprint(poses[i].mdl); + dprint("\"\t, frame: "); + dprint(ftos(poses[i].frame)); + dprint("\t},\n"); + } +} + +//============================================================================ diff --git a/quakec_mg1/misc_fx.qc b/quakec_mg1/misc_fx.qc new file mode 100644 index 0000000..9b1ebb4 --- /dev/null +++ b/quakec_mg1/misc_fx.qc @@ -0,0 +1,555 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +.float storednextthink; +.void() storedthink; + +//============================================================================ +// SCREENSHAKE +//============================================================================ +float SILENT_SHAKE = 1; + +void() T_Shake = +{ + local entity t; + local float starttime; + local float hit; + + starttime = self.storednextthink - self.wait; + + // early out if intermission is running + if (intermission_running) + return; + + // Completed, cleanup! + if (time > self.storednextthink) + { + if (!(self.spawnflags & SILENT_SHAKE)) + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + + t = find( world, classname, "player" ); + while (t) + { + t.v_angle_z = 0; + t = find( t, classname, "player" ); + } + return; + } + + // Ramp up? + if (time < self.delay) + hit = (time - starttime) / (self.wait/3); + + else + hit = 1; + + // dprint("intensity: "); + // dprint(ftos(hit)); + // dprint("\n"); + + hit = self.dmg * hit; + + // for each player + t = find( world, classname, "player" ); + while (t) + { + // do the shake! + t.punchangle_x = random() * hit; + t.punchangle_y = crandom() * hit; + t.punchangle_z = random() * hit; + + t = find( t, classname, "player" ); + } + + self.nextthink = time + 0.05; +}; + +void() screenshake_use = +{ + self.storednextthink = time + self.wait; // when to end + self.delay = time + (self.wait / 3); // how long to ramp up + + if (!(self.spawnflags & SILENT_SHAKE)) + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + + self.think = T_Shake; + self.nextthink = time + 0.05; +}; + +/*QUAKED trigger_screenshake + +wait is duration of the shake +dmg is the intensity of the shake +*/ +void() trigger_screenshake = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + // wait is sustain time + // dmg is intensity + if (!(self.spawnflags & SILENT_SHAKE)) + { + self.noise = "misc/quake.wav"; + self.noise1 = "misc/quakeend.wav"; + precache_sound(self.noise); + precache_sound(self.noise1); + } + + if (!self.wait) // no duration! + self.wait = 2; + + if (!self.dmg) // no intensity! + self.dmg = 3; + + self.use = screenshake_use; +} + +//============================================================================ +// SOUND TRIGGER +//============================================================================ + +void() trigger_sound_play = +{ + if (!self.noise) + return; + + sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM); +}; +/*QUAKED trigger_sound +*/ +void() trigger_sound = +{ + if (!self.noise) + remove(self); + + precache_sound (self.noise); + self.use = trigger_sound_play; + +}; + +//============================================================================ +// TRIGGER LIGHTNING +//============================================================================ + +const float LIGHTNING_RANDOM_TARGET = 1; +const float LIGHTNING_TRACE_BACKWARDS = 2; +const float LIGHTNING_NO_ACTIVATE_TARGET = 4; +const float LIGHTNING_TRIGGER_TARGETS_ONCE = 8; + +void() play_lightning = +{ + if (!self.target) + { + dprint("lightning has no target!\n"); + return; + } + + float rt = 0; + if(self.spawnflags & LIGHTNING_RANDOM_TARGET) + { + float cnt = SUB_CountTargets(self); + if(cnt == 0) return; + rt = floor(cnt * random()); + } + float triggertwice = (self.spawnflags & LIGHTNING_TRIGGER_TARGETS_ONCE) ? FALSE : TRUE; //Note boolean reversal + float activatetargets = (self.spawnflags & LIGHTNING_NO_ACTIVATE_TARGET) ? FALSE : TRUE; //Note boolean reversal + + local entity t, oself; + + float i = 0; + t = find(world, targetname, self.target); + while(t) + { + if(self.spawnflags & LIGHTNING_RANDOM_TARGET) + { + if (i < rt){ + t = find(t, targetname, self.target); + i++; + continue; + } + if (i > rt) return; + i++; + } + + vector endpos, startpos; + if(self.spawnflags & LIGHTNING_TRACE_BACKWARDS) + { + traceline(t.origin, self.origin, TRUE, self); + endpos = trace_endpos; + startpos = t.origin; + } + else + { + traceline(self.origin, t.origin, TRUE, self); + endpos = trace_endpos; + startpos = self.origin; + } + + + sound (t, CHAN_AUTO, self.noise, self.volume, ATTN_NORM); //Changed to CHAN_AUTO to allow more overlapping sounds + + // create the temp lightning entity + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, self.style); + WriteEntity (MSG_BROADCAST, t); + WriteCoord (MSG_BROADCAST, startpos_x); + WriteCoord (MSG_BROADCAST, startpos_y); + WriteCoord (MSG_BROADCAST, startpos_z); + WriteCoord (MSG_BROADCAST, endpos_x); + WriteCoord (MSG_BROADCAST, endpos_y); + WriteCoord (MSG_BROADCAST, endpos_z); + + if(self.dmg) + { + LightningDamage (startpos, endpos, self, self.dmg); + } + + //If activatetargets and t has a target, activate it. + if(activatetargets && t.target) + { + oself = self; + self = t; + SUB_UseTargets(); + if(triggertwice) + { + float odelay = self.delay; + self.delay = odelay + 0.2; + SUB_UseTargets(); //Quickly turn on and off + self.delay = odelay; + } + self = oself; + } + + t = find(t, targetname, self.target); + } +}; + +void() trigger_lightning = +{ + if(!self.noise) self.noise = "misc/power.wav"; + precache_sound(self.noise); + self.use = play_lightning; + + switch(self.style) + { + default: + case 0: + self.style = TE_LIGHTNING3; + break; + case 1: + self.style = TE_LIGHTNING1; + break; + case 2: + self.style = TE_LIGHTNING2; + break; + } + + if(!self.volume) self.volume = 1; +}; + +//============================================================================ +// TRIGGER FADE +//============================================================================ +/* Checks all targets to see if they are dead. If they are, then it starts a fade +*/ +void() SUB_FadeTargets = +{ + local entity t; + local float count; // how many entities are being faded + count = 0; + + if (self.target) + { + if(!self.state) + { + t = world; + //Initialize all the targets with alpha = 1.0, so the fade out works. + t = find (t, targetname, self.target); + while (t) + { + t.alpha = 1.0; + t = find (t, targetname, self.target); + } + self.state = 1; // Don't repeat this initialization after the first think. + } + + t = world; + t = find (t, targetname, self.target); + while (t) + { + if (t.health <= 0) + { + if (t.alpha > 0) + { + t.alpha = t.alpha - (frametime/self.delay); + count = count + 1; + } + else + { + dprint("fade complete, removing "); + dprint(t.classname); + dprint("\n"); + remove(t); + } + } + else + { + dprint ("target is still alive\n"); + } + t = find (t, targetname, self.target); + } + } + if (count > 0) + { + self.nextthink = time; + } + else + { + remove(self); + dprint("removed fade manager\n"); + } +}; + +void() spawn_fade_manager = +{ + local entity fademanager; + fademanager = spawn(); + dprint("spawn fade manager\n"); + fademanager.think = SUB_FadeTargets; + fademanager.nextthink = time; + fademanager.target = self.target; + fademanager.delay = self.delay; // use delay to determine fade time, default to 1 +} + +void() trigger_fade = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + self.use = spawn_fade_manager; + + if (!self.delay) + self.delay = 1; // delay determines fade time +}; + +//============================================================================ +// TRIGGER FREEZE +//============================================================================ +// Yoder Jan 14 2021 + + +void() SUB_Freeze = +{ + self.storednextthink = self.nextthink; + self.storedthink = self.think; + self.nextthink = time; + self.think = SUB_Null; + self.is_frozen = 1; +}; + +void() SUB_Unfreeze = +{ + self.nextthink = self.storednextthink; + self.think = self.storedthink; + self.storednextthink = -1; + self.is_frozen = 0; +}; + +void() SUB_FreezeTargets = +{ + local entity t; + + if (self.target) + { + t = world; + do + { + t = find (t, targetname, self.target); + if (!t) + return; + + if (!(t.is_frozen)) // check if already frozen + { + // freeze + t.storednextthink = t.nextthink; + t.storedthink = t.think; + t.nextthink = time; + t.think = SUB_Null; + t.is_frozen = 1; + + } + else + { + // unfreeze + t.nextthink = t.storednextthink; + t.think = t.storedthink; + t.storednextthink = -1; + t.is_frozen = 0; + } + } while (1); + } +}; + +/* Define the entity, trigger_freeze + Works like a trigger relay +*/ +void() trigger_freeze = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + self.use = SUB_FreezeTargets; +}; + +//============================================================================ +// particle_embers +//============================================================================ +void() particle_embers_think = +{ + local vector pos; + local vector speed; + speed = '0 0 2' * random() + '0 0 2'; + speed_x = crandom() * self.velocity_x; + speed_y = crandom() * self.velocity_y; + speed_z = speed_z * self.velocity_z; + pos = self.origin; + pos_x = pos_x + self.size_x * crandom(); + pos_y = pos_y + self.size_y * crandom(); + //pos = crandom() * self.size + self.origin; + particle(pos, speed, 234, 2); + self.nextthink = time + self.wait + self.delay * random(); +} + +void() particle_embers = +{ + // size determines the box the particles can spawn within + if (!self.size) + self.size = '128 128 0'; + // delay is random time added per loop + if (!self.delay) + self.delay = 0.1; + + // wait is time always added per loop + if (!self.wait) + self.wait = 0.05; + + // velocity is used as a scalar on speed + if (!self.velocity) + self.velocity = '1 1 1'; + + self.think = particle_embers_think; + self.nextthink = time + self.wait + self.delay * random(); +}; + +void() particle_embers_tall = +{ + if (!self.size) + self.size = '40 40 0'; + if (!self.delay) + self.delay = 0.1; + if (!self.wait) + self.wait = 0.05; + if (!self.velocity) // scalar for speed + self.velocity = '1 1 2'; + self.think = particle_embers_think; + self.nextthink = time + self.wait + self.delay * random(); +}; + +//============================================================================ +// particle_tele +//============================================================================ +void() particle_tele_think = +{ + local vector pos; // where to spawn + local float dist; // scalar from org, used for speed too + local vector rando; + + rando_x = crandom() * 10; + rando_y = crandom() * 10; + rando_z = crandom() * 5; + rando = normalize(rando); + + dist = 64; + + pos = self.origin + (rando * dist); + + // spawn particle + particle(pos, rando * dist * -.125, 3, 3); + self.nextthink = time + self.wait + self.delay * random(); +} + + +void() particle_tele = +{ + /* + 1. get origin + 2. get a random vector, then normalize it. Save it for later + 3. scale a copy of the vector by distance + 4. spawn the particle at vector + origin + 5. set the particle's velocity to the old normalized vector * -scalar. + */ + // size determines the box the particles can spawn within + if (!self.size) + self.size = '128 128 0'; + // delay is random time added per loop + if (!self.delay) + self.delay = 0.1; + + // wait is time always added per loop + if (!self.wait) + self.wait = 0; + + self.think = particle_tele_think; + self.nextthink = time + self.wait + self.delay * random(); +} + +void() particle_tele_fountain_think = +{ + local vector dir; + dir_x = crandom() * self.velocity_x; + dir_y = crandom() * self.velocity_y; + dir_z = self.velocity_z; + particle(self.origin, dir, 13, 2); + self.nextthink = time + self.wait + self.delay * random(); +}; +void() particle_tele_fountain_use = +{ + self.think = particle_tele_fountain_think; + self.nextthink = time + 0.1; +}; +void() particle_tele_fountain = +{ + if (!self.delay) + self.delay = 0.1; + + if (!self.wait) + self.wait = 0.05; + + if (!self.velocity) + self.velocity = '1 1 6'; + + if (self.spawnflags & START_OFF) + self.use = particle_tele_fountain_use; + else + { + self.think = particle_tele_fountain_think; + self.nextthink = time + self.wait + self.delay * random(); + } +}; diff --git a/quakec_mg1/models.qc b/quakec_mg1/models.qc new file mode 100644 index 0000000..7088e46 --- /dev/null +++ b/quakec_mg1/models.qc @@ -0,0 +1,604 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* +=============================================================================== + +WORLD WEAPONS + +=============================================================================== +*/ + +$modelname g_shot +$cd id1/models/g_shot +$origin 0 0 -24 +$flags 8 // client side rotate +$base base +$skin skin +$frame shot1 + + +$modelname g_nail +$cd id1/models/g_nail +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + + +$modelname g_nail2 +$cd id1/models/g_nail2 +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot2 + + +$modelname g_rock +$cd id1/models/g_rock +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + + +$modelname g_rock2 +$cd id1/models/g_rock2 +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + +$modelname g_light +$cd id1/models/g_light +$flags 8 // client side rotate +$origin 0 0 -24 +$base base +$skin skin +$frame shot1 + +/* +=============================================================================== + +VIEW WEAPONS + +=============================================================================== +*/ + +$modelname v_axe +$cd id1/models/v_axe +$origin 0 5 54 +$base base +$skin skin +$frame frame1 frame2 frame3 frame4 frame5 frame6 frame7 frame8 frame9 + + +$modelname v_shot +$cd id1/models/v_shot +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 + + +$modelname v_shot2 +$cd id1/models/v_shot2 +$origin 0 0 56 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 + + +$modelname v_rock2 +$cd id1/models/v_rock2 +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot6 + + +$modelname v_rock +$cd id1/models/v_rock +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 + + +$modelname v_nail2 +$cd id1/models/v_nail2 +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 shot8 shot9 + + +$modelname v_nail +$cd id1/models/v_nail +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 shot6 shot7 shot8 shot9 + +$modelname v_light +$cd id1/models/v_light +$origin 0 0 54 +$base base +$skin skin +$frame shot1 shot2 shot3 shot4 shot5 + + +/* +=============================================================================== + +ITEMS + +=============================================================================== +*/ + +$modelname w_g_key +$cd id1/models/w_g_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname w_s_key +$cd id1/models/w_s_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname m_g_key +$cd id1/models/m_g_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname m_s_key +$cd id1/models/m_s_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname b_g_key +$cd id1/models/b_g_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname b_s_key +$cd id1/models/b_s_key +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + + +$modelname quaddama +$cd id1/models/quaddama +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname invisibl +$cd id1/models/invisibl +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname invulner +$flags 8 // client side rotate +$cd id1/models/invulner +$base base +$skin skin +$frame frame1 + +//modelname jetpack +//cd id1/models/jetpack +//flags 8 // client side rotate +//base base +//skin skin +//frame frame1 + +$modelname cube +$cd id1/models/cube +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname suit +$cd id1/models/suit +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname boots +$cd id1/models/boots +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end1 +$cd id1/models/end1 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end2 +$cd id1/models/end2 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end3 +$cd id1/models/end3 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + +$modelname end4 +$cd id1/models/end4 +$flags 8 // client side rotate +$base base +$skin skin +$frame frame1 + + +/* +=============================================================================== + +GIBS + +=============================================================================== +*/ + +$modelname gib1 +$cd id1/models/gib1 +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + + +// torso +$modelname gib2 +$cd id1/models/gib2 +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname gib3 +$cd id1/models/gib3 +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + + +// heads + +$modelname h_player +$cd id1/models/h_player +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_dog +$cd id1/models/h_dog +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_mega +$cd id1/models/h_mega +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_guard +$cd id1/models/h_guard +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_wizard +$cd id1/models/h_wizard +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_knight +$cd id1/models/h_knight +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_hellkn +$cd id1/models/h_hellkn +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_zombie +$cd id1/models/h_zombie +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_shams +$cd id1/models/h_shams +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_shal +$cd id1/models/h_shal +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_ogre +$cd id1/models/h_ogre +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname h_demon +$cd id1/models/h_demon +$flags 4 // EF_GIB +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +/* +=============================================================================== + +MISC + +=============================================================================== +*/ + +$modelname armor +$cd id1/models/armor +$flags 8 // client side rotate +$origin 0 0 -8 +$base base +$skin skin +$skin skin2 +$skin skin3 +$frame armor + +$modelname s_light // shambler lightning ready +$cd id1/models/s_light +$origin 0 0 24 +$base base +$skin skin +$frame frame1 frame2 frame3 + +$modelname bolt3 // lightning towar bolts +$cd id1/models/bolt2 +$origin 0 0 0 +$base base +$scale 4 +$skin skin +$frame light + +$modelname bolt2 +$cd id1/models/bolt2 +$origin 0 0 0 +$base base +$skin skin +$frame light + +$modelname bolt +$cd id1/models/bolt +$origin 0 0 0 +$base light +$skin light +$frame light + +$modelname laser +$cd id1/models/laser +$base base +$skin skin +$scale 2 +$frame frame1 + +$modelname flame // with torch +$cd id1/models/flame +$origin 0 0 12 +$base base +$skin skin +$framegroupstart +$frame flame1 0.1 +$frame flame2 0.1 +$frame flame3 0.1 +$frame flame4 0.1 +$frame flame5 0.1 +$frame flame6 0.1 +$framegroupend + +$modelname flame2 // standing flame, no torch +$cd id1/models/flame2 +$origin 0 0 12 +$base base +$skin skin +$framegroupstart +$frame flame1 0.1 +$frame flame2 0.1 +$frame flame3 0.1 +$frame flame4 0.1 +$frame flame5 0.1 +$frame flame6 0.1 +$framegroupend +$framegroupstart +$frame flameb1 +$frame flameb2 +$frame flameb3 +$frame flameb4 +$frame flameb5 +$frame flameb6 +$frame flameb7 +$frame flameb8 +$frame flameb9 +$frame flameb10 +$frame flameb11 +$framegroupend + +$modelname zom_gib +$cd id1/models/zom_gib +$flags 32 // EF_ZOMGIB +$base base +$skin skin +$frame frame1 + +$modelname eyes +$cd id1/models/eyes +$origin 0 0 -24 +$base base +$skin skin +$frame frame1 + +$modelname spike +$cd id1/models/spike +$origin 0 0 0 +$base spike +$skin skin +$frame spike + +$modelname s_spike +$cd id1/models/s_spike +$origin 0 0 0 +$base spike +$skin skin +$frame spike + +$modelname v_spike +$cd id1/models/v_spike +$flags 128 // EF_TRACER3 +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname w_spike +$cd id1/models/w_spike +$flags 16 // EF_TRACER +$origin 0 0 0 +$base base +$skin skin +$framegroupstart +$frame frame1 0.1 +$frame frame2 0.1 +$frame frame3 0.1 +$frame frame4 0.1 +$framegroupend + +$modelname k_spike +$cd id1/models/k_spike +$flags 64 // EF_TRACER2 +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname backpack +$cd id1/models/backpack +$flags 8 // EF_ROTATE +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname grenade +$cd id1/models/grenade2 +$flags 2 // EF_GRENADE +$origin 0 0 0 +$base base +$skin skin +$frame grenade + +$modelname missile +$cd id1/models/missile +$flags 1 // EF_ROCKET +$origin 0 0 0 +$base base +$skin skin +$frame missile + +$modelname lavaball +$cd id1/models/lavaball +$flags 1 // EF_ROCKET +$origin 0 0 0 +$base base +$skin skin +$frame frame1 + +$modelname teleport +$cd id1/models/teleport +$origin 0 0 24 +$base base +$skin skin +$frame frame1 + diff --git a/quakec_mg1/monsters.qc b/quakec_mg1/monsters.qc new file mode 100644 index 0000000..06f0503 --- /dev/null +++ b/quakec_mg1/monsters.qc @@ -0,0 +1,301 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* ALL MONSTERS SHOULD BE 1 0 0 IN COLOR */ + +// name =[framenum, nexttime, nextthink] {code} +// expands to: +// name () +// { +// self.frame=framenum; +// self.nextthink = time + nexttime; +// self.think = nextthink +// +// }; + +float MONSTER_SPAWNED = 4; +float MONSTER_SPAWNED_ANGRY = 8; +float MONSTER_SPAWNED_TFOG = 16; +float MONSTER_WAITWALK = 4096; + +float MONSTER_TYPE_WALK = 1; +float MONSTER_TYPE_FLY = 2; +float MONSTER_TYPE_SWIM = 3; + +float MONSTER_SIZE_SMALL = 1; +float MONSTER_SIZE_LARGE = 2; + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void() monster_use = +{ + if (self.enemy) + return; + if (self.health <= 0) + return; + if (activator.items & IT_INVISIBILITY) + return; + if (activator.flags & FL_NOTARGET) + return; + if (activator.classname != "player") + return; + +// delay reaction so if the monster is teleported, its sound is still +// heard + self.enemy = activator; + self.nextthink = time + 0.1; + self.think = FoundTarget; +}; + +/* +================ +monster_death_use + +When a mosnter dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void() monster_death_use = +{ + // fall to ground + if (self.flags & FL_FLY) + self.flags = self.flags - FL_FLY; + if (self.flags & FL_SWIM) + self.flags = self.flags - FL_SWIM; + + if (cvar("horde") && self.classname != "monster_zombie") + { + remote_wavecheck(); + } + + + if (!self.target) + return; + + activator = self.enemy; + SUB_UseTargets (); +}; + +void monster_begin_walking() +{ + self.use = monster_use; + self.pausetime = 0; + self.th_walk(); +} + +//============================================================================ + +void() StartMonster = +{ + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, self.mdl); + if(self.state == MONSTER_SIZE_SMALL) setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + if(self.state == MONSTER_SIZE_LARGE) setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + + if(self.lefty == MONSTER_TYPE_WALK) + { + self.origin_z = self.origin_z + 1; // raise off floor a bit + droptofloor(); + } + + if (!walkmove(0,0)) + { + dprint ("monster in wall at: "); + dprint (vtos(self.origin)); + dprint (":\n"); + + dprint (self.classname); + if(self.target){ + dprint (" (target = "); + dprint (self.target); + dprint (" )"); + } + if(self.targetname){ + dprint (" (targetname = "); + dprint (self.targetname); + dprint (" )"); + } + dprint ("\n"); + } + + if(self.spawnflags & MONSTER_SPAWNED) + { + spawn_tdeath (self.origin, self); + if(self.spawnflags & MONSTER_SPAWNED_TFOG) + { + spawn_tfog(self.origin); + } + } + + + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 20; + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + + self.flags = self.flags | FL_MONSTER; + if(self.lefty == MONSTER_TYPE_FLY) self.flags = self.flags | FL_FLY; + if(self.lefty == MONSTER_TYPE_SWIM) self.flags = self.flags | FL_SWIM; + + if (self.target) + { + if(self.spawnflags & MONSTER_ATTACK_FRIEND) + { + entity f = SUB_RandomTarget(self, CanTakedamage); + if(f) + { + self.enemy = f; + FoundTarget (); + return; + } + } + + self.goalentity = self.movetarget = find(world, targetname, self.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + if (self.movetarget.classname == "path_corner") + { + if(self.spawnflags & MONSTER_WAITWALK) + { + self.pausetime = 99999999; + self.th_stand (); + self.use = monster_begin_walking; + } + else + { + self.th_walk (); + } + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } + + if(self.spawnflags & MONSTER_SPAWNED) + { + // Yoder, Sept29, horde fix so that preplaced monsters are counted on spawn_tdeath + if (horde_ent) + total_monsters = total_monsters + 1; + // end Yoder chunk, back to normal + + if(self.spawnflags & MONSTER_SPAWNED_ANGRY) + { + monster_use(); //Make monster angry! + } + + + } + else + { + // spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + } +}; + +void InitMonster(string model, float type, float size) +{ + if (deathmatch) + { + remove(self); + return; + } + + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + self.flags |= FL_FUTUREMONSTER; + + self.mdl = model; + self.lefty = type; + self.state = size; + + // CG: This was removed because it caused the entity alpha to differ from the baseline alpha somehow, causing it to be transmitted over the network every frame for every monster. + // self.alpha = 1; + + // Yoder, Sept29, fix for preplaced monsters in horde_ent + // in horde, mosnters shouldn't increase total count until spawned + if (cvar("horde")) + { + if (!(self.spawnflags & MONSTER_SPAWNED)) + total_monsters = total_monsters + 1; + } + else // normal behavior + total_monsters = total_monsters + 1; + + if(self.spawnflags & MONSTER_SPAWNED) + { + self.use = StartMonster; + return; + } + // delay drop to floor to make sure all doors have been spawned + // spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.think = StartMonster; + +} + +// Yoder Sept24 2021, Horde Merge +// Monster fade, used to fade out the model a few seconds after deathmatch +void() MonsterFade2 = +{ + if (self.alpha > 0) + { + self.alpha = self.alpha - 1 * frametime; + //self.think = MonsterFade; + self.nextthink = time; // think next frame + } + else + remove(self); +} +void() MonsterFade = +{ + if (!cvar("horde")) + { + self.think = SUB_Null; + return; + } + if (!self.alpha) + self.alpha = 1; + self.think = MonsterFade2; + self.nextthink = time + 10 + random() * 5; // wait a bit before fading +}; \ No newline at end of file diff --git a/quakec_mg1/monsters/boss.qc b/quakec_mg1/monsters/boss.qc new file mode 100644 index 0000000..dc93bf6 --- /dev/null +++ b/quakec_mg1/monsters/boss.qc @@ -0,0 +1,502 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BOSS-ONE + +============================================================================== +*/ +$cd id1/models/boss1 +$origin 0 0 -15 +$base base +$skin skin +$scale 5 + +$frame rise1 rise2 rise3 rise4 rise5 rise6 rise7 rise8 rise9 rise10 +$frame rise11 rise12 rise13 rise14 rise15 rise16 rise17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 +$frame walk9 walk10 walk11 walk12 walk13 walk14 walk15 +$frame walk16 walk17 walk18 walk19 walk20 walk21 walk22 +$frame walk23 walk24 walk25 walk26 walk27 walk28 walk29 walk30 walk31 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 attack12 attack13 attack14 attack15 +$frame attack16 attack17 attack18 attack19 attack20 attack21 attack22 +$frame attack23 + +$frame shocka1 shocka2 shocka3 shocka4 shocka5 shocka6 shocka7 shocka8 +$frame shocka9 shocka10 + +$frame shockb1 shockb2 shockb3 shockb4 shockb5 shockb6 + +$frame shockc1 shockc2 shockc3 shockc4 shockc5 shockc6 shockc7 shockc8 +$frame shockc9 shockc10 + +float BOSS_KILLABLE = 2; + +void(vector p) boss_missile; + +void(entity attacker, float damage) boss_pain; // Yoder add, September 9 2020 +void() SUB_Freeze; // Yoder add, Dec 16 2020 +void() SUB_Unfreeze; // Yoder add, Dec 16 2020 +void() boss_missile1; // yoder add, Jan 20 2020 +void() boss_face = +{ +// go for another player if multi player + if (self.enemy.health <= 0 || random() < 0.02) + { + self.enemy = find(self.enemy, classname, "player"); + if (!self.enemy) + self.enemy = find(self.enemy, classname, "player"); + } + if (self.enemy.health > 0) // yoder add Jan 20 2020 to stop Chthon from twitching each idle step. + ai_face(); +}; + +void() boss_rise1 =[ $rise1, boss_rise2 ] { +sound (self, CHAN_WEAPON, "boss1/out1.wav", 1, ATTN_NORM); +}; +void() boss_rise2 =[ $rise2, boss_rise3 ] { +sound (self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); +}; +void() boss_rise3 =[ $rise3, boss_rise4 ] {}; +void() boss_rise4 =[ $rise4, boss_rise5 ] {}; +void() boss_rise5 =[ $rise5, boss_rise6 ] {}; +void() boss_rise6 =[ $rise6, boss_rise7 ] {}; +void() boss_rise7 =[ $rise7, boss_rise8 ] {}; +void() boss_rise8 =[ $rise8, boss_rise9 ] {}; +void() boss_rise9 =[ $rise9, boss_rise10 ] {}; +void() boss_rise10 =[ $rise10, boss_rise11 ] {}; +void() boss_rise11 =[ $rise11, boss_rise12 ] {}; +void() boss_rise12 =[ $rise12, boss_rise13 ] {}; +void() boss_rise13 =[ $rise13, boss_rise14 ] {}; +void() boss_rise14 =[ $rise14, boss_rise15 ] {}; +void() boss_rise15 =[ $rise15, boss_rise16 ] {}; +void() boss_rise16 =[ $rise16, boss_rise17 ] {}; +void() boss_rise17 =[ $rise17, boss_missile1 ] {}; + +void() boss_idle1 =[ $walk1, boss_idle2 ] +{ +// look for other players +}; +void() boss_idle2 =[ $walk2, boss_idle3 ] {boss_face();}; +void() boss_idle3 =[ $walk3, boss_idle4 ] {boss_face();}; +void() boss_idle4 =[ $walk4, boss_idle5 ] {boss_face();}; +void() boss_idle5 =[ $walk5, boss_idle6 ] {boss_face();}; +void() boss_idle6 =[ $walk6, boss_idle7 ] {boss_face();}; +void() boss_idle7 =[ $walk7, boss_idle8 ] {boss_face();}; +void() boss_idle8 =[ $walk8, boss_idle9 ] {boss_face();}; +void() boss_idle9 =[ $walk9, boss_idle10 ] {boss_face();}; +void() boss_idle10 =[ $walk10, boss_idle11 ] {boss_face();}; +void() boss_idle11 =[ $walk11, boss_idle12 ] {boss_face();}; +void() boss_idle12 =[ $walk12, boss_idle13 ] {boss_face();}; +void() boss_idle13 =[ $walk13, boss_idle14 ] {boss_face();}; +void() boss_idle14 =[ $walk14, boss_idle15 ] {boss_face();}; +void() boss_idle15 =[ $walk15, boss_idle16 ] {boss_face();}; +void() boss_idle16 =[ $walk16, boss_idle17 ] {boss_face();}; +void() boss_idle17 =[ $walk17, boss_idle18 ] {boss_face();}; +void() boss_idle18 =[ $walk18, boss_idle19 ] {boss_face();}; +void() boss_idle19 =[ $walk19, boss_idle20 ] {boss_face();}; +void() boss_idle20 =[ $walk20, boss_idle21 ] {boss_face();}; +void() boss_idle21 =[ $walk21, boss_idle22 ] {boss_face();}; +void() boss_idle22 =[ $walk22, boss_idle23 ] {boss_face();}; +void() boss_idle23 =[ $walk23, boss_idle24 ] {boss_face();}; +void() boss_idle24 =[ $walk24, boss_idle25 ] {boss_face();}; +void() boss_idle25 =[ $walk25, boss_idle26 ] {boss_face();}; +void() boss_idle26 =[ $walk26, boss_idle27 ] {boss_face();}; +void() boss_idle27 =[ $walk27, boss_idle28 ] {boss_face();}; +void() boss_idle28 =[ $walk28, boss_idle29 ] {boss_face();}; +void() boss_idle29 =[ $walk29, boss_idle30 ] {boss_face();}; +void() boss_idle30 =[ $walk30, boss_idle31 ] {boss_face();}; +//void() boss_idle31 =[ $walk31, boss_idle1 ] {boss_face();}; +// Yoder change, Jan 20 2020 so that Chthon can re-aquire a target +// CURRENT BUG: Boss looks back and forth between dead player corpses each idle step, looks twitchy! +void() boss_idle31 =[ $walk31, boss_idle1 ] +{ + boss_face(); + if (self.enemy) + boss_missile1(); + else + boss_idle1(); +} + + +void() boss_missile1 =[ $attack1, boss_missile2 ] {boss_face();}; +void() boss_missile2 =[ $attack2, boss_missile3 ] {boss_face();}; +void() boss_missile3 =[ $attack3, boss_missile4 ] {boss_face();}; +void() boss_missile4 =[ $attack4, boss_missile5 ] {boss_face();}; +void() boss_missile5 =[ $attack5, boss_missile6 ] {boss_face();}; +void() boss_missile6 =[ $attack6, boss_missile7 ] {boss_face();}; +void() boss_missile7 =[ $attack7, boss_missile8 ] {boss_face();}; +void() boss_missile8 =[ $attack8, boss_missile9 ] {boss_face();}; +void() boss_missile9 =[ $attack9, boss_missile10 ] {boss_missile('100 100 200');}; +void() boss_missile10 =[ $attack10, boss_missile11 ] {boss_face();}; +void() boss_missile11 =[ $attack11, boss_missile12 ] {boss_face();}; +void() boss_missile12 =[ $attack12, boss_missile13 ] {boss_face();}; +void() boss_missile13 =[ $attack13, boss_missile14 ] {boss_face();}; +void() boss_missile14 =[ $attack14, boss_missile15 ] {boss_face();}; +void() boss_missile15 =[ $attack15, boss_missile16 ] {boss_face();}; +void() boss_missile16 =[ $attack16, boss_missile17 ] {boss_face();}; +void() boss_missile17 =[ $attack17, boss_missile18 ] {boss_face();}; +void() boss_missile18 =[ $attack18, boss_missile19 ] {boss_face();}; +void() boss_missile19 =[ $attack19, boss_missile20 ] {boss_face();}; +void() boss_missile20 =[ $attack20, boss_missile21 ] {boss_missile('100 -100 200');}; +void() boss_missile21 =[ $attack21, boss_missile22 ] {boss_face();}; +void() boss_missile22 =[ $attack22, boss_missile23 ] {boss_face();}; +void() boss_missile23 =[ $attack23, boss_missile1 ] {boss_face();}; + +// Yoder add, Jan 05 2021 +// this is a version of shock a but without doing the Freeze +void() boss_shockz1 =[ $shocka1, boss_shockz2 ] {}; +void() boss_shockz2 =[ $shocka2, boss_shockz3 ] {}; +void() boss_shockz3 =[ $shocka3, boss_shockz4 ] {}; +void() boss_shockz4 =[ $shocka4, boss_shockz5 ] {}; +void() boss_shockz5 =[ $shocka5, boss_shockz6 ] {}; +void() boss_shockz6 =[ $shocka6, boss_shockz7 ] {}; +void() boss_shockz7 =[ $shocka7, boss_shockz8 ] {}; +void() boss_shockz8 =[ $shocka8, boss_shockz9 ] {}; +void() boss_shockz9 =[ $shocka9, boss_shockz10 ] {}; +void() boss_shockz10 =[ $shocka10, boss_missile1 ] {}; +// end of add + +void() boss_shocka1 =[ $shocka1, boss_shocka2 ] {}; +void() boss_shocka2 =[ $shocka2, boss_shocka3 ] {}; +void() boss_shocka3 =[ $shocka3, boss_shocka4 ] {}; +void() boss_shocka4 =[ $shocka4, boss_shocka5 ] {}; +void() boss_shocka5 =[ $shocka5, boss_shocka6 ] {}; +void() boss_shocka6 =[ $shocka6, boss_shocka7 ] {}; +void() boss_shocka7 =[ $shocka7, boss_shocka8 ] {}; +void() boss_shocka8 =[ $shocka8, boss_shocka9 ] {}; +void() boss_shocka9 =[ $shocka9, boss_shocka10 ] {}; +void() boss_shocka10 =[ $shocka10, boss_missile1 ] { + if (self.spawnflags & BOSS_KILLABLE) + { + SUB_Freeze(); + self.use = SUB_Unfreeze; + } +}; + +void() boss_shockb1 =[ $shockb1, boss_shockb2 ] {}; +void() boss_shockb2 =[ $shockb2, boss_shockb3 ] {}; +void() boss_shockb3 =[ $shockb3, boss_shockb4 ] {}; +void() boss_shockb4 =[ $shockb4, boss_shockb5 ] {}; +void() boss_shockb5 =[ $shockb5, boss_shockb6 ] {}; +void() boss_shockb6 =[ $shockb6, boss_shockb7 ] {}; +void() boss_shockb7 =[ $shockb1, boss_shockb8 ] {}; +void() boss_shockb8 =[ $shockb2, boss_shockb9 ] {}; +void() boss_shockb9 =[ $shockb3, boss_shockb10 ] {}; +void() boss_shockb10 =[ $shockb4, boss_missile1 ] { + if (self.spawnflags & BOSS_KILLABLE) + { + SUB_Freeze(); + self.use = SUB_Unfreeze; + } +}; + +void() boss_shockc1 =[ $shockc1, boss_shockc2 ] {}; +void() boss_shockc2 =[ $shockc2, boss_shockc3 ] {}; +void() boss_shockc3 =[ $shockc3, boss_shockc4 ] {}; +void() boss_shockc4 =[ $shockc4, boss_shockc5 ] {}; +void() boss_shockc5 =[ $shockc5, boss_shockc6 ] {}; +void() boss_shockc6 =[ $shockc6, boss_shockc7 ] {}; +void() boss_shockc7 =[ $shockc7, boss_shockc8 ] {}; +void() boss_shockc8 =[ $shockc8, boss_shockc9 ] {}; +void() boss_shockc9 =[ $shockc9, boss_shockc10 ] {}; +void() boss_shockc10 =[ $shockc10, boss_death1 ] {}; + +void() boss_death1 = [$death1, boss_death2] { +sound (self, CHAN_VOICE, "boss1/death.wav", 1, ATTN_NORM); +}; +void() boss_death2 = [$death2, boss_death3] {}; +void() boss_death3 = [$death3, boss_death4] {}; +void() boss_death4 = [$death4, boss_death5] {}; +void() boss_death5 = [$death5, boss_death6] {}; +void() boss_death6 = [$death6, boss_death7] {}; +void() boss_death7 = [$death7, boss_death8] {}; +void() boss_death8 = [$death8, boss_death9] {}; +void() boss_death9 = [$death9, boss_death10] +{ + sound (self, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); +}; + +void() boss_death10 = [$death9, boss_death10] +{ + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); + SUB_UseTargets (); + remove (self); +}; + +void(vector p) boss_missile = +{ + local vector offang; + local vector org, vec, d; + local float t; + + offang = vectoangles (self.enemy.origin - self.origin); + makevectors (offang); + + org = self.origin + p_x*v_forward + p_y*v_right + p_z*'0 0 1'; + +// lead the player on hard mode + if (skill > 1) + { + t = vlen(self.enemy.origin - org) / 300; + vec = self.enemy.velocity; + vec_z = 0; + d = self.enemy.origin + t * vec; + } + else + { + d = self.enemy.origin; + } + + vec = normalize (d - org); + + launch_spike (org, vec); + setmodel (newmis, "progs/lavaball.mdl"); + newmis.avelocity = '200 100 300'; + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.touch = T_MissileTouch; // rocket explosion + sound (self, CHAN_WEAPON, "boss1/throw.wav", 1, ATTN_NORM); + +// check for dead enemy + if (self.enemy.health <= 0) + boss_idle1 (); +}; + +void() boss_awake = +{ + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + self.takedamage = DAMAGE_NO; + + setmodel (self, "progs/boss.mdl"); + setsize (self, '-128 -128 -24', '128 128 256'); + + if (skill == 0) + self.health = 1; + else + self.health = 3; + + self.enemy = activator; + + // Yoder add, June 14, 2020 + // Checking a spawnflag to see if chthon is killable + if (self.spawnflags & BOSS_KILLABLE) + { + self.takedamage = DAMAGE_AIM; + self.th_die = boss_death1; + self.health = 4000; + self.max_health = 4000; + self.use = monster_use; // prevent from being re-triggered into boss_awake state + self.th_pain = boss_pain; // Yoder add, September 9 2020 + } + // end of Yoder add + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.yaw_speed = 20; + boss_rise1 (); +}; + + +/*QUAKED monster_boss (1 0 0) (-128 -128 -24) (128 128 256) +*/ +void() monster_boss = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/boss.mdl"); + precache_model ("progs/lavaball.mdl"); + + precache_sound ("weapons/rocket1i.wav"); + precache_sound ("boss1/out1.wav"); + precache_sound ("boss1/sight1.wav"); + precache_sound ("misc/power.wav"); + precache_sound ("boss1/throw.wav"); + precache_sound ("boss1/pain.wav"); + precache_sound ("boss1/death.wav"); + + total_monsters = total_monsters + 1; + + self.use = boss_awake; +}; + +//=========================================================================== + +entity le1, le2; +float lightning_end; + +void() lightning_fire = +{ + local vector p1, p2; + + if (time >= lightning_end) + { // done here, put the terminals back up + self = le1; + door_go_down (); + self = le2; + door_go_down (); + return; + } + + p1 = (le1.mins + le1.maxs) * 0.5; + p1_z = le1.absmin_z - 16; + + p2 = (le2.mins + le2.maxs) * 0.5; + p2_z = le2.absmin_z - 16; + + // compensate for length of bolt + p2 = p2 - normalize(p2-p1)*100; + + self.nextthink = time + 0.1; + self.think = lightning_fire; + + WriteByte (MSG_ALL, SVC_TEMPENTITY); + WriteByte (MSG_ALL, TE_LIGHTNING3); + WriteEntity (MSG_ALL, world); + WriteCoord (MSG_ALL, p1_x); + WriteCoord (MSG_ALL, p1_y); + WriteCoord (MSG_ALL, p1_z); + WriteCoord (MSG_ALL, p2_x); + WriteCoord (MSG_ALL, p2_y); + WriteCoord (MSG_ALL, p2_z); +}; + +void() lightning_use = +{ + if (lightning_end >= time + 1) + return; + + le1 = find( world, target, "lightning"); + le2 = find( le1, target, "lightning"); + if (!le1 || !le2) + { + dprint ("missing lightning targets\n"); + return; + } + + if ( + (le1.state != STATE_TOP && le1.state != STATE_BOTTOM) + || (le2.state != STATE_TOP && le2.state != STATE_BOTTOM) + || (le1.state != le2.state) ) + { +// dprint ("not aligned\n"); + return; + } + +// don't let the electrodes go back up until the bolt is done + le1.nextthink = -1; + le2.nextthink = -1; + lightning_end = time + 2; + + sound (self, CHAN_VOICE, "misc/power.wav", 1, ATTN_NORM); + lightning_fire (); + +// advance the boss pain if down + self = find (world, classname, "monster_boss"); + if (!self) + return; + self.enemy = activator; + if (le1.state == STATE_TOP && self.health > 0) + { + sound (self, CHAN_VOICE, "boss1/pain.wav", 1, ATTN_NORM); + self.health = self.health - 1; + if (self.health >= 2) + boss_shocka1(); + else if (self.health == 1) + boss_shockb1(); + else if (self.health == 0) + boss_shockc1(); + } +}; + + +/*QUAKED event_lightning (0 1 1) (-16 -16 -16) (16 16 16) +Just for boss level. +*/ +void() event_lightning = +{ + self.use = lightning_use; +}; + +/* boss_pain +Play the pain animations when health drops below a certain percent + +Yoder add, September 9, 2020 +*/ +void(entity attacker, float damage) boss_pain = +{ + local float priorhealth; + + if (!(self.spawnflags & BOSS_KILLABLE)) + return; + + priorhealth = self.health + damage; + dprint("prior health: "); + dprint(ftos(priorhealth)); + dprint("\ncurrent health: "); + dprint(ftos(self.health)); + dprint("\n"); + + // check if first hit + if (priorhealth == self.max_health && self.health < self.max_health) + { + dprint("FIRST HIT!\n"); + boss_shockz1(); + SUB_UseTargets (); + } + // check if crossed 1/3 threshold + if ((self.health < (self.max_health/3)) && (priorhealth > (self.max_health/3))) + { + dprint("CROSSED 1/3 MARK!\n"); + boss_shockb1(); + SUB_UseTargets (); // Yoder add, Dec 16 2020 + return; + } + + // check if crossed 2/3 threshold + if ((self.health < (self.max_health * 2/3)) && (priorhealth > self.max_health * 2/3)) + { + dprint("CROSSED 2/3 MARK!\n"); + boss_shocka1(); + SUB_UseTargets (); // Yoder add, Dec 16 2020 + return; + } + +}; \ No newline at end of file diff --git a/quakec_mg1/monsters/demon.qc b/quakec_mg1/monsters/demon.qc new file mode 100644 index 0000000..edf4adf --- /dev/null +++ b/quakec_mg1/monsters/demon.qc @@ -0,0 +1,373 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +$cd id1/models/demon3 +$scale 0.8 +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + +$frame run1 run2 run3 run4 run5 run6 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 leap10 +$frame leap11 leap12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 CORPSEFRAME_DEMON_1 + +$frame attacka1 attacka2 attacka3 attacka4 attacka5 attacka6 attacka7 attacka8 +$frame attacka9 attacka10 attacka11 attacka12 attacka13 attacka14 attacka15 + +//============================================================================ + +void() Demon_JumpTouch; + +void() demon1_stand1 =[ $stand1, demon1_stand2 ] {ai_stand();}; +void() demon1_stand2 =[ $stand2, demon1_stand3 ] {ai_stand();}; +void() demon1_stand3 =[ $stand3, demon1_stand4 ] {ai_stand();}; +void() demon1_stand4 =[ $stand4, demon1_stand5 ] {ai_stand();}; +void() demon1_stand5 =[ $stand5, demon1_stand6 ] {ai_stand();}; +void() demon1_stand6 =[ $stand6, demon1_stand7 ] {ai_stand();}; +void() demon1_stand7 =[ $stand7, demon1_stand8 ] {ai_stand();}; +void() demon1_stand8 =[ $stand8, demon1_stand9 ] {ai_stand();}; +void() demon1_stand9 =[ $stand9, demon1_stand10 ] {ai_stand();}; +void() demon1_stand10 =[ $stand10, demon1_stand11 ] {ai_stand();}; +void() demon1_stand11 =[ $stand11, demon1_stand12 ] {ai_stand();}; +void() demon1_stand12 =[ $stand12, demon1_stand13 ] {ai_stand();}; +void() demon1_stand13 =[ $stand13, demon1_stand1 ] {ai_stand();}; + +void() demon1_walk1 =[ $walk1, demon1_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); +ai_walk(8); +}; +void() demon1_walk2 =[ $walk2, demon1_walk3 ] {ai_walk(6);}; +void() demon1_walk3 =[ $walk3, demon1_walk4 ] {ai_walk(6);}; +void() demon1_walk4 =[ $walk4, demon1_walk5 ] {ai_walk(7);}; +void() demon1_walk5 =[ $walk5, demon1_walk6 ] {ai_walk(4);}; +void() demon1_walk6 =[ $walk6, demon1_walk7 ] {ai_walk(6);}; +void() demon1_walk7 =[ $walk7, demon1_walk8 ] {ai_walk(10);}; +void() demon1_walk8 =[ $walk8, demon1_walk1 ] {ai_walk(10);}; + +void() demon1_run1 =[ $run1, demon1_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); +ai_run(20);}; +void() demon1_run2 =[ $run2, demon1_run3 ] {ai_run(15);}; +void() demon1_run3 =[ $run3, demon1_run4 ] {ai_run(36);}; +void() demon1_run4 =[ $run4, demon1_run5 ] {ai_run(20);}; +void() demon1_run5 =[ $run5, demon1_run6 ] {ai_run(15);}; +void() demon1_run6 =[ $run6, demon1_run1 ] {ai_run(36);}; + +void() demon1_jump1 =[ $leap1, demon1_jump2 ] {ai_face();}; +void() demon1_jump2 =[ $leap2, demon1_jump3 ] {ai_face();}; +void() demon1_jump3 =[ $leap3, demon1_jump4 ] {ai_face();}; +void() demon1_jump4 =[ $leap4, demon1_jump5 ] +{ + ai_face(); + + self.touch = Demon_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 250'; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; +void() demon1_jump5 =[ $leap5, demon1_jump6 ] {}; +void() demon1_jump6 =[ $leap6, demon1_jump7 ] {}; +void() demon1_jump7 =[ $leap7, demon1_jump8 ] {}; +void() demon1_jump8 =[ $leap8, demon1_jump9 ] {}; +void() demon1_jump9 =[ $leap9, demon1_jump10 ] {}; +void() demon1_jump10 =[ $leap10, demon1_jump1 ] { +self.nextthink = time + 3; +// if three seconds pass, assume demon is stuck and jump again +}; + +void() demon1_jump11 =[ $leap11, demon1_jump12 ] {}; +void() demon1_jump12 =[ $leap12, demon1_run1 ] {}; + + +void() demon1_atta1 =[ $attacka1, demon1_atta2 ] {ai_charge(4);}; +void() demon1_atta2 =[ $attacka2, demon1_atta3 ] {ai_charge(0);}; +void() demon1_atta3 =[ $attacka3, demon1_atta4 ] {ai_charge(0);}; +void() demon1_atta4 =[ $attacka4, demon1_atta5 ] {ai_charge(1);}; +void() demon1_atta5 =[ $attacka5, demon1_atta6 ] {ai_charge(2); Demon_Melee(200);}; +void() demon1_atta6 =[ $attacka6, demon1_atta7 ] {ai_charge(1);}; +void() demon1_atta7 =[ $attacka7, demon1_atta8 ] {ai_charge(6);}; +void() demon1_atta8 =[ $attacka8, demon1_atta9 ] {ai_charge(8);}; +void() demon1_atta9 =[ $attacka9, demon1_atta10] {ai_charge(4);}; +void() demon1_atta10 =[ $attacka10, demon1_atta11] {ai_charge(2);}; +void() demon1_atta11 =[ $attacka11, demon1_atta12] {Demon_Melee(-200);}; +void() demon1_atta12 =[ $attacka12, demon1_atta13] {ai_charge(5);}; +void() demon1_atta13 =[ $attacka13, demon1_atta14] {ai_charge(8);}; +void() demon1_atta14 =[ $attacka14, demon1_atta15] {ai_charge(4);}; +void() demon1_atta15 =[ $attacka15, demon1_run1] {ai_charge(4);}; + +void() demon1_pain1 =[ $pain1, demon1_pain2 ] {}; +void() demon1_pain2 =[ $pain2, demon1_pain3 ] {}; +void() demon1_pain3 =[ $pain3, demon1_pain4 ] {}; +void() demon1_pain4 =[ $pain4, demon1_pain5 ] {}; +void() demon1_pain5 =[ $pain5, demon1_pain6 ] {}; +void() demon1_pain6 =[ $pain6, demon1_run1 ] {}; + +void(entity attacker, float damage) demon1_pain = +{ + if (self.touch == Demon_JumpTouch) + return; + + if (self.pain_finished > time) + return; + + self.pain_finished = time + 1; + sound (self, CHAN_VOICE, "demon/dpain1.wav", 1, ATTN_NORM); + + if (random()*200 > damage) + return; // didn't flinch + + demon1_pain1 (); +}; + +void() demon1_die1 =[ $death1, demon1_die2 ] { +sound (self, CHAN_VOICE, "demon/ddeath.wav", 1, ATTN_NORM);}; +void() demon1_die2 =[ $death2, demon1_die3 ] {}; +void() demon1_die3 =[ $death3, demon1_die4 ] {}; +void() demon1_die4 =[ $death4, demon1_die5 ] {}; +void() demon1_die5 =[ $death5, demon1_die6 ] {}; +void() demon1_die6 =[ $death6, demon1_die7 ] +{self.solid = SOLID_NOT;}; +void() demon1_die7 =[ $death7, demon1_die8 ] {}; +void() demon1_die8 =[ $death8, demon1_die9 ] {}; +void() demon1_die9 =[ $CORPSEFRAME_DEMON_1, demon1_die9 ] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + +void() demon_die = +{ +// check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_demon.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + return; + } + +// regular death + demon1_die1 (); +}; + + +void() Demon_MeleeAttack = +{ + demon1_atta1 (); +}; + + +/*QUAKED monster_demon1 (1 0 0) (-32 -32 -24) (32 32 64) Ambush + +*/ +void() monster_demon1 = +{ + precache_model ("progs/demon.mdl"); + precache_model ("progs/h_demon.mdl"); + + precache_sound ("demon/ddeath.wav"); + precache_sound ("demon/dhit2.wav"); + precache_sound ("demon/djump.wav"); + precache_sound ("demon/dpain1.wav"); + precache_sound ("demon/idle1.wav"); + precache_sound ("demon/sight2.wav"); + + self.health = 300; + + self.th_stand = demon1_stand1; + self.th_walk = demon1_walk1; + self.th_run = demon1_run1; + self.th_die = demon_die; + self.th_melee = Demon_MeleeAttack; // one of two attacks + self.th_missile = demon1_jump1; // jump attack + self.th_pain = demon1_pain; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + InitMonster("progs/demon.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_LARGE); +}; + + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +/* +============== +CheckDemonMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDemonMelee = +{ + if (enemy_range == RANGE_MELEE) + { + self.attack_state = AS_MELEE; + return TRUE; + } + return FALSE; +}; + +/* +============== +CheckDemonJump + +============== +*/ +float() CheckDemonJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 100) + return FALSE; + + if (d > 200) + { + if (random() < 0.9) + return FALSE; + } + + return TRUE; +}; + +float() DemonCheckAttack = +{ + // if close enough for slashing, go for it + if (CheckDemonMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDemonJump ()) + { + self.attack_state = AS_MISSILE; + sound (self, CHAN_VOICE, "demon/djump.wav", 1, ATTN_NORM); + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +void(float side) Demon_Melee = +{ + local float ldmg; + local vector delta; + + ai_face (); + walkmove (self.ideal_yaw, 12); // allow a little closing + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + sound (self, CHAN_WEAPON, "demon/dhit2.wav", 1, ATTN_NORM); + ldmg = 10 + 5*random(); + T_Damage (self.enemy, self, self, ldmg); + + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); +}; + + +void() Demon_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage && self.attack_finished < time) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 40 + 10*random(); + T_Damage (other, self, self, ldmg); + self.attack_finished = time + 0.5; + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { + self.touch = SUB_Null; + self.think = demon1_jump1; + self.nextthink = time + 0.1; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = demon1_jump11; + self.nextthink = time + 0.1; +}; + diff --git a/quakec_mg1/monsters/dog.qc b/quakec_mg1/monsters/dog.qc new file mode 100644 index 0000000..c8c890b --- /dev/null +++ b/quakec_mg1/monsters/dog.qc @@ -0,0 +1,380 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DOG + +============================================================================== +*/ +$cd id1/models/dog +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 CORPSEFRAME_DOG_1 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame CORPSEFRAME_DOG_2 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + + +void() dog_leap1; +void() dog_run1; + +/* +================ +dog_bite + +================ +*/ +void() dog_bite = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + + ai_charge(10); + + if (!CanDamage (self.enemy, self)) + return; + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 8; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() Dog_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage && self.attack_finished < time) + { + if ( vlen(self.velocity) > 300 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + self.attack_finished = time + 0.5; + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = dog_leap1; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = dog_run1; + self.nextthink = time + 0.1; +}; + + +void() dog_stand1 =[ $stand1, dog_stand2 ] {ai_stand();}; +void() dog_stand2 =[ $stand2, dog_stand3 ] {ai_stand();}; +void() dog_stand3 =[ $stand3, dog_stand4 ] {ai_stand();}; +void() dog_stand4 =[ $stand4, dog_stand5 ] {ai_stand();}; +void() dog_stand5 =[ $stand5, dog_stand6 ] {ai_stand();}; +void() dog_stand6 =[ $stand6, dog_stand7 ] {ai_stand();}; +void() dog_stand7 =[ $stand7, dog_stand8 ] {ai_stand();}; +void() dog_stand8 =[ $stand8, dog_stand9 ] {ai_stand();}; +void() dog_stand9 =[ $stand9, dog_stand1 ] {ai_stand();}; + +void() dog_walk1 =[ $walk1 , dog_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); +ai_walk(8);}; +void() dog_walk2 =[ $walk2 , dog_walk3 ] {ai_walk(8);}; +void() dog_walk3 =[ $walk3 , dog_walk4 ] {ai_walk(8);}; +void() dog_walk4 =[ $walk4 , dog_walk5 ] {ai_walk(8);}; +void() dog_walk5 =[ $walk5 , dog_walk6 ] {ai_walk(8);}; +void() dog_walk6 =[ $walk6 , dog_walk7 ] {ai_walk(8);}; +void() dog_walk7 =[ $walk7 , dog_walk8 ] {ai_walk(8);}; +void() dog_walk8 =[ $walk8 , dog_walk1 ] {ai_walk(8);}; + +void() dog_run1 =[ $run1 , dog_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); +ai_run(16);}; +void() dog_run2 =[ $run2 , dog_run3 ] {ai_run(32);}; +void() dog_run3 =[ $run3 , dog_run4 ] {ai_run(32);}; +void() dog_run4 =[ $run4 , dog_run5 ] {ai_run(20);}; +void() dog_run5 =[ $run5 , dog_run6 ] {ai_run(64);}; +void() dog_run6 =[ $run6 , dog_run7 ] {ai_run(32);}; +void() dog_run7 =[ $run7 , dog_run8 ] {ai_run(16);}; +void() dog_run8 =[ $run8 , dog_run9 ] {ai_run(32);}; +void() dog_run9 =[ $run9 , dog_run10 ] {ai_run(32);}; +void() dog_run10 =[ $run10 , dog_run11 ] {ai_run(20);}; +void() dog_run11 =[ $run11 , dog_run12 ] {ai_run(64);}; +void() dog_run12 =[ $run12 , dog_run1 ] {ai_run(32);}; + +void() dog_atta1 =[ $attack1, dog_atta2 ] {ai_charge(10);}; +void() dog_atta2 =[ $attack2, dog_atta3 ] {ai_charge(10);}; +void() dog_atta3 =[ $attack3, dog_atta4 ] {ai_charge(10);}; +void() dog_atta4 =[ $attack4, dog_atta5 ] { +sound (self, CHAN_VOICE, "dog/dattack1.wav", 1, ATTN_NORM); +dog_bite();}; +void() dog_atta5 =[ $attack5, dog_atta6 ] {ai_charge(10);}; +void() dog_atta6 =[ $attack6, dog_atta7 ] {ai_charge(10);}; +void() dog_atta7 =[ $attack7, dog_atta8 ] {ai_charge(10);}; +void() dog_atta8 =[ $attack8, dog_run1 ] {ai_charge(10);}; + +void() dog_leap1 =[ $leap1, dog_leap2 ] {ai_face();}; +void() dog_leap2 =[ $leap2, dog_leap3 ] +{ + ai_face(); + + self.touch = Dog_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 300 + '0 0 200'; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; + +void() dog_leap3 =[ $leap3, dog_leap4 ] {}; +void() dog_leap4 =[ $leap4, dog_leap5 ] {}; +void() dog_leap5 =[ $leap5, dog_leap6 ] {}; +void() dog_leap6 =[ $leap6, dog_leap7 ] {}; +void() dog_leap7 =[ $leap7, dog_leap8 ] {}; +void() dog_leap8 =[ $leap8, dog_leap9 ] {}; +void() dog_leap9 =[ $leap9, dog_leap9 ] {}; + +void() dog_pain1 =[ $pain1 , dog_pain2 ] {}; +void() dog_pain2 =[ $pain2 , dog_pain3 ] {}; +void() dog_pain3 =[ $pain3 , dog_pain4 ] {}; +void() dog_pain4 =[ $pain4 , dog_pain5 ] {}; +void() dog_pain5 =[ $pain5 , dog_pain6 ] {}; +void() dog_pain6 =[ $pain6 , dog_run1 ] {}; + +void() dog_painb1 =[ $painb1 , dog_painb2 ] {}; +void() dog_painb2 =[ $painb2 , dog_painb3 ] {}; +void() dog_painb3 =[ $painb3 , dog_painb4 ] {ai_pain(4);}; +void() dog_painb4 =[ $painb4 , dog_painb5 ] {ai_pain(12);}; +void() dog_painb5 =[ $painb5 , dog_painb6 ] {ai_pain(12);}; +void() dog_painb6 =[ $painb6 , dog_painb7 ] {ai_pain(2);}; +void() dog_painb7 =[ $painb7 , dog_painb8 ] {}; +void() dog_painb8 =[ $painb8 , dog_painb9 ] {ai_pain(4);}; +void() dog_painb9 =[ $painb9 , dog_painb10 ] {}; +void() dog_painb10 =[ $painb10 , dog_painb11 ] {ai_pain(10);}; +void() dog_painb11 =[ $painb11 , dog_painb12 ] {}; +void() dog_painb12 =[ $painb12 , dog_painb13 ] {}; +void() dog_painb13 =[ $painb13 , dog_painb14 ] {}; +void() dog_painb14 =[ $painb14 , dog_painb15 ] {}; +void() dog_painb15 =[ $painb15 , dog_painb16 ] {}; +void() dog_painb16 =[ $painb16 , dog_run1 ] {}; + +void(entity attacker, float damage) dog_pain = +{ + sound (self, CHAN_VOICE, "dog/dpain1.wav", 1, ATTN_NORM); + + if (random() > 0.5) + dog_pain1 (); + else + dog_painb1 (); +}; + +void() dog_die1 =[ $death1, dog_die2 ] {}; +void() dog_die2 =[ $death2, dog_die3 ] {}; +void() dog_die3 =[ $death3, dog_die4 ] {}; +void() dog_die4 =[ $death4, dog_die5 ] {}; +void() dog_die5 =[ $death5, dog_die6 ] {}; +void() dog_die6 =[ $death6, dog_die7 ] {}; +void() dog_die7 =[ $death7, dog_die8 ] {}; +void() dog_die8 =[ $death8, dog_die9 ] {}; +void() dog_die9 =[ $CORPSEFRAME_DOG_1, dog_die9 ] +{ + self.think = MonsterFade; + self.nextthink = time + FADETIME; +}; + +void() dog_dieb1 =[ $deathb1, dog_dieb2 ] {}; +void() dog_dieb2 =[ $deathb2, dog_dieb3 ] {}; +void() dog_dieb3 =[ $deathb3, dog_dieb4 ] {}; +void() dog_dieb4 =[ $deathb4, dog_dieb5 ] {}; +void() dog_dieb5 =[ $deathb5, dog_dieb6 ] {}; +void() dog_dieb6 =[ $deathb6, dog_dieb7 ] {}; +void() dog_dieb7 =[ $deathb7, dog_dieb8 ] {}; +void() dog_dieb8 =[ $deathb8, dog_dieb9 ] {}; +void() dog_dieb9 =[ $CORPSEFRAME_DOG_2, dog_dieb9 ] +{ + self.think = MonsterFade; + self.nextthink = time + FADETIME; +}; + + +void() dog_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowHead ("progs/h_dog.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "dog/ddeath.wav", 1, ATTN_NORM); + self.solid = SOLID_NOT; + + if (random() > 0.5) + dog_die1 (); + else + dog_dieb1 (); +}; + +//============================================================================ + +/* +============== +CheckDogMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDogMelee = +{ + if (enemy_range == RANGE_MELEE) + { + self.attack_state = AS_MELEE; + return TRUE; + } + return FALSE; +}; + +/* +============== +CheckDogJump + +============== +*/ +float() CheckDogJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 80) + return FALSE; + + if (d > 150) + return FALSE; + + return TRUE; +}; + +float() DogCheckAttack = +{ + // if close enough for slashing, go for it + if (CheckDogMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDogJump ()) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +/*QUAKED monster_dog (1 0 0) (-32 -32 -24) (32 32 40) Ambush + +*/ +void() monster_dog = +{ + precache_model ("progs/h_dog.mdl"); + precache_model ("progs/dog.mdl"); + + precache_sound ("dog/dattack1.wav"); + precache_sound ("dog/ddeath.wav"); + precache_sound ("dog/dpain1.wav"); + precache_sound ("dog/dsight.wav"); + precache_sound ("dog/idle.wav"); + + self.health = 25; + + self.th_stand = dog_stand1; + self.th_walk = dog_walk1; + self.th_run = dog_run1; + self.th_pain = dog_pain; + self.th_die = dog_die; + self.th_melee = dog_atta1; + self.th_missile = dog_leap1; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + InitMonster("progs/dog.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_LARGE); +}; diff --git a/quakec_mg1/monsters/enforcer.qc b/quakec_mg1/monsters/enforcer.qc new file mode 100644 index 0000000..f459ece --- /dev/null +++ b/quakec_mg1/monsters/enforcer.qc @@ -0,0 +1,365 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd id1/models/enforcer +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 death12 death13 CORPSEFRAME_ENFORCER_1 + +$frame fdeath1 fdeath2 fdeath3 fdeath4 fdeath5 fdeath6 fdeath7 fdeath8 +$frame fdeath9 fdeath10 CORPSEFRAME_ENFORCER_2 + +$frame paina1 paina2 paina3 paina4 + +$frame painb1 painb2 painb3 painb4 painb5 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 +$frame paind9 paind10 paind11 paind12 paind13 paind14 paind15 paind16 +$frame paind17 paind18 paind19 + + +void() Laser_Touch = +{ + local vector org; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + sound (self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); + org = self.origin - 8*normalize(self.velocity); + + if (other.health) + { + SpawnBlood (org, self.velocity*0.2, 15); + T_Damage (other, self, self.owner, 15); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } + + remove(self); +}; + +void(vector org, vector vec) LaunchLaser = +{ + if (self.classname == "monster_enforcer") + sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM); + + vec = normalize(vec); + + newmis = spawn(); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLY; + newmis.solid = SOLID_BBOX; + newmis.effects = EF_DIMLIGHT; + + setmodel (newmis, "progs/laser.mdl"); + setsize (newmis, '0 0 0', '0 0 0'); + + setorigin (newmis, org); + + newmis.velocity = vec * 600; + newmis.angles = vectoangles(newmis.velocity); + + newmis.nextthink = time + 5; + newmis.think = SUB_Remove; + newmis.touch = Laser_Touch; +}; + + + +void() enforcer_fire = +{ + local vector org; + + self.effects = self.effects | EF_MUZZLEFLASH; + makevectors (self.angles); + + org = self.origin + v_forward * 30 + v_right * 8.5 + '0 0 16'; + + LaunchLaser(org, self.enemy.origin - self.origin); +}; + +//============================================================================ + +void() enf_stand1 =[ $stand1, enf_stand2 ] {ai_stand();}; +void() enf_stand2 =[ $stand2, enf_stand3 ] {ai_stand();}; +void() enf_stand3 =[ $stand3, enf_stand4 ] {ai_stand();}; +void() enf_stand4 =[ $stand4, enf_stand5 ] {ai_stand();}; +void() enf_stand5 =[ $stand5, enf_stand6 ] {ai_stand();}; +void() enf_stand6 =[ $stand6, enf_stand7 ] {ai_stand();}; +void() enf_stand7 =[ $stand7, enf_stand1 ] {ai_stand();}; + +void() enf_walk1 =[ $walk1 , enf_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); +ai_walk(2);}; +void() enf_walk2 =[ $walk2 , enf_walk3 ] {ai_walk(4);}; +void() enf_walk3 =[ $walk3 , enf_walk4 ] {ai_walk(4);}; +void() enf_walk4 =[ $walk4 , enf_walk5 ] {ai_walk(3);}; +void() enf_walk5 =[ $walk5 , enf_walk6 ] {ai_walk(1);}; +void() enf_walk6 =[ $walk6 , enf_walk7 ] {ai_walk(2);}; +void() enf_walk7 =[ $walk7 , enf_walk8 ] {ai_walk(2);}; +void() enf_walk8 =[ $walk8 , enf_walk9 ] {ai_walk(1);}; +void() enf_walk9 =[ $walk9 , enf_walk10 ] {ai_walk(2);}; +void() enf_walk10 =[ $walk10, enf_walk11 ] {ai_walk(4);}; +void() enf_walk11 =[ $walk11, enf_walk12 ] {ai_walk(4);}; +void() enf_walk12 =[ $walk12, enf_walk13 ] {ai_walk(1);}; +void() enf_walk13 =[ $walk13, enf_walk14 ] {ai_walk(2);}; +void() enf_walk14 =[ $walk14, enf_walk15 ] {ai_walk(3);}; +void() enf_walk15 =[ $walk15, enf_walk16 ] {ai_walk(4);}; +void() enf_walk16 =[ $walk16, enf_walk1 ] {ai_walk(2);}; + +void() enf_run1 =[ $run1 , enf_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); +ai_run(18);}; +void() enf_run2 =[ $run2 , enf_run3 ] {ai_run(14);}; +void() enf_run3 =[ $run3 , enf_run4 ] {ai_run(7);}; +void() enf_run4 =[ $run4 , enf_run5 ] {ai_run(12);}; +void() enf_run5 =[ $run5 , enf_run6 ] {ai_run(14);}; +void() enf_run6 =[ $run6 , enf_run7 ] {ai_run(14);}; +void() enf_run7 =[ $run7 , enf_run8 ] {ai_run(7);}; +void() enf_run8 =[ $run8 , enf_run1 ] {ai_run(11);}; + +void() enf_atk1 =[ $attack1, enf_atk2 ] {ai_face();}; +void() enf_atk2 =[ $attack2, enf_atk3 ] {ai_face();}; +void() enf_atk3 =[ $attack3, enf_atk4 ] {ai_face();}; +void() enf_atk4 =[ $attack4, enf_atk5 ] {ai_face();}; +void() enf_atk5 =[ $attack5, enf_atk6 ] {ai_face();}; +void() enf_atk6 =[ $attack6, enf_atk7 ] {enforcer_fire();}; +void() enf_atk7 =[ $attack7, enf_atk8 ] {ai_face();}; +void() enf_atk8 =[ $attack8, enf_atk9 ] {ai_face();}; +void() enf_atk9 =[ $attack5, enf_atk10 ] {ai_face();}; +void() enf_atk10 =[ $attack6, enf_atk11 ] {enforcer_fire();}; +void() enf_atk11 =[ $attack7, enf_atk12 ] {ai_face();}; +void() enf_atk12 =[ $attack8, enf_atk13 ] {ai_face();}; +void() enf_atk13 =[ $attack9, enf_atk14 ] {ai_face();}; +void() enf_atk14 =[ $attack10, enf_run1 ] {ai_face(); +SUB_CheckRefire (enf_atk1); +}; + +void() enf_paina1 =[ $paina1, enf_paina2 ] {}; +void() enf_paina2 =[ $paina2, enf_paina3 ] {}; +void() enf_paina3 =[ $paina3, enf_paina4 ] {}; +void() enf_paina4 =[ $paina4, enf_run1 ] {}; + +void() enf_painb1 =[ $painb1, enf_painb2 ] {}; +void() enf_painb2 =[ $painb2, enf_painb3 ] {}; +void() enf_painb3 =[ $painb3, enf_painb4 ] {}; +void() enf_painb4 =[ $painb4, enf_painb5 ] {}; +void() enf_painb5 =[ $painb5, enf_run1 ] {}; + +void() enf_painc1 =[ $painc1, enf_painc2 ] {}; +void() enf_painc2 =[ $painc2, enf_painc3 ] {}; +void() enf_painc3 =[ $painc3, enf_painc4 ] {}; +void() enf_painc4 =[ $painc4, enf_painc5 ] {}; +void() enf_painc5 =[ $painc5, enf_painc6 ] {}; +void() enf_painc6 =[ $painc6, enf_painc7 ] {}; +void() enf_painc7 =[ $painc7, enf_painc8 ] {}; +void() enf_painc8 =[ $painc8, enf_run1 ] {}; + +void() enf_paind1 =[ $paind1, enf_paind2 ] {}; +void() enf_paind2 =[ $paind2, enf_paind3 ] {}; +void() enf_paind3 =[ $paind3, enf_paind4 ] {}; +void() enf_paind4 =[ $paind4, enf_paind5 ] {ai_painforward(2);}; +void() enf_paind5 =[ $paind5, enf_paind6 ] {ai_painforward(1);}; +void() enf_paind6 =[ $paind6, enf_paind7 ] {}; +void() enf_paind7 =[ $paind7, enf_paind8 ] {}; +void() enf_paind8 =[ $paind8, enf_paind9 ] {}; +void() enf_paind9 =[ $paind9, enf_paind10 ] {}; +void() enf_paind10 =[ $paind10, enf_paind11 ] {}; +void() enf_paind11 =[ $paind11, enf_paind12 ] {ai_painforward(1);}; +void() enf_paind12 =[ $paind12, enf_paind13 ] {ai_painforward(1);}; +void() enf_paind13 =[ $paind13, enf_paind14 ] {ai_painforward(1);}; +void() enf_paind14 =[ $paind14, enf_paind15 ] {}; +void() enf_paind15 =[ $paind15, enf_paind16 ] {}; +void() enf_paind16 =[ $paind16, enf_paind17 ] {ai_pain(1);}; +void() enf_paind17 =[ $paind17, enf_paind18 ] {ai_pain(1);}; +void() enf_paind18 =[ $paind18, enf_paind19 ] {}; +void() enf_paind19 =[ $paind19, enf_run1 ] {}; + +void(entity attacker, float damage) enf_pain = +{ + local float r; + + r = random (); + if (self.pain_finished > time) + return; + + + if (r < 0.5) + sound (self, CHAN_VOICE, "enforcer/pain1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "enforcer/pain2.wav", 1, ATTN_NORM); + + if (r < 0.2) + { + self.pain_finished = time + 1; + enf_paina1 (); + } + else if (r < 0.4) + { + self.pain_finished = time + 1; + enf_painb1 (); + } + else if (r < 0.7) + { + self.pain_finished = time + 1; + enf_painc1 (); + } + else + { + self.pain_finished = time + 2; + enf_paind1 (); + } +}; + +//============================================================================ + + + + +void() enf_die1 =[ $death1, enf_die2 ] {}; +void() enf_die2 =[ $death2, enf_die3 ] {}; +void() enf_die3 =[ $death3, enf_die4 ] +{self.solid = SOLID_NOT;self.ammo_cells = 5;DropBackpack();}; +void() enf_die4 =[ $death4, enf_die5 ] {ai_forward(14);}; +void() enf_die5 =[ $death5, enf_die6 ] {ai_forward(2);}; +void() enf_die6 =[ $death6, enf_die7 ] {}; +void() enf_die7 =[ $death7, enf_die8 ] {}; +void() enf_die8 =[ $death8, enf_die9 ] {}; +void() enf_die9 =[ $death9, enf_die10 ] {ai_forward(3);}; +void() enf_die10 =[ $death10, enf_die11 ] {ai_forward(5);}; +void() enf_die11 =[ $death11, enf_die12 ] {ai_forward(5);}; +void() enf_die12 =[ $death12, enf_die13 ] {ai_forward(5);}; +void() enf_die13 =[ $death13, enf_die14 ] {}; +void() enf_die14 =[ $CORPSEFRAME_ENFORCER_1, enf_die14 ] +{ + self.think = MonsterFade; + self.nextthink = time + FADETIME; +}; + +void() enf_fdie1 =[ $fdeath1, enf_fdie2 ] {}; +void() enf_fdie2 =[ $fdeath2, enf_fdie3 ] {}; +void() enf_fdie3 =[ $fdeath3, enf_fdie4 ] +{self.solid = SOLID_NOT;self.ammo_cells = 5;DropBackpack();}; +void() enf_fdie4 =[ $fdeath4, enf_fdie5 ] {}; +void() enf_fdie5 =[ $fdeath5, enf_fdie6 ] {}; +void() enf_fdie6 =[ $fdeath6, enf_fdie7 ] {}; +void() enf_fdie7 =[ $fdeath7, enf_fdie8 ] {}; +void() enf_fdie8 =[ $fdeath8, enf_fdie9 ] {}; +void() enf_fdie9 =[ $fdeath9, enf_fdie10 ] {}; +void() enf_fdie10 =[ $fdeath10, enf_fdie11 ] {}; +void() enf_fdie11 =[ $CORPSEFRAME_ENFORCER_2, enf_fdie11 ] +{ + self.think = MonsterFade; + self.nextthink = time + FADETIME; +}; + + +void() enf_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_mega.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "enforcer/death1.wav", 1, ATTN_NORM); + if (random() > 0.5) + enf_die1 (); + else + enf_fdie1 (); +}; + + +/*QUAKED monster_enforcer (1 0 0) (-16 -16 -24) (16 16 40) Ambush + +*/ +void() monster_enforcer = +{ + precache_model2 ("progs/enforcer.mdl"); + precache_model2 ("progs/h_mega.mdl"); + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/idle1.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + + self.health = 80; + + self.th_stand = enf_stand1; + self.th_walk = enf_walk1; + self.th_run = enf_run1; + self.th_pain = enf_pain; + self.th_die = enf_die; + self.th_missile = enf_atk1; + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + + InitMonster("progs/enforcer.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_SMALL); +}; diff --git a/quakec_mg1/monsters/fish.qc b/quakec_mg1/monsters/fish.qc new file mode 100644 index 0000000..47934b7 --- /dev/null +++ b/quakec_mg1/monsters/fish.qc @@ -0,0 +1,199 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +$cd id1/models/fish +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 attack11 attack12 attack13 +$frame attack14 attack15 attack16 attack17 attack18 + +$frame death1 death2 death3 death4 death5 death6 death7 +$frame death8 death9 death10 death11 death12 death13 death14 death15 +$frame death16 death17 death18 death19 death20 CORPSEFRAME_FISH_1 + +$frame swim1 swim2 swim3 swim4 swim5 swim6 swim7 swim8 +$frame swim9 swim10 swim11 swim12 swim13 swim14 swim15 swim16 swim17 +$frame swim18 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 pain7 pain8 +$frame pain9 + +void() f_stand1 =[ $swim1, f_stand2 ] {ai_stand();}; +void() f_stand2 =[ $swim2, f_stand3 ] {ai_stand();}; +void() f_stand3 =[ $swim3, f_stand4 ] {ai_stand();}; +void() f_stand4 =[ $swim4, f_stand5 ] {ai_stand();}; +void() f_stand5 =[ $swim5, f_stand6 ] {ai_stand();}; +void() f_stand6 =[ $swim6, f_stand7 ] {ai_stand();}; +void() f_stand7 =[ $swim7, f_stand8 ] {ai_stand();}; +void() f_stand8 =[ $swim8, f_stand9 ] {ai_stand();}; +void() f_stand9 =[ $swim9, f_stand10 ] {ai_stand();}; +void() f_stand10 =[ $swim10, f_stand11 ] {ai_stand();}; +void() f_stand11 =[ $swim11, f_stand12 ] {ai_stand();}; +void() f_stand12 =[ $swim12, f_stand13 ] {ai_stand();}; +void() f_stand13 =[ $swim13, f_stand14 ] {ai_stand();}; +void() f_stand14 =[ $swim14, f_stand15 ] {ai_stand();}; +void() f_stand15 =[ $swim15, f_stand16 ] {ai_stand();}; +void() f_stand16 =[ $swim16, f_stand17 ] {ai_stand();}; +void() f_stand17 =[ $swim17, f_stand18 ] {ai_stand();}; +void() f_stand18 =[ $swim18, f_stand1 ] {ai_stand();}; + +void() f_walk1 =[ $swim1, f_walk2 ] {ai_walk(8);}; +void() f_walk2 =[ $swim2, f_walk3 ] {ai_walk(8);}; +void() f_walk3 =[ $swim3, f_walk4 ] {ai_walk(8);}; +void() f_walk4 =[ $swim4, f_walk5 ] {ai_walk(8);}; +void() f_walk5 =[ $swim5, f_walk6 ] {ai_walk(8);}; +void() f_walk6 =[ $swim6, f_walk7 ] {ai_walk(8);}; +void() f_walk7 =[ $swim7, f_walk8 ] {ai_walk(8);}; +void() f_walk8 =[ $swim8, f_walk9 ] {ai_walk(8);}; +void() f_walk9 =[ $swim9, f_walk10 ] {ai_walk(8);}; +void() f_walk10 =[ $swim10, f_walk11 ] {ai_walk(8);}; +void() f_walk11 =[ $swim11, f_walk12 ] {ai_walk(8);}; +void() f_walk12 =[ $swim12, f_walk13 ] {ai_walk(8);}; +void() f_walk13 =[ $swim13, f_walk14 ] {ai_walk(8);}; +void() f_walk14 =[ $swim14, f_walk15 ] {ai_walk(8);}; +void() f_walk15 =[ $swim15, f_walk16 ] {ai_walk(8);}; +void() f_walk16 =[ $swim16, f_walk17 ] {ai_walk(8);}; +void() f_walk17 =[ $swim17, f_walk18 ] {ai_walk(8);}; +void() f_walk18 =[ $swim18, f_walk1 ] {ai_walk(8);}; + +void() f_run1 =[ $swim1, f_run2 ] {ai_run(12); + if (random() < 0.5) + sound (self, CHAN_VOICE, "fish/idle.wav", 1, ATTN_NORM); +}; +void() f_run2 =[ $swim3, f_run3 ] {ai_run(12);}; +void() f_run3 =[ $swim5, f_run4 ] {ai_run(12);}; +void() f_run4 =[ $swim7, f_run5 ] {ai_run(12);}; +void() f_run5 =[ $swim9, f_run6 ] {ai_run(12);}; +void() f_run6 =[ $swim11, f_run7 ] {ai_run(12);}; +void() f_run7 =[ $swim13, f_run8 ] {ai_run(12);}; +void() f_run8 =[ $swim15, f_run9 ] {ai_run(12);}; +void() f_run9 =[ $swim17, f_run1 ] {ai_run(12);}; + +void() fish_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + sound (self, CHAN_VOICE, "fish/bite.wav", 1, ATTN_NORM); + ldmg = (random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() f_attack1 =[ $attack1, f_attack2 ] {ai_charge(10);}; +void() f_attack2 =[ $attack2, f_attack3 ] {ai_charge(10);}; +void() f_attack3 =[ $attack3, f_attack4 ] {fish_melee();}; +void() f_attack4 =[ $attack4, f_attack5 ] {ai_charge(10);}; +void() f_attack5 =[ $attack5, f_attack6 ] {ai_charge(10);}; +void() f_attack6 =[ $attack6, f_attack7 ] {ai_charge(10);}; +void() f_attack7 =[ $attack7, f_attack8 ] {ai_charge(10);}; +void() f_attack8 =[ $attack8, f_attack9 ] {ai_charge(10);}; +void() f_attack9 =[ $attack9, f_attack10] {fish_melee();}; +void() f_attack10 =[ $attack10, f_attack11] {ai_charge(10);}; +void() f_attack11 =[ $attack11, f_attack12] {ai_charge(10);}; +void() f_attack12 =[ $attack12, f_attack13] {ai_charge(10);}; +void() f_attack13 =[ $attack13, f_attack14] {ai_charge(10);}; +void() f_attack14 =[ $attack14, f_attack15] {ai_charge(10);}; +void() f_attack15 =[ $attack15, f_attack16] {fish_melee();}; +void() f_attack16 =[ $attack16, f_attack17] {ai_charge(10);}; +void() f_attack17 =[ $attack17, f_attack18] {ai_charge(10);}; +void() f_attack18 =[ $attack18, f_run1 ] {ai_charge(10);}; + +void() f_death1 =[ $death1, f_death2 ] { +sound (self, CHAN_VOICE, "fish/death.wav", 1, ATTN_NORM); +}; +void() f_death2 =[ $death2, f_death3 ] {self.solid = SOLID_NOT;}; +void() f_death3 =[ $death3, f_death4 ] {}; +void() f_death4 =[ $death4, f_death5 ] {}; +void() f_death5 =[ $death5, f_death6 ] {}; +void() f_death6 =[ $death6, f_death7 ] {}; +void() f_death7 =[ $death7, f_death8 ] {}; +void() f_death8 =[ $death8, f_death9 ] {}; +void() f_death9 =[ $death9, f_death10 ] {}; +void() f_death10 =[ $death10, f_death11 ] {}; +void() f_death11 =[ $death11, f_death12 ] {}; +void() f_death12 =[ $death12, f_death13 ] {}; +void() f_death13 =[ $death13, f_death14 ] {}; +void() f_death14 =[ $death14, f_death15 ] {}; +void() f_death15 =[ $death15, f_death16 ] {}; +void() f_death16 =[ $death16, f_death17 ] {}; +void() f_death17 =[ $death17, f_death18 ] {}; +void() f_death18 =[ $death18, f_death19 ] {}; +void() f_death19 =[ $death19, f_death20 ] {}; +void() f_death20 =[ $death20, f_death21 ] {}; +void() f_death21 =[ $CORPSEFRAME_FISH_1, f_death21 ] {}; + +void() f_pain1 =[ $pain1, f_pain2 ] {}; +void() f_pain2 =[ $pain2, f_pain3 ] {ai_pain(6);}; +void() f_pain3 =[ $pain3, f_pain4 ] {ai_pain(6);}; +void() f_pain4 =[ $pain4, f_pain5 ] {ai_pain(6);}; +void() f_pain5 =[ $pain5, f_pain6 ] {ai_pain(6);}; +void() f_pain6 =[ $pain6, f_pain7 ] {ai_pain(6);}; +void() f_pain7 =[ $pain7, f_pain8 ] {ai_pain(6);}; +void() f_pain8 =[ $pain8, f_pain9 ] {ai_pain(6);}; +void() f_pain9 =[ $pain9, f_run1 ] {ai_pain(6);}; + +void(entity attacker, float damage) fish_pain = +{ + + // fish allways do pain frames + f_pain1 (); +}; + + + +//TODO: Fix fish just swimming up to the surface of the water and getting stuck there when player is above. +/*QUAKED monster_fish (1 0 0) (-16 -16 -24) (16 16 24) Ambush +*/ +void() monster_fish = +{ + precache_model2 ("progs/fish.mdl"); + + precache_sound2 ("fish/death.wav"); + precache_sound2 ("fish/bite.wav"); + precache_sound2 ("fish/idle.wav"); + + self.health = 25; + + self.th_stand = f_stand1; + self.th_walk = f_walk1; + self.th_run = f_run1; + self.th_die = f_death1; + self.th_pain = fish_pain; + self.th_melee = f_attack1; + self.combat_style = CS_MELEE; + + #ifdef MONSTERS_AWARE_OF_CONTENTS + //If someone placed fish in slime or lava, it was probably intentional. + self.spawnflags |= SPAWNFLAG_NO_CONTENTS_DAMAGE; + #endif + + InitMonster("progs/fish.mdl", MONSTER_TYPE_SWIM, MONSTER_SIZE_SMALL); +}; + diff --git a/quakec_mg1/monsters/hknight.qc b/quakec_mg1/monsters/hknight.qc new file mode 100644 index 0000000..5861bfa --- /dev/null +++ b/quakec_mg1/monsters/hknight.qc @@ -0,0 +1,465 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd id1/models/knight2 +$origin 0 0 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 walk15 walk16 walk17 +$frame walk18 walk19 walk20 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 CORPSEFRAME_HKNIGHT_1 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame CORPSEFRAME_HKNIGHT_2 + +$frame char_a1 char_a2 char_a3 char_a4 char_a5 char_a6 char_a7 char_a8 +$frame char_a9 char_a10 char_a11 char_a12 char_a13 char_a14 char_a15 char_a16 + +$frame magica1 magica2 magica3 magica4 magica5 magica6 magica7 magica8 +$frame magica9 magica10 magica11 magica12 magica13 magica14 + +$frame magicb1 magicb2 magicb3 magicb4 magicb5 magicb6 magicb7 magicb8 +$frame magicb9 magicb10 magicb11 magicb12 magicb13 + +$frame char_b1 char_b2 char_b3 char_b4 char_b5 char_b6 + +$frame slice1 slice2 slice3 slice4 slice5 slice6 slice7 slice8 slice9 slice10 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 smash8 smash9 smash10 +$frame smash11 + +$frame w_attack1 w_attack2 w_attack3 w_attack4 w_attack5 w_attack6 w_attack7 +$frame w_attack8 w_attack9 w_attack10 w_attack11 w_attack12 w_attack13 w_attack14 +$frame w_attack15 w_attack16 w_attack17 w_attack18 w_attack19 w_attack20 +$frame w_attack21 w_attack22 + +$frame magicc1 magicc2 magicc3 magicc4 magicc5 magicc6 magicc7 magicc8 +$frame magicc9 magicc10 magicc11 + + +void() hknight_char_a1; +void() hknight_run1; +void() hk_idle_sound; + +void(float offset) hknight_shot = +{ + local vector offang; + local vector org, vec; + + offang = vectoangles (self.enemy.origin - self.origin); + offang_y = offang_y + offset * 6; + + makevectors (offang); + + org = self.origin + self.mins + self.size*0.5 + v_forward * 20; + +// set missile speed + vec = normalize (v_forward); + vec_z = 0 - vec_z + (random() - 0.5)*0.1; + + launch_spike (org, vec); + newmis.classname = "knightspike"; + setmodel (newmis, "progs/k_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.effects = EF_CANDLELIGHT; + sound (self, CHAN_WEAPON, "hknight/attack1.wav", 1, ATTN_NORM); +}; + +void() CheckForCharge = +{ +// check for mad charge +if (!enemy_visible) + return; +if (time < self.attack_finished) + return; +if ( fabs(self.origin_z - self.enemy.origin_z) > 20) + return; // too much height change +if ( vlen (self.origin - self.enemy.origin) < 80) + return; // use regular attack + +// charge + SUB_AttackFinished (2); + hknight_char_a1 (); + +}; + +void() CheckContinueCharge = +{ + if (time > self.attack_finished) + { + SUB_AttackFinished (3); + hknight_run1 (); + return; // done charging + } + if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); + else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +}; + +//=========================================================================== + +void() hknight_stand1 =[ $stand1, hknight_stand2 ] {ai_stand();}; +void() hknight_stand2 =[ $stand2, hknight_stand3 ] {ai_stand();}; +void() hknight_stand3 =[ $stand3, hknight_stand4 ] {ai_stand();}; +void() hknight_stand4 =[ $stand4, hknight_stand5 ] {ai_stand();}; +void() hknight_stand5 =[ $stand5, hknight_stand6 ] {ai_stand();}; +void() hknight_stand6 =[ $stand6, hknight_stand7 ] {ai_stand();}; +void() hknight_stand7 =[ $stand7, hknight_stand8 ] {ai_stand();}; +void() hknight_stand8 =[ $stand8, hknight_stand9 ] {ai_stand();}; +void() hknight_stand9 =[ $stand9, hknight_stand1 ] {ai_stand();}; + +//=========================================================================== + +void() hknight_walk1 =[ $walk1, hknight_walk2 ] { +hk_idle_sound(); +ai_walk(2);}; +void() hknight_walk2 =[ $walk2, hknight_walk3 ] {ai_walk(5);}; +void() hknight_walk3 =[ $walk3, hknight_walk4 ] {ai_walk(5);}; +void() hknight_walk4 =[ $walk4, hknight_walk5 ] {ai_walk(4);}; +void() hknight_walk5 =[ $walk5, hknight_walk6 ] {ai_walk(4);}; +void() hknight_walk6 =[ $walk6, hknight_walk7 ] {ai_walk(2);}; +void() hknight_walk7 =[ $walk7, hknight_walk8 ] {ai_walk(2);}; +void() hknight_walk8 =[ $walk8, hknight_walk9 ] {ai_walk(3);}; +void() hknight_walk9 =[ $walk9, hknight_walk10 ] {ai_walk(3);}; +void() hknight_walk10 =[ $walk10, hknight_walk11 ] {ai_walk(4);}; +void() hknight_walk11 =[ $walk11, hknight_walk12 ] {ai_walk(3);}; +void() hknight_walk12 =[ $walk12, hknight_walk13 ] {ai_walk(4);}; +void() hknight_walk13 =[ $walk13, hknight_walk14 ] {ai_walk(6);}; +void() hknight_walk14 =[ $walk14, hknight_walk15 ] {ai_walk(2);}; +void() hknight_walk15 =[ $walk15, hknight_walk16 ] {ai_walk(2);}; +void() hknight_walk16 =[ $walk16, hknight_walk17 ] {ai_walk(4);}; +void() hknight_walk17 =[ $walk17, hknight_walk18 ] {ai_walk(3);}; +void() hknight_walk18 =[ $walk18, hknight_walk19 ] {ai_walk(3);}; +void() hknight_walk19 =[ $walk19, hknight_walk20 ] {ai_walk(3);}; +void() hknight_walk20 =[ $walk20, hknight_walk1 ] {ai_walk(2);}; + +//=========================================================================== + +void() hknight_run1 =[ $run1, hknight_run2 ] { +hk_idle_sound(); +ai_run (20); CheckForCharge (); }; +void() hknight_run2 =[ $run2, hknight_run3 ] {ai_run(25);}; +void() hknight_run3 =[ $run3, hknight_run4 ] {ai_run(18);}; +void() hknight_run4 =[ $run4, hknight_run5 ] {ai_run(16);}; +void() hknight_run5 =[ $run5, hknight_run6 ] {ai_run(14);}; +void() hknight_run6 =[ $run6, hknight_run7 ] {ai_run(25);}; +void() hknight_run7 =[ $run7, hknight_run8 ] {ai_run(21);}; +void() hknight_run8 =[ $run8, hknight_run1 ] {ai_run(13);}; + +//============================================================================ + +void() hknight_pain1 =[ $pain1, hknight_pain2 ] {sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM);}; +void() hknight_pain2 =[ $pain2, hknight_pain3 ] {}; +void() hknight_pain3 =[ $pain3, hknight_pain4 ] {}; +void() hknight_pain4 =[ $pain4, hknight_pain5 ] {}; +void() hknight_pain5 =[ $pain5, hknight_run1 ] {}; + +//============================================================================ + +void() hknight_die1 =[ $death1, hknight_die2 ] {ai_forward(10);}; +void() hknight_die2 =[ $death2, hknight_die3 ] {ai_forward(8);}; +void() hknight_die3 =[ $death3, hknight_die4 ] +{self.solid = SOLID_NOT; ai_forward(7);}; +void() hknight_die4 =[ $death4, hknight_die5 ] {}; +void() hknight_die5 =[ $death5, hknight_die6 ] {}; +void() hknight_die6 =[ $death6, hknight_die7 ] {}; +void() hknight_die7 =[ $death7, hknight_die8 ] {}; +void() hknight_die8 =[ $death8, hknight_die9 ] {ai_forward(10);}; +void() hknight_die9 =[ $death9, hknight_die10 ] {ai_forward(11);}; +void() hknight_die10 =[ $death10, hknight_die11 ] {}; +void() hknight_die11 =[ $death11, hknight_die12 ] {}; +void() hknight_die12 =[ $CORPSEFRAME_HKNIGHT_1, hknight_die12 ] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + +void() hknight_dieb1 =[ $deathb1, hknight_dieb2 ] {}; +void() hknight_dieb2 =[ $deathb2, hknight_dieb3 ] {}; +void() hknight_dieb3 =[ $deathb3, hknight_dieb4 ] +{self.solid = SOLID_NOT;}; +void() hknight_dieb4 =[ $deathb4, hknight_dieb5 ] {}; +void() hknight_dieb5 =[ $deathb5, hknight_dieb6 ] {}; +void() hknight_dieb6 =[ $deathb6, hknight_dieb7 ] {}; +void() hknight_dieb7 =[ $deathb7, hknight_dieb8 ] {}; +void() hknight_dieb8 =[ $deathb8, hknight_dieb9 ] {}; +void() hknight_dieb9 =[ $CORPSEFRAME_HKNIGHT_2, hknight_dieb9 ] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + +void() hknight_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_hellkn.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "hknight/death1.wav", 1, ATTN_NORM); + if (random() > 0.5) + hknight_die1 (); + else + hknight_dieb1 (); +}; + + +//============================================================================ + +void() hknight_magica1 =[ $magica1, hknight_magica2 ] {ai_face();}; +void() hknight_magica2 =[ $magica2, hknight_magica3 ] {ai_face();}; +void() hknight_magica3 =[ $magica3, hknight_magica4 ] {ai_face();}; +void() hknight_magica4 =[ $magica4, hknight_magica5 ] {ai_face();}; +void() hknight_magica5 =[ $magica5, hknight_magica6 ] {ai_face();}; +void() hknight_magica6 =[ $magica6, hknight_magica7 ] {ai_face();}; +void() hknight_magica7 =[ $magica7, hknight_magica8 ] {hknight_shot(-2);}; +void() hknight_magica8 =[ $magica8, hknight_magica9 ] {hknight_shot(-1);}; +void() hknight_magica9 =[ $magica9, hknight_magica10] {hknight_shot(0);}; +void() hknight_magica10 =[ $magica10, hknight_magica11] {hknight_shot(1);}; +void() hknight_magica11 =[ $magica11, hknight_magica12] {hknight_shot(2);}; +void() hknight_magica12 =[ $magica12, hknight_magica13] {hknight_shot(3);}; +void() hknight_magica13 =[ $magica13, hknight_magica14] {ai_face();}; +void() hknight_magica14 =[ $magica14, hknight_run1 ] {ai_face();}; + +//============================================================================ + +void() hknight_magicb1 =[ $magicb1, hknight_magicb2 ] {ai_face();}; +void() hknight_magicb2 =[ $magicb2, hknight_magicb3 ] {ai_face();}; +void() hknight_magicb3 =[ $magicb3, hknight_magicb4 ] {ai_face();}; +void() hknight_magicb4 =[ $magicb4, hknight_magicb5 ] {ai_face();}; +void() hknight_magicb5 =[ $magicb5, hknight_magicb6 ] {ai_face();}; +void() hknight_magicb6 =[ $magicb6, hknight_magicb7 ] {ai_face();}; +void() hknight_magicb7 =[ $magicb7, hknight_magicb8 ] {hknight_shot(-2);}; +void() hknight_magicb8 =[ $magicb8, hknight_magicb9 ] {hknight_shot(-1);}; +void() hknight_magicb9 =[ $magicb9, hknight_magicb10] {hknight_shot(0);}; +void() hknight_magicb10 =[ $magicb10, hknight_magicb11] {hknight_shot(1);}; +void() hknight_magicb11 =[ $magicb11, hknight_magicb12] {hknight_shot(2);}; +void() hknight_magicb12 =[ $magicb12, hknight_magicb13] {hknight_shot(3);}; +void() hknight_magicb13 =[ $magicb13, hknight_run1] {ai_face();}; + +//============================================================================ + +void() hknight_magicc1 =[ $magicc1, hknight_magicc2 ] {ai_face();}; +void() hknight_magicc2 =[ $magicc2, hknight_magicc3 ] {ai_face();}; +void() hknight_magicc3 =[ $magicc3, hknight_magicc4 ] {ai_face();}; +void() hknight_magicc4 =[ $magicc4, hknight_magicc5 ] {ai_face();}; +void() hknight_magicc5 =[ $magicc5, hknight_magicc6 ] {ai_face();}; +void() hknight_magicc6 =[ $magicc6, hknight_magicc7 ] {hknight_shot(-2);}; +void() hknight_magicc7 =[ $magicc7, hknight_magicc8 ] {hknight_shot(-1);}; +void() hknight_magicc8 =[ $magicc8, hknight_magicc9 ] {hknight_shot(0);}; +void() hknight_magicc9 =[ $magicc9, hknight_magicc10] {hknight_shot(1);}; +void() hknight_magicc10 =[ $magicc10, hknight_magicc11] {hknight_shot(2);}; +void() hknight_magicc11 =[ $magicc11, hknight_run1] {hknight_shot(3);}; + +//=========================================================================== + +void() hknight_char_a1 =[ $char_a1, hknight_char_a2 ] {ai_charge(20);}; +void() hknight_char_a2 =[ $char_a2, hknight_char_a3 ] {ai_charge(25);}; +void() hknight_char_a3 =[ $char_a3, hknight_char_a4 ] {ai_charge(18);}; +void() hknight_char_a4 =[ $char_a4, hknight_char_a5 ] {ai_charge(16);}; +void() hknight_char_a5 =[ $char_a5, hknight_char_a6 ] {ai_charge(14);}; +void() hknight_char_a6 =[ $char_a6, hknight_char_a7 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a7 =[ $char_a7, hknight_char_a8 ] {ai_charge(21); ai_melee();}; +void() hknight_char_a8 =[ $char_a8, hknight_char_a9 ] {ai_charge(13); ai_melee();}; +void() hknight_char_a9 =[ $char_a9, hknight_char_a10 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a10=[ $char_a10, hknight_char_a11 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a11=[ $char_a11, hknight_char_a12 ] {ai_charge(18); ai_melee();}; +void() hknight_char_a12=[ $char_a12, hknight_char_a13 ] {ai_charge(16);}; +void() hknight_char_a13=[ $char_a13, hknight_char_a14 ] {ai_charge(14);}; +void() hknight_char_a14=[ $char_a14, hknight_char_a15 ] {ai_charge(25);}; +void() hknight_char_a15=[ $char_a15, hknight_char_a16 ] {ai_charge(21);}; +void() hknight_char_a16=[ $char_a16, hknight_run1 ] {ai_charge(13);}; + +//=========================================================================== + +void() hknight_char_b1 =[ $char_b1, hknight_char_b2 ] +{CheckContinueCharge (); ai_charge(23); ai_melee();}; +void() hknight_char_b2 =[ $char_b2, hknight_char_b3 ] {ai_charge(17); ai_melee();}; +void() hknight_char_b3 =[ $char_b3, hknight_char_b4 ] {ai_charge(12); ai_melee();}; +void() hknight_char_b4 =[ $char_b4, hknight_char_b5 ] {ai_charge(22); ai_melee();}; +void() hknight_char_b5 =[ $char_b5, hknight_char_b6 ] {ai_charge(18); ai_melee();}; +void() hknight_char_b6 =[ $char_b6, hknight_char_b1 ] {ai_charge(8); ai_melee();}; + +//=========================================================================== + +void() hknight_slice1 =[ $slice1, hknight_slice2 ] {ai_charge(9);}; +void() hknight_slice2 =[ $slice2, hknight_slice3 ] {ai_charge(6);}; +void() hknight_slice3 =[ $slice3, hknight_slice4 ] {ai_charge(13);}; +void() hknight_slice4 =[ $slice4, hknight_slice5 ] {ai_charge(4);}; +void() hknight_slice5 =[ $slice5, hknight_slice6 ] {ai_charge(7); ai_melee();}; +void() hknight_slice6 =[ $slice6, hknight_slice7 ] {ai_charge(15); ai_melee();}; +void() hknight_slice7 =[ $slice7, hknight_slice8 ] {ai_charge(8); ai_melee();}; +void() hknight_slice8 =[ $slice8, hknight_slice9 ] {ai_charge(2); ai_melee();}; +void() hknight_slice9 =[ $slice9, hknight_slice10 ] {ai_melee();}; +void() hknight_slice10 =[ $slice10, hknight_run1 ] {ai_charge(3);}; + +//=========================================================================== + +void() hknight_smash1 =[ $smash1, hknight_smash2 ] {ai_charge(1);}; +void() hknight_smash2 =[ $smash2, hknight_smash3 ] {ai_charge(13);}; +void() hknight_smash3 =[ $smash3, hknight_smash4 ] {ai_charge(9);}; +void() hknight_smash4 =[ $smash4, hknight_smash5 ] {ai_charge(11);}; +void() hknight_smash5 =[ $smash5, hknight_smash6 ] {ai_charge(10); ai_melee();}; +void() hknight_smash6 =[ $smash6, hknight_smash7 ] {ai_charge(7); ai_melee();}; +void() hknight_smash7 =[ $smash7, hknight_smash8 ] {ai_charge(12); ai_melee();}; +void() hknight_smash8 =[ $smash8, hknight_smash9 ] {ai_charge(2); ai_melee();}; +void() hknight_smash9 =[ $smash9, hknight_smash10 ] {ai_charge(3); ai_melee();}; +void() hknight_smash10 =[ $smash10, hknight_smash11 ] {ai_charge(0);}; +void() hknight_smash11 =[ $smash11, hknight_run1 ] {ai_charge(0);}; + +//============================================================================ + +void() hknight_watk1 =[ $w_attack1, hknight_watk2 ] {ai_charge(2);}; +void() hknight_watk2 =[ $w_attack2, hknight_watk3 ] {ai_charge(0);}; +void() hknight_watk3 =[ $w_attack3, hknight_watk4 ] {ai_charge(0);}; +void() hknight_watk4 =[ $w_attack4, hknight_watk5 ] {ai_melee();}; +void() hknight_watk5 =[ $w_attack5, hknight_watk6 ] {ai_melee();}; +void() hknight_watk6 =[ $w_attack6, hknight_watk7 ] {ai_melee();}; +void() hknight_watk7 =[ $w_attack7, hknight_watk8 ] {ai_charge(1);}; +void() hknight_watk8 =[ $w_attack8, hknight_watk9 ] {ai_charge(4);}; +void() hknight_watk9 =[ $w_attack9, hknight_watk10 ] {ai_charge(5);}; +void() hknight_watk10 =[ $w_attack10, hknight_watk11 ] {ai_charge(3); ai_melee();}; +void() hknight_watk11 =[ $w_attack11, hknight_watk12 ] {ai_charge(2); ai_melee();}; +void() hknight_watk12 =[ $w_attack12, hknight_watk13 ] {ai_charge(2); ai_melee();}; +void() hknight_watk13 =[ $w_attack13, hknight_watk14 ] {ai_charge(0);}; +void() hknight_watk14 =[ $w_attack14, hknight_watk15 ] {ai_charge(0);}; +void() hknight_watk15 =[ $w_attack15, hknight_watk16 ] {ai_charge(0);}; +void() hknight_watk16 =[ $w_attack16, hknight_watk17 ] {ai_charge(1);}; +void() hknight_watk17 =[ $w_attack17, hknight_watk18 ] {ai_charge(1); ai_melee();}; +void() hknight_watk18 =[ $w_attack18, hknight_watk19 ] {ai_charge(3); ai_melee();}; +void() hknight_watk19 =[ $w_attack19, hknight_watk20 ] {ai_charge(4); ai_melee();}; +void() hknight_watk20 =[ $w_attack20, hknight_watk21 ] {ai_charge(6);}; +void() hknight_watk21 =[ $w_attack21, hknight_watk22 ] {ai_charge(7);}; +void() hknight_watk22 =[ $w_attack22, hknight_run1 ] {ai_charge(3);}; + +//============================================================================ + +void() hk_idle_sound = +{ + if (random() < 0.2) + sound (self, CHAN_VOICE, "hknight/idle.wav", 1, ATTN_NORM); +}; + +void(entity attacker, float damage) hknight_pain = +{ + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM); + + if (time - self.pain_finished > 5) + { // allways go into pain frame if it has been a while + hknight_pain1 (); + self.pain_finished = time + 1; + return; + } + + if ((random()*30 > damage) ) + return; // didn't flinch + + self.pain_finished = time + 1; + hknight_pain1 (); +}; + +float hknight_type; + +void() hknight_melee = +{ + hknight_type = hknight_type + 1; + + sound (self, CHAN_WEAPON, "hknight/slash1.wav", 1, ATTN_NORM); + if (hknight_type == 1) + hknight_slice1 (); + else if (hknight_type == 2) + hknight_smash1 (); + else if (hknight_type == 3) + { + hknight_watk1 (); + hknight_type = 0; + } +}; + +/*QUAKED monster_hell_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_hell_knight = +{ + precache_model2 ("progs/hknight.mdl"); + precache_model2 ("progs/k_spike.mdl"); + precache_model2 ("progs/h_hellkn.mdl"); + + + precache_sound2 ("hknight/attack1.wav"); + precache_sound2 ("hknight/death1.wav"); + precache_sound2 ("hknight/pain1.wav"); + precache_sound2 ("hknight/sight1.wav"); + precache_sound ("hknight/hit.wav"); // used by C code, so don't sound2 + precache_sound2 ("hknight/slash1.wav"); + precache_sound2 ("hknight/idle.wav"); + precache_sound2 ("hknight/grunt.wav"); + + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + + self.health = 250; + + self.th_stand = hknight_stand1; + self.th_walk = hknight_walk1; + self.th_run = hknight_run1; + self.th_melee = hknight_melee; + self.th_missile = hknight_magicc1; + self.th_pain = hknight_pain; + self.th_die = hknight_die; + self.allowPathFind = TRUE; + self.combat_style = CS_MIXED; + + InitMonster("progs/hknight.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_SMALL); +}; diff --git a/quakec_mg1/monsters/knight.qc b/quakec_mg1/monsters/knight.qc new file mode 100644 index 0000000..d758234 --- /dev/null +++ b/quakec_mg1/monsters/knight.qc @@ -0,0 +1,294 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd id1/models/knight +$origin 0 0 24 +$base base +$skin badass3 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame runb1 runb2 runb3 runb4 runb5 runb6 runb7 runb8 + +//frame runc1 runc2 runc3 runc4 runc5 runc6 + +$frame runattack1 runattack2 runattack3 runattack4 runattack5 +$frame runattack6 runattack7 runattack8 runattack9 runattack10 +$frame runattack11 + +$frame pain1 pain2 pain3 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 +$frame painb10 painb11 + +//frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 +//frame attack8 attack9 attack10 attack11 + +$frame attackb1 attackb2 attackb3 attackb4 attackb5 +$frame attackb6 attackb7 attackb8 attackb9 attackb10 attackb11 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 + +$frame kneel1 kneel2 kneel3 kneel4 kneel5 + +$frame standing2 standing3 standing4 standing5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 CORPSEFRAME_KNIGHT_1 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 deathb10 CORPSEFRAME_KNIGHT_2 + +void() knight_stand1 =[ $stand1, knight_stand2 ] {ai_stand();}; +void() knight_stand2 =[ $stand2, knight_stand3 ] {ai_stand();}; +void() knight_stand3 =[ $stand3, knight_stand4 ] {ai_stand();}; +void() knight_stand4 =[ $stand4, knight_stand5 ] {ai_stand();}; +void() knight_stand5 =[ $stand5, knight_stand6 ] {ai_stand();}; +void() knight_stand6 =[ $stand6, knight_stand7 ] {ai_stand();}; +void() knight_stand7 =[ $stand7, knight_stand8 ] {ai_stand();}; +void() knight_stand8 =[ $stand8, knight_stand9 ] {ai_stand();}; +void() knight_stand9 =[ $stand9, knight_stand1 ] {ai_stand();}; + +void() knight_walk1 =[ $walk1, knight_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "knight/idle.wav", 1, ATTN_IDLE); +ai_walk(3);}; +void() knight_walk2 =[ $walk2, knight_walk3 ] {ai_walk(2);}; +void() knight_walk3 =[ $walk3, knight_walk4 ] {ai_walk(3);}; +void() knight_walk4 =[ $walk4, knight_walk5 ] {ai_walk(4);}; +void() knight_walk5 =[ $walk5, knight_walk6 ] {ai_walk(3);}; +void() knight_walk6 =[ $walk6, knight_walk7 ] {ai_walk(3);}; +void() knight_walk7 =[ $walk7, knight_walk8 ] {ai_walk(3);}; +void() knight_walk8 =[ $walk8, knight_walk9 ] {ai_walk(4);}; +void() knight_walk9 =[ $walk9, knight_walk10 ] {ai_walk(3);}; +void() knight_walk10 =[ $walk10, knight_walk11 ] {ai_walk(3);}; +void() knight_walk11 =[ $walk11, knight_walk12 ] {ai_walk(2);}; +void() knight_walk12 =[ $walk12, knight_walk13 ] {ai_walk(3);}; +void() knight_walk13 =[ $walk13, knight_walk14 ] {ai_walk(4);}; +void() knight_walk14 =[ $walk14, knight_walk1 ] {ai_walk(3);}; + + +void() knight_run1 =[ $runb1, knight_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "knight/idle.wav", 1, ATTN_IDLE); +ai_run(16);}; +void() knight_run2 =[ $runb2, knight_run3 ] {ai_run(20);}; +void() knight_run3 =[ $runb3, knight_run4 ] {ai_run(13);}; +void() knight_run4 =[ $runb4, knight_run5 ] {ai_run(7);}; +void() knight_run5 =[ $runb5, knight_run6 ] {ai_run(16);}; +void() knight_run6 =[ $runb6, knight_run7 ] {ai_run(20);}; +void() knight_run7 =[ $runb7, knight_run8 ] {ai_run(14);}; +void() knight_run8 =[ $runb8, knight_run1 ] {ai_run(6);}; + + +void() knight_runatk1 =[ $runattack1, knight_runatk2 ] +{ +if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); +else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +ai_charge(20); +}; +void() knight_runatk2 =[ $runattack2, knight_runatk3 ] {ai_charge_side();}; +void() knight_runatk3 =[ $runattack3, knight_runatk4 ] {ai_charge_side();}; +void() knight_runatk4 =[ $runattack4, knight_runatk5 ] {ai_charge_side();}; +void() knight_runatk5 =[ $runattack5, knight_runatk6 ] {ai_melee_side();}; +void() knight_runatk6 =[ $runattack6, knight_runatk7 ] {ai_melee_side();}; +void() knight_runatk7 =[ $runattack7, knight_runatk8 ] {ai_melee_side();}; +void() knight_runatk8 =[ $runattack8, knight_runatk9 ] {ai_melee_side();}; +void() knight_runatk9 =[ $runattack9, knight_runatk10 ] {ai_melee_side();}; +void() knight_runatk10 =[ $runattack10, knight_runatk11 ] {ai_charge_side();}; +void() knight_runatk11 =[ $runattack11, knight_run1 ] {ai_charge(10);}; + +void() knight_atk1 =[ $attackb1, knight_atk2 ] +{ +sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +ai_charge(0);}; +void() knight_atk2 =[ $attackb2, knight_atk3 ] {ai_charge(7);}; +void() knight_atk3 =[ $attackb3, knight_atk4 ] {ai_charge(4);}; +void() knight_atk4 =[ $attackb4, knight_atk5 ] {ai_charge(0);}; +void() knight_atk5 =[ $attackb5, knight_atk6 ] {ai_charge(3);}; +void() knight_atk6 =[ $attackb6, knight_atk7 ] {ai_charge(4); ai_melee();}; +void() knight_atk7 =[ $attackb7, knight_atk8 ] {ai_charge(1); ai_melee();}; +void() knight_atk8 =[ $attackb8, knight_atk9 ] {ai_charge(3); +ai_melee();}; +void() knight_atk9 =[ $attackb9, knight_atk10] {ai_charge(1);}; +void() knight_atk10=[ $attackb10, knight_run1 ] {ai_charge(5);}; + +//void() knight_atk9 =[ $attack9, knight_atk10 ] {}; +//void() knight_atk10 =[ $attack10, knight_atk11 ] {}; +//void() knight_atk11 =[ $attack11, knight_run1 ] {}; + +//=========================================================================== + +void() knight_pain1 =[ $pain1, knight_pain2 ] {}; +void() knight_pain2 =[ $pain2, knight_pain3 ] {}; +void() knight_pain3 =[ $pain3, knight_run1 ] {}; + +void() knight_painb1 =[ $painb1, knight_painb2 ] {ai_painforward(0);}; +void() knight_painb2 =[ $painb2, knight_painb3 ] {ai_painforward(3);}; +void() knight_painb3 =[ $painb3, knight_painb4 ] {}; +void() knight_painb4 =[ $painb4, knight_painb5 ] {}; +void() knight_painb5 =[ $painb5, knight_painb6 ] {ai_painforward(2);}; +void() knight_painb6 =[ $painb6, knight_painb7 ] {ai_painforward(4);}; +void() knight_painb7 =[ $painb7, knight_painb8 ] {ai_painforward(2);}; +void() knight_painb8 =[ $painb8, knight_painb9 ] {ai_painforward(5);}; +void() knight_painb9 =[ $painb9, knight_painb10 ] {ai_painforward(5);}; +void() knight_painb10 =[ $painb10, knight_painb11 ] {ai_painforward(0);}; +void() knight_painb11 =[ $painb11, knight_run1 ] {}; + +void(entity attacker, float damage) knight_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + sound (self, CHAN_VOICE, "knight/khurt.wav", 1, ATTN_NORM); + if (r < 0.85) + { + knight_pain1 (); + self.pain_finished = time + 1; + } + else + { + knight_painb1 (); + self.pain_finished = time + 1; + } + +}; + +//=========================================================================== + +void() knight_bow1 =[ $kneel1, knight_bow2 ] {ai_turn();}; +void() knight_bow2 =[ $kneel2, knight_bow3 ] {ai_turn();}; +void() knight_bow3 =[ $kneel3, knight_bow4 ] {ai_turn();}; +void() knight_bow4 =[ $kneel4, knight_bow5 ] {ai_turn();}; + +void() knight_bow5 =[ $kneel5, knight_bow5 ] {ai_turn();}; + +void() knight_bow6 =[ $kneel4, knight_bow7 ] {ai_turn();}; +void() knight_bow7 =[ $kneel3, knight_bow8 ] {ai_turn();}; +void() knight_bow8 =[ $kneel2, knight_bow9 ] {ai_turn();}; +void() knight_bow9 =[ $kneel1, knight_bow10 ] {ai_turn();}; +void() knight_bow10 =[ $walk1, knight_walk1 ] {ai_turn();}; + + + +void() knight_die1 =[ $death1, knight_die2 ] {}; +void() knight_die2 =[ $death2, knight_die3 ] {}; +void() knight_die3 =[ $death3, knight_die4 ] +{self.solid = SOLID_NOT;}; +void() knight_die4 =[ $death4, knight_die5 ] {}; +void() knight_die5 =[ $death5, knight_die6 ] {}; +void() knight_die6 =[ $death6, knight_die7 ] {}; +void() knight_die7 =[ $death7, knight_die8 ] {}; +void() knight_die8 =[ $death8, knight_die9 ] {}; +void() knight_die9 =[ $death9, knight_die10] {}; +void() knight_die10=[ $CORPSEFRAME_KNIGHT_1, knight_die10] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + + +void() knight_dieb1 =[ $deathb1, knight_dieb2 ] {}; +void() knight_dieb2 =[ $deathb2, knight_dieb3 ] {}; +void() knight_dieb3 =[ $deathb3, knight_dieb4 ] +{self.solid = SOLID_NOT;}; +void() knight_dieb4 =[ $deathb4, knight_dieb5 ] {}; +void() knight_dieb5 =[ $deathb5, knight_dieb6 ] {}; +void() knight_dieb6 =[ $deathb6, knight_dieb7 ] {}; +void() knight_dieb7 =[ $deathb7, knight_dieb8 ] {}; +void() knight_dieb8 =[ $deathb8, knight_dieb9 ] {}; +void() knight_dieb9 =[ $deathb9, knight_dieb10] {}; +void() knight_dieb10 = [ $deathb10, knight_dieb11] {}; +void() knight_dieb11 = [ $CORPSEFRAME_KNIGHT_2, knight_dieb11] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + + +void() knight_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_knight.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "knight/kdeath.wav", 1, ATTN_NORM); + if (random() < 0.5) + knight_die1 (); + else + knight_dieb1 (); +}; + + +/*QUAKED monster_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_knight = +{ + precache_model ("progs/knight.mdl"); + precache_model ("progs/h_knight.mdl"); + + precache_sound ("knight/kdeath.wav"); + precache_sound ("knight/khurt.wav"); + precache_sound ("knight/ksight.wav"); + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + precache_sound ("knight/idle.wav"); + + self.health = 75; + + self.th_stand = knight_stand1; + self.th_walk = knight_walk1; + self.th_run = knight_run1; + self.th_melee = knight_atk1; + self.th_pain = knight_pain; + self.th_die = knight_die; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + InitMonster("progs/knight.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_SMALL); +}; diff --git a/quakec_mg1/monsters/ogre.qc b/quakec_mg1/monsters/ogre.qc new file mode 100644 index 0000000..4b09dc0 --- /dev/null +++ b/quakec_mg1/monsters/ogre.qc @@ -0,0 +1,430 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OGRE + +============================================================================== +*/ + +$cd id1/models/ogre_c +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame swing1 swing2 swing3 swing4 swing5 swing6 swing7 +$frame swing8 swing9 swing10 swing11 swing12 swing13 swing14 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 smash13 smash14 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame painb1 painb2 painb3 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 paind14 paind15 paind16 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 death11 death12 +$frame death13 CORPSEFRAME_OGRE_1 + +$frame bdeath1 bdeath2 bdeath3 bdeath4 bdeath5 bdeath6 +$frame bdeath7 bdeath8 bdeath9 CORPSEFRAME_OGRE_2 + +$frame pull1 pull2 pull3 pull4 pull5 pull6 pull7 pull8 pull9 pull10 pull11 + +//============================================================================= + +/* +================ +OgreFireGrenade +================ +*/ +void() OgreFireGrenade = +{ + self.effects = self.effects | EF_MUZZLEFLASH; + float damage = 40; + vector velocity; + makevectors (self.angles); + + velocity = normalize(self.enemy.origin - self.origin); + velocity = velocity * 600; + velocity_z = 200; + + W_FireGrenade(damage, velocity); +}; + + +//============================================================================= + +/* +================ +chainsaw +================ +*/ +void(float side) chainsaw = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 4; + T_Damage (self.enemy, self, self, ldmg); + + if (side) + { + makevectors (self.angles); + if (side == 1) + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + else + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + + +void() ogre_stand1 =[ $stand1, ogre_stand2 ] {ai_stand();}; +void() ogre_stand2 =[ $stand2, ogre_stand3 ] {ai_stand();}; +void() ogre_stand3 =[ $stand3, ogre_stand4 ] {ai_stand();}; +void() ogre_stand4 =[ $stand4, ogre_stand5 ] {ai_stand();}; +void() ogre_stand5 =[ $stand5, ogre_stand6 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); +ai_stand(); +}; +void() ogre_stand6 =[ $stand6, ogre_stand7 ] {ai_stand();}; +void() ogre_stand7 =[ $stand7, ogre_stand8 ] {ai_stand();}; +void() ogre_stand8 =[ $stand8, ogre_stand9 ] {ai_stand();}; +void() ogre_stand9 =[ $stand9, ogre_stand1 ] {ai_stand();}; + +void() ogre_walk1 =[ $walk1, ogre_walk2 ] {ai_walk(3);}; +void() ogre_walk2 =[ $walk2, ogre_walk3 ] {ai_walk(2);}; +void() ogre_walk3 =[ $walk3, ogre_walk4 ] { +ai_walk(2); +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); +}; +void() ogre_walk4 =[ $walk4, ogre_walk5 ] {ai_walk(2);}; +void() ogre_walk5 =[ $walk5, ogre_walk6 ] {ai_walk(2);}; +void() ogre_walk6 =[ $walk6, ogre_walk7 ] { +ai_walk(5); +if (random() < 0.1) + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE); +}; +void() ogre_walk7 =[ $walk7, ogre_walk8 ] {ai_walk(3);}; +void() ogre_walk8 =[ $walk8, ogre_walk9 ] {ai_walk(2);}; +void() ogre_walk9 =[ $walk9, ogre_walk10 ] {ai_walk(3);}; +void() ogre_walk10 =[ $walk10, ogre_walk11 ] {ai_walk(1);}; +void() ogre_walk11 =[ $walk11, ogre_walk12 ] {ai_walk(2);}; +void() ogre_walk12 =[ $walk12, ogre_walk13 ] {ai_walk(3);}; +void() ogre_walk13 =[ $walk13, ogre_walk14 ] {ai_walk(3);}; +void() ogre_walk14 =[ $walk14, ogre_walk15 ] {ai_walk(3);}; +void() ogre_walk15 =[ $walk15, ogre_walk16 ] {ai_walk(3);}; +void() ogre_walk16 =[ $walk16, ogre_walk1 ] {ai_walk(4);}; + +void() ogre_run1 =[ $run1, ogre_run2 ] {ai_run(9); +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle2.wav", 1, ATTN_IDLE); +}; +void() ogre_run2 =[ $run2, ogre_run3 ] {ai_run(12);}; +void() ogre_run3 =[ $run3, ogre_run4 ] {ai_run(8);}; +void() ogre_run4 =[ $run4, ogre_run5 ] {ai_run(22);}; +void() ogre_run5 =[ $run5, ogre_run6 ] {ai_run(16);}; +void() ogre_run6 =[ $run6, ogre_run7 ] {ai_run(4);}; +void() ogre_run7 =[ $run7, ogre_run8 ] {ai_run(13);}; +void() ogre_run8 =[ $run8, ogre_run1 ] {ai_run(24);}; + +void() ogre_swing1 =[ $swing1, ogre_swing2 ] {ai_charge(11); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM); +}; +void() ogre_swing2 =[ $swing2, ogre_swing3 ] {ai_charge(1);}; +void() ogre_swing3 =[ $swing3, ogre_swing4 ] {ai_charge(4);}; +void() ogre_swing4 =[ $swing4, ogre_swing5 ] {ai_charge(13);}; +void() ogre_swing5 =[ $swing5, ogre_swing6 ] {ai_charge(9); chainsaw(0);self.angles_y = self.angles_y + random()*25;}; +void() ogre_swing6 =[ $swing6, ogre_swing7 ] {chainsaw(200);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing7 =[ $swing7, ogre_swing8 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing8 =[ $swing8, ogre_swing9 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing9 =[ $swing9, ogre_swing10 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing10 =[ $swing10, ogre_swing11 ] {chainsaw(-200);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing11 =[ $swing11, ogre_swing12 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing12 =[ $swing12, ogre_swing13 ] {ai_charge(3);}; +void() ogre_swing13 =[ $swing13, ogre_swing14 ] {ai_charge(8);}; +void() ogre_swing14 =[ $swing14, ogre_run1 ] {ai_charge(9);}; + +void() ogre_smash1 =[ $smash1, ogre_smash2 ] {ai_charge(6); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM); +}; +void() ogre_smash2 =[ $smash2, ogre_smash3 ] {ai_charge(0);}; +void() ogre_smash3 =[ $smash3, ogre_smash4 ] {ai_charge(0);}; +void() ogre_smash4 =[ $smash4, ogre_smash5 ] {ai_charge(1);}; +void() ogre_smash5 =[ $smash5, ogre_smash6 ] {ai_charge(4);}; +void() ogre_smash6 =[ $smash6, ogre_smash7 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash7 =[ $smash7, ogre_smash8 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash8 =[ $smash8, ogre_smash9 ] {ai_charge(10); chainsaw(0);}; +void() ogre_smash9 =[ $smash9, ogre_smash10 ] {ai_charge(13); chainsaw(0);}; +void() ogre_smash10 =[ $smash10, ogre_smash11 ] {chainsaw(1);}; +void() ogre_smash11 =[ $smash11, ogre_smash12 ] {ai_charge(2); chainsaw(0); +self.nextthink = self.nextthink + random()*0.2;}; // slight variation +void() ogre_smash12 =[ $smash12, ogre_smash13 ] {ai_charge(0);}; +void() ogre_smash13 =[ $smash13, ogre_smash14 ] {ai_charge(4);}; +void() ogre_smash14 =[ $smash14, ogre_run1 ] {ai_charge(12);}; + +void() ogre_nail1 =[ $shoot1, ogre_nail2 ] {ai_face();}; +void() ogre_nail2 =[ $shoot2, ogre_nail3 ] {ai_face();}; +void() ogre_nail3 =[ $shoot2, ogre_nail4 ] {ai_face();}; +void() ogre_nail4 =[ $shoot3, ogre_nail5 ] {ai_face();OgreFireGrenade();}; +void() ogre_nail5 =[ $shoot4, ogre_nail6 ] {ai_face();}; +void() ogre_nail6 =[ $shoot5, ogre_nail7 ] {ai_face();}; +void() ogre_nail7 =[ $shoot6, ogre_run1 ] {ai_face();}; + +void() ogre_pain1 =[ $pain1, ogre_pain2 ] {}; +void() ogre_pain2 =[ $pain2, ogre_pain3 ] {}; +void() ogre_pain3 =[ $pain3, ogre_pain4 ] {}; +void() ogre_pain4 =[ $pain4, ogre_pain5 ] {}; +void() ogre_pain5 =[ $pain5, ogre_run1 ] {}; + + +void() ogre_painb1 =[ $painb1, ogre_painb2 ] {}; +void() ogre_painb2 =[ $painb2, ogre_painb3 ] {}; +void() ogre_painb3 =[ $painb3, ogre_run1 ] {}; + + +void() ogre_painc1 =[ $painc1, ogre_painc2 ] {}; +void() ogre_painc2 =[ $painc2, ogre_painc3 ] {}; +void() ogre_painc3 =[ $painc3, ogre_painc4 ] {}; +void() ogre_painc4 =[ $painc4, ogre_painc5 ] {}; +void() ogre_painc5 =[ $painc5, ogre_painc6 ] {}; +void() ogre_painc6 =[ $painc6, ogre_run1 ] {}; + + +void() ogre_paind1 =[ $paind1, ogre_paind2 ] {}; +void() ogre_paind2 =[ $paind2, ogre_paind3 ] {ai_pain(10);}; +void() ogre_paind3 =[ $paind3, ogre_paind4 ] {ai_pain(9);}; +void() ogre_paind4 =[ $paind4, ogre_paind5 ] {ai_pain(4);}; +void() ogre_paind5 =[ $paind5, ogre_paind6 ] {}; +void() ogre_paind6 =[ $paind6, ogre_paind7 ] {}; +void() ogre_paind7 =[ $paind7, ogre_paind8 ] {}; +void() ogre_paind8 =[ $paind8, ogre_paind9 ] {}; +void() ogre_paind9 =[ $paind9, ogre_paind10 ] {}; +void() ogre_paind10=[ $paind10, ogre_paind11 ] {}; +void() ogre_paind11=[ $paind11, ogre_paind12 ] {}; +void() ogre_paind12=[ $paind12, ogre_paind13 ] {}; +void() ogre_paind13=[ $paind13, ogre_paind14 ] {}; +void() ogre_paind14=[ $paind14, ogre_paind15 ] {}; +void() ogre_paind15=[ $paind15, ogre_paind16 ] {}; +void() ogre_paind16=[ $paind16, ogre_run1 ] {}; + +void() ogre_paine1 =[ $paine1, ogre_paine2 ] {}; +void() ogre_paine2 =[ $paine2, ogre_paine3 ] {ai_pain(10);}; +void() ogre_paine3 =[ $paine3, ogre_paine4 ] {ai_pain(9);}; +void() ogre_paine4 =[ $paine4, ogre_paine5 ] {ai_pain(4);}; +void() ogre_paine5 =[ $paine5, ogre_paine6 ] {}; +void() ogre_paine6 =[ $paine6, ogre_paine7 ] {}; +void() ogre_paine7 =[ $paine7, ogre_paine8 ] {}; +void() ogre_paine8 =[ $paine8, ogre_paine9 ] {}; +void() ogre_paine9 =[ $paine9, ogre_paine10 ] {}; +void() ogre_paine10=[ $paine10, ogre_paine11 ] {}; +void() ogre_paine11=[ $paine11, ogre_paine12 ] {}; +void() ogre_paine12=[ $paine12, ogre_paine13 ] {}; +void() ogre_paine13=[ $paine13, ogre_paine14 ] {}; +void() ogre_paine14=[ $paine14, ogre_paine15 ] {}; +void() ogre_paine15=[ $paine15, ogre_run1 ] {}; + + +void(entity attacker, float damage) ogre_pain = +{ + local float r; + +// don't make multiple pain sounds right after each other + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "ogre/ogpain1.wav", 1, ATTN_NORM); + + r = random(); + + if (r < 0.25) + { + ogre_pain1 (); + self.pain_finished = time + 1; + } + else if (r < 0.5) + { + ogre_painb1 (); + self.pain_finished = time + 1; + } + else if (r < 0.75) + { + ogre_painc1 (); + self.pain_finished = time + 1; + } + else if (r < 0.88) + { + ogre_paind1 (); + self.pain_finished = time + 2; + } + else + { + ogre_paine1 (); + self.pain_finished = time + 2; + } +}; + +void() ogre_die1 =[ $death1, ogre_die2 ] {}; +void() ogre_die2 =[ $death2, ogre_die3 ] {}; +void() ogre_die3 =[ $death3, ogre_die4 ] +{self.solid = SOLID_NOT; +self.ammo_rockets = 2;DropBackpack();}; +void() ogre_die4 =[ $death4, ogre_die5 ] {}; +void() ogre_die5 =[ $death5, ogre_die6 ] {}; +void() ogre_die6 =[ $death6, ogre_die7 ] {}; +void() ogre_die7 =[ $death7, ogre_die8 ] {}; +void() ogre_die8 =[ $death8, ogre_die9 ] {}; +void() ogre_die9 =[ $death9, ogre_die10 ] {}; +void() ogre_die10 =[ $death10, ogre_die11 ] {}; +void() ogre_die11 =[ $death11, ogre_die12 ] {}; +void() ogre_die12 =[ $death12, ogre_die13 ] {}; +void() ogre_die13 =[ $death13, ogre_die14 ] {}; +void() ogre_die14 =[ $CORPSEFRAME_OGRE_1, ogre_die14 ] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + +void() ogre_bdie1 =[ $bdeath1, ogre_bdie2 ] {}; +void() ogre_bdie2 =[ $bdeath2, ogre_bdie3 ] {ai_forward(5);}; +void() ogre_bdie3 =[ $bdeath3, ogre_bdie4 ] +{self.solid = SOLID_NOT; +self.ammo_rockets = 2;DropBackpack();}; +void() ogre_bdie4 =[ $bdeath4, ogre_bdie5 ] {ai_forward(1);}; +void() ogre_bdie5 =[ $bdeath5, ogre_bdie6 ] {ai_forward(3);}; +void() ogre_bdie6 =[ $bdeath6, ogre_bdie7 ] {ai_forward(7);}; +void() ogre_bdie7 =[ $bdeath7, ogre_bdie8 ] {ai_forward(25);}; +void() ogre_bdie8 =[ $bdeath8, ogre_bdie9 ] {}; +void() ogre_bdie9 =[ $bdeath9, ogre_bdie10 ] {}; +void() ogre_bdie10 =[ $CORPSEFRAME_OGRE_2, ogre_bdie10 ] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + +void() ogre_die = +{ +// check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_ogre.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "ogre/ogdth.wav", 1, ATTN_NORM); + + if (random() < 0.5) + ogre_die1 (); + else + ogre_bdie1 (); +}; + +void() ogre_melee = +{ + if (random() > 0.5) + ogre_smash1 (); + else + ogre_swing1 (); +}; + + +/*QUAKED monster_ogre (1 0 0) (-32 -32 -24) (32 32 64) Ambush + +*/ +void() monster_ogre = +{ + precache_model ("progs/ogre.mdl"); + precache_model ("progs/h_ogre.mdl"); + precache_model ("progs/grenade.mdl"); + + precache_sound ("ogre/ogdrag.wav"); + precache_sound ("ogre/ogdth.wav"); + precache_sound ("ogre/ogidle.wav"); + precache_sound ("ogre/ogidle2.wav"); + precache_sound ("ogre/ogpain1.wav"); + precache_sound ("ogre/ogsawatk.wav"); + precache_sound ("ogre/ogwake.wav"); + + self.model = "progs/ogre.mdl"; + + + self.health = 200; + + self.th_stand = ogre_stand1; + self.th_walk = ogre_walk1; + self.th_run = ogre_run1; + self.th_die = ogre_die; + self.th_melee = ogre_melee; + self.th_missile = ogre_nail1; + self.th_pain = ogre_pain; + self.allowPathFind = TRUE; + self.combat_style = CS_MIXED; + + InitMonster("progs/ogre.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_LARGE); +}; + +void() monster_ogre_marksman = +{ + monster_ogre (); +}; + + diff --git a/quakec_mg1/monsters/oldone.qc b/quakec_mg1/monsters/oldone.qc new file mode 100644 index 0000000..c29a09f --- /dev/null +++ b/quakec_mg1/monsters/oldone.qc @@ -0,0 +1,302 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OLD ONE + +============================================================================== +*/ +$cd id1/models/old_one +$origin 0 0 24 +$base base +$skin skin +$scale 1 + +void() finale_1; +void() finale_2; +void() finale_3; +void() finale_4; + + +entity shub; + +$frame old1 old2 old3 old4 old5 old6 old7 old8 old9 +$frame old10 old11 old12 old13 old14 old15 old16 old17 old18 old19 +$frame old20 old21 old22 old23 old24 old25 old26 old27 old28 old29 +$frame old30 old31 old32 old33 old34 old35 old36 old37 old38 old39 +$frame old40 old41 old42 old43 old44 old45 old46 + +$frame shake1 shake2 shake3 shake4 shake5 shake6 shake7 shake8 +$frame shake9 shake10 shake11 shake12 shake13 shake14 +$frame shake15 shake16 shake17 shake18 shake19 shake20 + +//void() old_stand =[ $old1, old_stand ] {}; + +void() old_idle1 =[ $old1, old_idle2 ] {}; +void() old_idle2 =[ $old2, old_idle3 ] {}; +void() old_idle3 =[ $old3, old_idle4 ] {}; +void() old_idle4 =[ $old4, old_idle5 ] {}; +void() old_idle5 =[ $old5, old_idle6 ] {}; +void() old_idle6 =[ $old6, old_idle7 ] {}; +void() old_idle7 =[ $old7, old_idle8 ] {}; +void() old_idle8 =[ $old8, old_idle9 ] {}; +void() old_idle9 =[ $old9, old_idle10 ] {}; +void() old_idle10 =[ $old10, old_idle11 ] {}; +void() old_idle11 =[ $old11, old_idle12 ] {}; +void() old_idle12 =[ $old12, old_idle13 ] {}; +void() old_idle13 =[ $old13, old_idle14 ] {}; +void() old_idle14 =[ $old14, old_idle15 ] {}; +void() old_idle15 =[ $old15, old_idle16 ] {}; +void() old_idle16 =[ $old16, old_idle17 ] {}; +void() old_idle17 =[ $old17, old_idle18 ] {}; +void() old_idle18 =[ $old18, old_idle19 ] {}; +void() old_idle19 =[ $old19, old_idle20 ] {}; +void() old_idle20 =[ $old20, old_idle21 ] {}; +void() old_idle21 =[ $old21, old_idle22 ] {}; +void() old_idle22 =[ $old22, old_idle23 ] {}; +void() old_idle23 =[ $old23, old_idle24 ] {}; +void() old_idle24 =[ $old24, old_idle25 ] {}; +void() old_idle25 =[ $old25, old_idle26 ] {}; +void() old_idle26 =[ $old26, old_idle27 ] {}; +void() old_idle27 =[ $old27, old_idle28 ] {}; +void() old_idle28 =[ $old28, old_idle29 ] {}; +void() old_idle29 =[ $old29, old_idle30 ] {}; +void() old_idle30 =[ $old30, old_idle31 ] {}; +void() old_idle31 =[ $old31, old_idle32 ] {}; +void() old_idle32 =[ $old32, old_idle33 ] {}; +void() old_idle33 =[ $old33, old_idle34 ] {}; +void() old_idle34 =[ $old34, old_idle35 ] {}; +void() old_idle35 =[ $old35, old_idle36 ] {}; +void() old_idle36 =[ $old36, old_idle37 ] {}; +void() old_idle37 =[ $old37, old_idle38 ] {}; +void() old_idle38 =[ $old38, old_idle39 ] {}; +void() old_idle39 =[ $old39, old_idle40 ] {}; +void() old_idle40 =[ $old40, old_idle41 ] {}; +void() old_idle41 =[ $old41, old_idle42 ] {}; +void() old_idle42 =[ $old42, old_idle43 ] {}; +void() old_idle43 =[ $old43, old_idle44 ] {}; +void() old_idle44 =[ $old44, old_idle45 ] {}; +void() old_idle45 =[ $old45, old_idle46 ] {}; +void() old_idle46 =[ $old46, old_idle1 ] {}; + + +void() old_thrash1 =[ $shake1, old_thrash2 ] {lightstyle(0, "m");}; +void() old_thrash2 =[ $shake2, old_thrash3 ] {lightstyle(0, "k");}; +void() old_thrash3 =[ $shake3, old_thrash4 ] {lightstyle(0, "k");}; +void() old_thrash4 =[ $shake4, old_thrash5 ] {lightstyle(0, "i");}; +void() old_thrash5 =[ $shake5, old_thrash6 ] {lightstyle(0, "g");}; +void() old_thrash6 =[ $shake6, old_thrash7 ] {lightstyle(0, "e");}; +void() old_thrash7 =[ $shake7, old_thrash8 ] {lightstyle(0, "c");}; +void() old_thrash8 =[ $shake8, old_thrash9 ] {lightstyle(0, "a");}; +void() old_thrash9 =[ $shake9, old_thrash10 ] {lightstyle(0, "c");}; +void() old_thrash10 =[ $shake10, old_thrash11 ] {lightstyle(0, "e");}; +void() old_thrash11 =[ $shake11, old_thrash12 ] {lightstyle(0, "g");}; +void() old_thrash12 =[ $shake12, old_thrash13 ] {lightstyle(0, "i");}; +void() old_thrash13 =[ $shake13, old_thrash14 ] {lightstyle(0, "k");}; +void() old_thrash14 =[ $shake14, old_thrash15 ] {lightstyle(0, "m");}; +void() old_thrash15 =[ $shake15, old_thrash16 ] {lightstyle(0, "m"); +self.cnt = self.cnt + 1; +if (self.cnt != 3) + self.think = old_thrash1; +}; +void() old_thrash16 =[ $shake16, old_thrash17 ] {lightstyle(0, "g");}; +void() old_thrash17 =[ $shake17, old_thrash18 ] {lightstyle(0, "c");}; +void() old_thrash18 =[ $shake18, old_thrash19 ] {lightstyle(0, "b");}; +void() old_thrash19 =[ $shake19, old_thrash20 ] {lightstyle(0, "a");}; +void() old_thrash20 =[ $shake20, old_thrash20 ] {finale_4();}; + +//============================================================================ + +void() finale_1 = +{ + local entity pos, pl; + local entity timer; + + intermission_exittime = time + 10000000; // never allow exit + intermission_running = 1; + + // find the intermission spot + pos = find (world, classname, "info_intermission"); + if (!pos) + error ("no info_intermission"); + pl = find (world, classname, "misc_teleporttrain"); + if (!pl) + error ("no teleporttrain"); + remove (pl); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, ""); + + pl = find (world, classname, "player"); + while (pl != world) + { + pl.view_ofs = '0 0 0'; + pl.angles = other.v_angle = pos.mangle; + pl.fixangle = TRUE; // turn this way immediately + pl.map = self.map; + pl.nextthink = time + 0.5; + pl.takedamage = DAMAGE_NO; + pl.solid = SOLID_NOT; + pl.movetype = MOVETYPE_NONE; + pl.modelindex = 0; + setorigin (pl, pos.origin); + pl = find (pl, classname, "player"); + } + + // make fake versions of all players as standins, and move the real + // players to the intermission spot + + // wait for 1 second + timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_2; +}; + +void() finale_2 = +{ + local vector o; + + // start a teleport splash inside shub + + o = shub.origin - '0 100 0'; + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, o_x); + WriteCoord (MSG_BROADCAST, o_y); + WriteCoord (MSG_BROADCAST, o_z); + + sound (shub, CHAN_VOICE, "misc/r_tele1.wav", 1, ATTN_NORM); + + self.nextthink = time + 2; + self.think = finale_3; +}; + +void() finale_3 = +{ + // start shub thrashing wildly + shub.think = old_thrash1; + sound (shub, CHAN_VOICE, "boss2/death.wav", 1, ATTN_NORM); + lightstyle(0, "abcdefghijklmlkjihgfedcb"); +}; + +void() finale_4 = +{ + // throw tons of meat chunks + local vector oldo; + local float x, y, z; + local float r; + local entity n; + + sound (self, CHAN_VOICE, "boss2/pop2.wav", 1, ATTN_NORM); + + oldo = self.origin; + + z = 16; + while (z <= 144) + { + x = -64; + while (x <= 64) + { + y = -64; + while (y <= 64) + { + self.origin_x = oldo_x + x; + self.origin_y = oldo_y + y; + self.origin_z = oldo_z + z; + + r = random(); + if (r < 0.3) + ThrowGib ("progs/gib1.mdl", -999); + else if (r < 0.6) + ThrowGib ("progs/gib2.mdl", -999); + else + ThrowGib ("progs/gib3.mdl", -999); + y = y + 32; + } + x = x + 32; + } + z = z + 96; + } + // start the end text + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_end"); + +// put a player model down + n = spawn(); + setmodel (n, "progs/player.mdl"); + oldo = oldo - '32 264 0'; + setorigin (n, oldo); + n.angles = '0 290 0'; + n.frame = 1; + + remove (self); + +// switch cd track + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + lightstyle(0, "m"); +}; + +//============================================================================ + +void (entity attacker, float damage) nopain = +{ + self.health = 40000; +}; + +//============================================================================ + + +/*QUAKED monster_oldone (1 0 0) (-16 -16 -24) (16 16 32) +*/ +void() monster_oldone = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/oldone.mdl"); + + precache_sound2 ("boss2/death.wav"); + precache_sound2 ("boss2/idle.wav"); + precache_sound2 ("boss2/sight.wav"); + precache_sound2 ("boss2/pop2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/oldone.mdl"); + setsize (self, '-160 -128 -24', '160 128 256'); + + self.health = 40000; // kill by telefrag + self.think = old_idle1; + self.nextthink = time + 0.1; + self.takedamage = DAMAGE_YES; + self.th_pain = nopain; + self.th_die = finale_1; + shub = self; + + total_monsters = total_monsters + 1; +}; + diff --git a/quakec_mg1/monsters/shalrath.qc b/quakec_mg1/monsters/shalrath.qc new file mode 100644 index 0000000..5f6985b --- /dev/null +++ b/quakec_mg1/monsters/shalrath.qc @@ -0,0 +1,252 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAL-RATH + +============================================================================== +*/ +$cd id1/models/shalrath +$origin 0 0 24 +$base base +$skin skin +$scale 0.7 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 CORPSEFRAME_SHALRATH_1 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 + +void(entity attacker, float damage) shalrath_pain; +void() ShalMissile; +void() shal_stand =[ $walk1, shal_stand ] {ai_stand();}; + +void() shal_walk1 =[ $walk2, shal_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); +ai_walk(6);}; +void() shal_walk2 =[ $walk3, shal_walk3 ] {ai_walk(4);}; +void() shal_walk3 =[ $walk4, shal_walk4 ] {ai_walk(0);}; +void() shal_walk4 =[ $walk5, shal_walk5 ] {ai_walk(0);}; +void() shal_walk5 =[ $walk6, shal_walk6 ] {ai_walk(0);}; +void() shal_walk6 =[ $walk7, shal_walk7 ] {ai_walk(0);}; +void() shal_walk7 =[ $walk8, shal_walk8 ] {ai_walk(5);}; +void() shal_walk8 =[ $walk9, shal_walk9 ] {ai_walk(6);}; +void() shal_walk9 =[ $walk10, shal_walk10 ] {ai_walk(5);}; +void() shal_walk10 =[ $walk11, shal_walk11 ] {ai_walk(0);}; +void() shal_walk11 =[ $walk12, shal_walk12 ] {ai_walk(4);}; +void() shal_walk12 =[ $walk1, shal_walk1 ] {ai_walk(5);}; + +void() shal_run1 =[ $walk2, shal_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); +ai_run(6);}; +void() shal_run2 =[ $walk3, shal_run3 ] {ai_run(4);}; +void() shal_run3 =[ $walk4, shal_run4 ] {ai_run(0);}; +void() shal_run4 =[ $walk5, shal_run5 ] {ai_run(0);}; +void() shal_run5 =[ $walk6, shal_run6 ] {ai_run(0);}; +void() shal_run6 =[ $walk7, shal_run7 ] {ai_run(0);}; +void() shal_run7 =[ $walk8, shal_run8 ] {ai_run(5);}; +void() shal_run8 =[ $walk9, shal_run9 ] {ai_run(6);}; +void() shal_run9 =[ $walk10, shal_run10 ] {ai_run(5);}; +void() shal_run10 =[ $walk11, shal_run11 ] {ai_run(0);}; +void() shal_run11 =[ $walk12, shal_run12 ] {ai_run(4);}; +void() shal_run12 =[ $walk1, shal_run1 ] {ai_run(5);}; + +void() shal_attack1 =[ $attack1, shal_attack2 ] { +sound (self, CHAN_VOICE, "shalrath/attack.wav", 1, ATTN_NORM); +ai_face(); +}; +void() shal_attack2 =[ $attack2, shal_attack3 ] {ai_face();}; +void() shal_attack3 =[ $attack3, shal_attack4 ] {ai_face();}; +void() shal_attack4 =[ $attack4, shal_attack5 ] {ai_face();}; +void() shal_attack5 =[ $attack5, shal_attack6 ] {ai_face();}; +void() shal_attack6 =[ $attack6, shal_attack7 ] {ai_face();}; +void() shal_attack7 =[ $attack7, shal_attack8 ] {ai_face();}; +void() shal_attack8 =[ $attack8, shal_attack9 ] {ai_face();}; +void() shal_attack9 =[ $attack9, shal_attack10 ] {ShalMissile();}; +void() shal_attack10 =[ $attack10, shal_attack11 ] {ai_face();}; +void() shal_attack11 =[ $attack11, shal_run1 ] {}; + +void() shal_pain1 =[ $pain1, shal_pain2 ] {}; +void() shal_pain2 =[ $pain2, shal_pain3 ] {}; +void() shal_pain3 =[ $pain3, shal_pain4 ] {}; +void() shal_pain4 =[ $pain4, shal_pain5 ] {}; +void() shal_pain5 =[ $pain5, shal_run1 ] {}; + +void() shal_death1 =[ $death1, shal_death2 ] {}; +void() shal_death2 =[ $death2, shal_death3 ] {}; +void() shal_death3 =[ $death3, shal_death4 ] {}; +void() shal_death4 =[ $death4, shal_death5 ] {}; +void() shal_death5 =[ $death5, shal_death6 ] {}; +void() shal_death6 =[ $death6, shal_death7 ] {}; +void() shal_death7 =[ $CORPSEFRAME_SHALRATH_1, shal_death7 ] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + + +void(entity attacker, float damage) shalrath_pain = +{ + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "shalrath/pain.wav", 1, ATTN_NORM); + shal_pain1(); + self.pain_finished = time + 3; +}; + +void() shalrath_die = +{ +// check for gib + if (self.health < -90) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shal.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "shalrath/death.wav", 1, ATTN_NORM); + shal_death1(); + self.solid = SOLID_NOT; + // insert death sounds here +}; + +/* +================ +ShalMissile +================ +*/ +void() ShalMissileTouch; +void() ShalHome; +void() ShalMissile = +{ + local entity missile; + local vector dir; + local float dist, flytime; + + dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + dist = vlen (self.enemy.origin - self.origin); + flytime = dist * 0.002; + if (flytime < 0.1) + flytime = 0.1; + + self.effects = self.effects | EF_MUZZLEFLASH; + sound (self, CHAN_WEAPON, "shalrath/attack2.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + setmodel (missile, "progs/v_spike.mdl"); + + setsize (missile, '0 0 0', '0 0 0'); + + missile.origin = self.origin + '0 0 10'; + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.nextthink = flytime + time; + missile.think = ShalHome; + missile.enemy = self.enemy; + missile.touch = ShalMissileTouch; +}; + +void() ShalHome = +{ + local vector dir, vtemp; + vtemp = self.enemy.origin + '0 0 10'; + if (self.enemy.health < 1) + { + remove(self); + return; + } + dir = normalize(vtemp - self.origin); + self.velocity = dir * 250; + self.nextthink = time + 0.2; + self.think = ShalHome; +}; + +void() ShalMissileTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (other.classname == "monster_zombie") + T_Damage (other, self, self, 110); + T_RadiusDamage (self, self.owner, 40, world); + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +//================================================================= + +/*QUAKED monster_shalrath (1 0 0) (-32 -32 -24) (32 32 48) Ambush +*/ +void() monster_shalrath = +{ + precache_model2 ("progs/shalrath.mdl"); + precache_model2 ("progs/h_shal.mdl"); + precache_model2 ("progs/v_spike.mdl"); + + precache_sound2 ("shalrath/attack.wav"); + precache_sound2 ("shalrath/attack2.wav"); + precache_sound2 ("shalrath/death.wav"); + precache_sound2 ("shalrath/idle.wav"); + precache_sound2 ("shalrath/pain.wav"); + precache_sound2 ("shalrath/sight.wav"); + + self.health = 400; + + self.th_stand = shal_stand; + self.th_walk = shal_walk1; + self.th_run = shal_run1; + self.th_die = shalrath_die; + self.th_pain = shalrath_pain; + self.th_missile = shal_attack1; + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + + InitMonster("progs/shalrath.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_LARGE); +}; diff --git a/quakec_mg1/monsters/shambler.qc b/quakec_mg1/monsters/shambler.qc new file mode 100644 index 0000000..913b015 --- /dev/null +++ b/quakec_mg1/monsters/shambler.qc @@ -0,0 +1,400 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAMBLER + +============================================================================== +*/ + +$cd id1/models/shams +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 stand14 stand15 stand16 stand17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 + +$frame run1 run2 run3 run4 run5 run6 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 + +$frame swingr1 swingr2 swingr3 swingr4 swingr5 +$frame swingr6 swingr7 swingr8 swingr9 + +$frame swingl1 swingl2 swingl3 swingl4 swingl5 +$frame swingl6 swingl7 swingl8 swingl9 + +$frame magic1 magic2 magic3 magic4 magic5 +$frame magic6 magic7 magic8 magic9 magic10 magic11 magic12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 CORPSEFRAME_SHAMBLER_1 + + +void() sham_stand1 =[ $stand1, sham_stand2 ] {ai_stand();}; +void() sham_stand2 =[ $stand2, sham_stand3 ] {ai_stand();}; +void() sham_stand3 =[ $stand3, sham_stand4 ] {ai_stand();}; +void() sham_stand4 =[ $stand4, sham_stand5 ] {ai_stand();}; +void() sham_stand5 =[ $stand5, sham_stand6 ] {ai_stand();}; +void() sham_stand6 =[ $stand6, sham_stand7 ] {ai_stand();}; +void() sham_stand7 =[ $stand7, sham_stand8 ] {ai_stand();}; +void() sham_stand8 =[ $stand8, sham_stand9 ] {ai_stand();}; +void() sham_stand9 =[ $stand9, sham_stand10] {ai_stand();}; +void() sham_stand10 =[ $stand10, sham_stand11] {ai_stand();}; +void() sham_stand11 =[ $stand11, sham_stand12] {ai_stand();}; +void() sham_stand12 =[ $stand12, sham_stand13] {ai_stand();}; +void() sham_stand13 =[ $stand13, sham_stand14] {ai_stand();}; +void() sham_stand14 =[ $stand14, sham_stand15] {ai_stand();}; +void() sham_stand15 =[ $stand15, sham_stand16] {ai_stand();}; +void() sham_stand16 =[ $stand16, sham_stand17] {ai_stand();}; +void() sham_stand17 =[ $stand17, sham_stand1 ] {ai_stand();}; + +void() sham_walk1 =[ $walk1, sham_walk2 ] {ai_walk(10);}; +void() sham_walk2 =[ $walk2, sham_walk3 ] {ai_walk(9);}; +void() sham_walk3 =[ $walk3, sham_walk4 ] {ai_walk(9);}; +void() sham_walk4 =[ $walk4, sham_walk5 ] {ai_walk(5);}; +void() sham_walk5 =[ $walk5, sham_walk6 ] {ai_walk(6);}; +void() sham_walk6 =[ $walk6, sham_walk7 ] {ai_walk(12);}; +void() sham_walk7 =[ $walk7, sham_walk8 ] {ai_walk(8);}; +void() sham_walk8 =[ $walk8, sham_walk9 ] {ai_walk(3);}; +void() sham_walk9 =[ $walk9, sham_walk10] {ai_walk(13);}; +void() sham_walk10 =[ $walk10, sham_walk11] {ai_walk(9);}; +void() sham_walk11 =[ $walk11, sham_walk12] {ai_walk(7);}; +void() sham_walk12 =[ $walk12, sham_walk1 ] {ai_walk(7); +if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE);}; + +void() sham_run1 =[ $run1, sham_run2 ] {ai_run(20);}; +void() sham_run2 =[ $run2, sham_run3 ] {ai_run(24);}; +void() sham_run3 =[ $run3, sham_run4 ] {ai_run(20);}; +void() sham_run4 =[ $run4, sham_run5 ] {ai_run(20);}; +void() sham_run5 =[ $run5, sham_run6 ] {ai_run(24);}; +void() sham_run6 =[ $run6, sham_run1 ] {ai_run(20); +if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE); +}; + +void() sham_smash1 =[ $smash1, sham_smash2 ] { +sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); +ai_charge(2);}; +void() sham_smash2 =[ $smash2, sham_smash3 ] {ai_charge(6);}; +void() sham_smash3 =[ $smash3, sham_smash4 ] {ai_charge(6);}; +void() sham_smash4 =[ $smash4, sham_smash5 ] {ai_charge(5);}; +void() sham_smash5 =[ $smash5, sham_smash6 ] {ai_charge(4);}; +void() sham_smash6 =[ $smash6, sham_smash7 ] {ai_charge(1);}; +void() sham_smash7 =[ $smash7, sham_smash8 ] {ai_charge(0);}; +void() sham_smash8 =[ $smash8, sham_smash9 ] {ai_charge(0);}; +void() sham_smash9 =[ $smash9, sham_smash10 ] {ai_charge(0);}; +void() sham_smash10 =[ $smash10, sham_smash11 ] { +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + ai_charge(0); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + ldmg = (random() + random() + random()) * 40; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); +}; +void() sham_smash11 =[ $smash11, sham_smash12 ] {ai_charge(5);}; +void() sham_smash12 =[ $smash12, sham_run1 ] {ai_charge(4);}; + +void() sham_swingr1; + +void(float side) ShamClaw = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 20; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + if (side) + { + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + +void() sham_swingl1 =[ $swingl1, sham_swingl2 ] { +sound (self, CHAN_VOICE, "shambler/melee2.wav", 1, ATTN_NORM); +ai_charge(5);}; +void() sham_swingl2 =[ $swingl2, sham_swingl3 ] {ai_charge(3);}; +void() sham_swingl3 =[ $swingl3, sham_swingl4 ] {ai_charge(7);}; +void() sham_swingl4 =[ $swingl4, sham_swingl5 ] {ai_charge(3);}; +void() sham_swingl5 =[ $swingl5, sham_swingl6 ] {ai_charge(7);}; +void() sham_swingl6 =[ $swingl6, sham_swingl7 ] {ai_charge(9);}; +void() sham_swingl7 =[ $swingl7, sham_swingl8 ] {ai_charge(5); ShamClaw(250);}; +void() sham_swingl8 =[ $swingl8, sham_swingl9 ] {ai_charge(4);}; +void() sham_swingl9 =[ $swingl9, sham_run1 ] { +ai_charge(8); +if (random()<0.5) + self.think = sham_swingr1; +}; + +void() sham_swingr1 =[ $swingr1, sham_swingr2 ] { +sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); +ai_charge(1);}; +void() sham_swingr2 =[ $swingr2, sham_swingr3 ] {ai_charge(8);}; +void() sham_swingr3 =[ $swingr3, sham_swingr4 ] {ai_charge(14);}; +void() sham_swingr4 =[ $swingr4, sham_swingr5 ] {ai_charge(7);}; +void() sham_swingr5 =[ $swingr5, sham_swingr6 ] {ai_charge(3);}; +void() sham_swingr6 =[ $swingr6, sham_swingr7 ] {ai_charge(6);}; +void() sham_swingr7 =[ $swingr7, sham_swingr8 ] {ai_charge(6); ShamClaw(-250);}; +void() sham_swingr8 =[ $swingr8, sham_swingr9 ] {ai_charge(3);}; +void() sham_swingr9 =[ $swingr9, sham_run1 ] {ai_charge(1); +ai_charge(10); +if (random()<0.5) + self.think = sham_swingl1; +}; + +void() sham_melee = +{ + local float chance; + + chance = random(); + if (chance > 0.6 || self.health == 600) + sham_smash1 (); + else if (chance > 0.3) + sham_swingr1 (); + else + sham_swingl1 (); +}; + + +//============================================================================ + +void() CastLightning = +{ + local vector org, dir; + + self.effects = self.effects | EF_MUZZLEFLASH; + + ai_face (); + + org = self.origin + '0 0 40'; + + dir = self.enemy.origin + '0 0 16' - org; + dir = normalize (dir); + + traceline (org, self.origin + dir*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING1); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + // overload the frags field which is unused on enemies + self.frags += 1; + + LightningDamage (org, trace_endpos, self, 10); +}; + +void() sham_magic1 =[ $magic1, sham_magic2 ] {ai_face(); + sound (self, CHAN_WEAPON, "shambler/sattck1.wav", 1, ATTN_NORM); +}; +void() sham_magic2 =[ $magic2, sham_magic3 ] {ai_face();}; +void() sham_magic3 =[ $magic3, sham_magic4 ] {ai_face();self.nextthink = self.nextthink + 0.2; +local entity o; + +self.effects = self.effects | EF_MUZZLEFLASH; +ai_face(); +self.owner = spawn(); +o = self.owner; +setmodel (o, "progs/s_light.mdl"); +setorigin (o, self.origin); +o.angles = self.angles; +o.nextthink = time + 0.7; +o.think = SUB_Remove; +}; +void() sham_magic4 =[ $magic4, sham_magic5 ] +{ +self.effects = self.effects | EF_MUZZLEFLASH; +self.owner.frame = 1; +}; +void() sham_magic5 =[ $magic5, sham_magic6 ] +{ +self.effects = self.effects | EF_MUZZLEFLASH; +self.owner.frame = 2; +}; +void() sham_magic6 =[ $magic6, sham_magic9 ] +{ +remove (self.owner); +CastLightning(); +sound (self, CHAN_WEAPON, "shambler/sboom.wav", 1, ATTN_NORM); +}; +void() sham_magic9 =[ $magic9, sham_magic10 ] +{CastLightning();}; +void() sham_magic10 =[ $magic10, sham_magic11 ] +{CastLightning();}; +void() sham_magic11 =[ $magic11, sham_magic12 ] {}; +void() sham_magic12 =[ $magic12, sham_run1 ] {}; + + + +void() sham_pain1 =[ $pain1, sham_pain2 ] {}; +void() sham_pain2 =[ $pain2, sham_pain3 ] {}; +void() sham_pain3 =[ $pain3, sham_pain4 ] {}; +void() sham_pain4 =[ $pain4, sham_pain5 ] {}; +void() sham_pain5 =[ $pain5, sham_pain6 ] {}; +void() sham_pain6 =[ $pain6, sham_run1 ] {}; + +void(entity attacker, float damage) sham_pain = +{ + sound (self, CHAN_VOICE, "shambler/shurt2.wav", 1, ATTN_NORM); + + if (damage >= self.health && attacker.classname == "player" && attacker.weapon == IT_AXE) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_CLOSE_SHAVE"); + } + + // overload frags field for shambler dance achievement + if (damage >= self.health && attacker.classname == "player" && self.frags == 0) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SHAMBLER_DANCE"); + } + + if (self.health <= 0) + return; // allready dying, don't go into pain frame + + if (random()*400 > damage) + return; // didn't flinch + + if (self.pain_finished > time) + return; + self.pain_finished = time + 2; + + sham_pain1 (); +}; + + +//============================================================================ + +void() sham_death1 =[ $death1, sham_death2 ] {}; +void() sham_death2 =[ $death2, sham_death3 ] {}; +void() sham_death3 =[ $death3, sham_death4 ] {self.solid = SOLID_NOT;}; +void() sham_death4 =[ $death4, sham_death5 ] {}; +void() sham_death5 =[ $death5, sham_death6 ] {}; +void() sham_death6 =[ $death6, sham_death7 ] {}; +void() sham_death7 =[ $death7, sham_death8 ] {}; +void() sham_death8 =[ $death8, sham_death9 ] {}; +void() sham_death9 =[ $death9, sham_death10 ] {}; +void() sham_death10 =[ $death10, sham_death11 ] {}; +void() sham_death11 =[ $CORPSEFRAME_SHAMBLER_1, sham_death11 ] +{ + self.think = MonsterFade; + self.nextthink = time + FADETIME; +}; + +void() sham_die = +{ +// check for gib + if (self.health < -60) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shams.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "shambler/sdeath.wav", 1, ATTN_NORM); + sham_death1 (); +}; + +//============================================================================ + + +/*QUAKED monster_shambler (1 0 0) (-32 -32 -24) (32 32 64) Ambush +*/ +void() monster_shambler = +{ + precache_model ("progs/shambler.mdl"); + precache_model ("progs/s_light.mdl"); + precache_model ("progs/h_shams.mdl"); + precache_model ("progs/bolt.mdl"); + + precache_sound ("shambler/sattck1.wav"); + precache_sound ("shambler/sboom.wav"); + precache_sound ("shambler/sdeath.wav"); + precache_sound ("shambler/shurt2.wav"); + precache_sound ("shambler/sidle.wav"); + precache_sound ("shambler/ssight.wav"); + precache_sound ("shambler/melee1.wav"); + precache_sound ("shambler/melee2.wav"); + precache_sound ("shambler/smack.wav"); + + self.health = 600; + + self.th_stand = sham_stand1; + self.th_walk = sham_walk1; + self.th_run = sham_run1; + self.th_die = sham_die; + self.th_melee = sham_melee; + self.th_missile = sham_magic1; + self.th_pain = sham_pain; + self.allowPathFind = TRUE; + self.combat_style = CS_MIXED; + + // overload frags for lightning attack counts + self.frags = 0; + + InitMonster("progs/shambler.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_LARGE); +}; diff --git a/quakec_mg1/monsters/soldier.qc b/quakec_mg1/monsters/soldier.qc new file mode 100644 index 0000000..74787fc --- /dev/null +++ b/quakec_mg1/monsters/soldier.qc @@ -0,0 +1,301 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd id1/models/soldier3 +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 CORPSEFRAME_SOLDIER_1 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 CORPSEFRAME_SOLDIER_2 + +$frame load1 load2 load3 load4 load5 load6 load7 load8 load9 load10 load11 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 shoot7 shoot8 shoot9 + +$frame prowl_1 prowl_2 prowl_3 prowl_4 prowl_5 prowl_6 prowl_7 prowl_8 +$frame prowl_9 prowl_10 prowl_11 prowl_12 prowl_13 prowl_14 prowl_15 prowl_16 +$frame prowl_17 prowl_18 prowl_19 prowl_20 prowl_21 prowl_22 prowl_23 prowl_24 + +/* +============================================================================== +SOLDIER CODE +============================================================================== +*/ + +void() army_fire; + +void() army_stand1 =[ $stand1, army_stand2 ] {ai_stand();}; +void() army_stand2 =[ $stand2, army_stand3 ] {ai_stand();}; +void() army_stand3 =[ $stand3, army_stand4 ] {ai_stand();}; +void() army_stand4 =[ $stand4, army_stand5 ] {ai_stand();}; +void() army_stand5 =[ $stand5, army_stand6 ] {ai_stand();}; +void() army_stand6 =[ $stand6, army_stand7 ] {ai_stand();}; +void() army_stand7 =[ $stand7, army_stand8 ] {ai_stand();}; +void() army_stand8 =[ $stand8, army_stand1 ] {ai_stand();}; + +void() army_walk1 =[ $prowl_1, army_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); +ai_walk(1);}; +void() army_walk2 =[ $prowl_2, army_walk3 ] {ai_walk(1);}; +void() army_walk3 =[ $prowl_3, army_walk4 ] {ai_walk(1);}; +void() army_walk4 =[ $prowl_4, army_walk5 ] {ai_walk(1);}; +void() army_walk5 =[ $prowl_5, army_walk6 ] {ai_walk(2);}; +void() army_walk6 =[ $prowl_6, army_walk7 ] {ai_walk(3);}; +void() army_walk7 =[ $prowl_7, army_walk8 ] {ai_walk(4);}; +void() army_walk8 =[ $prowl_8, army_walk9 ] {ai_walk(4);}; +void() army_walk9 =[ $prowl_9, army_walk10 ] {ai_walk(2);}; +void() army_walk10 =[ $prowl_10, army_walk11 ] {ai_walk(2);}; +void() army_walk11 =[ $prowl_11, army_walk12 ] {ai_walk(2);}; +void() army_walk12 =[ $prowl_12, army_walk13 ] {ai_walk(1);}; +void() army_walk13 =[ $prowl_13, army_walk14 ] {ai_walk(0);}; +void() army_walk14 =[ $prowl_14, army_walk15 ] {ai_walk(1);}; +void() army_walk15 =[ $prowl_15, army_walk16 ] {ai_walk(1);}; +void() army_walk16 =[ $prowl_16, army_walk17 ] {ai_walk(1);}; +void() army_walk17 =[ $prowl_17, army_walk18 ] {ai_walk(3);}; +void() army_walk18 =[ $prowl_18, army_walk19 ] {ai_walk(3);}; +void() army_walk19 =[ $prowl_19, army_walk20 ] {ai_walk(3);}; +void() army_walk20 =[ $prowl_20, army_walk21 ] {ai_walk(3);}; +void() army_walk21 =[ $prowl_21, army_walk22 ] {ai_walk(2);}; +void() army_walk22 =[ $prowl_22, army_walk23 ] {ai_walk(1);}; +void() army_walk23 =[ $prowl_23, army_walk24 ] {ai_walk(1);}; +void() army_walk24 =[ $prowl_24, army_walk1 ] {ai_walk(1);}; + +void() army_run1 =[ $run1, army_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); +ai_run(11);}; +void() army_run2 =[ $run2, army_run3 ] {ai_run(15);}; +void() army_run3 =[ $run3, army_run4 ] {ai_run(10);}; +void() army_run4 =[ $run4, army_run5 ] {ai_run(10);}; +void() army_run5 =[ $run5, army_run6 ] {ai_run(8);}; +void() army_run6 =[ $run6, army_run7 ] {ai_run(15);}; +void() army_run7 =[ $run7, army_run8 ] {ai_run(10);}; +void() army_run8 =[ $run8, army_run1 ] {ai_run(8);}; + +void() army_atk1 =[ $shoot1, army_atk2 ] {ai_face();}; +void() army_atk2 =[ $shoot2, army_atk3 ] {ai_face();}; +void() army_atk3 =[ $shoot3, army_atk4 ] {ai_face();}; +void() army_atk4 =[ $shoot4, army_atk5 ] {ai_face();}; +void() army_atk5 =[ $shoot5, army_atk6 ] {ai_face();army_fire(); +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() army_atk6 =[ $shoot6, army_atk7 ] {ai_face();}; +void() army_atk7 =[ $shoot7, army_atk8 ] {ai_face();SUB_CheckRefire (army_atk1);}; +void() army_atk8 =[ $shoot8, army_atk9 ] {ai_face();}; +void() army_atk9 =[ $shoot9, army_run1 ] {ai_face();}; + + +void() army_pain1 =[ $pain1, army_pain2 ] {}; +void() army_pain2 =[ $pain2, army_pain3 ] {}; +void() army_pain3 =[ $pain3, army_pain4 ] {}; +void() army_pain4 =[ $pain4, army_pain5 ] {}; +void() army_pain5 =[ $pain5, army_pain6 ] {}; +void() army_pain6 =[ $pain6, army_run1 ] {ai_pain(1);}; + +void() army_painb1 =[ $painb1, army_painb2 ] {}; +void() army_painb2 =[ $painb2, army_painb3 ] {ai_painforward(13);}; +void() army_painb3 =[ $painb3, army_painb4 ] {ai_painforward(9);}; +void() army_painb4 =[ $painb4, army_painb5 ] {}; +void() army_painb5 =[ $painb5, army_painb6 ] {}; +void() army_painb6 =[ $painb6, army_painb7 ] {}; +void() army_painb7 =[ $painb7, army_painb8 ] {}; +void() army_painb8 =[ $painb8, army_painb9 ] {}; +void() army_painb9 =[ $painb9, army_painb10] {}; +void() army_painb10=[ $painb10, army_painb11] {}; +void() army_painb11=[ $painb11, army_painb12] {}; +void() army_painb12=[ $painb12, army_painb13] {ai_pain(2);}; +void() army_painb13=[ $painb13, army_painb14] {}; +void() army_painb14=[ $painb14, army_run1 ] {}; + +void() army_painc1 =[ $painc1, army_painc2 ] {}; +void() army_painc2 =[ $painc2, army_painc3 ] {ai_pain(1);}; +void() army_painc3 =[ $painc3, army_painc4 ] {}; +void() army_painc4 =[ $painc4, army_painc5 ] {}; +void() army_painc5 =[ $painc5, army_painc6 ] {ai_painforward(1);}; +void() army_painc6 =[ $painc6, army_painc7 ] {ai_painforward(1);}; +void() army_painc7 =[ $painc7, army_painc8 ] {}; +void() army_painc8 =[ $painc8, army_painc9 ] {ai_pain(1);}; +void() army_painc9 =[ $painc9, army_painc10] {ai_painforward(4);}; +void() army_painc10=[ $painc10, army_painc11] {ai_painforward(3);}; +void() army_painc11=[ $painc11, army_painc12] {ai_painforward(6);}; +void() army_painc12=[ $painc12, army_painc13] {ai_painforward(8);}; +void() army_painc13=[ $painc13, army_run1] {}; + +void(entity attacker, float damage) army_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (r < 0.2) + { + self.pain_finished = time + 0.6; + army_pain1 (); + sound (self, CHAN_VOICE, "soldier/pain1.wav", 1, ATTN_NORM); + } + else if (r < 0.6) + { + self.pain_finished = time + 1.1; + army_painb1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } + else + { + self.pain_finished = time + 1.1; + army_painc1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } +}; + + +void() army_fire = +{ + local vector dir; + local entity en; + + ai_face(); + + sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM); + +// fire somewhat behind the player, so a dodging player is harder to hit + en = self.enemy; + + dir = en.origin - en.velocity*0.2; + dir = normalize (dir - self.origin); + + FireBullets (4, dir, '0.1 0.1 0'); +}; + + + +void() army_die1 =[ $death1, army_die2 ] {}; +void() army_die2 =[ $death2, army_die3 ] {}; +void() army_die3 =[ $death3, army_die4 ] +{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();}; +void() army_die4 =[ $death4, army_die5 ] {}; +void() army_die5 =[ $death5, army_die6 ] {}; +void() army_die6 =[ $death6, army_die7 ] {}; +void() army_die7 =[ $death7, army_die8 ] {}; +void() army_die8 =[ $death8, army_die9 ] {}; +void() army_die9 =[ $death9, army_die10 ] {}; +void() army_die10 =[ $CORPSEFRAME_SOLDIER_1, army_die10 ] +{ + self.think = MonsterFade; + self.nextthink = time + FADETIME; +}; + +void() army_cdie1 =[ $deathc1, army_cdie2 ] {}; +void() army_cdie2 =[ $deathc2, army_cdie3 ] {ai_back(5);}; +void() army_cdie3 =[ $deathc3, army_cdie4 ] +{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();ai_back(4);}; +void() army_cdie4 =[ $deathc4, army_cdie5 ] {ai_back(13);}; +void() army_cdie5 =[ $deathc5, army_cdie6 ] {ai_back(3);}; +void() army_cdie6 =[ $deathc6, army_cdie7 ] {ai_back(4);}; +void() army_cdie7 =[ $deathc7, army_cdie8 ] {}; +void() army_cdie8 =[ $deathc8, army_cdie9 ] {}; +void() army_cdie9 =[ $deathc9, army_cdie10 ] {}; +void() army_cdie10 =[ $deathc10, army_cdie11 ] {}; +void() army_cdie11 =[ $CORPSEFRAME_SOLDIER_2, army_cdie11 ] +{ + self.think = MonsterFade; + self.nextthink = time + FADETIME; +}; + + +void() army_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_guard.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "soldier/death1.wav", 1, ATTN_NORM); + if (random() < 0.5) + army_die1 (); + else + army_cdie1 (); +}; + + +/*QUAKED monster_army (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_army = +{ + precache_model ("progs/soldier.mdl"); + precache_model ("progs/h_guard.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_sound ("soldier/death1.wav"); + precache_sound ("soldier/idle.wav"); + precache_sound ("soldier/pain1.wav"); + precache_sound ("soldier/pain2.wav"); + precache_sound ("soldier/sattck1.wav"); + precache_sound ("soldier/sight1.wav"); + + precache_sound ("player/udeath.wav"); // gib death + + self.health = 30; + + self.th_stand = army_stand1; + self.th_walk = army_walk1; + self.th_run = army_run1; + self.th_missile = army_atk1; + self.th_pain = army_pain; + self.th_die = army_die; + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + + InitMonster("progs/soldier.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_SMALL); +}; diff --git a/quakec_mg1/monsters/tarbaby.qc b/quakec_mg1/monsters/tarbaby.qc new file mode 100644 index 0000000..0cfa417 --- /dev/null +++ b/quakec_mg1/monsters/tarbaby.qc @@ -0,0 +1,234 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BLOB + +============================================================================== +*/ + +$cd id1/models/tarbaby +$origin 0 0 24 +$base base + +$skin skin + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 +$frame walk20 walk21 walk22 walk23 walk24 walk25 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 run13 +$frame run14 run15 run16 run17 run18 run19 run20 run21 run22 run23 +$frame run24 run25 + +$frame jump1 jump2 jump3 jump4 jump5 jump6 + +$frame fly1 fly2 fly3 fly4 + +$frame exp + +void() tbaby_stand1 =[ $walk1, tbaby_stand1 ] {ai_stand();}; + +void() tbaby_hang1 =[ $walk1, tbaby_hang1 ] {ai_stand();}; + +void() tbaby_walk1 =[ $walk1, tbaby_walk2 ] {ai_turn();}; +void() tbaby_walk2 =[ $walk2, tbaby_walk3 ] {ai_turn();}; +void() tbaby_walk3 =[ $walk3, tbaby_walk4 ] {ai_turn();}; +void() tbaby_walk4 =[ $walk4, tbaby_walk5 ] {ai_turn();}; +void() tbaby_walk5 =[ $walk5, tbaby_walk6 ] {ai_turn();}; +void() tbaby_walk6 =[ $walk6, tbaby_walk7 ] {ai_turn();}; +void() tbaby_walk7 =[ $walk7, tbaby_walk8 ] {ai_turn();}; +void() tbaby_walk8 =[ $walk8, tbaby_walk9 ] {ai_turn();}; +void() tbaby_walk9 =[ $walk9, tbaby_walk10 ] {ai_turn();}; +void() tbaby_walk10 =[ $walk10, tbaby_walk11 ] {ai_turn();}; +void() tbaby_walk11 =[ $walk11, tbaby_walk12 ] {ai_walk(2);}; +void() tbaby_walk12 =[ $walk12, tbaby_walk13 ] {ai_walk(2);}; +void() tbaby_walk13 =[ $walk13, tbaby_walk14 ] {ai_walk(2);}; +void() tbaby_walk14 =[ $walk14, tbaby_walk15 ] {ai_walk(2);}; +void() tbaby_walk15 =[ $walk15, tbaby_walk16 ] {ai_walk(2);}; +void() tbaby_walk16 =[ $walk16, tbaby_walk17 ] {ai_walk(2);}; +void() tbaby_walk17 =[ $walk17, tbaby_walk18 ] {ai_walk(2);}; +void() tbaby_walk18 =[ $walk18, tbaby_walk19 ] {ai_walk(2);}; +void() tbaby_walk19 =[ $walk19, tbaby_walk20 ] {ai_walk(2);}; +void() tbaby_walk20 =[ $walk20, tbaby_walk21 ] {ai_walk(2);}; +void() tbaby_walk21 =[ $walk21, tbaby_walk22 ] {ai_walk(2);}; +void() tbaby_walk22 =[ $walk22, tbaby_walk23 ] {ai_walk(2);}; +void() tbaby_walk23 =[ $walk23, tbaby_walk24 ] {ai_walk(2);}; +void() tbaby_walk24 =[ $walk24, tbaby_walk25 ] {ai_walk(2);}; +void() tbaby_walk25 =[ $walk25, tbaby_walk1 ] {ai_walk(2);}; + +void() tbaby_run1 =[ $run1, tbaby_run2 ] {ai_face();}; +void() tbaby_run2 =[ $run2, tbaby_run3 ] {ai_face();}; +void() tbaby_run3 =[ $run3, tbaby_run4 ] {ai_face();}; +void() tbaby_run4 =[ $run4, tbaby_run5 ] {ai_face();}; +void() tbaby_run5 =[ $run5, tbaby_run6 ] {ai_face();}; +void() tbaby_run6 =[ $run6, tbaby_run7 ] {ai_face();}; +void() tbaby_run7 =[ $run7, tbaby_run8 ] {ai_face();}; +void() tbaby_run8 =[ $run8, tbaby_run9 ] {ai_face();}; +void() tbaby_run9 =[ $run9, tbaby_run10 ] {ai_face();}; +void() tbaby_run10 =[ $run10, tbaby_run11 ] {ai_face();}; +void() tbaby_run11 =[ $run11, tbaby_run12 ] {ai_run(2);}; +void() tbaby_run12 =[ $run12, tbaby_run13 ] {ai_run(2);}; +void() tbaby_run13 =[ $run13, tbaby_run14 ] {ai_run(2);}; +void() tbaby_run14 =[ $run14, tbaby_run15 ] {ai_run(2);}; +void() tbaby_run15 =[ $run15, tbaby_run16 ] {ai_run(2);}; +void() tbaby_run16 =[ $run16, tbaby_run17 ] {ai_run(2);}; +void() tbaby_run17 =[ $run17, tbaby_run18 ] {ai_run(2);}; +void() tbaby_run18 =[ $run18, tbaby_run19 ] {ai_run(2);}; +void() tbaby_run19 =[ $run19, tbaby_run20 ] {ai_run(2);}; +void() tbaby_run20 =[ $run20, tbaby_run21 ] {ai_run(2);}; +void() tbaby_run21 =[ $run21, tbaby_run22 ] {ai_run(2);}; +void() tbaby_run22 =[ $run22, tbaby_run23 ] {ai_run(2);}; +void() tbaby_run23 =[ $run23, tbaby_run24 ] {ai_run(2);}; +void() tbaby_run24 =[ $run24, tbaby_run25 ] {ai_run(2);}; +void() tbaby_run25 =[ $run25, tbaby_run1 ] {ai_run(2);}; + + +//============================================================================ + + +void() tbaby_jump1; + +void() Tar_JumpTouch = +{ + local float ldmg; + + if (other.takedamage && other.classname != self.classname) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + sound (self, CHAN_WEAPON, "blob/hit1.wav", 1, ATTN_NORM); + } + } + else + sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM); + + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = tbaby_run1; + self.movetype = MOVETYPE_STEP; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = tbaby_jump1; + self.nextthink = time + 0.1; +}; + +void() tbaby_jump5; + +void() tbaby_fly1 =[ $fly1, tbaby_fly2 ] { RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; }; +void() tbaby_fly2 =[ $fly2, tbaby_fly3 ] { RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; }; +void() tbaby_fly3 =[ $fly3, tbaby_fly4 ] { RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; }; +void() tbaby_fly4 =[ $fly4, tbaby_fly1 ] { + RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; +self.cnt = self.cnt + 1; +if (self.cnt == 4) +{ +//dprint ("spawn hop\n"); +tbaby_jump5 (); +} +}; + +void() tbaby_jump1 =[ $jump1, tbaby_jump2 ] { RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; ai_face(); }; +void() tbaby_jump2 =[ $jump2, tbaby_jump3 ] { RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; ai_face(); }; +void() tbaby_jump3 =[ $jump3, tbaby_jump4 ] { RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; ai_face(); }; +void() tbaby_jump4 =[ $jump4, tbaby_jump5 ] { RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; ai_face(); }; +void() tbaby_jump5 =[ $jump5, tbaby_jump6 ] +{ + RETURN_IF_DIED_FROM_CONTENTS_DAMAGE; + self.movetype = MOVETYPE_BOUNCE; + self.touch = Tar_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 200'; + self.velocity_z = self.velocity_z + random()*150; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; + self.cnt = 0; +}; +void() tbaby_jump6 =[ $jump6,tbaby_fly1 ] {}; + + + +//============================================================================= + +void() tbaby_die1 =[ $exp, tbaby_die2 ] { +self.takedamage = DAMAGE_NO; +}; +void() tbaby_die2 =[ $exp, tbaby_run1 ] +{ + T_RadiusDamage (self, self, 120, world); + + sound (self, CHAN_VOICE, "blob/death1.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TAREXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +//============================================================================= + + +/*QUAKED monster_tarbaby (1 0 0) (-16 -16 -24) (16 16 24) Ambush +*/ +void() monster_tarbaby = +{ + precache_model2 ("progs/tarbaby.mdl"); + + precache_sound2 ("blob/death1.wav"); + precache_sound2 ("blob/hit1.wav"); + precache_sound2 ("blob/land1.wav"); + precache_sound2 ("blob/sight1.wav"); + + self.health = 80; + + self.th_stand = tbaby_stand1; + self.th_walk = tbaby_walk1; + self.th_run = tbaby_run1; + self.th_missile = tbaby_jump1; + self.th_melee = tbaby_jump1; + self.th_die = tbaby_die1; + self.allowPathFind = TRUE; + self.combat_style = CS_MELEE; + + InitMonster("progs/tarbaby.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_SMALL); +}; + diff --git a/quakec_mg1/monsters/wizard.qc b/quakec_mg1/monsters/wizard.qc new file mode 100644 index 0000000..437c793 --- /dev/null +++ b/quakec_mg1/monsters/wizard.qc @@ -0,0 +1,427 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +WIZARD + +============================================================================== +*/ + +$cd id1/models/a_wizard +$origin 0 0 24 +$base wizbase +$skin wizbase + +$frame hover1 hover2 hover3 hover4 hover5 hover6 hover7 hover8 +$frame hover9 hover10 hover11 hover12 hover13 hover14 hover15 + +$frame fly1 fly2 fly3 fly4 fly5 fly6 fly7 fly8 fly9 fly10 +$frame fly11 fly12 fly13 fly14 + +$frame magatt1 magatt2 magatt3 magatt4 magatt5 magatt6 magatt7 +$frame magatt8 magatt9 magatt10 magatt11 magatt12 magatt13 + +$frame pain1 pain2 pain3 pain4 + +$frame death1 death2 death3 death4 death5 death6 death7 CORPSEFRAME_WIZARD_1 + + +/* +============================================================================== + +WIZARD + +If the player moves behind cover before the missile is launched, launch it +at the last visible spot with no velocity leading, in hopes that the player +will duck back out and catch it. +============================================================================== +*/ + +/* +============= +LaunchMissile + +Sets the given entities velocity and angles so that it will hit self.enemy +if self.enemy maintains it's current velocity +0.1 is moderately accurate, 0.0 is totally accurate +============= +*/ +void(entity missile, float mspeed, float accuracy) LaunchMissile = +{ + local vector vec, move; + local float fly; + + makevectors (self.angles); + +// set missile speed + vec = self.enemy.origin + self.enemy.mins + self.enemy.size * 0.7 - missile.origin; + +// calc aproximate time for missile to reach vec + fly = vlen (vec) / mspeed; + +// get the entities xy velocity + move = self.enemy.velocity; + move_z = 0; + +// project the target forward in time + vec = vec + move * fly; + + vec = normalize(vec); + vec = vec + accuracy*v_up*(random()- 0.5) + accuracy*v_right*(random()- 0.5); + + missile.velocity = vec * mspeed; + + missile.angles = '0 0 0'; + missile.angles_y = vectoyaw(missile.velocity); + +// set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; +}; + + +void() wiz_run1; +void() wiz_side1; + +/* +================= +WizardCheckAttack +================= +*/ +float() WizardCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (time < self.attack_finished) + return FALSE; + if (!enemy_visible) + return FALSE; + + if (enemy_range == RANGE_FAR) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != targ) + { // don't have a clear shot, so move to a side + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.6; + else if (enemy_range == RANGE_MID) + chance = 0.2; + else + chance = 0; + + if (random () < chance) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + if (enemy_range == RANGE_MID) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + } + else + { + if (self.attack_state != AS_SLIDING) + { + self.attack_state = AS_SLIDING; + wiz_side1 (); + } + } + + return FALSE; +}; + +/* +================= +WizardAttackFinished +================= +*/ +void() WizardAttackFinished = +{ + if (enemy_range >= RANGE_MID || !enemy_visible) + { + self.attack_state = AS_STRAIGHT; + self.think = wiz_run1; + } + else + { + self.attack_state = AS_SLIDING; + self.think = wiz_side1; + } +}; + +/* +============================================================================== + +FAST ATTACKS + +============================================================================== +*/ + +void() Wiz_FastFire = +{ + local vector vec; + local vector dst; + + if (self.owner.health > 0) + { + self.owner.effects = self.owner.effects | EF_MUZZLEFLASH; + + makevectors (self.enemy.angles); + dst = self.enemy.origin - 13*self.movedir; + + vec = normalize(dst - self.origin); + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + launch_spike (self.origin, vec); + newmis.velocity = vec*600; + newmis.owner = self.owner; + newmis.classname = "wizspike"; + setmodel (newmis, "progs/w_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + } + + remove (self); +}; + + +void() Wiz_StartFast = +{ + local entity missile; + + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + self.v_angle = self.angles; + makevectors (self.angles); + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 0.6; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right*14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.8; + missile.think = Wiz_FastFire; + missile.movedir = v_right; + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 1; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right* -14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.3; + missile.think = Wiz_FastFire; + missile.movedir = VEC_ORIGIN - v_right; +}; + + + +void() Wiz_idlesound = +{ +local float wr; + wr = random() * 5; + + if (self.waitmin < time) + { + self.waitmin = time + 2; + if (wr > 4.5) + sound (self, CHAN_VOICE, "wizard/widle1.wav", 1, ATTN_IDLE); + if (wr < 1.5) + sound (self, CHAN_VOICE, "wizard/widle2.wav", 1, ATTN_IDLE); + } + return; +}; + +void() wiz_stand1 =[ $hover1, wiz_stand2 ] {ai_stand();}; +void() wiz_stand2 =[ $hover2, wiz_stand3 ] {ai_stand();}; +void() wiz_stand3 =[ $hover3, wiz_stand4 ] {ai_stand();}; +void() wiz_stand4 =[ $hover4, wiz_stand5 ] {ai_stand();}; +void() wiz_stand5 =[ $hover5, wiz_stand6 ] {ai_stand();}; +void() wiz_stand6 =[ $hover6, wiz_stand7 ] {ai_stand();}; +void() wiz_stand7 =[ $hover7, wiz_stand8 ] {ai_stand();}; +void() wiz_stand8 =[ $hover8, wiz_stand1 ] {ai_stand();}; + +void() wiz_walk1 =[ $hover1, wiz_walk2 ] {ai_walk(8); +Wiz_idlesound();}; +void() wiz_walk2 =[ $hover2, wiz_walk3 ] {ai_walk(8);}; +void() wiz_walk3 =[ $hover3, wiz_walk4 ] {ai_walk(8);}; +void() wiz_walk4 =[ $hover4, wiz_walk5 ] {ai_walk(8);}; +void() wiz_walk5 =[ $hover5, wiz_walk6 ] {ai_walk(8);}; +void() wiz_walk6 =[ $hover6, wiz_walk7 ] {ai_walk(8);}; +void() wiz_walk7 =[ $hover7, wiz_walk8 ] {ai_walk(8);}; +void() wiz_walk8 =[ $hover8, wiz_walk1 ] {ai_walk(8);}; + +void() wiz_side1 =[ $hover1, wiz_side2 ] {ai_run(8); +Wiz_idlesound();}; +void() wiz_side2 =[ $hover2, wiz_side3 ] {ai_run(8);}; +void() wiz_side3 =[ $hover3, wiz_side4 ] {ai_run(8);}; +void() wiz_side4 =[ $hover4, wiz_side5 ] {ai_run(8);}; +void() wiz_side5 =[ $hover5, wiz_side6 ] {ai_run(8);}; +void() wiz_side6 =[ $hover6, wiz_side7 ] {ai_run(8);}; +void() wiz_side7 =[ $hover7, wiz_side8 ] {ai_run(8);}; +void() wiz_side8 =[ $hover8, wiz_side1 ] {ai_run(8);}; + +void() wiz_run1 =[ $fly1, wiz_run2 ] {ai_run(16); +Wiz_idlesound(); +}; +void() wiz_run2 =[ $fly2, wiz_run3 ] {ai_run(16);}; +void() wiz_run3 =[ $fly3, wiz_run4 ] {ai_run(16);}; +void() wiz_run4 =[ $fly4, wiz_run5 ] {ai_run(16);}; +void() wiz_run5 =[ $fly5, wiz_run6 ] {ai_run(16);}; +void() wiz_run6 =[ $fly6, wiz_run7 ] {ai_run(16);}; +void() wiz_run7 =[ $fly7, wiz_run8 ] {ai_run(16);}; +void() wiz_run8 =[ $fly8, wiz_run9 ] {ai_run(16);}; +void() wiz_run9 =[ $fly9, wiz_run10 ] {ai_run(16);}; +void() wiz_run10 =[ $fly10, wiz_run11 ] {ai_run(16);}; +void() wiz_run11 =[ $fly11, wiz_run12 ] {ai_run(16);}; +void() wiz_run12 =[ $fly12, wiz_run13 ] {ai_run(16);}; +void() wiz_run13 =[ $fly13, wiz_run14 ] {ai_run(16);}; +void() wiz_run14 =[ $fly14, wiz_run1 ] {ai_run(16);}; + +void() wiz_fast1 =[ $magatt1, wiz_fast2 ] {ai_face();Wiz_StartFast();}; +void() wiz_fast2 =[ $magatt2, wiz_fast3 ] {ai_face();}; +void() wiz_fast3 =[ $magatt3, wiz_fast4 ] {ai_face();}; +void() wiz_fast4 =[ $magatt4, wiz_fast5 ] {ai_face();}; +void() wiz_fast5 =[ $magatt5, wiz_fast6 ] {ai_face();}; +void() wiz_fast6 =[ $magatt6, wiz_fast7 ] {ai_face();}; +void() wiz_fast7 =[ $magatt5, wiz_fast8 ] {ai_face();}; +void() wiz_fast8 =[ $magatt4, wiz_fast9 ] {ai_face();}; +void() wiz_fast9 =[ $magatt3, wiz_fast10 ] {ai_face();}; +void() wiz_fast10 =[ $magatt2, wiz_run1 ] {ai_face();SUB_AttackFinished(2);WizardAttackFinished ();}; + +void() wiz_pain1 =[ $pain1, wiz_pain2 ] {}; +void() wiz_pain2 =[ $pain2, wiz_pain3 ] {}; +void() wiz_pain3 =[ $pain3, wiz_pain4 ] {}; +void() wiz_pain4 =[ $pain4, wiz_run1 ] {}; + +void() wiz_death1 =[ $death1, wiz_death2 ] { + +self.velocity_x = -200 + 400*random(); +self.velocity_y = -200 + 400*random(); +self.velocity_z = 100 + 100*random(); +self.flags = self.flags - (self.flags & FL_ONGROUND); +sound (self, CHAN_VOICE, "wizard/wdeath.wav", 1, ATTN_NORM); +}; +void() wiz_death2 =[ $death2, wiz_death3 ] {}; +void() wiz_death3 =[ $death3, wiz_death4 ]{self.solid = SOLID_NOT;}; +void() wiz_death4 =[ $death4, wiz_death5 ] {}; +void() wiz_death5 =[ $death5, wiz_death6 ] {}; +void() wiz_death6 =[ $death6, wiz_death7 ] {}; +void() wiz_death7 =[ $death7, wiz_death8 ] {}; +void() wiz_death8 =[ $CORPSEFRAME_WIZARD_1, wiz_death8 ] +{ + if (horde_ent) + { + self.think = MonsterFade; + self.nextthink = time + FADETIME; + } +}; + +void() wiz_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_wizard.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + return; + } + + wiz_death1 (); +}; + + +void(entity attacker, float damage) Wiz_Pain = +{ + sound (self, CHAN_VOICE, "wizard/wpain.wav", 1, ATTN_NORM); + if (random()*70 > damage) + return; // didn't flinch + + wiz_pain1 (); +}; + + +void() Wiz_Missile = +{ + wiz_fast1(); +}; + +/*QUAKED monster_wizard (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_wizard = +{ + precache_model ("progs/wizard.mdl"); + precache_model ("progs/h_wizard.mdl"); + precache_model ("progs/w_spike.mdl"); + + precache_sound ("wizard/hit.wav"); // used by c code + precache_sound ("wizard/wattack.wav"); + precache_sound ("wizard/wdeath.wav"); + precache_sound ("wizard/widle1.wav"); + precache_sound ("wizard/widle2.wav"); + precache_sound ("wizard/wpain.wav"); + precache_sound ("wizard/wsight.wav"); + + self.health = 80; + + self.th_stand = wiz_stand1; + self.th_walk = wiz_walk1; + self.th_run = wiz_run1; + self.th_missile = Wiz_Missile; + self.th_pain = Wiz_Pain; + self.th_die = wiz_die; + self.combat_style = CS_RANGED; + + InitMonster("progs/wizard.mdl", MONSTER_TYPE_FLY, MONSTER_SIZE_SMALL); +}; diff --git a/quakec_mg1/monsters/zombie.qc b/quakec_mg1/monsters/zombie.qc new file mode 100644 index 0000000..5f364c4 --- /dev/null +++ b/quakec_mg1/monsters/zombie.qc @@ -0,0 +1,524 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +ZOMBIE + +============================================================================== +*/ +$cd id1/models/zombie + +$origin 0 0 24 + +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 +$frame stand9 stand10 stand11 stand12 stand13 stand14 stand15 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 walk11 +$frame walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 +$frame run13 run14 run15 run16 run17 run18 + +$frame atta1 atta2 atta3 atta4 atta5 atta6 atta7 atta8 atta9 atta10 atta11 +$frame atta12 atta13 + +$frame attb1 attb2 attb3 attb4 attb5 attb6 attb7 attb8 attb9 attb10 attb11 +$frame attb12 attb13 attb14 + +$frame attc1 attc2 attc3 attc4 attc5 attc6 attc7 attc8 attc9 attc10 attc11 +$frame attc12 + +$frame paina1 paina2 paina3 paina4 paina5 paina6 paina7 paina8 paina9 paina10 +$frame paina11 paina12 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 painb17 painb18 painb19 +$frame painb20 painb21 painb22 painb23 painb24 painb25 painb26 painb27 painb28 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 painc14 painc15 painc16 painc17 painc18 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 paine16 paine17 paine18 paine19 +$frame paine20 paine21 paine22 paine23 paine24 paine25 paine26 paine27 paine28 +$frame paine29 paine30 + +$frame cruc_1 cruc_2 cruc_3 cruc_4 cruc_5 cruc_6 + +float SPAWN_CRUCIFIED = 1; + +//============================================================================= + +.float inpain; + +void() zombie_stand1 =[ $stand1, zombie_stand2 ] {ai_stand();}; +void() zombie_stand2 =[ $stand2, zombie_stand3 ] {ai_stand();}; +void() zombie_stand3 =[ $stand3, zombie_stand4 ] {ai_stand();}; +void() zombie_stand4 =[ $stand4, zombie_stand5 ] {ai_stand();}; +void() zombie_stand5 =[ $stand5, zombie_stand6 ] {ai_stand();}; +void() zombie_stand6 =[ $stand6, zombie_stand7 ] {ai_stand();}; +void() zombie_stand7 =[ $stand7, zombie_stand8 ] {ai_stand();}; +void() zombie_stand8 =[ $stand8, zombie_stand9 ] {ai_stand();}; +void() zombie_stand9 =[ $stand9, zombie_stand10 ] {ai_stand();}; +void() zombie_stand10 =[ $stand10, zombie_stand11 ] {ai_stand();}; +void() zombie_stand11 =[ $stand11, zombie_stand12 ] {ai_stand();}; +void() zombie_stand12 =[ $stand12, zombie_stand13 ] {ai_stand();}; +void() zombie_stand13 =[ $stand13, zombie_stand14 ] {ai_stand();}; +void() zombie_stand14 =[ $stand14, zombie_stand15 ] {ai_stand();}; +void() zombie_stand15 =[ $stand15, zombie_stand1 ] {ai_stand();}; + +void() zombie_cruc1 = [ $cruc_1, zombie_cruc2 ] { +if (random() < 0.1) + sound (self, CHAN_VOICE, "zombie/idle_w2.wav", 1, ATTN_STATIC);}; +void() zombie_cruc2 = [ $cruc_2, zombie_cruc3 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc3 = [ $cruc_3, zombie_cruc4 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc4 = [ $cruc_4, zombie_cruc5 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc5 = [ $cruc_5, zombie_cruc6 ] {self.nextthink = time + 0.1 + random()*0.1;}; +void() zombie_cruc6 = [ $cruc_6, zombie_cruc1 ] {self.nextthink = time + 0.1 + random()*0.1;}; + +void() zombie_walk1 =[ $walk1, zombie_walk2 ] {ai_walk(0);}; +void() zombie_walk2 =[ $walk2, zombie_walk3 ] {ai_walk(2);}; +void() zombie_walk3 =[ $walk3, zombie_walk4 ] {ai_walk(3);}; +void() zombie_walk4 =[ $walk4, zombie_walk5 ] {ai_walk(2);}; +void() zombie_walk5 =[ $walk5, zombie_walk6 ] {ai_walk(1);}; +void() zombie_walk6 =[ $walk6, zombie_walk7 ] {ai_walk(0);}; +void() zombie_walk7 =[ $walk7, zombie_walk8 ] {ai_walk(0);}; +void() zombie_walk8 =[ $walk8, zombie_walk9 ] {ai_walk(0);}; +void() zombie_walk9 =[ $walk9, zombie_walk10 ] {ai_walk(0);}; +void() zombie_walk10 =[ $walk10, zombie_walk11 ] {ai_walk(0);}; +void() zombie_walk11 =[ $walk11, zombie_walk12 ] {ai_walk(2);}; +void() zombie_walk12 =[ $walk12, zombie_walk13 ] {ai_walk(2);}; +void() zombie_walk13 =[ $walk13, zombie_walk14 ] {ai_walk(1);}; +void() zombie_walk14 =[ $walk14, zombie_walk15 ] {ai_walk(0);}; +void() zombie_walk15 =[ $walk15, zombie_walk16 ] {ai_walk(0);}; +void() zombie_walk16 =[ $walk16, zombie_walk17 ] {ai_walk(0);}; +void() zombie_walk17 =[ $walk17, zombie_walk18 ] {ai_walk(0);}; +void() zombie_walk18 =[ $walk18, zombie_walk19 ] {ai_walk(0);}; +void() zombie_walk19 =[ $walk19, zombie_walk1 ] { +ai_walk(0); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE);}; + +void() zombie_run1 =[ $run1, zombie_run2 ] {ai_run(1);self.inpain = 0;}; +void() zombie_run2 =[ $run2, zombie_run3 ] {ai_run(1);}; +void() zombie_run3 =[ $run3, zombie_run4 ] {ai_run(0);}; +void() zombie_run4 =[ $run4, zombie_run5 ] {ai_run(1);}; +void() zombie_run5 =[ $run5, zombie_run6 ] {ai_run(2);}; +void() zombie_run6 =[ $run6, zombie_run7 ] {ai_run(3);}; +void() zombie_run7 =[ $run7, zombie_run8 ] {ai_run(4);}; +void() zombie_run8 =[ $run8, zombie_run9 ] {ai_run(4);}; +void() zombie_run9 =[ $run9, zombie_run10 ] {ai_run(2);}; +void() zombie_run10 =[ $run10, zombie_run11 ] {ai_run(0);}; +void() zombie_run11 =[ $run11, zombie_run12 ] {ai_run(0);}; +void() zombie_run12 =[ $run12, zombie_run13 ] {ai_run(0);}; +void() zombie_run13 =[ $run13, zombie_run14 ] {ai_run(2);}; +void() zombie_run14 =[ $run14, zombie_run15 ] {ai_run(4);}; +void() zombie_run15 =[ $run15, zombie_run16 ] {ai_run(6);}; +void() zombie_run16 =[ $run16, zombie_run17 ] {ai_run(7);}; +void() zombie_run17 =[ $run17, zombie_run18 ] {ai_run(3);}; +void() zombie_run18 =[ $run18, zombie_run1 ] { +ai_run(8); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); +if (random() > 0.8) + sound (self, CHAN_VOICE, "zombie/z_idle1.wav", 1, ATTN_IDLE); +}; + +/* +============================================================================= + +ATTACKS + +============================================================================= +*/ + +void() ZombieGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage) + { + T_Damage (other, self, self.owner, 10 ); + sound (self, CHAN_WEAPON, "zombie/z_hit.wav", 1, ATTN_NORM); + remove (self); + return; + } + sound (self, CHAN_WEAPON, "zombie/z_miss.wav", 1, ATTN_NORM); // bounce sound + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.touch = SUB_Remove; +}; + +/* +================ +ZombieFireGrenade +================ +*/ +void(vector st) ZombieFireGrenade = +{ + local entity missile; + local vector org; + + sound (self, CHAN_WEAPON, "zombie/z_shot1.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + +// calc org + org = self.origin + st_x * v_forward + st_y * v_right + (st_z - 24) * v_up; + +// set missile speed + + makevectors (self.angles); + + missile.velocity = normalize(self.enemy.origin - org); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + + missile.avelocity = '3000 1000 2000'; + + missile.touch = ZombieGrenadeTouch; + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + + +void() zombie_atta1 =[ $atta1, zombie_atta2 ] {ai_face();}; +void() zombie_atta2 =[ $atta2, zombie_atta3 ] {ai_face();}; +void() zombie_atta3 =[ $atta3, zombie_atta4 ] {ai_face();}; +void() zombie_atta4 =[ $atta4, zombie_atta5 ] {ai_face();}; +void() zombie_atta5 =[ $atta5, zombie_atta6 ] {ai_face();}; +void() zombie_atta6 =[ $atta6, zombie_atta7 ] {ai_face();}; +void() zombie_atta7 =[ $atta7, zombie_atta8 ] {ai_face();}; +void() zombie_atta8 =[ $atta8, zombie_atta9 ] {ai_face();}; +void() zombie_atta9 =[ $atta9, zombie_atta10 ] {ai_face();}; +void() zombie_atta10 =[ $atta10, zombie_atta11 ] {ai_face();}; +void() zombie_atta11 =[ $atta11, zombie_atta12 ] {ai_face();}; +void() zombie_atta12 =[ $atta12, zombie_atta13 ] {ai_face();}; +void() zombie_atta13 =[ $atta13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -22 30');}; + +void() zombie_attb1 =[ $attb1, zombie_attb2 ] {ai_face();}; +void() zombie_attb2 =[ $attb2, zombie_attb3 ] {ai_face();}; +void() zombie_attb3 =[ $attb3, zombie_attb4 ] {ai_face();}; +void() zombie_attb4 =[ $attb4, zombie_attb5 ] {ai_face();}; +void() zombie_attb5 =[ $attb5, zombie_attb6 ] {ai_face();}; +void() zombie_attb6 =[ $attb6, zombie_attb7 ] {ai_face();}; +void() zombie_attb7 =[ $attb7, zombie_attb8 ] {ai_face();}; +void() zombie_attb8 =[ $attb8, zombie_attb9 ] {ai_face();}; +void() zombie_attb9 =[ $attb9, zombie_attb10 ] {ai_face();}; +void() zombie_attb10 =[ $attb10, zombie_attb11 ] {ai_face();}; +void() zombie_attb11 =[ $attb11, zombie_attb12 ] {ai_face();}; +void() zombie_attb12 =[ $attb12, zombie_attb13 ] {ai_face();}; +void() zombie_attb13 =[ $attb13, zombie_attb14 ] {ai_face();}; +void() zombie_attb14 =[ $attb13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -24 29');}; + +void() zombie_attc1 =[ $attc1, zombie_attc2 ] {ai_face();}; +void() zombie_attc2 =[ $attc2, zombie_attc3 ] {ai_face();}; +void() zombie_attc3 =[ $attc3, zombie_attc4 ] {ai_face();}; +void() zombie_attc4 =[ $attc4, zombie_attc5 ] {ai_face();}; +void() zombie_attc5 =[ $attc5, zombie_attc6 ] {ai_face();}; +void() zombie_attc6 =[ $attc6, zombie_attc7 ] {ai_face();}; +void() zombie_attc7 =[ $attc7, zombie_attc8 ] {ai_face();}; +void() zombie_attc8 =[ $attc8, zombie_attc9 ] {ai_face();}; +void() zombie_attc9 =[ $attc9, zombie_attc10 ] {ai_face();}; +void() zombie_attc10 =[ $attc10, zombie_attc11 ] {ai_face();}; +void() zombie_attc11 =[ $attc11, zombie_attc12 ] {ai_face();}; +void() zombie_attc12 =[ $attc12, zombie_run1 ] {ai_face();ZombieFireGrenade('-12 -19 29');}; + +void() zombie_missile = +{ + local float r; + + r = random(); + + if (r < 0.3) + zombie_atta1 (); + else if (r < 0.6) + zombie_attb1 (); + else + zombie_attc1 (); +}; + + +/* +============================================================================= + +PAIN + +============================================================================= +*/ + +void() zombie_paina1 =[ $paina1, zombie_paina2 ] {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() zombie_paina2 =[ $paina2, zombie_paina3 ] {ai_painforward(3);}; +void() zombie_paina3 =[ $paina3, zombie_paina4 ] {ai_painforward(1);}; +void() zombie_paina4 =[ $paina4, zombie_paina5 ] {ai_pain(1);}; +void() zombie_paina5 =[ $paina5, zombie_paina6 ] {ai_pain(3);}; +void() zombie_paina6 =[ $paina6, zombie_paina7 ] {ai_pain(1);}; +void() zombie_paina7 =[ $paina7, zombie_paina8 ] {}; +void() zombie_paina8 =[ $paina8, zombie_paina9 ] {}; +void() zombie_paina9 =[ $paina9, zombie_paina10 ] {}; +void() zombie_paina10 =[ $paina10, zombie_paina11 ] {}; +void() zombie_paina11 =[ $paina11, zombie_paina12 ] {}; +void() zombie_paina12 =[ $paina12, zombie_run1 ] {}; + +void() zombie_painb1 =[ $painb1, zombie_painb2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painb2 =[ $painb2, zombie_painb3 ] {ai_pain(2);}; +void() zombie_painb3 =[ $painb3, zombie_painb4 ] {ai_pain(8);}; +void() zombie_painb4 =[ $painb4, zombie_painb5 ] {ai_pain(6);}; +void() zombie_painb5 =[ $painb5, zombie_painb6 ] {ai_pain(2);}; +void() zombie_painb6 =[ $painb6, zombie_painb7 ] {}; +void() zombie_painb7 =[ $painb7, zombie_painb8 ] {}; +void() zombie_painb8 =[ $painb8, zombie_painb9 ] {}; +void() zombie_painb9 =[ $painb9, zombie_painb10 ] {sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM);}; +void() zombie_painb10 =[ $painb10, zombie_painb11 ] {}; +void() zombie_painb11 =[ $painb11, zombie_painb12 ] {}; +void() zombie_painb12 =[ $painb12, zombie_painb13 ] {}; +void() zombie_painb13 =[ $painb13, zombie_painb14 ] {}; +void() zombie_painb14 =[ $painb14, zombie_painb15 ] {}; +void() zombie_painb15 =[ $painb15, zombie_painb16 ] {}; +void() zombie_painb16 =[ $painb16, zombie_painb17 ] {}; +void() zombie_painb17 =[ $painb17, zombie_painb18 ] {}; +void() zombie_painb18 =[ $painb18, zombie_painb19 ] {}; +void() zombie_painb19 =[ $painb19, zombie_painb20 ] {}; +void() zombie_painb20 =[ $painb20, zombie_painb21 ] {}; +void() zombie_painb21 =[ $painb21, zombie_painb22 ] {}; +void() zombie_painb22 =[ $painb22, zombie_painb23 ] {}; +void() zombie_painb23 =[ $painb23, zombie_painb24 ] {}; +void() zombie_painb24 =[ $painb24, zombie_painb25 ] {}; +void() zombie_painb25 =[ $painb25, zombie_painb26 ] {ai_painforward(1);}; +void() zombie_painb26 =[ $painb26, zombie_painb27 ] {}; +void() zombie_painb27 =[ $painb27, zombie_painb28 ] {}; +void() zombie_painb28 =[ $painb28, zombie_run1 ] {}; + +void() zombie_painc1 =[ $painc1, zombie_painc2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painc2 =[ $painc2, zombie_painc3 ] {}; +void() zombie_painc3 =[ $painc3, zombie_painc4 ] {ai_pain(3);}; +void() zombie_painc4 =[ $painc4, zombie_painc5 ] {ai_pain(1);}; +void() zombie_painc5 =[ $painc5, zombie_painc6 ] {}; +void() zombie_painc6 =[ $painc6, zombie_painc7 ] {}; +void() zombie_painc7 =[ $painc7, zombie_painc8 ] {}; +void() zombie_painc8 =[ $painc8, zombie_painc9 ] {}; +void() zombie_painc9 =[ $painc9, zombie_painc10 ] {}; +void() zombie_painc10 =[ $painc10, zombie_painc11 ] {}; +void() zombie_painc11 =[ $painc11, zombie_painc12 ] {ai_painforward(1);}; +void() zombie_painc12 =[ $painc12, zombie_painc13 ] {ai_painforward(1);}; +void() zombie_painc13 =[ $painc13, zombie_painc14 ] {}; +void() zombie_painc14 =[ $painc14, zombie_painc15 ] {}; +void() zombie_painc15 =[ $painc15, zombie_painc16 ] {}; +void() zombie_painc16 =[ $painc16, zombie_painc17 ] {}; +void() zombie_painc17 =[ $painc17, zombie_painc18 ] {}; +void() zombie_painc18 =[ $painc18, zombie_run1 ] {}; + +void() zombie_paind1 =[ $paind1, zombie_paind2 ] {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() zombie_paind2 =[ $paind2, zombie_paind3 ] {}; +void() zombie_paind3 =[ $paind3, zombie_paind4 ] {}; +void() zombie_paind4 =[ $paind4, zombie_paind5 ] {}; +void() zombie_paind5 =[ $paind5, zombie_paind6 ] {}; +void() zombie_paind6 =[ $paind6, zombie_paind7 ] {}; +void() zombie_paind7 =[ $paind7, zombie_paind8 ] {}; +void() zombie_paind8 =[ $paind8, zombie_paind9 ] {}; +void() zombie_paind9 =[ $paind9, zombie_paind10 ] {ai_pain(1);}; +void() zombie_paind10 =[ $paind10, zombie_paind11 ] {}; +void() zombie_paind11 =[ $paind11, zombie_paind12 ] {}; +void() zombie_paind12 =[ $paind12, zombie_paind13 ] {}; +void() zombie_paind13 =[ $paind13, zombie_run1 ] {}; + +void() zombie_paine1 =[ $paine1, zombie_paine2 ] { +sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM); +self.health = 60; +}; +void() zombie_paine2 =[ $paine2, zombie_paine3 ] {ai_pain(8);}; +void() zombie_paine3 =[ $paine3, zombie_paine4 ] {ai_pain(5);}; +void() zombie_paine4 =[ $paine4, zombie_paine5 ] {ai_pain(3);}; +void() zombie_paine5 =[ $paine5, zombie_paine6 ] {ai_pain(1);}; +void() zombie_paine6 =[ $paine6, zombie_paine7 ] {ai_pain(2);}; +void() zombie_paine7 =[ $paine7, zombie_paine8 ] {ai_pain(1);}; +void() zombie_paine8 =[ $paine8, zombie_paine9 ] {ai_pain(1);}; +void() zombie_paine9 =[ $paine9, zombie_paine10 ] {ai_pain(2);}; +void() zombie_paine10 =[ $paine10, zombie_paine11 ] { +sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM); +self.solid = SOLID_NOT; +}; +void() zombie_paine11 =[ $paine11, zombie_paine12 ] {self.nextthink = self.nextthink + 5;self.health = 60;}; +void() zombie_paine12 =[ $paine12, zombie_paine13 ]{ +// see if ok to stand up +self.health = 60; +sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); +self.solid = SOLID_SLIDEBOX; +if (!walkmove (0, 0)) +{ + self.think = zombie_paine11; + self.solid = SOLID_NOT; + return; +} +}; +void() zombie_paine13 =[ $paine13, zombie_paine14 ] {}; +void() zombie_paine14 =[ $paine14, zombie_paine15 ] {}; +void() zombie_paine15 =[ $paine15, zombie_paine16 ] {}; +void() zombie_paine16 =[ $paine16, zombie_paine17 ] {}; +void() zombie_paine17 =[ $paine17, zombie_paine18 ] {}; +void() zombie_paine18 =[ $paine18, zombie_paine19 ] {}; +void() zombie_paine19 =[ $paine19, zombie_paine20 ] {}; +void() zombie_paine20 =[ $paine20, zombie_paine21 ] {}; +void() zombie_paine21 =[ $paine21, zombie_paine22 ] {}; +void() zombie_paine22 =[ $paine22, zombie_paine23 ] {}; +void() zombie_paine23 =[ $paine23, zombie_paine24 ] {}; +void() zombie_paine24 =[ $paine24, zombie_paine25 ] {}; +void() zombie_paine25 =[ $paine25, zombie_paine26 ] {ai_painforward(5);}; +void() zombie_paine26 =[ $paine26, zombie_paine27 ] {ai_painforward(3);}; +void() zombie_paine27 =[ $paine27, zombie_paine28 ] {ai_painforward(1);}; +void() zombie_paine28 =[ $paine28, zombie_paine29 ] {ai_pain(1);}; +void() zombie_paine29 =[ $paine29, zombie_paine30 ] {}; +void() zombie_paine30 =[ $paine30, zombie_run1 ] {}; + +void() zombie_die = +{ + sound (self, CHAN_VOICE, "zombie/z_gib.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_zombie.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); +}; + +/* +================= +zombie_pain + +Zombies can only be killed (gibbed) by doing 60 hit points of damage +in a single frame (rockets, grenades, quad shotgun, quad nailgun). + +A hit of 25 points or more (super shotgun, quad nailgun) will allways put it +down to the ground. + +A hit of from 10 to 40 points in one frame will cause it to go down if it +has been twice in two seconds, otherwise it goes into one of the four +fast pain frames. + +A hit of less than 10 points of damage (winged by a shotgun) will be ignored. +================= +*/ +void(entity attacker, float take) zombie_pain = +{ + local float r; + + self.health = 60; // allways reset health + + if (take < 9) + return; // totally ignore + + if (self.inpain == 2) + return; // down on ground, so don't reset any counters + +// go down immediately if a big enough hit + if (take >= 25) + { + self.inpain = 2; + zombie_paine1 (); + return; + } + + if (self.inpain) + { +// if hit again in next gre seconds while not in pain frames, definately drop + self.pain_finished = time + 3; + return; // currently going through an animation, don't change + } + + if (self.pain_finished > time) + { +// hit again, so drop down + self.inpain = 2; + zombie_paine1 (); + return; + } + +// gp into one of the fast pain animations + self.inpain = 1; + + r = random(); + if (r < 0.25) + zombie_paina1 (); + else if (r < 0.5) + zombie_painb1 (); + else if (r < 0.75) + zombie_painc1 (); + else + zombie_paind1 (); +}; + +//============================================================================ + +/*QUAKED monster_zombie (1 0 0) (-16 -16 -24) (16 16 32) Crucified ambush + +If crucified, stick the bounding box 12 pixels back into a wall to look right. +*/ +void() monster_zombie = +{ + precache_model ("progs/zombie.mdl"); + precache_model ("progs/h_zombie.mdl"); + precache_model ("progs/zom_gib.mdl"); + + precache_sound ("zombie/z_idle.wav"); + precache_sound ("zombie/z_idle1.wav"); + precache_sound ("zombie/z_shot1.wav"); + precache_sound ("zombie/z_gib.wav"); + precache_sound ("zombie/z_pain.wav"); + precache_sound ("zombie/z_pain1.wav"); + precache_sound ("zombie/z_fall.wav"); + precache_sound ("zombie/z_miss.wav"); + precache_sound ("zombie/z_hit.wav"); + precache_sound ("zombie/idle_w2.wav"); + + self.health = 60; + + self.th_stand = zombie_stand1; + self.th_walk = zombie_walk1; + self.th_run = zombie_run1; + self.th_pain = zombie_pain; + self.th_die = zombie_die; + self.th_missile = zombie_missile; + + if ( self.spawnflags & SPAWN_CRUCIFIED ) { + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_NONE; + setmodel (self, "progs/zombie.mdl"); + setsize (self, '-16 -16 -24', '16 16 40'); + zombie_cruc1 (); + } else { + self.allowPathFind = TRUE; + self.combat_style = CS_RANGED; + InitMonster("progs/zombie.mdl", MONSTER_TYPE_WALK, MONSTER_SIZE_SMALL); + } +}; diff --git a/quakec_mg1/plats.qc b/quakec_mg1/plats.qc new file mode 100644 index 0000000..608b6a2 --- /dev/null +++ b/quakec_mg1/plats.qc @@ -0,0 +1,393 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + + +void() plat_center_touch; +void() plat_outside_touch; +void() plat_trigger_use; +void() plat_go_up; +void() plat_go_down; +void() plat_crush; +float PLAT_LOW_TRIGGER = 1; + +void() plat_spawn_inside_trigger = +{ + local entity trigger; + local vector tmin, tmax; + +// +// middle trigger +// + trigger = spawn(); + trigger.touch = plat_center_touch; + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.enemy = self; + + tmin = self.mins + '25 25 0'; + tmax = self.maxs - '25 25 -8'; + tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8); + if (self.spawnflags & PLAT_LOW_TRIGGER) + tmax_z = tmin_z + 8; + + if (self.size_x <= 50) + { + tmin_x = (self.mins_x + self.maxs_x) / 2; + tmax_x = tmin_x + 1; + } + if (self.size_y <= 50) + { + tmin_y = (self.mins_y + self.maxs_y) / 2; + tmax_y = tmin_y + 1; + } + + setsize (trigger, tmin, tmax); +}; + +void() plat_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + self.think = plat_go_down; + self.nextthink = self.ltime + 3; +}; + +void() plat_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() plat_go_down = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_DOWN; + SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom); +}; + +void() plat_go_up = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos1, self.speed, plat_hit_top); +}; + +void() plat_center_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + self = self.enemy; + if (self.state == STATE_BOTTOM) + plat_go_up (); + else if (self.state == STATE_TOP) + self.nextthink = self.ltime + 1; // delay going down +}; + +void() plat_outside_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + +//dprint ("plat_outside_touch\n"); + self = self.enemy; + if (self.state == STATE_TOP) + plat_go_down (); +}; + +void() plat_trigger_use = +{ + if (self.think) + return; // allready activated + plat_go_down(); +}; + + +void() plat_crush = +{ +//dprint ("plat_crush\n"); + + T_Damage (other, self, self, 1); + + if (self.state == STATE_UP) + plat_go_down (); + else if (self.state == STATE_DOWN) + plat_go_up (); + else + objerror ("plat_crush: bad self.state\n"); +}; + +void() plat_use = +{ + self.use = SUB_Null; + if (self.state != STATE_UP) + objerror ("plat_use: not in up state"); + plat_go_down(); +}; + + +/*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. + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height. +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + + +void() func_plat = + +{ + if (!self.t_length) + self.t_length = 80; + if (!self.t_width) + self.t_width = 10; + + if (self.sounds == 0) + self.sounds = 2; +// FIX THIS TO LOAD A GENERIC PLAT SOUND + + if (self.sounds == 1) + { + precache_sound ("plats/plat1.wav"); + precache_sound ("plats/plat2.wav"); + self.noise = "plats/plat1.wav"; + self.noise1 = "plats/plat2.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("plats/medplat1.wav"); + precache_sound ("plats/medplat2.wav"); + self.noise = "plats/medplat1.wav"; + self.noise1 = "plats/medplat2.wav"; + } + + + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "plat"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + + self.blocked = plat_crush; + if (!self.speed) + self.speed = 150; + +// pos1 is the top position, pos2 is the bottom + self.pos1 = self.origin; + self.pos2 = self.origin; + if (self.height) + self.pos2_z = self.origin_z - self.height; + else + self.pos2_z = self.origin_z - self.size_z + 8; + + self.use = plat_trigger_use; + + plat_spawn_inside_trigger (); // the "start moving" trigger + + if (self.targetname) + { + self.state = STATE_UP; + self.use = plat_use; + } + else + { + setorigin (self, self.pos2); + self.state = STATE_BOTTOM; + } +}; + +//============================================================================ + +void() train_next; +void() func_train_find; + +void() train_blocked = +{ + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; +void() train_use = +{ + if (self.think != func_train_find) + return; // already activated + train_next(); +}; + +void() train_wait = +{ + if (self.wait) + { + self.nextthink = self.ltime + self.wait; + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + else + self.nextthink = self.ltime + 0.1; + + self.think = train_next; +}; + +void() train_next = +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + if (!self.target) + objerror ("train_next: no next target"); + if (targ.wait) + self.wait = targ.wait; + else + self.wait = 0; + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + SUB_CalcMove (targ.origin - self.mins, self.speed, train_wait); +}; + +void() func_train_find = + +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + setorigin (self, targ.origin - self.mins); + if (!self.targetname) + { // not triggered, so start immediately + self.nextthink = self.ltime + 0.1; + self.think = train_next; + } +}; + + +const float FUNC_TRAIN_NONSOLID = 1; + +/*QUAKED func_train (0 .5 .8) ? +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 +sounds +1) ratchet metal + +*/ +void() func_train = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("func_train without a target"); + if (!self.dmg) + self.dmg = 2; + + if (self.sounds == 0) + { + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + } + + if (self.sounds == 1) + { + self.noise = ("plats/train2.wav");//stop sound + precache_sound ("plats/train2.wav"); + self.noise1 = ("plats/train1.wav");//move sound + precache_sound ("plats/train1.wav"); + } + + self.cnt = 1; + if(self.spawnflags & FUNC_TRAIN_NONSOLID) + { + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_PUSH; + //MOVETYPE_NOCLIP + } + else + { + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + } + self.use = train_use; + self.classname = "train"; + + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + +/*QUAKED misc_teleporttrain (0 .5 .8) (-8 -8 -8) (8 8 8) +This is used for the final bos +*/ +void() misc_teleporttrain = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("func_train without a target"); + + self.cnt = 1; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = train_use; + self.avelocity = '100 200 300'; + + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + + precache_model2 ("progs/teleport.mdl"); + setmodel (self, "progs/teleport.mdl"); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + diff --git a/quakec_mg1/player.qc b/quakec_mg1/player.qc new file mode 100644 index 0000000..509b9be --- /dev/null +++ b/quakec_mg1/player.qc @@ -0,0 +1,692 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +//Yoder Sept24 2021, Horde Merge +void() MonsterFade; // Used for handling gibs and corpses + +void() bubble_bob; + +/* +============================================================================== + +PLAYER + +============================================================================== +*/ + +$cd id1/models/player_4 +$origin 0 -6 24 +$base base +$skin skin + +// +// running +// +$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6 + +$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6 + +// +// standing +// +$frame stand1 stand2 stand3 stand4 stand5 + +$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6 +$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12 + + +// +// pain +// +$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + + +// +// death +// + +$frame axdeth1 axdeth2 axdeth3 axdeth4 axdeth5 axdeth6 +$frame axdeth7 axdeth8 CORPSEFRAME_PLAYER_1 + +$frame deatha1 deatha2 deatha3 deatha4 deatha5 deatha6 deatha7 deatha8 +$frame deatha9 deatha10 CORPSEFRAME_PLAYER_2 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame CORPSEFRAME_PLAYER_3 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 deathc12 deathc13 deathc14 CORPSEFRAME_PLAYER_4 + +$frame deathd1 deathd2 deathd3 deathd4 deathd5 deathd6 deathd7 +$frame deathd8 CORPSEFRAME_PLAYER_5 + +$frame deathe1 deathe2 deathe3 deathe4 deathe5 deathe6 deathe7 +$frame deathe8 CORPSEFRAME_PLAYER_6 + + +// +// attacks +// +$frame nailatt1 nailatt2 + +$frame light1 light2 + +$frame rockatt1 rockatt2 rockatt3 rockatt4 rockatt5 rockatt6 + +$frame shotatt1 shotatt2 shotatt3 shotatt4 shotatt5 shotatt6 + +$frame axatt1 axatt2 axatt3 axatt4 axatt5 axatt6 + +$frame axattb1 axattb2 axattb3 axattb4 axattb5 axattb6 + +$frame axattc1 axattc2 axattc3 axattc4 axattc5 axattc6 + +$frame axattd1 axattd2 axattd3 axattd4 axattd5 axattd6 + + +/* +============================================================================== +PLAYER +============================================================================== +*/ + +void() player_run; + +void() player_stand1 =[ $axstnd1, player_stand1 ] +{ + self.weaponframe=0; + if (self.velocity_x || self.velocity_y) + { + self.walkframe=0; + player_run(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe >= 12) + self.walkframe = 0; + self.frame = $axstnd1 + self.walkframe; + } + else + { + if (self.walkframe >= 5) + self.walkframe = 0; + self.frame = $stand1 + self.walkframe; + } + self.walkframe = self.walkframe + 1; +}; + +void() player_run =[ $rockrun1, player_run ] +{ + self.weaponframe=0; + if (!self.velocity_x && !self.velocity_y) + { + self.walkframe=0; + player_stand1(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe == 6) + self.walkframe = 0; + self.frame = $axrun1 + self.walkframe; + } + else + { + if (self.walkframe == 6) + self.walkframe = 0; + self.frame = self.frame + self.walkframe; + } + self.walkframe = self.walkframe + 1; +}; + + +void() player_shot1 = [$shotatt1, player_shot2 ] {self.weaponframe=1; +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() player_shot2 = [$shotatt2, player_shot3 ] {self.weaponframe=2;}; +void() player_shot3 = [$shotatt3, player_shot4 ] {self.weaponframe=3;}; +void() player_shot4 = [$shotatt4, player_shot5 ] {self.weaponframe=4;}; +void() player_shot5 = [$shotatt5, player_shot6 ] {self.weaponframe=5;}; +void() player_shot6 = [$shotatt6, player_run ] {self.weaponframe=6;}; + +void() player_axe1 = [$axatt1, player_axe2 ] {self.weaponframe=1;}; +void() player_axe2 = [$axatt2, player_axe3 ] {self.weaponframe=2;}; +void() player_axe3 = [$axatt3, player_axe4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axe4 = [$axatt4, player_run ] {self.weaponframe=4;}; + +void() player_axeb1 = [$axattb1, player_axeb2 ] {self.weaponframe=5;}; +void() player_axeb2 = [$axattb2, player_axeb3 ] {self.weaponframe=6;}; +void() player_axeb3 = [$axattb3, player_axeb4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axeb4 = [$axattb4, player_run ] {self.weaponframe=8;}; + +void() player_axec1 = [$axattc1, player_axec2 ] {self.weaponframe=1;}; +void() player_axec2 = [$axattc2, player_axec3 ] {self.weaponframe=2;}; +void() player_axec3 = [$axattc3, player_axec4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axec4 = [$axattc4, player_run ] {self.weaponframe=4;}; + +void() player_axed1 = [$axattd1, player_axed2 ] {self.weaponframe=5;}; +void() player_axed2 = [$axattd2, player_axed3 ] {self.weaponframe=6;}; +void() player_axed3 = [$axattd3, player_axed4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axed4 = [$axattd4, player_run ] {self.weaponframe=8;}; + + +//============================================================================ + +void() player_nail1 =[$nailatt1, player_nail2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireSpikes (4); + self.attack_finished = time + 0.2; +}; +void() player_nail2 =[$nailatt2, player_nail1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireSpikes (-4); + self.attack_finished = time + 0.2; +}; + +//============================================================================ + +void() player_light1 =[$light1, player_light2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) + self.weaponframe = 1; + SuperDamageSound(); + W_FireLightning(); + self.attack_finished = time + 0.2; +}; +void() player_light2 =[$light2, player_light1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) + self.weaponframe = 1; + SuperDamageSound(); + W_FireLightning(); + self.attack_finished = time + 0.2; +}; + +//============================================================================ + + +void() player_rocket1 =[$rockatt1, player_rocket2 ] {self.weaponframe=1; +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() player_rocket2 =[$rockatt2, player_rocket3 ] {self.weaponframe=2;}; +void() player_rocket3 =[$rockatt3, player_rocket4 ] {self.weaponframe=3;}; +void() player_rocket4 =[$rockatt4, player_rocket5 ] {self.weaponframe=4;}; +void() player_rocket5 =[$rockatt5, player_rocket6 ] {self.weaponframe=5;}; +void() player_rocket6 =[$rockatt6, player_run ] {self.weaponframe=6;}; +void(float num_bubbles) DeathBubbles; + +void() PainSound = +{ +local float rs; + + if (self.health < 0) + return; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + +// water pain sounds + if (self.watertype == CONTENT_WATER && self.waterlevel == 3) + { + DeathBubbles(1); + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM); + return; + } + +// slime pain sounds + if (self.watertype == CONTENT_SLIME) + { +// FIX ME put in some steam here + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + return; + } + + if (self.watertype == CONTENT_LAVA) + { + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + return; + } + + if (self.pain_finished > time) + { + self.axhitme = 0; + return; + } + self.pain_finished = time + 0.5; + +// don't make multiple pain sounds right after each other + +// ax pain sound + if (self.axhitme == 1) + { + self.axhitme = 0; + sound (self, CHAN_VOICE, "player/axhit1.wav", 1, ATTN_NORM); + return; + } + + + rs = rint((random() * 5) + 1); + + self.noise = ""; + if (rs == 1) + self.noise = "player/pain1.wav"; + else if (rs == 2) + self.noise = "player/pain2.wav"; + else if (rs == 3) + self.noise = "player/pain3.wav"; + else if (rs == 4) + self.noise = "player/pain4.wav"; + else if (rs == 5) + self.noise = "player/pain5.wav"; + else + self.noise = "player/pain6.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + return; +}; + +void() player_pain1 = [ $pain1, player_pain2 ] {PainSound();self.weaponframe=0;}; +void() player_pain2 = [ $pain2, player_pain3 ] {}; +void() player_pain3 = [ $pain3, player_pain4 ] {}; +void() player_pain4 = [ $pain4, player_pain5 ] {}; +void() player_pain5 = [ $pain5, player_pain6 ] {}; +void() player_pain6 = [ $pain6, player_run ] {}; + +void() player_axpain1 = [ $axpain1, player_axpain2 ] {PainSound();self.weaponframe=0;}; +void() player_axpain2 = [ $axpain2, player_axpain3 ] {}; +void() player_axpain3 = [ $axpain3, player_axpain4 ] {}; +void() player_axpain4 = [ $axpain4, player_axpain5 ] {}; +void() player_axpain5 = [ $axpain5, player_axpain6 ] {}; +void() player_axpain6 = [ $axpain6, player_run ] {}; + +void(entity attacker, float damage) player_pain = +{ + if (self.weaponframe) + return; + + if (self.invisible_finished > time) + return; // eyes don't have pain frames + + if (self.weapon == IT_AXE) + player_axpain1 (); + else + player_pain1 (); +}; + +void() player_diea1; +void() player_dieb1; +void() player_diec1; +void() player_died1; +void() player_diee1; +void() player_die_ax1; + +void() DeathBubblesSpawn = +{ +local entity bubble; + if (self.owner.waterlevel != 3) + return; + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.owner.origin + '0 0 24'); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + 0.1; + self.think = DeathBubblesSpawn; + self.air_finished = self.air_finished + 1; + if (self.air_finished >= self.bubble_count) + remove(self); +}; + +void(float num_bubbles) DeathBubbles = +{ +local entity bubble_spawner; + + bubble_spawner = spawn(); + setorigin (bubble_spawner, self.origin); + bubble_spawner.movetype = MOVETYPE_NONE; + bubble_spawner.solid = SOLID_NOT; + bubble_spawner.nextthink = time + 0.1; + bubble_spawner.think = DeathBubblesSpawn; + bubble_spawner.air_finished = 0; + bubble_spawner.owner = self; + bubble_spawner.bubble_count = num_bubbles; + return; +}; + + +void() DeathSound = +{ +local float rs; + + // water death sounds + if (self.waterlevel == 3) + { + DeathBubbles(20); + sound (self, CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + + rs = rint ((random() * 4) + 1); + if (rs == 1) + self.noise = "player/death1.wav"; + if (rs == 2) + self.noise = "player/death2.wav"; + if (rs == 3) + self.noise = "player/death3.wav"; + if (rs == 4) + self.noise = "player/death4.wav"; + if (rs == 5) + self.noise = "player/death5.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NONE); + return; +}; + + +void() PlayerDead = +{ + self.nextthink = -1; +// allow respawn after a certain time + self.deadflag = DEAD_DEAD; +}; + +vector(float dm) VelocityForDamage = +{ + local vector v; + + v_x = 100 * crandom(); + v_y = 100 * crandom(); + v_z = 200 + 100 * random(); + + if (dm > -50) + { +// dprint ("level 1\n"); + v = v * 0.7; + } + else if (dm > -200) + { +// dprint ("level 3\n"); + v = v * 2; + } + else + v = v * 10; + + return v; +}; + +void(string gibname, float dm) ThrowGib = +{ + local entity new; + + new = spawn(); + new.origin = self.origin; + setmodel (new, gibname); + setsize (new, '0 0 0', '0 0 0'); + new.velocity = VelocityForDamage (dm); + new.movetype = MOVETYPE_BOUNCE; + new.solid = SOLID_NOT; + new.avelocity_x = random()*600; + new.avelocity_y = random()*600; + new.avelocity_z = random()*600; + new.think = SUB_Remove; + new.ltime = time; + new.nextthink = time + 10 + random()*10; + new.frame = 0; + new.flags = 0; +}; + +void(string gibname, float dm) ThrowHead = +{ + setmodel (self, gibname); + self.frame = 0; + self.nextthink = -1; + self.movetype = MOVETYPE_BOUNCE; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.view_ofs = '0 0 8'; + setsize (self, '-16 -16 0', '16 16 56'); + self.velocity = VelocityForDamage (dm); + self.origin_z = self.origin_z - 24; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.avelocity = crandom() * '0 600 0'; + + // Yoder Sept24 2021, Horde Merge + if (horde_ent) + { + // only monsters gibs should fade away + if (self.classname != "player") + { + self.think = MonsterFade; + self.nextthink = time + 1; + } + } +}; + + +void() GibPlayer = +{ + ThrowHead ("progs/h_player.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + + self.deadflag = DEAD_DEAD; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (damage_attacker.classname == "teledeath2") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (random() < 0.5) + sound (self, CHAN_VOICE, "player/gib.wav", 1, ATTN_NONE); + else + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NONE); +}; + +void() PlayerDie = +{ + local float i; + + self.items = self.items - (self.items & IT_INVISIBILITY); + self.invisible_finished = 0; // don't die as eyes + self.invincible_finished = 0; + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.modelindex = modelindex_player; // don't use eyes + + if (deathmatch || coop) + DropBackpack(); + + self.weaponmodel=""; + self.view_ofs = '0 0 -8'; + self.deadflag = DEAD_DYING; + self.solid = SOLID_NOT; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.movetype = MOVETYPE_TOSS; + if (self.velocity_z < 10) + self.velocity_z = self.velocity_z + random()*300; + + if (self.health < -40) + { + GibPlayer (); + return; + } + + DeathSound(); + + self.angles_x = 0; + self.angles_z = 0; + + if (self.weapon == IT_AXE) + { + player_die_ax1 (); + return; + } + + i = cvar("temp1"); + if (!i) + i = 1 + floor(random()*6); + + if (i == 1) + player_diea1(); + else if (i == 2) + player_dieb1(); + else if (i == 3) + player_diec1(); + else if (i == 4) + player_died1(); + else + player_diee1(); + +}; + +void() set_suicide_frame = +{ // used by klill command and diconnect command + if (self.model != "progs/player.mdl") + return; // allready gibbed + self.frame = $CORPSEFRAME_PLAYER_2; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_TOSS; + self.deadflag = DEAD_DEAD; + self.nextthink = -1; +}; + +void() player_die_ax1 = [ $axdeth1, player_die_ax2 ] {}; +void() player_die_ax2 = [ $axdeth2, player_die_ax3 ] {}; +void() player_die_ax3 = [ $axdeth3, player_die_ax4 ] {}; +void() player_die_ax4 = [ $axdeth4, player_die_ax5 ] {}; +void() player_die_ax5 = [ $axdeth5, player_die_ax6 ] {}; +void() player_die_ax6 = [ $axdeth6, player_die_ax7 ] {}; +void() player_die_ax7 = [ $axdeth7, player_die_ax8 ] {}; +void() player_die_ax8 = [ $axdeth8, player_die_ax9 ] {}; +void() player_die_ax9 = [ $CORPSEFRAME_PLAYER_1, player_die_ax9 ] {PlayerDead();}; + +void() player_diea1 = [ $deatha1, player_diea2 ] {}; +void() player_diea2 = [ $deatha2, player_diea3 ] {}; +void() player_diea3 = [ $deatha3, player_diea4 ] {}; +void() player_diea4 = [ $deatha4, player_diea5 ] {}; +void() player_diea5 = [ $deatha5, player_diea6 ] {}; +void() player_diea6 = [ $deatha6, player_diea7 ] {}; +void() player_diea7 = [ $deatha7, player_diea8 ] {}; +void() player_diea8 = [ $deatha8, player_diea9 ] {}; +void() player_diea9 = [ $deatha9, player_diea10 ] {}; +void() player_diea10 = [ $deatha10, player_diea11 ] {}; +void() player_diea11 = [ $CORPSEFRAME_PLAYER_2, player_diea11 ] {PlayerDead();}; + +void() player_dieb1 = [ $deathb1, player_dieb2 ] {}; +void() player_dieb2 = [ $deathb2, player_dieb3 ] {}; +void() player_dieb3 = [ $deathb3, player_dieb4 ] {}; +void() player_dieb4 = [ $deathb4, player_dieb5 ] {}; +void() player_dieb5 = [ $deathb5, player_dieb6 ] {}; +void() player_dieb6 = [ $deathb6, player_dieb7 ] {}; +void() player_dieb7 = [ $deathb7, player_dieb8 ] {}; +void() player_dieb8 = [ $deathb8, player_dieb9 ] {}; +void() player_dieb9 = [ $CORPSEFRAME_PLAYER_3, player_dieb9 ] {PlayerDead();}; + +void() player_diec1 = [ $deathc1, player_diec2 ] {}; +void() player_diec2 = [ $deathc2, player_diec3 ] {}; +void() player_diec3 = [ $deathc3, player_diec4 ] {}; +void() player_diec4 = [ $deathc4, player_diec5 ] {}; +void() player_diec5 = [ $deathc5, player_diec6 ] {}; +void() player_diec6 = [ $deathc6, player_diec7 ] {}; +void() player_diec7 = [ $deathc7, player_diec8 ] {}; +void() player_diec8 = [ $deathc8, player_diec9 ] {}; +void() player_diec9 = [ $deathc9, player_diec10 ] {}; +void() player_diec10 = [ $deathc10, player_diec11 ] {}; +void() player_diec11 = [ $deathc11, player_diec12 ] {}; +void() player_diec12 = [ $deathc12, player_diec13 ] {}; +void() player_diec13 = [ $deathc13, player_diec14 ] {}; +void() player_diec14 = [ $deathc14, player_diec15 ] {}; +void() player_diec15 = [ $CORPSEFRAME_PLAYER_4, player_diec15 ] {PlayerDead();}; + +void() player_died1 = [ $deathd1, player_died2 ] {}; +void() player_died2 = [ $deathd2, player_died3 ] {}; +void() player_died3 = [ $deathd3, player_died4 ] {}; +void() player_died4 = [ $deathd4, player_died5 ] {}; +void() player_died5 = [ $deathd5, player_died6 ] {}; +void() player_died6 = [ $deathd6, player_died7 ] {}; +void() player_died7 = [ $deathd7, player_died8 ] {}; +void() player_died8 = [ $deathd8, player_died9 ] {}; +void() player_died9 = [ $CORPSEFRAME_PLAYER_5, player_died9 ] {PlayerDead();}; + +void() player_diee1 = [ $deathe1, player_diee2 ] {}; +void() player_diee2 = [ $deathe2, player_diee3 ] {}; +void() player_diee3 = [ $deathe3, player_diee4 ] {}; +void() player_diee4 = [ $deathe4, player_diee5 ] {}; +void() player_diee5 = [ $deathe5, player_diee6 ] {}; +void() player_diee6 = [ $deathe6, player_diee7 ] {}; +void() player_diee7 = [ $deathe7, player_diee8 ] {}; +void() player_diee8 = [ $deathe8, player_diee9 ] {}; +void() player_diee9 = [ $CORPSEFRAME_PLAYER_5, player_diee9 ] {PlayerDead();}; + diff --git a/quakec_mg1/progs.src b/quakec_mg1/progs.src new file mode 100644 index 0000000..e8909cb --- /dev/null +++ b/quakec_mg1/progs.src @@ -0,0 +1,78 @@ +#output "../progs.dat" + +#copyright "Machinegames 2021" + +// Enables downed zombies to be gibbed by nearby explosions +// #define GIB_DOWNED_ZOMBIES + +// Enables grenade downward slope bounce fix +#define GRENADE_BOUNCE_FIX + +// Enables monsters being aware of being in dangerous liquids +#define MONSTERS_AWARE_OF_CONTENTS + +// Enables experimental coop respawn behaviour: items respawn times so all players have a chance to pick them up +#define COOP_RESPAWN_ITEMS_FOR_PLAYERS + +// Enables experimental coop respawn behaviour: keep all the players weapons when a checkpoint is activated +#define COOP_RESPAWN_KEEP_WEAPONS + +// Makes it so that killing an entity that has a delayed trigger behaviour while the delay is pending also cancels the delayed trigger +#define ALLOW_DELAYED_THINK_CANCEL + +#includelist + +defs.qc +math.qc +subs.qc +frametick.qc +fight.qc +ai.qc +combat.qc +items.qc +items_runes.qc +weapons.qc +fog.qc // Fog controls +world.qc +client.qc +player.qc +monsters.qc +doors.qc +buttons.qc +triggers.qc +plats.qc +misc.qc +func_bob.qc +lights.qc +rotate.qc +func_toss.qc +// func_fade.qc // Bad behavior + + +monsters/ogre.qc +monsters/demon.qc +monsters/shambler.qc +monsters/knight.qc +monsters/soldier.qc +monsters/wizard.qc +monsters/dog.qc +monsters/zombie.qc +monsters/boss.qc + +monsters/tarbaby.qc // registered +monsters/hknight.qc // registered +monsters/fish.qc // registered +monsters/shalrath.qc // registered +monsters/enforcer.qc // registered +monsters/oldone.qc // registered + +misc_corpses.qc //Corpses yay +misc_fx.qc // adds screenshake + +//Map specific code? Why not.. +map_specific/mge2m2.qc +map_specific/hub.qc + +horde.qc // Yoder readded Sept 24, 2021 + +#endlist \ No newline at end of file diff --git a/quakec_mg1/rotate.qc b/quakec_mg1/rotate.qc new file mode 100644 index 0000000..d0c94d6 --- /dev/null +++ b/quakec_mg1/rotate.qc @@ -0,0 +1,198 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +=============================================================================== +Bmodel rotation +=============================================================================== +*/ + +vector ModAngles(vector a) +{ + while(a_x > 360) a_x -= 360; + while(a_x < 0) a_x += 360; + while(a_y > 360) a_y -= 360; + while(a_y < 0) a_y += 360; + while(a_z > 360) a_z -= 360; + while(a_x < 0) a_z += 360; + return a; +} + +float Clamp(float v, float min, float max) +{ + if(v < min) return min; + if(v > max) return max; + return v; +} + +/*QUAKED info_rotate (0.4 1.0 0.6) (-8 -8 -8) (8 8 8) +Used to indicate center of rotation. +*/ +void info_rotate_axis() +{ + remove(self); +} + +/* +=============================================================================== +Continuous rotation +self.state = state: either off, on, accel up, or deccel down. +self.avelocity = rotation degrees per second +self.speed = current fraction of angluar velocity +self.distance = change in self.speed per second, negative if decelerating +self.delay = editor field for how long the acceleration should take +=============================================================================== +*/ + +enum : int +{ + RotateStateOff, + RotateStateAccelUp, + RotateStateOn, + RotateStateDecelDown +}; + +void rotate_object_continuously_tick(float DeltaTime) +{ + self.angles = ModAngles(self.angles + self.avelocity * DeltaTime); +} + +void rotate_object_tween_tick(float DeltaTime) +{ + self.speed += self.distance * DeltaTime; + self.speed = Clamp(self.speed, 0, 1); + if(self.speed == 0) + { + self.state = RotateStateOff; + RemoveFrameTickEntity(self); + return; + } + if(self.speed == 1) + { + self.state = RotateStateOn; + self.tick = rotate_object_continuously_tick; + self.tick(DeltaTime); + + return; + } + self.angles = ModAngles(self.angles + self.avelocity * DeltaTime * self.speed); +} + +void rotate_object_continuously_use() +{ + if(self.delay <= 0) + { + //No acceleration, just toggle on or off + if(self.state == RotateStateOff) + { + self.state = RotateStateOn; + RegisterFrameTickEntity(self); + } + else + { + self.state = RotateStateOff; + RemoveFrameTickEntity(self); + } + return; + } + + switch(self.state) + { + case RotateStateOff: + self.state = RotateStateAccelUp; + self.tick = rotate_object_tween_tick; + self.distance = fabs(self.distance); + RegisterFrameTickEntity(self); + return; + + case RotateStateAccelUp: + self.state = RotateStateDecelDown; + self.tick = rotate_object_tween_tick; + self.distance = -fabs(self.distance); + return; + + case RotateStateOn: + self.state = RotateStateDecelDown; + self.tick = rotate_object_tween_tick; + self.distance = -fabs(self.distance); + return; + + case RotateStateDecelDown: + self.state = RotateStateAccelUp; + self.tick = rotate_object_tween_tick; + self.distance = fabs(self.distance); + return; + } +} + +/*QUAKED rotate_object_continuously (0.4 1.0 0.6) (? ? ?) (? ? ?) START_OFF +Non-solid object that rotates continuously. +Trigger to turn on and off. +*/ +void rotate_object_continuously() +{ + if(!self.avelocity) + { + self.avelocity = '0 30 0'; + } + if(self.delay > 0) + { + self.distance = 1 / self.delay; + } + + if(self.spawnflags & SOLID_BSP) + { + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + } + else + { + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + } + self.angles = '0 0 0'; + if(self.pos2) + { + self.origin = self.pos2; + setorigin(self, self.origin); + } + setmodel(self, self.model); + + self.use = rotate_object_continuously_use; + self.tick = rotate_object_continuously_tick; + + if(self.spawnflags & START_OFF) + { + self.state = RotateStateOff; + } + else + { + self.state = RotateStateOn; + self.speed = 1; + RegisterFrameTickEntity(self); + } +} + +/* +=============================================================================== +Accel/decel rotation +=============================================================================== +*/ + + diff --git a/quakec_mg1/subs.qc b/quakec_mg1/subs.qc new file mode 100644 index 0000000..2cd8a35 --- /dev/null +++ b/quakec_mg1/subs.qc @@ -0,0 +1,458 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +const float MESSAGE_ALL_PLAYERS = 2097152; + +void() SUB_Null = {}; +float(...) SUB_True = { return TRUE; }; +void(entity attacker, float damage) SUB_NullPain = {}; + +void() SUB_Remove = {remove(self);}; + + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void() SetMovedir = +{ + if (self.movedir) + { + self.angles = '0 0 0'; + return; + } + if (self.angles == '0 -1 0') + self.movedir = '0 0 1'; + else if (self.angles == '0 -2 0') + self.movedir = '0 0 -1'; + else + { + makevectors (self.angles); + self.movedir = v_forward; + } + + self.angles = '0 0 0'; +}; + + +/* +================ +RemovedOutsideCoop +Removes self if COOP_ONLY spawnflag is set and we're not in coop. +Returns TRUE if it was removed. +================ +*/ +float RemovedOutsideCoop() +{ + if (!coop && (self.spawnflags & COOP_ONLY)) + { + remove(self); + return TRUE; + } + return FALSE; +} + +/* +================ +InitTrigger +================ +*/ +void() InitTrigger = +{ + if (RemovedOutsideCoop()) return; + + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (self.angles != '0 0 0') + SetMovedir (); + self.solid = SOLID_TRIGGER; + setmodel (self, self.model); // set size and link into world + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + self.model = ""; +}; + +/* +============= +SUB_CalcMove + +calculate self.velocity and self.nextthink to reach dest from +self.origin traveling at speed +=============== +*/ +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt = +{ +local entity stemp; + stemp = self; + self = ent; + + SUB_CalcMove (tdest, tspeed, func); + self = stemp; +}; + +void(vector tdest, float tspeed, void() func) SUB_CalcMove = +{ +local vector vdestdelta; +local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + + self.think1 = func; + self.finaldest = tdest; + self.think = SUB_CalcMoveDone; + + if (tdest == self.origin) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + +// set destdelta to the vector needed to move + vdestdelta = tdest - self.origin; + +// calculate length of vector + len = vlen (vdestdelta); + +// divide by speed to get time to reach dest + traveltime = len / tspeed; + + if (traveltime < 0.1) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + +// set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + +// scale the destdelta vector by the time spent traveling to get velocity + self.velocity = vdestdelta * (1/traveltime); // qcc won't take vec/float +}; + +/* +============ +After moving, set origin to exact final destination +============ +*/ +void() SUB_CalcMoveDone = +{ + setorigin(self, self.finaldest); + self.velocity = '0 0 0'; + self.nextthink = -1; + if (self.think1) + self.think1(); +}; + + +/* +============= +SUB_CalcAngleMove + +calculate self.avelocity and self.nextthink to reach destangle from +self.angles rotating + +The calling function should make sure self.think is valid +=============== +*/ +void(entity ent, vector destangle, float tspeed, void() func) SUB_CalcAngleMoveEnt = +{ +local entity stemp; + stemp = self; + self = ent; + SUB_CalcAngleMove (destangle, tspeed, func); + self = stemp; +}; + +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove = +{ +local vector destdelta; +local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + + // set destdelta to the vector needed to move + destdelta = destangle - self.angles; + + // calculate length of vector + len = vlen (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / tspeed; + + // set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + + // scale the destdelta vector by the time spent traveling to get velocity + self.avelocity = destdelta * (1 / traveltime); + + self.think1 = func; + self.finalangle = destangle; + self.think = SUB_CalcAngleMoveDone; +}; + +/* +============ +After rotating, set angle to exact final angle +============ +*/ +void() SUB_CalcAngleMoveDone = +{ + self.angles = self.finalangle; + self.avelocity = '0 0 0'; + self.nextthink = -1; + if (self.think1) + self.think1(); +}; + + +//============================================================================= +void() DelayThink = +{ + activator = self.enemy; + SUB_UseTargets (); + remove(self); +}; + +/* +============================== +SUB_UseTargets + +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. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void() SUB_UseTargets = +{ + local entity t, stemp, otemp, act; + + // + // check for a delay + // + if (self.delay) + { + // create a temp object to fire at a later time + t = spawn(); + t.classname = "DelayedUse"; + t.nextthink = time + self.delay; + t.think = DelayThink; + t.enemy = activator; + t.message = self.message; + t.killtarget = self.killtarget; + t.target = self.target; + t.spawnflags = self.spawnflags & MESSAGE_ALL_PLAYERS; +#ifdef ALLOW_DELAYED_THINK_CANCEL + t.targetname = self.targetname; + t.use = SUB_Null; +#endif + return; + } + + + // + // print the message + // + if (activator.classname == "player" && self.message != "") + { + if(self.spawnflags & MESSAGE_ALL_PLAYERS) + { + centerprint_all (self.message); //Ingame message, localized + } + else + { + centerprint (activator, self.message); //Ingame message, localized + } + if (!self.noise) + sound (activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + + // + // kill the killtagets + // + if (self.killtarget) + { + t = find (world, targetname, self.killtarget); + + while( t ) + { + remove (t); + t = find (t, targetname, self.killtarget); + } + } + + // + // fire targets + // + if (self.target) + { + act = activator; + t = find (world, targetname, self.target); + while( t ) + { + stemp = self; + otemp = other; + self = t; + other = stemp; + if (self.use != SUB_Null) + { + if (self.use) + self.use (); + } + self = stemp; + other = otemp; + activator = act; + t = find (t, targetname, self.target); + } + } + + +}; + + +/* + +in nightmare mode, all attack_finished times become 0 +some monsters refire twice automatically +update: not anymore! it makes nightmare too easy +*/ + +void(float normal) SUB_AttackFinished = +{ + self.cnt = 0; // refire count for nightmare + //if (skill != 3) + self.attack_finished = time + normal; +}; + +float (entity targ) visible; + +void (void() thinkst) SUB_CheckRefire = +{ + if (skill != 3) + return; + if (self.cnt == 1) + return; + if (!visible (self.enemy)) + return; + self.cnt = 1; + self.think = thinkst; +}; + + + +/* +================ +SUB_SwitchTargets +================ +*/ + +void SUB_SwitchTargets(.string field, string oldtarget, string newtarget) +{ + entity e = find(world, targetname, oldtarget); + while(e) + { + e.field = newtarget; + e = find(e, targetname, oldtarget); + } +} + +/* +================ +SUB_FindWithPredicate +================ +*/ + +entity SUB_FindWithPredicate(entity start, .string field, string search, float(entity e) predicate = (float(entity e))SUB_True) +{ + entity t = find(start, field, search); + while(t && !predicate(t)) + { + t = find(t, field, search); + } + return t; +} + +/* +================ +SUB_CountTargets +================ +*/ + +float SUB_CountTargets(entity e, float(entity e) predicate = (float(entity e))SUB_True) +{ + float cnt = 0; + entity t = SUB_FindWithPredicate(world, targetname, e.target, predicate); + while(t) + { + cnt++; + t = SUB_FindWithPredicate(t, targetname, e.target, predicate); + } + return cnt; +} + +/* +================ +SUB_RandomTarget +================ +*/ + +entity SUB_RandomTarget(entity e, float(entity e) predicate = (float(entity e))SUB_True) +{ + float cnt = SUB_CountTargets(e, predicate); + if (cnt == 0) return world; + cnt = floor(cnt * random()); + entity t = SUB_FindWithPredicate(world, targetname, e.target, predicate); + while(cnt--) + { + t = SUB_FindWithPredicate(t, targetname, e.target, predicate); + } + return t; +} + +/* +================ +SUB_SetWorldType +================ +*/ + +void SUB_SetWorldtype() +{ + if(self.worldtype) + { + self.worldtype--; + } + else + { + self.worldtype = world.worldtype; + } +} + diff --git a/quakec_mg1/triggers.qc b/quakec_mg1/triggers.qc new file mode 100644 index 0000000..58663eb --- /dev/null +++ b/quakec_mg1/triggers.qc @@ -0,0 +1,1060 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Yoder Sept 24 2021 Horde merge +float COUNTER_LOOPS = 2; // so counters can loop back to the start again +float MONSTER_ONLY = 8; // for trigger_hurt + +void() trigger_reactivate = +{ + self.solid = SOLID_TRIGGER; +}; + +//============================================================================= + +// the wait time has passed, so set back up for another activation +void() multi_wait = +{ + if (self.max_health) + { + self.health = self.max_health; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + } +}; + + +// the trigger was just touched/killed/used +// self.enemy 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 = +{ + if (self.nextthink > time) + { + return; // allready been triggered + } + + if (self.classname == "trigger_secret") + { + if (self.enemy.classname != "player") + return; + found_secrets = found_secrets + 1; + WriteByte (MSG_ALL, SVC_FOUNDSECRET); + + msg_entity = self.enemy; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_FIND_SECRET"); + } + + if (self.noise) + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + +// don't trigger again until reset + self.takedamage = DAMAGE_NO; + + activator = self.enemy; + + SUB_UseTargets(); + + if (self.wait > 0) + { + self.think = multi_wait; + self.nextthink = time + self.wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called wheil C code is looping through area links... + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + } +}; + +void() multi_killed = +{ + self.enemy = damage_attacker; + multi_trigger(); +}; + +void() multi_use = +{ + self.enemy = activator; + multi_trigger(); +}; + +void() multi_touch = +{ + if (other.classname != "player") + return; + +// if the trigger has an angles field, check player's facing direction + if (self.movedir != '0 0 0') + { + makevectors (other.angles); + if (v_forward * self.movedir < 0) + return; // not facing the right way + } + + self.enemy = other; + multi_trigger (); +}; + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_multiple = +{ + INHIBIT_COOP + self.netname = "trigger_multiple"; // for bot nav support. + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + else if (self.sounds == 3) + { + precache_sound ("misc/trigger1.wav"); + self.noise = "misc/trigger1.wav"; + } + self.sounds = 0; + if(self.spawnflags & SPAWNFLAG_TRIGGER_FIRST) + { + self.use = trigger_multiple; + self.spawnflags &~= SPAWNFLAG_TRIGGER_FIRST; + return; + } + + if (!self.wait) + self.wait = 0.2; + self.use = multi_use; + + InitTrigger (); + + if (self.health) + { + if (self.spawnflags & SPAWNFLAG_NOTOUCH) + objerror ("health and notouch don't make sense\n"); + self.max_health = self.health; + self.th_die = multi_killed; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + setorigin (self, self.origin); // make sure it links into the world + } + else + { + if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) ) + { + self.touch = multi_touch; + } + } +}; + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. 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 "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_once = +{ + self.wait = -1; + trigger_multiple(); + self.netname = "trigger_once"; // for bot nav support. +}; + +//============================================================================= + +/*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. It can contain killtargets, targets, delays, and messages. +*/ +void() trigger_relay = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + self.use = SUB_UseTargets; +}; + + +//============================================================================= + +/*QUAKED trigger_secret (.5 .5 .5) ? +secret counter trigger +sounds +1) secret +2) beep beep +3) +4) +set "message" to text string +*/ +void() trigger_secret = +{ + INHIBIT_COOP + total_secrets = total_secrets + 1; + self.wait = -1; + if (!self.message) + self.message = "$qc_found_secret"; + if (!self.sounds) + self.sounds = 1; + + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + + trigger_multiple (); +}; + +//============================================================================= + + +void() counter_use = +{ + self.count = self.count - 1; + if (self.count < 0) + return; + + if (self.count != 0) + { + if (activator.classname == "player" + && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + { + if (self.count >= 4) + { + centerprint_all ("$qc_more_go"); + } + else if (self.count == 3) + { + centerprint_all ("$qc_three_more"); + } + else if (self.count == 2) + { + centerprint_all ("$qc_two_more"); + } + else + { + centerprint_all ("$qc_one_more"); + } + } + return; + } + + if (activator.classname == "player" && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + { + centerprint_all("$qc_sequence_completed"); + } + self.enemy = activator; + + // yoder Sept24, 2021, Horde Merge + if (self.spawnflags & COUNTER_LOOPS) + self.count = self.wait; + + multi_trigger (); +}; + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage message_all +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 = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + // Yoder Sept24, 2021 Horde Merge + // .count is decremented each use and fires its targets on 0 + // .wait stores the original .count and restores to this if COUNTER_LOOPS + if (!self.count) + self.count = 2; + + if (!self.wait) + self.wait = self.count; + + self.use = counter_use; +}; + + +//============================================================================= + + +void() counter_timed_think = +{ + self.count = self.cnt; +} + +void() counter_timed_use = +{ + self.count = self.count - 1; + if (self.count < 0) + return; + + self.nextthink = time + self.delay; + + if (self.count != 0) + { + if (activator.classname == "player" + && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + { + if (self.count >= 4) + { + centerprint_all ("$qc_more_go"); + } + else if (self.count == 3) + { + centerprint_all ("$qc_three_more"); + } + else if (self.count == 2) + { + centerprint_all ("$qc_two_more"); + } + else + { + centerprint_all ("$qc_one_more"); + } + } + return; + } + + if (activator.classname == "player" && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + { + centerprint_all("$qc_sequence_completed"); + } + self.enemy = activator; + self.delay = 0; + self.nextthink = -1; + self.think = SUB_Null; + multi_trigger (); + remove(self); +}; + +/*QUAKED trigger_counter_timed (.5 .5 .5) ? nomessage message_all +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_timed = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + self.wait = -1; + if (!self.count) + self.count = 2; + + if (!self.delay) + self.delay = 2; + + self.cnt = self.count; + + self.use = counter_timed_use; + self.think = counter_timed_think; +}; + + +/* +============================================================================== + +TELEPORT TRIGGERS + +============================================================================== +*/ + +float PLAYER_ONLY = 1; +float SILENT = 2; +float IGNORE_TARGETNAME = 4; + +void() play_teleport = +{ + local float v; + local string tmpstr; + + v = random() * 5; + if (v < 1) + tmpstr = "misc/r_tele1.wav"; + else if (v < 2) + tmpstr = "misc/r_tele2.wav"; + else if (v < 3) + tmpstr = "misc/r_tele3.wav"; + else if (v < 4) + tmpstr = "misc/r_tele4.wav"; + else + tmpstr = "misc/r_tele5.wav"; + + sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM); + remove (self); +}; + +void(vector org) spawn_tfog = +{ + entity s = spawn (); + s.origin = org; + s.nextthink = time + 0.2; + s.think = play_teleport; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); +}; + +void(vector org) spawn_tfog_silent = +{ + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); +}; + + +void() tdeath_touch = +{ + if (other == self.owner) + return; + +// frag anyone who teleports in on top of an invincible player + if (other.classname == "player") + { + if (other.invincible_finished > time) + self.classname = "teledeath2"; + if (self.owner.classname != "player") + { // other monsters explode themselves + T_Damage (self.owner, self, self, 50000); + return; + } + + } + + if (other.health) + { + T_Damage (other, self, self, 50000); + } +}; + +// Yoder sept24 2021 horde merge +// a faster version of tdeath to minimize telefragging +void(vector org, entity death_owner) spawn_tdeath_fast = +{ +local entity death; + + death = spawn(); + death.classname = "teledeath"; + death.movetype = MOVETYPE_NONE; + death.solid = SOLID_TRIGGER; + death.angles = '0 0 0'; + setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1'); + setorigin (death, org); + death.touch = tdeath_touch; + death.nextthink = time + 0.01; + death.think = SUB_Remove; + death.owner = death_owner; + + force_retouch = 2; // make sure even still objects get hit +}; + +void(vector org, entity death_owner) spawn_tdeath = +{ +local entity death; + + death = spawn(); + death.classname = "teledeath"; + death.movetype = MOVETYPE_NONE; + death.solid = SOLID_TRIGGER; + death.angles = '0 0 0'; + setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1'); + setorigin (death, org); + death.touch = tdeath_touch; + death.nextthink = time + 0.2; + death.think = SUB_Remove; + death.owner = death_owner; + + force_retouch = 2; // make sure even still objects get hit +}; + +void() teleport_touch = +{ +local entity t; +local vector org; + + if (self.targetname && (self.spawnflags & IGNORE_TARGETNAME) == 0) + { + if (self.nextthink < time) + { + return; // not fired yet + } + } + + if (self.spawnflags & PLAYER_ONLY) + { + if (other.classname != "player") + return; + } + +// only teleport living creatures + if (other.health <= 0 || other.solid != SOLID_SLIDEBOX) + return; + + SUB_UseTargets (); + +// put a tfog where the player was + spawn_tfog (other.origin); + + t = find (world, targetname, self.target); + if (!t) + objerror ("couldn't find target"); + +// spawn a tfog flash in front of the destination + makevectors (t.mangle); + org = t.origin + 32 * v_forward; + + spawn_tfog (org); + spawn_tdeath(t.origin, other); + +// move the player and lock him down for a little while + if (!other.health) + { + other.origin = t.origin; + other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y); + return; + } + + setorigin (other, t.origin); + other.angles = t.mangle; + if (other.classname == "player") + { + other.fixangle = 1; // turn this way immediately + other.teleport_time = time + 0.7; + if (other.flags & FL_ONGROUND) + other.flags = other.flags - FL_ONGROUND; + other.velocity = v_forward * 300; + if(!coop && !deathmatch) + { + FogPushSettingsFrom(other, t, 0); + } + } + other.flags = other.flags - other.flags & FL_ONGROUND; +}; + +/*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32) +This is the destination marker for a teleporter. It should have a "targetname" field with the same value as a teleporter's "target" field. +*/ +void() info_teleport_destination = +{ + self.netname = "info_teleport_destination"; // for bot nav support. + // this does nothing, just serves as a target spot + self.mangle = self.angles; + self.angles = '0 0 0'; + self.model = ""; + self.origin = self.origin + '0 0 27'; + if (!self.targetname) + objerror ("no targetname"); +}; + +void() teleport_use = +{ + self.nextthink = time + 0.2; + force_retouch = 2; // make sure even still objects get hit + self.think = SUB_Null; +}; + +/*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT +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. +*/ +void() trigger_teleport = +{ + self.netname = "trigger_teleport"; // for bot nav support. + local vector o; + + InitTrigger (); + self.touch = teleport_touch; + // find the destination + if (!self.target) + objerror ("no target"); + self.use = teleport_use; + + if (!(self.spawnflags & SILENT)) + { + precache_sound ("ambience/hum1.wav"); + o = (self.mins + self.maxs)*0.5; + ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC); + } +}; + +/* +============================================================================== + +trigger_setskill + +============================================================================== +*/ + +void() trigger_skill_touch = +{ + if (other.classname != "player") + return; + + cvar_set ("skill", self.message); +}; + +/*QUAKED trigger_setskill (.5 .5 .5) ? +sets skill level to the value of "message". +Only used on start map. +*/ +void() trigger_setskill = +{ + InitTrigger (); + self.touch = trigger_skill_touch; +}; + + +/* +============================================================================== + +ONLY REGISTERED TRIGGERS + +============================================================================== +*/ + +void() trigger_onlyregistered_touch = +{ + if (other.classname != "player") + return; + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + if (cvar("registered")) + { + self.message = ""; + SUB_UseTargets (); + remove (self); + } + else + { + if (self.message != "") + { + centerprint (other, self.message); // Ingame message, localized + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } + } +}; + +/*QUAKED trigger_onlyregistered (.5 .5 .5) ? +Only fires if playing the registered version, otherwise prints the message +*/ +void() trigger_onlyregistered = +{ + precache_sound ("misc/talk.wav"); + InitTrigger (); + self.touch = trigger_onlyregistered_touch; +}; + +//============================================================================ + +void hurt_use() +{ + self.state = 1 - self.state; +} + +void() hurt_on = +{ + self.solid = SOLID_TRIGGER; + self.nextthink = -1; +}; + +void() hurt_touch = +{ + if(self.state) return; + if (other.takedamage) + { + // Yoder Sept24 2021 Horde merge + // allows for monster-only hurt volumes + if ((self.spawnflags & MONSTER_ONLY) && !(other.flags & FL_MONSTER)) + return; + + self.solid = SOLID_NOT; + T_Damage (other, self, self, self.dmg); + self.think = hurt_on; + self.nextthink = time + self.wait; + } +}; + +/*QUAKED trigger_hurt (.5 .5 .5) ? +Any object touching this will be hurt +set dmg to damage amount +defalt dmg = 5 +*/ +void() trigger_hurt = +{ + self.netname = "trigger_hurt"; // for bot nav support. + InitTrigger (); + self.touch = hurt_touch; + if (!self.dmg) + self.dmg = 5; + if (!self.wait) + self.wait = 1; + if(self.spawnflags & START_OFF) + { + self.state = 1; + } + self.use = hurt_use; +}; + +//============================================================================ + +float PUSH_ONCE = 1; +float ADDITIVE_PUSH = 2; +float PUSH_START_OFF = 4; + +void() trigger_push_touch = +{ + // yoder Sept 24 2021 horde merge + + if (horde_ent) + { + if (other.spawnflags & PUSH_START_OFF) // ignore this spawnflag in horde mode + other.spawnflags-= PUSH_START_OFF; + + if ((other.classname != "player")) + { + if ((other.classname == "item_artifact_invulnerability") || (other.classname == "item_artifact_super_damage")) + { + if (self.spawnflags & ADDITIVE_PUSH) + other.velocity = other.velocity + (self.speed * self.movedir * 10 * frametime); + else + other.velocity = self.speed * self.movedir * 10; + } + return; + } + } + + if (other.health > 0 || other.classname == "grenade") + { + if(other.flags & FL_INSHELTER) return; + + if (self.spawnflags & ADDITIVE_PUSH) // yoder add, Jan 28 2021 + other.velocity = other.velocity + self.speed * self.movedir * 10 * frametime; + else + other.velocity = self.speed * self.movedir * 10; + if (other.classname == "player") + { + if (other.fly_sound < time) + { + other.fly_sound = time + 1.5; + sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); + } + } + if(other.flags & FL_MONSTER) other.flags (-) FL_ONGROUND; //If a monster walks inside a trigger_push, make it fly + } + if (self.spawnflags & PUSH_ONCE) + remove(self); +}; + +void() trigger_push_use = +{ + if(self.solid == SOLID_TRIGGER) + { + dprint("trigger_push: switched off\n"); + self.solid = SOLID_NOT; + } + else + { + dprint("trigger_push: switched on\n"); + self.solid = SOLID_TRIGGER; + force_retouch = 1; + } +} + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE ADDITIVE_PUSH START_OFF +Pushes the player +*/ +void() trigger_push = +{ + self.netname = "trigger_push"; // for bot nav support. + InitTrigger (); + precache_sound ("ambience/windfly.wav"); + self.touch = trigger_push_touch; + self.use = trigger_push_use; + if(self.spawnflags & PUSH_START_OFF) self.solid = SOLID_NOT; + if (!self.speed) + self.speed = 1000; +}; + +// ================================================================= + +const float SHELTER_FLIPPED = 1; + +void() trigger_shelter_portal_touch = +{ + if (other.health > 0 || other.classname == "grenade") + { + vector offs = other.origin - self.pos1; + float dot = offs * self.pos2; + if(dot >= 0) + { + other.flags (+) FL_INSHELTER; + } + else + { + other.flags (-) FL_INSHELTER; + } + } +} + +/*QUAKED trigger_shelter (.5 .5 .5) +Shelters the player from pushes +*/ +void() trigger_shelter_portal = +{ + InitTrigger (); + self.touch = trigger_shelter_portal_touch; + self.pos1 = self.mins + (self.size * 0.5); + if(self.size_x < self.size_y) + { + if(self.size_x < self.size_z) + self.pos2 = '1 0 0'; + else + self.pos2 = '0 0 1'; + } + else + { + if(self.size_y < self.size_z) + self.pos2 = '0 1 0'; + else + self.pos2 = '0 0 1'; + } + + if(self.spawnflags & SHELTER_FLIPPED) self.pos2 *= -1; +}; + +//============================================================================ + +void() trigger_monsterjump_touch = +{ + if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER ) + return; + + // yoder add, July 10th 2020 + // making it so trigger_monsterjump's with spawnflag 8 only affect mosnters with spawnflag 8 + if (self.spawnflags & 64) + { + if (!(other.spawnflags & 64)) + { + dprint("monster didn't have spawnflag 8!\n"); + return; + } + } + +// set XY even if not on ground, so the jump will clear lips + other.velocity_x = self.movedir_x * self.speed; + other.velocity_y = self.movedir_y * self.speed; + + if ( !(other.flags & FL_ONGROUND) ) + return; + + other.flags = other.flags - FL_ONGROUND; + + other.velocity_z = self.height; +}; + +/*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 = +{ + if (!self.speed) + self.speed = 200; + if (!self.height) + self.height = 200; + if (self.angles == '0 0 0') + self.angles = '0 360 0'; + InitTrigger (); + self.touch = trigger_monsterjump_touch; +}; + +//============================================================================ +const float NO_DAMAGE = 1; +void() trigger_explosion_activate = +{ + self.delay = 0; + SUB_UseTargets (); + + if (!(self.spawnflags & NO_DAMAGE)) + T_RadiusDamage (self, self.owner, 120, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion(); +} + +void() trigger_explosion_use = +{ + if(self.delay > 0) + { + self.think = trigger_explosion_activate; + self.nextthink = time + self.delay; + } + else + { + trigger_explosion_activate(); + } +} + +void() trigger_explosion = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + self.use = trigger_explosion_use; +}; + +// ====================================================== +float REPEATER_ON = 1; +void() repeater_think = +{ + SUB_UseTargets(); + self.nextthink = time + self.wait + (self.pausetime * random()); +}; + +void() repeater_use = +{ + if (self.spawnflags & REPEATER_ON) + { + // turn off + self.spawnflags = self.spawnflags - REPEATER_ON; + self.nextthink = 0; + self.think = SUB_Null; + } + else + { + // turn on + self.spawnflags = self.spawnflags + REPEATER_ON; + self.nextthink = time + self.wait + (self.pausetime * random()); + self.think = repeater_think; + } +}; + +void() trigger_repeater = +{ + INHIBIT_COOP + if (RemovedOutsideCoop()) return; + + if (!self.wait) + { + self.wait = 1; + } + self.use = repeater_use; + if (self.spawnflags & REPEATER_ON) + { + self.nextthink = time + self.wait + (self.pausetime * random()); + self.think = repeater_think; + } + else + { + self.nextthink = 0; + self.think = SUB_Null; + } +}; + +// ====================================================== +/*QUAKED trigger_multitouch (.5 .5 .5) ? +A trigger that fires its targets on "first touch", "emptied" or both + +YODER Feb08, 2022 +*/ +float IGNORE_FIRST_TOUCH = 16; +float IGNORE_EMPTIED = 32; + +void() trigger_multitouch_think = +{ + bprint("untouched\n"); + self.wait = 0; + if (!(self.spawnflags & IGNORE_EMPTIED)) + SUB_UseTargets(); +}; + +void() trigger_multitouch_touch = +{ + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + if (self.wait == 0) // first touch? + { + self.wait = 1; + bprint(" first touch\n"); + if (!(self.spawnflags & IGNORE_FIRST_TOUCH)) + SUB_UseTargets(); + } + + // think + self.think = trigger_multitouch_think; + self.nextthink = time + 0.2; +}; + +void() trigger_multitouch = +{ + InitTrigger (); + self.touch = trigger_multitouch_touch; + self.wait = 0; // 0 is empty, 1 is touched +}; \ No newline at end of file diff --git a/quakec_mg1/weapons.qc b/quakec_mg1/weapons.qc new file mode 100644 index 0000000..a669a65 --- /dev/null +++ b/quakec_mg1/weapons.qc @@ -0,0 +1,1503 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +*/ +void (entity targ, entity inflictor, entity attacker, float damage) T_Damage; +void () player_run; +void(entity bomb, entity attacker, float rad, entity ignore) T_RadiusDamage; +void(vector org, vector vel, float damage) SpawnBlood; +void() SuperDamageSound; + + +// called by worldspawn +void() W_Precache = +{ + precache_sound ("weapons/r_exp3.wav"); // new rocket explosion + precache_sound ("weapons/rocket1i.wav"); // spike gun + precache_sound ("weapons/sgun1.wav"); + precache_sound ("weapons/guncock.wav"); // player shotgun + precache_sound ("weapons/ric1.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric2.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric3.wav"); // ricochet (used in c code) + precache_sound ("weapons/spike2.wav"); // super spikes + precache_sound ("weapons/tink1.wav"); // spikes tink (used in c code) + precache_sound ("weapons/grenade.wav"); // grenade launcher + precache_sound ("weapons/bounce.wav"); // grenade bounce + precache_sound ("weapons/shotgn2.wav"); // super shotgun +}; + +float() crandom = +{ + return 2*(random() - 0.5); +}; + + +/* +================ +W_FireAxe +================ +*/ +void() W_FireAxe = +{ + local vector source; + local vector org; + + makevectors (self.v_angle); + source = self.origin + '0 0 16'; + + traceline (source, source + v_forward*64, FALSE, self); + if (trace_fraction == 1.0) + return; + org = trace_endpos - v_forward*4; + + if (trace_ent.takedamage && trace_ent.classname != "monster_boss") // yoder mod, Jan 05 2021 + { + trace_ent.axhitme = 1; + SpawnBlood (org, '0 0 0', 20); + T_Damage (trace_ent, self, self, 20); + } + else + { // hit wall + sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + + +//============================================================================ + + +vector() wall_velocity = +{ + local vector vel; + + vel = normalize (self.velocity); + vel = normalize(vel + v_up*(random()- 0.5) + v_right*(random()- 0.5)); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + return vel; +}; + + +/* +================ +SpawnMeatSpray +================ +*/ +void(vector org, vector vel) SpawnMeatSpray = +{ + local entity missile; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_NOT; + + makevectors (self.angles); + + missile.velocity = vel; + missile.velocity_z = missile.velocity_z + 250 + 50*random(); + + missile.avelocity = '3000 1000 2000'; + +// set missile duration + missile.nextthink = time + 1; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + +/* +================ +SpawnBlood +================ +*/ +void(vector org, vector vel, float damage) SpawnBlood = +{ + particle (org, vel*0.1, 73, damage*2); +}; + +/* +================ +spawn_touchblood +================ +*/ +void(float damage) spawn_touchblood = +{ + local vector vel; + + vel = wall_velocity () * 0.2; + SpawnBlood (self.origin + vel*0.01, vel, damage); +}; + + +/* +================ +SpawnChunk +================ +*/ +void(vector org, vector vel) SpawnChunk = +{ + particle (org, vel*0.02, 0, 10); +}; + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +entity multi_ent; +float multi_damage; + +void() ClearMultiDamage = +{ + multi_ent = world; + multi_damage = 0; +}; + +void() ApplyMultiDamage = +{ + if (!multi_ent) + return; + T_Damage (multi_ent, self, self, multi_damage); +}; + +void(entity hit, float damage) AddMultiDamage = +{ + if (!hit) + return; + + if (hit != multi_ent) + { + ApplyMultiDamage (); + multi_damage = damage; + multi_ent = hit; + } + else + multi_damage = multi_damage + damage; +}; + +/* +============================================================================== + +BULLETS + +============================================================================== +*/ + +/* +================ +TraceAttack +================ +*/ +void(float damage, vector dir) TraceAttack = +{ + local vector vel, org; + + vel = normalize(dir + v_up*crandom() + v_right*crandom()); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + org = trace_endpos - dir*4; + + if (trace_ent.takedamage && trace_ent.classname != "monster_boss") // yoder mod, Jan 05 2021 + { + SpawnBlood (org, vel*0.2, damage); + AddMultiDamage (trace_ent, damage); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + +/* +================ +FireBullets + +Used by shotgun, super shotgun, and enemy soldier firing +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void(float shotcount, vector dir, vector spread) FireBullets = +{ + local vector direction; + local vector src; + + makevectors(self.v_angle); + + src = self.origin + v_forward*10; + src_z = self.absmin_z + self.size_z * 0.7; + + ClearMultiDamage (); + while (shotcount > 0) + { + direction = dir + crandom()*spread_x*v_right + crandom()*spread_y*v_up; + + traceline (src, src + direction*2048, FALSE, self); + if (trace_fraction != 1.0) + TraceAttack (4, direction); + + shotcount = shotcount - 1; + } + ApplyMultiDamage (); +}; + +/* +================ +W_FireShotgun +================ +*/ +void() W_FireShotgun = +{ + local vector dir; + + sound (self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + self.currentammo = self.ammo_shells = self.ammo_shells - 1; + dir = aim (self, 100000); + FireBullets (6, dir, '0.04 0.04 0'); +}; + + +/* +================ +W_FireSuperShotgun +================ +*/ +void() W_FireSuperShotgun = +{ + local vector dir; + + if (self.currentammo == 1) + { + W_FireShotgun (); + return; + } + + sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM); + + self.punchangle_x = -4; + + self.currentammo = self.ammo_shells = self.ammo_shells - 2; + dir = aim (self, 100000); + FireBullets (14, dir, '0.14 0.08 0'); +}; + + +/* +============================================================================== + +ROCKETS + +============================================================================== +*/ + +void() s_explode1 = [0, s_explode2] {}; +void() s_explode2 = [1, s_explode3] {}; +void() s_explode3 = [2, s_explode4] {}; +void() s_explode4 = [3, s_explode5] {}; +void() s_explode5 = [4, s_explode6] {}; +void() s_explode6 = [5, SUB_Remove] {}; + +void() BecomeExplosion = +{ + self.movetype = MOVETYPE_NONE; + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +void() T_MissileTouch = +{ + local float damg; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + damg = 100 + random()*20; + + if (other.health) + { + if (other.classname == "monster_shambler") + damg = damg * 0.5; // mostly immune + T_Damage (other, self, self.owner, damg ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact + T_RadiusDamage (self, self.owner, 120, other); + +// sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + + + +/* +================ +W_FireRocket +================ +*/ +void() W_FireRocket = +{ + local entity missile; + + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.solid = SOLID_BBOX; + missile.classname = "missile"; + +// set missile speed + + makevectors (self.v_angle); + missile.velocity = aim(self, 1000); + missile.velocity = missile.velocity * 1000; + missile.angles = vectoangles(missile.velocity); + + missile.touch = T_MissileTouch; + +// set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/missile.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + v_forward*8 + '0 0 16'); +}; + +/* +=============================================================================== + +LIGHTNING + +=============================================================================== +*/ + +/* +================= +LightningDamage +================= +*/ +void(vector p1, vector p2, entity from, float damage) LightningDamage = +{ + local entity e1, e2; + local vector f; + + f = p2 - p1; + normalize (f); + f_x = 0 - f_y; + f_y = f_x; + f_z = 0; + f = f*16; + + e1 = e2 = world; + + traceline (p1, p2, FALSE, self); + if (trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + if (self.classname == "player") + { + if (other.classname == "player") + trace_ent.velocity_z = trace_ent.velocity_z + 400; + } + } + e1 = trace_ent; + + traceline (p1 + f, p2 + f, FALSE, self); + if (trace_ent != e1 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } + e2 = trace_ent; + + traceline (p1 - f, p2 - f, FALSE, self); + if (trace_ent != e1 && trace_ent != e2 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } +}; + + +void() W_FireLightning = +{ + local vector org; + local float cells; + + if (self.ammo_cells < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + +// explode if under water + if (self.waterlevel > 1) + { + cells = self.ammo_cells; + self.ammo_cells = 0; + W_SetCurrentAmmo (); + T_RadiusDamage (self, self, 35*cells, world); + return; + } + + if (self.t_width < time) + { + sound (self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + self.t_width = time + 0.6; + } + self.punchangle_x = -2; + + self.currentammo = self.ammo_cells = self.ammo_cells - 1; + + org = self.origin + '0 0 16'; + + traceline (org, org + v_forward*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + LightningDamage (self.origin, trace_endpos + v_forward*4, self, 30); +}; + + +//============================================================================= + + +void() GrenadeExplode = +{ + T_RadiusDamage (self, self.owner, self.dmg, world); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +void GrenadeThink() +{ + self.cnt += 1; + // Count to 50 at 20 ticks per second = 2.5 seconds until explosion + if(self.cnt == 50) + { + GrenadeExplode(); + return; + } + float oldVel = vlen(self.oldorigin); + float vel = vlen(self.velocity); + if(vel == 0) //Stuck on downward slope? + { + if(oldVel > 120 && self.oldorigin_z < 0) //Were we moving downwards at a decent speed last tick? + { + // Trace straight down and find the normal of the surface it bounced off + traceline(self.origin + '0 0 4', self.origin - '0 0 8', TRUE, self); + if(trace_fraction < 1.0) // Hit something... + { + // dprint("reflected grenade bounce\n"); + // Reflect the previous velocity off that normal + vector reflected = self.oldorigin - 2 * (self.oldorigin * trace_plane_normal) * trace_plane_normal; + + self.flags &~= FL_ONGROUND; //Remove FL_ONGROUND + self.velocity = reflected * 0.6; // Scale it down a bit so it feels better + self.avelocity = '400 400 400'; // Roll that sucker around + } + } + } + self.oldorigin = self.velocity; + self.nextthink = time + 0.05; +} + +void() GrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage == DAMAGE_AIM) + { + GrenadeExplode(); + return; + } + if(self.velocity == '0 0 0') + { + self.avelocity = '0 0 0'; + } + if(self.attack_finished < time) + sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + self.attack_finished = time + 0.1; +}; + +/* +================ +W_FireGrenade +================ +*/ +void W_FireGrenade(float damage, vector direction); +void W_FireGrenadeLauncher() +{ + float damage = 120; + makevectors (self.v_angle); + + vector velocity; + if (self.v_angle_x) + velocity = v_forward*600 + v_up * 200 + crandom()*v_right*10 + crandom()*v_up*10; + else + { + velocity = aim(self, 10000); + velocity = velocity * 600; + velocity_z = 200; + } + W_FireGrenade(damage, velocity) ; +} + +void W_FireGrenade(float damage, vector direction) +{ + local entity missile; + + self.currentammo = self.ammo_rockets = self.ammo_rockets - 1; + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + missile.classname = "grenade"; + missile.dmg = damage; + + // set missile speed + + missile.velocity = direction; + missile.oldorigin = direction; + missile.avelocity = '300 300 300'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = GrenadeTouch; + + #ifdef GRENADE_BOUNCE_FIX + // set missile duration + missile.nextthink = time + 0.05; + missile.think = GrenadeThink; + #else + // Uncomment this to go back to old grenade behaviour with no slope bounce fix. + missile.nextthink = time + 2.5; + missile.think = GrenadeExplode; + #endif //GRENADE_BOUNCE_FIX + + + setmodel (missile, "progs/grenade.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); +}; + + +//============================================================================= + +void() spike_touch; +void() superspike_touch; + + +/* +=============== +launch_spike + +Used for both the player and the ogre +=============== +*/ +void(vector org, vector dir) launch_spike = +{ + newmis = spawn (); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + + newmis.angles = vectoangles(dir); + + newmis.touch = spike_touch; + newmis.classname = "spike"; + newmis.think = SUB_Remove; + newmis.nextthink = time + 6; + setmodel (newmis, "progs/spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + setorigin (newmis, org); + + newmis.velocity = dir * 1000; +}; + +void() W_FireSuperSpikes = +{ + local vector dir; + + sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_nails = self.ammo_nails - 2; + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16', dir); + newmis.touch = superspike_touch; + setmodel (newmis, "progs/s_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + self.punchangle_x = -2; +}; + +void(float ox) W_FireSpikes = +{ + local vector dir; + + makevectors (self.v_angle); + + if (self.ammo_nails >= 2 && self.weapon == IT_SUPER_NAILGUN) + { + W_FireSuperSpikes (); + return; + } + + if (self.ammo_nails < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_nails = self.ammo_nails - 1; + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16' + v_right*ox, dir); + + self.punchangle_x = -2; +}; + + + +.float hit_z; +void() spike_touch = +{ + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage && other.classname != "monster_boss") // yoder mod, jan 05 2021 + { + spawn_touchblood (9); + T_Damage (other, self, self.owner, 9); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + + if (self.classname == "wizspike") + WriteByte (MSG_BROADCAST, TE_WIZSPIKE); + else if (self.classname == "knightspike") + WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE); + else + WriteByte (MSG_BROADCAST, TE_SPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + +void() superspike_touch = +{ + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage && other.classname != "monster_boss") // yoder mod, Jan 05 2021 + { + spawn_touchblood (18); + T_Damage (other, self, self.owner, 18); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_SUPERSPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + + +/* +=============================================================================== + +PLAYER WEAPON USE + +=============================================================================== +*/ + +void() W_SetCurrentAmmo = +{ + player_run (); // get out of any weapon firing states + + self.items = self.items - ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS) ); + + if (self.weapon == IT_AXE) + { + self.currentammo = 0; + self.weaponmodel = "progs/v_axe.mdl"; + self.weaponframe = 0; + } + else if (self.weapon == IT_SHOTGUN) + { + self.currentammo = self.ammo_shells; + self.weaponmodel = "progs/v_shot.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.currentammo = self.ammo_shells; + self.weaponmodel = "progs/v_shot2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_NAILGUN) + { + self.currentammo = self.ammo_nails; + self.weaponmodel = "progs/v_nail.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_NAILS; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.currentammo = self.ammo_nails; + self.weaponmodel = "progs/v_nail2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_NAILS; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.currentammo = self.ammo_rockets; + self.weaponmodel = "progs/v_rock.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.currentammo = self.ammo_rockets; + self.weaponmodel = "progs/v_rock2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_LIGHTNING) + { + self.currentammo = self.ammo_cells; + self.weaponmodel = "progs/v_light.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_CELLS; + } + else + { + self.currentammo = 0; + self.weaponmodel = string_null; + self.weaponframe = 0; + } +}; + +float() W_BestWeapon = +{ + local float it; + + it = self.items; + + if (self.waterlevel <= 1 && self.ammo_cells >= 1 && (it & IT_LIGHTNING) ) + return IT_LIGHTNING; + if(self.ammo_nails >= 2 && (it & IT_SUPER_NAILGUN) ) + return IT_SUPER_NAILGUN; + if(self.ammo_shells >= 2 && (it & IT_SUPER_SHOTGUN) ) + return IT_SUPER_SHOTGUN; + if(self.ammo_nails >= 1 && (it & IT_NAILGUN) ) + return IT_NAILGUN; + if(self.ammo_shells >= 1 && (it & IT_SHOTGUN) ) + return IT_SHOTGUN; + if(it & IT_AXE) + return IT_AXE; + //No weapon + return 0; +}; + +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon = +{ + float playerFlags = CheckPlayerEXFlags(playerEnt); + if((playerFlags & PEF_CHANGENEVER) != 0) + { + return 0; + } + + if(((playerFlags & PEF_CHANGEONLYNEW) != 0) && old == new) + { + return 0; + } + + return 1; +}; + +float() W_CheckNoAmmo = +{ + if (self.currentammo > 0) + return TRUE; + + if (self.weapon == IT_AXE) + return TRUE; + + self.weapon = W_BestWeapon (); + + W_SetCurrentAmmo (); + +// drop the weapon down + return FALSE; +}; + +/* +============ +W_Attack + +An attack impulse can be triggered now +============ +*/ +void() player_axe1; +void() player_axeb1; +void() player_axec1; +void() player_axed1; +void() player_shot1; +void() player_nail1; +void() player_light1; +void() player_rocket1; + +void() W_Attack = +{ + local float r; + + if (!self.weapon) + return; + + if (!W_CheckNoAmmo ()) + return; + + makevectors (self.v_angle); // calculate forward angle for velocity + self.show_hostile = time + 1; // wake monsters up + + if (self.weapon == IT_AXE) + { + sound (self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); + r = random(); + + if(cvar("horde") && (serverflags & SIGIL_E3)) // horde rune behavior + { + if ((self.axe_hit_chain >= 2) && (time < self.axe_hit_chain_time)) + { + dprint("vertical chop: "); + // vertical CHOP + if (r < 0.5) + { + dprint(" axe1\n"); + player_axe1 (); + } + else + { + dprint(" axec1\n"); + player_axec1 (); + } + self.attack_finished = time + 0.8; // long recover + } + else + { + dprint("diagonal sweep:"); + // diagonal SWEEP + if (r < 0.5) + { + dprint(" axeb1\n"); + player_axeb1 (); + } + else + { + dprint(" axed1\n"); + player_axed1 (); + } + + if (self.axe_hit_chain > 1) + self.attack_finished = time + 0.6; + else + self.attack_finished = time + 0.4; + + } + } + else // normal behavior + { + r = random(); + if (r < 0.25) + { + dprint("axe1\n"); + player_axe1 (); + } + else if (r<0.5) + { + dprint("axeb1\n"); + player_axeb1 (); + } + else if (r<0.75) + { + dprint("axec1\n"); + player_axec1 (); + } + else + { + dprint("axed1\n"); + player_axed1 (); + } + self.attack_finished = time + 0.5; + } + + } + else if (self.weapon == IT_SHOTGUN) + { + player_shot1 (); + W_FireShotgun (); + self.attack_finished = time + 0.5; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + player_shot1 (); + W_FireSuperShotgun (); + self.attack_finished = time + 0.7; + } + else if (self.weapon == IT_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + player_rocket1(); + W_FireGrenadeLauncher(); + self.attack_finished = time + 0.6; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + player_rocket1(); + W_FireRocket(); + self.attack_finished = time + 0.8; + } + else if (self.weapon == IT_LIGHTNING) + { + player_light1(); + self.attack_finished = time + 0.1; + sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + } +}; + +/* +============ +W_ChangeWeapon + +============ +*/ +void() W_ChangeWeapon = +{ + local float it, am, fl; + + it = self.items; + am = 0; + + if (self.impulse == 1) + { + fl = IT_AXE; + } + else if (self.impulse == 2) + { + fl = IT_SHOTGUN; + if (self.ammo_shells < 1) + am = 1; + } + else if (self.impulse == 3) + { + fl = IT_SUPER_SHOTGUN; + if (self.ammo_shells < 2) + am = 1; + } + else if (self.impulse == 4) + { + fl = IT_NAILGUN; + if (self.ammo_nails < 1) + am = 1; + } + else if (self.impulse == 5) + { + fl = IT_SUPER_NAILGUN; + if (self.ammo_nails < 2) + am = 1; + } + else if (self.impulse == 6) + { + fl = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.impulse == 7) + { + fl = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.impulse == 8) + { + fl = IT_LIGHTNING; + if (self.ammo_cells < 1) + am = 1; + } + + self.impulse = 0; + + if (!(self.items & fl)) + { // don't have the weapon or the ammo + sprint (self, "$qc_no_weapon"); + return; + } + + if (am) + { // don't have the ammo + sprint (self, "$qc_not_enough_ammo"); + return; + } + +// +// set weapon, set ammo +// + self.weapon = fl; + W_SetCurrentAmmo (); +}; + +/* +============ +CheatCommand +============ +*/ +void(float NoKeys = FALSE) CheatCommand = +{ + if ( ( deathmatch || coop ) && cheats_allowed == 0 ) { // mal: allow impulse 9 IF cheats allowed + return; + } + + self.ammo_rockets = 100; + self.ammo_nails = 200; + self.ammo_shells = 100; + if(NoKeys) + { + self.items = self.items | + IT_AXE | + IT_SHOTGUN | + IT_SUPER_SHOTGUN | + IT_NAILGUN | + IT_SUPER_NAILGUN | + IT_GRENADE_LAUNCHER | + IT_ROCKET_LAUNCHER; + } + else + { + self.items = self.items | + IT_AXE | + IT_SHOTGUN | + IT_SUPER_SHOTGUN | + IT_NAILGUN | + IT_SUPER_NAILGUN | + IT_GRENADE_LAUNCHER | + IT_ROCKET_LAUNCHER | + IT_KEY1 | IT_KEY2; + } + + self.ammo_cells = 200; + self.items = self.items | IT_LIGHTNING; + + self.weapon = IT_ROCKET_LAUNCHER; + self.impulse = 0; + W_SetCurrentAmmo (); +}; + +/* +============ +CycleWeaponCommand + +Go to the next weapon with ammo +============ +*/ +void() CycleWeaponCommand = +{ + local float it, am, wep; + + it = self.items; + wep = self.weapon; + self.impulse = 0; + + float cnt = 10; + + while (cnt) + { + am = 0; + + if(!wep) + { + wep = IT_AXE; + } + else if (wep == IT_LIGHTNING) + { + wep = IT_AXE; + } + else if (wep == IT_AXE) + { + wep = IT_SHOTGUN; + if (self.ammo_shells < 1) + am = 1; + } + else if (wep == IT_SHOTGUN) + { + wep = IT_SUPER_SHOTGUN; + if (self.ammo_shells < 2) + am = 1; + } + else if (wep == IT_SUPER_SHOTGUN) + { + wep = IT_NAILGUN; + if (self.ammo_nails < 1) + am = 1; + } + else if (wep == IT_NAILGUN) + { + wep = IT_SUPER_NAILGUN; + if (self.ammo_nails < 2) + am = 1; + } + else if (wep == IT_SUPER_NAILGUN) + { + wep = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (wep == IT_GRENADE_LAUNCHER) + { + wep = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (wep == IT_ROCKET_LAUNCHER) + { + wep = IT_LIGHTNING; + if (self.ammo_cells < 1) + am = 1; + } + + if ( (it & wep) && am == 0) + { + self.weapon = wep; + W_SetCurrentAmmo (); + return; + } + + cnt--; + } + +}; + +/* +============ +CycleWeaponReverseCommand + +Go to the prev weapon with ammo +============ +*/ +void() CycleWeaponReverseCommand = +{ + local float it, am, wep; + + it = self.items; + wep = self.weapon; + self.impulse = 0; + + float cnt = 10; + + while (cnt) + { + am = 0; + + if(!wep) + { + wep = IT_LIGHTNING; + } + else if (wep == IT_LIGHTNING) + { + wep = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (wep == IT_ROCKET_LAUNCHER) + { + wep = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + else if (wep == IT_GRENADE_LAUNCHER) + { + wep = IT_SUPER_NAILGUN; + if (self.ammo_nails < 2) + am = 1; + } + else if (wep == IT_SUPER_NAILGUN) + { + wep = IT_NAILGUN; + if (self.ammo_nails < 1) + am = 1; + } + else if (wep == IT_NAILGUN) + { + wep = IT_SUPER_SHOTGUN; + if (self.ammo_shells < 2) + am = 1; + } + else if (wep == IT_SUPER_SHOTGUN) + { + wep = IT_SHOTGUN; + if (self.ammo_shells < 1) + am = 1; + } + else if (wep == IT_SHOTGUN) + { + wep = IT_AXE; + } + else if (wep == IT_AXE) + { + wep = IT_LIGHTNING; + if (self.ammo_cells < 1) + am = 1; + } + + if ( (it & wep) && am == 0) + { + self.weapon = wep; + W_SetCurrentAmmo (); + return; + } + + cnt--; + } + +}; + +/* +============ +ServerflagsCommand + +Just for development +============ +*/ +void() ServerflagsCommand = +{ + serverflags = serverflags * 2 + 1; +}; + +void() QuadCheat = +{ + if (deathmatch || coop) + return; + self.super_time = 1; + self.super_damage_finished = time + 30; + self.items = self.items | IT_QUAD; + dprint ("quad cheat\n"); +}; + +void() Omnicide = +{ + entity oself = self; + self = nextent(world); + while(self) + { + if(self.flags & (FL_MONSTER | FL_FUTUREMONSTER)) + { + if(self.target || self.killtarget) + { + SUB_UseTargets(); + } + SUB_Remove(); + } + self = nextent(self); + } + self = oself; + + killed_monsters = total_monsters; + WriteByte (MSG_ALL, SVC_UPDATESTAT); + WriteByte(MSG_ALL, 14); // 14 = STAT_MONSTERS + WriteLong(MSG_ALL, total_monsters); +} + +/* +============ +ImpulseCommands + +============ +*/ +void() ImpulseCommands = +{ + if (self.impulse >= 1 && self.impulse <= 8) + W_ChangeWeapon (); + + else if (self.impulse == 9) + CheatCommand (); + else if (self.impulse == 99) + CheatCommand (TRUE); + else if (self.impulse == 10) + CycleWeaponCommand (); + else if (self.impulse == 11) + ServerflagsCommand (); + else if (self.impulse == 12) + CycleWeaponReverseCommand (); + + else if (self.impulse == 255) + QuadCheat (); + + else if (self.impulse == 219) + Omnicide (); + + self.impulse = 0; +}; + +/* +============ +W_WeaponFrame + +Called every frame so impulse events can be handled as well as possible +============ +*/ +void() W_WeaponFrame = +{ + if (time < self.attack_finished) + return; + + ImpulseCommands (); + +// check for attack + if (self.button0) + { + SuperDamageSound (); + W_Attack (); + } +}; + +/* +======== +SuperDamageSound + +Plays sound if needed +======== +*/ +void() SuperDamageSound = +{ + if (self.super_damage_finished > time) + { + if (self.super_sound < time) + { + self.super_sound = time + 1; + sound (self, CHAN_BODY, "items/damage3.wav", 1, ATTN_NORM); + } + } + return; +}; \ No newline at end of file diff --git a/quakec_mg1/world.qc b/quakec_mg1/world.qc new file mode 100644 index 0000000..41db13a --- /dev/null +++ b/quakec_mg1/world.qc @@ -0,0 +1,407 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Yoder Sept24, Horde Merge +void() PrecacheMonsters; + +void() InitBodyQue; +void() InitLightStyles; + +void() main = +{ + dprint ("main function\n"); + +// these are just commands the the prog compiler to copy these files + + precache_file ("progs.dat"); + precache_file ("gfx.wad"); + precache_file ("quake.rc"); + precache_file ("default.cfg"); + + precache_file ("end1.bin"); + precache_file2 ("end2.bin"); + + precache_file ("demo1.dem"); + precache_file ("demo2.dem"); + precache_file ("demo3.dem"); + +// +// these are all of the lumps from the cached.ls files +// + precache_file ("gfx/palette.lmp"); + precache_file ("gfx/colormap.lmp"); + + precache_file2 ("gfx/pop.lmp"); + + precache_file ("gfx/complete.lmp"); + precache_file ("gfx/inter.lmp"); + + precache_file ("gfx/ranking.lmp"); + precache_file ("gfx/vidmodes.lmp"); + precache_file ("gfx/finale.lmp"); + precache_file ("gfx/conback.lmp"); + precache_file ("gfx/qplaque.lmp"); + + precache_file ("gfx/menudot1.lmp"); + precache_file ("gfx/menudot2.lmp"); + precache_file ("gfx/menudot3.lmp"); + precache_file ("gfx/menudot4.lmp"); + precache_file ("gfx/menudot5.lmp"); + precache_file ("gfx/menudot6.lmp"); + + precache_file ("gfx/menuplyr.lmp"); + precache_file ("gfx/bigbox.lmp"); + precache_file ("gfx/dim_modm.lmp"); + precache_file ("gfx/dim_drct.lmp"); + precache_file ("gfx/dim_ipx.lmp"); + precache_file ("gfx/dim_tcp.lmp"); + precache_file ("gfx/dim_mult.lmp"); + precache_file ("gfx/mainmenu.lmp"); + + precache_file ("gfx/box_tl.lmp"); + precache_file ("gfx/box_tm.lmp"); + precache_file ("gfx/box_tr.lmp"); + + precache_file ("gfx/box_ml.lmp"); + precache_file ("gfx/box_mm.lmp"); + precache_file ("gfx/box_mm2.lmp"); + precache_file ("gfx/box_mr.lmp"); + + precache_file ("gfx/box_bl.lmp"); + precache_file ("gfx/box_bm.lmp"); + precache_file ("gfx/box_br.lmp"); + + precache_file ("gfx/sp_menu.lmp"); + precache_file ("gfx/ttl_sgl.lmp"); + precache_file ("gfx/ttl_main.lmp"); + precache_file ("gfx/ttl_cstm.lmp"); + + precache_file ("gfx/mp_menu.lmp"); + + precache_file ("gfx/netmen1.lmp"); + precache_file ("gfx/netmen2.lmp"); + precache_file ("gfx/netmen3.lmp"); + precache_file ("gfx/netmen4.lmp"); + precache_file ("gfx/netmen5.lmp"); + + precache_file ("gfx/sell.lmp"); + + precache_file ("gfx/help0.lmp"); + precache_file ("gfx/help1.lmp"); + precache_file ("gfx/help2.lmp"); + precache_file ("gfx/help3.lmp"); + precache_file ("gfx/help4.lmp"); + precache_file ("gfx/help5.lmp"); + + precache_file ("gfx/pause.lmp"); + precache_file ("gfx/loading.lmp"); + + precache_file ("gfx/p_option.lmp"); + precache_file ("gfx/p_load.lmp"); + precache_file ("gfx/p_save.lmp"); + precache_file ("gfx/p_multi.lmp"); + +// sounds loaded by C code + precache_sound ("misc/menu1.wav"); + precache_sound ("misc/menu2.wav"); + precache_sound ("misc/menu3.wav"); + + precache_sound ("ambience/water1.wav"); + precache_sound ("ambience/wind2.wav"); + +// shareware + precache_file ("maps/start.bsp"); + + precache_file ("maps/e1m1.bsp"); + precache_file ("maps/e1m2.bsp"); + precache_file ("maps/e1m3.bsp"); + precache_file ("maps/e1m4.bsp"); + precache_file ("maps/e1m5.bsp"); + precache_file ("maps/e1m6.bsp"); + precache_file ("maps/e1m7.bsp"); + precache_file ("maps/e1m8.bsp"); + +// registered + precache_file2 ("gfx/pop.lmp"); + + precache_file2 ("maps/e2m1.bsp"); + precache_file2 ("maps/e2m2.bsp"); + precache_file2 ("maps/e2m3.bsp"); + precache_file2 ("maps/e2m4.bsp"); + precache_file2 ("maps/e2m5.bsp"); + precache_file2 ("maps/e2m6.bsp"); + precache_file2 ("maps/e2m7.bsp"); + + precache_file2 ("maps/e3m1.bsp"); + precache_file2 ("maps/e3m2.bsp"); + precache_file2 ("maps/e3m3.bsp"); + precache_file2 ("maps/e3m4.bsp"); + precache_file2 ("maps/e3m5.bsp"); + precache_file2 ("maps/e3m6.bsp"); + precache_file2 ("maps/e3m7.bsp"); + + precache_file2 ("maps/e4m1.bsp"); + precache_file2 ("maps/e4m2.bsp"); + precache_file2 ("maps/e4m3.bsp"); + precache_file2 ("maps/e4m4.bsp"); + precache_file2 ("maps/e4m5.bsp"); + precache_file2 ("maps/e4m6.bsp"); + precache_file2 ("maps/e4m7.bsp"); + precache_file2 ("maps/e4m8.bsp"); + + precache_file2 ("maps/end.bsp"); + + precache_file2 ("maps/dm1.bsp"); + precache_file2 ("maps/dm2.bsp"); + precache_file2 ("maps/dm3.bsp"); + precache_file2 ("maps/dm4.bsp"); + precache_file2 ("maps/dm5.bsp"); + precache_file2 ("maps/dm6.bsp"); +}; + + +entity lastspawn; + +//======================= +/*QUAKED worldspawn (0 0 0) ? +Only used for the world entity. +Set message to the level name. +Set sounds to the cd track to play. + +World Types: +0: medieval +1: metal +2: base +*/ +//======================= +float startingserverflags; + +void() worldspawn = +{ + startingserverflags = serverflags; + lastspawn = world; + InitBodyQue (); + +// custom map attributes + if (self.model == "maps/e1m8.bsp") + cvar_set ("sv_gravity", "100"); + else + cvar_set ("sv_gravity", "800"); + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + +// sounds used from C physics code + precache_sound ("demon/dland2.wav"); // landing thud + precache_sound ("misc/h2ohit1.wav"); // landing splash + +// setup precaches allways needed + precache_sound ("items/itembk2.wav"); // item respawn sound + precache_sound ("player/plyrjmp8.wav"); // player jump + precache_sound ("player/land.wav"); // player landing + precache_sound ("player/land2.wav"); // player hurt landing + precache_sound ("player/drown1.wav"); // drowning pain + precache_sound ("player/drown2.wav"); // drowning pain + precache_sound ("player/gasp1.wav"); // gasping for air + precache_sound ("player/gasp2.wav"); // taking breath + precache_sound ("player/h2odeath.wav"); // drowning death + + precache_sound ("misc/talk.wav"); // talk + precache_sound ("player/teledth1.wav"); // telefrag + precache_sound ("misc/r_tele1.wav"); // teleport sounds + precache_sound ("misc/r_tele2.wav"); + precache_sound ("misc/r_tele3.wav"); + precache_sound ("misc/r_tele4.wav"); + precache_sound ("misc/r_tele5.wav"); + precache_sound ("weapons/lock4.wav"); // ammo pick up + precache_sound ("weapons/pkup.wav"); // weapon up + precache_sound ("items/armor1.wav"); // armor up + precache_sound ("weapons/lhit.wav"); //lightning + precache_sound ("weapons/lstart.wav"); //lightning start + precache_sound ("items/damage3.wav"); + + precache_sound ("misc/power.wav"); //lightning for boss + +// player gib sounds + precache_sound ("player/gib.wav"); // player gib sound + precache_sound ("player/udeath.wav"); // player gib sound + precache_sound ("player/tornoff2.wav"); // gib sound + +// player pain sounds + + precache_sound ("player/pain1.wav"); + precache_sound ("player/pain2.wav"); + precache_sound ("player/pain3.wav"); + precache_sound ("player/pain4.wav"); + precache_sound ("player/pain5.wav"); + precache_sound ("player/pain6.wav"); + +// player death sounds + precache_sound ("player/death1.wav"); + precache_sound ("player/death2.wav"); + precache_sound ("player/death3.wav"); + precache_sound ("player/death4.wav"); + precache_sound ("player/death5.wav"); + +// ax sounds + precache_sound ("weapons/ax1.wav"); // ax swoosh + precache_sound ("player/axhit1.wav"); // ax hit meat + precache_sound ("player/axhit2.wav"); // ax hit world + + precache_sound ("player/h2ojump.wav"); // player jumping into water + precache_sound ("player/slimbrn2.wav"); // player enter slime + precache_sound ("player/inh2o.wav"); // player enter water + precache_sound ("player/inlava.wav"); // player enter lava + precache_sound ("misc/outwater.wav"); // leaving water sound + + precache_sound ("player/lburn1.wav"); // lava burn + precache_sound ("player/lburn2.wav"); // lava burn + + precache_sound ("misc/water1.wav"); // swimming + precache_sound ("misc/water2.wav"); // swimming + + precache_model ("progs/player.mdl"); + precache_model ("progs/eyes.mdl"); + precache_model ("progs/h_player.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_model ("progs/s_bubble.spr"); // drowning bubbles + precache_model ("progs/s_explod.spr"); // sprite explosion + + precache_model ("progs/v_axe.mdl"); + precache_model ("progs/v_shot.mdl"); + precache_model ("progs/v_nail.mdl"); + precache_model ("progs/v_rock.mdl"); + precache_model ("progs/v_shot2.mdl"); + precache_model ("progs/v_nail2.mdl"); + precache_model ("progs/v_rock2.mdl"); + + precache_model ("progs/bolt.mdl"); // for lightning gun + precache_model ("progs/bolt2.mdl"); // for lightning gun + precache_model ("progs/bolt3.mdl"); // for boss shock + precache_model ("progs/lavaball.mdl"); // for testing + + precache_model ("progs/missile.mdl"); + precache_model ("progs/grenade.mdl"); + precache_model ("progs/spike.mdl"); + precache_model ("progs/s_spike.mdl"); + + precache_model ("progs/backpack.mdl"); + + precache_model ("progs/zom_gib.mdl"); + + precache_model ("progs/v_light.mdl"); + + // Yoder Sept24, 2021, Horde Merge + /* + if (horde) + { + dprint("precaching monsters\n"); + PrecacheMonsters(); + } + else + dprint("Not horde. Don't precache horde stuff.\n"); + */ + + // Moved to lights.qc + InitLightStyles(); +}; + +void LoadGame(); + +float LastTime; + +void() StartFrame = +{ + teamplay = cvar("teamplay"); + skill = cvar("skill"); + cheats_allowed = cvar("sv_cheats"); + isHordeMode = cvar( "horde" ); + framecount = framecount + 1; + float DeltaTime = time - LastTime; + LastTime = time; + + // save off a global here so it gets included in savegames + if (!campaign_valid) { + campaign_valid = TRUE; + campaign = cvar("campaign"); + } else { + cvar_set("campaign", ftos(campaign)); + } + + if(!load_flag && framecount > 2) + { + if(framecount != 3) + LoadGame(); + load_flag = TRUE; + } + RunFrameTickEntities(DeltaTime); +}; + +/* +============================================================================== + +BODY QUE + +============================================================================== +*/ + +entity bodyque_head; + +void() bodyque = +{ // just here so spawn functions don't complain after the world + // creates bodyques +}; + +void() InitBodyQue = +{ + bodyque_head = spawn(); + bodyque_head.classname = "bodyque"; + bodyque_head.owner = spawn(); + bodyque_head.owner.classname = "bodyque"; + bodyque_head.owner.owner = spawn(); + bodyque_head.owner.owner.classname = "bodyque"; + bodyque_head.owner.owner.owner = spawn(); + bodyque_head.owner.owner.owner.classname = "bodyque"; + bodyque_head.owner.owner.owner.owner = bodyque_head; +}; + + +// make a body que entry for the given ent so the ent can be +// respawned elsewhere +void(entity ent) CopyToBodyQue = +{ + bodyque_head.angles = ent.angles; + bodyque_head.model = ent.model; + bodyque_head.modelindex = ent.modelindex; + bodyque_head.frame = ent.frame; + bodyque_head.colormap = ent.colormap; + bodyque_head.movetype = ent.movetype; + bodyque_head.velocity = ent.velocity; + bodyque_head.flags = 0; + setorigin (bodyque_head, ent.origin); + setsize (bodyque_head, ent.mins, ent.maxs); + bodyque_head = bodyque_head.owner; +}; + + diff --git a/quakec_rogue/ai.qc b/quakec_rogue/ai.qc new file mode 100644 index 0000000..412e004 --- /dev/null +++ b/quakec_rogue/ai.qc @@ -0,0 +1,782 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() movetarget_f; +void() t_movetarget; +void() knight_walk1; +void() knight_bow6; +void() knight_bow1; +void(entity etemp, entity stemp, entity stemp, float dmg) T_Damage; +/* + +.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. + +.huntt_ime +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 +*/ + + +// +// globals +// +float current_yaw; + +// +// when a monster becomes angry at a player, that monster will be used +// as the sight target the next frame so that monsters near that one +// will wake up even if they wouldn't have noticed the player +// +entity sight_entity; +float sight_entity_time; + +float(float v) anglemod = +{ + while (v >= 360) + v = v - 360; + while (v < 0) + v = v + 360; + return v; +}; + +/* +============================================================================== + +MOVETARGET CODE + +The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target. + +targetname +must be present. The name of this movetarget. + +target +the next spot to move to. If not present, stop here for good. + +pausetime +The number of seconds to spend standing or bowing for path_stand or path_bow + +============================================================================== +*/ + + +void() movetarget_f = +{ + if (!self.targetname) + objerror ("monster_movetarget: no targetname"); + + self.solid = SOLID_TRIGGER; + self.touch = t_movetarget; + setsize (self, '-8 -8 -8', '8 8 8'); + +}; + +/*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8) +Monsters will continue walking towards the next target corner. +*/ +void() path_corner = +{ + movetarget_f (); +}; + + +/* +============= +t_movetarget + +Something has bumped into a movetarget. If it is a monster +moving towards it, change the next destination and continue. +============== +*/ +void() t_movetarget = +{ +local entity temp; + + if (other.movetarget != self) + return; + + if (other.enemy) + return; // fighting, not following a path + + temp = self; + self = other; + other = temp; + + if (self.classname == "monster_ogre") + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound + +//dprint ("t_movetarget\n"); + self.goalentity = self.movetarget = find (world, targetname, other.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + if (!self.movetarget) + { + self.pausetime = time + 999999; + self.th_stand (); + return; + } +}; + + + +//============================================================================ + +/* +============= +range + +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 +============= +*/ +float(entity targ) range = +{ +local vector spot1, spot2; +local float r; + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + r = vlen (spot1 - spot2); + if (r < 120) + return RANGE_MELEE; + if (r < 500) + return RANGE_NEAR; + if (r < 1000) + return RANGE_MID; + return RANGE_FAR; +}; + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +float (entity targ) visible = +{ + local vector spot1, spot2; + + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + traceline (spot1, spot2, TRUE, self); // see through other monsters + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_fraction == 1) + return TRUE; + return FALSE; +}; + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +float(entity targ) infront = +{ + local vector vec; + local float dot; + + makevectors (self.angles); + vec = normalize (targ.origin - self.origin); + dot = vec * v_forward; + + if ( dot > 0.3) + { + return TRUE; + } + return FALSE; +}; + + +//============================================================================ + +/* +=========== +ChangeYaw + +Turns towards self.ideal_yaw at self.yaw_speed +Sets the global variable current_yaw +Called every 0.1 sec by monsters +============ +*/ +/* + +void() ChangeYaw = +{ + local float ideal, move; + +//current_yaw = self.ideal_yaw; +// mod down the current angle + current_yaw = anglemod( self.angles_y ); + ideal = self.ideal_yaw; + + if (current_yaw == ideal) + return; + + move = ideal - current_yaw; + if (ideal > current_yaw) + { + if (move > 180) + move = move - 360; + } + else + { + if (move < -180) + move = move + 360; + } + + if (move > 0) + { + if (move > self.yaw_speed) + move = self.yaw_speed; + } + else + { + if (move < 0-self.yaw_speed ) + move = 0-self.yaw_speed; + } + + current_yaw = anglemod (current_yaw + move); + + self.angles_y = current_yaw; +}; + +*/ + + +//============================================================================ + +void() HuntTarget = +{ + self.goalentity = self.enemy; + self.think = self.th_run; + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + self.nextthink = time + 0.1; + SUB_AttackFinished (1); // wait a while before first attack +}; + +void() SightSound = +{ +local float rsnd; + + if (self.classname == "monster_ogre") + sound (self, CHAN_VOICE, "ogre/ogwake.wav", 1, ATTN_NORM); + else if (self.classname == "monster_knight") + { + if (self.skin == 0) + sound (self, CHAN_VOICE, "knight/ksight.wav", 1, ATTN_NORM); + } + else if (self.classname == "monster_shambler") + sound (self, CHAN_VOICE, "shambler/ssight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_demon1") + sound (self, CHAN_VOICE, "demon/sight2.wav", 1, ATTN_NORM); + else if (self.classname == "monster_wizard") + sound (self, CHAN_VOICE, "wizard/wsight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_zombie") + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_NORM); + else if (self.classname == "monster_dog") + sound (self, CHAN_VOICE, "dog/dsight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_hell_knight") + { + if (self.skin == 0) + sound (self, CHAN_VOICE, "hknight/sight1.wav", 1, ATTN_NORM); + } + else if (self.classname == "monster_tarbaby") + sound (self, CHAN_VOICE, "blob/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_vomit") + sound (self, CHAN_VOICE, "vomitus/v_sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_eel") + sound (self, CHAN_VOICE, "eel/eelc5.wav", 1, ATTN_NORM); + else if (self.classname == "monster_wrath") + sound (self, CHAN_VOICE, "wrath/wsee.wav", 1, ATTN_NORM); + else if (self.classname == "monster_enforcer") + { + rsnd = rint(random() * 3); + if (rsnd == 1) + sound (self, CHAN_VOICE, "enforcer/sight1.wav", 1, ATTN_NORM); + else if (rsnd == 2) + sound (self, CHAN_VOICE, "enforcer/sight2.wav", 1, ATTN_NORM); + else if (rsnd == 0) + sound (self, CHAN_VOICE, "enforcer/sight3.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "enforcer/sight4.wav", 1, ATTN_NORM); + } + else if (self.classname == "monster_army") + sound (self, CHAN_VOICE, "soldier/sight1.wav", 1, ATTN_NORM); + else if (self.classname == "monster_shalrath") + sound (self, CHAN_VOICE, "shalrath/sight.wav", 1, ATTN_NORM); + else if (self.classname == "monster_dragon") + sound (self, CHAN_VOICE, "dragon/see.wav", 1, ATTN_NORM); +}; + +void() FoundTarget = +{ + if (self.enemy.classname == "player") + { // let other monsters see this monster for a while + sight_entity = self; + sight_entity_time = time; + } + + self.show_hostile = time + 1; // wake up other monsters + + SightSound (); + HuntTarget (); +}; + +/* +=========== +FindTarget + +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. +============ +*/ +float() FindTarget = +{ + local entity client; + local float r; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry + +// spawnflags & 3 is a big hack, because zombie crucified used the first +// spawn flag prior to the ambush flag, and I forgot about it, so the second +// spawn flag works as well + if (sight_entity_time >= time - 0.1 && !(self.spawnflags & 3) ) + { + client = sight_entity; + if (client.enemy == self.enemy) + return; + } + else + { + client = checkclient (); + if (!client) + return FALSE; // current check entity isn't in PVS + } + + if (client == self.enemy) + return FALSE; + + if (client.flags & FL_NOTARGET) + return FALSE; + if (client.items & IT_INVISIBILITY) + return FALSE; + + r = range (client); + if (r == RANGE_FAR) + return FALSE; + + if (!visible (client)) + return FALSE; + + if (r == RANGE_NEAR) + { + if (client.show_hostile < time && !infront (client)) + return FALSE; + } + else if (r == RANGE_MID) + { + if ( /* client.show_hostile < time || */ !infront (client)) + return FALSE; + } + +// +// got one +// + self.enemy = client; + if (self.enemy.classname != "player") + { + self.enemy = self.enemy.enemy; + if (self.enemy.classname != "player") + { + self.enemy = world; + return FALSE; + } + } + +// PGM hack + if (self.classname != "dragon") + FoundTarget (); + + return TRUE; +}; + + +//============================================================================= + +void(float dist) ai_forward = +{ + walkmove (self.angles_y, dist); +}; + +void(float dist) ai_back = +{ + walkmove ( (self.angles_y+180), dist); +}; + +/* +============= +ai_pain + +stagger back a bit +============= +*/ +void(float dist) ai_pain = +{ + ai_back (dist); +/* + local float away; + + away = anglemod (vectoyaw (self.origin - self.enemy.origin) + + 180*(random()- 0.5) ); + + walkmove (away, dist); +*/ +}; + +/* +============= +ai_painforward + +stagger back a bit +============= +*/ +void(float dist) ai_painforward = +{ + walkmove (self.ideal_yaw, dist); +}; + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void(float dist) ai_walk = +{ + local vector mtemp; + + movedist = dist; + +/* Dragon movement handled in dragon.qc.... this is legacy. + if (self.classname == "monster_dragon") + { + movetogoal (dist); + return; + } +*/ + + // check for noticing a player + if (FindTarget ()) + return; + + movetogoal (dist); +}; + + +/* +============= +ai_stand + +The monster is staying in one place for a while, with slight angle turns +============= +*/ +void() ai_stand = +{ + if (FindTarget ()) + return; + + if (time > self.pausetime) + { + self.th_walk (); + return; + } + +// change angle slightly + +}; + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +============= +*/ +void() ai_turn = +{ + if (FindTarget ()) + return; + + ChangeYaw (); +}; + +//============================================================================= + +/* +============= +ChooseTurn +============= +*/ +void(vector dest3) ChooseTurn = +{ + local vector dir, newdir; + + dir = self.origin - dest3; + + newdir_x = trace_plane_normal_y; + newdir_y = 0 - trace_plane_normal_x; + newdir_z = 0; + + if (dir * newdir > 0) + { + dir_x = 0 - trace_plane_normal_y; + dir_y = trace_plane_normal_x; + } + else + { + dir_x = trace_plane_normal_y; + dir_y = 0 - trace_plane_normal_x; + } + + dir_z = 0; + self.ideal_yaw = vectoyaw(dir); +}; + +/* +============ +FacingIdeal + +============ +*/ +float() FacingIdeal = +{ + local float delta; + + delta = anglemod(self.angles_y - self.ideal_yaw); + if (delta > 45 && delta < 315) + return FALSE; + return TRUE; +}; + + +//============================================================================= + +float() WizardCheckAttack; +float() DogCheckAttack; + +float() CheckAnyAttack = +{ + if (!enemy_vis) + return; + if (self.classname == "monster_army") + return SoldierCheckAttack (); + if (self.classname == "monster_ogre") + return OgreCheckAttack (); + if (self.classname == "monster_shambler") + return ShamCheckAttack (); + if (self.classname == "monster_demon1") + return DemonCheckAttack (); + if (self.classname == "monster_dog") + return DogCheckAttack (); + if (self.classname == "monster_wizard") + return WizardCheckAttack (); + return CheckAttack (); +}; + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void() ai_run_melee = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + + if (FacingIdeal()) + { + self.th_melee (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void() ai_run_missile = +{ + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (FacingIdeal()) + { + self.th_missile (); + self.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void() ai_run_slide = +{ + local float ofs; + + self.ideal_yaw = enemy_yaw; + ChangeYaw (); + if (self.lefty) + ofs = 90; + else + ofs = -90; + + if (walkmove (self.ideal_yaw + ofs, movedist)) + return; + + self.lefty = 1 - self.lefty; + + walkmove (self.ideal_yaw - ofs, movedist); +}; + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void(float dist) ai_run = +{ + local vector delta; + local float axis; + local float direct, ang_rint, ang_floor, ang_ceil; + + movedist = dist; +// see if the enemy is dead + if (self.enemy.health <= 0) + { + self.enemy = world; + // FIXME: look all around for other targets + if (self.oldenemy.health > 0) + { + self.enemy = self.oldenemy; + HuntTarget (); + } + else + { + if (self.movetarget) + self.th_walk (); + else + self.th_stand (); + return; + } + } + + self.show_hostile = time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self.enemy); + if (enemy_vis) + self.search_time = time + 5; + +// look for other coop players + if (coop && self.search_time < time) + { + if (FindTarget ()) + return; + } + + enemy_infront = infront(self.enemy); + enemy_range = range(self.enemy); + enemy_yaw = vectoyaw(self.enemy.origin - self.origin); + + if (self.attack_state == AS_MISSILE) + { +//dprint ("ai_run_missile\n"); + ai_run_missile (); + return; + } + if (self.attack_state == AS_MELEE) + { +//dprint ("ai_run_melee\n"); + ai_run_melee (); + return; + } + + if (CheckAnyAttack ()) + return; // beginning an attack + + if (self.attack_state == AS_SLIDING) + { + ai_run_slide (); + return; + } + +// head straight in + movetogoal (dist); // done in C code... +}; + diff --git a/quakec_rogue/boss.qc b/quakec_rogue/boss.qc new file mode 100644 index 0000000..aa63fb4 --- /dev/null +++ b/quakec_rogue/boss.qc @@ -0,0 +1,404 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BOSS-ONE + +============================================================================== +*/ +$cd /raid/quake/id1/models/boss1 +$origin 0 0 -15 +$base base +$skin skin +$scale 5 + +$frame rise1 rise2 rise3 rise4 rise5 rise6 rise7 rise8 rise9 rise10 +$frame rise11 rise12 rise13 rise14 rise15 rise16 rise17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 +$frame walk9 walk10 walk11 walk12 walk13 walk14 walk15 +$frame walk16 walk17 walk18 walk19 walk20 walk21 walk22 +$frame walk23 walk24 walk25 walk26 walk27 walk28 walk29 walk30 walk31 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 attack12 attack13 attack14 attack15 +$frame attack16 attack17 attack18 attack19 attack20 attack21 attack22 +$frame attack23 + +$frame shocka1 shocka2 shocka3 shocka4 shocka5 shocka6 shocka7 shocka8 +$frame shocka9 shocka10 + +$frame shockb1 shockb2 shockb3 shockb4 shockb5 shockb6 + +$frame shockc1 shockc2 shockc3 shockc4 shockc5 shockc6 shockc7 shockc8 +$frame shockc9 shockc10 + + +void(vector p) boss_missile; + +void() boss_face = +{ + +// go for another player if multi player + if (self.enemy.health <= 0 || random() < 0.02) + { + self.enemy = find(self.enemy, classname, "player"); + if (!self.enemy) + self.enemy = find(self.enemy, classname, "player"); + } + ai_face(); +}; + +void() boss_rise1 =[ $rise1, boss_rise2 ] { +sound (self, CHAN_WEAPON, "boss1/out1.wav", 1, ATTN_NORM); +}; +void() boss_rise2 =[ $rise2, boss_rise3 ] { +sound (self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); +}; +void() boss_rise3 =[ $rise3, boss_rise4 ] {}; +void() boss_rise4 =[ $rise4, boss_rise5 ] {}; +void() boss_rise5 =[ $rise5, boss_rise6 ] {}; +void() boss_rise6 =[ $rise6, boss_rise7 ] {}; +void() boss_rise7 =[ $rise7, boss_rise8 ] {}; +void() boss_rise8 =[ $rise8, boss_rise9 ] {}; +void() boss_rise9 =[ $rise9, boss_rise10 ] {}; +void() boss_rise10 =[ $rise10, boss_rise11 ] {}; +void() boss_rise11 =[ $rise11, boss_rise12 ] {}; +void() boss_rise12 =[ $rise12, boss_rise13 ] {}; +void() boss_rise13 =[ $rise13, boss_rise14 ] {}; +void() boss_rise14 =[ $rise14, boss_rise15 ] {}; +void() boss_rise15 =[ $rise15, boss_rise16 ] {}; +void() boss_rise16 =[ $rise16, boss_rise17 ] {}; +void() boss_rise17 =[ $rise17, boss_missile1 ] {}; + +void() boss_idle1 =[ $walk1, boss_idle2 ] +{ +// look for other players +}; +void() boss_idle2 =[ $walk2, boss_idle3 ] {boss_face();}; +void() boss_idle3 =[ $walk3, boss_idle4 ] {boss_face();}; +void() boss_idle4 =[ $walk4, boss_idle5 ] {boss_face();}; +void() boss_idle5 =[ $walk5, boss_idle6 ] {boss_face();}; +void() boss_idle6 =[ $walk6, boss_idle7 ] {boss_face();}; +void() boss_idle7 =[ $walk7, boss_idle8 ] {boss_face();}; +void() boss_idle8 =[ $walk8, boss_idle9 ] {boss_face();}; +void() boss_idle9 =[ $walk9, boss_idle10 ] {boss_face();}; +void() boss_idle10 =[ $walk10, boss_idle11 ] {boss_face();}; +void() boss_idle11 =[ $walk11, boss_idle12 ] {boss_face();}; +void() boss_idle12 =[ $walk12, boss_idle13 ] {boss_face();}; +void() boss_idle13 =[ $walk13, boss_idle14 ] {boss_face();}; +void() boss_idle14 =[ $walk14, boss_idle15 ] {boss_face();}; +void() boss_idle15 =[ $walk15, boss_idle16 ] {boss_face();}; +void() boss_idle16 =[ $walk16, boss_idle17 ] {boss_face();}; +void() boss_idle17 =[ $walk17, boss_idle18 ] {boss_face();}; +void() boss_idle18 =[ $walk18, boss_idle19 ] {boss_face();}; +void() boss_idle19 =[ $walk19, boss_idle20 ] {boss_face();}; +void() boss_idle20 =[ $walk20, boss_idle21 ] {boss_face();}; +void() boss_idle21 =[ $walk21, boss_idle22 ] {boss_face();}; +void() boss_idle22 =[ $walk22, boss_idle23 ] {boss_face();}; +void() boss_idle23 =[ $walk23, boss_idle24 ] {boss_face();}; +void() boss_idle24 =[ $walk24, boss_idle25 ] {boss_face();}; +void() boss_idle25 =[ $walk25, boss_idle26 ] {boss_face();}; +void() boss_idle26 =[ $walk26, boss_idle27 ] {boss_face();}; +void() boss_idle27 =[ $walk27, boss_idle28 ] {boss_face();}; +void() boss_idle28 =[ $walk28, boss_idle29 ] {boss_face();}; +void() boss_idle29 =[ $walk29, boss_idle30 ] {boss_face();}; +void() boss_idle30 =[ $walk30, boss_idle31 ] {boss_face();}; +void() boss_idle31 =[ $walk31, boss_idle1 ] {boss_face();}; + +void() boss_missile1 =[ $attack1, boss_missile2 ] {boss_face();}; +void() boss_missile2 =[ $attack2, boss_missile3 ] {boss_face();}; +void() boss_missile3 =[ $attack3, boss_missile4 ] {boss_face();}; +void() boss_missile4 =[ $attack4, boss_missile5 ] {boss_face();}; +void() boss_missile5 =[ $attack5, boss_missile6 ] {boss_face();}; +void() boss_missile6 =[ $attack6, boss_missile7 ] {boss_face();}; +void() boss_missile7 =[ $attack7, boss_missile8 ] {boss_face();}; +void() boss_missile8 =[ $attack8, boss_missile9 ] {boss_face();}; +void() boss_missile9 =[ $attack9, boss_missile10 ] {boss_missile('100 100 200');}; +void() boss_missile10 =[ $attack10, boss_missile11 ] {boss_face();}; +void() boss_missile11 =[ $attack11, boss_missile12 ] {boss_face();}; +void() boss_missile12 =[ $attack12, boss_missile13 ] {boss_face();}; +void() boss_missile13 =[ $attack13, boss_missile14 ] {boss_face();}; +void() boss_missile14 =[ $attack14, boss_missile15 ] {boss_face();}; +void() boss_missile15 =[ $attack15, boss_missile16 ] {boss_face();}; +void() boss_missile16 =[ $attack16, boss_missile17 ] {boss_face();}; +void() boss_missile17 =[ $attack17, boss_missile18 ] {boss_face();}; +void() boss_missile18 =[ $attack18, boss_missile19 ] {boss_face();}; +void() boss_missile19 =[ $attack19, boss_missile20 ] {boss_face();}; +void() boss_missile20 =[ $attack20, boss_missile21 ] {boss_missile('100 -100 200');}; +void() boss_missile21 =[ $attack21, boss_missile22 ] {boss_face();}; +void() boss_missile22 =[ $attack22, boss_missile23 ] {boss_face();}; +void() boss_missile23 =[ $attack23, boss_missile1 ] {boss_face();}; + +void() boss_shocka1 =[ $shocka1, boss_shocka2 ] {}; +void() boss_shocka2 =[ $shocka2, boss_shocka3 ] {}; +void() boss_shocka3 =[ $shocka3, boss_shocka4 ] {}; +void() boss_shocka4 =[ $shocka4, boss_shocka5 ] {}; +void() boss_shocka5 =[ $shocka5, boss_shocka6 ] {}; +void() boss_shocka6 =[ $shocka6, boss_shocka7 ] {}; +void() boss_shocka7 =[ $shocka7, boss_shocka8 ] {}; +void() boss_shocka8 =[ $shocka8, boss_shocka9 ] {}; +void() boss_shocka9 =[ $shocka9, boss_shocka10 ] {}; +void() boss_shocka10 =[ $shocka10, boss_missile1 ] {}; + +void() boss_shockb1 =[ $shockb1, boss_shockb2 ] {}; +void() boss_shockb2 =[ $shockb2, boss_shockb3 ] {}; +void() boss_shockb3 =[ $shockb3, boss_shockb4 ] {}; +void() boss_shockb4 =[ $shockb4, boss_shockb5 ] {}; +void() boss_shockb5 =[ $shockb5, boss_shockb6 ] {}; +void() boss_shockb6 =[ $shockb6, boss_shockb7 ] {}; +void() boss_shockb7 =[ $shockb1, boss_shockb8 ] {}; +void() boss_shockb8 =[ $shockb2, boss_shockb9 ] {}; +void() boss_shockb9 =[ $shockb3, boss_shockb10 ] {}; +void() boss_shockb10 =[ $shockb4, boss_missile1 ] {}; + +void() boss_shockc1 =[ $shockc1, boss_shockc2 ] {}; +void() boss_shockc2 =[ $shockc2, boss_shockc3 ] {}; +void() boss_shockc3 =[ $shockc3, boss_shockc4 ] {}; +void() boss_shockc4 =[ $shockc4, boss_shockc5 ] {}; +void() boss_shockc5 =[ $shockc5, boss_shockc6 ] {}; +void() boss_shockc6 =[ $shockc6, boss_shockc7 ] {}; +void() boss_shockc7 =[ $shockc7, boss_shockc8 ] {}; +void() boss_shockc8 =[ $shockc8, boss_shockc9 ] {}; +void() boss_shockc9 =[ $shockc9, boss_shockc10 ] {}; +void() boss_shockc10 =[ $shockc10, boss_death1 ] {}; + +void() boss_death1 = [$death1, boss_death2] { +sound (self, CHAN_VOICE, "boss1/death.wav", 1, ATTN_NORM); +}; +void() boss_death2 = [$death2, boss_death3] {}; +void() boss_death3 = [$death3, boss_death4] {}; +void() boss_death4 = [$death4, boss_death5] {}; +void() boss_death5 = [$death5, boss_death6] {}; +void() boss_death6 = [$death6, boss_death7] {}; +void() boss_death7 = [$death7, boss_death8] {}; +void() boss_death8 = [$death8, boss_death9] {}; +void() boss_death9 = [$death9, boss_death10] +{ + sound (self, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); +}; + +void() boss_death10 = [$death9, boss_death10] +{ + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); // FIXME: reliable broadcast + SUB_UseTargets (); + remove (self); +}; + +void(vector p) boss_missile = +{ + local vector offang; + local vector org, vec, d; + local float t; + + offang = vectoangles (self.enemy.origin - self.origin); + makevectors (offang); + + org = self.origin + p_x*v_forward + p_y*v_right + p_z*'0 0 1'; + +// lead the player on hard mode + if (skill > 1) + { + t = vlen(self.enemy.origin - org) / 300; + vec = self.enemy.velocity; + vec_z = 0; + d = self.enemy.origin + t * vec; + } + else + { + d = self.enemy.origin; + } + + vec = normalize (d - org); + + launch_spike (org, vec); + setmodel (newmis, "progs/lavaball.mdl"); + newmis.avelocity = '200 100 300'; + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.touch = T_MissileTouch; // rocket explosion + sound (self, CHAN_WEAPON, "boss1/throw.wav", 1, ATTN_NORM); + +// check for dead enemy + if (self.enemy.health <= 0) + boss_idle1 (); +}; + + +void() boss_awake = +{ + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + self.takedamage = DAMAGE_NO; + + setmodel (self, "progs/boss.mdl"); + setsize (self, '-128 -128 -24', '128 128 256'); + + if (skill == 0) + self.health = 1; + else + self.health = 3; + + self.enemy = activator; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.yaw_speed = 20; + boss_rise1 (); +}; + + +/*QUAKED monster_boss (1 0 0) (-128 -128 -24) (128 128 256) +*/ +void() monster_boss = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/boss.mdl"); + precache_model ("progs/lavaball.mdl"); + + precache_sound ("weapons/rocket1i.wav"); + precache_sound ("boss1/out1.wav"); + precache_sound ("boss1/sight1.wav"); + precache_sound ("misc/power.wav"); + precache_sound ("boss1/throw.wav"); + precache_sound ("boss1/pain.wav"); + precache_sound ("boss1/death.wav"); + + total_monsters = total_monsters + 1; + + self.use = boss_awake; +}; + +//=========================================================================== + +entity le1, le2; +float lightning_end; + +void() lightning_fire = +{ + local vector p1, p2; + + if (time >= lightning_end) + { // done here, put the terminals back up + self = le1; + door_go_down (); + self = le2; + door_go_down (); + return; + } + + p1 = (le1.mins + le1.maxs) * 0.5; + p1_z = le1.absmin_z - 16; + + p2 = (le2.mins + le2.maxs) * 0.5; + p2_z = le2.absmin_z - 16; + + // compensate for length of bolt + p2 = p2 - normalize(p2-p1)*100; + + self.nextthink = time + 0.1; + self.think = lightning_fire; + + WriteByte (MSG_ALL, SVC_TEMPENTITY); + WriteByte (MSG_ALL, TE_LIGHTNING3); + WriteEntity (MSG_ALL, world); + WriteCoord (MSG_ALL, p1_x); + WriteCoord (MSG_ALL, p1_y); + WriteCoord (MSG_ALL, p1_z); + WriteCoord (MSG_ALL, p2_x); + WriteCoord (MSG_ALL, p2_y); + WriteCoord (MSG_ALL, p2_z); +}; + +void() lightning_use = +{ + if (lightning_end >= time + 1) + return; + + le1 = find( world, target, "lightning"); + le2 = find( le1, target, "lightning"); + if (!le1 || !le2) + { + dprint ("missing lightning targets\n"); + return; + } + + if ( + (le1.state != STATE_TOP && le1.state != STATE_BOTTOM) + || (le2.state != STATE_TOP && le2.state != STATE_BOTTOM) + || (le1.state != le2.state) ) + { +// dprint ("not aligned\n"); + return; + } + +// don't let the electrodes go back up until the bolt is done + le1.nextthink = -1; + le2.nextthink = -1; + lightning_end = time + 1; + + sound (self, CHAN_VOICE, "misc/power.wav", 1, ATTN_NORM); + lightning_fire (); + +// advance the boss pain if down + self = find (world, classname, "monster_boss"); + if (!self) + return; + self.enemy = activator; + if (le1.state == STATE_TOP && self.health > 0) + { + sound (self, CHAN_VOICE, "boss1/pain.wav", 1, ATTN_NORM); + self.health = self.health - 1; + if (self.health >= 2) + boss_shocka1(); + else if (self.health == 1) + boss_shockb1(); + else if (self.health == 0) + boss_shockc1(); + } +}; + + +/*QUAKED event_lightning (0 1 1) (-16 -16 -16) (16 16 16) +Just for boss level. +*/ +void() event_lightning = +{ + self.use = lightning_use; +}; + + diff --git a/quakec_rogue/bots/bot.qc b/quakec_rogue/bots/bot.qc new file mode 100644 index 0000000..86d4a3a --- /dev/null +++ b/quakec_rogue/bots/bot.qc @@ -0,0 +1,52 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +======================== +Bot_PreThink + +Called by the engine every frame before running the bots update in C++ + +Example usages of this function: + + 1. Telling bots what to do or where to go. + 2. Modifying properties on the bot player ( or other players in-game ) that the bots can see/use doing their update. +======================== +*/ +void Bot_PreThink() { + + +}; + +/* +======================== +Bot_PostThink + +Called by the engine every frame after running the bots update in C++ + +Example usages of this function: + + 1. Overriding/modifying bot user cmds. + 2. Overriding/modifying bot player properties. +======================== +*/ +void Bot_PostThink() { + + +}; \ No newline at end of file diff --git a/quakec_rogue/buttons.qc b/quakec_rogue/buttons.qc new file mode 100644 index 0000000..e3ad6ff --- /dev/null +++ b/quakec_rogue/buttons.qc @@ -0,0 +1,161 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// button and multiple button + +void() button_wait; +void() button_return; + +void() button_wait = +{ + self.state = STATE_TOP; + self.nextthink = self.ltime + self.wait; + self.think = button_return; + activator = self.enemy; + SUB_UseTargets(); + self.frame = 1; // use alternate textures +}; + +void() button_done = +{ + self.state = STATE_BOTTOM; +}; + +void() button_return = +{ + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, button_done); + self.frame = 0; // use normal textures + if (self.health) + self.takedamage = DAMAGE_YES; // can be shot again +}; + + +void() button_blocked = +{ // do nothing, just don't ome all the way back out +}; + + +void() button_fire = +{ + if (self.state == STATE_UP || self.state == STATE_TOP) + return; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, button_wait); +}; + + +void() button_use = +{ + self.enemy = activator; + button_fire (); +}; + +void() button_touch = +{ + if (other.classname != "player") + return; + self.enemy = other; + button_fire (); +}; + +void() button_killed = +{ + self.enemy = damage_attacker; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + button_fire (); +}; + + +/*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" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +void() func_button = +{ +local float gtemp, ftemp; + + if (self.sounds == 0) + { + precache_sound ("buttons/airbut1.wav"); + self.noise = "buttons/airbut1.wav"; + } + if (self.sounds == 1) + { + precache_sound ("buttons/switch21.wav"); + self.noise = "buttons/switch21.wav"; + } + if (self.sounds == 2) + { + precache_sound ("buttons/switch02.wav"); + self.noise = "buttons/switch02.wav"; + } + if (self.sounds == 3) + { + precache_sound ("buttons/switch04.wav"); + self.noise = "buttons/switch04.wav"; + } + + SetMovedir (); + + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + setmodel (self, self.model); + + self.blocked = button_blocked; + self.use = button_use; + + if (self.health) + { + self.max_health = self.health; + self.th_die = button_killed; + self.takedamage = DAMAGE_YES; + } + else + self.touch = button_touch; + + if (!self.speed) + self.speed = 40; + if (!self.wait) + self.wait = 1; + if (!self.lip) + self.lip = 4; + + self.state = STATE_BOTTOM; + + self.pos1 = self.origin; + vector movedir_fabs = { fabs(self.movedir[0]), fabs(self.movedir[1]), fabs(self.movedir[2]) }; + self.pos2 = self.pos1 + ((movedir_fabs * self.size) - self.lip) * self.movedir; +}; + diff --git a/quakec_rogue/buzzsaw.qc b/quakec_rogue/buzzsaw.qc new file mode 100644 index 0000000..35fac5e --- /dev/null +++ b/quakec_rogue/buzzsaw.qc @@ -0,0 +1,164 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// buzzsaw.qc + +$skin buzzsaw +$frame buzzsaw +$frame bzzrot01 bzzrot02 bzzrot03 bzzrot04 bzzrot05 bzzrot06 + +void() buzzsaw_stand1 = [ $buzzsaw, buzzsaw_stand1 ] +{ + if ( self.pain_finished < time) + { + sound ( self, CHAN_VOICE, "buzz/buzz1.wav", 0.2, ATTN_NORM); + self.pain_finished = time + 1; + } + + self.angles_x = self.angles_x - 60; + self.avelocity_x = 60; +}; + +void() buzzsaw_fly = +{ + local vector dir; + + if ( self.pain_finished < time) + { + sound ( self, CHAN_VOICE, "buzz/buzz1.wav", 0.2, ATTN_NORM); + self.pain_finished = time + 1; + } + + dir = self.goalentity.origin - self.origin; + dir = normalize (dir); + dir = dir * self.speed; + setorigin (self, self.origin + dir); + self.angles_x = self.angles_x - 60; + self.avelocity_x = 60; +}; + +/* +void() buzzsaw_fly1 = [ $bzzrot01, buzzsaw_fly2 ] { buzzsaw_fly(); }; +void() buzzsaw_fly2 = [ $bzzrot02, buzzsaw_fly3 ] { buzzsaw_fly(); }; +void() buzzsaw_fly3 = [ $bzzrot03, buzzsaw_fly4 ] { buzzsaw_fly(); }; +void() buzzsaw_fly4 = [ $bzzrot04, buzzsaw_fly5 ] { buzzsaw_fly(); }; +void() buzzsaw_fly5 = [ $bzzrot05, buzzsaw_fly6 ] { buzzsaw_fly(); }; +void() buzzsaw_fly6 = [ $bzzrot06, buzzsaw_fly1 ] { buzzsaw_fly(); }; +*/ + +void() buzzsaw_fly1 = [ $bzzrot01, buzzsaw_fly1 ] { buzzsaw_fly(); }; + +void() buzzsaw_touch = +{ + local vector sprayDir; + + if ( other.classname == "player" || other.flags & FL_MONSTER) + { + if ( self.attack_finished < time ) + { + sound (self, CHAN_WEAPON, "buzz/buzz.wav", 1, ATTN_NORM); + self.attack_finished = time + 2; + } + T_Damage (other, self, self, self.currentammo); + + sprayDir = normalize(self.goalentity.origin - self.origin); + sprayDir = sprayDir * 200; + SpawnMeatSpray ( self.origin, sprayDir); + + other.velocity = sprayDir; + other.velocity_z = 200; + } +}; + +void() buzzsaw_use = +{ + self.touch = buzzsaw_touch; + if (self.target) + { + self.movetarget = find(world, targetname, self.target); + self.goalentity = self.movetarget; + self.th_stand = buzzsaw_fly1; + self.th_walk = buzzsaw_fly1; + self.th_run = buzzsaw_fly1; + self.think = buzzsaw_fly1; + self.nextthink = time + 0.1; + } + else + { + self.nextthink = time + 0.1; + self.think = buzzsaw_stand1; + } + self.use = SUB_Null; +}; + +/*QUAKED buzzsaw (0 .5 .8) (-18 -18 -18) (18 18 18) Vertical +The buzzsaw trap. + +currentammo: amount of damage for each contact. (default 10) +speed: speed that it will follow it's path. (default 10) + +Use the angle buttons to point the buzzsaw in the direction it +should face. + +Place on a monster path if you want it to move. + +If it is targeted, it will wait until triggered to activate. + +It will not damage players until activated. +*/ +void() buzzsaw = +{ + precache_model ("progs/buzzsaw.mdl"); + + precache_sound ("buzz/buzz.wav"); + precache_sound ("buzz/buzz1.wav"); + + setmodel (self, "progs/buzzsaw.mdl"); + + self.takedamage = DAMAGE_NO; + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_FLY; + + if(self.angles_y == 0 || self.angles_y == 180) + setsize (self, '-18 0 -18', '18 0 18'); + else if(self.angles_y == 90 || self.angles_y == 270) + setsize (self, '0 -18 -18', '0 18 18'); + else + objerror ("Buzzsaw: Not at 90 degree angle!"); + + setorigin (self, self.origin); + + if (!self.speed) + self.speed = 10; + if (!self.currentammo) + self.currentammo = 10; + + self.pain_finished = time + random()*2; + + if (!self.targetname) + { + self.think = buzzsaw_use; + self.nextthink = time + 0.2; + } + else + { + self.use = buzzsaw_use; + } +}; + diff --git a/quakec_rogue/client.qc b/quakec_rogue/client.qc new file mode 100644 index 0000000..8bc1c92 --- /dev/null +++ b/quakec_rogue/client.qc @@ -0,0 +1,2117 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// updated to quake v1.06 10/8/96 + +// prototypes +void () W_WeaponFrame; +void() W_SetCurrentAmmo; +void() player_pain; +void() player_stand1; +void (vector org) spawn_tfog; +void (vector org, entity death_owner) spawn_tdeath; + +void(entity targ, entity attacker) dmatch_score; + +float modelindex_eyes, modelindex_player; + +/* +============================================================================= + + LEVEL CHANGING / INTERMISSION + +============================================================================= +*/ + +float intermission_running; +float intermission_exittime; + +/*QUAKED info_intermission (1 0.5 0.5) (-16 -16 -16) (16 16 16) +This is the camera point for the intermission. +Use mangle instead of angle, so you can set pitch or roll as well as yaw. 'pitch roll yaw' +*/ +void() info_intermission = +{ +}; + + + +void() SetChangeParms = +{ + if (self.health <= 0 || deathmatch) + { + SetNewParms (); + parm14 = self.steam; // Save the current team of the player + return; + } + +// remove items + self.items = self.items - (self.items & (IT_KEY1 | IT_KEY2 | + IT_INVULNERABILITY | IT_SUIT | + IT_QUAD | IT_INVISIBILITY) ); + + self.items2 = self.items2 - (self.items2 & + (IT2_ANTIGRAV | IT2_SHIELD | IT2_SUPERHEALTH)); + self.gravity = 1.0; + +// cap super health + if (self.health > self.max_health) + self.health = self.max_health; + + if (self.health < self.max_health / 2) + self.health = self.max_health / 2; + + teamplay = cvar ( "teamplay" ); + if (teamplay >= TEAM_CTF) // don't carry items between levels in CTF + SetNewParms(); + else + { + parm1 = self.items; + parm2 = self.health; + parm3 = self.armorvalue; + if (self.ammo_shells1 < 25) + parm4 = 25; + else + parm4 = self.ammo_shells1; + parm5 = self.ammo_nails1; + parm6 = self.ammo_rockets1; + parm7 = self.ammo_cells1; + parm8 = self.weapon; + parm9 = self.armortype * 100; + parm10 = self.items2; + parm11 = self.ammo_lava_nails; + parm12 = self.ammo_multi_rockets; + parm13 = self.ammo_plasma; + } + parm14 = self.steam; // save team setting +}; + +void() SetNewParms = +{ +//ZOID-- + if (deathmatch && teamplay >= TEAM_CTF) { + parm1 = IT_SHOTGUN | IT_AXE | IT_GRAPPLE; + parm10 = IT2_ARMOR1; + parm3 = 50; + parm9 = 30; + } else { + parm1 = IT_SHOTGUN | IT_AXE; + parm3 = 0; + parm9 = 0; + parm10 = 0; + } +//--ZOID + if (skill == 3 && !deathmatch) + parm2 = 50; + else + parm2 = 100; + parm4 = 25; + parm5 = 0; + parm6 = 0; + parm7 = 0; + parm8 = 1; + parm11 = 0; + parm12 = 0; + parm13 = 0; +//ZOID-- + parm14 = -1; // CTF: reset current team selection +//--ZOID +}; + +void() DecodeLevelParms = +{ + if (serverflags) + { + if (world.model == "maps/start.bsp") + SetNewParms (); // take away all stuff on starting new episode + } + if (world.model == "maps/r2m1.bsp" && !deathmatch) + SetNewParms (); + // PGM - 01/29 change to have level 8 reset all your stuff. + + self.items = parm1; + self.health = parm2; + self.armorvalue = parm3; + self.ammo_shells1 = parm4; + self.ammo_nails1 = parm5; + self.ammo_rockets1 = parm6; + self.ammo_cells1 = parm7; + // cute bug, if grapple was selected, it still will be on level change + if (parm8 == IT_GRAPPLE && teamplay < TEAM_CTF) + self.weapon = IT_AXE; + else + self.weapon = parm8; + self.armortype = parm9 * 0.01; + self.items2 = parm10; + self.ammo_lava_nails = parm11; + self.ammo_multi_rockets = parm12; + self.ammo_plasma = parm13; +//ZOID-- + if (TeamColorIsLegal(parm14)) { + self.steam = parm14; + TeamSetSkin(); + } +//--ZOID +}; + +/* +============ +FindIntermission + +Returns the entity to view from +============ +*/ +entity() FindIntermission = +{ + local entity spot; + local float cyc; + +// look for info_intermission first + spot = find (world, classname, "info_intermission"); + if (spot) + { // pick a random one + cyc = random() * 4; + while (cyc > 1) + { + spot = find (spot, classname, "info_intermission"); + if (!spot) + spot = find (spot, classname, "info_intermission"); + cyc = cyc - 1; + } + return spot; + } + +// then look for the start position + spot = find (world, classname, "info_player_start"); + if (spot) + return spot; + +// testinfo_player_start is only found in regioned levels + spot = find (world, classname, "testplayerstart"); + if (spot) + return spot; + + objerror ("FindIntermission: no spot"); +}; + + +string nextmap; +void() GotoNextMap = +{ + if (cvar("samelevel")) // if samelevel is set, stay on same level + changelevel (mapname); + else + changelevel (nextmap); +}; + + +void() ExitIntermission = +{ +// skip any text in deathmatch + if (deathmatch) + { + GotoNextMap (); + return; + } + + intermission_exittime = time + 1; + intermission_running = intermission_running + 1; + +// +// run some text if at the end of an episode +// + if (intermission_running == 2) + { + if (world.model == "maps/e1m7.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + if (!cvar("registered")) + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e1_shareware"); + } + else + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e1"); + } + return; + } + else if (world.model == "maps/e2m6.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e2"); + return; + } + else if (world.model == "maps/e3m6.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e3"); + return; + } + else if (world.model == "maps/e4m7.bsp") + { + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 2); + WriteByte (MSG_ALL, 3); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_e4"); + return; + } + + else if (world.model == "maps/r1m7.bsp") + { + WriteByte (MSG_ALL, SVC_FINALE); +//------------------------------------- end here + WriteString (MSG_ALL, "$qc_finale_r1"); + return; + } + + if (coop && mapname == "r2m8" ) + { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + return; + } + else + { + GotoNextMap(); + } + } + + if (intermission_running == 3) + { + if (!cvar("registered")) + { // shareware episode has been completed, go to sell screen + WriteByte (MSG_ALL, SVC_SELLSCREEN); + return; + } + + if ( (serverflags&15) == 15) + { + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_all_runes"); + return; + } + + } + + if (coop) + { + if ( mapname == "r2m8" ) + { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + return; + } + } + else + { + GotoNextMap(); + } +}; + +/* +============ +IntermissionThink + +When the player presses attack or jump, change to the next level +============ +*/ +void() IntermissionThink = +{ + if (time < intermission_exittime) + return; + + if (!self.button0 && !self.button1 && !self.button2) + return; + + ExitIntermission (); +}; + +void() execute_changelevel = +{ + local entity pos; + + intermission_running = 1; + +// enforce a wait time before allowing changelevel + if (deathmatch) + intermission_exittime = time + 5; + else + intermission_exittime = time + 2; + + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + + pos = FindIntermission (); + + other = find (world, classname, "player"); + while (other != world) + { + other.view_ofs = '0 0 0'; + other.angles = other.v_angle = pos.mangle; + other.fixangle = TRUE; // turn this way immediately + other.nextthink = time + 0.5; + other.takedamage = DAMAGE_NO; + other.solid = SOLID_NOT; + other.movetype = MOVETYPE_NONE; + other.modelindex = 0; + setorigin (other, pos.origin); + other = find (other, classname, "player"); + } + + WriteByte (MSG_ALL, SVC_INTERMISSION); + + if (campaign && world.model == "maps/r1m7.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_R1M7"); + } +}; + + +void() changelevel_touch = +{ + local entity pos; + + if (other.classname != "player") + return; + + if ((cvar("noexit") == 1) || ((cvar("noexit") == 2) && (mapname != "start"))) + { + T_Damage (other, self, self, 50000); + return; + } + + if (coop || deathmatch) + { + bprint("$qc_exited", other.netname); + + } + + nextmap = self.map; + + SUB_UseTargets (); + + if ( (self.spawnflags & 1) && (deathmatch == 0) ) + { // NO_INTERMISSION + GotoNextMap(); + return; + } + + self.touch = SUB_Null; + +// we can't move people right now, because touch functions are called +// in the middle of C movement code, so set a think time to do it + self.think = execute_changelevel; + self.nextthink = time + 0.1; +}; + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ +void() trigger_changelevel = +{ + if (!self.map) + objerror ("chagnelevel trigger doesn't have map"); + + InitTrigger (); + self.touch = changelevel_touch; +}; + + +/* +============================================================================= + + PLAYER GAME EDGE FUNCTIONS + +============================================================================= +*/ + +void() set_suicide_frame; + +// called by ClientKill and DeadThink +void() respawn = +{ + if (coop) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue (self); + // get the spawn parms as they were at level start + setspawnparms (self); + // respawn + PutClientInServer (); + } + else if (deathmatch) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue (self); + // set default spawn parms + SetNewParms (); + // respawn + PutClientInServer (); + // set the skin correctly. + TeamSetSkin(); + } + else + { // restart the entire server + cvar_set("campaign", ftos(campaign)); + localcmd ("restart\n"); + } +}; + + +/* +============ +ClientKill + +Player entered the suicide command +============ +*/ +void() ClientKill = +{ + local entity vSphere; + +// ZOID-- + if (self.suicide_count > 3) + { + sprint(self, "$qc_suicided_too_much"); + return; + } + TeamDropFlagOfPlayer(self); + self.suicide_count = self.suicide_count + 1; +// --ZOID + + if ( tag_token_owner == self) + { + self.health = 0; + self.solid = SOLID_NOT; + tag_token_drop(); + } + + if ( self.items2 & IT2_V_SPHERE) + { + vSphere = find ( world, classname, "Vengeance"); + while (vSphere) + { + if ( vSphere.owner == self ) + { + remove (vSphere); + } + vSphere = find(vSphere, classname, "Vengeance"); + } + } + + bprint("$qc_suicides", self.netname); + set_suicide_frame (); + self.modelindex = modelindex_player; + self.frags = self.frags - 2; // extra penalty + respawn (); +}; + +/* +============ +PlayerVisibleToSpawnPoint + +Returns true if player can see this point +============ +*/ +float PlayerVisibleToSpawnPoint( entity point ) { + local vector spot1, spot2; + local entity player = find( world, classname, "player" ); + while ( player ) { + if ( player.health > 0 ) { + spot1 = point.origin + player.view_ofs; + spot2 = player.origin + player.view_ofs; + + traceline( spot1, spot2, TRUE, point ); + if ( trace_fraction >= 1.0f ) { + return TRUE; + } + } + + player = find( player, classname, "player" ); + } + + return FALSE; +} + +float IDEAL_DIST_FROM_DM_SPAWN_POINT = 384; +float MIN_DIST_FROM_DM_SPAWN_POINT = 84; + +/* +============ +SelectSpawnPoint + +Returns the entity to spawn at +============ +*/ +entity() SelectSpawnPointRogue = +{ + local entity spot; + local entity thing; + local float pcount; +//ZOID-- + local entity startspot; + local float t; +//--ZOID + +// testinfo_player_start is only found in regioned levels + spot = find (world, classname, "testplayerstart"); + if (spot) + return spot; + +// choose a info_player_deathmatch point + if (coop) + { + lastspawn = find(lastspawn, classname, "info_player_coop"); + if (lastspawn == world) + lastspawn = find (lastspawn, classname, "info_player_start"); + if (lastspawn != world) + return lastspawn; + } + else if (deathmatch) + { + startspot = spot = lastspawn; + t = 0; + if (!self.ctf_killed && teamplay >= TEAM_CTF) + { + if (self.steam == TEAM1) + { + startspot = spot = team1_lastspawn; + t = TEAM1; + } + else if (self.steam == TEAM2) + { + startspot = spot = team2_lastspawn; + t = TEAM2; + } + } + + while (1) + { + if (t == TEAM1) + spot = find(spot, classname, "info_player_team1"); + else if (t == TEAM2) + spot = find(spot, classname, "info_player_team2"); + else + spot = find(spot, classname, "info_player_deathmatch"); + + if (spot != world) + { + if (spot == startspot) + return startspot; + pcount = 0; + thing = findradius(spot.origin, 32); + while(thing) + { + if (thing.classname == "player") + pcount = pcount + 1; + thing = thing.chain; + } + if (pcount == 0) + { + if (t == TEAM1) + team1_lastspawn = spot; + else if (t == TEAM2) + team2_lastspawn = spot; + else + lastspawn = spot; + return spot; + } + } + else + t = 0; + // pgm fix for ctf in levels w/o ctf support + } + } + + if (serverflags) + { // return with a rune to start + spot = find (world, classname, "info_player_start2"); + if (spot) + return spot; + } + + spot = find (world, classname, "info_player_start"); + if (!spot) + error ("PutClientInServer: no info_player_start on level"); + + return spot; +}; + +entity SelectSpawnPointEX(float forceSpawn) { + local entity spot, thing; + local float numspots, totalspots; + local float pcount; + local entity spots; + + numspots = 0; + totalspots = 0; + + // testinfo_player_start is only found in regioned levels + spot = find( world, classname, "testplayerstart" ); + if ( spot ) + return spot; + + // choose a info_player_deathmatch point + if ( coop ) { + lastspawn = find( lastspawn, classname, "info_player_coop" ); + + if ( lastspawn == world ) { + lastspawn = find( lastspawn, classname, "info_player_start" ); + } + + if ( lastspawn != world ) { + return lastspawn; + } + } else if ( deathmatch ) { + + // find all spots that don't have visible players nearby + spots = world; + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + totalspots = totalspots + 1; + + thing = findradius( spot.origin, IDEAL_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { + if ( PlayerVisibleToSpawnPoint( spot ) ) { + pcount = pcount + 1; + } + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + + totalspots = totalspots - 1; + + // on small maps with few spawn points, our "ideal" spawn conditions may not be possible to meet + // so fallback to just trying to pick a point without a player on top of it, so we don't start + // a spawn frag loop + if ( numspots == 0 ) { + spot = find( world, classname, "info_player_deathmatch" ); + + while( spot ) { + thing = findradius( spot.origin, MIN_DIST_FROM_DM_SPAWN_POINT ); + pcount = 0; + + while( thing ) { + if ( thing.classname == "player" && thing.health > 0 ) { + pcount = pcount + 1; + } + + thing = thing.chain; + } + + if ( pcount == 0 ) { // good spot! + spot.goalentity = spots; + spots = spot; + numspots = numspots + 1; + } + + // Get the next spot in the chain + spot = find( spot, classname, "info_player_deathmatch" ); + } + } + + // uncomment to force a deferred spawn + // if (forceSpawn == FALSE) return world; + + if ( !numspots ) { + if (forceSpawn == FALSE) { + return world; + } + + // no spots available so just pick one at random + totalspots = rint( ( random() * totalspots ) ); + spot = find( world, classname, "info_player_deathmatch" ); + + while( totalspots > 0 ) { + totalspots = totalspots - 1; + spot = find( spot, classname, "info_player_deathmatch" ); + } + return spot; + } + + // Generate a random number between 1 and numspots + numspots = numspots - 1; + + numspots = rint( ( random() * numspots ) ); + + spot = spots; + while( numspots > 0 ) { + spot = spot.goalentity; + numspots = numspots - 1; + } + + return spot; + } + + if ( serverflags ) { // return with a rune to start + spot = find( world, classname, "info_player_start2" ); + + if ( spot ) { + return spot; + } + } + + spot = find( world, classname, "info_player_start" ); + + if ( !spot ) { + error( "PutClientInServer: no info_player_start on level" ); + } + + return spot; +}; + +entity SelectSpawnPoint(float forceSpawn) { + // use the original rogue spawn logic in tctf + if (teamplay >= TEAM_CTF) { + return SelectSpawnPointRogue(); + } else { + return SelectSpawnPointEX(forceSpawn); + } +} + +/* +=========== +PutClientInServer + +called each time a player is spawned +============ +*/ +void() DecodeLevelParms; +void() PlayerDie; + +void() player_touch; + +void() PutClientInServer = +{ + local entity spot; + + self.classname = "player"; + if (skill == 3 && !deathmatch) + self.health = 50; + else + self.health = 100; + self.takedamage = DAMAGE_AIM; + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_WALK; + self.show_hostile = 0; + if (skill == 3 && !deathmatch) + self.max_health = 50; + else + self.max_health = 100; + self.flags = FL_CLIENT; + self.air_finished = time + 12; + self.dmg = 2; // initial water damage + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.invisible_finished = 0; + self.invincible_finished = 0; + self.effects = 0; + self.invincible_time = 0; + + self.shield_finished = 0; + self.antigrav_finished = 0; + +//ZOID-- + self.ctf_lastreturnedflag = -10; + self.ctf_lastfraggedcarrier = -10; + self.ctf_flagsince = -10; + self.ctf_lasthurtcarrier = -10; +//--ZOID + + if ( coop ) { + self.team = TEAM_HUMANS; + } + + DecodeLevelParms (); + + // spawn selection must be after DecodeLevelParms because of team stuff + spot = SelectSpawnPoint (); + + W_SetCurrentAmmo (); + + self.attack_finished = time; + self.th_pain = player_pain; + self.th_die = PlayerDie; + + self.deadflag = DEAD_NO; +// paustime is set by teleporters to keep the player from moving a while + self.pausetime = 0; + +// spot = SelectSpawnPoint (); + + self.origin = spot.origin + '0 0 1'; + self.angles = spot.angles; + self.fixangle = TRUE; // turn this way immediately + + self.touch = player_touch; + +// oh, this is a hack! + setmodel (self, "progs/eyes.mdl"); + modelindex_eyes = self.modelindex; + + setmodel (self, "progs/player.mdl"); + modelindex_player = self.modelindex; + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + + self.view_ofs = '0 0 22'; + + player_stand1 (); + + if (deathmatch || coop) + { + makevectors(self.angles); + spawn_tfog (self.origin + v_forward*20); + } + + //Grapple stuff + self.on_hook = FALSE; + self.hook_out = FALSE; + + spawn_tdeath (self.origin, self); + + stuffcmd(self, "-attack\n"); // prevent shooting after respawning +}; + + +/* +============================================================================= + + QUAKED FUNCTIONS + +============================================================================= +*/ + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 24) +The normal starting point for a level. +*/ +void() info_player_start = +{ +}; + + +/*QUAKED info_player_start2 (1 0 0) (-16 -16 -24) (16 16 24) +Only used on start map for the return point from an episode. +*/ +void() info_player_start2 = +{ +}; + + +/* +saved out by quaked in region mode +*/ +void() testplayerstart = +{ +}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for deathmatch games +*/ +void() info_player_deathmatch = +{ + SpawnRunes(); +}; + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for coop games +*/ +void() info_player_coop = +{ +}; + +/* +=============================================================================== + +RULES + +=============================================================================== +*/ + +/* +go to the next level for deathmatch +*/ +void() NextLevel = +{ + local entity o; + + if (mapname == "start") + { +/* + if (!cvar("registered")) + { + mapname = "e1m1"; + } + else if (!(serverflags & 1)) + { + mapname = "e1m1"; + serverflags = serverflags | 1; + } + else if (!(serverflags & 2)) + { + mapname = "e2m1"; + serverflags = serverflags | 2; + } + else if (!(serverflags & 4)) + { + mapname = "e3m1"; + serverflags = serverflags | 4; + } + else if (!(serverflags & 8)) + { + mapname = "e4m1"; + serverflags = serverflags - 7; + } +*/ + // PGM 03/02/97 - make it always go to rogue level1 when in our progs + mapname = "r1m1"; + o = spawn(); + o.map = mapname; + } + else + { + // find a trigger changelevel + o = find(world, classname, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if (!o) + { + mapname = "start"; + o = spawn(); + o.map = mapname; + } + } + + nextmap = o.map; + gameover = TRUE; + + if (o.nextthink < time) + { + o.think = execute_changelevel; + o.nextthink = time + 0.1; + } +}; + +/* +============ +CheckRules + +Exit deathmatch games upon conditions +============ +*/ +void() CheckRules = +{ + local float timelimit; + local float fraglimit; + + if (gameover) // someone else quit the game already + return; + + timelimit = cvar("timelimit") * 60; + fraglimit = cvar("fraglimit"); + + if (timelimit && time >= timelimit) + { + NextLevel (); + return; + } + + if (fraglimit && self.frags >= fraglimit) + { + NextLevel (); + return; + } + +//ZOID-- + // update team scores? + TeamCheckUpdate(); +//--ZOID +}; + +//============================================================================ + +void() PlayerDeathThink = +{ + local entity old_self; + local float forward; + + if ((self.flags & FL_ONGROUND)) + { + forward = vlen (self.velocity); + forward = forward - 20; + if (forward <= 0) + self.velocity = '0 0 0'; + else + self.velocity = forward * normalize(self.velocity); + } + + if (self.spawn_deferred) + { + local entity spot; + + spot = SelectSpawnPoint(FALSE); + //dprint("time {} >= self.spawn_deferred {}\n", ftos(time), ftos(self.spawn_deferred)); + if (spot != world || time >= self.spawn_deferred) { + respawn(); + } + + return; + } + +// wait for all buttons released + if (self.deadflag == DEAD_DEAD) + { + if (self.button2 || self.button1 || self.button0) + return; + self.deadflag = DEAD_RESPAWNABLE; + return; + } + +// wait for any button down + if (!self.button2 && !self.button1 && !self.button0) + return; + + self.button0 = 0; + self.button1 = 0; + self.button2 = 0; + respawn(); +}; + + +void() PlayerJump = +{ + local vector start, end; + + if (self.flags & FL_WATERJUMP) + return; + + if (self.waterlevel >= 2) + { + if (self.watertype == CONTENT_WATER) + self.velocity_z = 100; + else if (self.watertype == CONTENT_SLIME) + self.velocity_z = 80; + else + self.velocity_z = 50; + +// play swiming sound + if (self.swim_flag < time) + { + self.swim_flag = time + 1; + if (random() < 0.5) + sound (self, CHAN_BODY, "misc/water1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_BODY, "misc/water2.wav", 1, ATTN_NORM); + } + + return; + } + + if (!(self.flags & FL_ONGROUND)) + return; + + if ( !(self.flags & FL_JUMPRELEASED) ) + return; // don't pogo stick + + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + + self.flags = self.flags - FL_ONGROUND; // don't stairwalk + + self.button2 = 0; +// player jumping sound + sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM); + self.velocity_z = self.velocity_z + 270; +}; + + +/* +=========== +WaterMove + +============ +*/ +.float dmgtime; + +void() WaterMove = +{ +//dprint (ftos(self.waterlevel)); + if (self.movetype == MOVETYPE_NOCLIP) + return; + if (self.health < 0) + return; + + if (self.waterlevel != 3) + { + if (self.air_finished < time) + sound (self, CHAN_VOICE, "player/gasp2.wav", 1, ATTN_NORM); + else if (self.air_finished < time + 9) + sound (self, CHAN_VOICE, "player/gasp1.wav", 1, ATTN_NORM); + self.air_finished = time + 12; + self.dmg = 2; + } + else if (self.air_finished < time) + { // drown! + if (self.pain_finished < time) + { + self.dmg = self.dmg + 2; + if (self.dmg > 15) + self.dmg = 10; + T_Damage (self, world, world, self.dmg); + self.pain_finished = time + 1; + } + } + + if (!self.waterlevel) + { + if (self.flags & FL_INWATER) + { + // play leave water sound + sound (self, CHAN_BODY, "misc/outwater.wav", 1, ATTN_NORM); + self.flags = self.flags - FL_INWATER; + } + return; + } + + if (self.watertype == CONTENT_LAVA) + { // do damage + if (self.dmgtime < time) + { + if (self.radsuit_finished > time) + self.dmgtime = time + 1; + else + self.dmgtime = time + 0.2; + + T_Damage (self, world, world, 10*self.waterlevel); + } + } + else if (self.watertype == CONTENT_SLIME) + { // do damage + if (self.dmgtime < time && self.radsuit_finished < time) + { + self.dmgtime = time + 1; + T_Damage (self, world, world, 4*self.waterlevel); + } + } + + if ( !(self.flags & FL_INWATER) ) + { + +// player enter water sound + + if (self.watertype == CONTENT_LAVA) + sound (self, CHAN_BODY, "player/inlava.wav", 1, ATTN_NORM); + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/inh2o.wav", 1, ATTN_NORM); + if (self.watertype == CONTENT_SLIME) + sound (self, CHAN_BODY, "player/slimbrn2.wav", 1, ATTN_NORM); + + self.flags = self.flags + FL_INWATER; + self.dmgtime = 0; + } + + if (! (self.flags & FL_WATERJUMP) ) + self.velocity = self.velocity - 0.8*self.waterlevel*frametime*self.velocity; +}; + +void() CheckWaterJump = +{ + local vector start, end; + +// check for a jump-out-of-water + makevectors (self.angles); + start = self.origin; + start_z = start_z + 8; + v_forward_z = 0; + normalize(v_forward); + end = start + v_forward*24; + traceline (start, end, TRUE, self); + if (trace_fraction < 1) + { // solid at waist + start_z = start_z + self.maxs_z - 8; + end = start + v_forward*24; + self.movedir = trace_plane_normal * -50; + traceline (start, end, TRUE, self); + if (trace_fraction == 1) + { // open at eye level + self.flags = self.flags | FL_WATERJUMP; + self.velocity_z = 225; + self.flags = self.flags - (self.flags & FL_JUMPRELEASED); + self.teleport_time = time + 2; // safety net + return; + } + } +}; + + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void() PlayerPreThink = +{ + local float mspeed, aspeed; + local float r; + + if (intermission_running) + { + IntermissionThink (); // otherwise a button could be missed between + return; // the think tics + } + + if (self.view_ofs == '0 0 0') + return; // intermission or finale + + if (earthquake_active) + { + if ( self.flags & FL_ONGROUND ) + { + self.velocity_x = self.velocity_x + + (random() * earthquake_intensity * 2) - + earthquake_intensity; + self.velocity_y = self.velocity_y + + (random() * earthquake_intensity * 2) - + earthquake_intensity; + self.velocity_z = self.velocity_z + + (random() * earthquake_intensity * 2) - + earthquake_intensity; + } + } + + makevectors (self.v_angle); // is this still used + +//ZOID-- + // TeamCheckLock performs all necessary teamlock checking, and performs all + // actions needed. + TeamCheckLock(); +//--ZOID + + CheckMOTD(); + CheckRules (); + WaterMove (); + + if (self.waterlevel == 2) + CheckWaterJump (); + + if (self.deadflag >= DEAD_DEAD) + { + PlayerDeathThink (); + return; + } + + if (self.deadflag == DEAD_DYING) + return; // dying, so do nothing + + if (self.button2) + { + PlayerJump (); + } + else + self.flags = self.flags | FL_JUMPRELEASED; + +// teleporters can force a non-moving pause time + if (time < self.pausetime) + self.velocity = '0 0 0'; + + if (self.items2 & IT2_ANTIGRAV) + { + if ( time > self.AGping ) + { + sound ( self, CHAN_AUTO, "belt/use.wav", 0.4, ATTN_NORM); + self. AGping = time + 3; + if(self. AGping >= (self.antigrav_finished - 3)) + self. AGping = self.antigrav_finished + 3; + } + } + + RuneApplyElder(self); // regeneration rune + + // Track the grapple + if (self.hook_out) + GrappleService(); +}; + +/* +================ +CheckPowerups + +Check for turning off powerups +================ +*/ +void() CheckPowerups = +{ + if (self.health <= 0) + return; + +// invisibility + if (self.invisible_finished) + { +// sound and screen flash when items starts to run out + if (self.invisible_sound < time) + { + sound (self, CHAN_AUTO, "items/inv3.wav", 0.5, ATTN_IDLE); + self.invisible_sound = time + ((random() * 3) + 1); + } + + + if (self.invisible_finished < time + 3) + { + if (self.invisible_time == 1) + { + sprint(self, "$qc_ring_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/inv2.wav", 1, ATTN_NORM); + self.invisible_time = time + 1; + } + + if (self.invisible_time < time) + { + self.invisible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invisible_finished < time) + { // just stopped + self.items = self.items - IT_INVISIBILITY; + self.invisible_finished = 0; + self.invisible_time = 0; + } + + // use the eyes + self.frame = 0; + self.modelindex = modelindex_eyes; + } + else + self.modelindex = modelindex_player; // don't use eyes + +// invincibility + if (self.invincible_finished) + { +// sound and screen flash when items starts to run out + if (self.invincible_finished < time + 3) + { + if (self.invincible_time == 1) + { + sprint(self, "$qc_protection_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/protect2.wav", 1, ATTN_NORM); + self.invincible_time = time + 1; + } + + if (self.invincible_time < time) + { + self.invincible_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.invincible_finished < time) + { // just stopped + self.items = self.items - IT_INVULNERABILITY; + self.invincible_time = 0; + self.invincible_finished = 0; + } + if (self.invincible_finished > time) + self.effects = self.effects | EF_PENTALIGHT; + else + self.effects = self.effects - (self.effects & EF_PENTALIGHT); + } + +// super damage + if (self.super_damage_finished) + { + +// sound and screen flash when items starts to run out + + if (self.super_damage_finished < time + 3) + { + if (self.super_time == 1) + { + sprint(self, "$qc_quad_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/damage2.wav", 1, ATTN_NORM); + self.super_time = time + 1; + } + + if (self.super_time < time) + { + self.super_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.super_damage_finished < time) + { // just stopped + self.items = self.items - IT_QUAD; + self.super_damage_finished = 0; + self.super_time = 0; + } + if (self.super_damage_finished > time) + self.effects = self.effects | EF_QUADLIGHT; + else + self.effects = self.effects - (self.effects & EF_QUADLIGHT); + } + +// suit + if (self.radsuit_finished) + { + self.air_finished = time + 12; // don't drown + +// sound and screen flash when items starts to run out + if (self.radsuit_finished < time + 3) + { + if (self.rad_time == 1) + { + sprint(self, "$qc_biosuit_fade"); + stuffcmd (self, "bf\n"); + sound (self, CHAN_AUTO, "items/suit2.wav", 1, ATTN_NORM); + self.rad_time = time + 1; + } + + if (self.rad_time < time) + { + self.rad_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if (self.radsuit_finished < time) + { // just stopped + self.items = self.items - IT_SUIT; + self.rad_time = 0; + self.radsuit_finished = 0; + } + } + + if(self.shield_finished) + { + if ( self.shield_finished < time + 3) + { + if (self.shield_time == 1) + { + sprint (self, "$qc_shield_failing"); + sound (self, CHAN_AUTO, "shield/fadeout.wav", 1, ATTN_NORM); + self.shield_time = 0; + } + if (self.shield_time < time) + { + self.shield_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if ( self.shield_finished < time) + { + sprint (self, "$qc_shield_lost"); + self.shield_finished = 0; + self.items2 = self.items2 - IT2_SHIELD; + } + } + + if(self.antigrav_finished) + { + if ( self.antigrav_finished < time + 3) + { + if (self.antigrav_time == 1) + { + sprint (self, "$qc_antigrav_failing"); + self.antigrav_time = 0; + sound (self, CHAN_AUTO, "belt/fadeout.wav", 1, ATTN_NORM); + } + if (self.antigrav_time < time) + { + self.antigrav_time = time + 1; + stuffcmd (self, "bf\n"); + } + } + + if ( self.antigrav_finished < time) + { + sprint (self, "$qc_antigrav_lost"); + self.antigrav_finished = 0; + self.items2 = self.items2 - IT2_ANTIGRAV; + self.gravity = 1.0; + } + } +}; + + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void() xpackEnding; + +void() PlayerPostThink = +{ + local float mspeed, aspeed; + local float r; + + if (self.view_ofs == '0 0 0') + return; // intermission or finale + if (self.deadflag) + return; + + if (cutscene_running) + { + xpackEnding(); + } + +// do weapon stuff + + W_WeaponFrame (); + +// check to see if player landed and play landing sound + if ((self.jump_flag < -300) && + (self.flags & FL_ONGROUND) && (self.health > 0)) + { + if (self.watertype == CONTENT_WATER) + sound (self, CHAN_BODY, "player/h2ojump.wav", 1, ATTN_NORM); + else if (self.jump_flag < -650) + { + T_Damage (self, world, world, 5); + sound (self, CHAN_VOICE, "player/land2.wav", 1, ATTN_NORM); + self.deathtype = "falling"; + } + else + sound (self, CHAN_VOICE, "player/land.wav", 1, ATTN_NORM); + + self.jump_flag = 0; + } + + if (!(self.flags & FL_ONGROUND)) + self.jump_flag = self.velocity_z; + + CheckPowerups (); +}; + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +void() ClientConnect = +{ + bprint("$qc_entered", self.netname); + +//ZOID-- + self.suicide_count = 0; + self.ctf_killed = 0; + + SetMOTD(); + + // If this is our first connection, parm14 is < 0 + // Set lastteam negative. + if (parm14 < 0 && teamplay > 0) + { + if (cvar("gamecfg") & GAMECFG_USE_COLOR) + self.steam = self.team; // accept joining players color if legal + else + self.steam = -50; // always reassign + TeamCheckLock(); + } +//--ZOID + +// a client connecting during an intermission can cause problems + if (intermission_running) + ExitIntermission (); +}; + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server +============ +*/ +void() ClientDisconnect = +{ + if (gameover) + return; + // if the level end trigger has been activated, just return + // since they aren't *really* leaving + + // let everyone else know + bprint("$qc_left_game", self.netname, ftos(self.frags)); + sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE); + + sphere_remove(self); + if ( tag_token_owner == self) + tag_token_drop(); + +//ZOID-- + TeamDropFlagOfPlayer(self); + self.steam = -50; + self.team = 0; +//--ZOID + + set_suicide_frame (); +}; + +/* +=========== +ClientObituary + +called when a player dies +============ +*/ +void(entity targ, entity attacker) ClientObituary = +{ + local float rnum; + local string deathstring, deathstring2; + rnum = random(); + + if (targ.classname == "player") + { +// ZOID-- + TeamResetCarrier(targ); +// --ZOID + + if (attacker.classname == "teledeath") + { + bprint("$qc_telefragged", targ.netname, attacker.owner.netname); + + attacker.owner.frags = attacker.owner.frags + 1; + return; + } + + if (attacker.classname == "teledeath2") + { + bprint("$qc_satans_power", targ.netname); + + targ.frags = targ.frags - 1; + return; + } + + if (attacker.classname == "player") + { + if (targ == attacker) + { + // killed self + attacker.frags = attacker.frags - 1; + + if (targ.weapon == 64 && targ.waterlevel > 1) + { + if (targ.watertype == CONTENT_SLIME) + bprint("$qc_discharge_slime", targ.netname); + + else if (targ.watertype == CONTENT_LAVA) + bprint("$qc_discharge_lava", targ.netname); + + else + bprint("$qc_discharge_water", targ.netname); + + return; + } + if (targ.weapon == 16) + bprint("$qc_suicide_pin", targ.netname); + else if (rnum < 0.50) + bprint("$qc_suicide_bored", targ.netname); + +//ZOID-- +//gibbed for changing teams + else if (teamplay && (targ.team != targ.steam)) + { + if (cvar("gamecfg") & GAMECFG_ALLOW_CHG) + bprint("$qc_changed_teams", targ.netname); + else + bprint("$qc_tried_change_teams", targ.netname); + } +//--ZOID + + else + bprint("$qc_suicide_loaded", targ.netname); + + return; + } + else if ( (teamplay == 2) && (targ.team > 0)&&(targ.team == attacker.team) ) + { + if (rnum < 0.25) + bprint("$qc_ff_teammate", attacker.netname); + else if (rnum < 0.50) + bprint("$qc_ff_glasses", attacker.netname); + else if (rnum < 0.75) + bprint("$qc_ff_otherteam", attacker.netname); + else + bprint("$qc_ff_friend", attacker.netname); + attacker.frags = attacker.frags - 1; + return; + } + else + { + if (teamplay == TEAM_DMATCH_TAG) + dmatch_score (targ, attacker); + else + { + attacker.frags = attacker.frags + 1; +//ZOID-- + TeamAssists(targ, attacker); +//--ZOID + } + + rnum = attacker.weapon; + if (rnum == IT_AXE) + { + bprint("$qc_death_ax", targ.netname, attacker.netname); + return; + } +//ZOID-- + if (rnum == IT_GRAPPLE) + { + bprint("$qc_death_grappled", targ.netname, attacker.netname); + return; + } +//--ZOID + if (rnum == IT_SHOTGUN) + { + bprint("$qc_death_sg", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SUPER_SHOTGUN) + { + bprint("$qc_death_dbl", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_NAILGUN) + { + bprint("$qc_death_nail", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_SUPER_NAILGUN) + { + bprint("$qc_death_sng", targ.netname, attacker.netname); + return; + } + + if (rnum == IT_GRENADE_LAUNCHER) + { + if (targ.health < -40) + { + bprint("$qc_death_gl1", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_gl2", targ.netname, attacker.netname); + return; + } + } + + if (rnum == IT_ROCKET_LAUNCHER) + { + if (attacker.super_damage_finished > 0 && targ.health < -40) + { + rnum = random(); + + if (rnum < 0.3) + { + bprint("$qc_death_rl_quad1", targ.netname, attacker.netname); + return; + } + else if (rnum < 0.6) + { + bprint("$qc_death_rl_quad2", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_rl1", targ.netname, attacker.netname); + return; + } + } + else + { + if (targ.health < -40) + { + bprint("$qc_death_rl2", targ.netname, attacker.netname); + return; + } + else + { + bprint("$qc_death_rl3", targ.netname, attacker.netname); + return; + } + } + } + + if (rnum == IT_LIGHTNING) + { + if (attacker.waterlevel > 1) + { + bprint("$qc_death_lg1", targ.netname, attacker.netname); + if (attacker.invincible_finished) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SURVIVE_DISCHARGE"); + } + } + else + bprint("$qc_death_lg2", targ.netname, attacker.netname); + + return; + } + + if (rnum == IT_LAVA_NAILGUN || rnum == IT_LAVA_SUPER_NAILGUN) + { + bprint("$qc_death_burned", targ.netname, attacker.netname); + return; + } + else if (rnum == IT_PLASMA_GUN) + { + bprint("$qc_death_fused", targ.netname, attacker.netname); + return; + } + else if (rnum == IT_MULTI_GRENADE || rnum == IT_MULTI_ROCKET) + { + bprint("$qc_death_blasted", targ.netname, attacker.netname); + return; + } + } + return; + } + else + { + targ.frags = targ.frags - 1; + + // killed by a montser? + if (attacker.flags & FL_MONSTER) + { + if (attacker.classname == "monster_army") + bprint ("$qc_ks_grunt", targ.netname); + if (attacker.classname == "monster_demon1") + bprint ("$qc_ks_fiend", targ.netname); + if (attacker.classname == "monster_dog") + bprint ("$qc_ks_rottweiler", targ.netname); + if (attacker.classname == "monster_dragon") + bprint ("$qc_ks_dragon1", targ.netname); + if (attacker.classname == "monster_dragon_dead") + bprint ("$qc_ks_dragon2", targ.netname); + if (attacker.classname == "monster_enforcer") + bprint ("$qc_ks_enforcer", targ.netname); + if (attacker.classname == "monster_fish") + bprint ("$qc_ks_rotfish", targ.netname); + if (attacker.classname == "monster_hell_knight") + bprint ("$qc_ks_deathknight", targ.netname); + if (attacker.classname == "monster_knight") + bprint ("$qc_ks_knight", targ.netname); + if (attacker.classname == "monster_ogre") + bprint ("$qc_ks_ogre", targ.netname); + if (attacker.classname == "monster_oldone") + bprint ("$qc_ks_shub", targ.netname); + if (attacker.classname == "monster_shalrath") + bprint ("$qc_ks_vore", targ.netname); + if (attacker.classname == "monster_shambler") + bprint ("$qc_ks_shambler", targ.netname); + if (attacker.classname == "monster_tarbaby") + bprint ("$qc_ks_spawn", targ.netname); + if (attacker.classname == "monster_vomit") + bprint ("$qc_ks_vomitus", targ.netname); + if (attacker.classname == "monster_wizard") + bprint ("$qc_ks_scrag", targ.netname); + if (attacker.classname == "monster_zombie") + bprint ("$qc_ks_zombie", targ.netname); + + if (attacker.classname == "monster_eel") + bprint ("$qc_ks_eel", targ.netname); + if (attacker.classname == "monster_wrath") + bprint ("$qc_ks_wrath", targ.netname); + if (attacker.classname == "monster_super_wrath") + bprint ("$qc_ks_overlord", targ.netname); + if (attacker.classname == "monster_sword") + bprint ("$qc_ks_swordsman", targ.netname); + if (attacker.classname == "monster_lava_man") + bprint ("$qc_ks_hephaestus", targ.netname); + if (attacker.classname == "monster_morph") + bprint ("$qc_ks_guardian", targ.netname); + if (attacker.classname == "monster_mummy") + bprint ("$qc_ks_mummy", targ.netname); + + return; + } + + // tricks and traps + if (attacker.classname == "explo_box") + { + bprint ("$qc_ks_blew_up", targ.netname); + return; + } + if (attacker.solid == SOLID_BSP && attacker != world) + { + bprint ("$qc_death_squish", targ.netname); + return; + } + if (attacker.classname == "trap_shooter" || attacker.classname == "trap_spikeshooter") + { + bprint ("$qc_ks_spiked", targ.netname); + return; + } + if (attacker.classname == "fireball") + { + bprint ("$qc_ks_lavaball", targ.netname); + return; + } + if (attacker.classname == "trigger_changelevel") + { + bprint ("$qc_ks_tried_leave", targ.netname); + return; + } + + if (attacker.classname == "ltrail_start" || + attacker.classname == "ltrail_relay") + { + bprint ("$qc_ks_rode_lightning", targ.netname); + return; + } + if (attacker.classname == "pendulum") + { + bprint ("$qc_ks_cleaved", targ.netname); + return; + } + if (attacker.classname == "buzzsaw") + { + bprint ("$qc_ks_sliced", targ.netname); + return; + } + if (attacker.classname == "plasma") + { + bprint ( "$qc_ks_plasma", targ.netname); + return; + } + if (attacker.classname == "Vengeance") + { + // vengeance sphere doesn't count as a negative frag + targ.frags = targ.frags + 1; + bprint ( "$qc_death_vengeance", targ.netname); + return; + } + if (attacker.classname == "power_shield") + { + // shield attacks should count for frags too.. + // shield death doesn't count as a negative frag + targ.frags = targ.frags + 1; + attacker.owner.frags = attacker.owner.frags + 1; + TeamAssists(targ, attacker.owner); //PGM + + bprint("$qc_death_smashed", targ.netname, attacker.owner.netname); + return; + } + + // in-water deaths + rnum = targ.watertype; + if (rnum == -3) + { + if (random() < 0.5) + bprint("$qc_death_drown1", targ.netname); + else + bprint("$qc_death_drown2", targ.netname); + + return; + } + else if (rnum == -4) + { + if (random() < 0.5) + bprint("$qc_death_slime1", targ.netname); + else + bprint("$qc_death_slime2", targ.netname); + + return; + } + else if (rnum == -5) + { + if (targ.health < -15) + { + bprint("$qc_death_lava1", targ.netname); + return; + } + + if (random() < 0.5) + bprint("$qc_death_lava2", targ.netname); + else + bprint("$qc_death_lava3", targ.netname); + + return; + } + + if (attacker.solid == SOLID_BSP && attacker != world) + { + bprint("$qc_death_squish", targ.netname); + return; + } + + if (targ.deathtype == "falling") + { + targ.deathtype = string_null; + bprint("$qc_death_fall", targ.netname); + return; + } + + bprint("$qc_death_died", targ.netname); + } + } +}; diff --git a/quakec_rogue/combat.qc b/quakec_rogue/combat.qc new file mode 100644 index 0000000..d140d68 --- /dev/null +++ b/quakec_rogue/combat.qc @@ -0,0 +1,412 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// combat.qc + +float IsExplosionDamage; + +void() T_MissileTouch; +void() info_player_start; +void(entity targ, entity attacker) ClientObituary; + +void() monster_death_use; + +//============================================================================ + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +float(entity targ, entity inflictor) CanDamage = +{ +// bmodels need special checking because their origin is 0,0,0 + if (targ.movetype == MOVETYPE_PUSH) + { + traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self); + if (trace_fraction == 1) + return TRUE; + if (trace_ent == targ) + return TRUE; + return FALSE; + } + + traceline(inflictor.origin, targ.origin, TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + traceline(inflictor.origin, targ.origin + '15 -15 0', TRUE, self); + if (trace_fraction == 1) + return TRUE; + + return FALSE; +}; + + +/* +============ +Killed +============ +*/ +void(entity targ, entity attacker) Killed = +{ + local entity oself; + + oself = self; + self = targ; + + if (self.health < -99) + self.health = -99; // don't let sbar look bad if a player + + if (self.movetype == MOVETYPE_PUSH || self.movetype == MOVETYPE_NONE) + { // doors, triggers, etc + self.th_die (); + self = oself; + return; + } + + self.enemy = attacker; + +// bump the monster counter + if (self.flags & FL_MONSTER) + { + killed_monsters = killed_monsters + 1; + WriteByte (MSG_ALL, SVC_KILLEDMONSTER); + if (attacker.classname == "player") + { + attacker.frags = attacker.frags + 1; + } + + if (attacker != self && attacker.flags & FL_MONSTER) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_FRIENDLY_FIRE"); + } + } + +// PGM - guarantee gib if killed by buzzsaw. + if (attacker.classname == "buzzsaw" || + attacker.classname == "Vengeance" || + attacker.classname == "pendulum") + self.health = -99; + +// PGM - gravity fix! + self.gravity = 1.0; + ClientObituary(self, attacker); + + self.takedamage = DAMAGE_NO; + self.touch = SUB_Null; + + monster_death_use(); + self.th_die (); + + self = oself; +}; + +// ==== +// Function prototype for shield impact handler. +// ==== +float(entity targ, entity inflictor, entity attacker, float damage) shield_hit; + +/* +============ +T_Damage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +============ +*/ +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage= +{ + local vector dir; + local entity oldself; + local float save; + local float take; + + if (!targ.takedamage) + return; + + // mal: in Coop, don't let bots hurt human teammates - that would be REALLY annoying... + if ( coop && targ != attacker && targ.classname == "player" && attacker.classname == "player" ) { + if ( attacker.flags & FL_ISBOT && !( targ.flags & FL_ISBOT ) ) { + return; + } + } + + if (targ.classname == "trigger_explosion") + { + if ( IsExplosionDamage != 1) + return; + } + IsExplosionDamage = 0; + + if (targ.items2 & IT2_SHIELD) + { + // Allow some damage to pass through. + damage = shield_hit(targ, inflictor, attacker, damage); + if (damage == 0) + return; + } + +// used by buttons and triggers to set activator for target firing + damage_attacker = attacker; + +// check for quad damage powerup on the attacker + if (attacker.super_damage_finished > time) + damage = damage * 4; +// rune power up (Black Magic) + if (deathmatch) + damage = RuneApplyBlack(damage, attacker); + +// save damage based on the target's armor level + +//ZOID-- + if (TeamArmorDam(targ, inflictor, attacker, damage)) + save = ceil(targ.armortype*damage); + else + save = 0; +//--ZOID + + if (save >= targ.armorvalue) + { + save = targ.armorvalue; + targ.armortype = 0; // lost all armor + targ.items2 = targ.items2 - + (targ.items2 & (IT2_ARMOR1 | IT2_ARMOR2 | IT2_ARMOR3)); + } + + targ.armorvalue = targ.armorvalue - save; + take = ceil(damage-save); + +// add to the damage total for clients, which will be sent as a single +// message at the end of the frame +// FIXME: remove after combining shotgun blasts? + if (targ.flags & FL_CLIENT) + { + targ.dmg_take = targ.dmg_take + take; + targ.dmg_save = targ.dmg_save + save; + targ.dmg_inflictor = inflictor; + } + +// figure momentum add + if ( (inflictor != world) && (targ.movetype == MOVETYPE_WALK) ) + { + dir = targ.origin - (inflictor.absmin + inflictor.absmax) * 0.5; + dir = normalize(dir); + targ.velocity = targ.velocity + dir*damage*8; + } + +// check for godmode or invincibility + if (targ.flags & FL_GODMODE) + return; + if (targ.invincible_finished >= time) + { + if (targ.invincible_sound < time) + { + sound (targ, CHAN_ITEM, "items/protect3.wav", 1, ATTN_NORM); + targ.invincible_sound = time + 2; + } + return; + } + +// team play damage avoidance +// if ( (teamplay == 1) && (targ.team > 0)&&(targ.team == attacker.team) ) +// return; +//ZOID-- + if (!TeamHealthDam(targ, inflictor, attacker, damage)) + return; // same team +//--ZOID + +// rune power up (Earth Magic) + if (deathmatch) + take = RuneApplyEarth(take, targ); + +// do the damage + targ.health = targ.health - take; + + if (targ.health <= 0) + { + Killed (targ, attacker); + return; + } + +// react to the damage + oldself = self; + self = targ; + + if ( (self.flags & FL_MONSTER) && attacker != world) + { + // get mad unless of the same class (except for soldiers) + if (self != attacker && attacker != self.enemy) + { + if ( (self.classname != attacker.classname) + || (self.classname == "monster_army" ) ) + { + if ( self.classname != "monster_dragon") + { + if (self.enemy.classname == "player") + self.oldenemy = self.enemy; + self.enemy = attacker; + FoundTarget (); + } + } + } + } + + if (self.th_pain) + { + self.th_pain (attacker, take); + // nightmare mode monsters don't go into pain frames often + if (skill == 3) + self.pain_finished = time + 5; + } + + self = oldself; +}; + +/* +============ +T_EELZap +============ +*/ +void(entity inflictor, entity attacker, float damage) T_EELZap = +{ + local float points; + local entity head; + local vector org; + + head = findradius(inflictor.origin, damage+40); + + while (head) + { + if (head.takedamage) + { + org = head.origin + (head.mins + head.maxs)*0.5; + points = 0.5*vlen (inflictor.origin - org); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (head, inflictor)) + { // eels take no damage from this attack + if (head.classname != "monster_eel" && + (head.flags & FL_INWATER)) + T_Damage (head, inflictor, attacker, points); + } + } + } + head = head.chain; + } +}; + +/* +============ +T_RadiusDamage +============ +*/ +void(entity inflictor, entity attacker, float damage, entity ignore) T_RadiusDamage = +{ + local float points; + local entity head; + local vector org; + + IsExplosionDamage = 1; + + head = findradius(inflictor.origin, damage+40); + + while (head) + { + if (head != ignore) + { + if (head.takedamage) + { + org = head.origin + (head.mins + head.maxs)*0.5; + points = 0.5*vlen (inflictor.origin - org); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (head, inflictor)) + { // shambler takes half damage from all explosions + if (head.classname == "monster_shambler") + T_Damage (head, inflictor, attacker, points*0.5); + else + T_Damage (head, inflictor, attacker, points); + } + } + } + } + head = head.chain; + } +}; + +/* +============ +T_BeamDamage +============ +*/ +void(entity attacker, float damage) T_BeamDamage = +{ + local float points; + local entity head; + + head = findradius(attacker.origin, damage+40); + + while (head) + { + if (head.takedamage) + { + points = 0.5*vlen (attacker.origin - head.origin); + if (points < 0) + points = 0; + points = damage - points; + if (head == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (head, attacker)) + { + if (head.classname == "monster_shambler") + T_Damage (head, attacker, attacker, points*0.5); + else + T_Damage (head, attacker, attacker, points); + } + } + } + head = head.chain; + } +}; + diff --git a/quakec_rogue/defs.qc b/quakec_rogue/defs.qc new file mode 100644 index 0000000..043b580 --- /dev/null +++ b/quakec_rogue/defs.qc @@ -0,0 +1,882 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* +============================================================================== + + SOURCE FOR GLOBALVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system globals +// +entity self; +entity other; +entity world; +float time; +float frametime; + +float force_retouch; // force all entities to touch triggers + // next frame. this is needed because + // non-moving things don't normally scan + // for triggers, and when a trigger is + // created (like a teleport trigger), it + // needs to catch everything. + // decremented each frame, so set to 2 + // to guarantee everything is touched +string mapname; + +float deathmatch; +float coop; +float teamplay; +//float norandom; // ROGUE: disable random respawn of powerups. + +float serverflags; // propagated from level to level, used to + // keep track of completed episodes + +float total_secrets; +float total_monsters; + +float found_secrets; // number of secrets found +float killed_monsters; // number of monsters killed + + +// spawnparms are used to encode information about clients across server +// level changes +float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16; + +// +// global variables set by built in functions +// +vector v_forward, v_up, v_right; // set by makevectors() + +// set by traceline / tracebox +float trace_allsolid; +float trace_startsolid; +float trace_fraction; +vector trace_endpos; +vector trace_plane_normal; +float trace_plane_dist; +entity trace_ent; +float trace_inopen; +float trace_inwater; + +entity msg_entity; // destination of single entity writes + +// +// required prog functions +// +void() main; // only for testing + +void() StartFrame; + +void() PlayerPreThink; +void() PlayerPostThink; + +void() ClientKill; +void() ClientConnect; +void() PutClientInServer; // call after setting the parm1... parms +void() ClientDisconnect; + +void() SetNewParms; // called when a client first connects to + // a server. sets parms so they can be + // saved off for restarts + +void() SetChangeParms; // call to set parms for self so they can + // be saved for a level transition + +//================================================ +void end_sys_globals; // flag for structure dumping +//================================================ + +/* +============================================================================== + + SOURCE FOR ENTVARS_T C STRUCTURE + +============================================================================== +*/ + +// +// system fields (*** = do not set in prog code, maintained by C code) +// +.float modelindex; // *** model index in the precached list +.vector absmin, absmax; // *** origin + mins / maxs + +.float ltime; // local time for entity +.float movetype; +.float solid; + +.vector origin; // *** +.vector oldorigin; // *** +.vector velocity; +.vector angles; +.vector avelocity; + +.vector punchangle; // temp angle adjust from damage or recoil + +.string classname; // spawn function +.string model; +.float frame; +.float skin; +.float effects; + +.vector mins, maxs; // bounding box extents reletive to origin +.vector size; // maxs - mins + +.void() touch; +.void() use; +.void() think; +.void() blocked; // for doors or plats, called when can't push other + +.float nextthink; +.entity groundentity; + +// stats +.float health; +.float frags; +.float weapon; // one of the IT_SHOTGUN, etc flags +.string weaponmodel; +.float weaponframe; +.float currentammo; +.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; + +.float items; // bit flags + +.float takedamage; +.entity chain; +.float deadflag; + +.vector view_ofs; // add to origin to get eye point + + +.float button0; // fire +.float button1; // use +.float button2; // jump + +.float impulse; // weapon changes + +.float fixangle; +.vector v_angle; // view / targeting angle for players +.float idealpitch; // calculated pitch angle for lookup up slopes + + +.string netname; + +.entity enemy; + +.float flags; + +.float colormap; +.float team; + +.float max_health; // players maximum health is stored here + +.float teleport_time; // don't back up + +.float armortype; // save this fraction of incoming damage +.float armorvalue; + +.float waterlevel; // 0 = not in, 1 = feet, 2 = wast, 3 = eyes +.float watertype; // a contents value + +.float ideal_yaw; +.float yaw_speed; + +.entity aiment; + +.entity goalentity; // a movetarget or an enemy + +.float spawnflags; + +.string target; +.string targetname; + +// damage is accumulated through a frame. and sent as one single +// message, so the super shotgun doesn't generate huge messages +.float dmg_take; +.float dmg_save; +.entity dmg_inflictor; + +.entity owner; // who launched a missile +.vector movedir; // mostly for doors, but also used for waterjump + +.string message; // trigger messages + +.float sounds; // either a cd track number or sound number + +.string noise, noise1, noise2, noise3; // contains names of wavs to play + +//================================================ +void end_sys_fields; // flag for structure dumping +//================================================ + +.float items2; +//.float new_weapon; +//.float new_items; +.float ammo_shells1, ammo_nails1, ammo_rockets1, ammo_cells1; +.float ammo_lava_nails; +.float ammo_multi_rockets; +.float ammo_plasma; +.float gravity; + +/* +============================================================================== + + VARS NOT REFERENCED BY C CODE + +============================================================================== +*/ + + +// +// constants +// + +float FALSE = 0; +float TRUE = 1; + +// edict.flags +float FL_FLY = 1; +float FL_SWIM = 2; +float FL_CLIENT = 8; // set for all client edicts +float FL_INWATER = 16; // for enter / leave water splash +float FL_MONSTER = 32; +float FL_GODMODE = 64; // player cheat +float FL_NOTARGET = 128; // player cheat +float FL_ITEM = 256; // extra wide size for bonus items +float FL_ONGROUND = 512; // standing on something +float FL_PARTIALGROUND = 1024; // not all corners are valid +float FL_WATERJUMP = 2048; // player jumping out of water +float FL_JUMPRELEASED = 4096; // for jump debouncing +float FL_ISBOT = 8192; // mal: this will be set on bot players +float FL_NO_PLAYERS = 16384; // mal: used by the spawn point editor +float FL_NO_MONSTERS = 32768; // mal: used by the spawn point editor +float FL_NO_BOTS = 65536; // mal: any item marked with this will be ignored by bots. +float FL_OBJECTIVE = 131072; // mal: any item marked with this is an objective of some kind. + +// edict.movetype values +float MOVETYPE_NONE = 0; // never moves +//float MOVETYPE_ANGLENOCLIP = 1; +//float MOVETYPE_ANGLECLIP = 2; +float MOVETYPE_WALK = 3; // players only +float MOVETYPE_STEP = 4; // discrete, not real time unless fall +float MOVETYPE_FLY = 5; +float MOVETYPE_TOSS = 6; // gravity +float MOVETYPE_PUSH = 7; // no clip to world, push and crush +float MOVETYPE_NOCLIP = 8; +float MOVETYPE_FLYMISSILE = 9; // fly with extra size against monsters +float MOVETYPE_BOUNCE = 10; +float MOVETYPE_BOUNCEMISSILE = 11; // bounce with extra size + +// edict.solid values +float SOLID_NOT = 0; // no interaction with other objects +float SOLID_TRIGGER = 1; // touch on edge, but not blocking +float SOLID_BBOX = 2; // touch on edge, block +float SOLID_SLIDEBOX = 3; // touch on edge, but not an onground +float SOLID_BSP = 4; // bsp clip, touch on edge, block + +// range values +float RANGE_MELEE = 0; +float RANGE_NEAR = 1; +float RANGE_MID = 2; +float RANGE_FAR = 3; + +// deadflag values + +float DEAD_NO = 0; +float DEAD_DYING = 1; +float DEAD_DEAD = 2; +float DEAD_RESPAWNABLE = 3; + +// takedamage values + +float DAMAGE_NO = 0; +float DAMAGE_YES = 1; +float DAMAGE_AIM = 2; + +// items +float IT_AXE = 2048; // 1 +float IT_SHOTGUN = 1; +float IT_SUPER_SHOTGUN = 2; +float IT_NAILGUN = 4; +float IT_SUPER_NAILGUN = 8; +float IT_GRENADE_LAUNCHER = 16; +float IT_ROCKET_LAUNCHER = 32; +float IT_LIGHTNING = 64; // 8 +float IT_SHELLS = 128; +float IT_NAILS = 256; +float IT_ROCKETS = 512; +float IT_CELLS = 1024; +float IT_LAVA_NAILGUN = 4096; +float IT_LAVA_SUPER_NAILGUN = 8192; +float IT_MULTI_GRENADE = 16384; //15 +float IT_MULTI_ROCKET = 32768; +float IT_PLASMA_GUN = 65536; +float IT_KEY1 = 131072; +float IT_KEY2 = 262144; +float IT_INVISIBILITY = 524288; +float IT_INVULNERABILITY = 1048576; +float IT_SUIT = 2097152; +float IT_QUAD = 4194304; //23 +//ZOID -- +float IT_GRAPPLE = 8388608; // grapple overload, hope it +//-- ZOID // doesn't mess things up! + + +float IT2_ARMOR1 = 1; //24 +float IT2_ARMOR2 = 2; +float IT2_ARMOR3 = 4; +float IT2_LAVA_NAILS = 8; +float IT2_PLASMA_AMMO = 16; +float IT2_MULTI_ROCKETS = 32; //29 +float IT2_SHIELD = 64; +float IT2_ANTIGRAV = 128; +float IT2_SUPERHEALTH = 256; //32 +float IT2_EARTHQUAKE = 512; +float IT2_V_SPHERE = 1024; + +// point content values + +float CONTENT_EMPTY = -1; +float CONTENT_SOLID = -2; +float CONTENT_WATER = -3; +float CONTENT_SLIME = -4; +float CONTENT_LAVA = -5; +float CONTENT_SKY = -6; + +float STATE_TOP = 0; +float STATE_BOTTOM = 1; +float STATE_UP = 2; +float STATE_DOWN = 3; + +vector VEC_ORIGIN = '0 0 0'; +vector VEC_HULL_MIN = '-16 -16 -24'; +vector VEC_HULL_MAX = '16 16 32'; + +vector VEC_HULL2_MIN = '-32 -32 -24'; +vector VEC_HULL2_MAX = '32 32 64'; + +// protocol bytes +float SVC_UPDATESTAT = 3; // pgm black magic + +float SVC_TEMPENTITY = 23; +float SVC_KILLEDMONSTER = 27; +float SVC_FOUNDSECRET = 28; +float SVC_INTERMISSION = 30; +float SVC_FINALE = 31; +float SVC_CDTRACK = 32; +float SVC_SELLSCREEN = 33; +float SVC_CUTSCENE = 34; + +float SVC_ACHIEVEMENT = 52; + +float STAT_TOTALMONSTERS = 12; // pgm black magic + +float TE_SPIKE = 0; +float TE_SUPERSPIKE = 1; +float TE_GUNSHOT = 2; +float TE_EXPLOSION = 3; +float TE_TAREXPLOSION = 4; +float TE_LIGHTNING1 = 5; +float TE_LIGHTNING2 = 6; +float TE_WIZSPIKE = 7; +float TE_KNIGHTSPIKE = 8; +float TE_LIGHTNING3 = 9; +float TE_LAVASPLASH = 10; +float TE_TELEPORT = 11; +float TE_EXPLOSION2 = 12; +float TE_BEAM = 13; + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +float CHAN_AUTO = 0; +float CHAN_WEAPON = 1; +float CHAN_VOICE = 2; +float CHAN_ITEM = 3; +float CHAN_BODY = 4; + +float ATTN_NONE = 0; +float ATTN_NORM = 1; +float ATTN_IDLE = 2; +float ATTN_STATIC = 3; + +// update types + +float UPDATE_GENERAL = 0; +float UPDATE_STATIC = 1; +float UPDATE_BINARY = 2; +float UPDATE_TEMP = 3; + +// entity effects + +float EF_BRIGHTFIELD = 1; +float EF_MUZZLEFLASH = 2; +float EF_BRIGHTLIGHT = 4; +float EF_DIMLIGHT = 8; +float EF_QUADLIGHT = 16; +float EF_PENTALIGHT = 32; +float EF_CANDLELIGHT = 64; + + +// messages +float MSG_BROADCAST = 0; // unreliable to all +float MSG_ONE = 1; // reliable to one (msg_entity) +float MSG_ALL = 2; // reliable to all +float MSG_INIT = 3; // write to the init string + +// team numbers for Coop +float TEAM_MONSTERS = 0; +float TEAM_HUMANS = 1; + +// mal: basic colors - based on the Quake color palette. +float COLOR_RED = 251; +float COLOR_GREEN = 184; +float COLOR_BLUE = 208; +float COLOR_YELLOW = 192; +float COLOR_WHITE = 254; +float COLOR_BLACK = 0; +float COLOR_CYAN = 244; +float COLOR_ORANGE = 95; + +float DRAW_AI_SINGLE_FRAME = .1; // mal: use for AI that think every 10th of a second +float DRAW_GAME_SINGLE_FRAME = -1; // mal: use in game code that updates every frame. + +// player expanded settings flags +float PEF_CHANGEONLYNEW = 1; +float PEF_CHANGENEVER = 2; + +enum class BotGoalReturnCode : int { + Error = 0, + Success, + InProgress +}; + +//================================================ + +// +// globals +// +float movedist; +float gameover; // set when a rule exits + +string string_null; // null string, nothing should be held here +float empty_float; + +entity newmis; // launch_spike sets this after spawning it + +entity activator; // the entity that activated a trigger or brush + +entity damage_attacker; // set by T_Damage +float framecount; + +float skill; +float campaign_valid; +float campaign; + +float cheats_allowed; // mal: let's us run cheat commands in DM/Coop games. + +// rogue globals! +float earthquake_active; +float earthquake_intensity; +float cutscene_running; +entity tag_token_owner; + +//================================================ + +// +// world fields (FIXME: make globals) +// +.string wad; +.string map; +.float worldtype; // 0=medieval 1=metal 2=base + +//================================================ + +.string killtarget; + +// +// quakeed fields +// +.float light_lev; // not used by game, but parsed by light util +.float style; + + +// +// monster ai +// +.void() th_stand; +.void() th_walk; +.void() th_run; +.void() th_missile; +.void() th_melee; +.void(entity attacker, float damage) th_pain; +.void() th_die; + +.entity oldenemy; // mad at this player before taking damage + +.float speed; + +.float lefty; + +.float search_time; +.float attack_state; + +float AS_STRAIGHT = 1; +float AS_SLIDING = 2; +float AS_MELEE = 3; +float AS_MISSILE = 4; + +// +// player only fields +// +.float walkframe; + +.float attack_finished; +.float pain_finished; + +.float invincible_finished; +.float invisible_finished; +.float super_damage_finished; +.float radsuit_finished; + +.float invincible_time, invincible_sound; +.float invisible_time, invisible_sound; +.float super_time, super_sound; +.float rad_time; +.float fly_sound; + +.float shield_finished, antigrav_finished; +.float shield_time, antigrav_time; +.entity shield_entity; + +.float axhitme; + +.float show_hostile; // set to time+0.2 whenever a client fires a + // weapon or takes damage. Used to alert + // monsters that otherwise would let the player go +.float jump_flag; // player jump flag +.float swim_flag; // player swimming sound flag +.float air_finished; // when time > air_finished, start drowning +.float bubble_count; // keeps track of the number of bubbles +.string deathtype; // keeps track of how the player died + +// +// object stuff +// +.string mdl; +.vector mangle; // angle at start + +.vector oldorigin; // only used by secret door + +.float t_length, t_width; + + +// +// doors, etc +// +.vector dest, dest1, dest2; +.float wait; // time from firing to restarting +.float delay; // time from activation to firing +.entity trigger_field; // door's trigger entity +.string noise4; + +// +// monsters +// +.float pausetime; +.entity movetarget; + + +// +// doors +// +.float aflag; +.float dmg; // damage done by door when hit + +// +// misc +// +.float cnt; // misc flag + +// +// subs +// +.void() think1; +.vector finaldest, finalangle; + +// +// triggers +// +.float count; // for counting triggers + + +// +// plats / doors / buttons +// +.float lip; +.float state; +.vector pos1, pos2; // top and bottom positions +.float height; + +// +// sounds +// +.float waitmin, waitmax; +.float distance; +.float volume; + + +// +// elevator fields +// + +.float elevatorLastUse; +.float elevatorOnFloor; +.float elevatorToFloor; +.vector elevatorDestination; + +// +// plat2 fields +// +.float plat2Called; +.float plat2LastMove; +.float plat2GoTime; +.float plat2GoTo; + +// +// new AI fields +// +.float orbitPosition; + +// +// dragon specific fields +// +.float playerInRoom; +.float playerInTransit; +.float dragonInRoom; +.float dragonInTransit; +.float dragonAttacking; +.float dragonPainSequence; +.vector dragonLastVelocity; + +// +// Other Rogue Fields +// +.float AGping; +.float childrenSpawned; +.float ltrailLastUsed; +.float shield_death_time; +.float shieldSoundTime; +.float dragonDeathState; + +.float tag_frags; +.float tag_message_time; + +// +// new to ex +// +.float spawn_deferred; + +//=========================================================================== + + +// +// builtin functions +// + +void(vector ang) makevectors = #1; // sets v_forward, etc globals +void(entity e, vector o) setorigin = #2; +void(entity e, string m) setmodel = #3; // set movetype and solid first +void(entity e, vector min, vector max) setsize = #4; +// #5 was removed +void() break = #6; +float() random = #7; // returns 0 - 1 +void(entity e, float chan, string samp, float vol, float atten) sound = #8; +vector(vector v) normalize = #9; +void(string e) error = #10; +void(string e) objerror = #11; +float(vector v) vlen = #12; +float(vector v) vectoyaw = #13; +entity() spawn = #14; +void(entity e) remove = #15; + +// sets trace_* globals +// nomonsters can be: +// An entity will also be ignored for testing if forent == test, +// forent->owner == test, or test->owner == forent +// a forent of world is ignored +void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16; + +entity() checkclient = #17; // returns a client to look for +entity(entity start, .string fld, string match) find = #18; +string(string s) precache_sound = #19; +string(string s) precache_model = #20; +void(entity client, string s)stuffcmd = #21; +entity(vector org, float rad) findradius = #22; +void(string s, ...) bprint = #0:ex_bprint; +void(entity client, string s, ...) sprint = #0:ex_sprint; +void(string s) dprint = #25; +string(float f) ftos = #26; +string(vector v) vtos = #27; +void() coredump = #28; // prints all edicts +void() traceon = #29; // turns statment trace on +void() traceoff = #30; +void(entity e) eprint = #31; // prints an entire edict +float(float yaw, float dist) walkmove = #32; // returns TRUE or FALSE +// #33 was removed +float(float yaw, float dist) droptofloor= #34; // TRUE if landed on floor +void(float style, string value) lightstyle = #35; +float(float v) rint = #36; // round to nearest int +float(float v) floor = #37; // largest integer <= v +float(float v) ceil = #38; // smallest integer >= v +// #39 was removed +float(entity e) checkbottom = #40; // true if self is on ground +float(vector v) pointcontents = #41; // returns a CONTENT_* +// #42 was removed +float(float f) fabs = #43; +vector(entity e, float speed) aim = #44; // returns the shooting vector +float(string s) cvar = #45; // return cvar.value +void(string s) localcmd = #46; // put string into local que +entity(entity e) nextent = #47; // for looping through all ents +void(vector o, vector d, float color, float count) particle = #48;// start a particle effect +void() ChangeYaw = #49; // turn towards self.ideal_yaw + // at self.yaw_speed +// #50 was removed +vector(vector v) vectoangles = #51; + +// +// direct client message generation +// +void(float to, float f) WriteByte = #52; +void(float to, float f) WriteChar = #53; +void(float to, float f) WriteShort = #54; +void(float to, float f) WriteLong = #55; +void(float to, float f) WriteCoord = #56; +void(float to, float f) WriteAngle = #57; +void(float to, string s) WriteString = #58; +void(float to, entity s) WriteEntity = #59; + +// +// broadcast client message generation +// + +// void(float f) bWriteByte = #59; +// void(float f) bWriteChar = #60; +// void(float f) bWriteShort = #61; +// void(float f) bWriteLong = #62; +// void(float f) bWriteCoord = #63; +// void(float f) bWriteAngle = #64; +// void(string s) bWriteString = #65; +// void(entity e) bWriteEntity = #66; + +void(float step) movetogoal = #67; + +string(string s) precache_file = #68; // no effect except for -copy +void(entity e) makestatic = #69; +void(string s) changelevel = #70; + +//#71 was removed + +void(string var, string val) cvar_set = #72; // sets cvar.value + +void(entity client, string s, ...) centerprint = #0:ex_centerprint; // sprint, but in middle + +void(vector pos, string samp, float vol, float atten) ambientsound = #74; + +string(string s) precache_model2 = #75; // registered version only +string(string s) precache_sound2 = #76; // registered version only +string(string s) precache_file2 = #77; // registered version only + +void(entity e) setspawnparms = #78; // set parm1... to the + // values at level start + // for coop respawn + +float() finaleFinished = #0:ex_finaleFinished; + +// Plays a sound that only "client" can hear. Useful for announcers... +void localsound( entity client, string sample ) = #0:ex_localsound; + +// Debug rendering functions. +void draw_point( vector point, float colormap, float lifetime, float depthtest ) = #0:ex_draw_point; +void draw_line( vector start, vector end, float colormap, float lifetime, float depthtest ) = #0:ex_draw_line; +void draw_arrow( vector start, vector end, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_arrow; +void draw_ray( vector start, vector direction, float length, float colormap, float size, float lifetime, float depthtest ) = #0:ex_draw_ray; +void draw_circle( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_circle; +void draw_bounds( vector min, vector max, float colormap, float lifetime, float depthtest ) = #0:ex_draw_bounds; +void draw_worldtext( string s, vector origin, float size, float lifetime, float depthtest ) = #0:ex_draw_worldtext; +void draw_sphere( vector origin, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_sphere; +void draw_cylinder( vector origin, float halfHeight, float radius, float colormap, float lifetime, float depthtest ) = #0:ex_draw_cylinder; + +// Bot functions that return a value based on "BOT_GOAL_", depending on the status of the goal. +float bot_movetopoint( entity bot, vector point ) = #0:ex_bot_movetopoint; +float bot_followentity( entity bot, entity goal ) = #0:ex_bot_followentity; + +// Function for checking new player configured flags, such as weapon swap behaviour +float CheckPlayerEXFlags( entity playerEnt ) = #0:ex_CheckPlayerEXFlags; + +// Path monsters to the goal using any available nav nodes on the map +float walkpathtogoal( float movedist, vector goal ) = #0:ex_walkpathtogoal; + +float checkextension( string s ) = #99; + +//============================================================================ + +// +// subs.qc +// +void(vector tdest, float tspeed, void() func) SUB_CalcMove; +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt; +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove; +void() SUB_CalcMoveDone; +void() SUB_CalcAngleMoveDone; +void() SUB_Null; +void() SUB_UseTargets; +void() SUB_Remove; + +// +// combat.qc +// +void(entity targ, entity inflictor, entity attacker, float damage) T_Damage; + + +float (entity e, float healamount, float ignore) T_Heal; // health function + +float(entity targ, entity inflictor) CanDamage; + + +// +// new_items.qc +// +void(entity theEntity) UpdateAmmoCounts; + +// +// dmatch.qc +// +void() tag_token_respawn; +void() tag_token_touch; +void() tag_token_drop; + +// sphere.qc +void(entity ownerEntity) sphere_remove; + diff --git a/quakec_rogue/demon.qc b/quakec_rogue/demon.qc new file mode 100644 index 0000000..5da63f8 --- /dev/null +++ b/quakec_rogue/demon.qc @@ -0,0 +1,382 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +$cd /raid/quake/id1/models/demon3 +$scale 0.8 +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + +$frame run1 run2 run3 run4 run5 run6 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 leap10 +$frame leap11 leap12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attacka1 attacka2 attacka3 attacka4 attacka5 attacka6 attacka7 attacka8 +$frame attacka9 attacka10 attacka11 attacka12 attacka13 attacka14 attacka15 + +//============================================================================ + +void() Demon_JumpTouch; + +void() demon1_stand1 =[ $stand1, demon1_stand2 ] {ai_stand();}; +void() demon1_stand2 =[ $stand2, demon1_stand3 ] {ai_stand();}; +void() demon1_stand3 =[ $stand3, demon1_stand4 ] {ai_stand();}; +void() demon1_stand4 =[ $stand4, demon1_stand5 ] {ai_stand();}; +void() demon1_stand5 =[ $stand5, demon1_stand6 ] {ai_stand();}; +void() demon1_stand6 =[ $stand6, demon1_stand7 ] {ai_stand();}; +void() demon1_stand7 =[ $stand7, demon1_stand8 ] {ai_stand();}; +void() demon1_stand8 =[ $stand8, demon1_stand9 ] {ai_stand();}; +void() demon1_stand9 =[ $stand9, demon1_stand10 ] {ai_stand();}; +void() demon1_stand10 =[ $stand10, demon1_stand11 ] {ai_stand();}; +void() demon1_stand11 =[ $stand11, demon1_stand12 ] {ai_stand();}; +void() demon1_stand12 =[ $stand12, demon1_stand13 ] {ai_stand();}; +void() demon1_stand13 =[ $stand13, demon1_stand1 ] {ai_stand();}; + +void() demon1_walk1 =[ $walk1, demon1_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); +ai_walk(8); +}; +void() demon1_walk2 =[ $walk2, demon1_walk3 ] {ai_walk(6);}; +void() demon1_walk3 =[ $walk3, demon1_walk4 ] {ai_walk(6);}; +void() demon1_walk4 =[ $walk4, demon1_walk5 ] {ai_walk(7);}; +void() demon1_walk5 =[ $walk5, demon1_walk6 ] {ai_walk(4);}; +void() demon1_walk6 =[ $walk6, demon1_walk7 ] {ai_walk(6);}; +void() demon1_walk7 =[ $walk7, demon1_walk8 ] {ai_walk(10);}; +void() demon1_walk8 =[ $walk8, demon1_walk1 ] {ai_walk(10);}; + +void() demon1_run1 =[ $run1, demon1_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "demon/idle1.wav", 1, ATTN_IDLE); +ai_run(20);}; +void() demon1_run2 =[ $run2, demon1_run3 ] {ai_run(15);}; +void() demon1_run3 =[ $run3, demon1_run4 ] {ai_run(36);}; +void() demon1_run4 =[ $run4, demon1_run5 ] {ai_run(20);}; +void() demon1_run5 =[ $run5, demon1_run6 ] {ai_run(15);}; +void() demon1_run6 =[ $run6, demon1_run1 ] {ai_run(36);}; + +void() demon1_jump1 =[ $leap1, demon1_jump2 ] {ai_face();}; +void() demon1_jump2 =[ $leap2, demon1_jump3 ] {ai_face();}; +void() demon1_jump3 =[ $leap3, demon1_jump4 ] {ai_face();}; +void() demon1_jump4 =[ $leap4, demon1_jump5 ] +{ + ai_face(); + + self.touch = Demon_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 250'; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; +void() demon1_jump5 =[ $leap5, demon1_jump6 ] {}; +void() demon1_jump6 =[ $leap6, demon1_jump7 ] {}; +void() demon1_jump7 =[ $leap7, demon1_jump8 ] {}; +void() demon1_jump8 =[ $leap8, demon1_jump9 ] {}; +void() demon1_jump9 =[ $leap9, demon1_jump10 ] {}; +void() demon1_jump10 =[ $leap10, demon1_jump1 ] { +self.nextthink = time + 3; +// if three seconds pass, assume demon is stuck and jump again +}; + +void() demon1_jump11 =[ $leap11, demon1_jump12 ] {}; +void() demon1_jump12 =[ $leap12, demon1_run1 ] {}; + + +void() demon1_atta1 =[ $attacka1, demon1_atta2 ] {ai_charge(4);}; +void() demon1_atta2 =[ $attacka2, demon1_atta3 ] {ai_charge(0);}; +void() demon1_atta3 =[ $attacka3, demon1_atta4 ] {ai_charge(0);}; +void() demon1_atta4 =[ $attacka4, demon1_atta5 ] {ai_charge(1);}; +void() demon1_atta5 =[ $attacka5, demon1_atta6 ] {ai_charge(2); Demon_Melee(200);}; +void() demon1_atta6 =[ $attacka6, demon1_atta7 ] {ai_charge(1);}; +void() demon1_atta7 =[ $attacka7, demon1_atta8 ] {ai_charge(6);}; +void() demon1_atta8 =[ $attacka8, demon1_atta9 ] {ai_charge(8);}; +void() demon1_atta9 =[ $attacka9, demon1_atta10] {ai_charge(4);}; +void() demon1_atta10 =[ $attacka10, demon1_atta11] {ai_charge(2);}; +void() demon1_atta11 =[ $attacka11, demon1_atta12] {Demon_Melee(-200);}; +void() demon1_atta12 =[ $attacka12, demon1_atta13] {ai_charge(5);}; +void() demon1_atta13 =[ $attacka13, demon1_atta14] {ai_charge(8);}; +void() demon1_atta14 =[ $attacka14, demon1_atta15] {ai_charge(4);}; +void() demon1_atta15 =[ $attacka15, demon1_run1] {ai_charge(4);}; + +void() demon1_pain1 =[ $pain1, demon1_pain2 ] {}; +void() demon1_pain2 =[ $pain2, demon1_pain3 ] {}; +void() demon1_pain3 =[ $pain3, demon1_pain4 ] {}; +void() demon1_pain4 =[ $pain4, demon1_pain5 ] {}; +void() demon1_pain5 =[ $pain5, demon1_pain6 ] {}; +void() demon1_pain6 =[ $pain6, demon1_run1 ] {}; + +void(entity attacker, float damage) demon1_pain = +{ + if (self.touch == Demon_JumpTouch) + return; + + if (self.pain_finished > time) + return; + + self.pain_finished = time + 1; + sound (self, CHAN_VOICE, "demon/dpain1.wav", 1, ATTN_NORM); + + if (random()*200 > damage) + return; // didn't flinch + + demon1_pain1 (); +}; + +void() demon1_die1 =[ $death1, demon1_die2 ] { +sound (self, CHAN_VOICE, "demon/ddeath.wav", 1, ATTN_NORM);}; +void() demon1_die2 =[ $death2, demon1_die3 ] {}; +void() demon1_die3 =[ $death3, demon1_die4 ] {}; +void() demon1_die4 =[ $death4, demon1_die5 ] {}; +void() demon1_die5 =[ $death5, demon1_die6 ] {}; +void() demon1_die6 =[ $death6, demon1_die7 ] +{self.solid = SOLID_NOT;}; +void() demon1_die7 =[ $death7, demon1_die8 ] {}; +void() demon1_die8 =[ $death8, demon1_die9 ] {}; +void() demon1_die9 =[ $death9, demon1_die9 ] {}; + +void() demon_die = +{ +// check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_demon.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + return; + } + +// regular death + demon1_die1 (); +}; + + +void() Demon_MeleeAttack = +{ + demon1_atta1 (); +}; + + +/*QUAKED monster_demon1 (1 0 0) (-32 -32 -24) (32 32 64) Ambush + +*/ +void() monster_demon1 = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/demon.mdl"); + precache_model ("progs/h_demon.mdl"); + + precache_sound ("demon/ddeath.wav"); + precache_sound ("demon/dhit2.wav"); + precache_sound ("demon/djump.wav"); + precache_sound ("demon/dpain1.wav"); + precache_sound ("demon/idle1.wav"); + precache_sound ("demon/sight2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/demon.mdl"); + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 300; + + self.th_stand = demon1_stand1; + self.th_walk = demon1_walk1; + self.th_run = demon1_run1; + self.th_die = demon_die; + self.th_melee = Demon_MeleeAttack; // one of two attacks + self.th_missile = demon1_jump1; // jump attack + self.th_pain = demon1_pain; + + walkmonster_start(); +}; + + +/* +============================================================================== + +DEMON + +============================================================================== +*/ + +/* +============== +CheckDemonMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDemonMelee = +{ + if (enemy_range == RANGE_MELEE) + { // FIXME: check canreach + self.attack_state = AS_MELEE; + return TRUE; + } + return FALSE; +}; + +/* +============== +CheckDemonJump + +============== +*/ +float() CheckDemonJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 100) + return FALSE; + + if (d > 200) + { + if (random() < 0.9) + return FALSE; + } + + return TRUE; +}; + +float() DemonCheckAttack = +{ + local vector vec; + +// if close enough for slashing, go for it + if (CheckDemonMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDemonJump ()) + { + self.attack_state = AS_MISSILE; + sound (self, CHAN_VOICE, "demon/djump.wav", 1, ATTN_NORM); + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +void(float side) Demon_Melee = +{ + local float ldmg; + local vector delta; + + ai_face (); + walkmove (self.ideal_yaw, 12); // allow a little closing + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + sound (self, CHAN_WEAPON, "demon/dhit2.wav", 1, ATTN_NORM); + ldmg = 10 + 5*random(); + T_Damage (self.enemy, self, self, ldmg); + + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); +}; + + +void() Demon_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 40 + 10*random(); + T_Damage (other, self, self, ldmg); + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = demon1_jump1; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = demon1_jump11; + self.nextthink = time + 0.1; +}; + diff --git a/quakec_rogue/dmatch.qc b/quakec_rogue/dmatch.qc new file mode 100644 index 0000000..eb9b575 --- /dev/null +++ b/quakec_rogue/dmatch.qc @@ -0,0 +1,200 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// dmatch - general deathmatch additions + +// ======================================= +// ======================================= +void() tag_token_respawn = +{ + local entity spawnPoint; + local entity tagToken; + + tagToken = find (world, classname, "dmatch_tag_token"); + if(tagToken == world) + return; + + spawnPoint = SelectSpawnPoint(); + setorigin(tagToken, spawnPoint.origin); + tag_token_owner = world; + tagToken.solid = SOLID_TRIGGER; + tagToken.touch = tag_token_touch; + tagToken.think = SUB_Null; + tagToken.owner = world; + tagToken.tag_frags = 0; + droptofloor(); +}; + +// ======================================= +// ======================================= +void() tag_token_fall = +{ + self.tag_frags = 0; + self.think = tag_token_respawn; + self.nextthink = time + 30; + droptofloor(); +}; + +// ======================================= +// ======================================= +void() tag_token_drop = +{ + local entity tagToken; + + tagToken = find (world, classname, "dmatch_tag_token"); + if(tagToken == world) + return; + + bprint("$qc_lost_token", tagToken.owner.netname); + tagToken.tag_frags = 0; + tagToken.solid = SOLID_TRIGGER; + tagToken.owner = world; + tagToken.touch = tag_token_touch; + tagToken.think = tag_token_fall; + tagToken.nextthink = time + 0.1; +}; + +// ======================================= +// ======================================= +void() tag_token_think = +{ + if ( self.owner.health > 0) + { + if ( self.tag_message_time < time ) + { + bprint("$qc_has_token", self.owner.netname); + self.tag_message_time = time + 30; + } + + setorigin ( self, self.owner.origin + '0 0 48'); + self.think = tag_token_think; + self.nextthink = time + 0.1; + } + else + { + tag_token_drop(); + } +}; + +// ======================================= +// ======================================= +void() tag_token_touch = +{ + if (other.classname != "player") + return; + + tag_token_owner = other; + self.tag_frags = 0; + + sound ( self, CHAN_AUTO, "runes/end1.wav", 1, ATTN_NORM); + + bprint("$qc_got_token", other.netname); + self.tag_message_time = time + 30; + + self.owner = other; + self.solid = SOLID_NOT; + self.touch = SUB_Null; + self.think = tag_token_think; + self.nextthink = time + 0.1; +}; + +/*QUAKED dmatch_tag_token (1 1 0) (-16 -16 -16) (16 16 16) +The deathmatch tag token. +*/ +void() dmatch_tag_token = +{ + if ( cvar("teamplay") != TEAM_DMATCH_TAG ) + { + remove (self); + return; + } + + precache_model ("progs/sphere.mdl"); + precache_sound ("runes/end1.wav"); + setmodel(self, "progs/sphere.mdl"); + self.skin = 1; + setsize (self, '-16 -16 -16', '16 16 16'); + self.touch = tag_token_touch; + self.effects = self.effects | EF_DIMLIGHT; + StartItem(); +}; + +// ======================================= +// ======================================= +void(entity targ, entity attacker) dmatch_score = +{ + local entity tagToken; + + if ( teamplay == TEAM_DMATCH_TAG ) + { + tagToken = find (world, classname, "dmatch_tag_token"); + if(tagToken == world) + { + attacker.frags = attacker.frags + 1; + return; + } + + if ( attacker == tag_token_owner) + { + tagToken.tag_frags = tagToken.tag_frags + 1; + attacker.frags = attacker.frags + 3; + if ( tagToken.tag_frags == 5 ) + { + sprint (attacker, "$qc_got_quad"); + attacker.items = attacker.items | IT_QUAD; + stuffcmd (attacker, "bf\n"); + sound (attacker,CHAN_VOICE,"items/damage.wav", 1, ATTN_NORM); + attacker.super_time = 1; + attacker.super_damage_finished = time + 30; + } + else if (tagToken.tag_frags == 10) + { + bprint("$qc_lost_token", attacker.netname); + tagToken.solid = SOLID_TRIGGER; + tagToken.tag_frags = 0; + tagToken.touch = tag_token_touch; + tag_token_respawn(); + } + } + else + { + if (targ == tag_token_owner) + { + attacker.frags = attacker.frags + 5; + sound ( self, CHAN_AUTO, "runes/end1.wav", 1, ATTN_NORM); + if (attacker.classname == "player") + { + tag_token_owner = attacker; + + tagToken.tag_frags = 0; + tagToken.tag_message_time = time + 0.5; + tagToken.owner = attacker; + tagToken.solid = SOLID_NOT; + tagToken.touch = SUB_Null; + tagToken.think = tag_token_think; + tagToken.nextthink = time + 0.1; + } + } + else + attacker.frags = attacker.frags + 1; + } + } +}; + + diff --git a/quakec_rogue/dog.qc b/quakec_rogue/dog.qc new file mode 100644 index 0000000..333f6a3 --- /dev/null +++ b/quakec_rogue/dog.qc @@ -0,0 +1,382 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +DOG + +============================================================================== +*/ +$cd /raid/quake/id1/models/dog +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 + +$frame leap1 leap2 leap3 leap4 leap5 leap6 leap7 leap8 leap9 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 + + +void() dog_leap1; +void() dog_run1; + +/* +================ +dog_bite + +================ +*/ +void() dog_bite = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + + ai_charge(10); + + if (!CanDamage (self.enemy, self)) + return; + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 8; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() Dog_JumpTouch = +{ + local float ldmg; + + if (self.health <= 0) + return; + + if (other.takedamage) + { + if ( vlen(self.velocity) > 300 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + } + } + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = dog_leap1; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = dog_run1; + self.nextthink = time + 0.1; +}; + + +void() dog_stand1 =[ $stand1, dog_stand2 ] {ai_stand();}; +void() dog_stand2 =[ $stand2, dog_stand3 ] {ai_stand();}; +void() dog_stand3 =[ $stand3, dog_stand4 ] {ai_stand();}; +void() dog_stand4 =[ $stand4, dog_stand5 ] {ai_stand();}; +void() dog_stand5 =[ $stand5, dog_stand6 ] {ai_stand();}; +void() dog_stand6 =[ $stand6, dog_stand7 ] {ai_stand();}; +void() dog_stand7 =[ $stand7, dog_stand8 ] {ai_stand();}; +void() dog_stand8 =[ $stand8, dog_stand9 ] {ai_stand();}; +void() dog_stand9 =[ $stand9, dog_stand1 ] {ai_stand();}; + +void() dog_walk1 =[ $walk1 , dog_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); +ai_walk(8);}; +void() dog_walk2 =[ $walk2 , dog_walk3 ] {ai_walk(8);}; +void() dog_walk3 =[ $walk3 , dog_walk4 ] {ai_walk(8);}; +void() dog_walk4 =[ $walk4 , dog_walk5 ] {ai_walk(8);}; +void() dog_walk5 =[ $walk5 , dog_walk6 ] {ai_walk(8);}; +void() dog_walk6 =[ $walk6 , dog_walk7 ] {ai_walk(8);}; +void() dog_walk7 =[ $walk7 , dog_walk8 ] {ai_walk(8);}; +void() dog_walk8 =[ $walk8 , dog_walk1 ] {ai_walk(8);}; + +void() dog_run1 =[ $run1 , dog_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "dog/idle.wav", 1, ATTN_IDLE); +ai_run(16);}; +void() dog_run2 =[ $run2 , dog_run3 ] {ai_run(32);}; +void() dog_run3 =[ $run3 , dog_run4 ] {ai_run(32);}; +void() dog_run4 =[ $run4 , dog_run5 ] {ai_run(20);}; +void() dog_run5 =[ $run5 , dog_run6 ] {ai_run(64);}; +void() dog_run6 =[ $run6 , dog_run7 ] {ai_run(32);}; +void() dog_run7 =[ $run7 , dog_run8 ] {ai_run(16);}; +void() dog_run8 =[ $run8 , dog_run9 ] {ai_run(32);}; +void() dog_run9 =[ $run9 , dog_run10 ] {ai_run(32);}; +void() dog_run10 =[ $run10 , dog_run11 ] {ai_run(20);}; +void() dog_run11 =[ $run11 , dog_run12 ] {ai_run(64);}; +void() dog_run12 =[ $run12 , dog_run1 ] {ai_run(32);}; + +void() dog_atta1 =[ $attack1, dog_atta2 ] {ai_charge(10);}; +void() dog_atta2 =[ $attack2, dog_atta3 ] {ai_charge(10);}; +void() dog_atta3 =[ $attack3, dog_atta4 ] {ai_charge(10);}; +void() dog_atta4 =[ $attack4, dog_atta5 ] { +sound (self, CHAN_VOICE, "dog/dattack1.wav", 1, ATTN_NORM); +dog_bite();}; +void() dog_atta5 =[ $attack5, dog_atta6 ] {ai_charge(10);}; +void() dog_atta6 =[ $attack6, dog_atta7 ] {ai_charge(10);}; +void() dog_atta7 =[ $attack7, dog_atta8 ] {ai_charge(10);}; +void() dog_atta8 =[ $attack8, dog_run1 ] {ai_charge(10);}; + +void() dog_leap1 =[ $leap1, dog_leap2 ] {ai_face();}; +void() dog_leap2 =[ $leap2, dog_leap3 ] +{ + ai_face(); + + self.touch = Dog_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 300 + '0 0 200'; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; +}; + +void() dog_leap3 =[ $leap3, dog_leap4 ] {}; +void() dog_leap4 =[ $leap4, dog_leap5 ] {}; +void() dog_leap5 =[ $leap5, dog_leap6 ] {}; +void() dog_leap6 =[ $leap6, dog_leap7 ] {}; +void() dog_leap7 =[ $leap7, dog_leap8 ] {}; +void() dog_leap8 =[ $leap8, dog_leap9 ] {}; +void() dog_leap9 =[ $leap9, dog_leap9 ] {}; + +void() dog_pain1 =[ $pain1 , dog_pain2 ] {}; +void() dog_pain2 =[ $pain2 , dog_pain3 ] {}; +void() dog_pain3 =[ $pain3 , dog_pain4 ] {}; +void() dog_pain4 =[ $pain4 , dog_pain5 ] {}; +void() dog_pain5 =[ $pain5 , dog_pain6 ] {}; +void() dog_pain6 =[ $pain6 , dog_run1 ] {}; + +void() dog_painb1 =[ $painb1 , dog_painb2 ] {}; +void() dog_painb2 =[ $painb2 , dog_painb3 ] {}; +void() dog_painb3 =[ $painb3 , dog_painb4 ] {ai_pain(4);}; +void() dog_painb4 =[ $painb4 , dog_painb5 ] {ai_pain(12);}; +void() dog_painb5 =[ $painb5 , dog_painb6 ] {ai_pain(12);}; +void() dog_painb6 =[ $painb6 , dog_painb7 ] {ai_pain(2);}; +void() dog_painb7 =[ $painb7 , dog_painb8 ] {}; +void() dog_painb8 =[ $painb8 , dog_painb9 ] {ai_pain(4);}; +void() dog_painb9 =[ $painb9 , dog_painb10 ] {}; +void() dog_painb10 =[ $painb10 , dog_painb11 ] {ai_pain(10);}; +void() dog_painb11 =[ $painb11 , dog_painb12 ] {}; +void() dog_painb12 =[ $painb12 , dog_painb13 ] {}; +void() dog_painb13 =[ $painb13 , dog_painb14 ] {}; +void() dog_painb14 =[ $painb14 , dog_painb15 ] {}; +void() dog_painb15 =[ $painb15 , dog_painb16 ] {}; +void() dog_painb16 =[ $painb16 , dog_run1 ] {}; + +void() dog_pain = +{ + sound (self, CHAN_VOICE, "dog/dpain1.wav", 1, ATTN_NORM); + + if (random() > 0.5) + dog_pain1 (); + else + dog_painb1 (); +}; + +void() dog_die1 =[ $death1, dog_die2 ] {}; +void() dog_die2 =[ $death2, dog_die3 ] {}; +void() dog_die3 =[ $death3, dog_die4 ] {}; +void() dog_die4 =[ $death4, dog_die5 ] {}; +void() dog_die5 =[ $death5, dog_die6 ] {}; +void() dog_die6 =[ $death6, dog_die7 ] {}; +void() dog_die7 =[ $death7, dog_die8 ] {}; +void() dog_die8 =[ $death8, dog_die9 ] {}; +void() dog_die9 =[ $death9, dog_die9 ] {}; + +void() dog_dieb1 =[ $deathb1, dog_dieb2 ] {}; +void() dog_dieb2 =[ $deathb2, dog_dieb3 ] {}; +void() dog_dieb3 =[ $deathb3, dog_dieb4 ] {}; +void() dog_dieb4 =[ $deathb4, dog_dieb5 ] {}; +void() dog_dieb5 =[ $deathb5, dog_dieb6 ] {}; +void() dog_dieb6 =[ $deathb6, dog_dieb7 ] {}; +void() dog_dieb7 =[ $deathb7, dog_dieb8 ] {}; +void() dog_dieb8 =[ $deathb8, dog_dieb9 ] {}; +void() dog_dieb9 =[ $deathb9, dog_dieb9 ] {}; + + +void() dog_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowHead ("progs/h_dog.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "dog/ddeath.wav", 1, ATTN_NORM); + self.solid = SOLID_NOT; + + if (random() > 0.5) + dog_die1 (); + else + dog_dieb1 (); +}; + +//============================================================================ + +/* +============== +CheckDogMelee + +Returns TRUE if a melee attack would hit right now +============== +*/ +float() CheckDogMelee = +{ + if (enemy_range == RANGE_MELEE) + { // FIXME: check canreach + self.attack_state = AS_MELEE; + return TRUE; + } + return FALSE; +}; + +/* +============== +CheckDogJump + +============== +*/ +float() CheckDogJump = +{ + local vector dist; + local float d; + + if (self.origin_z + self.mins_z > self.enemy.origin_z + self.enemy.mins_z + + 0.75 * self.enemy.size_z) + return FALSE; + + if (self.origin_z + self.maxs_z < self.enemy.origin_z + self.enemy.mins_z + + 0.25 * self.enemy.size_z) + return FALSE; + + dist = self.enemy.origin - self.origin; + dist_z = 0; + + d = vlen(dist); + + if (d < 80) + return FALSE; + + if (d > 150) + return FALSE; + + return TRUE; +}; + +float() DogCheckAttack = +{ + local vector vec; + +// if close enough for slashing, go for it + if (CheckDogMelee ()) + { + self.attack_state = AS_MELEE; + return TRUE; + } + + if (CheckDogJump ()) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + return FALSE; +}; + + +//=========================================================================== + +/*QUAKED monster_dog (1 0 0) (-32 -32 -24) (32 32 40) Ambush + +*/ +void() monster_dog = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/h_dog.mdl"); + precache_model ("progs/dog.mdl"); + + precache_sound ("dog/dattack1.wav"); + precache_sound ("dog/ddeath.wav"); + precache_sound ("dog/dpain1.wav"); + precache_sound ("dog/dsight.wav"); + precache_sound ("dog/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/dog.mdl"); + + setsize (self, '-32 -32 -24', '32 32 40'); + self.health = 25; + + self.th_stand = dog_stand1; + self.th_walk = dog_walk1; + self.th_run = dog_run1; + self.th_pain = dog_pain; + self.th_die = dog_die; + self.th_melee = dog_atta1; + self.th_missile = dog_leap1; + + walkmonster_start(); +}; diff --git a/quakec_rogue/doors.qc b/quakec_rogue/doors.qc new file mode 100644 index 0000000..a57e2bf --- /dev/null +++ b/quakec_rogue/doors.qc @@ -0,0 +1,813 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// updated to quake 1.06 - 10/8/96 + +float DOOR_START_OPEN = 1; +float DOOR_DONT_LINK = 4; +float DOOR_GOLD_KEY = 8; +float DOOR_SILVER_KEY = 16; +float DOOR_TOGGLE = 32; + +/* + +Doors are similar to buttons, but can spawn a fat trigger field around them +to open without a touch, and they link together to form simultanious +double/quad doors. + +Door.owner is the master door. If there is only one door, it points to itself. +If multiple doors, all will point to a single one. + +Door.enemy chains from the master door through all doors linked in the chain. + +*/ + +/* +============================================================================= + +THINK FUNCTIONS + +============================================================================= +*/ + +void() door_go_down; +void() door_go_up; + +void() door_blocked = +{ + T_Damage (other, self, self, self.dmg); + +// 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.wait >= 0) + { + if (self.state == STATE_DOWN) + door_go_up (); + else + door_go_down (); + } +}; + + +void() door_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + if (self.spawnflags & DOOR_TOGGLE) + return; // don't come down automatically + self.think = door_go_down; + self.nextthink = self.ltime + self.wait; +}; + +void() door_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() door_go_down = +{ + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + if (self.max_health) + { + self.takedamage = DAMAGE_YES; + self.health = self.max_health; + } + + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, door_hit_bottom); +}; + +void() door_go_up = +{ + if (self.state == STATE_UP) + return; // allready going up + + if (self.state == STATE_TOP) + { // reset top wait time + self.nextthink = self.ltime + self.wait; + return; + } + + sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, door_hit_top); + + SUB_UseTargets(); +}; + + +/* +============================================================================= + +ACTIVATION FUNCTIONS + +============================================================================= +*/ + +void() door_fire = +{ + local entity oself; + local entity starte; + + if (self.owner != self) + objerror ("door_fire: self.owner != self"); + +// play use key sound + + if (self.items) + sound (self, CHAN_VOICE, self.noise4, 1, ATTN_NORM); + + self.message = string_null; // no more message + oself = self; + + if (self.spawnflags & DOOR_TOGGLE) + { + if (self.state == STATE_UP || self.state == STATE_TOP) + { + starte = self; + do + { + door_go_down (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + self = oself; + return; + } + } + +// trigger all paired doors + starte = self; + do + { + door_go_up (); + self = self.enemy; + } while ( (self != starte) && (self != world) ); + self = oself; +}; + + +void() door_use = +{ + local entity oself; + + self.message = ""; // door message are for touch only + self.owner.message = ""; + self.enemy.message = ""; + oself = self; + self = self.owner; + door_fire (); + self = oself; +}; + + +void() door_trigger_touch = +{ + if (other.health <= 0) + return; + + if (time < self.attack_finished) + return; + self.attack_finished = time + 1; + + activator = other; + + self = self.owner; + door_use (); +}; + + +void() door_killed = +{ + local entity oself; + + oself = self; + self = self.owner; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + door_use (); + self = oself; +}; + + +/* +================ +door_touch + +Prints messages and opens key doors +================ +*/ +void() door_touch = +{ + if (other.classname != "player") + return; + if (self.owner.attack_finished > time) + return; + + self.owner.attack_finished = time + 2; + + if (self.owner.message != "") + { + centerprint (other, self.owner.message); + sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + +// key door stuff + if (!self.items) + return; + +// FIXME: blink key on player's status bar + if ( (self.items & other.items) != self.items ) + { + if (self.owner.items == IT_KEY1) + { + if (world.worldtype == 2) + { + centerprint (other, "$qc_need_silver_keycard"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 1) + { + centerprint (other, "$qc_need_silver_runekey"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 0) + { + centerprint (other, "$qc_need_silver_key"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + } + else + { + if (world.worldtype == 2) + { + centerprint (other, "$qc_need_gold_keycard"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 1) + { + centerprint (other, "$qc_need_gold_runekey"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + else if (world.worldtype == 0) + { + centerprint (other, "$qc_need_gold_key"); + sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + } + } + return; + } + + other.items = other.items - self.items; + self.touch = SUB_Null; + if (self.enemy) + self.enemy.touch = SUB_Null; // get paired door + door_use (); +}; + +/* +============================================================================= + +SPAWNING FUNCTIONS + +============================================================================= +*/ + + +entity(vector fmins, vector fmaxs) spawn_field = +{ + local entity trigger; + local vector t1, t2; + + trigger = spawn(); + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.owner = self; + trigger.touch = door_trigger_touch; + + t1 = fmins; + t2 = fmaxs; + setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); + return (trigger); +}; + + +float (entity e1, entity e2) EntitiesTouching = +{ + if (e1.mins_x > e2.maxs_x) + return FALSE; + if (e1.mins_y > e2.maxs_y) + return FALSE; + if (e1.mins_z > e2.maxs_z) + return FALSE; + if (e1.maxs_x < e2.mins_x) + return FALSE; + if (e1.maxs_y < e2.mins_y) + return FALSE; + if (e1.maxs_z < e2.mins_z) + return FALSE; + return TRUE; +}; + + +/* +============= +LinkDoors + + +============= +*/ +void() LinkDoors = +{ + local entity t, starte; + local vector cmins, cmaxs; + + if (self.enemy) + return; // already linked by another door + if (self.spawnflags & 4) + { + self.owner = self.enemy = self; + return; // don't want to link this door + } + + cmins = self.mins; + cmaxs = self.maxs; + + starte = self; + t = self; + + do + { + self.owner = starte; // master door + + if (self.health) + starte.health = self.health; + if (self.targetname) + starte.targetname = self.targetname; + if (self.message != "") + starte.message = self.message; + + t = find (t, classname, self.classname); + if (!t) + { + self.enemy = starte; // make the chain a loop + + // shootable, fired, or key doors just needed the owner/enemy links, + // they don't spawn a field + + self = self.owner; + + if (self.health) + return; + if (self.targetname) + return; + if (self.items) + return; + + self.owner.trigger_field = spawn_field(cmins, cmaxs); + + return; + } + + if (EntitiesTouching(self,t)) + { + if (t.enemy) + objerror ("cross connected doors"); + + self.enemy = t; + self = t; + + if (t.mins_x < cmins_x) + cmins_x = t.mins_x; + if (t.mins_y < cmins_y) + cmins_y = t.mins_y; + if (t.mins_z < cmins_z) + cmins_z = t.mins_z; + if (t.maxs_x > cmaxs_x) + cmaxs_x = t.maxs_x; + if (t.maxs_y > cmaxs_y) + cmaxs_y = t.maxs_y; + if (t.maxs_z > cmaxs_z) + cmaxs_z = t.maxs_z; + } + } while (1 ); + +}; + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). + +Key doors are allways wait -1. + +"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" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +void() func_door = + +{ + +local vector tempVect; + + if (world.worldtype == 0) + { + precache_sound ("doors/medtry.wav"); + precache_sound ("doors/meduse.wav"); + self.noise3 = "doors/medtry.wav"; + self.noise4 = "doors/meduse.wav"; + } + else if (world.worldtype == 1) + { + precache_sound ("doors/runetry.wav"); + precache_sound ("doors/runeuse.wav"); + self.noise3 = "doors/runetry.wav"; + self.noise4 = "doors/runeuse.wav"; + } + else if (world.worldtype == 2) + { + precache_sound ("doors/basetry.wav"); + precache_sound ("doors/baseuse.wav"); + self.noise3 = "doors/basetry.wav"; + self.noise4 = "doors/baseuse.wav"; + } + else + { + dprint ("no worldtype set!\n"); + } + if (self.sounds == 0) + { + precache_sound ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = "misc/null.wav"; + self.noise2 = "misc/null.wav"; + } + if (self.sounds == 1) + { + precache_sound ("doors/drclos4.wav"); + precache_sound ("doors/doormv1.wav"); + self.noise1 = "doors/drclos4.wav"; + self.noise2 = "doors/doormv1.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/hydro1.wav"); + precache_sound ("doors/hydro2.wav"); + self.noise2 = "doors/hydro1.wav"; + self.noise1 = "doors/hydro2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/stndr1.wav"); + precache_sound ("doors/stndr2.wav"); + self.noise2 = "doors/stndr1.wav"; + self.noise1 = "doors/stndr2.wav"; + } + if (self.sounds == 4) + { + precache_sound ("doors/ddoor1.wav"); + precache_sound ("doors/ddoor2.wav"); + self.noise1 = "doors/ddoor2.wav"; + self.noise2 = "doors/ddoor1.wav"; + } + + + SetMovedir (); + + self.max_health = self.health; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + self.classname = "door"; + + self.blocked = door_blocked; + self.use = door_use; + + if (self.spawnflags & DOOR_SILVER_KEY) + self.items = IT_KEY1; + if (self.spawnflags & DOOR_GOLD_KEY) + self.items = IT_KEY2; + + if (!self.speed) + self.speed = 100; + if (!self.wait) + self.wait = 3; + if (!self.lip) + self.lip = 8; + if (!self.dmg) + self.dmg = 2; + + self.pos1=self.origin; +// self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip); + +// PGM fix for cameron's door problem - 02/19/97 + + tempVect = self.movedir; + if (tempVect_x < 0) + tempVect_x = 0 - tempVect_x; + if (tempVect_y < 0) + tempVect_y = 0 - tempVect_y; +// if (tempVect_z < 0) +// tempVect_z = 0 - tempVect_z; +// self.pos2 = self.pos1 + self.movedir*(tempVect*self.size - self.lip); + vector movedir_fabs = { fabs(self.movedir[0]), fabs(self.movedir[1]), fabs(self.movedir[2]) }; + self.pos2 = self.pos1 + ((movedir_fabs * self.size) - self.lip) * self.movedir; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if (self.spawnflags & DOOR_START_OPEN) + { + setorigin (self, self.pos2); + self.pos2 = self.pos1; + self.pos1 = self.origin; + } + + self.state = STATE_BOTTOM; + + if (self.health) + { + self.takedamage = DAMAGE_YES; + self.th_die = door_killed; + } + + if (self.items) + self.wait = -1; + + self.touch = door_touch; + +// LinkDoors can't be done until all of the doors have been spawned, so +// the sizes can be detected properly. + self.think = LinkDoors; + self.nextthink = self.ltime + 0.1; +}; + +/* +============================================================================= + +SECRET DOORS + +============================================================================= +*/ + +void() fd_secret_move1; +void() fd_secret_move2; +void() fd_secret_move3; +void() fd_secret_move4; +void() fd_secret_move5; +void() fd_secret_move6; +void() fd_secret_done; + +float SECRET_OPEN_ONCE = 1; // stays open +float SECRET_1ST_LEFT = 2; // 1st move is left of arrow +float SECRET_1ST_DOWN = 4; // 1st move is down from arrow +float SECRET_NO_SHOOT = 8; // only opened by trigger +float SECRET_YES_SHOOT = 16; // shootable even if targeted + + +void () fd_secret_use = +{ + local float temp; + + self.health = 10000; + + // exit if still moving around... + if (self.origin != self.oldorigin) + return; + + self.message = string_null; // no more message + + SUB_UseTargets(); // fire all targets / killtargets + + if (!(self.spawnflags & SECRET_NO_SHOOT)) + { + self.th_pain = SUB_Null; + self.takedamage = DAMAGE_NO; + } + self.velocity = '0 0 0'; + + // Make a sound, wait a little... + + sound(self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.nextthink = self.ltime + 0.1; + + temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1 + makevectors(self.mangle); + + if (!self.t_width) + { + if (self.spawnflags & SECRET_1ST_DOWN) + self. t_width = fabs(v_up * self.size); + else + self. t_width = fabs(v_right * self.size); + } + + if (!self.t_length) + self. t_length = fabs(v_forward * self.size); + + if (self.spawnflags & SECRET_1ST_DOWN) + self.dest1 = self.origin - v_up * self.t_width; + else + self.dest1 = self.origin + v_right * (self.t_width * temp); + + self.dest2 = self.dest1 + v_forward * self.t_length; + SUB_CalcMove(self.dest1, self.speed, fd_secret_move1); + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); +}; + +// Wait after first movement... +void () fd_secret_move1 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move2; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +// Start moving sideways w/sound... +void () fd_secret_move2 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest2, self.speed, fd_secret_move3); +}; + +// Wait here until time to go back... +void () fd_secret_move3 = +{ + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); + if (!(self.spawnflags & SECRET_OPEN_ONCE)) + { + self.nextthink = self.ltime + self.wait; + self.think = fd_secret_move4; + } +}; + +// Move backward... +void () fd_secret_move4 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.dest1, self.speed, fd_secret_move5); +}; + +// Wait 1 second... +void () fd_secret_move5 = +{ + self.nextthink = self.ltime + 1.0; + self.think = fd_secret_move6; + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () fd_secret_move6 = +{ + sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM); + SUB_CalcMove(self.oldorigin, self.speed, fd_secret_done); +}; + +void () fd_secret_done = +{ + if (!self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use; + } + sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM); +}; + +void () secret_blocked = +{ + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; + +/* +================ +secret_touch + +Prints messages +================ +*/ +void() secret_touch = +{ + if (other.classname != "player") + return; + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + + if (self.message) + { + centerprint (other, self.message); + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } +}; + + +/*QUAKED func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot +Basic secret door. Slides back, then to the side. Angle determines direction. +wait = # of seconds before coming back +1st_left = 1st move is left of arrow +1st_down = 1st move is down from arrow +always_shoot = even if targeted, keep shootable +t_width = override WIDTH to move back (or height if going down) +t_length = override LENGTH to move sideways +"dmg" damage to inflict when blocked (2 default) + +If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. +"sounds" +1) medieval +2) metal +3) base +*/ + +void () func_door_secret = +{ + if (self.sounds == 0) + self.sounds = 3; + if (self.sounds == 1) + { + precache_sound ("doors/latch2.wav"); + precache_sound ("doors/winch2.wav"); + precache_sound ("doors/drclos4.wav"); + self.noise1 = "doors/latch2.wav"; + self.noise2 = "doors/winch2.wav"; + self.noise3 = "doors/drclos4.wav"; + } + if (self.sounds == 2) + { + precache_sound ("doors/airdoor1.wav"); + precache_sound ("doors/airdoor2.wav"); + self.noise2 = "doors/airdoor1.wav"; + self.noise1 = "doors/airdoor2.wav"; + self.noise3 = "doors/airdoor2.wav"; + } + if (self.sounds == 3) + { + precache_sound ("doors/basesec1.wav"); + precache_sound ("doors/basesec2.wav"); + self.noise2 = "doors/basesec1.wav"; + self.noise1 = "doors/basesec2.wav"; + self.noise3 = "doors/basesec2.wav"; + } + + if (!self.dmg) + self.dmg = 2; + + // Magic formula... + self.mangle = self.angles; + self.angles = '0 0 0'; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.classname = "door"; + setmodel (self, self.model); + setorigin (self, self.origin); + + self.touch = secret_touch; + self.blocked = secret_blocked; + self.speed = 50; + self.use = fd_secret_use; + if ( !self.targetname || self.spawnflags&SECRET_YES_SHOOT) + { + self.health = 10000; + self.takedamage = DAMAGE_YES; + self.th_pain = fd_secret_use; + self.th_die = fd_secret_use; + } + self.oldorigin = self.origin; + if (!self.wait) + self.wait = 5; // 5 seconds before closing +}; diff --git a/quakec_rogue/dragon.qc b/quakec_rogue/dragon.qc new file mode 100644 index 0000000..afc71e6 --- /dev/null +++ b/quakec_rogue/dragon.qc @@ -0,0 +1,860 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// dragon.qc + +$base dragrest +$skin dragskin + +$frame dragon6 + +$frame drgfly01 drgfly02 drgfly03 drgfly04 +$frame drgfly05 drgfly06 drgfly07 drgfly08 +$frame drgfly09 drgfly10 drgfly11 drgfly12 drgfly13 + +$frame drgtrn01 drgtrn02 drgtrn03 drgtrn04 drgtrn05 drgtrn06 + +$frame drgslh01 drgslh02 drgslh03 drgslh04 drgslh05 drgslh06 +$frame drgslh07 drgslh08 drgslh09 drgslh10 drgslh11 drgslh12 drgslh13 + +$frame drgfir01 drgfir02 drgfir03 drgfir04 drgfir05 +$frame drgfir06 drgfir07 drgfir08 drgfir09 drgfir10 + +$frame drgfix1a drgfix1b drgfix1c +$frame drgfix2a drgfix2b drgfix2c +$frame drgfix3a drgfix3b drgfix3c +$frame drgfix4a drgfix4b drgfix4c +$frame drgfix5a drgfix5b drgfix5c +$frame drgfix6a drgfix6b drgfix6c drgfix6d + +$frame drgpan1a drgpan1b drgpan1c +$frame drgpan2a drgpan2b drgpan2c +$frame drgpan3a drgpan3b drgpan3c +$frame drgpan4a drgpan4b drgpan4c +$frame drgpan5a drgpan5b drgpan5c +$frame drgpan6a drgpan6b drgpan6c + +$frame drgdth01 drgdth02 drgdth03 drgdth04 drgdth05 drgdth06 +$frame drgdth07 drgdth08 drgdth09 drgdth10 drgdth11 drgdth12 +$frame drgdth13 drgdth14 drgdth15 drgdth16 drgdth17 drgdth18 +$frame drgdth19 drgdth20 drgdth21 + +void() dragon_fireball; +void() dragon_melee1; + +// =================================== +// global variables +// =================================== + +float DDIE_FALLING = 1; +float DDIE_STOPPED = 2; +float DDIE_EXPLODED = 3; + +float DT_ROOM_A = 1; +float DT_ROOM_B = 2; +float DT_ROOM_C = 4; + +float FIRE_PLASMA = 1; +float FIRE_FIREBALL = 2; + +// =================================== +// dragon_stop_attack +// =================================== +void() dragon_stop_attack = +{ + local entity tempDest; + + if (self.dragonAttacking == FALSE) + return; + +// skill ramp? + self.attack_finished = time + (random() * 2 ) + 4; + self.attack_finished = self.attack_finished - skill; + + self.dragonAttacking = FALSE; + traceline (self.origin, self.movetarget.origin, TRUE, world); + + if (trace_fraction == 1) + return; + else + bprint ("Error: Dragon cannot get to next target!\n"); +}; + +// =================================== +// dragon_check_attack +// =================================== +void() dragon_check_attack = +{ + local vector vec; + local float dot; + + if ( self.dragonAttacking == TRUE) + return; + if ( self.th_missile == SUB_Null) + return; + if ( self.attack_finished > time ) + return; + if ( self.enemy.health < 0) + self.enemy = world; + if ( self.enemy.flags & FL_NOTARGET ) + return; + + if (self.enemy == world) + FindTarget(); + else + { + makevectors (self.angles); + vec = normalize (self.enemy.origin - self.origin); + dot = vec * v_forward; +// if ( dot > 0.55 ) + if ( dot > 0.3 ) + { + traceline (self.origin, self.enemy.origin, TRUE, world); + if (trace_fraction == 1) + { + dot = vlen ( self.enemy.origin - self.origin ); + if (dot < 350) + { + self.dragonAttacking = TRUE; + self.think = dragon_melee1; + return; + } + else + { + self.dragonAttacking = TRUE; + self.think = self.th_missile; + } + } +// else +// bprint ("can't see player!\n"); + } + } +}; + +// =================================== +// dragon_move +// =================================== +void(float dist) dragon_move = +{ + local vector moveDir; + local vector moveAngles; + local float leftTurn, rightTurn, turnOffset; + local entity oldEnemy; + + if ( self.health < 1 ) + { + remove(self); + return; + } + + if (self.dragonAttacking == FALSE) // && self.dragonInTransit == FALSE) + dragon_check_attack(); + + oldEnemy = self.enemy; + if ( self.dragonAttacking == FALSE ) + { + moveDir = self.movetarget.origin - self.origin; + moveAngles = vectoangles ( moveDir ); + self.enemy = self.movetarget; + } + else + { + moveDir = self.enemy.origin - self.origin; + moveAngles = vectoangles ( moveDir ); + } + + turnOffset = self.angles_y - moveAngles_y; + if (turnOffset != 0) + { + turnOffset = 180 - self.angles_y; + leftTurn = anglemod ( moveAngles_y + turnOffset ) - 180; + rightTurn = 180 - anglemod ( moveAngles_y + turnOffset ); + + if (leftTurn < 0) + leftTurn = 360; + else if (rightTurn < 0) + rightTurn = 360; + + self.yaw_speed = 10; + if (rightTurn < 180) + { + if ( self.yaw_speed < rightTurn ) + self.angles_y = self.angles_y - self.yaw_speed; + else + self.angles_y = moveAngles_y; + + if ( rightTurn > 5 ) + { + self.angles_z = self.angles_z + 5; + if (self.angles_z > 30) + self.angles_z = 30; + } + turnOffset = rightTurn; + } + else + { + if ( self.yaw_speed < rightTurn ) + self.angles_y = self.angles_y + self.yaw_speed; + else + self.angles_y = moveAngles_y; + + if ( leftTurn > 5 ) + { + self.angles_z = self.angles_z - 5; + if (self.angles_z < -30) + self.angles_z = -30; + } + turnOffset = leftTurn; + } + } + else if (self.angles_z != 0) + { + if (self.angles_z < -5) + self.angles_z = self.angles_z + 5; + else if (self.angles_z < 5) + self.angles_z = 0; + else if (self.angles_z > 5) + self.angles_z = self.angles_z - 5; + } + + if ( moveDir_z > 5) + setorigin ( self, self.origin + '0 0 5'); + else if ( moveDir_z < -5) + setorigin ( self, self.origin - '0 0 5'); + + + moveAngles = self.origin; + walkmove ( self.angles_y, dist ); + + if (moveAngles == self.origin) + { +// bprint ( "Cannot move smoothly. movetogoal..\n"); + movetogoal ( dist ); + } + self.enemy = oldEnemy; +}; + +// =================================== +// movement +// =================================== +void() dragon_stand1 = [ $drgfly01, dragon_walk1 ] + { +// bprint ( "dragon_stand1: ERROR - got to stand frame!\n"); + }; + +// attack A: start - walk1 stop - walk 5 +// ================ +void() dragon_atk_a1=[$drgfix1a, dragon_atk_a2 ] + { dragon_move(17); }; +void() dragon_atk_a2=[$drgfix1b, dragon_atk_a3 ] + { dragon_move(17);dragon_fireball();}; +void() dragon_atk_a3=[$drgfix1c, dragon_walk5 ] + { dragon_move(17);dragon_stop_attack();}; + +// attack B: start - walk3 stop - walk7 +// ================ +void() dragon_atk_b1=[$drgfix2a, dragon_atk_b2 ] + { dragon_move(17); }; +void() dragon_atk_b2=[$drgfix2b, dragon_atk_b3 ] + { dragon_move(17);dragon_fireball();}; +void() dragon_atk_b3=[$drgfix2c, dragon_walk7 ] + { dragon_move(17);dragon_stop_attack();}; + +// attack C: start - walk5 stop - walk9 +// ================ +void() dragon_atk_c1=[$drgfix3a, dragon_atk_c2 ] + {dragon_move(17); }; +void() dragon_atk_c2=[$drgfix3b, dragon_atk_c3 ] + { dragon_move(17);dragon_fireball();}; +void() dragon_atk_c3=[$drgfix3c, dragon_walk9 ] + { dragon_move(17);dragon_stop_attack();}; + +// attack d: start - walk7 stop - walk11 +// ================ +void() dragon_atk_d1=[$drgfix4a, dragon_atk_d2 ] + { dragon_move(17); }; +void() dragon_atk_d2=[$drgfix4b, dragon_atk_d3 ] + { dragon_move(17);dragon_fireball();}; +void() dragon_atk_d3=[$drgfix4c, dragon_walk11 ] + { dragon_move(17);dragon_stop_attack();}; + +// attack E: start - walk9 stop - walk13 +// ================ +void() dragon_atk_e1=[$drgfix5a, dragon_atk_e2 ] + { dragon_move(17); }; +void() dragon_atk_e2=[$drgfix5b, dragon_atk_e3 ] + { dragon_move(17);dragon_fireball();}; +void() dragon_atk_e3=[$drgfix5c, dragon_walk13 ] + { dragon_move(17);dragon_stop_attack();}; + +// attack F: start - walk11 stop - walk3 +// ================ +void() dragon_atk_f1=[$drgfix6a, dragon_atk_f2 ] + {dragon_move(17); }; +void() dragon_atk_f2=[$drgfix6b, dragon_atk_f3 ] + { dragon_move(17);dragon_fireball();}; +void() dragon_atk_f3=[$drgfix6c, dragon_atk_f4 ] + { dragon_move(17); }; +void() dragon_atk_f4=[$drgfix6c, dragon_walk3 ] + { dragon_move(17);dragon_stop_attack();}; + +// =================================== +// dragon walk frames +// =================================== +void() dragon_walk1 =[ $drgfly01, dragon_walk2 ] + { + if (self.dragonAttacking) + dragon_stop_attack(); + + self.th_missile=dragon_atk_a1; + self.dragonPainSequence = 1; + dragon_move(17); + if (random() < 0.2) + sound (self, CHAN_VOICE, "dragon/active.wav", 0.6, ATTN_IDLE); + }; +void() dragon_walk2 =[ $drgfly02, dragon_walk3 ] + { self.th_missile=SUB_Null; dragon_move(17);}; +void() dragon_walk3 =[ $drgfly03, dragon_walk4 ] + { + self.th_missile=dragon_atk_b1; dragon_move(17); + self.dragonPainSequence=2; + }; +void() dragon_walk4 =[ $drgfly04, dragon_walk5 ] + { self.th_missile=SUB_Null; dragon_move(17);}; +void() dragon_walk5 =[ $drgfly05, dragon_walk6 ] + { + self.th_missile=dragon_atk_c1; dragon_move(17); + self.dragonPainSequence=3; + }; +void() dragon_walk6 =[ $drgfly06, dragon_walk7 ] + { self.th_missile=SUB_Null; dragon_move(17); }; +void() dragon_walk7 =[ $drgfly07, dragon_walk8 ] + { + self.th_missile=dragon_atk_d1; dragon_move(17); + self.dragonPainSequence=4; + }; +void() dragon_walk8 =[ $drgfly08, dragon_walk9 ] + { self.th_missile=SUB_Null; dragon_move(17); }; +void() dragon_walk9 =[ $drgfly09, dragon_walk10 ] + { + self.th_missile=dragon_atk_e1; dragon_move(17); + self.dragonPainSequence=5; + }; +void() dragon_walk10 =[ $drgfly10, dragon_walk11 ] + { self.th_missile=SUB_Null; dragon_move(17); }; +void() dragon_walk11 =[ $drgfly11, dragon_walk12 ] + { + self.th_missile=dragon_atk_f1; dragon_move(17); + self.dragonPainSequence=6; + }; +void() dragon_walk12 =[ $drgfly12, dragon_walk13 ] + { self.th_missile=SUB_Null; dragon_move(17); }; +void() dragon_walk13 =[ $drgfly13, dragon_walk1 ] + { self.th_missile=SUB_Null; dragon_move(17); self.dragonPainSequence=1;}; + +// =================================== +// attack +// =================================== + +void() FireballTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (self.owner.classname == "monster_dragon") + T_RadiusDamage (self, self.owner, 90, self.owner); + else + T_RadiusDamage (self, self.owner, 30, world); + + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 228); // 247 + WriteByte (MSG_BROADCAST, 5); + + remove(self); +// BecomeExplosion(); +}; + +void(vector org, vector dir) launch_fireball = +{ + local entity fireball; + local float fireSpeed; + + self.effects = self.effects | EF_MUZZLEFLASH; + fireball = spawn(); + fireball.owner = self; + fireball.movetype = MOVETYPE_FLYMISSILE; + fireball.solid = SOLID_BBOX; + fireball.classname = "fireball"; + setmodel (fireball, "progs/fireball.mdl"); + setorigin (fireball, org); + setsize (fireball, '0 0 0', '0 0 0'); + + fireSpeed = random() * 300 + 900; + fireball.velocity = dir * fireSpeed; + fireball.avelocity = '0 0 300'; + fireball.angles = vectoangles(fireball.velocity); + fireball.enemy = self.enemy; + + fireball.touch = FireballTouch; + fireball.nextthink = time + 6; + fireball.think = SUB_Remove; +}; + +// =========================================== +// dragon_fireball +// =========================================== +void() dragon_fireball = +{ + local float fireCount; + local vector org, dir; + local float distortion; + local float fireType; + + sound (self, CHAN_VOICE, "dragon/attack.wav", 1, ATTN_NORM); + + makevectors (self.angles); + org = self.origin + v_forward*112 + v_up*32; + + if (random() > 0.66) + { + fireCount = 1; + if ( skill > 1) + fireCount = 2; + fireType = FIRE_PLASMA; + } + else + { + fireType = FIRE_FIREBALL; + fireCount = random() * skill; // easy 0, med 0-1, hard 0-2 + fireCount = rint ( fireCount ) + 1; + // easy 1, med 1-2, hard 1-3 + } + + while (fireCount > 0) + { + distortion = ( random() - 0.5 ) * 0.25; + dir = normalize ( self.enemy.origin - org ); + makevectors (dir); + dir = dir + v_right * distortion; + + if (fireType == FIRE_FIREBALL) + launch_fireball(org, dir); + else + launch_plasma(org, dir); + + fireCount = fireCount - 1; + } + +}; + +// =================================== +// melee attacks +// =================================== +void() dragon_tail_touch = +{ + local vector delta; + local float tailDamage; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + ai_charge(10); + + delta = self.enemy.origin - self.origin; + tailDamage = vlen(delta); + if (vlen(delta) > 150) + { + return; + } + + tailDamage = ( random() * 30 ) + 30; + T_Damage ( self.enemy, self, self, tailDamage); +}; + +void() dragon_tail = +{ + local float dist; + local vector dir; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + dragon_move(10); + + dir = self.enemy.origin - self.origin; + dist = vlen ( dir ); + if ( dist < 250 ) + { + T_Damage ( self.enemy, self, self, 30 ); + dir = normalize (dir); + self.enemy.velocity = dir * 500; + self.enemy.velocity_z = 350; + } + + dragon_stop_attack(); +}; + +void() dragon_melee1=[$drgslh01, dragon_melee2 ] { dragon_move(12);}; +void() dragon_melee2=[$drgslh02, dragon_melee3 ] { dragon_move(12);}; +void() dragon_melee3=[$drgslh03, dragon_melee4 ] { dragon_move(12);}; +void() dragon_melee4=[$drgslh04, dragon_melee5 ] { dragon_move(12);}; +void() dragon_melee5=[$drgslh05, dragon_melee6 ] { dragon_move(12);}; +void() dragon_melee6=[$drgslh06, dragon_melee7 ] { dragon_move(12);}; +void() dragon_melee7=[$drgslh07, dragon_melee8 ] { dragon_move(12);}; +void() dragon_melee8=[$drgslh08, dragon_melee9 ] { dragon_move(12);}; +void() dragon_melee9=[$drgslh09, dragon_melee10] { dragon_move(12);}; +void() dragon_melee10=[$drgslh10, dragon_melee11] { dragon_tail();}; +void() dragon_melee11=[$drgslh11, dragon_melee12] { dragon_move(10);}; +void() dragon_melee12=[$drgslh12, dragon_melee13] { dragon_move(10);}; +void() dragon_melee13=[$drgslh13, dragon_walk1 ] +{ dragon_move(10);}; + +// =================================== +// pain +// =================================== +void() dragon_painA1 = [ $drgpan1a, dragon_painA2 ] {dragon_move(12);}; +void() dragon_painA2 = [ $drgpan1b, dragon_painA3 ] {dragon_move(12);}; +void() dragon_painA3 = [ $drgpan1c, dragon_walk5 ] {dragon_move(12);}; + +void() dragon_painB1 = [ $drgpan2a, dragon_painB2 ] {dragon_move(12);}; +void() dragon_painB2 = [ $drgpan2b, dragon_painB3 ] {dragon_move(12);}; +void() dragon_painB3 = [ $drgpan2c, dragon_walk7 ] {dragon_move(12);}; + +void() dragon_painC1 = [ $drgpan3a, dragon_painC2 ] {dragon_move(12);}; +void() dragon_painC2 = [ $drgpan3b, dragon_painC3 ] {dragon_move(12);}; +void() dragon_painC3 = [ $drgpan3c, dragon_walk9 ] {dragon_move(12);}; + +void() dragon_painD1 = [ $drgpan4a, dragon_painD2 ] {dragon_move(12);}; +void() dragon_painD2 = [ $drgpan4b, dragon_painD3 ] {dragon_move(12);}; +void() dragon_painD3 = [ $drgpan4c, dragon_walk11 ] {dragon_move(12);}; + +void() dragon_painE1 = [ $drgpan5a, dragon_painE2 ] {dragon_move(12);}; +void() dragon_painE2 = [ $drgpan5b, dragon_painE3 ] {dragon_move(12);}; +void() dragon_painE3 = [ $drgpan5c, dragon_walk13 ] {dragon_move(12);}; + +void() dragon_painF1 = [ $drgpan6a, dragon_painF2 ] {dragon_move(12);}; +void() dragon_painF2 = [ $drgpan6b, dragon_painF3 ] {dragon_move(12);}; +void() dragon_painF3 = [ $drgpan6c, dragon_walk2 ] {dragon_move(12);}; + +void() dragon_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (r < 0.25) + { + dragon_stop_attack(); + + sound (self, CHAN_VOICE, "dragon/pain.wav", 1, ATTN_NORM); + self.pain_finished = time + 2; + + if (self.dragonPainSequence == 1) + self.think = dragon_painA1; + else if (self.dragonPainSequence == 2) + self.think = dragon_painF1; + else if (self.dragonPainSequence == 3) + self.think = dragon_painE1; + else if (self.dragonPainSequence == 4) + self.think = dragon_painD1; + else if (self.dragonPainSequence == 5) + self.think = dragon_painC1; + else if (self.dragonPainSequence == 6) + self.think = dragon_painB1; + } +}; + +// =================================== +// death +// =================================== +void() dragon_boom2 = +{ + self.velocity = self.dragonLastVelocity; + ViolentDeath(15); + self.target = "dragondoor"; + SUB_UseTargets(); + remove(self); +}; + +void() dragon_boom1 = +{ + if ( self.dragonDeathState > DDIE_STOPPED ) +// objerror ( "dragon_boom1"); + return; + + self.dragonDeathState = DDIE_EXPLODED; + ThrowGib ("progs/drggib01.mdl", -100); + ThrowGib ("progs/drggib02.mdl", -100); + ThrowGib ("progs/drggib03.mdl", -100); + sound (self, CHAN_BODY, "player/tornoff2.wav", 1, ATTN_NONE); + self.think = dragon_boom2; + self.nextthink = time + 0.1; +}; + +void() dragon_explode = +{ + if ( self.dragonDeathState > DDIE_FALLING ) +// objerror ( "dragon_explode"); + return; + + if (vlen(self.velocity) < 100 || self.flags & FL_INWATER) + { + self.dragonDeathState = DDIE_STOPPED; + dragon_boom1(); + } + else + { + makevectors ( self.angles ); + self.velocity = self.velocity - 40 * v_up; + self.dragonLastVelocity = self.velocity; + } +}; + +void() dragon_squish = +{ + if (other.classname == "player") + { + self.classname = "monster_dragon_dead"; + T_Damage( other, self, self, 200); + } + if (other == world) + { + self.velocity = '0 0 0'; + dragon_explode(); + } +}; + +void() dragon_death1 = [ $drgdth01, dragon_death2 ] + { + if ( self.dragonDeathState > 0) +// objerror ( "dragon_death1"); + return; + + self.dragonDeathState = DDIE_FALLING; + self.use = SUB_Null; + makevectors ( self.angles ); + self.velocity = 300 * v_forward - 40 * v_up; + self.flags = self.flags - (self.flags & FL_ONGROUND); + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + self.touch = dragon_squish; + + sound (self, CHAN_VOICE, "dragon/death.wav", 1, ATTN_NONE); + self.dragonAttacking = FALSE; + }; +void() dragon_death2 = [ $drgdth02, dragon_death3 ] {dragon_explode();}; +void() dragon_death3 = [ $drgdth03, dragon_death4 ] {dragon_explode();}; +void() dragon_death4 = [ $drgdth04, dragon_death5 ] {dragon_explode();}; +void() dragon_death5 = [ $drgdth05, dragon_death6 ] {dragon_explode();}; +void() dragon_death6 = [ $drgdth06, dragon_death7 ] {dragon_explode();}; +void() dragon_death7 = [ $drgdth07, dragon_death8 ] {dragon_explode();}; +void() dragon_death8 = [ $drgdth08, dragon_death9 ] {dragon_explode();}; +void() dragon_death9 = [ $drgdth09, dragon_death10 ] {dragon_explode();}; +void() dragon_death10 = [ $drgdth10, dragon_death11 ] {dragon_explode();}; +void() dragon_death11 = [ $drgdth11, dragon_death12 ] {dragon_explode();}; +void() dragon_death12 = [ $drgdth12, dragon_death13 ] {dragon_explode();}; +void() dragon_death13 = [ $drgdth13, dragon_death14 ] {dragon_explode();}; +void() dragon_death14 = [ $drgdth14, dragon_death15 ] {dragon_explode();}; +void() dragon_death15 = [ $drgdth15, dragon_death16 ] {dragon_explode();}; +void() dragon_death16 = [ $drgdth16, dragon_death17 ] {dragon_explode();}; +void() dragon_death17 = [ $drgdth17, dragon_death18 ] {dragon_explode();}; +void() dragon_death18 = [ $drgdth18, dragon_death19 ] {dragon_explode();}; +void() dragon_death19 = [ $drgdth19, dragon_death20 ] {dragon_explode();}; +void() dragon_death20 = [ $drgdth20, dragon_death21 ] {dragon_explode();}; +void() dragon_death21 = [ $drgdth21, dragon_death21 ] +{ + ViolentDeath(39); + self.target = "dragondoor"; + SUB_UseTargets(); + remove(self); +}; + +// =================================== +// Delayed Activation +// =================================== +void() dragon_use = +{ + if (self.health < 1) + { + self.use = SUB_Null; + return; + } + + self.th_stand = dragon_walk1; + self.th_walk = dragon_walk1; + self.think = dragon_walk1; + self.nextthink = time + 0.1; +}; + +// =================================== +// dragon activation code +// =================================== +void() dragon_activate = +{ + self.takedamage = DAMAGE_AIM; + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 10; + self.view_ofs = '0 0 25'; + + self.flags = self.flags | FL_FLY; + self.flags = self.flags | FL_MONSTER; + + if (!walkmove(0,0)) + { + dprint ("flymonster in wall!\n"); + dprint (self.classname); + dprint (" at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + if (self.target) + { + self.movetarget = find(world, targetname, self.target); + self.goalentity = self.movetarget; + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + } + + if (self.targetname) + self.use = dragon_use; + else + dragon_use(); + +}; + +/*QUAKED monster_dragon (1 0 0) (-32 -32 -24) (32 32 64) Ambush +*/ +void() monster_dragon = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/dragon.mdl"); + precache_model ("progs/fireball.mdl"); + precache_model ("progs/drggib01.mdl"); + precache_model ("progs/drggib02.mdl"); + precache_model ("progs/drggib03.mdl"); + + precache_sound ("dragon/see.wav"); + precache_sound ("dragon/active.wav"); + precache_sound ("dragon/attack.wav"); + precache_sound ("dragon/pain.wav"); + precache_sound ("dragon/death.wav"); + + self.dragonInRoom = DT_ROOM_A; + self.dragonInTransit = 0; + self.dragonAttacking = FALSE; + self.playerInRoom = DT_ROOM_A; + self.playerInTransit = 0; + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/dragon.mdl"); + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 3000 + (1000 * cvar("skill")); + + self.th_run = dragon_walk1; + self.th_pain = dragon_pain; + self.th_die = dragon_death1; + + self.dragonPainSequence=1; + + total_monsters = total_monsters + 1; + self.nextthink = self.nextthink + 0.1; + self.think = dragon_activate; +}; + +// ================================================= +// dragon trigger +// OBSOLETE!! +// ================================================= + +/*QUAKED trigger_dragon (.5 .5 .5) ? A B C +Trigger to let dragon know what room you're in. + +Set the A,B,C to which room this trigger is at. + +Point the trigger towards the room. If the player is going +the direction the trigger points, he is entering the room. +If not, he is leaving. +*/ +void() trigger_dragon = +{ + remove(self); +}; + + +// ================================================= +// dragon corners +// ================================================= +void() dragon_corner_touch = +{ + if (other.movetarget != self) + return; + + if (other.classname != "monster_dragon") + return; + + other.movetarget = find (world, targetname, self.target); + other.goalentity = other.movetarget; + other.target = self.target; + + if (!other.movetarget) + { + objerror ("dragon_corner: no target found"); + } +}; + +/*QUAKED dragon_corner (0.5 0.3 0) (-16 -16 -16) (16 16 16) +Only for use with monster_dragon. Use only as transitions +between rooms. +*/ +void() dragon_corner = +{ + if (!self.targetname) + objerror ("dragon_corner: no targetname"); + + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_NONE; + self.touch = dragon_corner_touch; + self.modelindex = 0; + self.model = ""; + setsize (self, '-16 -16 -16', '16 16 16'); +}; + diff --git a/quakec_rogue/earthq.qc b/quakec_rogue/earthq.qc new file mode 100644 index 0000000..62a54b6 --- /dev/null +++ b/quakec_rogue/earthq.qc @@ -0,0 +1,181 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// earthquake + +// ============================================================ +// Level-Wide Earthquakes +// ============================================================ +float EQ_RANDOM = 1; + +void() stop_earthquake; + +void() earthquake_rumble = +{ + if (self.attack_finished < time) + stop_earthquake(); + else + { + sound( self, CHAN_VOICE, "equake/rumble.wav", 1, ATTN_NONE ); + self.think = earthquake_rumble; + self.nextthink = time + 1; + } +}; + +void() start_earthquake = +{ + earthquake_active = 1; + if ( self.spawnflags & EQ_RANDOM ) + self.attack_finished = time + random() * self.delay; + else + self.attack_finished = time + self.delay; + earthquake_rumble(); +}; + +void() stop_earthquake = +{ + earthquake_active = 0; + self.think = start_earthquake; + if ( self.spawnflags & EQ_RANDOM ) + self.nextthink = time + random() * self.wait; + else + self.nextthink = time + self.wait; +}; + +/*QUAKED earthquake (0 1 0) (-8 -8 -8) (8 8 8) Random +The Earthquake generator. + +delay - duration of the tremor (default 20) +wait - time between tremors (default 60) +weapon - richter scale of movement (default 40) + +RANDOM affects the times only. It will change the randomly between +0-n where n is the duration or time between. + +weapon - if you give a weapon value of 40, the X and Y displacement +can vary between -20 and +20, a range of 40. +*/ +void() earthquake = +{ + if (!self.delay) + self.delay = 20; + if (!self.wait) + self.wait = 60; + if (!self.weapon) + self.weapon = 40; + + precache_sound ("equake/rumble.wav"); + earthquake_active = 0; + earthquake_intensity = self.weapon * 0.5; + + setsize (self, '0 0 0', '0 0 0'); + self.think = stop_earthquake; + self.nextthink = time + 1; +}; + +// ============================================================ +// Earthquake trigger +// ============================================================ +void() earthquake_touch = +{ + if (self.delay) + { + if ( self.attack_finished < time ) + { + sound ( self, CHAN_VOICE, "equake/rumble.wav", 1, ATTN_NORM ); + self.attack_finished = time + 1; + } + + if ( other.classname == "player" ) + { + if ( other.flags & FL_ONGROUND ) + { + other.velocity_x = other.velocity_x + + (random() * self.weapon * 2) - + self.weapon; + other.velocity_y = other.velocity_y + + (random() * self.weapon * 2) - + self.weapon; + other.velocity_z = other.velocity_z + + (random() * self.weapon * 2) - + self.weapon; + } + } + } +}; + +void() earthquake_use = +{ + self.delay = !self.delay; +}; + +/*QUAKED trigger_earthquake (.5 .5 .5) ? +The Earthquake generator. + +Anytime a person is in an active field, they shake. If the trigger is a target, it will be OFF until triggered. It will then toggle between ON and OFF. + +weapon - richter scale of movement (default 40) + +weapon - if you give a weapon value of 40, the X and Y displacement +can vary between -20 and +20, a range of 40. +*/ +void() trigger_earthquake = +{ + precache_sound ("equake/rumble.wav"); + + if (!self.weapon) + self.weapon = 40; + + self.weapon = self.weapon * 0.5; + self.delay = 1; + self.touch = earthquake_touch; + + if (self.targetname) + { + self.use = earthquake_use; + self.delay = 0; + } + + InitTrigger(); +}; + +void() kill_earthquake = +{ + local entity eq; + + if ( other.classname != "player" ) + return; + + eq = find (world, classname, "earthquake"); + if (eq != world) + { + earthquake_active = 0; + remove (eq); + } +}; + +/*QUAKED trigger_earthquake_kill (.5 .5 .5) ? +Trigger to kill the level-wide earthquake. +*/ +void() trigger_earthquake_kill = +{ + self.touch = kill_earthquake; + + InitTrigger(); +}; \ No newline at end of file diff --git a/quakec_rogue/eel.qc b/quakec_rogue/eel.qc new file mode 100644 index 0000000..df24c5d --- /dev/null +++ b/quakec_rogue/eel.qc @@ -0,0 +1,242 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +$cd /qwork/xpack/models/eel +$origin 0 0 0 +$scale .12 +$base eelrest3 +$skin eelfin +$frame eelswim1 eelswim2 eelswim3 eelswim4 eelswim5 eelswim6 +$frame eeldth1 eeldth2 eeldth3 eeldth4 eeldth5 +$frame eeldth6 eeldth7 eeldth8 eeldth9 eeldth10 + +void() swimmonster_start; +void() eel_pain1; + +void() eel_pitch_change = +{ + if ( pointcontents ( self.origin ) != CONTENT_WATER ) + { + droptofloor(); + T_Damage ( self, world, world, 6); + return; + } + + if ( time < self.delay ) + return; + + if ( self.weapon > 10 ) + self.weapon = -10; + + if ( self.weapon < 0 ) + self.angles_x = self.angles_x - 1.5; + else if ( self.weapon > 0) + self.angles_x = self.angles_x + 1.5; + + self.weapon = self.weapon + 1; +}; + +void() eel_stand1 =[ $eelswim1, eel_stand2 ] +{ ai_stand(); eel_pitch_change();} ; +void() eel_stand2 =[ $eelswim2, eel_stand3 ] +{ ai_stand(); eel_pitch_change();}; +void() eel_stand3 =[ $eelswim3, eel_stand4 ] +{ ai_stand(); eel_pitch_change();}; +void() eel_stand4 =[ $eelswim4, eel_stand5 ] +{ ai_stand(); eel_pitch_change();}; +void() eel_stand5 =[ $eelswim5, eel_stand6 ] +{ ai_stand(); eel_pitch_change();}; +void() eel_stand6 =[ $eelswim6, eel_stand1 ] +{ ai_stand(); eel_pitch_change();}; + +void() eel_walk1 =[ $eelswim1, eel_walk2 ] +{ + if (random() < 0.2) + sound (self, CHAN_VOICE, "eel/eactive1.wav", 1, ATTN_IDLE); + ai_walk(6); + eel_pitch_change(); +}; +void() eel_walk2 =[ $eelswim2, eel_walk3 ] {ai_walk(6);eel_pitch_change();}; +void() eel_walk3 =[ $eelswim3, eel_walk4 ] {ai_walk(6);eel_pitch_change();}; +void() eel_walk4 =[ $eelswim4, eel_walk5 ] {ai_walk(6);eel_pitch_change();}; +void() eel_walk5 =[ $eelswim5, eel_walk6 ] {ai_walk(6);eel_pitch_change();}; +void() eel_walk6 =[ $eelswim6, eel_walk1 ] {ai_walk(6);eel_pitch_change();}; + +void() eel_run1 =[ $eelswim1, eel_run2 ] +{ + if (random() < 0.4) + sound (self, CHAN_VOICE, "eel/eactive1.wav", 1, ATTN_IDLE); + ai_run(10); + eel_pitch_change(); +}; +void() eel_run2 =[ $eelswim2, eel_run3 ] {ai_run(10);eel_pitch_change();}; +void() eel_run3 =[ $eelswim3, eel_run4 ] {ai_run(10);eel_pitch_change();}; +void() eel_run4 =[ $eelswim4, eel_run5 ] {ai_run(10);eel_pitch_change();}; +void() eel_run5 =[ $eelswim5, eel_run6 ] {ai_run(10);eel_pitch_change();}; +void() eel_run6 =[ $eelswim6, eel_run1 ] {ai_run(10);eel_pitch_change();}; + +void() eel_melee = +{ + local float attackOK; + + attackOK = 1; + + traceline (self.origin, self.enemy.origin, FALSE, self); + + if (trace_ent != self.enemy) + attackOK = FALSE; // don't have a clear shot + if (trace_inopen && trace_inwater) + attackOK = FALSE; // sight line crossed contents + + if (attackOK) + T_EELZap (self, self, 45); + self.skin=0; + self.effects=0; +}; + +void() eel_attack1 =[ $eelswim1, eel_attack2 ] + { ai_charge(8); eel_pitch_change();}; +void() eel_attack2 =[ $eelswim2, eel_attack3 ] + { ai_charge(8); eel_pitch_change();}; +void() eel_attack3 =[ $eelswim3, eel_attack4 ] + { ai_charge(8); eel_pitch_change();}; +void() eel_attack4 =[ $eelswim4, eel_attack5 ] + { ai_charge(8); eel_pitch_change();}; +void() eel_attack5 =[ $eelswim5, eel_attack6 ] + { ai_charge(8); eel_pitch_change();}; +void() eel_attack6 =[ $eelswim6, eel_attack7 ] + { ai_charge(8); eel_pitch_change();}; +void() eel_attack7 =[ $eelswim1, eel_attack8 ] + { ai_charge(8); eel_pitch_change();}; +void() eel_attack8 =[ $eelswim2, eel_attack9 ] +{ + self.effects = EF_DIMLIGHT; + self.skin=1; + ai_charge(8); + eel_pitch_change(); + sound (self, CHAN_WEAPON, "eel/eatt1.wav", 1, ATTN_NORM); +}; +void() eel_attack9 =[ $eelswim3, eel_attack10 ] + { self.skin=2;ai_charge(8);eel_pitch_change(); }; +void() eel_attack10=[ $eelswim4, eel_attack11 ] + { self.skin=3;ai_charge(8);eel_pitch_change(); }; +void() eel_attack11=[ $eelswim5, eel_attack12 ] + { self.effects = EF_BRIGHTLIGHT; self.skin=4;ai_charge(8);eel_pitch_change();}; +void() eel_attack12=[ $eelswim6, eel_run1 ] + { self.skin=5;eel_melee(); }; + +void() eel_death1 =[ $eeldth1, eel_death2 ] +{ + self.skin = 0; + self.effects = 0; + sound (self, CHAN_VOICE, "eel/edie3r.wav", 1, ATTN_NORM); +}; +void() eel_death2 =[ $eeldth2, eel_death3 ] {}; +void() eel_death3 =[ $eeldth3, eel_death4 ] {}; +void() eel_death4 =[ $eeldth4, eel_death5 ] {}; +void() eel_death5 =[ $eeldth3, eel_death6 ] {}; +void() eel_death6 =[ $eeldth2, eel_death7 ] {}; +void() eel_death7 =[ $eeldth1, eel_death8 ] {}; +void() eel_death8 =[ $eeldth2, eel_death9 ] {}; +void() eel_death9 =[ $eeldth3, eel_death10 ] {}; +void() eel_death10 =[ $eeldth4, eel_death11 ] {}; +void() eel_death11 =[ $eeldth5, eel_death12 ] { self.flags = self.flags - FL_SWIM; }; +void() eel_death12 =[ $eeldth6, eel_death13 ] {}; +void() eel_death13 =[ $eeldth7, eel_death14 ] {}; +void() eel_death14 =[ $eeldth8, eel_death15 ] {}; +void() eel_death15 =[ $eeldth9, eel_death16 ] {droptofloor();}; +void() eel_death16 =[ $eeldth10, eel_death16 ] {self.solid = SOLID_NOT;}; + +void() eel_death = +{ + self.flags = self.flags + FL_SWIM; + setsize (self, '0 0 0', '0 0 0'); + +// self.skin = 1; // change to bloody eel skin! + if (self.health < -12) + { + self.skin = 0; + self.effects = 0; + ThrowHead ("progs/eelgib.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + return; + } + else + { + eel_death1 (); + } +}; + +void() eel_pain1 =[ $eeldth1, eel_pain2 ] +{ + if (self.pain_finished > time) + return; + + self.pain_finished = time + 1; + sound (self, CHAN_VOICE, "eel/epain3.wav", 1, ATTN_NORM); + self.skin=0; +}; +void() eel_pain2 =[ $eeldth2, eel_pain3 ] {}; +void() eel_pain3 =[ $eeldth3, eel_pain4 ] {}; +void() eel_pain4 =[ $eeldth4, eel_pain5 ] {}; +void() eel_pain5 =[ $eeldth3, eel_pain6 ] {}; +void() eel_pain6 =[ $eeldth2, eel_pain7 ] {}; +void() eel_pain7 =[ $eeldth1, eel_run1 ] {}; + +/*QUAKED monster_eel (1 0 0) (-16 -16 -24) (16 16 32) Ambush +*/ +void() monster_eel = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/eel2.mdl"); + precache_model ("progs/eelgib.mdl"); + precache_model ("progs/gib1.mdl"); + + precache_sound ("eel/edie3r.wav"); + precache_sound ("eel/epain3.wav"); + precache_sound ("eel/eactive1.wav"); + precache_sound ("eel/eatt1.wav"); + precache_sound ("eel/eelc5.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/eel2.mdl"); + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + self.health = 60; + + self.th_stand = eel_stand1; + self.th_walk = eel_walk1; + self.th_run = eel_run1; + self.th_die = eel_death; + self.th_pain = eel_pain1; + self.th_melee = eel_attack1; + + self.delay = time + random() * 6; + self.weapon = 0; + + swimmonster_start (); +}; diff --git a/quakec_rogue/elevatr.qc b/quakec_rogue/elevatr.qc new file mode 100644 index 0000000..f314a1c --- /dev/null +++ b/quakec_rogue/elevatr.qc @@ -0,0 +1,175 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// elevator button +// pmack +// sept 96 + +float ELVTR_DOWN = 1; + +void() elvtr_button_wait; +void() elvtr_button_return; + +void() elvtr_button_wait = +{ + elvButnDir = 0; + if (self.spawnflags & ELVTR_DOWN) + elvButnDir = -1; + else + elvButnDir = 1; + + self.state = STATE_TOP; + self.nextthink = self.ltime + self.wait; + self.think = elvtr_button_return; + activator = self.enemy; + SUB_UseTargets(); + self.frame = 1; // use alternate textures +}; + +void() elvtr_button_done = +{ + self.state = STATE_BOTTOM; +}; + +void() elvtr_button_return = +{ + self.state = STATE_DOWN; + SUB_CalcMove (self.pos1, self.speed, elvtr_button_done); + self.frame = 0; // use normal textures + if (self.health) + self.takedamage = DAMAGE_YES; // can be shot again +}; + + +void() elvtr_button_blocked = +{ // do nothing, just don't ome all the way back out +}; + + +void() elvtr_button_fire = +{ + if (self.state == STATE_UP || self.state == STATE_TOP) + return; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + + self.state = STATE_UP; + SUB_CalcMove (self.pos2, self.speed, elvtr_button_wait); +}; + + +void() elvtr_button_use = +{ + self.enemy = activator; + elvtr_button_fire (); +}; + +void() elvtr_button_touch = +{ + if (other.classname != "player") + return; + self.enemy = other; + elvtr_button_fire (); +}; + +void() elvtr_button_killed = +{ + self.enemy = damage_attacker; + self.health = self.max_health; + self.takedamage = DAMAGE_NO; // wil be reset upon return + elvtr_button_fire (); +}; + + +/*QUAKED func_elvtr_button (0 .5 .8) ? ELVTR_DOWN +ELEVATOR BUTTON ONLY! + +ELVTR_DOWN causes this to be a DOWN button. +Default is UP. + +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" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +void() func_elvtr_button = +{ +local float gtemp, ftemp; + + if (self.sounds == 0) + { + precache_sound ("buttons/airbut1.wav"); + self.noise = "buttons/airbut1.wav"; + } + if (self.sounds == 1) + { + precache_sound ("buttons/switch21.wav"); + self.noise = "buttons/switch21.wav"; + } + if (self.sounds == 2) + { + precache_sound ("buttons/switch02.wav"); + self.noise = "buttons/switch02.wav"; + } + if (self.sounds == 3) + { + precache_sound ("buttons/switch04.wav"); + self.noise = "buttons/switch04.wav"; + } + + SetMovedir (); + + self.movetype = MOVETYPE_PUSH; + self.solid = SOLID_BSP; + setmodel (self, self.model); + + self.blocked = elvtr_button_blocked; + self.use = elvtr_button_use; + + if (self.health) + { + self.max_health = self.health; + self.th_die = elvtr_button_killed; + self.takedamage = DAMAGE_YES; + } + else + self.touch = elvtr_button_touch; + + if (!self.speed) + self.speed = 40; + if (!self.wait) + self.wait = 1; + if (!self.lip) + self.lip = 4; + + self.state = STATE_BOTTOM; + + self.pos1 = self.origin; + self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip); +}; + diff --git a/quakec_rogue/ending.qc b/quakec_rogue/ending.qc new file mode 100644 index 0000000..0864c3c --- /dev/null +++ b/quakec_rogue/ending.qc @@ -0,0 +1,416 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// ending.qc +// code to handle big ending of Xpack. +// ------------------------------------------------ +$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6 +$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6 +$frame stand1 stand2 stand3 stand4 stand5 +$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6 +$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12 +$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6 +$frame pain1 pain2 pain3 pain4 pain5 pain6 +$frame axdeth1 axdeth2 axdeth3 axdeth4 axdeth5 axdeth6 +$frame axdeth7 axdeth8 axdeth9 +$frame deatha1 deatha2 deatha3 deatha4 deatha5 deatha6 deatha7 deatha8 +$frame deatha9 deatha10 deatha11 +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 deathc12 deathc13 deathc14 deathc15 +$frame deathd1 deathd2 deathd3 deathd4 deathd5 deathd6 deathd7 +$frame deathd8 deathd9 +$frame deathe1 deathe2 deathe3 deathe4 deathe5 deathe6 deathe7 +$frame deathe8 deathe9 +$frame nailatt1 nailatt2 +$frame light1 light2 +$frame rockatt1 rockatt2 rockatt3 rockatt4 rockatt5 rockatt6 +$frame shotatt1 shotatt2 shotatt3 shotatt4 shotatt5 shotatt6 +$frame axatt1 axatt2 axatt3 axatt4 axatt5 axatt6 +$frame axattb1 axattb2 axattb3 axattb4 axattb5 axattb6 +$frame axattc1 axattc2 axattc3 axattc4 axattc5 axattc6 +$frame axattd1 axattd2 axattd3 axattd4 axattd5 axattd6 + +entity theActor; +entity theMachine; + +float actorStage; + +float STAGE_START = 0; +float STAGE_TO_POINT1 = 1; +float STAGE_AT_POINT1 = 2; +float STAGE_TO_POINT2 = 3; +float STAGE_AT_POINT2 = 4; +float STAGE_FIRING = 5; + +void(entity thePlayer) spawn_actor; +void() actor_control; +void(entity playerEnt) move_camera; +void() time_crash; +void() ending_remove_stuff; + +void() ending_for_coop = +{ + intermission_exittime = time + 3; + intermission_running = 1; + + self.model = ""; + self.yaw_speed = 20; + self.view_ofs = '0 0 0'; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + setorigin (self, self.origin + '0 0 48'); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_coop"); + + ending_remove_stuff(); + + theMachine.think = time_crash; + theMachine.nextthink = time + 0.1; +}; + +// ------------------------------------------------ +// Camera and control functions +// ------------------------------------------------ + +void() xpackEnding = +{ + local entity cameraview; + + if (coop) + { + ending_for_coop(); + return; + } + + intermission_exittime = time + 10000000; // never allow exit + intermission_running = 1; + + WriteByte (MSG_ALL, SVC_CUTSCENE); + WriteString (MSG_ALL, ""); + + cameraview = find ( world, targetname, "cameraview"); + if (cameraview == world) + { + ending_for_coop(); + return; + } + + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + spawn_actor(self); + move_camera(self); +}; + +void() track_camera = +{ + local vector cameraAngle; + + cameraAngle = theActor.origin - self.origin; + cameraAngle_z = 0 - cameraAngle_z; + self.angles = vectoangles ( cameraAngle ); + self.v_angle = self.angles; + + self.think = track_camera; + self.nextthink = time + 0.1; +}; + +void(entity playerEnt) move_camera = +{ + local entity cameraPoint; + local vector cameraAngle; + + cameraPoint = find ( world, targetname, "cameraview"); + if ( cameraPoint == world ) + objerror ("Could not find camerapoint!"); + + self.model = ""; + self.yaw_speed = 20; + self.view_ofs = '0 0 0'; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + setorigin (self, cameraPoint.origin); + + cameraAngle = theActor.origin - self.origin; + self.angles = vectoangles ( cameraAngle ); + self.v_angle = self.angles; + + self.think = track_camera; + self.nextthink = time + 0.05; +}; + +void() ending_remove_stuff = +{ + local entity curEnt; + local entity removeEnt; + + curEnt = find ( world, classname, "ltrail_start"); + while(curEnt) + { + remove(curEnt); + curEnt = find ( world, classname, "ltrail_start"); + } + + curEnt = find ( world, classname, "item_time_core"); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, curEnt.origin_x); + WriteCoord (MSG_BROADCAST, curEnt.origin_y); + WriteCoord (MSG_BROADCAST, curEnt.origin_z); + WriteByte (MSG_BROADCAST, 230); + WriteByte (MSG_BROADCAST, 5); + + curEnt.think = SUB_Remove; + curEnt.nextthink = time + 0.1; +}; + +// ------------------------------------------------ +// actor code +// ------------------------------------------------ +void() actor_run = +{ + local vector dist; + local float ang; + local entity pointOne; + + if ( pointcontents (self.origin) == CONTENT_LAVA ) + { + pointOne = find ( world, targetname, "point1" ); + if (pointOne != world) + setorigin ( self, pointOne.origin ); + } + + if ( self.goalentity.targetname == "endpoint1") + { + actorStage = STAGE_AT_POINT1; + self.think = actor_control; + self.nextthink = time + 0.1; + return; + } + if ( self.goalentity.targetname == "endpoint2") + { + actorStage = STAGE_AT_POINT2; + self.think = actor_control; + self.nextthink = time + 0.1; + return; + } + + self.frame = self.frame + 1; + if (self.frame > 11) + self.frame = 6; + + dist = self.goalentity.origin - self.origin; +// self.ideal_yaw = vectoyaw ( dist ); +// ChangeYaw(); + + movetogoal ( 15 ); + self.think = actor_run; + self.nextthink = time + 0.1; +}; + +void() finale_transition = +{ + if (!coop) { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + } else { + changelevel("start"); + } +} + +void() finale_check = +{ + if (finaleFinished()) { + self.nextthink = time + 5; + self.think = finale_transition; + } else { + self.nextthink = time + 0.1; + } +} + +void() actor_fire1 =[$rockatt1, actor_fire2 ] + { + self.goalentity = theMachine; + theMachine.th_pain = time_crash; + theMachine.th_die = time_crash; + theMachine.health = 1; + + self.angles = vectoangles ( self.goalentity.origin - self.origin ); + self.v_angle = self.angles; + self.v_angle_x = 0 - self.angles_x; + self.effects = EF_MUZZLEFLASH; + W_FireRocket(); + + // start the end text + WriteByte (MSG_ALL, SVC_FINALE); + + WriteString (MSG_ALL, "$qc_finale_rogue_end"); + + if (campaign && world.model == "maps/r2m8.bsp") + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_R2M8"); + if (skill == 3) + { + WriteByte (MSG_ALL, SVC_ACHIEVEMENT); + WriteString(MSG_ALL, "ACH_COMPLETE_R2M8_NIGHTMARE"); + } + } + + // instead of sitting here forever, run the quake ex credits and send the user back to start + local entity timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_check; + }; +void() actor_fire2 =[$rockatt2, actor_fire3 ] + {self.v_angle_x = 0; self.angles_x = 0; self.nextthink=time+0.15;}; +void() actor_fire3 =[$rockatt3, actor_fire4 ] {self.nextthink=time+0.15;}; +void() actor_fire4 =[$rockatt4, actor_fire5 ] {self.nextthink=time+0.15;}; +void() actor_fire5 =[$rockatt5, actor_fire6 ] + { + self.nextthink=time+0.15; + ending_remove_stuff(); + if (theMachine.health > 0) + { + theMachine.think = time_crash; + theMachine.nextthink = time + 0.1; + } + }; +void() actor_fire6 =[$rockatt6, actor_fire7 ] + { self.effects=0;self.nextthink=time+0.15;}; +void() actor_fire7 =[$stand1, actor_fire8 ] {self.nextthink=time+0.15;}; +void() actor_fire8 =[$stand2, actor_fire9 ] {self.nextthink=time+0.15;}; +void() actor_fire9 =[$stand3, actor_fire10 ] {self.nextthink=time+0.15;}; +void() actor_fire10 =[$stand4, actor_fire11 ] {self.nextthink=time+0.15;}; +void() actor_fire11 =[$stand5, actor_fire12 ] {self.nextthink=time+0.15;}; +void() actor_fire12 =[$stand1, actor_fire13 ] {self.nextthink=time+0.15;}; +void() actor_fire13 =[$stand2, actor_fire14 ] {self.nextthink=time+0.15;}; +void() actor_fire14 =[$stand3, actor_fire15 ] {self.nextthink=time+0.15;}; +void() actor_fire15 =[$stand4, actor_fire16 ] {self.nextthink=time+0.15;}; +void() actor_fire16 =[$stand5, actor_fire17 ] {self.nextthink=time+0.15;}; +void() actor_fire17 =[$stand1, actor_fire18 ] {self.nextthink=time+0.15;}; +void() actor_fire18 =[$stand2, actor_fire19 ] {self.nextthink=time+0.15;}; +void() actor_fire19 =[$stand3, actor_fire20 ] {self.nextthink=time+0.15;}; +void() actor_fire20 =[$stand4, actor_fire21 ] {self.nextthink=time+0.15;}; +void() actor_fire21 =[$stand5, actor_control ] + { + self.nextthink=time+0.15; + actorStage = STAGE_TO_POINT2; + }; + +void() actor_teleport = +{ + spawn_tfog(self.origin); + self.model = string_null; + self.think = SUB_Null; + self.nextthink = time + 999999; +}; + +void() actor_control = +{ + local entity timepod; + + if (actorStage == STAGE_START) + { + self.target = "point1"; + self.movetarget = find (world, targetname, self.target); + self.goalentity = self.movetarget; + if ( self.goalentity == world ) + objerror ("End Sequence point1 placing screwed up!"); + self.frame = 6; + self.think = actor_run; + self.nextthink = time + 0.1; + actorStage = STAGE_TO_POINT1; + } + else if (actorStage == STAGE_AT_POINT1) + { + self.target = "machine"; + self.movetarget = find (world, targetname, self.target); + self.goalentity = self.movetarget; + if ( self.goalentity == world ) + objerror ("End Sequence machine placing screwed up!"); + actorStage = STAGE_FIRING; + self.think = actor_fire1; + self.nextthink = time + 0.1; + } + else if (actorStage == STAGE_AT_POINT2) + { + self.frame = $stand1; + self.think = actor_teleport; + self.nextthink = time + 2; + } + else if (actorStage == STAGE_TO_POINT2) + { + self.target = "timepod"; + SUB_UseTargets(); + + self.target = "point2"; + self.movetarget = find (world, targetname, self.target); + self.goalentity = self.movetarget; + if ( self.goalentity == world ) + objerror ("End Sequence point2 placing screwed up!"); + self.frame = 6; + self.think = actor_run; + self.nextthink = time + 0.1; + } +}; + +// ============== +// player actor spawn function +// ============== +void(entity thePlayer) spawn_actor = +{ + local entity pointOne; + + theActor = spawn(); + theActor.owner = self; + theActor.classname = "actor"; + theActor.health = 100; + theActor.solid = SOLID_SLIDEBOX; + theActor.movetype = MOVETYPE_STEP; + theActor.frame = self.frame; + setmodel ( theActor, "progs/player.mdl"); + setorigin ( theActor, self.origin); + setsize (theActor, VEC_HULL_MIN, VEC_HULL_MAX); + theActor.view_ofs = '0 0 22'; + theActor.angles = self.angles; + theActor.ideal_yaw = theActor.angles * '0 1 0'; + if (!theActor.yaw_speed) + theActor.yaw_speed = 20; + theActor.view_ofs = '0 0 25'; + theActor.flags = theActor.flags | FL_MONSTER; + actorStage == STAGE_START; + + if ( pointcontents (theActor.origin) == CONTENT_LAVA ) + { + pointOne = find ( world, targetname, "point1" ); + if (pointOne != world) + setorigin ( theActor, pointOne.origin ); + } + + theActor.think = actor_control; + theActor.nextthink = time + 0.1; +}; + diff --git a/quakec_rogue/enforcer.qc b/quakec_rogue/enforcer.qc new file mode 100644 index 0000000..126f8bb --- /dev/null +++ b/quakec_rogue/enforcer.qc @@ -0,0 +1,376 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd /raid/quake/id1/models/enforcer +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 death12 death13 death14 + +$frame fdeath1 fdeath2 fdeath3 fdeath4 fdeath5 fdeath6 fdeath7 fdeath8 +$frame fdeath9 fdeath10 fdeath11 + +$frame paina1 paina2 paina3 paina4 + +$frame painb1 painb2 painb3 painb4 painb5 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 +$frame paind9 paind10 paind11 paind12 paind13 paind14 paind15 paind16 +$frame paind17 paind18 paind19 + +void() Laser_Touch = +{ + local vector org; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + sound (self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); + org = self.origin - 8*normalize(self.velocity); + + if (other.health) + { + SpawnBlood (org, self.velocity*0.2, 15); + T_Damage (other, self, self.owner, 15); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } + + remove(self); +}; + +void(vector org, vector vec) LaunchLaser = +{ + local vector vec; + + if (self.classname == "monster_enforcer") + sound (self, CHAN_WEAPON, "enforcer/enfire.wav", 1, ATTN_NORM); + + vec = normalize(vec); + + newmis = spawn(); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLY; + newmis.solid = SOLID_BBOX; + newmis.effects = EF_DIMLIGHT; + newmis.classname = "enforcer_laser"; + + setmodel (newmis, "progs/laser.mdl"); + setsize (newmis, '0 0 0', '0 0 0'); + + if (self.classname == "monster_morph") + { + if (!self.owner) + newmis.skin = 1; + } + + setorigin (newmis, org); + + newmis.velocity = vec * 600; + newmis.angles = vectoangles(newmis.velocity); + + newmis.nextthink = time + 5; + newmis.think = SUB_Remove; + newmis.touch = Laser_Touch; +}; + + + +void() enforcer_fire = +{ + local vector org; + + self.effects = self.effects | EF_MUZZLEFLASH; + makevectors (self.angles); + + org = self.origin + v_forward * 30 + v_right * 8.5 + '0 0 16'; + + LaunchLaser(org, self.enemy.origin - self.origin); +}; + +//============================================================================ + +void() enf_stand1 =[ $stand1, enf_stand2 ] {ai_stand();}; +void() enf_stand2 =[ $stand2, enf_stand3 ] {ai_stand();}; +void() enf_stand3 =[ $stand3, enf_stand4 ] {ai_stand();}; +void() enf_stand4 =[ $stand4, enf_stand5 ] {ai_stand();}; +void() enf_stand5 =[ $stand5, enf_stand6 ] {ai_stand();}; +void() enf_stand6 =[ $stand6, enf_stand7 ] {ai_stand();}; +void() enf_stand7 =[ $stand7, enf_stand1 ] {ai_stand();}; + +void() enf_walk1 =[ $walk1 , enf_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); +ai_walk(2);}; +void() enf_walk2 =[ $walk2 , enf_walk3 ] {ai_walk(4);}; +void() enf_walk3 =[ $walk3 , enf_walk4 ] {ai_walk(4);}; +void() enf_walk4 =[ $walk4 , enf_walk5 ] {ai_walk(3);}; +void() enf_walk5 =[ $walk5 , enf_walk6 ] {ai_walk(1);}; +void() enf_walk6 =[ $walk6 , enf_walk7 ] {ai_walk(2);}; +void() enf_walk7 =[ $walk7 , enf_walk8 ] {ai_walk(2);}; +void() enf_walk8 =[ $walk8 , enf_walk9 ] {ai_walk(1);}; +void() enf_walk9 =[ $walk9 , enf_walk10 ] {ai_walk(2);}; +void() enf_walk10 =[ $walk10, enf_walk11 ] {ai_walk(4);}; +void() enf_walk11 =[ $walk11, enf_walk12 ] {ai_walk(4);}; +void() enf_walk12 =[ $walk12, enf_walk13 ] {ai_walk(1);}; +void() enf_walk13 =[ $walk13, enf_walk14 ] {ai_walk(2);}; +void() enf_walk14 =[ $walk14, enf_walk15 ] {ai_walk(3);}; +void() enf_walk15 =[ $walk15, enf_walk16 ] {ai_walk(4);}; +void() enf_walk16 =[ $walk16, enf_walk1 ] {ai_walk(2);}; + +void() enf_run1 =[ $run1 , enf_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "enforcer/idle1.wav", 1, ATTN_IDLE); +ai_run(18);}; +void() enf_run2 =[ $run2 , enf_run3 ] {ai_run(14);}; +void() enf_run3 =[ $run3 , enf_run4 ] {ai_run(7);}; +void() enf_run4 =[ $run4 , enf_run5 ] {ai_run(12);}; +void() enf_run5 =[ $run5 , enf_run6 ] {ai_run(14);}; +void() enf_run6 =[ $run6 , enf_run7 ] {ai_run(14);}; +void() enf_run7 =[ $run7 , enf_run8 ] {ai_run(7);}; +void() enf_run8 =[ $run8 , enf_run1 ] {ai_run(11);}; + +void() enf_atk1 =[ $attack1, enf_atk2 ] {ai_face();}; +void() enf_atk2 =[ $attack2, enf_atk3 ] {ai_face();}; +void() enf_atk3 =[ $attack3, enf_atk4 ] {ai_face();}; +void() enf_atk4 =[ $attack4, enf_atk5 ] {ai_face();}; +void() enf_atk5 =[ $attack5, enf_atk6 ] {ai_face();}; +void() enf_atk6 =[ $attack6, enf_atk7 ] {enforcer_fire();}; +void() enf_atk7 =[ $attack7, enf_atk8 ] {ai_face();}; +void() enf_atk8 =[ $attack8, enf_atk9 ] {ai_face();}; +void() enf_atk9 =[ $attack5, enf_atk10 ] {ai_face();}; +void() enf_atk10 =[ $attack6, enf_atk11 ] {enforcer_fire();}; +void() enf_atk11 =[ $attack7, enf_atk12 ] {ai_face();}; +void() enf_atk12 =[ $attack8, enf_atk13 ] {ai_face();}; +void() enf_atk13 =[ $attack9, enf_atk14 ] {ai_face();}; +void() enf_atk14 =[ $attack10, enf_run1 ] {ai_face(); +SUB_CheckRefire (enf_atk1); +}; + +void() enf_paina1 =[ $paina1, enf_paina2 ] {}; +void() enf_paina2 =[ $paina2, enf_paina3 ] {}; +void() enf_paina3 =[ $paina3, enf_paina4 ] {}; +void() enf_paina4 =[ $paina4, enf_run1 ] {}; + +void() enf_painb1 =[ $painb1, enf_painb2 ] {}; +void() enf_painb2 =[ $painb2, enf_painb3 ] {}; +void() enf_painb3 =[ $painb3, enf_painb4 ] {}; +void() enf_painb4 =[ $painb4, enf_painb5 ] {}; +void() enf_painb5 =[ $painb5, enf_run1 ] {}; + +void() enf_painc1 =[ $painc1, enf_painc2 ] {}; +void() enf_painc2 =[ $painc2, enf_painc3 ] {}; +void() enf_painc3 =[ $painc3, enf_painc4 ] {}; +void() enf_painc4 =[ $painc4, enf_painc5 ] {}; +void() enf_painc5 =[ $painc5, enf_painc6 ] {}; +void() enf_painc6 =[ $painc6, enf_painc7 ] {}; +void() enf_painc7 =[ $painc7, enf_painc8 ] {}; +void() enf_painc8 =[ $painc8, enf_run1 ] {}; + +void() enf_paind1 =[ $paind1, enf_paind2 ] {}; +void() enf_paind2 =[ $paind2, enf_paind3 ] {}; +void() enf_paind3 =[ $paind3, enf_paind4 ] {}; +void() enf_paind4 =[ $paind4, enf_paind5 ] {ai_painforward(2);}; +void() enf_paind5 =[ $paind5, enf_paind6 ] {ai_painforward(1);}; +void() enf_paind6 =[ $paind6, enf_paind7 ] {}; +void() enf_paind7 =[ $paind7, enf_paind8 ] {}; +void() enf_paind8 =[ $paind8, enf_paind9 ] {}; +void() enf_paind9 =[ $paind9, enf_paind10 ] {}; +void() enf_paind10 =[ $paind10, enf_paind11 ] {}; +void() enf_paind11 =[ $paind11, enf_paind12 ] {ai_painforward(1);}; +void() enf_paind12 =[ $paind12, enf_paind13 ] {ai_painforward(1);}; +void() enf_paind13 =[ $paind13, enf_paind14 ] {ai_painforward(1);}; +void() enf_paind14 =[ $paind14, enf_paind15 ] {}; +void() enf_paind15 =[ $paind15, enf_paind16 ] {}; +void() enf_paind16 =[ $paind16, enf_paind17 ] {ai_pain(1);}; +void() enf_paind17 =[ $paind17, enf_paind18 ] {ai_pain(1);}; +void() enf_paind18 =[ $paind18, enf_paind19 ] {}; +void() enf_paind19 =[ $paind19, enf_run1 ] {}; + +void(entity attacker, float damage) enf_pain = +{ + local float r; + + r = random (); + if (self.pain_finished > time) + return; + + + if (r < 0.5) + sound (self, CHAN_VOICE, "enforcer/pain1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "enforcer/pain2.wav", 1, ATTN_NORM); + + if (r < 0.2) + { + self.pain_finished = time + 1; + enf_paina1 (); + } + else if (r < 0.4) + { + self.pain_finished = time + 1; + enf_painb1 (); + } + else if (r < 0.7) + { + self.pain_finished = time + 1; + enf_painc1 (); + } + else + { + self.pain_finished = time + 2; + enf_paind1 (); + } +}; + +//============================================================================ + + + + +void() enf_die1 =[ $death1, enf_die2 ] {}; +void() enf_die2 =[ $death2, enf_die3 ] {}; +void() enf_die3 =[ $death3, enf_die4 ] +{self.solid = SOLID_NOT;self.ammo_cells = 5;DropBackpack();}; +void() enf_die4 =[ $death4, enf_die5 ] {ai_forward(14);}; +void() enf_die5 =[ $death5, enf_die6 ] {ai_forward(2);}; +void() enf_die6 =[ $death6, enf_die7 ] {}; +void() enf_die7 =[ $death7, enf_die8 ] {}; +void() enf_die8 =[ $death8, enf_die9 ] {}; +void() enf_die9 =[ $death9, enf_die10 ] {ai_forward(3);}; +void() enf_die10 =[ $death10, enf_die11 ] {ai_forward(5);}; +void() enf_die11 =[ $death11, enf_die12 ] {ai_forward(5);}; +void() enf_die12 =[ $death12, enf_die13 ] {ai_forward(5);}; +void() enf_die13 =[ $death13, enf_die14 ] {}; +void() enf_die14 =[ $death14, enf_die14 ] {}; + +void() enf_fdie1 =[ $fdeath1, enf_fdie2 ] { + +}; +void() enf_fdie2 =[ $fdeath2, enf_fdie3 ] {}; +void() enf_fdie3 =[ $fdeath3, enf_fdie4 ] +{self.solid = SOLID_NOT;self.ammo_cells = 5;DropBackpack();}; +void() enf_fdie4 =[ $fdeath4, enf_fdie5 ] {}; +void() enf_fdie5 =[ $fdeath5, enf_fdie6 ] {}; +void() enf_fdie6 =[ $fdeath6, enf_fdie7 ] {}; +void() enf_fdie7 =[ $fdeath7, enf_fdie8 ] {}; +void() enf_fdie8 =[ $fdeath8, enf_fdie9 ] {}; +void() enf_fdie9 =[ $fdeath9, enf_fdie10 ] {}; +void() enf_fdie10 =[ $fdeath10, enf_fdie11 ] {}; +void() enf_fdie11 =[ $fdeath11, enf_fdie11 ] {}; + + +void() enf_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_mega.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "enforcer/death1.wav", 1, ATTN_NORM); + if (random() > 0.5) + enf_die1 (); + else + enf_fdie1 (); +}; + + +/*QUAKED monster_enforcer (1 0 0) (-16 -16 -24) (16 16 40) Ambush + +*/ +void() monster_enforcer = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/enforcer.mdl"); + precache_model2 ("progs/h_mega.mdl"); + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/idle1.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/enforcer.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + + self.th_stand = enf_stand1; + self.th_walk = enf_walk1; + self.th_run = enf_run1; + self.th_pain = enf_pain; + self.th_die = enf_die; + self.th_missile = enf_atk1; + + walkmonster_start(); +}; diff --git a/quakec_rogue/fight.qc b/quakec_rogue/fight.qc new file mode 100644 index 0000000..1df908a --- /dev/null +++ b/quakec_rogue/fight.qc @@ -0,0 +1,427 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/* + +A monster is in fight mode if it thinks it can effectively attack its +enemy. + +When it decides it can't attack, it goes into hunt mode. + +*/ + +float(float v) anglemod; + +void() knight_atk1; +void() knight_runatk1; +void() ogre_smash1; +void() ogre_swing1; + +void() sham_smash1; +void() sham_swingr1; +void() sham_swingl1; + +float() DemonCheckAttack; +void(float side) Demon_Melee; + +void(vector dest) ChooseTurn; + +void() ai_face; + + +float enemy_vis, enemy_infront, enemy_range; +float enemy_yaw; + + +void() knight_attack = +{ + local float len; + +// decide if now is a good swing time + len = vlen(self.enemy.origin+self.enemy.view_ofs - (self.origin+self.view_ofs)); + + if (len<80) + knight_atk1 (); + else + knight_runatk1 (); +}; + +//============================================================================= + +/* +=========== +CheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() CheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != targ) + return FALSE; // don't have a clear shot + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (enemy_range == RANGE_MELEE) + { // melee attack + if (self.th_melee) + { + if (self.classname == "monster_knight") + knight_attack (); + else + self.th_melee (); + return TRUE; + } + } + +// missile attack + if (!self.th_missile) + return FALSE; + + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + { + chance = 0.9; + self.attack_finished = 0; + } + else if (enemy_range == RANGE_NEAR) + { + if (self.th_melee) + chance = 0.2; + else + chance = 0.4; + } + else if (enemy_range == RANGE_MID) + { + if (self.th_melee) + chance = 0.05; + else + chance = 0.1; + } + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (2*random()); + return TRUE; + } + + return FALSE; +}; + + +/* +============= +ai_face + +Stay facing the enemy +============= +*/ +void() ai_face = +{ + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); +}; + +/* +============= +ai_charge + +The monster is in a melee attack, so get as close as possible to .enemy +============= +*/ +float (entity targ) visible; +float(entity targ) infront; +float(entity targ) range; + +void(float d) ai_charge = +{ + ai_face (); + movetogoal (d); // done in C code... +}; + +void() ai_charge_side = +{ + local vector dtemp; + local float heading; + +// aim to the left of the enemy for a flyby + + self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin); + ChangeYaw (); + + makevectors (self.angles); + dtemp = self.enemy.origin - 30*v_right; + heading = vectoyaw(dtemp - self.origin); + + walkmove(heading, 20); +}; + + +/* +============= +ai_melee + +============= +*/ +void() ai_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +void() ai_melee_side = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + ai_charge_side(); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + if (!CanDamage (self.enemy, self)) + return; + ldmg = (random() + random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + + +//============================================================================= + +/* +=========== +SoldierCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() SoldierCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + return FALSE; // don't have a clear shot + + +// missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.4; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + if (random () < chance) + { + self.th_missile (); + SUB_AttackFinished (1 + random()); + if (random() < 0.3) + self.lefty = !self.lefty; + + return TRUE; + } + + return FALSE; +}; +//============================================================================= + +/* +=========== +ShamCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() ShamCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + local float enemy_yaw; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_vis) + return FALSE; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + if (vlen(spot1 - spot2) > 600) + return FALSE; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + +// missile attack + if (enemy_range == RANGE_FAR) + return FALSE; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (2 + 2*random()); + return TRUE; +}; + +//============================================================================ + +/* +=========== +OgreCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() OgreCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (enemy_range == RANGE_MELEE) + { + if (CanDamage (self.enemy, self)) + { + self.attack_state = AS_MELEE; + return TRUE; + } + } + + if (time < self.attack_finished) + return FALSE; + + if (!enemy_vis) + return FALSE; + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + + if (trace_ent != targ) + { + return FALSE; // don't have a clear shot + } + +// missile attack + if (time < self.attack_finished) + return FALSE; + + if (enemy_range == RANGE_FAR) + return FALSE; + + else if (enemy_range == RANGE_NEAR) + chance = 0.10; + else if (enemy_range == RANGE_MID) + chance = 0.05; + else + chance = 0; + + self.attack_state = AS_MISSILE; + SUB_AttackFinished (1 + 2*random()); + return TRUE; +}; + diff --git a/quakec_rogue/fish.qc b/quakec_rogue/fish.qc new file mode 100644 index 0000000..9c2b4ee --- /dev/null +++ b/quakec_rogue/fish.qc @@ -0,0 +1,206 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +$cd /raid/quake/id1/models/fish +$origin 0 0 24 +$base base +$skin skin + +$frame attack1 attack2 attack3 attack4 attack5 attack6 +$frame attack7 attack8 attack9 attack10 attack11 attack12 attack13 +$frame attack14 attack15 attack16 attack17 attack18 + +$frame death1 death2 death3 death4 death5 death6 death7 +$frame death8 death9 death10 death11 death12 death13 death14 death15 +$frame death16 death17 death18 death19 death20 death21 + +$frame swim1 swim2 swim3 swim4 swim5 swim6 swim7 swim8 +$frame swim9 swim10 swim11 swim12 swim13 swim14 swim15 swim16 swim17 +$frame swim18 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 pain7 pain8 +$frame pain9 + +void() swimmonster_start; + +void() f_stand1 =[ $swim1, f_stand2 ] {ai_stand();}; +void() f_stand2 =[ $swim2, f_stand3 ] {ai_stand();}; +void() f_stand3 =[ $swim3, f_stand4 ] {ai_stand();}; +void() f_stand4 =[ $swim4, f_stand5 ] {ai_stand();}; +void() f_stand5 =[ $swim5, f_stand6 ] {ai_stand();}; +void() f_stand6 =[ $swim6, f_stand7 ] {ai_stand();}; +void() f_stand7 =[ $swim7, f_stand8 ] {ai_stand();}; +void() f_stand8 =[ $swim8, f_stand9 ] {ai_stand();}; +void() f_stand9 =[ $swim9, f_stand10 ] {ai_stand();}; +void() f_stand10 =[ $swim10, f_stand11 ] {ai_stand();}; +void() f_stand11 =[ $swim11, f_stand12 ] {ai_stand();}; +void() f_stand12 =[ $swim12, f_stand13 ] {ai_stand();}; +void() f_stand13 =[ $swim13, f_stand14 ] {ai_stand();}; +void() f_stand14 =[ $swim14, f_stand15 ] {ai_stand();}; +void() f_stand15 =[ $swim15, f_stand16 ] {ai_stand();}; +void() f_stand16 =[ $swim16, f_stand17 ] {ai_stand();}; +void() f_stand17 =[ $swim17, f_stand18 ] {ai_stand();}; +void() f_stand18 =[ $swim18, f_stand1 ] {ai_stand();}; + +void() f_walk1 =[ $swim1, f_walk2 ] {ai_walk(8);}; +void() f_walk2 =[ $swim2, f_walk3 ] {ai_walk(8);}; +void() f_walk3 =[ $swim3, f_walk4 ] {ai_walk(8);}; +void() f_walk4 =[ $swim4, f_walk5 ] {ai_walk(8);}; +void() f_walk5 =[ $swim5, f_walk6 ] {ai_walk(8);}; +void() f_walk6 =[ $swim6, f_walk7 ] {ai_walk(8);}; +void() f_walk7 =[ $swim7, f_walk8 ] {ai_walk(8);}; +void() f_walk8 =[ $swim8, f_walk9 ] {ai_walk(8);}; +void() f_walk9 =[ $swim9, f_walk10 ] {ai_walk(8);}; +void() f_walk10 =[ $swim10, f_walk11 ] {ai_walk(8);}; +void() f_walk11 =[ $swim11, f_walk12 ] {ai_walk(8);}; +void() f_walk12 =[ $swim12, f_walk13 ] {ai_walk(8);}; +void() f_walk13 =[ $swim13, f_walk14 ] {ai_walk(8);}; +void() f_walk14 =[ $swim14, f_walk15 ] {ai_walk(8);}; +void() f_walk15 =[ $swim15, f_walk16 ] {ai_walk(8);}; +void() f_walk16 =[ $swim16, f_walk17 ] {ai_walk(8);}; +void() f_walk17 =[ $swim17, f_walk18 ] {ai_walk(8);}; +void() f_walk18 =[ $swim18, f_walk1 ] {ai_walk(8);}; + +void() f_run1 =[ $swim1, f_run2 ] {ai_run(12); + if (random() < 0.5) + sound (self, CHAN_VOICE, "fish/idle.wav", 1, ATTN_NORM); +}; +void() f_run2 =[ $swim3, f_run3 ] {ai_run(12);}; +void() f_run3 =[ $swim5, f_run4 ] {ai_run(12);}; +void() f_run4 =[ $swim7, f_run5 ] {ai_run(12);}; +void() f_run5 =[ $swim9, f_run6 ] {ai_run(12);}; +void() f_run6 =[ $swim11, f_run7 ] {ai_run(12);}; +void() f_run7 =[ $swim13, f_run8 ] {ai_run(12);}; +void() f_run8 =[ $swim15, f_run9 ] {ai_run(12);}; +void() f_run9 =[ $swim17, f_run1 ] {ai_run(12);}; + +void() fish_melee = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; // removed before stroke + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 60) + return; + + sound (self, CHAN_VOICE, "fish/bite.wav", 1, ATTN_NORM); + ldmg = (random() + random()) * 3; + T_Damage (self.enemy, self, self, ldmg); +}; + +void() f_attack1 =[ $attack1, f_attack2 ] {ai_charge(10);}; +void() f_attack2 =[ $attack2, f_attack3 ] {ai_charge(10);}; +void() f_attack3 =[ $attack3, f_attack4 ] {fish_melee();}; +void() f_attack4 =[ $attack4, f_attack5 ] {ai_charge(10);}; +void() f_attack5 =[ $attack5, f_attack6 ] {ai_charge(10);}; +void() f_attack6 =[ $attack6, f_attack7 ] {ai_charge(10);}; +void() f_attack7 =[ $attack7, f_attack8 ] {ai_charge(10);}; +void() f_attack8 =[ $attack8, f_attack9 ] {ai_charge(10);}; +void() f_attack9 =[ $attack9, f_attack10] {fish_melee();}; +void() f_attack10 =[ $attack10, f_attack11] {ai_charge(10);}; +void() f_attack11 =[ $attack11, f_attack12] {ai_charge(10);}; +void() f_attack12 =[ $attack12, f_attack13] {ai_charge(10);}; +void() f_attack13 =[ $attack13, f_attack14] {ai_charge(10);}; +void() f_attack14 =[ $attack14, f_attack15] {ai_charge(10);}; +void() f_attack15 =[ $attack15, f_attack16] {fish_melee();}; +void() f_attack16 =[ $attack16, f_attack17] {ai_charge(10);}; +void() f_attack17 =[ $attack17, f_attack18] {ai_charge(10);}; +void() f_attack18 =[ $attack18, f_run1 ] {ai_charge(10);}; + +void() f_death1 =[ $death1, f_death2 ] { +sound (self, CHAN_VOICE, "fish/death.wav", 1, ATTN_NORM); +}; +void() f_death2 =[ $death2, f_death3 ] {}; +void() f_death3 =[ $death3, f_death4 ] {}; +void() f_death4 =[ $death4, f_death5 ] {}; +void() f_death5 =[ $death5, f_death6 ] {}; +void() f_death6 =[ $death6, f_death7 ] {}; +void() f_death7 =[ $death7, f_death8 ] {}; +void() f_death8 =[ $death8, f_death9 ] {}; +void() f_death9 =[ $death9, f_death10 ] {}; +void() f_death10 =[ $death10, f_death11 ] {}; +void() f_death11 =[ $death11, f_death12 ] {}; +void() f_death12 =[ $death12, f_death13 ] {}; +void() f_death13 =[ $death13, f_death14 ] {}; +void() f_death14 =[ $death14, f_death15 ] {}; +void() f_death15 =[ $death15, f_death16 ] {}; +void() f_death16 =[ $death16, f_death17 ] {}; +void() f_death17 =[ $death17, f_death18 ] {}; +void() f_death18 =[ $death18, f_death19 ] {}; +void() f_death19 =[ $death19, f_death20 ] {}; +void() f_death20 =[ $death20, f_death21 ] {}; +void() f_death21 =[ $death21, f_death21 ] {self.solid = SOLID_NOT;}; + +void() f_pain1 =[ $pain1, f_pain2 ] {}; +void() f_pain2 =[ $pain2, f_pain3 ] {ai_pain(6);}; +void() f_pain3 =[ $pain3, f_pain4 ] {ai_pain(6);}; +void() f_pain4 =[ $pain4, f_pain5 ] {ai_pain(6);}; +void() f_pain5 =[ $pain5, f_pain6 ] {ai_pain(6);}; +void() f_pain6 =[ $pain6, f_pain7 ] {ai_pain(6);}; +void() f_pain7 =[ $pain7, f_pain8 ] {ai_pain(6);}; +void() f_pain8 =[ $pain8, f_pain9 ] {ai_pain(6);}; +void() f_pain9 =[ $pain9, f_run1 ] {ai_pain(6);}; + +void(entity attacker, float damage) fish_pain = +{ + +// fish allways do pain frames + f_pain1 (); +}; + + +//(-16 -16 -24) (16 16 24) +/*QUAKED monster_fish (1 0 0) (-16 -16 -24) (16 16 32) Ambush +*/ +void() monster_fish = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/fish.mdl"); + + precache_sound2 ("fish/death.wav"); + precache_sound2 ("fish/bite.wav"); + precache_sound2 ("fish/idle.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/fish.mdl"); + +// setsize (self, '-16 -16 -24', '16 16 24'); + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + self.health = 25; + + self.th_stand = f_stand1; + self.th_walk = f_walk1; + self.th_run = f_run1; + self.th_die = f_death1; + self.th_pain = fish_pain; + self.th_melee = f_attack1; + + swimmonster_start (); +}; + diff --git a/quakec_rogue/grapple.qc b/quakec_rogue/grapple.qc new file mode 100644 index 0000000..392796f --- /dev/null +++ b/quakec_rogue/grapple.qc @@ -0,0 +1,328 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Rogue Grapple Implementation +// Jan'97 by ZOID +// Under contract to id software for Rogue Entertainment + +// New entity fields +.entity hook; // this is my hook +.float on_hook; // we're on it +.float hook_out; // it's out + +// prototypes for WEAPONS.QC functions +float() crandom; +void(vector org, vector vel, float damage) SpawnBlood; + +void(entity h, entity player) GrappleTrail = +{ + // draw a line to the hook + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_BEAM); + WriteEntity (MSG_BROADCAST, h); + WriteCoord (MSG_BROADCAST, h.origin_x); + WriteCoord (MSG_BROADCAST, h.origin_y); + WriteCoord (MSG_BROADCAST, h.origin_z); + WriteCoord (MSG_BROADCAST, player.origin_x); + WriteCoord (MSG_BROADCAST, player.origin_y); + WriteCoord (MSG_BROADCAST, player.origin_z + 16); +}; + +void() GrappleReset = +{ + self.owner.on_hook = FALSE; + self.owner.hook_out = FALSE; + self.owner.weaponframe = 0; + self.owner.attack_finished = time +0.25; + + remove(self); +}; + +void() GrappleTrack = +{ + local vector spray; + + // Release dead targets + if (self.enemy.classname == "player" && self.enemy.health <= 0) + self.owner.on_hook = FALSE; + + // drop the hook if owner is dead or has released the button + if (!self.owner.on_hook || self.owner.health <= 0) { + GrappleReset(); + return; + } + + if (self.enemy.classname == "player") + { + if (self.enemy.teleport_time > time) + { + GrappleReset(); + return; + } + + // move the hook along with the player. It's invisible, but + // we need this to make the sound come from the right spot + setorigin(self, self.enemy.origin); + +// sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM); + sound (self, CHAN_WEAPON, "pendulum/hit.wav", 1, ATTN_NORM); + T_Damage (self.enemy, self, self.owner, 1); + makevectors (self.v_angle); + spray_x = 100 * crandom(); + spray_y = 100 * crandom(); + spray_z = 100 * crandom() + 50; + SpawnBlood(self.origin, spray, 20); + } + if (self.enemy.solid == SOLID_SLIDEBOX) { + self.velocity = '0 0 0'; + setorigin(self, self.enemy.origin + self.enemy.mins + + self.enemy.size * 0.5); + } else + self.velocity = self.enemy.velocity; + + self.nextthink = time + 0.1; +}; + +/* +entity(float head) GrappleMakeLink = +{ + newmis = spawn (); + + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_NOT; + newmis.owner = self;// SELF is the hook! + + newmis.avelocity = '200 200 200'; + + setmodel (newmis, "progs/bit.mdl"); + setorigin (newmis, self.origin); + setsize (newmis, '0 0 0' , '0 0 0'); + + return newmis; +}; +*/ + +// Removes all chain link entities; this is a separate function because CLIENT +// also needs to be able to remove the chain. Only one function required to +// remove all links. +/* +void () GrappleRemoveChain = +{ + self.think = SUB_Remove; + self.nextthink = time; + + if (self.goalentity) { + self.goalentity.think = SUB_Remove; + self.goalentity.nextthink = time; + if (self.goalentity.goalentity) { + self.goalentity.goalentity.think = SUB_Remove; + self.goalentity.goalentity.nextthink = time; + } + } +}; +*/ + +// Repositions the chain links each frame. This single function maintains the +// positions of all of the links. Only one link is thinking every frame. +/* +void() GrappleUpdateChain = +{ + local vector temp; + + if (!self.owner.hook_out) { + GrappleRemoveChain(); + return; + } + + temp = (self.owner.hook.origin - self.owner.origin); + + // These numbers are correct assuming 3 links. + // 4 links would be *20 *40 *60 and *80 + setorigin (self, self.owner.origin + temp * 0.25); + setorigin (self.goalentity, self.owner.origin + temp * 0.5); + setorigin (self.goalentity.goalentity, self.owner.origin + temp * 0.75); + self.nextthink = time + 0.1; +}; +*/ + +// +// Build_Chain - Builds the chain (linked list) +// +/* +void() GrappleBuildChain = +{ + self.goalentity = GrappleMakeLink(); + self.goalentity.think = GrappleUpdateChain; + self.goalentity.nextthink = time + 0.1; + self.goalentity.owner = self.owner; + + self.goalentity.goalentity = GrappleMakeLink(); + self.goalentity.goalentity.goalentity = GrappleMakeLink(); +}; +*/ + +// Tries to anchor the grapple to whatever it touches +void () GrappleAnchor = +{ + if (other == self.owner) // don't hook the guy that fired it + return; + + if (pointcontents(self.origin) == CONTENT_SKY) { + GrappleReset(); + return; + } + + if (other.classname == "player") + { + // glance off of teammates + if (other.steam == self.owner.steam) + { + GrappleReset(); // PGM - fix drift after teammate hit 01/20/97 + return; + } + + sound (self, CHAN_WEAPON, "player/axhit1.wav", 1, ATTN_NORM); + T_Damage (other, self, self.owner, 10); + } else { + sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + + // One point of damage inflicted upon impact. Subsequent + // damage will only be done to PLAYERS... this way secret + // doors and triggers will only be damaged once. + if (other.takedamage) + T_Damage (other, self, self.owner, 1); + + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + } + + self.frame = 2; // anchored + + sound (self.owner, CHAN_WEAPON, "weapons/tink1.wav", 1, ATTN_NORM); + + if (!self.owner.button0) { + GrappleReset(); + return; + } + + self.owner.on_hook = TRUE; + if (self.owner.flags & FL_ONGROUND) + self.owner.flags = self.owner.flags - FL_ONGROUND; + + // CHAIN2 is a looping sample. Use LEFTY as a flag so that client.qc + // will know to only play the tink sound ONCE to clear the weapons + // sound channel. (Lefty is a leftover from AI.QC, so I reused it to + // avoid adding a field) + self.owner.lefty = TRUE; + + self.enemy = other;// remember this guy! + self.think = GrappleTrack; + self.nextthink = time; + self.solid = SOLID_NOT; + self.touch = SUB_Null; +}; + +void () W_FireGrapple = +{ + if (self.hook_out)// reject subsequent calls from player.qc + return; + + self.punchangle_x = -2; // bump him + + // chain out sound (loops) + sound (self, CHAN_WEAPON, "weapons/chain1.wav", 1, ATTN_NORM); + + newmis = spawn(); + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + newmis.owner = self; // newmis belongs to me + self.hook = newmis; // This is my newmis + newmis.classname = "hook"; + + makevectors (self.v_angle); + newmis.velocity = v_forward * 800; +// newmis.avelocity = '0 0 -500'; + newmis.angles = vectoangles(v_forward); + + newmis.touch = GrappleAnchor; + newmis.think = GrappleReset; + // grapple only lives for two seconds, this gives max range on it + newmis.nextthink = time + 2; + newmis.frame = 1; // hook spread + + setmodel (newmis,"progs/hook.mdl"); + setorigin (newmis, self.origin + v_forward * 16 + '0 0 16'); + setsize(newmis, '0 0 0' , '0 0 0 '); + + self.hook_out = TRUE; +}; + +// called each frame by CLIENT.QC if client has hook_out +void() GrappleService = +{ + local vector o, dist, vel; + local float v; + + if (!self.on_hook) { + // just draw a line to the hook + if (vlen(self.hook.origin - self.origin) > 50) + GrappleTrail(self.hook, self); + return; + } + + // drop the hook if player lets go of button + if ((!self.button0 && self.weapon == IT_GRAPPLE) || + self.teleport_time > time) + { // release when we get 'ported + self = self.hook; + GrappleReset(); + return; + } + + makevectors (self.angles); + dist = self.hook.origin - self.origin; + vel = self.hook.origin - ( self.origin + (v_up * 16 * + (!self.button2)) + (v_forward * 16)); + + v = vlen (vel); + + if (v <= 100) + vel = normalize(vel) * v * 10; + else + vel = normalize(vel) * 1000; + + self.velocity = vel; + + if ( vlen(dist) <= 50) { + if (self.lefty) { // cancel chain sound + // If there is a chain, ditch it now. We're + // close enough. Having extra entities lying around + // is never a good idea. +// if (self.hook.goalentity) { +// self.hook.goalentity.think = GrappleRemoveChain; +// self.hook.goalentity.nextthink = time; +// } + + self.lefty = FALSE;// we've reset the sound channel. + } + } else + GrappleTrail(self.hook, self); +}; + + diff --git a/quakec_rogue/hknight.qc b/quakec_rogue/hknight.qc new file mode 100644 index 0000000..5b3cf64 --- /dev/null +++ b/quakec_rogue/hknight.qc @@ -0,0 +1,563 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd /raid/quake/id1/models/knight2 +$origin 0 0 24 +$base base +$skin skin +$skin statue2 + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 walk15 walk16 walk17 +$frame walk18 walk19 walk20 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 death11 death12 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame char_a1 char_a2 char_a3 char_a4 char_a5 char_a6 char_a7 char_a8 +$frame char_a9 char_a10 char_a11 char_a12 char_a13 char_a14 char_a15 char_a16 + +$frame magica1 magica2 magica3 magica4 magica5 magica6 magica7 magica8 +$frame magica9 magica10 magica11 magica12 magica13 magica14 + +$frame magicb1 magicb2 magicb3 magicb4 magicb5 magicb6 magicb7 magicb8 +$frame magicb9 magicb10 magicb11 magicb12 magicb13 + +$frame char_b1 char_b2 char_b3 char_b4 char_b5 char_b6 + +$frame slice1 slice2 slice3 slice4 slice5 slice6 slice7 slice8 slice9 slice10 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 smash8 smash9 smash10 +$frame smash11 + +$frame w_attack1 w_attack2 w_attack3 w_attack4 w_attack5 w_attack6 w_attack7 +$frame w_attack8 w_attack9 w_attack10 w_attack11 w_attack12 w_attack13 w_attack14 +$frame w_attack15 w_attack16 w_attack17 w_attack18 w_attack19 w_attack20 +$frame w_attack21 w_attack22 + +$frame magicc1 magicc2 magicc3 magicc4 magicc5 magicc6 magicc7 magicc8 +$frame magicc9 magicc10 magicc11 + + +void() hknight_char_a1; +void() hknight_run1; +void() hk_idle_sound; + +void(float offset) hknight_shot = +{ + local vector offang; + local vector org, vec; + + offang = vectoangles (self.enemy.origin - self.origin); + offang_y = offang_y + offset * 6; + + makevectors (offang); + + org = self.origin + self.mins + self.size*0.5 + v_forward * 20; + +// set missile speed + vec = normalize (v_forward); + vec_z = 0 - vec_z + (random() - 0.5)*0.1; + + launch_spike (org, vec); + newmis.classname = "knightspike"; + setmodel (newmis, "progs/k_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + newmis.velocity = vec*300; + newmis.effects = newmis.effects | EF_CANDLELIGHT; // give the projectiles a weak glow + sound (self, CHAN_WEAPON, "hknight/attack1.wav", 1, ATTN_NORM); +}; + +void() CheckForCharge = +{ +// check for mad charge +if (!enemy_vis) + return; +if (time < self.attack_finished) + return; +if ( fabs(self.origin_z - self.enemy.origin_z) > 20) + return; // too much height change +if ( vlen (self.origin - self.enemy.origin) < 80) + return; // use regular attack + +// charge + SUB_AttackFinished (2); + hknight_char_a1 (); + +}; + +void() CheckContinueCharge = +{ + if (time > self.attack_finished) + { + SUB_AttackFinished (3); + hknight_run1 (); + return; // done charging + } + if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); + else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +}; + +//=========================================================================== + +void() hknight_stand1 =[ $stand1, hknight_stand2 ] {ai_stand();}; +void() hknight_stand2 =[ $stand2, hknight_stand3 ] {ai_stand();}; +void() hknight_stand3 =[ $stand3, hknight_stand4 ] {ai_stand();}; +void() hknight_stand4 =[ $stand4, hknight_stand5 ] {ai_stand();}; +void() hknight_stand5 =[ $stand5, hknight_stand6 ] {ai_stand();}; +void() hknight_stand6 =[ $stand6, hknight_stand7 ] {ai_stand();}; +void() hknight_stand7 =[ $stand7, hknight_stand8 ] {ai_stand();}; +void() hknight_stand8 =[ $stand8, hknight_stand9 ] {ai_stand();}; +void() hknight_stand9 =[ $stand9, hknight_stand1 ] {ai_stand();}; + +//=========================================================================== + +void() hknight_walk1 =[ $walk1, hknight_walk2 ] { +hk_idle_sound(); +ai_walk(2);}; +void() hknight_walk2 =[ $walk2, hknight_walk3 ] {ai_walk(5);}; +void() hknight_walk3 =[ $walk3, hknight_walk4 ] {ai_walk(5);}; +void() hknight_walk4 =[ $walk4, hknight_walk5 ] {ai_walk(4);}; +void() hknight_walk5 =[ $walk5, hknight_walk6 ] {ai_walk(4);}; +void() hknight_walk6 =[ $walk6, hknight_walk7 ] {ai_walk(2);}; +void() hknight_walk7 =[ $walk7, hknight_walk8 ] {ai_walk(2);}; +void() hknight_walk8 =[ $walk8, hknight_walk9 ] {ai_walk(3);}; +void() hknight_walk9 =[ $walk9, hknight_walk10 ] {ai_walk(3);}; +void() hknight_walk10 =[ $walk10, hknight_walk11 ] {ai_walk(4);}; +void() hknight_walk11 =[ $walk11, hknight_walk12 ] {ai_walk(3);}; +void() hknight_walk12 =[ $walk12, hknight_walk13 ] {ai_walk(4);}; +void() hknight_walk13 =[ $walk13, hknight_walk14 ] {ai_walk(6);}; +void() hknight_walk14 =[ $walk14, hknight_walk15 ] {ai_walk(2);}; +void() hknight_walk15 =[ $walk15, hknight_walk16 ] {ai_walk(2);}; +void() hknight_walk16 =[ $walk16, hknight_walk17 ] {ai_walk(4);}; +void() hknight_walk17 =[ $walk17, hknight_walk18 ] {ai_walk(3);}; +void() hknight_walk18 =[ $walk18, hknight_walk19 ] {ai_walk(3);}; +void() hknight_walk19 =[ $walk19, hknight_walk20 ] {ai_walk(3);}; +void() hknight_walk20 =[ $walk20, hknight_walk1 ] {ai_walk(2);}; + +//=========================================================================== + +void() hknight_run1 =[ $run1, hknight_run2 ] { +hk_idle_sound(); +ai_run (20); CheckForCharge (); }; +void() hknight_run2 =[ $run2, hknight_run3 ] {ai_run(25);}; +void() hknight_run3 =[ $run3, hknight_run4 ] {ai_run(18);}; +void() hknight_run4 =[ $run4, hknight_run5 ] {ai_run(16);}; +void() hknight_run5 =[ $run5, hknight_run6 ] {ai_run(14);}; +void() hknight_run6 =[ $run6, hknight_run7 ] {ai_run(25);}; +void() hknight_run7 =[ $run7, hknight_run8 ] {ai_run(21);}; +void() hknight_run8 =[ $run8, hknight_run1 ] {ai_run(13);}; + +//============================================================================ + +void() hknight_pain1 =[ $pain1, hknight_pain2 ] + { + if (self.spawnflags & 2) // statue + sound (self, CHAN_VOICE, "statue/pain.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM); + }; +void() hknight_pain2 =[ $pain2, hknight_pain3 ] {}; +void() hknight_pain3 =[ $pain3, hknight_pain4 ] {}; +void() hknight_pain4 =[ $pain4, hknight_pain5 ] {}; +void() hknight_pain5 =[ $pain5, hknight_run1 ] {}; + +//============================================================================ + +void() hknight_die1 =[ $death1, hknight_die2 ] {ai_forward(10);}; +void() hknight_die2 =[ $death2, hknight_die3 ] {ai_forward(8);}; +void() hknight_die3 =[ $death3, hknight_die4 ] +{self.solid = SOLID_NOT; ai_forward(7);}; +void() hknight_die4 =[ $death4, hknight_die5 ] {}; +void() hknight_die5 =[ $death5, hknight_die6 ] {}; +void() hknight_die6 =[ $death6, hknight_die7 ] {}; +void() hknight_die7 =[ $death7, hknight_die8 ] {}; +void() hknight_die8 =[ $death8, hknight_die9 ] {ai_forward(10);}; +void() hknight_die9 =[ $death9, hknight_die10 ] {ai_forward(11);}; +void() hknight_die10 =[ $death10, hknight_die11 ] {}; +void() hknight_die11 =[ $death11, hknight_die12 ] {}; +void() hknight_die12 =[ $death12, hknight_die12 ] {}; + +void() hknight_dieb1 =[ $deathb1, hknight_dieb2 ] {}; +void() hknight_dieb2 =[ $deathb2, hknight_dieb3 ] {}; +void() hknight_dieb3 =[ $deathb3, hknight_dieb4 ] +{self.solid = SOLID_NOT;}; +void() hknight_dieb4 =[ $deathb4, hknight_dieb5 ] {}; +void() hknight_dieb5 =[ $deathb5, hknight_dieb6 ] {}; +void() hknight_dieb6 =[ $deathb6, hknight_dieb7 ] {}; +void() hknight_dieb7 =[ $deathb7, hknight_dieb8 ] {}; +void() hknight_dieb8 =[ $deathb8, hknight_dieb9 ] {}; +void() hknight_dieb9 =[ $deathb9, hknight_dieb9 ] {}; + +void() hknight_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_hellkn.mdl", self.health); + + if (self.spawnflags & 2) // statue + { + ThrowGib ( "progs/statgib1.mdl", self.health); + ThrowGib ( "progs/statgib2.mdl", self.health); + ThrowGib ( "progs/statgib3.mdl", self.health); + } + else + { + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + } + return; + } + +// regular death + + if (self.spawnflags & 2) // statue + sound (self, CHAN_VOICE, "statue/death.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "hknight/death1.wav", 1, ATTN_NORM); + + if (random() > 0.5) + hknight_die1 (); + else + hknight_dieb1 (); +}; + + +//============================================================================ + +void() hknight_magica1 =[ $magica1, hknight_magica2 ] {ai_face();}; +void() hknight_magica2 =[ $magica2, hknight_magica3 ] {ai_face();}; +void() hknight_magica3 =[ $magica3, hknight_magica4 ] {ai_face();}; +void() hknight_magica4 =[ $magica4, hknight_magica5 ] {ai_face();}; +void() hknight_magica5 =[ $magica5, hknight_magica6 ] {ai_face();}; +void() hknight_magica6 =[ $magica6, hknight_magica7 ] {ai_face();}; +void() hknight_magica7 =[ $magica7, hknight_magica8 ] {hknight_shot(-2);}; +void() hknight_magica8 =[ $magica8, hknight_magica9 ] {hknight_shot(-1);}; +void() hknight_magica9 =[ $magica9, hknight_magica10] {hknight_shot(0);}; +void() hknight_magica10 =[ $magica10, hknight_magica11] {hknight_shot(1);}; +void() hknight_magica11 =[ $magica11, hknight_magica12] {hknight_shot(2);}; +void() hknight_magica12 =[ $magica12, hknight_magica13] {hknight_shot(3);}; +void() hknight_magica13 =[ $magica13, hknight_magica14] {ai_face();}; +void() hknight_magica14 =[ $magica14, hknight_run1 ] {ai_face();}; + +//============================================================================ + +void() hknight_magicb1 =[ $magicb1, hknight_magicb2 ] {ai_face();}; +void() hknight_magicb2 =[ $magicb2, hknight_magicb3 ] {ai_face();}; +void() hknight_magicb3 =[ $magicb3, hknight_magicb4 ] {ai_face();}; +void() hknight_magicb4 =[ $magicb4, hknight_magicb5 ] {ai_face();}; +void() hknight_magicb5 =[ $magicb5, hknight_magicb6 ] {ai_face();}; +void() hknight_magicb6 =[ $magicb6, hknight_magicb7 ] {ai_face();}; +void() hknight_magicb7 =[ $magicb7, hknight_magicb8 ] {hknight_shot(-2);}; +void() hknight_magicb8 =[ $magicb8, hknight_magicb9 ] {hknight_shot(-1);}; +void() hknight_magicb9 =[ $magicb9, hknight_magicb10] {hknight_shot(0);}; +void() hknight_magicb10 =[ $magicb10, hknight_magicb11] {hknight_shot(1);}; +void() hknight_magicb11 =[ $magicb11, hknight_magicb12] {hknight_shot(2);}; +void() hknight_magicb12 =[ $magicb12, hknight_magicb13] {hknight_shot(3);}; +void() hknight_magicb13 =[ $magicb13, hknight_run1] {ai_face();}; + +//============================================================================ + +void() hknight_magicc1 =[ $magicc1, hknight_magicc2 ] {ai_face();}; +void() hknight_magicc2 =[ $magicc2, hknight_magicc3 ] {ai_face();}; +void() hknight_magicc3 =[ $magicc3, hknight_magicc4 ] {ai_face();}; +void() hknight_magicc4 =[ $magicc4, hknight_magicc5 ] {ai_face();}; +void() hknight_magicc5 =[ $magicc5, hknight_magicc6 ] {ai_face();}; +void() hknight_magicc6 =[ $magicc6, hknight_magicc7 ] {hknight_shot(-2);}; +void() hknight_magicc7 =[ $magicc7, hknight_magicc8 ] {hknight_shot(-1);}; +void() hknight_magicc8 =[ $magicc8, hknight_magicc9 ] {hknight_shot(0);}; +void() hknight_magicc9 =[ $magicc9, hknight_magicc10] {hknight_shot(1);}; +void() hknight_magicc10 =[ $magicc10, hknight_magicc11] {hknight_shot(2);}; +void() hknight_magicc11 =[ $magicc11, hknight_run1] {hknight_shot(3);}; + +//=========================================================================== + +void() hknight_char_a1 =[ $char_a1, hknight_char_a2 ] {ai_charge(20);}; +void() hknight_char_a2 =[ $char_a2, hknight_char_a3 ] {ai_charge(25);}; +void() hknight_char_a3 =[ $char_a3, hknight_char_a4 ] {ai_charge(18);}; +void() hknight_char_a4 =[ $char_a4, hknight_char_a5 ] {ai_charge(16);}; +void() hknight_char_a5 =[ $char_a5, hknight_char_a6 ] {ai_charge(14);}; +void() hknight_char_a6 =[ $char_a6, hknight_char_a7 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a7 =[ $char_a7, hknight_char_a8 ] {ai_charge(21); ai_melee();}; +void() hknight_char_a8 =[ $char_a8, hknight_char_a9 ] {ai_charge(13); ai_melee();}; +void() hknight_char_a9 =[ $char_a9, hknight_char_a10 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a10=[ $char_a10, hknight_char_a11 ] {ai_charge(20); ai_melee();}; +void() hknight_char_a11=[ $char_a11, hknight_char_a12 ] {ai_charge(18); ai_melee();}; +void() hknight_char_a12=[ $char_a12, hknight_char_a13 ] {ai_charge(16);}; +void() hknight_char_a13=[ $char_a13, hknight_char_a14 ] {ai_charge(14);}; +void() hknight_char_a14=[ $char_a14, hknight_char_a15 ] {ai_charge(25);}; +void() hknight_char_a15=[ $char_a15, hknight_char_a16 ] {ai_charge(21);}; +void() hknight_char_a16=[ $char_a16, hknight_run1 ] {ai_charge(13);}; + +//=========================================================================== + +void() hknight_char_b1 =[ $char_b1, hknight_char_b2 ] +{CheckContinueCharge (); ai_charge(23); ai_melee();}; +void() hknight_char_b2 =[ $char_b2, hknight_char_b3 ] {ai_charge(17); ai_melee();}; +void() hknight_char_b3 =[ $char_b3, hknight_char_b4 ] {ai_charge(12); ai_melee();}; +void() hknight_char_b4 =[ $char_b4, hknight_char_b5 ] {ai_charge(22); ai_melee();}; +void() hknight_char_b5 =[ $char_b5, hknight_char_b6 ] {ai_charge(18); ai_melee();}; +void() hknight_char_b6 =[ $char_b6, hknight_char_b1 ] {ai_charge(8); ai_melee();}; + +//=========================================================================== + +void() hknight_slice1 =[ $slice1, hknight_slice2 ] {ai_charge(9);}; +void() hknight_slice2 =[ $slice2, hknight_slice3 ] {ai_charge(6);}; +void() hknight_slice3 =[ $slice3, hknight_slice4 ] {ai_charge(13);}; +void() hknight_slice4 =[ $slice4, hknight_slice5 ] {ai_charge(4);}; +void() hknight_slice5 =[ $slice5, hknight_slice6 ] {ai_charge(7); ai_melee();}; +void() hknight_slice6 =[ $slice6, hknight_slice7 ] {ai_charge(15); ai_melee();}; +void() hknight_slice7 =[ $slice7, hknight_slice8 ] {ai_charge(8); ai_melee();}; +void() hknight_slice8 =[ $slice8, hknight_slice9 ] {ai_charge(2); ai_melee();}; +void() hknight_slice9 =[ $slice9, hknight_slice10 ] {ai_melee();}; +void() hknight_slice10 =[ $slice10, hknight_run1 ] {ai_charge(3);}; + +//=========================================================================== + +void() hknight_smash1 =[ $smash1, hknight_smash2 ] {ai_charge(1);}; +void() hknight_smash2 =[ $smash2, hknight_smash3 ] {ai_charge(13);}; +void() hknight_smash3 =[ $smash3, hknight_smash4 ] {ai_charge(9);}; +void() hknight_smash4 =[ $smash4, hknight_smash5 ] {ai_charge(11);}; +void() hknight_smash5 =[ $smash5, hknight_smash6 ] {ai_charge(10); ai_melee();}; +void() hknight_smash6 =[ $smash6, hknight_smash7 ] {ai_charge(7); ai_melee();}; +void() hknight_smash7 =[ $smash7, hknight_smash8 ] {ai_charge(12); ai_melee();}; +void() hknight_smash8 =[ $smash8, hknight_smash9 ] {ai_charge(2); ai_melee();}; +void() hknight_smash9 =[ $smash9, hknight_smash10 ] {ai_charge(3); ai_melee();}; +void() hknight_smash10 =[ $smash10, hknight_smash11 ] {ai_charge(0);}; +void() hknight_smash11 =[ $smash11, hknight_run1 ] {ai_charge(0);}; + +//============================================================================ + +void() hknight_watk1 =[ $w_attack1, hknight_watk2 ] {ai_charge(2);}; +void() hknight_watk2 =[ $w_attack2, hknight_watk3 ] {ai_charge(0);}; +void() hknight_watk3 =[ $w_attack3, hknight_watk4 ] {ai_charge(0);}; +void() hknight_watk4 =[ $w_attack4, hknight_watk5 ] {ai_melee();}; +void() hknight_watk5 =[ $w_attack5, hknight_watk6 ] {ai_melee();}; +void() hknight_watk6 =[ $w_attack6, hknight_watk7 ] {ai_melee();}; +void() hknight_watk7 =[ $w_attack7, hknight_watk8 ] {ai_charge(1);}; +void() hknight_watk8 =[ $w_attack8, hknight_watk9 ] {ai_charge(4);}; +void() hknight_watk9 =[ $w_attack9, hknight_watk10 ] {ai_charge(5);}; +void() hknight_watk10 =[ $w_attack10, hknight_watk11 ] {ai_charge(3); ai_melee();}; +void() hknight_watk11 =[ $w_attack11, hknight_watk12 ] {ai_charge(2); ai_melee();}; +void() hknight_watk12 =[ $w_attack12, hknight_watk13 ] {ai_charge(2); ai_melee();}; +void() hknight_watk13 =[ $w_attack13, hknight_watk14 ] {ai_charge(0);}; +void() hknight_watk14 =[ $w_attack14, hknight_watk15 ] {ai_charge(0);}; +void() hknight_watk15 =[ $w_attack15, hknight_watk16 ] {ai_charge(0);}; +void() hknight_watk16 =[ $w_attack16, hknight_watk17 ] {ai_charge(1);}; +void() hknight_watk17 =[ $w_attack17, hknight_watk18 ] {ai_charge(1); ai_melee();}; +void() hknight_watk18 =[ $w_attack18, hknight_watk19 ] {ai_charge(3); ai_melee();}; +void() hknight_watk19 =[ $w_attack19, hknight_watk20 ] {ai_charge(4); ai_melee();}; +void() hknight_watk20 =[ $w_attack20, hknight_watk21 ] {ai_charge(6);}; +void() hknight_watk21 =[ $w_attack21, hknight_watk22 ] {ai_charge(7);}; +void() hknight_watk22 =[ $w_attack22, hknight_run1 ] {ai_charge(3);}; + +//============================================================================ + +void() hk_idle_sound = +{ + if (random() < 0.2) + { + if (self.spawnflags & 2) // statue + sound (self, CHAN_VOICE, "statue/idle.wav", 1, ATTN_IDLE); + else + sound (self, CHAN_VOICE, "hknight/idle.wav", 1, ATTN_NORM); + } +}; + +void(entity attacker, float damage) hknight_pain = +{ + if (self.pain_finished > time) + return; + + if (self.spawnflags & 2) // statue + sound (self, CHAN_VOICE, "statue/pain.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "hknight/pain1.wav", 1, ATTN_NORM); + + if (time - self.pain_finished > 5) + { // allways go into pain frame if it has been a while + hknight_pain1 (); + self.pain_finished = time + 1; + return; + } + + if ((random()*30 > damage) ) + return; // didn't flinch + + self.pain_finished = time + 1; + hknight_pain1 (); +}; + +float hknight_type; + +void() hknight_melee = +{ + hknight_type = hknight_type + 1; + + sound (self, CHAN_WEAPON, "hknight/slash1.wav", 1, ATTN_NORM); + if (hknight_type == 1) + hknight_slice1 (); + else if (hknight_type == 2) + hknight_smash1 (); + else if (hknight_type == 3) + { + hknight_watk1 (); + hknight_type = 0; + } +}; + +void() hknight_pause = +{ + sound ( self, CHAN_VOICE, "statue/see.wav", 1, ATTN_NORM); + + self.takedamage = DAMAGE_AIM; + self.th_stand = hknight_stand1; + self.th_walk = hknight_walk1; + self.th_run = hknight_run1; + self.th_melee = hknight_melee; + self.th_missile = hknight_magicc1; + self.th_pain = hknight_pain; + self.th_die = hknight_die; + self.use = SUB_Null; + + self.enemy = activator; + self.nextthink = time + 0.1; + self.think = FoundTarget; + + self.delay = 0; +}; + +void() hknight_pause1 = +{ + self.takedamage = DAMAGE_NO; + self.use = hknight_pause; + self.think = hknight_pause1; + self.nextthink = time + 0.1; +}; + +/*QUAKED monster_hell_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush Statue + +Statue - select this to use the stone skin and have him frozen in place. + +delay: time from sight to attack (default 5) +frame: frame to be frozen in. (default 73) +*/ +void() monster_hell_knight = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/hknight.mdl"); + precache_model2 ("progs/k_spike.mdl"); + precache_model2 ("progs/h_hellkn.mdl"); + + if (self.spawnflags & 2) // statue + { + precache_model ( "progs/statgib1.mdl"); + precache_model ( "progs/statgib2.mdl"); + precache_model ( "progs/statgib3.mdl"); + precache_sound ( "statue/see.wav"); + precache_sound ( "statue/idle.wav"); + precache_sound ( "statue/pain.wav"); + precache_sound ( "statue/death.wav"); + } + else + { + precache_sound2 ("hknight/death1.wav"); + precache_sound2 ("hknight/pain1.wav"); + precache_sound2 ("hknight/sight1.wav"); + precache_sound2 ("hknight/idle.wav"); + + } + + precache_sound2 ("hknight/attack1.wav"); + precache_sound ("hknight/hit.wav"); // used by C code, so don't sound2 + precache_sound2 ("hknight/slash1.wav"); + precache_sound2 ("hknight/grunt.wav"); + + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/hknight.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 250; + + if (self.spawnflags & 2) // statue + { + if (self.frame == 0) + self.frame = 73; + self.skin = 1; + + if (!self.targetname) + { + dprint ("statue not targeted\n"); + dprint (self.classname); + dprint (" at "); + dprint (vtos(self.origin)); + dprint ("\n"); + return; + } + + self.use = hknight_pause; + self.th_stand = hknight_pause1; + self.th_walk = hknight_pause1; + self.th_run = hknight_pause1; + walkmonster_start (); + } + else + { + self.th_stand = hknight_stand1; + self.th_walk = hknight_walk1; + self.th_run = hknight_run1; + self.th_melee = hknight_melee; + self.th_missile = hknight_magicc1; + self.th_pain = hknight_pain; + self.th_die = hknight_die; + + walkmonster_start (); + } +}; diff --git a/quakec_rogue/invis_sw.qc b/quakec_rogue/invis_sw.qc new file mode 100644 index 0000000..0566612 --- /dev/null +++ b/quakec_rogue/invis_sw.qc @@ -0,0 +1,203 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SWORD + +============================================================================== +*/ + +$cd /qwork/xpack/models/sword +$origin 0 0 24 +$base base +$skin badass3 + +$frame stand1 +//$stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame runb1 runb2 runb3 runb4 runb5 runb6 runb7 runb8 + +//frame runc1 runc2 runc3 runc4 runc5 runc6 + +//$frame runattack1 runattack2 runattack3 runattack4 runattack5 +//$frame runattack6 runattack7 runattack8 runattack9 runattack10 +//$frame runattack11 + +//$frame pain1 pain2 pain3 + +//$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 +//$frame painb10 painb11 + +//frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 +//frame attack8 attack9 attack10 attack11 + +$frame attackb1 attackb2 attackb3 attackb4 attackb5 +$frame attackb6 attackb7 attackb8 attackb9 attackb10 + +//$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +//$frame walk10 walk11 walk12 walk13 walk14 + +//$frame kneel1 kneel2 kneel3 kneel4 kneel5 + +//$frame standing2 standing3 standing4 standing5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 deathb10 deathb11 + +void() sword_stand1 =[ $stand1, sword_stand1 ] {ai_stand();}; + +/* +void() sword_walk1 =[ $walk1, sword_walk2 ] {ai_walk(4);}; +void() sword_walk2 =[ $walk2, sword_walk3 ] {ai_walk(4);}; +void() sword_walk3 =[ $walk3, sword_walk4 ] {ai_walk(4);}; +void() sword_walk4 =[ $walk4, sword_walk5 ] {ai_walk(4);}; +void() sword_walk5 =[ $walk5, sword_walk6 ] {ai_walk(4);}; +void() sword_walk6 =[ $walk6, sword_walk7 ] {ai_walk(4);}; +void() sword_walk7 =[ $walk7, sword_walk8 ] {ai_walk(4);}; +void() sword_walk8 =[ $walk8, sword_walk9 ] {ai_walk(4);}; +void() sword_walk9 =[ $walk9, sword_walk10 ] {ai_walk(4);}; +void() sword_walk10 =[ $walk10, sword_walk11 ] {ai_walk(4);}; +void() sword_walk11 =[ $walk11, sword_walk12 ] {ai_walk(4);}; +void() sword_walk12 =[ $walk12, sword_walk13 ] {ai_walk(4);}; +void() sword_walk13 =[ $walk13, sword_walk14 ] {ai_walk(4);}; +void() sword_walk14 =[ $walk14, sword_walk1 ] {ai_walk(4);}; +*/ + +void() sword_run1 =[ $runb1, sword_run2 ] + {self.effects = EF_DIMLIGHT; ai_run(14);}; +void() sword_run2 =[ $runb2, sword_run3 ] {ai_run(14);}; +void() sword_run3 =[ $runb3, sword_run4 ] {ai_run(14);}; +void() sword_run4 =[ $runb4, sword_run5 ] {ai_run(14);}; +void() sword_run5 =[ $runb5, sword_run6 ] {ai_run(14);}; +void() sword_run6 =[ $runb6, sword_run7 ] {ai_run(14);}; +void() sword_run7 =[ $runb7, sword_run8 ] {ai_run(14);}; +void() sword_run8 =[ $runb8, sword_run1 ] {ai_run(14);}; + +void() sword_atk1 =[ $attackb1, sword_atk2 ] + {sound (self,0,"knight/sword1.wav", 1, 1);ai_charge(14);}; +void() sword_atk2 =[ $attackb2, sword_atk3 ] {ai_charge(14);}; +void() sword_atk3 =[ $attackb3, sword_atk4 ] {ai_charge(14);}; +void() sword_atk4 =[ $attackb4, sword_atk5 ] {ai_charge(14);}; +void() sword_atk5 =[ $attackb5, sword_atk6 ] {ai_melee();}; +void() sword_atk6 =[ $attackb6, sword_atk7 ] {ai_melee();}; +void() sword_atk7 =[ $attackb7, sword_atk8 ] {ai_melee();}; +void() sword_atk8 =[ $attackb8, sword_atk9 ] {ai_charge(14);}; +void() sword_atk9 =[ $attackb9, sword_atk10] {ai_charge(14);}; +void() sword_atk10=[ $attackb10, sword_run1 ] {ai_charge(14);}; + +//=========================================================================== + +void() sword_die1 =[ $death1, sword_die2 ] {}; +void() sword_die2 =[ $death2, sword_die3 ] {}; +void() sword_die3 =[ $death3, sword_die4 ] + {self.solid = SOLID_NOT;}; +void() sword_die4 =[ $death4, sword_die5 ] {}; +void() sword_die5 =[ $death5, sword_die6 ] {}; +void() sword_die6 =[ $death6, sword_die7 ] {}; +void() sword_die7 =[ $death7, sword_die8 ] + { sound (self, 1, "player/axhit2.wav", 0.5, ATTN_NORM);}; +void() sword_die8 =[ $death8, sword_die9 ] {}; +void() sword_die9 =[ $death9, sword_die10] {}; +void() sword_die10=[ $death10, sword_die10] {}; + +void() sword_dieb1 =[ $deathb1, sword_dieb2 ] {}; +void() sword_dieb2 =[ $deathb2, sword_dieb3 ] {}; +void() sword_dieb3 =[ $deathb3, sword_dieb4 ] + {self.solid = SOLID_NOT;}; +void() sword_dieb4 =[ $deathb4, sword_dieb5 ] {}; +void() sword_dieb5 =[ $deathb5, sword_dieb6 ] {}; +void() sword_dieb6 =[ $deathb6, sword_dieb7 ] {}; +void() sword_dieb7 =[ $deathb7, sword_dieb8 ] + { sound (self, 1, "player/axhit2.wav", 0.5, ATTN_NORM); }; +void() sword_dieb8 =[ $deathb8, sword_dieb9 ] {}; +void() sword_dieb9 =[ $deathb9, sword_dieb10] {}; +void() sword_dieb10 = [ $deathb10, sword_dieb11] {}; +void() sword_dieb11 = [ $deathb11, sword_dieb11] {}; + +void() sword_die = +{ +// regular death + self.effects=0; + if (random() < 0.5) + sword_die1 (); + else + sword_dieb1 (); +}; + +void() sword_pause = +{ + self.th_run = sword_run1; + self.think = sword_run1; + self.nextthink = time + self.delay; + self.delay = 0; +}; + +void() sword_pain = +{ + self.th_run = sword_run1; + self.think = sword_run1; + self.nextthink = time + 0.1; + self.delay = 0; + self.th_pain = SUB_Null; +}; + +/*QUAKED monster_sword (1 0 0) (-16 -16 -24) (16 16 40) Ambush +The Invisible Swordsman + +delay - seconds after first sighting before it attacks (default 10) +*/ +void() monster_sword = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/sword.mdl"); + + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/ksight.wav"); + precache_sound ("player/axhit2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/sword.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 150; // 75; + + if ( !self.delay) + self.delay = 10; + + self.th_stand = sword_stand1; +// self.th_walk = sword_walk1; + self.th_walk = sword_stand1; + self.th_run = sword_pause; + self.th_melee = sword_atk1; + self.th_die = sword_die; + self.th_pain = sword_pain; + + walkmonster_start (); +}; diff --git a/quakec_rogue/items.qc b/quakec_rogue/items.qc new file mode 100644 index 0000000..b4f8c4c --- /dev/null +++ b/quakec_rogue/items.qc @@ -0,0 +1,1753 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +void() W_SetCurrentAmmo; +/* ALL LIGHTS SHOULD BE 0 1 0 IN COLOR ALL OTHER ITEMS SHOULD +BE .8 .3 .4 IN COLOR */ + + +void() SUB_regen = +{ + self.model = self.mdl; // restore original model + self.solid = SOLID_TRIGGER; // allow it to be touched again + sound (self, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); // play respawn sound + setorigin (self, self.origin); +}; + + + +/*QUAKED noclass (0 0 0) (-8 -8 -8) (8 8 8) +prints a warning message when spawned +*/ +void() noclass = +{ + dprint ("noclass spawned at"); + dprint (vtos(self.origin)); + dprint ("\n"); + remove (self); +}; + + + +/* +============ +PlaceItem + +plants the object on the floor +============ +*/ +void() PlaceItem = +{ + local float oldz; + + self.mdl = self.model; // so it can be restored on respawn + self.flags = FL_ITEM; // make extra wide + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_TOSS; + self.velocity = '0 0 0'; + self.origin_z = self.origin_z + 6; + oldz = self.origin_z; + if (!droptofloor()) + { + dprint ("Bonus item fell out of level!\n"); + dprint (self.classname); + dprint (" at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + return; + } +}; + +/* +============ +StartItem + +Sets the clipping size and plants the object on the floor +============ +*/ +void() StartItem = +{ + self.nextthink = time + 0.2; // items start after other solids + self.think = PlaceItem; +}; + +/* +========================================================================= + +HEALTH BOX + +========================================================================= +*/ +// +// T_Heal: add health to an entity, limiting health to max_health +// "ignore" will ignore max_health limit +// +float (entity e, float healamount, float ignore) T_Heal = +{ + if (e.health <= 0) + return 0; + if ((!ignore) && (e.health >= other.max_health)) + return 0; + healamount = ceil(healamount); + + e.health = e.health + healamount; + if ((!ignore) && (e.health >= other.max_health)) + e.health = other.max_health; + + if (e.health > 250) + e.health = 250; + return 1; +}; + +/*QUAKED item_health (.3 .3 1) (0 0 0) (32 32 32) rotten megahealth +Health box. Normally gives 25 points. +Rotten box heals 5-10 points, +megahealth will add 100 health, then +rot you down to your maximum health limit, +one point per second. +*/ + +float H_ROTTEN = 1; +float H_MEGA = 2; +.float healamount, healtype; +void() health_touch; +void() item_megahealth_rot; + +void() item_health = +{ + self.touch = health_touch; + + if (self.spawnflags & H_ROTTEN) + { + precache_model("maps/b_bh10.bsp"); + + precache_sound("items/r_item1.wav"); + setmodel(self, "maps/b_bh10.bsp"); + self.noise = "items/r_item1.wav"; + self.healamount = 15; + self.healtype = 0; + } + else + if (self.spawnflags & H_MEGA) + { + precache_model("maps/b_bh100.bsp"); + precache_sound("items/r_item2.wav"); + setmodel(self, "maps/b_bh100.bsp"); + self.noise = "items/r_item2.wav"; + self.healamount = 100; + self.healtype = 2; + } + else + { + precache_model("maps/b_bh25.bsp"); + precache_sound("items/health1.wav"); + setmodel(self, "maps/b_bh25.bsp"); + self.noise = "items/health1.wav"; + self.healamount = 25; + self.healtype = 1; + } + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +void() health_touch = +{ + local float amount; + local string s; + + if (other.classname != "player") + return; + + if (self.healtype == 2) // Megahealth? Ignore max_health... + { + if (other.health >= 250) + return; + if (!T_Heal(other, self.healamount, 1)) + return; + } + else + { + if (!T_Heal(other, self.healamount, 0)) + return; + } + + sprint(other, "$qc_item_health", ftos(self.healamount)); + +// health touch sound + sound(other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + + stuffcmd (other, "bf\n"); + + self.model = string_null; + self.solid = SOLID_NOT; + + // Megahealth = rot down the player's super health + if (self.healtype == 2 && !RuneHasElder(other)) + { + other.items2 = other.items2 | IT2_SUPERHEALTH; + self.nextthink = time + 5; + self.think = item_megahealth_rot; + self.owner = other; + } + else + { + if (deathmatch != 2) // deathmatch 2 is the silly old rules + { + if (deathmatch) + self.nextthink = time + 20; + self.think = SUB_regen; + } + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + +void() item_megahealth_rot = +{ + other = self.owner; + + if (other.health > other.max_health) + { + other.health = other.health - 1; + self.nextthink = time + 1; + return; + } + +// it is possible for a player to die and respawn between rots, so don't +// just blindly subtract the flag off + other.items2 = other.items2 - (other.items2 & IT2_SUPERHEALTH); + + if (deathmatch && deathmatch != 2) // deathmatch 2 is silly old rules + { + self.nextthink = time + 20; + self.think = SUB_regen; + } +}; + +/* +=============================================================================== + +ARMOR + +=============================================================================== +*/ + +void() armor_touch; + +void() armor_touch = +{ + local float type, value, bit; + + if (other.health <= 0) + return; + if (other.classname != "player") + return; + + if (self.classname == "item_armor1") + { + type = 0.3; + value = 100; + bit = IT2_ARMOR1; + } + if (self.classname == "item_armor2") + { + type = 0.6; + value = 150; + bit = IT2_ARMOR2; + } + if (self.classname == "item_armorInv") + { + type = 0.8; + value = 200; + bit = IT2_ARMOR3; + } + if (other.armortype*other.armorvalue >= type*value) + return; + + other.armortype = type; + other.armorvalue = value; + other.items2 = other.items2 - + (other.items2 & (IT2_ARMOR1 | IT2_ARMOR2 | IT2_ARMOR3)) + bit; + + self.solid = SOLID_NOT; + self.model = string_null; + if (deathmatch && deathmatch != 2) + self.nextthink = time + 20; + self.think = SUB_regen; + + sprint(other, "$qc_item_armor"); +// armor touch sound + sound(other, CHAN_ITEM, "items/armor1.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_armor1 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armor1 = +{ + self.touch = armor_touch; + self.armortype = 0.3; + self.armorvalue = 100; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 0; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED item_armor2 (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armor2 = +{ + self.touch = armor_touch; + self.armortype = 0.6; + self.armorvalue = 150; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 1; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED item_armorInv (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() item_armorInv = +{ + self.touch = armor_touch; + self.armortype = 0.8; + self.armorvalue = 200; + precache_model ("progs/armor.mdl"); + setmodel (self, "progs/armor.mdl"); + self.skin = 2; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/* +=============================================================================== + +WEAPONS + +=============================================================================== +*/ + +void() bound_other_ammo = +{ + if (other.ammo_shells1 > 100) + other.ammo_shells1 = 100; + if (other.ammo_nails1 > 200) + other.ammo_nails1 = 200; + if (other.ammo_rockets1 > 100) + other.ammo_rockets1 = 100; + if (other.ammo_cells1 > 100) + other.ammo_cells1 = 100; + + // PGM addition + if (other.ammo_lava_nails > 200) + other.ammo_lava_nails = 200; + if (other.ammo_multi_rockets > 100) + other.ammo_multi_rockets = 100; + if (other.ammo_plasma > 100) + other.ammo_plasma = 100; +}; + + +float(float w) RankForWeapon = +{ + if (w == IT_PLASMA_GUN) + return 1; + if (w == IT_LIGHTNING) + return 2; + if (w == IT_MULTI_ROCKET) + return 3; + if (w == IT_ROCKET_LAUNCHER) + return 4; + if (w == IT_LAVA_SUPER_NAILGUN) + return 5; + if (w == IT_SUPER_NAILGUN) + return 6; + if (w == IT_MULTI_GRENADE) + return 7; + if (w == IT_GRENADE_LAUNCHER) + return 8; + if (w == IT_LAVA_NAILGUN) + return 9; + if (w == IT_SUPER_SHOTGUN) + return 10; + if (w == IT_NAILGUN) + return 11; + return 12; +}; + +/* +============= +Deathmatch_Weapon + +Deathmatch weapon change rules for picking up a weapon + +.float ammo_shells1, ammo_nails1, ammo_rockets1, ammo_cells1; +============= +*/ +void(float old, float new) Deathmatch_Weapon = +{ + local float or, nr; + + if ( self.flags & FL_ISBOT ) { + return; + } // mal: let bots pick their own weapon. + +// PGM - fix for changing to weapons when grappling. + if (self.weapon == IT_GRAPPLE && self.button0) + return; + +// change self.weapon if desired + or = RankForWeapon (self.weapon); + nr = RankForWeapon (new); + if ( nr < or ) + self.weapon = new; +}; + +void(entity comboOwner) EnableComboWeapons = +{ + if (comboOwner.classname != "player") + return; + + if (!(comboOwner.items & IT_LAVA_NAILGUN)) + { + if (comboOwner.ammo_lava_nails > 0 && (comboOwner.items & IT_NAILGUN)) + { + sprint (comboOwner, "$qc_lava_enabled"); + comboOwner.items = comboOwner.items | IT_LAVA_NAILGUN; + } + } + + if (!(comboOwner.items & IT_LAVA_SUPER_NAILGUN)) + { + if (comboOwner.ammo_lava_nails > 0 && + (comboOwner.items & IT_SUPER_NAILGUN)) + { + sprint (comboOwner, "$qc_super_lava_enabled"); + comboOwner.items = comboOwner.items | IT_LAVA_SUPER_NAILGUN; + } + } + + if (!(comboOwner.items & IT_MULTI_GRENADE)) + { + if (comboOwner.ammo_multi_rockets > 0 && + (comboOwner.items & IT_GRENADE_LAUNCHER)) + { + sprint (comboOwner, "$qc_multi_gl_enabled"); + comboOwner.items = comboOwner.items | IT_MULTI_GRENADE; + } + } + + if (!(comboOwner.items & IT_MULTI_ROCKET)) + { + if (comboOwner.ammo_multi_rockets > 0 && + (comboOwner.items & IT_ROCKET_LAUNCHER)) + { + sprint (comboOwner, "$qc_multi_rl_enabled"); + comboOwner.items = comboOwner.items | IT_MULTI_ROCKET; + } + } + + if (!(comboOwner.items & IT_PLASMA_GUN)) + { + if (comboOwner.ammo_plasma > 0 && (comboOwner.items & IT_LIGHTNING)) + { + sprint (comboOwner, "$qc_plasma_enabled"); + comboOwner.items = comboOwner.items | IT_PLASMA_GUN; + } + } +}; + +/* +============= +weapon_touch +============= +*/ +float() W_BestWeapon; +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon; + +void() weapon_touch = +{ + local float hadammo, best, new, old; + local entity stemp; + local float leave; + + if (!(other.flags & FL_CLIENT)) + return; + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + if (coop || deathmatch == 2 || deathmatch == 3 || deathmatch == 5) + leave = 1; + else + leave = 0; + + if (self.classname == "weapon_nailgun") + { + if (leave && (other.items & IT_NAILGUN) ) + return; + hadammo = other.ammo_nails1; + new = IT_NAILGUN; + if (leave) + other.ammo_lava_nails = other.ammo_lava_nails + 20; + other.ammo_nails1 = other.ammo_nails1 + 30; + } + else if (self.classname == "weapon_supernailgun") + { + if (leave && (other.items & IT_SUPER_NAILGUN) ) + return; + hadammo = other.ammo_rockets1; + new = IT_SUPER_NAILGUN; + if (leave) + other.ammo_lava_nails = other.ammo_lava_nails + 20; + other.ammo_nails1 = other.ammo_nails1 + 30; + } + else if (self.classname == "weapon_supershotgun") + { + if (leave && (other.items & IT_SUPER_SHOTGUN) ) + return; + hadammo = other.ammo_rockets1; + new = IT_SUPER_SHOTGUN; + other.ammo_shells1 = other.ammo_shells1 + 5; + } + else if (self.classname == "weapon_rocketlauncher") + { + if (leave && (other.items & IT_ROCKET_LAUNCHER) ) + return; + hadammo = other.ammo_rockets1; + new = IT_ROCKET_LAUNCHER; + if (leave) + other.ammo_multi_rockets = other.ammo_multi_rockets + 3; + other.ammo_rockets1 = other.ammo_rockets1 + 5; + } + else if (self.classname == "weapon_grenadelauncher") + { + if (leave && (other.items & IT_GRENADE_LAUNCHER) ) + return; + hadammo = other.ammo_rockets1; + new = IT_GRENADE_LAUNCHER; + if (leave) + other.ammo_multi_rockets = other.ammo_multi_rockets + 3; + other.ammo_rockets1 = other.ammo_rockets1 + 5; + } + else if (self.classname == "weapon_lightning") + { + if (leave && (other.items & IT_LIGHTNING) ) + return; + hadammo = other.ammo_rockets1; + new = IT_LIGHTNING; + if (leave) + other.ammo_plasma = other.ammo_plasma + 5; + other.ammo_cells1 = other.ammo_cells1 + 15; + } + else + objerror ("weapon_touch: unknown classname"); + + sprint(other, "$qc_got_item", self.netname); +// weapon touch sound + sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + bound_other_ammo (); + +// change to the weapon + old = other.items; + other.items = other.items | new; + + EnableComboWeapons ( other ); + + // PGM - fake picking up the plasma gun. + if (new == IT_LIGHTNING) + { + if (other.ammo_plasma > 0) + new = IT_PLASMA_GUN; + } + else if (new == IT_ROCKET_LAUNCHER) + { + if (other.ammo_multi_rockets > 0) + new = IT_MULTI_ROCKET; + } + else if (new == IT_GRENADE_LAUNCHER) + { + if (other.ammo_multi_rockets > 0) + new = IT_MULTI_GRENADE; + } + else if (new == IT_SUPER_NAILGUN) + { + if (other.ammo_lava_nails > 1) + new = IT_LAVA_SUPER_NAILGUN; + } + else if (new == IT_NAILGUN) + { + if (other.ammo_lava_nails > 0) + new = IT_LAVA_NAILGUN; + } + + + stemp = self; + self = other; + + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (!deathmatch) + self.weapon = new; + else + Deathmatch_Weapon (old, new); + } + + UpdateAmmoCounts ( self ); + W_SetCurrentAmmo(); + + self = stemp; + + if (leave) + return; + +// remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + if (deathmatch && deathmatch != 2) + self.nextthink = time + 30; + self.think = SUB_regen; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED weapon_supershotgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_supershotgun = +{ + precache_model ("progs/g_shot.mdl"); + setmodel (self, "progs/g_shot.mdl"); + self.weapon = IT_SUPER_SHOTGUN; + self.netname = "$qc_double_shotgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_nailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_nailgun = +{ + precache_model ("progs/g_nail.mdl"); + setmodel (self, "progs/g_nail.mdl"); + self.weapon = IT_NAILGUN; + self.netname = "$qc_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_supernailgun (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_supernailgun = +{ + precache_model ("progs/g_nail2.mdl"); + setmodel (self, "progs/g_nail2.mdl"); + self.weapon = IT_SUPER_NAILGUN; + self.netname = "$qc_super_nailgun"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_grenadelauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_grenadelauncher = +{ + precache_model ("progs/g_rock.mdl"); + setmodel (self, "progs/g_rock.mdl"); + self.weapon = IT_GRENADE_LAUNCHER; + self.netname = "$qc_grenade_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + +/*QUAKED weapon_rocketlauncher (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_rocketlauncher = +{ + precache_model ("progs/g_rock2.mdl"); + setmodel (self, "progs/g_rock2.mdl"); + self.weapon = IT_ROCKET_LAUNCHER; + self.netname = "$qc_rocket_launcher"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/*QUAKED weapon_lightning (0 .5 .8) (-16 -16 0) (16 16 32) +*/ + +void() weapon_lightning = +{ + precache_model ("progs/g_light.mdl"); + setmodel (self, "progs/g_light.mdl"); + self.weapon = IT_LIGHTNING; + self.netname = "$qc_thunderbolt"; + self.touch = weapon_touch; + setsize (self, '-16 -16 0', '16 16 56'); + StartItem (); +}; + + +/* +=============================================================================== + +AMMO + +=============================================================================== +*/ + +void() ammo_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + +// if the player was using his best weapon, change up to the new one if better + + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + +// by this point, other is the player and self is the ammo pack. - PGM + +// shotgun + if (self.weapon == 1) + { + if (other.ammo_shells1 >= 100) + return; + other.ammo_shells1 = other.ammo_shells1 + self.aflag; + } + +// spikes + if (self.weapon == 2) + { + if (other.ammo_nails1 >= 200) + return; + other.ammo_nails1 = other.ammo_nails1 + self.aflag; + } + +// rockets + if (self.weapon == 3) + { + if (other.ammo_rockets1 >= 100) + return; + other.ammo_rockets1 = other.ammo_rockets1 + self.aflag; + } + +// cells + if (self.weapon == 4) + { + if (other.ammo_cells1 >= 100) + return; + other.ammo_cells1 = other.ammo_cells1 + self.aflag; + } + +// lava spikes + if (self.weapon == 5) + { + if(other.ammo_lava_nails >= 200) + return; + other.ammo_lava_nails = other.ammo_lava_nails + self.aflag; + } + +// multi grenades + if (self.weapon == 6) + { + if(other.ammo_multi_rockets >= 100) + return; + other.ammo_multi_rockets = other.ammo_multi_rockets + self.aflag; + } + +// plasma + if (self.weapon == 7) + { + if(other.ammo_plasma >= 100) + return; + other.ammo_plasma = other.ammo_plasma + self.aflag; + } + + EnableComboWeapons ( other ); + UpdateAmmoCounts ( other ); + + bound_other_ammo (); + + sprint(other, "$qc_got_item", self.netname); + +// ammo touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + +// change to a better weapon if appropriate + + if ( other.weapon == best && (W_WantsToChangeWeapon(other, 0, 1) == 1)) + { + stemp = self; + self = other; + self.weapon = W_BestWeapon(); + W_SetCurrentAmmo (); + self = stemp; + } + +// if changed current ammo, update it + stemp = self; + self = other; + W_SetCurrentAmmo(); + self = stemp; + +// remove it in single player, or setup for respawning in deathmatch + self.model = string_null; + self.solid = SOLID_NOT; + + if (deathmatch) { + if (deathmatch == 3 || deathmatch == 5) { + self.nextthink = time + 15; + } else if (deathmatch != 2) { + self.nextthink = time + 30; + } + } + + self.think = SUB_regen; + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + + + +float WEAPON_BIG2 = 1; + +/*QUAKED item_shells (0 .5 .8) (0 0 0) (32 32 32) big +Small box is 20, Big box is 40. +*/ + +void() item_shells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_shell1.bsp"); + setmodel (self, "maps/b_shell1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_shell0.bsp"); + setmodel (self, "maps/b_shell0.bsp"); + self.aflag = 20; + } + self.weapon = 1; + self.netname = "$qc_shells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_spikes (0 .5 .8) (0 0 0) (32 32 32) big +Small box is 25, Big box is 50. +*/ + +void() item_spikes = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_nail1.bsp"); + setmodel (self, "maps/b_nail1.bsp"); + self.aflag = 50; + } + else + { + precache_model ("maps/b_nail0.bsp"); + setmodel (self, "maps/b_nail0.bsp"); + self.aflag = 25; + } + self.weapon = 2; + self.netname = "$qc_nails"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_lava_spikes (0 .5 .8) (0 0 0) (32 32 32) big +Small box is 25, Big box is 50. +*/ + +void() item_lava_spikes = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_lnail1.bsp"); + setmodel (self, "maps/b_lnail1.bsp"); + self.aflag = 50; + } + else + { + precache_model ("maps/b_lnail0.bsp"); + setmodel (self, "maps/b_lnail0.bsp"); + self.aflag = 25; + } + self.weapon = 5; + self.netname = "$qc_lava_nails"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_rockets (0 .5 .8) (0 0 0) (32 32 32) big +Small box is 5, Big box is 10. +*/ + +void() item_rockets = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_rock1.bsp"); + setmodel (self, "maps/b_rock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_rock0.bsp"); + setmodel (self, "maps/b_rock0.bsp"); + self.aflag = 5; + } + self.weapon = 3; + self.netname = "$qc_rockets"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_multi_rockets (0 .5 .8) (0 0 0) (32 32 32) big +Small box is 5, Big box is 10. +*/ + +void() item_multi_rockets = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_mrock1.bsp"); + setmodel (self, "maps/b_mrock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_mrock0.bsp"); + setmodel (self, "maps/b_mrock0.bsp"); + self.aflag = 5; + } + self.weapon = 6; + self.netname = "$qc_multi_rockets"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/*QUAKED item_cells (0 .5 .8) (0 0 0) (32 32 32) big +Small box is 6, Big box is 12. +*/ +void() item_cells = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_batt1.bsp"); + setmodel (self, "maps/b_batt1.bsp"); + self.aflag = 12; + } + else + { + precache_model ("maps/b_batt0.bsp"); + setmodel (self, "maps/b_batt0.bsp"); + self.aflag = 6; + } + self.weapon = 4; + self.netname = "$qc_cells"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + +/*QUAKED item_plasma (0 .5 .8) (0 0 0) (32 32 32) big +Small box is 6, Big box is 12. +*/ +void() item_plasma = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_BIG2) + { + precache_model ("maps/b_plas1.bsp"); + setmodel (self, "maps/b_plas1.bsp"); + self.aflag = 12; + } + else + { + precache_model ("maps/b_plas0.bsp"); + setmodel (self, "maps/b_plas0.bsp"); + self.aflag = 6; + } + self.weapon = 7; + self.netname = "plasma"; + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/*QUAKED item_weapon (0 .5 .8) (0 0 0) (32 32 32) shotgun rocket spikes big +DO NOT USE THIS!!!! IT WILL BE REMOVED! +*/ + +float WEAPON_SHOTGUN = 1; +float WEAPON_ROCKET = 2; +float WEAPON_SPIKES = 4; +float WEAPON_BIG = 8; +void() item_weapon = +{ + self.touch = ammo_touch; + + if (self.spawnflags & WEAPON_SHOTGUN) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_shell1.bsp"); + setmodel (self, "maps/b_shell1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_shell0.bsp"); + setmodel (self, "maps/b_shell0.bsp"); + self.aflag = 20; + } + self.weapon = 1; + self.netname = "$qc_shells"; + } + + if (self.spawnflags & WEAPON_SPIKES) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_nail1.bsp"); + setmodel (self, "maps/b_nail1.bsp"); + self.aflag = 40; + } + else + { + precache_model ("maps/b_nail0.bsp"); + setmodel (self, "maps/b_nail0.bsp"); + self.aflag = 20; + } + self.weapon = 2; + self.netname = "$qc_spikes"; + } + + if (self.spawnflags & WEAPON_ROCKET) + { + if (self.spawnflags & WEAPON_BIG) + { + precache_model ("maps/b_rock1.bsp"); + setmodel (self, "maps/b_rock1.bsp"); + self.aflag = 10; + } + else + { + precache_model ("maps/b_rock0.bsp"); + setmodel (self, "maps/b_rock0.bsp"); + self.aflag = 5; + } + self.weapon = 3; + self.netname = "$qc_rockets"; + } + + setsize (self, '0 0 0', '32 32 56'); + StartItem (); +}; + + +/* +=============================================================================== + +KEYS + +=============================================================================== +*/ + +void() key_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (other.items & self.items) + return; + + sprint(other, "$qc_got_item", self.netname); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + other.items = other.items | self.items; + + if (!coop) + { + self.solid = SOLID_NOT; + self.model = string_null; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +void() key_setsounds = +{ + if (world.worldtype == 0) + { + precache_sound ("misc/medkey.wav"); + self.noise = "misc/medkey.wav"; + } + if (world.worldtype == 1) + { + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + } + if (world.worldtype == 2) + { + precache_sound2 ("misc/basekey.wav"); + self.noise = "misc/basekey.wav"; + } +}; + +/*QUAKED item_key1 (0 .5 .8) (-16 -16 -24) (16 16 32) +SILVER key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key1 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_s_key.mdl"); + setmodel (self, "progs/w_s_key.mdl"); + self.netname = "$qc_silver_key"; + } + else if (world.worldtype == 1) + { + precache_model ("progs/m_s_key.mdl"); + setmodel (self, "progs/m_s_key.mdl"); + self.netname = "$qc_silver_runekey"; + } + else if (world.worldtype == 2) + { + precache_model2 ("progs/b_s_key.mdl"); + setmodel (self, "progs/b_s_key.mdl"); + self.netname = "$qc_silver_keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY1; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) +GOLD key +In order for keys to work +you MUST set your maps +worldtype to one of the +following: +0: medieval +1: metal +2: base +*/ + +void() item_key2 = +{ + if (world.worldtype == 0) + { + precache_model ("progs/w_g_key.mdl"); + setmodel (self, "progs/w_g_key.mdl"); + self.netname = "$qc_gold_key"; + } + if (world.worldtype == 1) + { + precache_model ("progs/m_g_key.mdl"); + setmodel (self, "progs/m_g_key.mdl"); + self.netname = "$qc_gold_runekey"; + } + if (world.worldtype == 2) + { + precache_model2 ("progs/b_g_key.mdl"); + setmodel (self, "progs/b_g_key.mdl"); + self.netname = "$qc_gold_keycard"; + } + key_setsounds(); + self.touch = key_touch; + self.items = IT_KEY2; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + +/* +=============================================================================== + +END OF LEVEL RUNES + +=============================================================================== +*/ + +void() sigil_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + centerprint(other, "$qc_got_rune"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + self.model = string_null; + serverflags = serverflags | (self.spawnflags & 15); + self.classname = ""; // so rune doors won't find it + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_sigil (0 .5 .8) (-16 -16 -24) (16 16 32) E1 E2 E3 E4 +End of level sigil, pick up to end episode and return to jrstart. +*/ + +void() item_sigil = +{ + if (!self.spawnflags) + objerror ("no spawnflags"); + + precache_sound ("misc/runekey.wav"); + self.noise = "misc/runekey.wav"; + + if (self.spawnflags & 1) + { + precache_model ("progs/end1.mdl"); + setmodel (self, "progs/end1.mdl"); + } + if (self.spawnflags & 2) + { + precache_model2 ("progs/end2.mdl"); + setmodel (self, "progs/end2.mdl"); + } + if (self.spawnflags & 4) + { + precache_model2 ("progs/end3.mdl"); + setmodel (self, "progs/end3.mdl"); + } + if (self.spawnflags & 8) + { + precache_model2 ("progs/end4.mdl"); + setmodel (self, "progs/end4.mdl"); + } + + self.touch = sigil_touch; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/* +=============================================================================== + +POWERUPS + +=============================================================================== +*/ + +void() powerup_touch; +void() random_regen; + +void() powerup_touch = +{ +local entity stemp; +local float best; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + sprint(other, "$qc_got_item", self.netname); + + if (deathmatch) + { + self.mdl = self.model; + + if (self.classname == "item_random_powerup") + { + self.nextthink = time + 30; + self.think = random_regen; + } + else + { + if ((self.classname == "item_artifact_invulnerability") || + (self.classname == "item_artifact_invisibility")) + self.nextthink = time + 60*5; + else + self.nextthink = time + 60; + + self.think = SUB_regen; + } + } + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | self.items; + self.model = string_null; + +// do the apropriate action +// if (self.classname == "item_artifact_envirosuit") + if (self.netname == "$qc_biosuit") + { + other.rad_time = 1; + other.radsuit_finished = time + 30; + } + +// if (self.classname == "item_artifact_invulnerability") + if (self.netname == "$qc_pentagram_of_protection") + { + other.invincible_time = 1; + other.invincible_finished = time + 30; + } + +// if (self.classname == "item_artifact_invisibility") + if (self.netname == "$qc_ring_of_shadows") + { + other.invisible_time = 1; + other.invisible_finished = time + 30; + } + +// if (self.classname == "item_artifact_super_damage") + if (self.netname == "$qc_quad_damage") + { + other.super_time = 1; + other.super_damage_finished = time + 30; + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + + +/*QUAKED item_artifact_invulnerability (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invulnerable for 30 seconds +*/ +void() item_artifact_invulnerability = +{ + self.touch = powerup_touch; + + precache_model ("progs/invulner.mdl"); + precache_sound ("items/protect.wav"); + precache_sound ("items/protect2.wav"); + precache_sound ("items/protect3.wav"); + self.noise = "items/protect.wav"; + setmodel (self, "progs/invulner.mdl"); + self.netname = "$qc_pentagram_of_protection"; + self.items = IT_INVULNERABILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_artifact_envirosuit (0 .5 .8) (-16 -16 -24) (16 16 32) +Player takes no damage from water or slime for 30 seconds +*/ +void() item_artifact_envirosuit = +{ + self.touch = powerup_touch; + + precache_model ("progs/suit.mdl"); + precache_sound ("items/suit.wav"); + precache_sound ("items/suit2.wav"); + self.noise = "items/suit.wav"; + setmodel (self, "progs/suit.mdl"); + self.netname = "$qc_biosuit"; + self.items = IT_SUIT; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_invisibility (0 .5 .8) (-16 -16 -24) (16 16 32) +Player is invisible for 30 seconds +*/ +void() item_artifact_invisibility = +{ + self.touch = powerup_touch; + + precache_model ("progs/invisibl.mdl"); + precache_sound ("items/inv1.wav"); + precache_sound ("items/inv2.wav"); + precache_sound ("items/inv3.wav"); + self.noise = "items/inv1.wav"; + setmodel (self, "progs/invisibl.mdl"); + self.netname = "$qc_ring_of_shadows"; + self.items = IT_INVISIBILITY; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/*QUAKED item_artifact_super_damage (0 .5 .8) (-16 -16 -24) (16 16 32) +The next attack from the player will do 4x damage +*/ +void() item_artifact_super_damage = +{ + self.touch = powerup_touch; + + precache_model ("progs/quaddama.mdl"); + precache_sound ("items/damage.wav"); + precache_sound ("items/damage2.wav"); + precache_sound ("items/damage3.wav"); + self.noise = "items/damage.wav"; + setmodel (self, "progs/quaddama.mdl"); + self.netname = "$qc_quad_damage"; + self.items = IT_QUAD; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + +/* +=============================================================================== + +PLAYER BACKPACKS + +=============================================================================== +*/ + +void() BackpackTouch = +{ + local string s; + local float best, old, new; + local entity stemp; + local float acount; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + +//ZOID-- + // Don't let the owner pick up his own backpack for a second. + if ((other == self.owner) && ((self.nextthink - time) > 119)) + return; +//--ZOID + + acount = 0; + sprint(other, "$qc_backpack_got"); + + if (self.items) + { + if ((other.items & self.items) == 0) + { + acount = 1; + sprint(other, "{}", self.netname); + } + } + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + +// change weapons + other.ammo_shells1 = other.ammo_shells1 + self.ammo_shells1; + other.ammo_nails1 = other.ammo_nails1 + self.ammo_nails1; + other.ammo_rockets1 = other.ammo_rockets1 + self.ammo_rockets1; + other.ammo_cells1 = other.ammo_cells1 + self.ammo_cells1; + + other.ammo_lava_nails = other.ammo_lava_nails + self.ammo_lava_nails; + other.ammo_multi_rockets = other.ammo_multi_rockets + + self.ammo_multi_rockets; + other.ammo_plasma = other.ammo_plasma + self.ammo_plasma; + + new = self.items; + if (!new) + new = other.weapon; + + old = other.items; + other.items = other.items | self.items; + + bound_other_ammo (); + + if (self.ammo_shells1) + { + if (acount) + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_shells", ftos(self.ammo_shells1)); + } + if (self.ammo_nails1) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_nails", ftos(self.ammo_nails1)); + } + if (self.ammo_rockets1) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_rockets", ftos(self.ammo_rockets1)); + } + if (self.ammo_cells1) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_cells", ftos(self.ammo_cells1)); + } + if (self.ammo_lava_nails) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_lava_nails", ftos(self.ammo_lava_nails)); + } + if (self.ammo_multi_rockets) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_multi_rockets", ftos(self.ammo_multi_rockets)); + } + if (self.ammo_plasma) + { + if (acount) + sprint(other, ", "); + + acount = 1; + sprint(other, "$qc_backpack_plasma_balls", ftos(self.ammo_plasma)); + } + + sprint (other, "\n"); + +// backpack touch sound + sound (other, CHAN_ITEM, "weapons/lock4.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + +// remove the backpack, change self to the player + remove(self); + self = other; + +// fixme -- move this to after weapon choice if problematic. + EnableComboWeapons ( self ); + +// change to the weapon + if(W_WantsToChangeWeapon(other, old, other.items) == 1) + { + if (!deathmatch) + self.weapon = new; + else + Deathmatch_Weapon (old, new); + } + +// EnableComboWeapons ( self ); + UpdateAmmoCounts (self); + + W_SetCurrentAmmo (); +}; + +/* +=============== +DropBackpack +=============== +*/ +void() DropBackpack = +{ + local entity item; + + // this is to compensate for the new way we're hacking the ammo + // counts. If they're monsters, just copy shells->shells1 etc. + if (self.flags & FL_MONSTER) + { + self.ammo_shells1 = self.ammo_shells; + self.ammo_nails1 = self.ammo_nails; + self.ammo_rockets1 = self.ammo_rockets; + self.ammo_cells1 = self.ammo_cells; + } + + if (!(self.ammo_shells1 + self.ammo_nails1 + self.ammo_rockets1 + + self.ammo_cells1 + self.ammo_lava_nails + self.ammo_multi_rockets)) + return; // nothing in it + + item = spawn(); + item.origin = self.origin - '0 0 24'; + + item.items = self.weapon; + + item.classname = "item_backpack"; // added for bots. + + if (item.items == IT_AXE) + item.netname = "$qc_axe"; + else if (item.items == IT_SHOTGUN) + item.netname = "$qc_shotgun"; + else if (item.items == IT_SUPER_SHOTGUN) + item.netname = "$qc_double_shotgun"; + else if (item.items == IT_NAILGUN) + item.netname = "$qc_nailgun"; + else if (item.items == IT_SUPER_NAILGUN) + item.netname = "$qc_super_nailgun"; + else if (item.items == IT_GRENADE_LAUNCHER) + item.netname = "$qc_grenade_launcher"; + else if (item.items == IT_ROCKET_LAUNCHER) + item.netname = "$qc_rocket_launcher"; + else if (item.items == IT_LIGHTNING) + item.netname = "$qc_thunderbolt"; + else if (item.items == IT_LAVA_NAILGUN) + { + item.netname = "$qc_got_lava_nailgun"; + item.items = IT_NAILGUN; + } + else if (item.items == IT_LAVA_SUPER_NAILGUN) + { + item.netname = "$qc_got_super_lava_nailgun"; + item.items = IT_SUPER_NAILGUN; + } + else if (item.items == IT_MULTI_GRENADE) + { + item.netname = "$qc_got_multi_gl"; + item.items = IT_GRENADE_LAUNCHER; + } + else if (item.items == IT_MULTI_ROCKET) + { + item.netname = "$qc_got_multi_rl"; + item.items = IT_ROCKET_LAUNCHER; + } + else if (item.items == IT_PLASMA_GUN) + { + item.netname = "$qc_got_plasma_gun"; + item.items = IT_LIGHTNING; + } + else + item.netname = ""; + + item.ammo_shells1 = self.ammo_shells1; + item.ammo_nails1 = self.ammo_nails1; + item.ammo_rockets1 = self.ammo_rockets1; + item.ammo_cells1 = self.ammo_cells1; + item.ammo_lava_nails = self.ammo_lava_nails; + item.ammo_multi_rockets = self.ammo_multi_rockets; + item.ammo_plasma = self.ammo_plasma; + + // include some ammo in the backpack if the weapon is being given + if (item.ammo_shells1 < 5 && (item.items == IT_SHOTGUN || item.items == IT_SUPER_SHOTGUN)) { + item.ammo_shells1 = 5; + } + + if (item.ammo_nails1 < 20 && (item.items == IT_NAILGUN || item.items == IT_SUPER_NAILGUN)) { + item.ammo_nails1 = 20; + } + + if (item.ammo_rockets1 < 5 && (item.items == IT_GRENADE_LAUNCHER || item.items == IT_ROCKET_LAUNCHER)) { + item.ammo_rockets1 = 5; + } + + if (item.ammo_cells1 < 15 && (item.items == IT_LIGHTNING)) { + item.ammo_cells1 = 15; + } + + item.velocity_z = 300; + item.velocity_x = -100 + (random() * 200); + item.velocity_y = -100 + (random() * 200); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + setmodel (item, "progs/backpack.mdl"); + setsize (item, '-16 -16 0', '16 16 56'); + item.touch = BackpackTouch; + + item.nextthink = time + 120; // remove after 2 minutes + item.think = SUB_Remove; +}; diff --git a/quakec_rogue/knight.qc b/quakec_rogue/knight.qc new file mode 100644 index 0000000..5c8868b --- /dev/null +++ b/quakec_rogue/knight.qc @@ -0,0 +1,396 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +KNIGHT + +============================================================================== +*/ + +$cd /raid/quake/id1/models/knight +$origin 0 0 24 +$base base +$skin badass3 +$skin statue + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame runb1 runb2 runb3 runb4 runb5 runb6 runb7 runb8 + +//frame runc1 runc2 runc3 runc4 runc5 runc6 + +$frame runattack1 runattack2 runattack3 runattack4 runattack5 +$frame runattack6 runattack7 runattack8 runattack9 runattack10 +$frame runattack11 + +$frame pain1 pain2 pain3 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 +$frame painb10 painb11 + +//frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 +//frame attack8 attack9 attack10 attack11 + +$frame attackb1 attackb1 attackb2 attackb3 attackb4 attackb5 +$frame attackb6 attackb7 attackb8 attackb9 attackb10 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 +$frame walk10 walk11 walk12 walk13 walk14 + +$frame kneel1 kneel2 kneel3 kneel4 kneel5 + +$frame standing2 standing3 standing4 standing5 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 deathb10 deathb11 + +void() knight_stand1 =[ $stand1, knight_stand2 ] {ai_stand();}; +void() knight_stand2 =[ $stand2, knight_stand3 ] {ai_stand();}; +void() knight_stand3 =[ $stand3, knight_stand4 ] {ai_stand();}; +void() knight_stand4 =[ $stand4, knight_stand5 ] {ai_stand();}; +void() knight_stand5 =[ $stand5, knight_stand6 ] {ai_stand();}; +void() knight_stand6 =[ $stand6, knight_stand7 ] {ai_stand();}; +void() knight_stand7 =[ $stand7, knight_stand8 ] {ai_stand();}; +void() knight_stand8 =[ $stand8, knight_stand9 ] {ai_stand();}; +void() knight_stand9 =[ $stand9, knight_stand1 ] {ai_stand();}; + +void() kn_idle_sound = +{ + if (random() < 0.2) + { + if (self.spawnflags & 2) // statue + sound (self, CHAN_VOICE, "statue/idle.wav", 1, ATTN_IDLE); + else + sound (self, CHAN_VOICE, "knight/idle.wav", 1, ATTN_IDLE); + } +}; + +void() knight_walk1 =[ $walk1, knight_walk2 ] + { kn_idle_sound(); ai_walk(3);}; +void() knight_walk2 =[ $walk2, knight_walk3 ] {ai_walk(2);}; +void() knight_walk3 =[ $walk3, knight_walk4 ] {ai_walk(3);}; +void() knight_walk4 =[ $walk4, knight_walk5 ] {ai_walk(4);}; +void() knight_walk5 =[ $walk5, knight_walk6 ] {ai_walk(3);}; +void() knight_walk6 =[ $walk6, knight_walk7 ] {ai_walk(3);}; +void() knight_walk7 =[ $walk7, knight_walk8 ] {ai_walk(3);}; +void() knight_walk8 =[ $walk8, knight_walk9 ] {ai_walk(4);}; +void() knight_walk9 =[ $walk9, knight_walk10 ] {ai_walk(3);}; +void() knight_walk10 =[ $walk10, knight_walk11 ] {ai_walk(3);}; +void() knight_walk11 =[ $walk11, knight_walk12 ] {ai_walk(2);}; +void() knight_walk12 =[ $walk12, knight_walk13 ] {ai_walk(3);}; +void() knight_walk13 =[ $walk13, knight_walk14 ] {ai_walk(4);}; +void() knight_walk14 =[ $walk14, knight_walk1 ] {ai_walk(3);}; + + +void() knight_run1 =[ $runb1, knight_run2 ] + { kn_idle_sound(); ai_run(16); }; +void() knight_run2 =[ $runb2, knight_run3 ] {ai_run(20);}; +void() knight_run3 =[ $runb3, knight_run4 ] {ai_run(13);}; +void() knight_run4 =[ $runb4, knight_run5 ] {ai_run(7);}; +void() knight_run5 =[ $runb5, knight_run6 ] {ai_run(16);}; +void() knight_run6 =[ $runb6, knight_run7 ] {ai_run(20);}; +void() knight_run7 =[ $runb7, knight_run8 ] {ai_run(14);}; +void() knight_run8 =[ $runb8, knight_run1 ] {ai_run(6);}; + + +void() knight_runatk1 =[ $runattack1, knight_runatk2 ] +{ +if (random() > 0.5) + sound (self, CHAN_WEAPON, "knight/sword2.wav", 1, ATTN_NORM); +else + sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +ai_charge(20); +}; +void() knight_runatk2 =[ $runattack2, knight_runatk3 ] {ai_charge_side();}; +void() knight_runatk3 =[ $runattack3, knight_runatk4 ] {ai_charge_side();}; +void() knight_runatk4 =[ $runattack4, knight_runatk5 ] {ai_charge_side();}; +void() knight_runatk5 =[ $runattack5, knight_runatk6 ] {ai_melee_side();}; +void() knight_runatk6 =[ $runattack6, knight_runatk7 ] {ai_melee_side();}; +void() knight_runatk7 =[ $runattack7, knight_runatk8 ] {ai_melee_side();}; +void() knight_runatk8 =[ $runattack8, knight_runatk9 ] {ai_melee_side();}; +void() knight_runatk9 =[ $runattack9, knight_runatk10 ] {ai_melee_side();}; +void() knight_runatk10 =[ $runattack10, knight_runatk11 ] {ai_charge_side();}; +void() knight_runatk11 =[ $runattack11, knight_run1 ] {ai_charge(10);}; + +void() knight_atk1 =[ $attackb1, knight_atk2 ] +{ +sound (self, CHAN_WEAPON, "knight/sword1.wav", 1, ATTN_NORM); +ai_charge(0);}; +void() knight_atk2 =[ $attackb2, knight_atk3 ] {ai_charge(7);}; +void() knight_atk3 =[ $attackb3, knight_atk4 ] {ai_charge(4);}; +void() knight_atk4 =[ $attackb4, knight_atk5 ] {ai_charge(0);}; +void() knight_atk5 =[ $attackb5, knight_atk6 ] {ai_charge(3);}; +void() knight_atk6 =[ $attackb6, knight_atk7 ] {ai_charge(4); ai_melee();}; +void() knight_atk7 =[ $attackb7, knight_atk8 ] {ai_charge(1); ai_melee();}; +void() knight_atk8 =[ $attackb8, knight_atk9 ] {ai_charge(3); +ai_melee();}; +void() knight_atk9 =[ $attackb9, knight_atk10] {ai_charge(1);}; +void() knight_atk10=[ $attackb10, knight_run1 ] {ai_charge(5);}; + +//void() knight_atk9 =[ $attack9, knight_atk10 ] {}; +//void() knight_atk10 =[ $attack10, knight_atk11 ] {}; +//void() knight_atk11 =[ $attack11, knight_run1 ] {}; + +//=========================================================================== + +void() knight_pain1 =[ $pain1, knight_pain2 ] {}; +void() knight_pain2 =[ $pain2, knight_pain3 ] {}; +void() knight_pain3 =[ $pain3, knight_run1 ] {}; + +void() knight_painb1 =[ $painb1, knight_painb2 ] {ai_painforward(0);}; +void() knight_painb2 =[ $painb2, knight_painb3 ] {ai_painforward(3);}; +void() knight_painb3 =[ $painb3, knight_painb4 ] {}; +void() knight_painb4 =[ $painb4, knight_painb5 ] {}; +void() knight_painb5 =[ $painb5, knight_painb6 ] {ai_painforward(2);}; +void() knight_painb6 =[ $painb6, knight_painb7 ] {ai_painforward(4);}; +void() knight_painb7 =[ $painb7, knight_painb8 ] {ai_painforward(2);}; +void() knight_painb8 =[ $painb8, knight_painb9 ] {ai_painforward(5);}; +void() knight_painb9 =[ $painb9, knight_painb10 ] {ai_painforward(5);}; +void() knight_painb10 =[ $painb10, knight_painb11 ] {ai_painforward(0);}; +void() knight_painb11 =[ $painb11, knight_run1 ] {}; + +void(entity attacker, float damage) knight_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (self.spawnflags & 2) // statue + sound (self, CHAN_VOICE, "statue/pain.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "knight/khurt.wav", 1, ATTN_NORM); + + if (r < 0.85) + { + knight_pain1 (); + self.pain_finished = time + 1; + } + else + { + knight_painb1 (); + self.pain_finished = time + 1; + } + +}; + +//=========================================================================== + +void() knight_bow1 =[ $kneel1, knight_bow2 ] {ai_turn();}; +void() knight_bow2 =[ $kneel2, knight_bow3 ] {ai_turn();}; +void() knight_bow3 =[ $kneel3, knight_bow4 ] {ai_turn();}; +void() knight_bow4 =[ $kneel4, knight_bow5 ] {ai_turn();}; + +void() knight_bow5 =[ $kneel5, knight_bow5 ] {ai_turn();}; + +void() knight_bow6 =[ $kneel4, knight_bow7 ] {ai_turn();}; +void() knight_bow7 =[ $kneel3, knight_bow8 ] {ai_turn();}; +void() knight_bow8 =[ $kneel2, knight_bow9 ] {ai_turn();}; +void() knight_bow9 =[ $kneel1, knight_bow10 ] {ai_turn();}; +void() knight_bow10 =[ $walk1, knight_walk1 ] {ai_turn();}; + + + +void() knight_die1 =[ $death1, knight_die2 ] {}; +void() knight_die2 =[ $death2, knight_die3 ] {}; +void() knight_die3 =[ $death3, knight_die4 ] +{self.solid = SOLID_NOT;}; +void() knight_die4 =[ $death4, knight_die5 ] {}; +void() knight_die5 =[ $death5, knight_die6 ] {}; +void() knight_die6 =[ $death6, knight_die7 ] {}; +void() knight_die7 =[ $death7, knight_die8 ] {}; +void() knight_die8 =[ $death8, knight_die9 ] {}; +void() knight_die9 =[ $death9, knight_die10] {}; +void() knight_die10=[ $death10, knight_die10] {}; + + +void() knight_dieb1 =[ $deathb1, knight_dieb2 ] {}; +void() knight_dieb2 =[ $deathb2, knight_dieb3 ] {}; +void() knight_dieb3 =[ $deathb3, knight_dieb4 ] +{self.solid = SOLID_NOT;}; +void() knight_dieb4 =[ $deathb4, knight_dieb5 ] {}; +void() knight_dieb5 =[ $deathb5, knight_dieb6 ] {}; +void() knight_dieb6 =[ $deathb6, knight_dieb7 ] {}; +void() knight_dieb7 =[ $deathb7, knight_dieb8 ] {}; +void() knight_dieb8 =[ $deathb8, knight_dieb9 ] {}; +void() knight_dieb9 =[ $deathb9, knight_dieb10] {}; +void() knight_dieb10 = [ $deathb10, knight_dieb11] {}; +void() knight_dieb11 = [ $deathb11, knight_dieb11] {}; + + +void() knight_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_knight.mdl", self.health); + + if (self.spawnflags & 2) // statue + { + ThrowGib ( "progs/statgib1.mdl", self.health); + ThrowGib ( "progs/statgib2.mdl", self.health); + ThrowGib ( "progs/statgib3.mdl", self.health); + } + else + { + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + } + return; + } + +// regular death + if (self.spawnflags & 2) // statue + sound (self, CHAN_VOICE, "statue/death.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "knight/kdeath.wav", 1, ATTN_NORM); + + if (random() < 0.5) + knight_die1 (); + else + knight_dieb1 (); +}; + +void() knight_pause = +{ + sound ( self, CHAN_VOICE, "statue/see.wav", 1, ATTN_NORM); + + self.takedamage = DAMAGE_AIM; + self.th_stand = knight_stand1; + self.th_walk = knight_walk1; + self.th_run = knight_run1; + self.th_melee = knight_atk1; + self.th_pain = knight_pain; + self.th_die = knight_die; + self.use = SUB_Null; + + self.think = knight_run1; + + self.enemy = activator; + self.nextthink = time + 0.1; + self.think = FoundTarget; + + self.delay = 0; +}; + +void() knight_pause1 = +{ + self.takedamage = DAMAGE_NO; + self.use = knight_pause; + self.think = knight_pause1; + self.nextthink = time + 0.1; +}; + +/*QUAKED monster_knight (1 0 0) (-16 -16 -24) (16 16 40) Ambush Statue + +Statue - select this to use the stone skin and have him frozen in place. + +delay: time from sight to attack (default 5) +frame: frame to be frozen in. (default 44) +*/ +void() monster_knight = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/knight.mdl"); + precache_model ("progs/h_knight.mdl"); + + if (self.spawnflags & 2) // statue + { + precache_model ( "progs/statgib1.mdl"); + precache_model ( "progs/statgib2.mdl"); + precache_model ( "progs/statgib3.mdl"); + precache_sound ( "statue/see.wav"); + precache_sound ( "statue/idle.wav"); + precache_sound ( "statue/pain.wav"); + precache_sound ( "statue/death.wav"); + } + else + { + precache_sound ("knight/kdeath.wav"); + precache_sound ("knight/khurt.wav"); + precache_sound ("knight/ksight.wav"); + precache_sound ("knight/idle.wav"); + } + + precache_sound ("knight/sword1.wav"); + precache_sound ("knight/sword2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/knight.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 75; + + if (self.spawnflags & 2) // statue + { + if (self.frame == 0) + self.frame = 44; + self.skin = 1; + + if (!self.targetname) + { + dprint ("statue not targeted\n"); + dprint (self.classname); + dprint (" at "); + dprint (vtos(self.origin)); + dprint ("\n"); + return; + } + + self.use = knight_pause; + self.th_stand = knight_pause1; + self.th_walk = knight_pause1; + self.th_run = knight_pause1; +/* + self.th_stand = knight_pause1; + self.th_walk = knight_pause; + self.th_run = knight_pause; + self.th_melee = knight_atk1; + self.th_pain = knight_pause; + self.th_die = knight_die; +*/ + walkmonster_start (); + } + else + { + self.th_stand = knight_stand1; + self.th_walk = knight_walk1; + self.th_run = knight_run1; + self.th_melee = knight_atk1; + self.th_pain = knight_pain; + self.th_die = knight_die; + walkmonster_start (); + } + +}; diff --git a/quakec_rogue/lava_wpn.qc b/quakec_rogue/lava_wpn.qc new file mode 100644 index 0000000..572c9ca --- /dev/null +++ b/quakec_rogue/lava_wpn.qc @@ -0,0 +1,224 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// LAVA Weapon Routines + +/* +=============== +launch_lava_spike + +Used for both the player and the ogre +=============== +*/ +void(vector org, vector dir) launch_lava_spike = +{ + newmis = spawn (); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + + newmis.angles = vectoangles(dir); + + newmis.touch = lavaspike_touch; + newmis.classname = "lava_spike"; + newmis.think = SUB_Remove; + newmis.nextthink = time + 6; + setmodel (newmis, "progs/lspike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + setorigin (newmis, org); + + newmis.velocity = dir * 1000; +}; + +void() W_FireSuperLavaSpikes = +{ + local vector dir; + local entity old; + + sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_lava_nails = self.ammo_lava_nails - 2; + UpdateAmmoCounts (self); + + dir = aim (self, 1000); + launch_lava_spike (self.origin + '0 0 16', dir); + newmis.touch = superlavaspike_touch; + +// setmodel (newmis, "progs/lspike.mdl"); + + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + self.punchangle_x = -2; +}; + +void(float ox) W_FireLavaSpikes = +{ + local vector dir; + local entity old; + + makevectors (self.v_angle); + + if (self.ammo_lava_nails >= 2 && self.weapon == IT_LAVA_SUPER_NAILGUN) + { + W_FireSuperLavaSpikes (); + return; + } + + if (self.ammo_lava_nails < 1) + { + sprint (self, "$qc_out_lava_nails"); + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_lava_nails = self.ammo_lava_nails - 1; + UpdateAmmoCounts (self); + + dir = aim (self, 1000); + launch_lava_spike (self.origin + '0 0 16' + v_right*ox, dir); + + self.punchangle_x = -2; +}; + +// =============== Lava Spike Touch Routines + +void() lavaspike_touch = +{ +local float rand; +local float old_armortype; +local float old_armorvalue; +local float old_armormask; + + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (9); + if(other.classname == "player") + { + old_armortype = other.armortype; + old_armorvalue = other.armorvalue; + old_armormask = other.items2 & (IT2_ARMOR1|IT2_ARMOR2|IT2_ARMOR3); + other.armortype = 0; + other.armorvalue = 0; + T_Damage (other, self, self.owner, 9); + other.armortype = old_armortype; + other.armorvalue = old_armorvalue; + other.items2 = other.items2 | old_armormask; + } + else // is a monster + { + if ( other.classname != "monster_lava_man") + { + T_Damage (other, self, self.owner, 15); + } + } + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + + if (self.classname == "wizspike") + WriteByte (MSG_BROADCAST, TE_WIZSPIKE); + else if (self.classname == "knightspike") + WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE); + else + WriteByte (MSG_BROADCAST, TE_SPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + +void() superlavaspike_touch = +{ +local float rand; +local float old_armortype; +//local float old_armorvalue; + + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (18); + + // halve the effectiveness of the armor for players.. + if(other.classname == "player") + { + // save the old armor values... + old_armortype = other.armortype; +// old_armorvalue = other.armorvalue; + other.armortype = other.armortype * 0.5; +// other.armorvalue = 0; + T_Damage (other, self, self.owner, 18); + + // if the damage didn't wipe out the armor, armortype + if(other.armortype != 0) + { + other.armortype = old_armortype; +// other.armorvalue = old_armorvalue; + } + } + else // is a monster, do 50% more damage + { + if ( other.classname != "monster_lava_man") + { + T_Damage (other, self, self.owner, 30); + } + } + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_SUPERSPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; diff --git a/quakec_rogue/lavaman.qc b/quakec_rogue/lavaman.qc new file mode 100644 index 0000000..90c4170 --- /dev/null +++ b/quakec_rogue/lavaman.qc @@ -0,0 +1,502 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +LavaMan (Smaller version of Boss E1M7) + +============================================================================== +*/ +$frame rise1 rise2 rise3 rise4 rise5 rise6 rise7 rise8 rise9 rise10 +$frame rise11 rise12 rise13 rise14 rise15 rise16 rise17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 +$frame walk9 walk10 walk11 walk12 walk13 walk14 walk15 +$frame walk16 walk17 walk18 walk19 walk20 walk21 walk22 +$frame walk23 walk24 walk25 walk26 walk27 walk28 walk29 walk30 walk31 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 death9 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 attack12 attack13 attack14 attack15 +$frame attack16 attack17 attack18 attack19 attack20 attack21 attack22 +$frame attack23 + +$frame shocka1 shocka2 shocka3 shocka4 shocka5 shocka6 shocka7 shocka8 +$frame shocka9 shocka10 + +//$frame shockb1 shockb2 shockb3 shockb4 shockb5 shockb6 + +//$frame shockc1 shockc2 shockc3 shockc4 shockc5 shockc6 shockc7 shockc8 +//$frame shockc9 shockc10 + +void(float AttackNumber) lavaman_missile; + +/* +=========== +LavaManCheckAttack + +The player is in view, so decide to move or launch an attack +Returns FALSE if movement should continue +============ +*/ +float() LavaManCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + + ai_face(); + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + '0 0 64'; + + traceline (spot1, targ.origin, FALSE, self); + + if (trace_inopen && trace_inwater) + return FALSE; // sight line crossed contents + if (trace_ent != targ) + return FALSE; // don't have a clear shot + +// missile attack + if (time < self.attack_finished) + return FALSE; + + self.th_missile (); + SUB_AttackFinished (1 + random()); + return TRUE; +}; + +// ============ +// ============ +void() lavaman_hunt = +{ + local entity possibleEnemy; + local vector viewPoint; + +// go for another player if multi player + if (self.enemy.health <= 0) + { + viewPoint = self.origin + '0 0 96'; + possibleEnemy = find(self.enemy, classname, "player"); + traceline ( viewPoint, possibleEnemy.origin, TRUE, world ); + if (trace_fraction == 1) + self.enemy = possibleEnemy; + } + if (self.enemy) + { + ai_face(); + self.movetarget = self.enemy; + self.goalentity = self.enemy; + } +}; +/* +void(float dist) lavaman_walk = +{ + lavaman_hunt(); + ai_walk(dist); +}; +*/ + +void() lavaman_rise1 =[ $rise1, lavaman_rise2 ] + { sound (self, CHAN_WEAPON, "boss1/out1.wav", 1, ATTN_NORM); +}; +void() lavaman_rise2 =[ $rise2, lavaman_rise3 ] + { +sound (self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); }; +void() lavaman_rise3 =[ $rise3, lavaman_rise4 ] {}; +void() lavaman_rise4 =[ $rise4, lavaman_rise5 ] {}; +void() lavaman_rise5 =[ $rise5, lavaman_rise6 ] {}; +void() lavaman_rise6 =[ $rise6, lavaman_rise7 ] {}; +void() lavaman_rise7 =[ $rise7, lavaman_rise8 ] {}; +void() lavaman_rise8 =[ $rise8, lavaman_rise9 ] {}; +void() lavaman_rise9 =[ $rise9, lavaman_rise10 ] {}; +void() lavaman_rise10 =[ $rise10, lavaman_rise11 ] {}; +void() lavaman_rise11 =[ $rise11, lavaman_rise12 ] {}; +void() lavaman_rise12 =[ $rise12, lavaman_rise13 ] {}; +void() lavaman_rise13 =[ $rise13, lavaman_rise14 ] {}; +void() lavaman_rise14 =[ $rise14, lavaman_rise15 ] {}; +void() lavaman_rise15 =[ $rise15, lavaman_rise16 ] {}; +void() lavaman_rise16 =[ $rise16, lavaman_rise17 ] {}; +void() lavaman_rise17 =[ $rise17, lavaman_fire1 ] {}; + +// ============ +// ============ +void() lavaman_stand = +{ + if (self.enemy) + LavaManCheckAttack(); + else + lavaman_hunt(); + + ai_stand (); +}; + +void() lavaman_idle1 =[ $walk1, lavaman_idle2 ] +{ + if ( random() < 0.2 ) + sound (self, CHAN_VOICE, "boss1/sight1.wav", 1, ATTN_NORM); +// look for other players +}; +void() lavaman_idle2 =[ $walk2, lavaman_idle3 ] {lavaman_stand();}; +void() lavaman_idle3 =[ $walk3, lavaman_idle4 ] {lavaman_stand();}; +void() lavaman_idle4 =[ $walk4, lavaman_idle5 ] {lavaman_stand();}; +void() lavaman_idle5 =[ $walk5, lavaman_idle6 ] {lavaman_stand();}; +void() lavaman_idle6 =[ $walk4, lavaman_idle7 ] {lavaman_stand();}; +void() lavaman_idle7 =[ $walk3, lavaman_idle8 ] {lavaman_stand();}; +void() lavaman_idle8 =[ $walk2, lavaman_idle9 ] {lavaman_stand();}; +void() lavaman_idle9 =[ $walk1, lavaman_idle1 ] {lavaman_stand();}; + +// ============ +// ============ +void() lavaman_walk = +{ + if (self.enemy) + LavaManCheckAttack(); + else + lavaman_hunt(); + ai_walk (2); +}; + +void() lavaman_walk1 =[ $walk1, lavaman_walk2 ] {lavaman_walk();}; +void() lavaman_walk2 =[ $walk2, lavaman_walk3 ] {lavaman_walk();}; +void() lavaman_walk3 =[ $walk3, lavaman_walk4 ] {lavaman_walk();}; +void() lavaman_walk4 =[ $walk4, lavaman_walk5 ] {lavaman_walk();}; +void() lavaman_walk5 =[ $walk5, lavaman_walk6 ] {lavaman_walk();}; +void() lavaman_walk6 =[ $walk6, lavaman_walk7 ] {lavaman_walk();}; +void() lavaman_walk7 =[ $walk7, lavaman_walk8 ] {lavaman_walk();}; +void() lavaman_walk8 =[ $walk8, lavaman_walk9 ] {lavaman_walk();}; +void() lavaman_walk9 =[ $walk9, lavaman_walk10 ] {lavaman_walk();}; +void() lavaman_walk10 =[ $walk10, lavaman_walk11 ] {lavaman_walk();}; +void() lavaman_walk11 =[ $walk11, lavaman_walk12 ] {lavaman_walk();}; +void() lavaman_walk12 =[ $walk12, lavaman_walk13 ] {lavaman_walk();}; +void() lavaman_walk13 =[ $walk13, lavaman_walk14 ] {lavaman_walk();}; +void() lavaman_walk14 =[ $walk14, lavaman_walk15 ] {lavaman_walk();}; +void() lavaman_walk15 =[ $walk15, lavaman_walk16 ] {lavaman_walk();}; +void() lavaman_walk16 =[ $walk16, lavaman_walk17 ] {lavaman_walk();}; +void() lavaman_walk17 =[ $walk17, lavaman_walk18 ] {lavaman_walk();}; +void() lavaman_walk18 =[ $walk18, lavaman_walk19 ] {lavaman_walk();}; +void() lavaman_walk19 =[ $walk19, lavaman_walk20 ] {lavaman_walk();}; +void() lavaman_walk20 =[ $walk20, lavaman_walk21 ] {lavaman_walk();}; +void() lavaman_walk21 =[ $walk21, lavaman_walk22 ] {lavaman_walk();}; +void() lavaman_walk22 =[ $walk22, lavaman_walk23 ] {lavaman_walk();}; +void() lavaman_walk23 =[ $walk23, lavaman_walk24 ] {lavaman_walk();}; +void() lavaman_walk24 =[ $walk24, lavaman_walk25 ] {lavaman_walk();}; +void() lavaman_walk25 =[ $walk25, lavaman_walk26 ] {lavaman_walk();}; +void() lavaman_walk26 =[ $walk26, lavaman_walk27 ] {lavaman_walk();}; +void() lavaman_walk27 =[ $walk27, lavaman_walk28 ] {lavaman_walk();}; +void() lavaman_walk28 =[ $walk28, lavaman_walk29 ] {lavaman_walk();}; +void() lavaman_walk29 =[ $walk29, lavaman_walk30 ] {lavaman_walk();}; +void() lavaman_walk30 =[ $walk30, lavaman_walk31 ] {lavaman_walk();}; +void() lavaman_walk31 =[ $walk31, lavaman_walk1 ] {}; + +void() lavaman_run = +{ + if (self.enemy) + LavaManCheckAttack(); + else + lavaman_hunt(); + ai_run (2); +}; + +void() lavaman_run1 =[ $walk1, lavaman_run2 ] {lavaman_run();}; +void() lavaman_run2 =[ $walk2, lavaman_run3 ] {lavaman_run();}; +void() lavaman_run3 =[ $walk3, lavaman_run4 ] {lavaman_run();}; +void() lavaman_run4 =[ $walk4, lavaman_run5 ] {lavaman_run();}; +void() lavaman_run5 =[ $walk5, lavaman_run6 ] {lavaman_run();}; +void() lavaman_run6 =[ $walk6, lavaman_run7 ] {lavaman_run();}; +void() lavaman_run7 =[ $walk7, lavaman_run8 ] {lavaman_run();}; +void() lavaman_run8 =[ $walk8, lavaman_run9 ] {lavaman_run();}; +void() lavaman_run9 =[ $walk9, lavaman_run10 ] {lavaman_run();}; +void() lavaman_run10 =[ $walk10, lavaman_run11 ] {lavaman_run ();}; +void() lavaman_run11 =[ $walk11, lavaman_run12 ] {lavaman_run ();}; +void() lavaman_run12 =[ $walk12, lavaman_run13 ] {lavaman_run ();}; +void() lavaman_run13 =[ $walk13, lavaman_run14 ] {lavaman_run ();}; +void() lavaman_run14 =[ $walk14, lavaman_run15 ] {lavaman_run ();}; +void() lavaman_run15 =[ $walk15, lavaman_run16 ] {lavaman_run ();}; +void() lavaman_run16 =[ $walk16, lavaman_run17 ] {lavaman_run ();}; +void() lavaman_run17 =[ $walk17, lavaman_run18 ] {lavaman_run ();}; +void() lavaman_run18 =[ $walk18, lavaman_run19 ] {lavaman_run ();}; +void() lavaman_run19 =[ $walk19, lavaman_run20 ] {lavaman_run ();}; +void() lavaman_run20 =[ $walk20, lavaman_run21 ] {lavaman_run ();}; +void() lavaman_run21 =[ $walk21, lavaman_run22 ] {lavaman_run ();}; +void() lavaman_run22 =[ $walk22, lavaman_run23 ] {lavaman_run ();}; +void() lavaman_run23 =[ $walk23, lavaman_run24 ] {lavaman_run ();}; +void() lavaman_run24 =[ $walk24, lavaman_run25 ] {lavaman_run ();}; +void() lavaman_run25 =[ $walk25, lavaman_run26 ] {lavaman_run ();}; +void() lavaman_run26 =[ $walk26, lavaman_run27 ] {lavaman_run ();}; +void() lavaman_run27 =[ $walk27, lavaman_run28 ] {lavaman_run ();}; +void() lavaman_run28 =[ $walk28, lavaman_run29 ] {lavaman_run ();}; +void() lavaman_run29 =[ $walk29, lavaman_run30 ] {lavaman_run ();}; +void() lavaman_run30 =[ $walk30, lavaman_run31 ] {lavaman_run ();}; +void() lavaman_run31 =[ $walk31, lavaman_run1 ] {}; + +void() lavaman_fire1 =[ $attack1, lavaman_fire2 ] { ai_face();}; +void() lavaman_fire2 =[ $attack2, lavaman_fire3 ] {ai_charge(1);}; +void() lavaman_fire3 =[ $attack3, lavaman_fire4 ] { ai_face();}; +void() lavaman_fire4 =[ $attack4, lavaman_fire5 ] {ai_face();}; +void() lavaman_fire5 =[ $attack5, lavaman_fire6 ] {ai_charge(1);}; +void() lavaman_fire6 =[ $attack6, lavaman_fire7 ] {ai_face();}; +void() lavaman_fire7 =[ $attack7, lavaman_fire8 ] {lavaman_missile(1);}; +void() lavaman_fire8 =[ $attack8, lavaman_fire9 ] {ai_face();}; +void() lavaman_fire9 =[ $attack9, lavaman_fire10 ] {ai_face();}; +void() lavaman_fire10 =[ $attack10, lavaman_fire11 ] {ai_charge(1);}; +void() lavaman_fire11 =[ $attack11, lavaman_fire12 ] {ai_face();}; +void() lavaman_fire12 =[ $attack12, lavaman_fire13 ] {ai_charge(1);}; +void() lavaman_fire13 =[ $attack13, lavaman_fire14 ] {ai_face();}; +void() lavaman_fire14 =[ $attack14, lavaman_fire15 ] {ai_face();}; +void() lavaman_fire15 =[ $attack15, lavaman_fire16 ] {ai_charge(1);}; +void() lavaman_fire16 =[ $attack16, lavaman_fire17 ] {ai_charge(1);}; +void() lavaman_fire17 =[ $attack17, lavaman_fire18 ] {ai_face();}; +void() lavaman_fire18 =[ $attack18, lavaman_fire19 ] {lavaman_missile(2);}; +void() lavaman_fire19 =[ $attack19, lavaman_fire20 ] {ai_face();}; +void() lavaman_fire20 =[ $attack20, lavaman_fire21 ] {ai_face();}; +void() lavaman_fire21 =[ $attack21, lavaman_fire22 ] {ai_face();}; +void() lavaman_fire22 =[ $attack22, lavaman_fire23 ] {ai_charge(1);}; +void() lavaman_fire23 =[ $attack23, lavaman_run1 ] {}; + +void() lavaman_shocka1 =[ $shocka1, lavaman_shocka2 ] {}; +void() lavaman_shocka2 =[ $shocka2, lavaman_shocka3 ] {}; +void() lavaman_shocka3 =[ $shocka3, lavaman_shocka4 ] {}; +void() lavaman_shocka4 =[ $shocka4, lavaman_shocka5 ] {}; +void() lavaman_shocka5 =[ $shocka5, lavaman_shocka6 ] {}; +void() lavaman_shocka6 =[ $shocka6, lavaman_shocka7 ] {}; +void() lavaman_shocka7 =[ $shocka7, lavaman_shocka8 ] {}; +void() lavaman_shocka8 =[ $shocka8, lavaman_shocka9 ] {}; +void() lavaman_shocka9 =[ $shocka9, lavaman_shocka10 ] {}; +void() lavaman_shocka10 =[ $shocka10, lavaman_run1 ] {}; + +void() lavaman_pain = +{ + if ( self.pain_finished > time ) + return; + + if (random() < 0.05) + { + self.pain_finished = time + 2; + lavaman_shocka1(); + } +}; + +void() lavaman_death1 = [$death1, lavaman_death2] + { +sound (self, CHAN_VOICE, "boss1/death.wav", 1, ATTN_NORM); +}; +void() lavaman_death2 = [$death2, lavaman_death3] {}; +void() lavaman_death3 = [$death3, lavaman_death4] {}; +void() lavaman_death4 = [$death4, lavaman_death5] {}; +void() lavaman_death5 = [$death5, lavaman_death6] {}; +void() lavaman_death6 = [$death6, lavaman_death7] {}; +void() lavaman_death7 = [$death7, lavaman_death8] {}; +void() lavaman_death8 = [$death8, lavaman_death9] {}; +void() lavaman_death9 = [$death9, lavaman_death10] +{ + sound (self, CHAN_BODY, "boss1/out1.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); +}; + +void() lavaman_death10 = [$death9, lavaman_death10] +{ + remove (self); +}; + +void() lavaman_touch = +{ + local float damg; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + damg = 40; + if (other.health) + { + if (other.classname == "monster_shambler") + damg = damg * 0.5; // mostly immune + T_Damage (other, self, self.owner, damg ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact + T_RadiusDamage (self, self.owner, 40, other); + +// sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + +// Change to an explosion2!!! Ramp in the non-diminishing reds... + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +void(float AttackNumber) lavaman_missile = +{ + local entity fireball; + local vector offang; + local vector org, vec, d; + local float t; + + makevectors (self.angles); + + // Right handed throw + if ( AttackNumber == 1) + { +// org = self.origin - v_forward*40 + v_right*90 + v_up*110; + org = self.origin + v_forward*40 + v_right*65 + v_up*130; + } + // Left handed throw + else if ( AttackNumber == 2) + { +// org = self.origin - v_forward*40 - v_right*90 + v_up*110; + org = self.origin + v_forward*40 - v_right*75 + v_up*125; + } + + d = self.enemy.origin; + t = vlen ( d - org ); + t = t / 380; + if ( t > 1.75 ) + t = 1.75; + if ( t < 1 ) + t = 1; + + vec = normalize (d - org); + + fireball = spawn (); + fireball.owner = self; +// fireball.movetype = MOVETYPE_FLYMISSILE; + fireball.movetype = MOVETYPE_BOUNCE; + fireball.solid = SOLID_BBOX; + fireball.angles = vectoangles(vec); + fireball.touch = lavaman_touch; + fireball.classname = "lavaman_ball"; + setmodel (fireball, "progs/lavaball.mdl"); + setsize (fireball, '0 0 0', '0 0 0'); + setorigin (fireball, org); + +// fireball.velocity = vec * 600 + '0 0 200'; + fireball.velocity = vec * 600 * t; + fireball.velocity_z = fireball.velocity_z + (200 * t); + + fireball.avelocity = '200 100 300'; + fireball.think = SUB_Remove; + fireball.nextthink = time + 6; + + sound (self, CHAN_WEAPON, "boss1/throw.wav", 1, ATTN_NORM); + +// check for dead enemy + if (self.enemy.health <= 0) + lavaman_idle1 (); +}; + +void() lavaman_awake = +{ + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + self.takedamage = DAMAGE_AIM; + self.flags = self.flags | FL_MONSTER; + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 20; + + setmodel (self, "progs/lavaman.mdl"); +// setsize (self, '-64 -64 -12', '64 64 128'); +// setsize (self, '-32 -32 0', '32 32 120'); + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.view_ofs = '0 0 48'; + +// self.health = 1500; + + self.health = 1250 + (250 * cvar("skill")); + + self.th_stand = lavaman_idle1; + self.th_walk = lavaman_walk1; + self.th_run = lavaman_walk1; + self.th_missile = lavaman_fire1; + self.th_pain = lavaman_pain; + self.th_die = lavaman_death1; + self.th_melee = lavaman_fire1; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + if (!(activator.items & IT_INVISIBILITY)) + { + if (!(activator.flags & FL_NOTARGET)) + { + if (activator.classname == "player") + self.enemy = activator; + } + } + droptofloor(); + + lavaman_rise1 (); +}; + +// (-64 -64 -12) (64 64 128) +/*QUAKED monster_lava_man (1 0 0) (-32 -32 -24) (32 32 64) Ambush Sleeping +*/ +void() monster_lava_man = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/lavaman.mdl"); + precache_model ("progs/lavaball.mdl"); + + precache_sound ("weapons/rocket1i.wav"); + precache_sound ("boss1/out1.wav"); + precache_sound ("boss1/sight1.wav"); + precache_sound ("misc/power.wav"); + precache_sound ("boss1/throw.wav"); + precache_sound ("boss1/pain.wav"); + precache_sound ("boss1/death.wav"); + + total_monsters = total_monsters + 1; + + // cause the lavaman to rise from the lava when triggered. + if (self.spawnflags & 2) + { + self.use = lavaman_awake; + return; + } + + lavaman_awake(); +}; + diff --git a/quakec_rogue/lightnin.qc b/quakec_rogue/lightnin.qc new file mode 100644 index 0000000..8946078 --- /dev/null +++ b/quakec_rogue/lightnin.qc @@ -0,0 +1,202 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// lightning trail +// pmack +// sept 96 + +// float ltrailLastUsed; -- now an entity field. + +float LT_TOGGLE = 1; +float LT_ACTIVE = 2; + +void() ltrail_chain = +{ + SUB_UseTargets(); + + self.think = SUB_Null; +}; + +void() ltrail_fire = +{ + local entity myTarget; + + if (self.classname != "ltrail_end") + { + sound (self, CHAN_VOICE, "weapons/lhit.wav", 1, ATTN_NORM); + myTarget = find(world, targetname, self.target); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteCoord (MSG_BROADCAST, myTarget.origin_x); + WriteCoord (MSG_BROADCAST, myTarget.origin_y); + WriteCoord (MSG_BROADCAST, myTarget.origin_z); + LightningDamage (self.origin, myTarget.origin, self, self.currentammo); + } + + if ( self.items < time) + { + self.think = ltrail_chain; + self.nextthink = time + self.frags; + } + else + { + self.think = ltrail_fire; + self.nextthink = time + 0.05; + } +}; + +void() ltrail_start_fire = +{ + // if it's a toggle ltrail, we ignore triggers from ltrail_end's + // when toggled off. + if (self.spawnflags & LT_TOGGLE) + { + // user is not a lightning trail - change activity state. + if ( other.classname != "ltrail_end" ) + { + if (self.spawnflags & LT_ACTIVE) + // currently active + { + self.spawnflags = self.spawnflags - LT_ACTIVE; + return; + } + else + // not active + { + self.spawnflags = self.spawnflags + LT_ACTIVE; + } + } + // user is lightning trail, but trail has been turned off. + // ignore the message. + else if (!(self.spawnflags & LT_ACTIVE)) + return; + } + + if (self.classname == "ltrail_start") + { + self.items = time + self.weapon; + ltrail_fire(); + self.ltrailLastUsed = time; + } + else if (self.classname == "ltrail_relay") + { + self.items = time + self.weapon; + ltrail_fire(); + } + else + { + self.think = ltrail_chain; + self.nextthink = time + self.frags; + } +}; + +/*QUAKED ltrail_start (0 1 0) (-8 -8 -8) (8 8 8) LT_TOGGLE +Starting point of a lightning trail. +Set currentammo to amount of damage you want the lightning to do. +Default is 25. + +Set frags to amount of time before next item is triggered. +Default is 0.3 seconds. + +Set weapon to amount of time to be firing the lightning. +Default is 0.3 seconds. + +Set the LT_TOGGLE checkbox if you want the lightning shooter to continuously fire until triggered again. +*/ +void() ltrail_start = +{ + self.ltrailLastUsed = time; + + precache_sound ("weapons/lhit.wav"); + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_BBOX; + self.use = ltrail_start_fire; + + if (self.currentammo == 0) + self.currentammo = 25; + + if (self.weapon == 0) + self.weapon = 0.3; + + if (self.frags == 0) + self.frags = 0.3; + + if (self.spawnflags & LT_ACTIVE) + { + self.items = time + 99999999; + self.think = ltrail_fire; + self.nextthink = time + 0.1; + } +}; + +/*QUAKED ltrail_relay (0 1 0) (-8 -8 -8) (8 8 8) +Relay point of a lightning trail. +Set currentammo to amount of damage you want the lightning to do. +Default is 25. + +Set frags to amount of time before next item is triggered. +Default is 0.3 seconds. + +Set weapon to amount of time to be firing the lightning. +Default is 0.3 seconds. +*/ +void() ltrail_relay = +{ + precache_sound ("weapons/lhit.wav"); + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_BBOX; + self.use = ltrail_start_fire; + + if (self.currentammo == 0) + self.currentammo = 25; + + if (self.weapon == 0) + self.weapon = 0.3; + + if (self.frags == 0) + self.frags = 0.3; +}; + +/*QUAKED ltrail_end (0 1 0) (-8 -8 -8) (8 8 8) +Ending point of a lightning trail. +Does not fire any lightning. + +Set frags to amount of time before next item is triggered. +Default is 0.3 seconds. +*/ +void() ltrail_end = +{ + precache_sound ("weapons/lhit.wav"); + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_BBOX; + self.use = ltrail_start_fire; + + if (self.currentammo == 0) + self.currentammo = 25; + + if (self.weapon == 0) + self.weapon = 0.3; + + if (self.frags == 0) + self.frags = 0.3; +}; diff --git a/quakec_rogue/misc.qc b/quakec_rogue/misc.qc new file mode 100644 index 0000000..4ac07ef --- /dev/null +++ b/quakec_rogue/misc.qc @@ -0,0 +1,746 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void() info_null = +{ + remove(self); +}; + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void() info_notnull = +{ +}; + +//============================================================================ + +float START_OFF = 1; + +void() light_use = +{ + if (self.spawnflags & START_OFF) + { + lightstyle(self.style, "m"); + self.spawnflags = self.spawnflags - START_OFF; + } + else + { + lightstyle(self.style, "a"); + self.spawnflags = self.spawnflags + START_OFF; + } +}; + +/*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, it will toggle between on or off. +*/ +void() light = +{ + if (!self.targetname) + { // inert light + remove(self); + return; + } + + if (self.style >= 32) + { + self.use = light_use; + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } +}; + +/*QUAKED light_fluoro (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, it will toggle between on or off. +Makes steady fluorescent humming sound +*/ +void() light_fluoro = +{ + if (self.style >= 32) + { + self.use = light_use; + if (self.spawnflags & START_OFF) + lightstyle(self.style, "a"); + else + lightstyle(self.style, "m"); + } + + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_fluorospark (0 1 0) (-8 -8 -8) (8 8 8) +Non-displayed light. +Default light value is 300 +Default style is 10 +Makes sparking, broken fluorescent sound +*/ +void() light_fluorospark = +{ + if (!self.style) + self.style = 10; + + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_globe (0 1 0) (-8 -8 -8) (8 8 8) +Sphere globe light. +Default light value is 300 +Default style is 0 +*/ +void() light_globe = +{ + precache_model ("progs/s_light.spr"); + setmodel (self, "progs/s_light.spr"); + makestatic (self); +}; + +void() FireAmbient = +{ + precache_sound ("ambience/fire1.wav"); +// attenuate fast + ambientsound (self.origin, "ambience/fire1.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED light_torch_small_walltorch (0 .5 0) (-10 -10 -20) (10 10 20) Quiet +Short wall torch +Default light value is 300 +Default style is 0 +*/ +void() light_torch_small_walltorch = +{ + precache_model ("progs/flame.mdl"); + setmodel (self, "progs/flame.mdl"); + if (!(self.spawnflags & 1)) + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_large_yellow (0 1 0) (-10 -10 -12) (12 12 18) +Large yellow flame ball +*/ +void() light_flame_large_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + self.frame = 1; + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_yellow (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Small yellow flame ball +*/ +void() light_flame_small_yellow = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + FireAmbient (); + makestatic (self); +}; + +/*QUAKED light_flame_small_white (0 1 0) (-10 -10 -40) (10 10 40) START_OFF +Small white flame ball +*/ +void() light_flame_small_white = +{ + precache_model ("progs/flame2.mdl"); + setmodel (self, "progs/flame2.mdl"); + FireAmbient (); + makestatic (self); +}; + +//============================================================================ + + +/*QUAKED misc_fireball (0 .5 .8) (-8 -8 -8) (8 8 8) +Lava Balls +*/ + +void() fire_fly; +void() fire_touch; +void() misc_fireball = +{ + + precache_model ("progs/lavaball.mdl"); + self.classname = "fireball"; + self.nextthink = time + (random() * 5); + self.think = fire_fly; + if (!self.speed) + self.speed == 1000; +}; + +void() fire_fly = +{ +local entity fireball; + + fireball = spawn(); + fireball.solid = SOLID_TRIGGER; + fireball.movetype = MOVETYPE_TOSS; + fireball.velocity = '0 0 1000'; + fireball.velocity_x = (random() * 100) - 50; + fireball.velocity_y = (random() * 100) - 50; + fireball.velocity_z = self.speed + (random() * 200); + fireball.classname = "fireball"; + setmodel (fireball, "progs/lavaball.mdl"); + setsize (fireball, '0 0 0', '0 0 0'); + setorigin (fireball, self.origin); + fireball.nextthink = time + 5; + fireball.think = SUB_Remove; + fireball.touch = fire_touch; + + self.nextthink = time + (random() * 5) + 3; + self.think = fire_fly; +}; + + +void() fire_touch = +{ + T_Damage (other, self, self, 20); + remove(self); +}; + +//============================================================================ + + +void() barrel_explode = +{ + self.takedamage = DAMAGE_NO; + self.classname = "explo_box"; + // did say self.owner + T_RadiusDamage (self, self, 160, world); + sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM); + particle (self.origin, '0 0 0', 75, 255); + + self.origin_z = self.origin_z + 32; + BecomeExplosion (); +}; + + + +/*QUAKED misc_explobox (0 .5 .8) (0 0 0) (32 32 64) +TESTING THING +*/ + +void() misc_explobox = +{ + local float oldz; + + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_NONE; + precache_model ("maps/b_explob.bsp"); + setmodel (self, "maps/b_explob.bsp"); + precache_sound ("weapons/r_exp3.wav"); + self.health = 20; + self.th_die = barrel_explode; + self.takedamage = DAMAGE_AIM; + + self.origin_z = self.origin_z + 2; + oldz = self.origin_z; + droptofloor(); + if (oldz - self.origin_z > 250) + { + dprint ("item fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + } +}; + + + + +/*QUAKED misc_explobox2 (0 .5 .8) (0 0 0) (32 32 64) +Smaller exploding box, REGISTERED ONLY +*/ + +void() misc_explobox2 = +{ + local float oldz; + + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_NONE; + precache_model2 ("maps/b_exbox2.bsp"); + setmodel (self, "maps/b_exbox2.bsp"); + precache_sound ("weapons/r_exp3.wav"); + self.health = 20; + self.th_die = barrel_explode; + self.takedamage = DAMAGE_AIM; + + self.origin_z = self.origin_z + 2; + oldz = self.origin_z; + droptofloor(); + if (oldz - self.origin_z > 250) + { + dprint ("item fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + } +}; + +//============================================================================ + +float SPAWNFLAG_SUPERSPIKE = 1; +float SPAWNFLAG_LASER = 2; +float SPAWNFLAG_LAVASPIKE = 4; +float SPAWNFLAG_SUPERLAVA = 8; +float SPAWNFLAG_LAVASKILL = 16; +float SPAWNFLAG_FIREBALL = 32; + +void(vector org, vector vec) LaunchLaser; +void(vector org, vector dir) launch_lava_spike; +void(vector org, vector dir) launch_fireball; + +void() spikeshooter_use = +{ + if (self.spawnflags & SPAWNFLAG_SUPERSPIKE || !(self.spawnflags)) + { + sound (self, CHAN_VOICE, "weapons/spike2.wav", 1, ATTN_NORM); + launch_spike (self.origin, self.movedir); + newmis.velocity = self.movedir * 500; + if (self.spawnflags & SPAWNFLAG_SUPERSPIKE) + newmis.touch = superspike_touch; + } + else if (self.spawnflags & SPAWNFLAG_LASER) + { + sound (self, CHAN_VOICE, "enforcer/enfire.wav", 1, ATTN_NORM); + LaunchLaser (self.origin, self.movedir); + } + else if (self.spawnflags & SPAWNFLAG_FIREBALL) + { + launch_fireball (self.origin, self.movedir); + } + else + { + sound (self, CHAN_VOICE, "weapons/spike2.wav", 1, ATTN_NORM); + launch_lava_spike (self.origin, self.movedir); + newmis.velocity = self.movedir * 500; + if (self.spawnflags & SPAWNFLAG_SUPERLAVA) + newmis.touch = superlavaspike_touch; + else if((self.spawnflags & SPAWNFLAG_LAVASKILL) && (skill > 1)) + newmis.touch = superlavaspike_touch; + } +}; + +void() shooter_think = +{ + spikeshooter_use (); + self.nextthink = time + self.wait; + newmis.velocity = self.movedir * 500; +}; + + +/*QUAKED trap_spikeshooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser lava superlava skilllava fireball +When triggered, fires a spike in the direction set in QuakeEd. +Laser, Lava and SuperLava are only for REGISTERED. +*/ + +void() trap_spikeshooter = +{ + SetMovedir (); + self.use = spikeshooter_use; + if (self.spawnflags & SPAWNFLAG_LASER) + { + precache_model2 ("progs/laser.mdl"); + + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + } + else if (self.spawnflags & SPAWNFLAG_LAVASPIKE) + { + precache_model2 ("progs/lspike.mdl"); + precache_sound2 ("lavagun/snail.wav"); // lava nail gun cooldown + precache_sound2 ("weapons/rocket1i.wav"); + } + else if (self.spawnflags & SPAWNFLAG_SUPERLAVA) + { + precache_model2 ("progs/lspike.mdl"); + precache_sound2 ("lavagun/snail.wav"); // lava nail gun cooldown + precache_sound2 ("weapons/spike2.wav"); + } + else if (self.spawnflags & SPAWNFLAG_LAVASKILL) + { + precache_model2 ("progs/lspike.mdl"); + precache_sound2 ("lavagun/snail.wav"); // lava nail gun cooldown + precache_sound2 ("weapons/rocket1i.wav"); + precache_sound2 ("weapons/spike2.wav"); + } + else if (self.spawnflags & SPAWNFLAG_FIREBALL) + { + precache_model ("progs/fireball.mdl"); + } + else + precache_sound ("weapons/spike2.wav"); +}; + + +/*QUAKED trap_shooter (0 .5 .8) (-8 -8 -8) (8 8 8) superspike laser +Continuously fires spikes. +"wait" time between spike (1.0 default) +"nextthink" delay before firing first spike, so multiple shooters can be stagered. +*/ +void() trap_shooter = +{ + trap_spikeshooter (); + + if (self.wait == 0) + self.wait = 1; + self.nextthink = self.nextthink + self.wait + self.ltime; + self.think = shooter_think; +}; + + + +/* +=============================================================================== + + +=============================================================================== +*/ + + +void() make_bubbles; +void() bubble_remove; +void() bubble_bob; + +/*QUAKED air_bubbles (0 .5 .8) (-8 -8 -8) (8 8 8) + +testing air bubbles +*/ + +void() air_bubbles = + +{ + if (deathmatch) + { + remove (self); + return; + } + precache_model ("progs/s_bubble.spr"); + self.nextthink = time + 1; + self.think = make_bubbles; +}; + +void() make_bubbles = +{ +local entity bubble; + + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + random() + 0.5; + self.think = make_bubbles; +}; + +void() bubble_split = +{ +local entity bubble; + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.origin); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = self.velocity; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.touch = bubble_remove; + bubble.classname = "bubble"; + bubble.frame = 1; + bubble.cnt = 10; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.frame = 1; + self.cnt = 10; + if (self.waterlevel != 3) + remove (self); +}; + +void() bubble_remove = +{ + if (other.classname == self.classname) + { +// dprint ("bump"); + return; + } + remove(self); +}; + +void() bubble_bob = +{ +local float rnd1, rnd2, rnd3; +local vector vtmp1, modi; + + // PGM fix - 02/25/97 so bubbles won't go through walls + rnd1 = pointcontents(self.origin); + if (rnd1 == CONTENT_SOLID) + remove(self); + // PGM fix - 02/25/97 so bubbles pop 2 seconds after leaving water + else if (rnd1 == CONTENT_EMPTY) + { + if (self.cnt < 16) + self.cnt = 16; + } + + self.cnt = self.cnt + 1; + if (self.cnt == 4) + bubble_split(); + if (self.cnt == 20) + remove(self); + + rnd1 = self.velocity_x + (-10 + (random() * 20)); + rnd2 = self.velocity_y + (-10 + (random() * 20)); + rnd3 = self.velocity_z + 10 + random() * 10; + + if (rnd1 > 10) + rnd1 = 5; + if (rnd1 < -10) + rnd1 = -5; + + if (rnd2 > 10) + rnd2 = 5; + if (rnd2 < -10) + rnd2 = -5; + + if (rnd3 < 10) + rnd3 = 15; + if (rnd3 > 30) + rnd3 = 25; + + self.velocity_x = rnd1; + self.velocity_y = rnd2; + self.velocity_z = rnd3; + + self.nextthink = time + 0.5; + self.think = bubble_bob; +}; + +/*~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~> +~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~<~>~*/ + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) + +Just for the debugging level. Don't use +*/ + +void() viewthing = + +{ + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + precache_model ("progs/player.mdl"); + setmodel (self, "progs/player.mdl"); +}; + + +/* +============================================================================== + +SIMPLE BMODELS + +============================================================================== +*/ + +void() func_wall_use = +{ // change to alternate textures + self.frame = 1 - self.frame; +}; + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibitted +*/ +void() func_wall = +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +void() func_illusionary = + +{ + self.angles = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + setmodel (self, self.model); + makestatic (); +}; + +/*QUAKED func_episodegate (0 .5 .8) ? E1 E2 E3 E4 +This bmodel will appear if the episode has allready been completed, so players can't reenter it. +*/ +void() func_episodegate = + +{ + if (!(serverflags & self.spawnflags)) + return; // can still enter episode + + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + +/*QUAKED func_bossgate (0 .5 .8) ? +This bmodel appears unless players have all of the episode sigils. +*/ +void() func_bossgate = + +{ + if ( (serverflags & 15) == 15) + return; // all episodes completed + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + self.use = func_wall_use; + setmodel (self, self.model); +}; + +//============================================================================ +/*QUAKED ambient_suck_wind (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_suck_wind = +{ + precache_sound ("ambience/suck1.wav"); + ambientsound (self.origin, "ambience/suck1.wav", 1, ATTN_STATIC); +}; + +/*QUAKED ambient_drone (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drone = +{ + precache_sound ("ambience/drone6.wav"); + ambientsound (self.origin, "ambience/drone6.wav", 0.5, ATTN_STATIC); +}; + +/*QUAKED ambient_flouro_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_flouro_buzz = +{ + precache_sound ("ambience/buzz1.wav"); + ambientsound (self.origin, "ambience/buzz1.wav", 1, ATTN_STATIC); +}; +/*QUAKED ambient_drip (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_drip = +{ + precache_sound ("ambience/drip1.wav"); + ambientsound (self.origin, "ambience/drip1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_comp_hum (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_comp_hum = +{ + precache_sound ("ambience/comp1.wav"); + ambientsound (self.origin, "ambience/comp1.wav", 1, ATTN_STATIC); +}; +/*QUAKED ambient_thunder (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_thunder = +{ + precache_sound ("ambience/thunder1.wav"); + ambientsound (self.origin, "ambience/thunder1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_light_buzz (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_light_buzz = +{ + precache_sound ("ambience/fl_hum1.wav"); + ambientsound (self.origin, "ambience/fl_hum1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_swamp1 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp1 = +{ + precache_sound ("ambience/swamp1.wav"); + ambientsound (self.origin, "ambience/swamp1.wav", 0.5, ATTN_STATIC); +}; +/*QUAKED ambient_swamp2 (0.3 0.1 0.6) (-10 -10 -8) (10 10 8) +*/ +void() ambient_swamp2 = +{ + precache_sound ("ambience/swamp2.wav"); + ambientsound (self.origin, "ambience/swamp2.wav", 0.5, ATTN_STATIC); +}; + +//============================================================================ + +void() noise_think = +{ + self.nextthink = time + 0.5; + sound (self, 1, "enforcer/enfire.wav", 1, ATTN_NORM); + sound (self, 2, "enforcer/enfstop.wav", 1, ATTN_NORM); + sound (self, 3, "enforcer/sight1.wav", 1, ATTN_NORM); + sound (self, 4, "enforcer/sight2.wav", 1, ATTN_NORM); + sound (self, 5, "enforcer/sight3.wav", 1, ATTN_NORM); + sound (self, 6, "enforcer/sight4.wav", 1, ATTN_NORM); + sound (self, 7, "enforcer/pain1.wav", 1, ATTN_NORM); +}; + +/*QUAKED misc_noisemaker (1 0.5 0) (-10 -10 -10) (10 10 10) + +For optimzation testing, starts a lot of sounds. +*/ + +void() misc_noisemaker = + +{ + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("enforcer/enfstop.wav"); + precache_sound2 ("enforcer/sight1.wav"); + precache_sound2 ("enforcer/sight2.wav"); + precache_sound2 ("enforcer/sight3.wav"); + precache_sound2 ("enforcer/sight4.wav"); + precache_sound2 ("enforcer/pain1.wav"); + precache_sound2 ("enforcer/pain2.wav"); + precache_sound2 ("enforcer/death1.wav"); + precache_sound2 ("enforcer/idle1.wav"); + + self.nextthink = time + 0.1 + random(); + self.think = noise_think; +}; diff --git a/quakec_rogue/monsters.qc b/quakec_rogue/monsters.qc new file mode 100644 index 0000000..6df4b5c --- /dev/null +++ b/quakec_rogue/monsters.qc @@ -0,0 +1,265 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* ALL MONSTERS SHOULD BE 1 0 0 IN COLOR */ + +// name =[framenum, nexttime, nextthink] {code} +// expands to: +// name () +// { +// self.frame=framenum; +// self.nextthink = time + nexttime; +// self.think = nextthink +// +// }; + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void() monster_use = +{ + if (self.enemy) + return; + if (self.health <= 0) + return; + if (activator.items & IT_INVISIBILITY) + return; + if (activator.flags & FL_NOTARGET) + return; + if (activator.classname != "player") + return; + +// delay reaction so if the monster is teleported, its sound is still +// heard + self.enemy = activator; + self.nextthink = time + 0.1; + self.think = FoundTarget; +}; + +/* +================ +monster_death_use + +When a mosnter dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void() monster_death_use = +{ + local entity ent, otemp, stemp; + +// fall to ground + if (self.flags & FL_FLY) + self.flags = self.flags - FL_FLY; + if (self.flags & FL_SWIM) + self.flags = self.flags - FL_SWIM; + + if (!self.target) + return; + + activator = self.enemy; + SUB_UseTargets (); +}; + + +//============================================================================ + +void() walkmonster_start_go = +{ +local string stemp; +local entity etemp; + + self.origin_z = self.origin_z + 1; // raise off floor a bit + droptofloor(); + + if (!walkmove(0,0)) + { + dprint ("walkmonster in wall!\n"); + dprint (self.classname); + dprint (" at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 20; + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + + self.flags = self.flags | FL_MONSTER; + + if (self.target) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } +// this used to be an objerror + if (self.movetarget.classname == "path_corner") + self.th_walk (); + else + self.pausetime = 99999999; + self.th_stand (); + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } + +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; +}; + + +void() walkmonster_start = +{ +// delay drop to floor to make sure all doors have been spawned +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.think = walkmonster_start_go; + total_monsters = total_monsters + 1; +}; + + + +void() flymonster_start_go = +{ + self.takedamage = DAMAGE_AIM; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 10; + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + + self.flags = self.flags | FL_FLY; + self.flags = self.flags | FL_MONSTER; + + if (!walkmove(0,0)) + { + dprint ("flymonster in wall!\n"); + dprint (self.classname); + dprint (" at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } + + if (self.target) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } +// this used to be an objerror + if (self.movetarget.classname == "path_corner") + self.th_walk (); + else + self.pausetime = 99999999; + self.th_stand (); + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } +}; + +void() flymonster_start = +{ +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.think = flymonster_start_go; + total_monsters = total_monsters + 1; +}; + + +void() swimmonster_start_go = +{ + if (deathmatch) + { + remove(self); + return; + } + + self.takedamage = DAMAGE_AIM; + total_monsters = total_monsters + 1; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 10; + self.view_ofs = '0 0 10'; + self.use = monster_use; + self.team = TEAM_MONSTERS; + + self.flags = self.flags | FL_SWIM; + self.flags = self.flags | FL_MONSTER; + + if (self.target) + { + self.goalentity = self.movetarget = find(world, targetname, self.target); + if (!self.movetarget) + { + dprint ("Monster can't find target at "); + dprint (vtos(self.origin)); + dprint ("\n"); + } +// this used to be an objerror + self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin); + self.th_walk (); + } + else + { + self.pausetime = 99999999; + self.th_stand (); + } + +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; +}; + +void() swimmonster_start = +{ +// spread think times so they don't all happen at same time + self.nextthink = self.nextthink + random()*0.5; + self.think = swimmonster_start_go; + +// Don't count here. swimmonster_start_go already increments +// the monster count!!! +// total_monsters = total_monsters + 1; +}; + + diff --git a/quakec_rogue/morph.qc b/quakec_rogue/morph.qc new file mode 100644 index 0000000..0a8c887 --- /dev/null +++ b/quakec_rogue/morph.qc @@ -0,0 +1,600 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// morph.qc + +$skin aztec +$skin pharoah +$skin roman +$skin florskin + +$frame mfstand1 +//$frame mfup01 mfup02 mfup03 mfup04 mfup05 mfup06 +//$frame mfup07 mfup08 mfup09 mfup10 mfup11 mfup12 + +$frame start01 start02 start03 start04 start05 start06 +$frame start07 start08 start09 start10 start11 start12 +$frame start13 start14 start15 start16 start17 start18 +$frame start19 start20 start21 start22 start23 start24 +$frame start25 start26 start27 start28 start29 start30 start31 + +$frame mfrun01 mfrun02 mfrun03 mfrun04 mfrun05 mfrun06 +$frame mfrun07 mfrun08 mfrun09 mfrun10 mfrun11 + +$frame mfwlk01 mfwlk02 mfwlk03 mfwlk04 mfwlk05 mfwlk06 mfwlk07 mfwlk08 +$frame mfwlk09 mfwlk10 mfwlk11 mfwlk12 mfwlk13 + +$frame mfata01 mfata02 mfata03 mfata04 mfata05 +$frame mfata06 mfata07 mfata08 mfata09 + +$frame mfatb01 mfatb02 mfatb03 mfatb04 mfatb05 mfatb06 +$frame mfatb07 mfatb08 mfatb09 mfatb10 mfatb11 + +$frame mfatc01 mfatc02 mfatc03 mfatc04 mfatc05 mfatc06 +$frame mfatc07 mfatc08 mfatc09 mfatc10 mfatc11 mfatc12 +$frame mfatc13 mfatc14 mfatc15 mfatc16 + +$frame mfknck01 mfknck02 mfknck03 mfknck04 mfknck05 mfknck06 +$frame mfknck07 mfknck08 mfknck09 mfknck10 mfknck11 mfknck12 + +$frame mfpain01 mfpain02 mfpain03 mfpain04 mfpain05 +$frame mfpain06 mfpain07 mfpain08 mfpain09 mfpain10 + +$frame mfpbin01 mfpbin02 mfpbin03 mfpbin04 mfpbin05 +$frame mfpbin06 mfpbin07 + +$frame mfdth01 mfdth02 mfdth03 mfdth04 mfdth05 +$frame mfdth06 mfdth07 mfdth08 mfdth09 mfdth10 +$frame mfdth11 mfdth12 mfdth13 mfdth14 mfdth15 +$frame mfdth16 mfdth17 mfdth18 mfdth19 mfdth20 +$frame mfdth21 + +float MORPH_AZTEC = 2; +float MORPH_EGYPT = 4; +float MORPH_GREEK = 8; + +void() morph_teleport; + +// ======================== +// standing +// ======================== +void() morph_stand1 = [ $mfstand1, morph_stand1 ] { ai_stand(); }; + +// ======================== +// movement +// ======================== +// 3 2 1 1 0 1 2 3 4 5 4 2 1 0 +// 3 2 1 2 3 4 4 3 2 1 2 3 3 +// 2 4 4 4 3 3 2 2 4 4 3 2 1 +// 4 5 5 4 3 2 2 4 5 4 3 2 2 +void() morph_walk1 = [ $mfwlk01, morph_walk2 ] {ai_walk(3);}; +void() morph_walk2 = [ $mfwlk02, morph_walk3 ] {ai_walk(2);}; +void() morph_walk3 = [ $mfwlk03, morph_walk4 ] {ai_walk(4);}; +void() morph_walk4 = [ $mfwlk04, morph_walk5 ] {ai_walk(5);}; +void() morph_walk5 = [ $mfwlk05, morph_walk6 ] {ai_walk(5);}; +void() morph_walk6 = [ $mfwlk06, morph_walk7 ] {ai_walk(4);}; + //1 +void() morph_walk7 = [ $mfwlk07, morph_walk8 ] {ai_walk(2);}; + //1 +void() morph_walk8 = [ $mfwlk08, morph_walk9 ] {ai_walk(3);}; +void() morph_walk9 = [ $mfwlk09, morph_walk10 ] {ai_walk(4);}; +void() morph_walk10 = [ $mfwlk10, morph_walk11 ] {ai_walk(5);}; +void() morph_walk11 = [ $mfwlk11, morph_walk12 ] {ai_walk(5);}; +void() morph_walk12 = [ $mfwlk12, morph_walk13 ] {ai_walk(4);}; +void() morph_walk13 = [ $mfwlk13, morph_walk1 ] {ai_walk(3);}; + // 1 + +// 8 6 4 10 13 13 11 10 +// 11 15 10 10 8 15 10 8 8 8 8 +// 6 9 13 13 9 6 9 13 15 13 9 +void() morph_run1 = [ $mfrun01, morph_run2 ] { ai_run( 7);}; + // 33 +void() morph_run2 = [ $mfrun02, morph_run3 ] { ai_run(11);}; +void() morph_run3 = [ $mfrun03, morph_run4 ] { ai_run(16);}; +void() morph_run4 = [ $mfrun04, morph_run5 ] { ai_run(16);}; + // 36 +void() morph_run5 = [ $mfrun05, morph_run6 ] { ai_run(11);}; +void() morph_run6 = [ $mfrun06, morph_run7 ] { ai_run( 7);}; + // 38 +void() morph_run7 = [ $mfrun07, morph_run8 ] { ai_run(11);}; +void() morph_run8 = [ $mfrun08, morph_run9 ] { ai_run(15);}; + // 40 +void() morph_run9 = [ $mfrun09, morph_run10 ] {ai_run(19);}; + +void() morph_run10 =[ $mfrun10, morph_run11 ] {ai_run(15);}; + +void() morph_run11 =[ $mfrun11, morph_run1 ] { ai_run(11);}; + // 43 + +// ======================== +// new attack code +// ======================== +void() morph_stab2 = +{ + local float dist; + local float attackDamage; + local vector org, dir; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + ai_face(); + + dir = self.enemy.origin - self.origin + self.enemy.view_ofs; + dist = vlen ( dir ); + dir = normalize ( dir ); + makevectors (self.angles); + + if ( dist <= 90 ) + { + sound (self, CHAN_WEAPON, "enforcer/enfstop.wav", 1, ATTN_STATIC); + attackDamage = (random() * 10) + 20; + T_Damage (self.enemy, self, self, attackDamage); + SpawnBlood ( self.enemy.origin, v_forward * 150, 14); + } + else + { + self.effects = self.effects | EF_MUZZLEFLASH; + org = self.origin + v_forward * 80 + v_right * 4 + '0 0 4'; + LaunchLaser(org, dir); + if ( dist ) + { + LaunchLaser(org, (dir + v_right * 0.04)); + LaunchLaser(org, (dir - v_right * 0.04)); + } + else + { + LaunchLaser(org, (dir + v_right * 0.10)); + LaunchLaser(org, (dir - v_right * 0.10)); + } + } +}; + +// ======================== +// hand to hand attack +// ======================== + +void() morph_attack01 = [ $mfatb01, morph_attack02 ] {ai_face();}; +void() morph_attack02 = [ $mfatb02, morph_attack03 ] {ai_face();}; +void() morph_attack03 = [ $mfatb03, morph_attack04 ] {ai_face();}; +void() morph_attack04 = [ $mfatb04, morph_attack05 ] {ai_face();}; +void() morph_attack05 = [ $mfatb05, morph_attack06 ] {ai_face();}; +void() morph_attack06 = [ $mfatb06, morph_attack07 ] {ai_face();}; +void() morph_attack07 = [ $mfatb07, morph_attack08 ] {morph_stab2();}; +void() morph_attack08 = [ $mfatb08, morph_attack09 ] {ai_face();}; +void() morph_attack09 = [ $mfatb09, morph_attack10 ] {ai_face();}; +void() morph_attack10 = [ $mfatb10, morph_attack11 ] {ai_face();}; +void() morph_attack11 = [ $mfatb11, morph_attack12 ] {ai_face();}; +void() morph_attack12 = [ $mfatb01, morph_run1 ] {ai_face();}; + +void() morph_bigattack01 = [ $mfatc01, morph_bigattack02 ] { ai_face(); }; +void() morph_bigattack02 = [ $mfatc02, morph_bigattack03 ] { ai_face(); }; +void() morph_bigattack03 = [ $mfatc03, morph_bigattack04 ] { ai_face(); }; +void() morph_bigattack04 = [ $mfatc04, morph_bigattack05 ] { ai_face(); }; +void() morph_bigattack05 = [ $mfatc05, morph_bigattack06 ] { ai_face(); }; +void() morph_bigattack06 = [ $mfatc06, morph_bigattack07 ] { ai_face(); }; +void() morph_bigattack07 = [ $mfatc07, morph_bigattack08 ] { ai_face(); }; +void() morph_bigattack08 = [ $mfatc08, morph_bigattack09 ] { ai_face(); }; +void() morph_bigattack09 = [ $mfatc09, morph_bigattack10 ] { ai_face(); }; +void() morph_bigattack10 = [ $mfatc10, morph_bigattack11 ] { morph_stab2();}; +void() morph_bigattack11 = [ $mfatc11, morph_bigattack12 ] { ai_face(); }; +void() morph_bigattack12 = [ $mfatc12, morph_bigattack13 ] { ai_face(); }; +void() morph_bigattack13 = [ $mfatc13, morph_bigattack14 ] { ai_face(); }; +void() morph_bigattack14 = [ $mfatc14, morph_bigattack15 ] { ai_face(); }; +void() morph_bigattack15 = [ $mfatc15, morph_bigattack16 ] { ai_face(); }; +void() morph_bigattack16 = [ $mfatc16, morph_bigattack17 ] { ai_face(); }; +void() morph_bigattack17 = [ $mfatc01, morph_run1 ] { ai_face(); }; + +void() morph_smack = +{ + local vector delta; + local float ldmg; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + ai_face(); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() * 10) + 10; + T_Damage (self.enemy, self, self, ldmg); + + makevectors (self.angles); + self.enemy.velocity = v_forward * 100 + '0 0 100'; +}; + +void() morph_knockback01 = [ $mfknck01, morph_knockback02 ] { ai_face(); }; +void() morph_knockback02 = [ $mfknck02, morph_knockback03 ] { ai_face(); }; +void() morph_knockback03 = [ $mfknck03, morph_knockback04 ] { ai_face(); }; +void() morph_knockback04 = [ $mfknck04, morph_knockback05 ] { ai_face(); }; +void() morph_knockback05 = [ $mfknck05, morph_knockback06 ] { ai_face(); }; +void() morph_knockback06 = [ $mfknck06, morph_knockback07 ] { ai_face(); }; +void() morph_knockback07 = [ $mfknck07, morph_knockback08 ] { ai_face(); }; +void() morph_knockback08 = [ $mfknck08, morph_knockback09 ] { ai_face(); }; +void() morph_knockback09 = [ $mfknck09, morph_knockback10 ] { morph_smack(); }; +void() morph_knockback10 = [ $mfknck10, morph_knockback11 ] { ai_face(); }; +void() morph_knockback11 = [ $mfknck11, morph_knockback12 ] { ai_face(); }; +void() morph_knockback12 = [ $mfknck12, morph_run1 ] { ai_face(); }; + +void() morph_attack = +{ + local float attackChoice; + + attackChoice = random(); + + if (attackChoice < 0.50) + morph_bigattack01(); + else if (attackChoice < 0.75) + morph_attack01(); + else + morph_knockback01(); +}; + +// ======================== +// ranged attack +// ======================== +void() morph_fire = +{ + local vector org, dir; + + ai_face(); + + self.effects = self.effects | EF_MUZZLEFLASH; + + makevectors (self.angles); + org = self.origin + v_forward * 30 + v_right * 8.5 + '0 0 16'; + dir = normalize ( self.enemy.origin - self.origin ) ; + LaunchLaser(org, dir); + if ( vlen (self.enemy.origin - self.origin) > 400 ) + { + LaunchLaser(org, (dir + v_right * 0.04)); + LaunchLaser(org, (dir - v_right * 0.04)); + } + else + { + LaunchLaser(org, (dir + v_right * 0.10)); + LaunchLaser(org, (dir - v_right * 0.10)); + } + +}; + +void() morph_fire1 = [ $mfata01, morph_fire2 ] {ai_face();}; +void() morph_fire2 = [ $mfata02, morph_fire3 ] {ai_face();}; +void() morph_fire3 = [ $mfata03, morph_fire4 ] {ai_face();}; +void() morph_fire4 = [ $mfata04, morph_fire5 ] {morph_fire();}; +void() morph_fire5 = [ $mfata05, morph_fire6 ] {ai_face();}; +void() morph_fire6 = [ $mfata06, morph_fire7 ] {ai_face();}; +void() morph_fire7 = [ $mfata07, morph_fire8 ] {ai_face();}; +void() morph_fire8 = [ $mfata08, morph_fire9] {ai_face();}; +void() morph_fire9 = [ $mfata09, morph_run1 ] {ai_face();}; + +// ======================== +// pain +// ======================== +void() morph_painA1 = [ $mfpain01, morph_painA2 ] {}; +void() morph_painA2 = [ $mfpain02, morph_painA3 ] {}; +void() morph_painA3 = [ $mfpain03, morph_painA4 ] {}; +void() morph_painA4 = [ $mfpain04, morph_painA5 ] {}; +void() morph_painA5 = [ $mfpain05, morph_painA6 ] {}; +void() morph_painA6 = [ $mfpain06, morph_painA7 ] {}; +void() morph_painA7 = [ $mfpain07, morph_painA8 ] {}; +void() morph_painA8 = [ $mfpain08, morph_painA9 ] {}; +void() morph_painA9 = [ $mfpain09, morph_painA10 ] {}; +void() morph_painA10 = [ $mfpain10, morph_run1 ] {morph_teleport();}; + +void() morph_painB1 = [ $mfpbin01, morph_painB2 ] {}; +void() morph_painB2 = [ $mfpbin02, morph_painB3 ] {}; +void() morph_painB3 = [ $mfpbin03, morph_painB4 ] {}; +void() morph_painB4 = [ $mfpbin04, morph_painB5 ] {}; +void() morph_painB5 = [ $mfpbin05, morph_painB6 ] {}; +void() morph_painB6 = [ $mfpbin06, morph_painB7 ] {}; +void() morph_painB7 = [ $mfpbin07, morph_run1 ] {morph_teleport();}; + +void() morph_pain = +{ + local float painChoice; + + if (skill == 3) + { + if (random() > 0.5) + morph_teleport(); + return; + } + + if (self.pain_finished > time) + return; + if (random() > 0.25) + return; + + painChoice = random(); + self.pain_finished = time + 2; + + sound (self, CHAN_VOICE, "guard/pain1.wav", 1, ATTN_NORM ); + + if ( painChoice > 0.6) + self.think = morph_painB1; + else + self.think = morph_painA1; + + self.nextthink = time + 0.1; +}; + +// ======================== +// die +// ======================== +void() morph_die1 = [ $mfdth01, morph_die2 ] {}; // 122 +void() morph_die2 = [ $mfdth02, morph_die3 ] {}; +void() morph_die3 = [ $mfdth03, morph_die4 ] {}; +void() morph_die4 = [ $mfdth04, morph_die5 ] {}; +void() morph_die5 = [ $mfdth05, morph_die6 ] {}; // 126 +void() morph_die6 = [ $mfdth06, morph_die7 ] {}; +void() morph_die7 = [ $mfdth07, morph_die8 ] {}; +void() morph_die8 = [ $mfdth08, morph_die9 ] {}; // 129 +void() morph_die9 = [ $mfdth09, morph_die10 ] { self.skin = self.skin + 1; }; +void() morph_die10 = [ $mfdth10, morph_die11 ] {}; +void() morph_die11 = [ $mfdth11, morph_die12 ] {}; +void() morph_die12 = [ $mfdth12, morph_die13 ] {}; // 132 +void() morph_die13 = [ $mfdth13, morph_die14 ] {}; +void() morph_die14 = [ $mfdth14, morph_die15 ] {}; +void() morph_die15 = [ $mfdth15, morph_die16 ] {}; +void() morph_die16 = [ $mfdth16, morph_die17 ] {}; // 136 +void() morph_die17 = [ $mfdth17, morph_die18 ] {}; +void() morph_die18 = [ $mfdth18, morph_die19 ] {}; +void() morph_die19 = [ $mfdth19, morph_die20 ] {}; +void() morph_die20 = [ $mfdth20, morph_die21 ] {}; // 140 +void() morph_die21 = [ $mfdth21, morph_die21 ] +{ + remove(self); +}; + +void() morph_die = +{ + sound ( self, CHAN_VOICE, "guard/death.wav", 1, ATTN_NORM); + self.solid = SOLID_NOT; + self.think = morph_die1; + self.nextthink = time + 0.1; +}; + + +// ======================== +// hide in floor +// ======================== +void() morph_wake1 = [ $start01, morph_wake2 ] +{ + sound ( self, CHAN_VOICE, "guard/see1.wav", 1, ATTN_NORM ); + if(self.owner) + { +// self.solid = SOLID_SLIDEBOX; +// self.takedamage = DAMAGE_NO; + + total_monsters = total_monsters + 1; + + self.owner.childrenSpawned = self.owner.childrenSpawned + 1; + + WriteByte (MSG_BROADCAST, SVC_UPDATESTAT); + WriteByte (MSG_BROADCAST, STAT_TOTALMONSTERS); + WriteLong (MSG_BROADCAST, total_monsters); + } +}; +void() morph_wake2 = [ $start02, morph_wake3 ] {}; +void() morph_wake3 = [ $start03, morph_wake4 ] {}; +void() morph_wake4 = [ $start04, morph_wake5 ] {}; +void() morph_wake5 = [ $start05, morph_wake6 ] {}; +void() morph_wake6 = [ $start06, morph_wake7 ] {}; +void() morph_wake7 = [ $start07, morph_wake8 ] {}; +void() morph_wake8 = [ $start08, morph_wake9 ] {}; +void() morph_wake9 = [ $start09, morph_wake10 ] {}; +void() morph_wake10 = [ $start10, morph_wake11 ] {}; +void() morph_wake11 = [ $start11, morph_wake12 ] {}; +void() morph_wake12 = [ $start12, morph_wake13 ] {}; +void() morph_wake13 = [ $start13, morph_wake14 ] {}; +void() morph_wake14 = [ $start14, morph_wake15 ] {}; +void() morph_wake15 = [ $start15, morph_wake16 ] + { self.skin = 1; }; +void() morph_wake16 = [ $start16, morph_wake17 ] {}; +void() morph_wake17 = [ $start17, morph_wake18 ] {}; +void() morph_wake18 = [ $start18, morph_wake20 ] {}; +//void() morph_wake18 = [ $start18, morph_wake19 ] {}; +//void() morph_wake19 = [ $start19, morph_wake20 ] {}; +void() morph_wake20 = [ $start20, morph_wake21 ] {}; +void() morph_wake21 = [ $start21, morph_wake22 ] {}; +void() morph_wake22 = [ $start22, morph_wake23 ] {}; +void() morph_wake23 = [ $start23, morph_wake24 ] {}; +void() morph_wake24 = [ $start24, morph_wake25 ] {}; +void() morph_wake25 = [ $start25, morph_wake26 ] {}; +void() morph_wake26 = [ $start26, morph_wake27 ] {}; +void() morph_wake27 = [ $start27, morph_wake28 ] {}; +void() morph_wake28 = [ $start28, morph_wake29 ] {}; +void() morph_wake29 = [ $start29, morph_wake30 ] {}; +void() morph_wake30 = [ $start30, morph_wake31 ] {}; +void() morph_wake31 = [ $start31, morph_stand1 ] +{ + self.solid = SOLID_SLIDEBOX; + self.takedamage = DAMAGE_AIM; + self.skin = self.skin - 1; + if ( self.owner ) + { + self.think = morph_run1; + self.nextthink = time + 0.1; + } +}; + +void(entity theMorph) morph_setup = +{ + theMorph.solid = SOLID_SLIDEBOX; + theMorph.movetype = MOVETYPE_STEP; + theMorph.takedamage = DAMAGE_NO; + theMorph.flags = theMorph.flags | FL_MONSTER; + theMorph.ideal_yaw = theMorph.angles * '0 1 0'; + if (!theMorph.yaw_speed) + theMorph.yaw_speed = 20; + theMorph.view_ofs = '0 0 25'; + theMorph.frame = $start01; + + + setmodel (theMorph, theMorph.model); + + setsize (theMorph, VEC_HULL_MIN, VEC_HULL_MAX); + + theMorph.th_stand = morph_stand1; + theMorph.th_walk = morph_walk1; + theMorph.th_run = morph_run1; + theMorph.th_missile = morph_fire1; + theMorph.th_melee = morph_attack; + theMorph.th_pain = morph_pain; + theMorph.th_die = morph_die; + + if ( theMorph.owner ) + { + theMorph.effects = 0; + theMorph.health = 200; + theMorph.spawnflags = theMorph.owner.spawnflags; + } + else + { + theMorph.effects = theMorph.effects | EF_DIMLIGHT; + theMorph.health = 2000; + } + +// theMorph.frame = $start01; + theMorph.skin = 2; +}; + +void() morph_wake = +{ + if (isSpawnPointEmpty(self)) + { + morph_setup(self); + self.think = morph_wake1; + } + else + { + self.think = morph_wake; + } + + self.nextthink = time + 0.1; +}; + +void() morph_use = +{ +// self.enemy = activator; + if ( self.delay ) + { + self.think = morph_wake; + self.nextthink = time + self.delay; + } + else + { + self.think = morph_wake; + self.nextthink = time + 0.1; + } +}; + + +void() morph_teleport = +{ + local entity theDest; + local entity theChild; + local entity selfTemp; + + if (self.owner) + return; + + if (self.childrenSpawned > (1+skill)) + return; + + theDest = overlord_find_dest3(); + if (theDest == world) + return; + + theChild = spawn(); + theChild.model = self.model; + theChild.angles = theDest.mangle; + theChild.owner = self; + theChild.enemy = self.enemy; + theChild.movetarget = self.movetarget; + theChild.goalentity = self.goalentity; + theChild.mangle = theDest.mangle; + theChild.classname = "monster_morph"; + morph_setup (theChild); + setorigin (theChild, theDest.origin); + droptofloor(); + + theChild.think = morph_wake1; + theChild.nextthink = time + 0.3; +}; + +/*QUAKED monster_morph (1 0 0) (-16 -16 -24) (16 16 32) Ambush Aztec Egyptian Greek +The morphing monster. + +Select the look of the monster (Aztec,Greek,Egyptian). +If the monster has a targetname (triggered by something) he will rise up out of the floor when triggered. +*/ +void() monster_morph = +{ + if (deathmatch) + { + remove(self); + return; + } + + if (self.spawnflags & MORPH_AZTEC) // Aztec + { + precache_model ("progs/morph_az.mdl"); + self.model = "progs/morph_az.mdl"; + } + else if (self.spawnflags & MORPH_EGYPT) + // Egyptian + { + precache_model ("progs/morph_eg.mdl"); + self.model = "progs/morph_eg.mdl"; + } + else if (self.spawnflags & MORPH_GREEK) // greek + { + precache_model ("progs/morph_gr.mdl"); + self.model = "progs/morph_gr.mdl"; + } + else + objerror ( "monster_morph: no skin selection!"); + + precache_model2 ("progs/laser.mdl"); + precache_sound2 ("enforcer/enfire.wav"); + precache_sound2 ("guard/death.wav"); + precache_sound2 ("guard/pain1.wav"); + precache_sound2 ("guard/see1.wav"); + + total_monsters = total_monsters + 1; + if(self.targetname) + { + self.use = morph_use; + } + else + { + morph_wake (); + } +}; + diff --git a/quakec_rogue/motd.qc b/quakec_rogue/motd.qc new file mode 100644 index 0000000..c3e585a --- /dev/null +++ b/quakec_rogue/motd.qc @@ -0,0 +1,89 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Rogue Teamplay Variants Message of the Day function +// Feb'97 by ZOID +// Under contract to id software for Rogue Entertainment + +.float motd_time; +.float motd_count; + +void() MOTD = +{ + local string tp; + + if (self.motd_count < 5) { + if (teamplay > 3 && teamplay < 7) + { + tp = ftos(teamplay); + stuffcmd(self, "teamplay "); + stuffcmd(self, tp); + stuffcmd(self, "\n"); + } + self.motd_time = time + 1; + self.motd_count = self.motd_count + 1; +/* + if (teamplay == TEAM_NORMAL_NODAMAGE) + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nTeam play: No Friendly Fire\n"); + else if (teamplay == TEAM_NORMAL_DAMAGE) + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nTeam play: Friendly Fire\n"); +*/ + if (teamplay == TEAM_DMATCH_TAG) + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nDeathmatch Tag!\n"); + else if (teamplay == TEAM_CTF) + if (self.steam == TEAM1) + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nCAPTURE THE FLAG!\n\nYou are ��� team!\n"); + else + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nCAPTURE THE FLAG!\n\nYou are ���� team!\n"); + else if (teamplay == TEAM_CTF_ONEFLAG) + if (self.steam == TEAM1) + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nCAPTURE THE FLAG!\n(One Flag Mode)\n\nYou are ��� team!\n"); + else + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nCAPTURE THE FLAG!\n(One Flag Mode)\n\nYou are ���� team!\n"); + else if (teamplay == TEAM_CTF_ALT) + if (self.steam == TEAM1) + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nCAPTURE THE FLAG!\n(Three Team Mode)\n\nYou are ��� team!\n"); + else if (self.steam == TEAM2) + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nCAPTURE THE FLAG!\n(Three Team Mode)\n\nYou are ���� team!\n"); + else + centerprint(self, "Welcome to\nThe Dissolution of Eternity!\nBy Rogue Entertainment\n\nCAPTURE THE FLAG!\n(Three Team Mode)\n\nYou are ���� team!\n"); + return; + } + self.motd_time = 0; +}; + +void() CheckMOTD = +{ +// if (self.motd_time && self.motd_time < time) + if (self.motd_time) + { + if(self.motd_time < time) + MOTD(); + } +}; + +void() SetMOTD = +{ + self.motd_time = time + 3; + if ( teamplay == 3 ) + self.motd_count = 2; + else + self.motd_count = 0; +}; + diff --git a/quakec_rogue/mult_wpn.qc b/quakec_rogue/mult_wpn.qc new file mode 100644 index 0000000..d01cecf --- /dev/null +++ b/quakec_rogue/mult_wpn.qc @@ -0,0 +1,570 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// multi_weapons.qc +// pmack +// sept 96 + +//============================================================================= +// Multi Grenade Code +//============================================================================= +void() MultiGrenadeTouch; + +//================================ +//================================ +void() MiniGrenadeExplode = +{ + if ( self.owner.classname == "player") + T_RadiusDamage (self, self.owner, 90, world); + else + T_RadiusDamage (self, self.owner, 60, world); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 230); + WriteByte (MSG_BROADCAST, 5); + + BecomeExplosion (); +}; + +//================================ +//================================ +void(float offsetAngle) MiniGrenadeLaunch = +{ + local entity missile, mpuff; + local float tempRand; + + missile = spawn (); + missile.owner = self.owner; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + missile.classname = "MiniGrenade"; + +// set missile speed + missile.v_angle = self.v_angle; + missile.v_angle_y = missile.v_angle_y + offsetAngle; + makevectors (missile.v_angle); + + missile.velocity = v_forward*100 + v_up*400; + tempRand = (crandom()*60) - 30; + missile.velocity = missile.velocity + tempRand * v_forward; + tempRand = (crandom()*40) - 20; + missile.velocity = missile.velocity + tempRand * v_right; + tempRand = (crandom()*60) - 30; + missile.velocity = missile.velocity + tempRand * v_up; + + missile.avelocity = '300 300 300'; + missile.angles = vectoangles(missile.velocity); + missile.touch = MultiGrenadeTouch; + + setmodel (missile, "progs/mervup.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); + +// set missile duration + missile.nextthink = time + 1 + (crandom() * 0.5); + missile.think = MiniGrenadeExplode; +}; + +//================================ +//================================ +void() MultiGrenadeExplode = +{ + MiniGrenadeLaunch(0); + MiniGrenadeLaunch(72); + MiniGrenadeLaunch(144); + MiniGrenadeLaunch(216); + MiniGrenadeLaunch(288); + + remove (self); +}; + +//================================ +//================================ +void() MultiGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage == DAMAGE_AIM) + { + if (self.classname == "MiniGrenade") + MiniGrenadeExplode(); + else + { + if (self.owner.classname == "player") + GrenadeExplode(); + else + MiniGrenadeExplode(); + } + return; + } + // bounce sound + sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +//================================ +//================================ +void() W_FireMultiGrenade = +{ + local entity missile, mpuff; + + self.currentammo = self.ammo_multi_rockets = self.ammo_multi_rockets - 1; + UpdateAmmoCounts (self); + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + missile.classname = "MultiGrenade"; + +// set missile speed + makevectors (self.v_angle); + if (self.v_angle_x) + missile.velocity = v_forward*600 + v_up * 200 + crandom()*v_right*10 + crandom()*v_up*10; + else + { + missile.velocity = aim(self, 10000); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + } + + missile.avelocity = '300 300 300'; + missile.angles = vectoangles(missile.velocity); + missile.touch = MultiGrenadeTouch; + +// set missile duration + missile.nextthink = time + 1; + missile.think = MultiGrenadeExplode; + + setmodel (missile, "progs/mervup.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); +}; + +//============================================================================= +// Multi Rocket Code +//============================================================================= + +//================================ +//================================ +void() MultiRocketExplode = +{ + local float damg; + +// Stock Single Rocket Damage... +// damg = 100 + random()*20; + + damg = 60 + random()*15; + + if (other.health) + { + if (other.classname == "monster_shambler") + damg = damg * 0.5; // mostly immune + if (other.classname == "monster_dragon") + damg = damg * 0.5; // mostly immune + T_Damage (other, self, self.owner, damg ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact +// Stock single rocket damage. +// T_RadiusDamage (self, self.owner, 120, other); + + T_RadiusDamage (self, self.owner, 75, other); + +// sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +//================================ +//================================ +void() MultiRocketTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + MultiRocketExplode (); +}; + + +//================================ +//================================ +void() HomingMissileThink = +{ + local vector dir; + + if (self.enemy.health < 1) + { + remove(self); + return; + } + + dir = normalize(self.enemy.origin - self.origin); + self.velocity = dir * 1000; + self.nextthink = time + 0.1; + self.think = HomingMissileThink; +}; + + +//================================ +//================================ +void() HomingMissileAcquire = +{ + local vector oldVelocity; + local vector aimangle; + + if ( self.delay < time ) + { + MultiRocketExplode (); + return; + } + + oldVelocity = self.velocity; + makevectors (self.v_angle); + self.velocity = aim (self, 1000); + self.velocity = self.velocity * 1000; + + aimangle = self.origin + self.velocity; + traceline ( self.origin, aimangle, FALSE, self ); + if (trace_fraction < 1) + { + if (trace_ent.flags & FL_MONSTER) + { + self.enemy = trace_ent; + HomingMissileThink(); + return; + } + } + + self.velocity = oldVelocity; + self.v_angle = vectoangles ( self.velocity ); + self.angles = self.v_angle; + self.think = HomingMissileAcquire; + self.nextthink = time + 0.2; +}; + +//================================ +//================================ +void(float offset, float frameNum) MultiRocketLaunch = +{ + local entity missile, mpuff; + local vector aimangle; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.solid = SOLID_BBOX; + missile.classname = "MultiRocket"; + missile.delay = time + 4; + missile.frame = frameNum; + missile.touch = MultiRocketTouch; + + if (deathmatch || coop) + setmodel (missile, "progs/rockup_d.mdl"); + else + setmodel (missile, "progs/rockup.mdl"); + + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + v_forward*8 + '0 0 16'); + + if ( coop || deathmatch) + { + aimangle = self.v_angle; + aimangle_y = aimangle_y + (offset * 0.66); + makevectors (aimangle); + missile.velocity = aim(self, 1000); + missile.velocity = missile.velocity * 1000; + missile.angles = vectoangles(missile.velocity); + + missile.think = MultiRocketExplode; + missile.nextthink = time + 4; + } + else + { + makevectors (self.v_angle); + missile.velocity = v_forward * 1000 - v_right*offset*8; + missile.angles = vectoangles(missile.velocity); + missile.v_angle = self.v_angle; + + aimangle = missile.origin + missile.velocity; + traceline ( missile.origin, aimangle, FALSE, self ); + if (trace_fraction < 1) + { + if (trace_ent.flags & FL_MONSTER) + { + missile.enemy = trace_ent; + missile.think = HomingMissileThink; + return; + } + } + + missile.think = HomingMissileAcquire; + missile.nextthink = time + 0.1; + } +}; + +//================================ +//================================ +void() W_FireMultiRocket = +{ + self.currentammo = self.ammo_multi_rockets = self.ammo_multi_rockets - 1; + UpdateAmmoCounts (self); + + sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + MultiRocketLaunch ( -10, 2 ); + MultiRocketLaunch ( -5, 3 ); + MultiRocketLaunch ( 5, 0 ); + MultiRocketLaunch ( 10, 1 ); +}; + +//============================================================================= +// Plasma Gun Code +//============================================================================= +void(vector p1, vector p2, entity from, float damage) PlasmaDamage = +{ + local entity e1, e2; + local vector f; + + f = p2 - p1; + normalize (f); + f_x = 0 - f_y; + f_y = f_x; + f_z = 0; + f = f*16; + + e1 = e2 = world; + + traceline (p1, p2, FALSE, self); + if (trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from.owner, damage); + if (self.classname == "player") + { + if (other.classname == "player") + trace_ent.velocity_z = trace_ent.velocity_z + 400; + } + } + e1 = trace_ent; + + traceline (p1 + f, p2 + f, FALSE, self); + if (trace_ent != e1 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from.owner, damage); + } + e2 = trace_ent; + + traceline (p1 - f, p2 - f, FALSE, self); + if (trace_ent != e1 && trace_ent != e2 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from.owner, damage); + } +}; + +//================================ +//================================ +void(entity current, float doDamage) PlasmaDischarge = +{ + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, current); + WriteCoord (MSG_BROADCAST, current.origin_x); + WriteCoord (MSG_BROADCAST, current.origin_y); + WriteCoord (MSG_BROADCAST, current.origin_z); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + sound (self, CHAN_VOICE, "weapons/lhit.wav", 1, ATTN_NORM); + if (doDamage == 1) + PlasmaDamage (self.origin, current.origin, self, 50); +}; + +//================================ +//================================ +void() PlasmaGroundOut = +{ + local entity current, start; + local float monstersHit; + + monstersHit = 0; + current = findradius ( self.origin, 320 ); + start = current; + while ( monstersHit < 5 ) + { + if ( current.flags & FL_MONSTER || current.classname == "player") + { + if ( current != self.owner ) + { + traceline ( self.origin, current.origin, TRUE, world ); + if (trace_fraction == 1) + { + monstersHit = monstersHit + 1; + PlasmaDischarge ( current, 1 ); + } + } + } + current = current.chain; + if (start == current || !current) + return; + } +}; + +//================================ +//================================ +void() PlasmaTouch = +{ + local float damg; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + damg = 80 + random()*20; + sound (self, CHAN_WEAPON, "plasma/explode.wav", 1, ATTN_NORM); + + if (other.health) + { + if (other.classname == "monster_shambler") + damg = damg * 0.5; // mostly immune + T_Damage (other, self, self.owner, damg ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact + T_RadiusDamage (self, self.owner, 70, other); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 244); + WriteByte (MSG_BROADCAST, 3); + + PlasmaGroundOut(); + remove (self); +}; + +//================================ +//================================ +void() PlasmaLaunch = +{ + self.velocity = normalize(self.velocity); + self.velocity = self.velocity * 1250; + + self.nextthink = time + 5; + self.think = SUB_Remove; +}; + +void(vector org, vector dir) launch_plasma = +{ + local entity missile, mpuff; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.solid = SOLID_BBOX; + missile.classname = "plasma"; + +// set missile speed + missile.velocity = dir * 0.01; + missile.avelocity = '300 300 300'; + missile.angles = vectoangles(missile.velocity); + missile.touch = PlasmaTouch; + + setmodel (missile, "progs/plasma.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); + + sound (missile, CHAN_WEAPON, "plasma/flight.wav", 1, ATTN_NORM); + if (!deathmatch && !coop) + missile.effects = EF_BRIGHTLIGHT; + +// set missile duration + missile.think = PlasmaLaunch; + missile.nextthink = time + 0.1; +}; + +//================================ +//================================ +void() W_FirePlasma = +{ + local float cells; + local vector dir; + + if (self.ammo_plasma < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + +// explode if under water + if (self.waterlevel > 1) + { + cells = self.ammo_plasma; + self.ammo_plasma = 0; + W_SetCurrentAmmo (); + T_RadiusDamage (self, self, 35*cells, world); + return; + } + self.currentammo = self.ammo_plasma = self.ammo_plasma - 1; + UpdateAmmoCounts (self); + sound (self, CHAN_WEAPON, "plasma/fire.wav", 0.5, ATTN_NORM); + self.punchangle_x = -2; + + makevectors (self.v_angle); + dir = aim ( self, 1000 ); + launch_plasma (self.origin + v_forward*24 + '0 0 16', dir); +}; + diff --git a/quakec_rogue/mummy.qc b/quakec_rogue/mummy.qc new file mode 100644 index 0000000..ce7a582 --- /dev/null +++ b/quakec_rogue/mummy.qc @@ -0,0 +1,526 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +mummy + +============================================================================== +*/ +$cd /qwork/xpack/models/mummy + +$origin 0 0 24 + +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 +$frame stand9 stand10 stand11 stand12 stand13 stand14 stand15 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 walk11 +$frame walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 +$frame run13 run14 run15 run16 run17 run18 + +$frame atta1 atta2 atta3 atta4 atta5 atta6 atta7 atta8 atta9 atta10 atta11 +$frame atta12 atta13 + +$frame attb1 attb2 attb3 attb4 attb5 attb6 attb7 attb8 attb9 attb10 attb11 +$frame attb12 attb13 attb14 + +$frame attc1 attc2 attc3 attc4 attc5 attc6 attc7 attc8 attc9 attc10 attc11 +$frame attc12 + +$frame paina1 paina2 paina3 paina4 paina5 paina6 paina7 paina8 paina9 paina10 +$frame paina11 paina12 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 painb17 painb18 painb19 +$frame painb20 painb21 painb22 painb23 painb24 painb25 painb26 painb27 painb28 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 painc14 painc15 painc16 painc17 painc18 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 paine16 paine17 paine18 paine19 +$frame paine20 paine21 paine22 paine23 paine24 paine25 paine26 paine27 paine28 +$frame paine29 paine30 + +//============================================================================= + +.float inpain; + + +void() mummy_stand1 =[ $stand1, mummy_stand2 ] {ai_stand();}; +void() mummy_stand2 =[ $stand2, mummy_stand3 ] {ai_stand();}; +void() mummy_stand3 =[ $stand3, mummy_stand4 ] {ai_stand();}; +void() mummy_stand4 =[ $stand4, mummy_stand5 ] {ai_stand();}; +void() mummy_stand5 =[ $stand5, mummy_stand6 ] {ai_stand();}; +void() mummy_stand6 =[ $stand6, mummy_stand7 ] {ai_stand();}; +void() mummy_stand7 =[ $stand7, mummy_stand8 ] {ai_stand();}; +void() mummy_stand8 =[ $stand8, mummy_stand9 ] {ai_stand();}; +void() mummy_stand9 =[ $stand9, mummy_stand10 ] {ai_stand();}; +void() mummy_stand10 =[ $stand10, mummy_stand11 ] {ai_stand();}; +void() mummy_stand11 =[ $stand11, mummy_stand12 ] {ai_stand();}; +void() mummy_stand12 =[ $stand12, mummy_stand13 ] {ai_stand();}; +void() mummy_stand13 =[ $stand13, mummy_stand14 ] {ai_stand();}; +void() mummy_stand14 =[ $stand14, mummy_stand15 ] {ai_stand();}; +void() mummy_stand15 =[ $stand15, mummy_stand1 ] {ai_stand();}; + + +void() mummy_walk1 =[ $walk1, mummy_walk2 ] {ai_walk(0);}; +void() mummy_walk2 =[ $walk2, mummy_walk3 ] {ai_walk(3);}; +void() mummy_walk3 =[ $walk3, mummy_walk4 ] {ai_walk(5);}; +void() mummy_walk4 =[ $walk4, mummy_walk5 ] {ai_walk(4);}; +void() mummy_walk5 =[ $walk5, mummy_walk6 ] {ai_walk(2);}; +void() mummy_walk6 =[ $walk6, mummy_walk7 ] {ai_walk(0);}; +void() mummy_walk7 =[ $walk7, mummy_walk8 ] {ai_walk(0);}; +void() mummy_walk8 =[ $walk8, mummy_walk9 ] {ai_walk(0);}; +void() mummy_walk9 =[ $walk9, mummy_walk10 ] {ai_walk(0);}; +void() mummy_walk10 =[ $walk10, mummy_walk11 ] {ai_walk(0);}; +void() mummy_walk11 =[ $walk11, mummy_walk12 ] {ai_walk(3);}; +void() mummy_walk12 =[ $walk12, mummy_walk13 ] {ai_walk(4);}; +void() mummy_walk13 =[ $walk13, mummy_walk14 ] {ai_walk(2);}; +void() mummy_walk14 =[ $walk14, mummy_walk15 ] {ai_walk(0);}; +void() mummy_walk15 =[ $walk15, mummy_walk16 ] {ai_walk(0);}; +void() mummy_walk16 =[ $walk16, mummy_walk17 ] {ai_walk(0);}; +void() mummy_walk17 =[ $walk17, mummy_walk18 ] {ai_walk(0);}; +void() mummy_walk18 =[ $walk18, mummy_walk19 ] {ai_walk(0);}; +void() mummy_walk19 =[ $walk19, mummy_walk1 ] + { + ai_walk(0); + if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); + }; + +void() mummy_run1 =[ $run1, mummy_run2 ] {ai_run(2);self.inpain = 0;}; +void() mummy_run2 =[ $run2, mummy_run3 ] {ai_run(2);}; +void() mummy_run3 =[ $run3, mummy_run4 ] {ai_run(0);}; +void() mummy_run4 =[ $run4, mummy_run5 ] {ai_run(2);}; +void() mummy_run5 =[ $run5, mummy_run6 ] {ai_run(4);}; +void() mummy_run6 =[ $run6, mummy_run7 ] {ai_run(6);}; +void() mummy_run7 =[ $run7, mummy_run8 ] {ai_run(8);}; +void() mummy_run8 =[ $run8, mummy_run9 ] {ai_run(8);}; +void() mummy_run9 =[ $run9, mummy_run10 ] {ai_run(4);}; +void() mummy_run10 =[ $run10, mummy_run11 ] {ai_run(0);}; +void() mummy_run11 =[ $run11, mummy_run12 ] {ai_run(0);}; +void() mummy_run12 =[ $run12, mummy_run13 ] {ai_run(0);}; +void() mummy_run13 =[ $run13, mummy_run14 ] {ai_run(4);}; +void() mummy_run14 =[ $run14, mummy_run15 ] {ai_run(8);}; +void() mummy_run15 =[ $run15, mummy_run16 ] {ai_run(12);}; +void() mummy_run16 =[ $run16, mummy_run17 ] {ai_run(14);}; +void() mummy_run17 =[ $run17, mummy_run18 ] {ai_run(6);}; +void() mummy_run18 =[ $run18, mummy_run1 ] + { + ai_run(16); + if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); + if (random() > 0.8) + sound (self, CHAN_VOICE, "zombie/z_idle1.wav", 1, ATTN_IDLE); + }; + +/* +============================================================================= + +ATTACKS + +============================================================================= +*/ + +void() mummyGrenadeTouch = +{ + local float mummyDamage; + + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage) + { + mummyDamage = 15 + random() * 15; + + T_Damage (other, self, self.owner, mummyDamage ); + sound (self, CHAN_WEAPON, "zombie/z_hit.wav", 1, ATTN_NORM); + remove (self); + return; + } + sound (self, CHAN_WEAPON, "zombie/z_miss.wav", 1, ATTN_NORM); // bounce sound + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.touch = SUB_Remove; +}; + +/* +================ +mummyFireGrenade +================ +*/ +void(vector st) mummyFireGrenade = +{ + local entity missile, mpuff; + local vector org; + + sound (self, CHAN_WEAPON, "zombie/z_shot1.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + +// calc org + org = self.origin + st_x * v_forward + st_y * v_right + (st_z - 24) * v_up; + +// set missile speed + + makevectors (self.angles); + + missile.velocity = normalize(self.enemy.origin - org); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + + missile.avelocity = '3000 1000 2000'; + + missile.touch = mummyGrenadeTouch; + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + + +void() mummy_atta1 =[ $atta1, mummy_atta2 ] {ai_face();}; +void() mummy_atta2 =[ $atta2, mummy_atta3 ] {ai_face();}; +void() mummy_atta3 =[ $atta3, mummy_atta4 ] {ai_face();}; +void() mummy_atta4 =[ $atta4, mummy_atta5 ] {ai_face();}; +void() mummy_atta5 =[ $atta5, mummy_atta6 ] {ai_face();}; +void() mummy_atta6 =[ $atta6, mummy_atta7 ] {ai_face();}; +void() mummy_atta7 =[ $atta7, mummy_atta8 ] {ai_face();}; +void() mummy_atta8 =[ $atta8, mummy_atta9 ] {ai_face();}; +void() mummy_atta9 =[ $atta9, mummy_atta10 ] {ai_face();}; +void() mummy_atta10 =[ $atta10, mummy_atta11 ] {ai_face();}; +void() mummy_atta11 =[ $atta11, mummy_atta12 ] {ai_face();}; +void() mummy_atta12 =[ $atta12, mummy_atta13 ] {ai_face();}; +void() mummy_atta13 =[ $atta13, mummy_run1 ] + { ai_face(); mummyFireGrenade('-10 -22 30'); }; + +void() mummy_attb1 =[ $attb1, mummy_attb2 ] {ai_face();}; +void() mummy_attb2 =[ $attb2, mummy_attb3 ] {ai_face();}; +void() mummy_attb3 =[ $attb3, mummy_attb4 ] {ai_face();}; +void() mummy_attb4 =[ $attb4, mummy_attb5 ] {ai_face();}; +void() mummy_attb5 =[ $attb5, mummy_attb6 ] {ai_face();}; +void() mummy_attb6 =[ $attb6, mummy_attb7 ] {ai_face();}; +void() mummy_attb7 =[ $attb7, mummy_attb8 ] {ai_face();}; +void() mummy_attb8 =[ $attb8, mummy_attb9 ] {ai_face();}; +void() mummy_attb9 =[ $attb9, mummy_attb10 ] {ai_face();}; +void() mummy_attb10 =[ $attb10, mummy_attb11 ] {ai_face();}; +void() mummy_attb11 =[ $attb11, mummy_attb12 ] {ai_face();}; +void() mummy_attb12 =[ $attb12, mummy_attb13 ] {ai_face();}; +void() mummy_attb13 =[ $attb13, mummy_attb14 ] {ai_face();}; +void() mummy_attb14 =[ $attb13, mummy_run1 ] + { ai_face(); mummyFireGrenade('-10 -24 29'); }; + +void() mummy_attc1 =[ $attc1, mummy_attc2 ] {ai_face();}; +void() mummy_attc2 =[ $attc2, mummy_attc3 ] {ai_face();}; +void() mummy_attc3 =[ $attc3, mummy_attc4 ] {ai_face();}; +void() mummy_attc4 =[ $attc4, mummy_attc5 ] {ai_face();}; +void() mummy_attc5 =[ $attc5, mummy_attc6 ] {ai_face();}; +void() mummy_attc6 =[ $attc6, mummy_attc7 ] {ai_face();}; +void() mummy_attc7 =[ $attc7, mummy_attc8 ] {ai_face();}; +void() mummy_attc8 =[ $attc8, mummy_attc9 ] {ai_face();}; +void() mummy_attc9 =[ $attc9, mummy_attc10 ] {ai_face();}; +void() mummy_attc10 =[ $attc10, mummy_attc11 ] {ai_face();}; +void() mummy_attc11 =[ $attc11, mummy_attc12 ] {ai_face();}; +void() mummy_attc12 =[ $attc12, mummy_run1 ] + { ai_face(); mummyFireGrenade('-12 -19 29'); }; + +void() mummy_missile = +{ + local float r; + + r = random(); + + if (r < 0.3) + mummy_atta1 (); + else if (r < 0.6) + mummy_attb1 (); + else + mummy_attc1 (); +}; + + +/* +============================================================================= + +PAIN + +============================================================================= +*/ + +void() mummy_paina1 =[ $paina1, mummy_paina2 ] + {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() mummy_paina2 =[ $paina2, mummy_paina3 ] {ai_painforward(3);}; +void() mummy_paina3 =[ $paina3, mummy_paina4 ] {ai_painforward(1);}; +void() mummy_paina4 =[ $paina4, mummy_paina5 ] {ai_pain(1);}; +void() mummy_paina5 =[ $paina5, mummy_paina6 ] {ai_pain(3);}; +void() mummy_paina6 =[ $paina6, mummy_paina7 ] {ai_pain(1);}; +void() mummy_paina7 =[ $paina7, mummy_paina8 ] {}; +void() mummy_paina8 =[ $paina8, mummy_paina9 ] {}; +void() mummy_paina9 =[ $paina9, mummy_paina10 ] {}; +void() mummy_paina10 =[ $paina10, mummy_paina11 ] {}; +void() mummy_paina11 =[ $paina11, mummy_paina12 ] {}; +void() mummy_paina12 =[ $paina12, mummy_run1 ] {}; + +void() mummy_painb1 =[ $painb1, mummy_painb2 ] + {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() mummy_painb2 =[ $painb2, mummy_painb3 ] {ai_pain(2);}; +void() mummy_painb3 =[ $painb3, mummy_painb4 ] {ai_pain(8);}; +void() mummy_painb4 =[ $painb4, mummy_painb5 ] {ai_pain(6);}; +void() mummy_painb5 =[ $painb5, mummy_painb6 ] {ai_pain(2);}; +void() mummy_painb6 =[ $painb6, mummy_painb7 ] {}; +void() mummy_painb7 =[ $painb7, mummy_painb8 ] {}; +void() mummy_painb8 =[ $painb8, mummy_painb9 ] {}; +void() mummy_painb9 =[ $painb9, mummy_painb10 ] + {sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM);}; +void() mummy_painb10 =[ $painb10, mummy_painb11 ] {}; +void() mummy_painb11 =[ $painb11, mummy_painb12 ] {}; +void() mummy_painb12 =[ $painb12, mummy_painb13 ] {}; +void() mummy_painb13 =[ $painb13, mummy_painb14 ] {}; +void() mummy_painb14 =[ $painb14, mummy_painb15 ] {}; +void() mummy_painb15 =[ $painb15, mummy_painb16 ] {}; +void() mummy_painb16 =[ $painb16, mummy_painb17 ] {}; +void() mummy_painb17 =[ $painb17, mummy_painb18 ] {}; +void() mummy_painb18 =[ $painb18, mummy_painb19 ] {}; +void() mummy_painb19 =[ $painb19, mummy_painb20 ] {}; +void() mummy_painb20 =[ $painb20, mummy_painb21 ] {}; +void() mummy_painb21 =[ $painb21, mummy_painb22 ] {}; +void() mummy_painb22 =[ $painb22, mummy_painb23 ] {}; +void() mummy_painb23 =[ $painb23, mummy_painb24 ] {}; +void() mummy_painb24 =[ $painb24, mummy_painb25 ] {}; +void() mummy_painb25 =[ $painb25, mummy_painb26 ] {ai_painforward(1);}; +void() mummy_painb26 =[ $painb26, mummy_painb27 ] {}; +void() mummy_painb27 =[ $painb27, mummy_painb28 ] {}; +void() mummy_painb28 =[ $painb28, mummy_run1 ] {}; + +void() mummy_painc1 =[ $painc1, mummy_painc2 ] + {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() mummy_painc2 =[ $painc2, mummy_painc3 ] {}; +void() mummy_painc3 =[ $painc3, mummy_painc4 ] {ai_pain(3);}; +void() mummy_painc4 =[ $painc4, mummy_painc5 ] {ai_pain(1);}; +void() mummy_painc5 =[ $painc5, mummy_painc6 ] {}; +void() mummy_painc6 =[ $painc6, mummy_painc7 ] {}; +void() mummy_painc7 =[ $painc7, mummy_painc8 ] {}; +void() mummy_painc8 =[ $painc8, mummy_painc9 ] {}; +void() mummy_painc9 =[ $painc9, mummy_painc10 ] {}; +void() mummy_painc10 =[ $painc10, mummy_painc11 ] {}; +void() mummy_painc11 =[ $painc11, mummy_painc12 ] {ai_painforward(1);}; +void() mummy_painc12 =[ $painc12, mummy_painc13 ] {ai_painforward(1);}; +void() mummy_painc13 =[ $painc13, mummy_painc14 ] {}; +void() mummy_painc14 =[ $painc14, mummy_painc15 ] {}; +void() mummy_painc15 =[ $painc15, mummy_painc16 ] {}; +void() mummy_painc16 =[ $painc16, mummy_painc17 ] {}; +void() mummy_painc17 =[ $painc17, mummy_painc18 ] {}; +void() mummy_painc18 =[ $painc18, mummy_run1 ] {}; + +void() mummy_paind1 =[ $paind1, mummy_paind2 ] + {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() mummy_paind2 =[ $paind2, mummy_paind3 ] {}; +void() mummy_paind3 =[ $paind3, mummy_paind4 ] {}; +void() mummy_paind4 =[ $paind4, mummy_paind5 ] {}; +void() mummy_paind5 =[ $paind5, mummy_paind6 ] {}; +void() mummy_paind6 =[ $paind6, mummy_paind7 ] {}; +void() mummy_paind7 =[ $paind7, mummy_paind8 ] {}; +void() mummy_paind8 =[ $paind8, mummy_paind9 ] {}; +void() mummy_paind9 =[ $paind9, mummy_paind10 ] {ai_pain(1);}; +void() mummy_paind10 =[ $paind10, mummy_paind11 ] {}; +void() mummy_paind11 =[ $paind11, mummy_paind12 ] {}; +void() mummy_paind12 =[ $paind12, mummy_paind13 ] {}; +void() mummy_paind13 =[ $paind13, mummy_run1 ] {}; + +void() mummy_paine1 =[ $paine1, mummy_paine2 ] + { sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM); }; +void() mummy_paine2 =[ $paine2, mummy_paine3 ] {ai_pain(8);}; +void() mummy_paine3 =[ $paine3, mummy_paine4 ] {ai_pain(5);}; +void() mummy_paine4 =[ $paine4, mummy_paine5 ] {ai_pain(3);}; +void() mummy_paine5 =[ $paine5, mummy_paine6 ] {ai_pain(1);}; +void() mummy_paine6 =[ $paine6, mummy_paine7 ] {ai_pain(2);}; +void() mummy_paine7 =[ $paine7, mummy_paine8 ] {ai_pain(1);}; +void() mummy_paine8 =[ $paine8, mummy_paine9 ] {ai_pain(1);}; +void() mummy_paine9 =[ $paine9, mummy_paine10 ] {ai_pain(2);}; +void() mummy_paine10 =[ $paine10, mummy_paine11 ] + { + sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM); + self.solid = SOLID_NOT; + }; +void() mummy_paine11 =[ $paine11, mummy_paine12 ] + { self.nextthink = self.nextthink + 5; }; +void() mummy_paine12 =[ $paine12, mummy_paine13 ] + { + // see if ok to stand up + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + self.solid = SOLID_SLIDEBOX; + if (!walkmove (0, 0)) + { + self.think = mummy_paine11; + self.solid = SOLID_NOT; + return; + } + }; +void() mummy_paine13 =[ $paine13, mummy_paine14 ] {}; +void() mummy_paine14 =[ $paine14, mummy_paine15 ] {}; +void() mummy_paine15 =[ $paine15, mummy_paine16 ] {}; +void() mummy_paine16 =[ $paine16, mummy_paine17 ] {}; +void() mummy_paine17 =[ $paine17, mummy_paine18 ] {}; +void() mummy_paine18 =[ $paine18, mummy_paine19 ] {}; +void() mummy_paine19 =[ $paine19, mummy_paine20 ] {}; +void() mummy_paine20 =[ $paine20, mummy_paine21 ] {}; +void() mummy_paine21 =[ $paine21, mummy_paine22 ] {}; +void() mummy_paine22 =[ $paine22, mummy_paine23 ] {}; +void() mummy_paine23 =[ $paine23, mummy_paine24 ] {}; +void() mummy_paine24 =[ $paine24, mummy_paine25 ] {}; +void() mummy_paine25 =[ $paine25, mummy_paine26 ] {ai_painforward(5);}; +void() mummy_paine26 =[ $paine26, mummy_paine27 ] {ai_painforward(3);}; +void() mummy_paine27 =[ $paine27, mummy_paine28 ] {ai_painforward(1);}; +void() mummy_paine28 =[ $paine28, mummy_paine29 ] {ai_pain(1);}; +void() mummy_paine29 =[ $paine29, mummy_paine30 ] {}; +void() mummy_paine30 =[ $paine30, mummy_run1 ] + { }; + +void() mummy_die = +{ + self.health = -35; + sound (self, CHAN_VOICE, "zombie/z_gib.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_zombie.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); +}; + +/* +================= +mummy_pain +================= +*/ +void(entity attacker, float take) mummy_pain = +{ + local float r; + + if ( self.pain_finished > time ) + return; + + r = random(); + if (r > 0.24) + return; + + self.pain_finished = time + 2.5; + + if (r < 0.06) + mummy_paina1 (); + else if (r < 0.12) + mummy_painb1 (); + else if (r < 0.18) + mummy_painc1 (); + else + mummy_paind1 (); +}; + +void() mummy_sleep = [ $paine11, mummy_sleep ] { ai_stand(); }; + +void() mummy_wake = +{ + self.th_stand = mummy_stand1; + self.th_walk = mummy_walk1; + self.th_pain = mummy_pain; + self.th_run = mummy_run1; + self.th_missile = mummy_missile; + + mummy_paine12(); +}; + +//============================================================================ + +float MUMMY_STRONG = 4; + +/*QUAKED monster_mummy (1 0 0) (-16 -16 -24) (16 16 32) Ambush Lying Stronger + +Lying - mummy will start lying down, and stand when player spotted. +Stronger - mummy starts with 1000 hitpoints, rather than 500. +*/ +void() monster_mummy = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/mummy.mdl"); + precache_model ("progs/h_zombie.mdl"); + precache_model ("progs/zom_gib.mdl"); + + precache_sound ("zombie/z_idle.wav"); + precache_sound ("zombie/z_idle1.wav"); + precache_sound ("zombie/z_shot1.wav"); + precache_sound ("zombie/z_gib.wav"); + precache_sound ("zombie/z_pain.wav"); + precache_sound ("zombie/z_pain1.wav"); + precache_sound ("zombie/z_fall.wav"); + precache_sound ("zombie/z_miss.wav"); + precache_sound ("zombie/z_hit.wav"); + precache_sound ("zombie/idle_w2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/mummy.mdl"); + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + if (self.spawnflags & MUMMY_STRONG) + self.health = 1000; + else + self.health = 500; + + self.th_stand = mummy_stand1; + self.th_walk = mummy_walk1; + self.th_run = mummy_run1; + self.th_pain = mummy_pain; + self.th_die = mummy_die; + self.th_missile = mummy_missile; + + if (self.spawnflags & SPAWN_LYING) + { + setsize (self, '-16 -16 -24', '16 16 -16'); + self.solid = SOLID_NOT; + self.th_stand = mummy_sleep; + self.th_run = mummy_wake; + self.th_walk = mummy_wake; + self.th_pain = mummy_wake; + self.th_missile = mummy_wake; + walkmonster_start(); + } + else + walkmonster_start(); +}; diff --git a/quakec_rogue/new_ai.qc b/quakec_rogue/new_ai.qc new file mode 100644 index 0000000..25cc5c5 --- /dev/null +++ b/quakec_rogue/new_ai.qc @@ -0,0 +1,171 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// new ai functions +// +// pmack + +// ================================================================ +// ai_orbit - used to have an object orbit an entity. +// +// destEnt: the entity to orbit +// radius: how large a radius to orbit at (0 will just track) +// offset: center of the orbit. this is added to dest.origin. +// +// Uses self.orbitPosition to determine current destination. +// ================================================================ +void(entity destEnt, float radius, vector offset) ai_orbit = +{ + local vector dir; + local float dist; + + if (self.orbitPosition > 3) + self.orbitPosition = 0; + else if (self.orbitPosition < 0) + self.orbitPosition = 0; + + traceline ( self.origin, destEnt.origin + offset, TRUE, world); + if ( trace_fraction < 1 ) + { + setorigin (self, destEnt.origin + offset); + self.orbitPosition = self.orbitPosition + 1; + return; + } + + if ( self.orbitPosition == 0) + { + dir = (destEnt.origin + offset) - self.origin; + dir_x = dir_x + radius; + } + else if ( self.orbitPosition == 1) + { + dir = (destEnt.origin + offset) - self.origin; + dir_y = dir_y + radius; + } + else if ( self.orbitPosition == 2) + { + dir = (destEnt.origin + offset) - self.origin; + dir_x = dir_x - radius; + } + else + { + dir = (destEnt.origin + offset) - self.origin; + dir_y = dir_y - radius; + } + + dist = vlen(dir); + + if( dist < 8 ) + { + self.orbitPosition = self.orbitPosition + 1; + } + else if( dist < 50 ) + { + self.velocity = normalize(dir); + self.velocity = self.velocity * 150; + } + else + { + self.velocity = normalize(dir); + self.velocity = self.velocity * 500; + } +}; + +// ================================================================ +// ai_track - used to have an object chase/track an enemy. the object +// flies directly at the destEnt's view_ofs point. +// +// destEnt: the entity to orbit +// trackSpeed: the velocity multiplier (speed) of the object +// ================================================================ +void(entity destEnt, float trackSpeed) ai_track = +{ + local vector dir; + + dir = destEnt.origin + destEnt.view_ofs; + dir = normalize(dir - self.origin); + self.velocity = dir * trackSpeed; +}; + +// ================================================================ +// ViolentDeath +// ================================================================ + +void(string gibname, float dm) AccelerateGib = +{ + local entity new; + + local float offset1; + + new = spawn(); + new.origin = self.origin; + setmodel (new, gibname); + setsize (new, '-8 -8 -8', '8 8 8'); + + new.velocity = -1.25 * self.velocity; + makevectors ( new.velocity ); + offset1 = random() * 300 - 150; + new.velocity = new.velocity + v_right * offset1; + offset1 = random() * 300 - 150; + new.velocity = new.velocity + v_up * offset1; + + + new.movetype = MOVETYPE_BOUNCE; + new.solid = SOLID_NOT; + new.avelocity_x = random()*600; + new.avelocity_y = random()*600; + new.avelocity_z = random()*600; + new.think = SUB_Remove; + new.ltime = time; + new.nextthink = time + 10 + random()*10; + new.frame = 0; + new.flags = 0; +}; + +void(float gibCount) ViolentDeath = +{ + while(gibCount > 0) + { + AccelerateGib ("progs/gib1.mdl", (-4 * gibCount)); + AccelerateGib ("progs/gib2.mdl", (-6 * gibCount)); + AccelerateGib ("progs/gib3.mdl", (-8 * gibCount)); + gibCount = gibCount - 3; + } +}; + +entity(string gibname) StartGib = +{ + local entity new; + + new = spawn(); + new.origin = self.origin; + setmodel (new, gibname); + setsize (new, '0 0 0', '0 0 0'); + + new.movetype = MOVETYPE_BOUNCE; + new.solid = SOLID_NOT; + new.think = SUB_Remove; + new.ltime = time; + new.nextthink = time + 10 + random()*10; + new.frame = 0; + new.flags = 0; + + return new; +}; + diff --git a/quakec_rogue/newitems.qc b/quakec_rogue/newitems.qc new file mode 100644 index 0000000..5e2537b --- /dev/null +++ b/quakec_rogue/newitems.qc @@ -0,0 +1,150 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// New Items +// +// Items added for the Rogue XPACK. + +void() sphere_spawn; + +void(entity theEntity) UpdateAmmoCounts = +{ + if ( self.weapon >= IT_LAVA_NAILGUN ) + { + theEntity.ammo_shells = theEntity.ammo_shells1; + theEntity.ammo_nails = theEntity.ammo_lava_nails; + theEntity.ammo_rockets = theEntity.ammo_multi_rockets; + theEntity.ammo_cells = theEntity.ammo_plasma; + } + else + { + theEntity.ammo_shells = theEntity.ammo_shells1; + theEntity.ammo_nails = theEntity.ammo_nails1; + theEntity.ammo_rockets = theEntity.ammo_rockets1; + theEntity.ammo_cells = theEntity.ammo_cells1; + } +}; + +void() newitems_touch = +{ + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + // only one per person, please. + if (self.classname == "item_sphere") + if (other.items2 & IT2_V_SPHERE) + return; + + sprint(other, "$qc_got_item", self.netname); + + if (deathmatch) + { + if (self.classname == "item_random_powerup") + { + self.nextthink = time + 60; + self.think = random_regen; + } + else if (self.classname == "item_sphere") + { + self.mdl = self.model; + self.nextthink = time + 60*3; + self.think = SUB_regen; + } + else + { + self.mdl = self.model; + self.nextthink = time + 60; + self.think = SUB_regen; + } + } + + sound (other, CHAN_VOICE, self.noise, 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + self.solid = SOLID_NOT; + other.items = other.items | self.items; + other.items2 = other.items2 | self.items2; + self.model = string_null; + +// do the apropriate action + if (self.netname == "$qc_power_shield") + { + other.shield_time = 1; + other.shield_finished = time + 30; + } + else if (self.netname == "$qc_anti_grav_belt") + { + other.antigrav_time = 1; + other.antigrav_finished = time + 45; + other.gravity = 0.25; + } + else if (self.classname == "item_sphere") + { + other.items2 = other.items2 | IT2_V_SPHERE; + sphere_spawn(); + } + + activator = other; + SUB_UseTargets(); // fire all targets / killtargets +}; + + +/*QUAKED item_powerup_shield (0 .5 .8) (-16 -16 -24) (16 16 32) +The shield upgrade +*/ +void() item_powerup_shield = +{ + self.touch = newitems_touch; + + precache_model ("progs/shield.mdl"); + precache_model ("progs/p_shield.mdl"); + precache_sound ("shield/pickup.wav"); + precache_sound ("shield/hit.wav"); + precache_sound ("shield/fadeout.wav"); + self.noise = "shield/pickup.wav"; + setmodel (self, "progs/shield.mdl"); + self.netname = "$qc_power_shield"; + self.items2 = IT2_SHIELD; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + +/*QUAKED item_powerup_belt (0 .5 .8) (-16 -16 -24) (16 16 32) +The anti-grav belt +*/ +void() item_powerup_belt = +{ + self.touch = newitems_touch; + + precache_model ("progs/beltup.mdl"); + precache_sound ("belt/pickup.wav"); + precache_sound ("belt/use.wav"); + precache_sound ("belt/fadeout.wav"); + self.noise = "belt/pickup.wav"; + setmodel (self, "progs/beltup.mdl"); + self.netname = "$qc_anti_grav_belt"; + self.items2 = IT2_ANTIGRAV; + setsize (self, '-16 -16 -24', '16 16 32'); + StartItem (); +}; + + + + diff --git a/quakec_rogue/newmisc.qc b/quakec_rogue/newmisc.qc new file mode 100644 index 0000000..8383a29 --- /dev/null +++ b/quakec_rogue/newmisc.qc @@ -0,0 +1,163 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Miscellaneous items used in the Rogue XPACK + + +/*QUAKED light_lantern (0 .5 0) (-10 -10 -20) (10 10 20) +Light-emitting lantern. +Default light value is 200 +Default style is 0 +*/ +void() light_lantern = +{ + precache_model ("progs/lantern.mdl"); + setmodel (self, "progs/lantern.mdl"); + makestatic (self); +}; + +/*QUAKED light_candle (0 .5 0) (-4 -4 -10) (4 4 10) +Candle +Default light value is 200 +Default style is 0 +*/ +void() light_candle = +{ + precache_model ("progs/candle.mdl"); + setmodel (self, "progs/candle.mdl"); + makestatic (self); +}; + +// ========================================== +// rubble generator +// ========================================== +void() rubble_touch = +{ + if ( other.classname == "player" || other.flags & FL_MONSTER ) + { + if ( vlen ( self.velocity ) > 0) + { + T_Damage ( other, self, self, 10 ); + } + } +}; + +void() rubble_throw = +{ + local vector throw; + local entity rubble; + + rubble = find ( world, targetname, self.target ); + throw = normalize ( rubble.origin - self.origin ); + throw_x = throw_x + (random() * 0.2) - 0.1; + throw_y = throw_y + (random() * 0.2) - 0.1; + throw_z = throw_z + (random() * 0.2) - 0.1; + + rubble = spawn (); + rubble.owner = self; + rubble.classname = "rubble"; + rubble.movetype = MOVETYPE_BOUNCE; + rubble.solid = SOLID_BBOX; + rubble.velocity = throw * 300; + setmodel ( rubble, "progs/rubble.mdl"); + setsize ( rubble, '-16 -16 -16', '16 16 16'); + setorigin ( rubble, self.origin ); + rubble.touch = rubble_touch; + + rubble.think = SUB_Remove; + rubble.nextthink = time + 30; + + if (self.spawnflags & 1) + rubble.skin = 1; + else + rubble.skin = 0; + + self.think = rubble_throw; + self.nextthink = time + self.delay; +}; + +void() rubble_use = +{ + if (self.wait == 0) + { + self.think = rubble_throw; + self.nextthink = time + self.delay; + self.wait = 1; + } + else + { + self.nextthink = time - 1; + self.wait = 0; + } +}; + +/*QUAKED rubble_generator (1 1 0) (-8 -8 -8) (8 8 8) LavaRock Active +Rubble Generator - cave colored rock chunks flung at the target. Triggering the generator will turn it off and on. + +LavaRock - a lava rock texture, based on rich's pumice +Active - start the generator immediately. + +delay - time between rubble pieces (Default 5 sec) +*/ +void() rubble_generator = +{ + precache_model ("progs/rubble.mdl"); + + if (!self.target) + objerror ("rubble_generator has no target!"); + + if (!self.delay) + self.delay = 5; + + self.solid = SOLID_NOT; + self.use = rubble_use; + + if (self.spawnflags & 2) + rubble_use(); +}; + + +void() trigEx_die = +{ + SUB_UseTargets(); + + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; +}; + +/*QUAKED trigger_explosion (.5 .5 .5) ? +Variable sized repeatable trigger. Must be targeted at one or more entities. Only set off when killed, and is only damaged by T_RadiusDamage. + +health: amount of damage needed to set off trigger. +*/ +void() trigger_explosion = +{ + InitTrigger (); + + if (!self.health) + self.health = 20; + + self.max_health = self.health; + self.th_die = trigEx_die; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + setorigin (self, self.origin); // make sure it links into the world +}; + diff --git a/quakec_rogue/newplats.qc b/quakec_rogue/newplats.qc new file mode 100644 index 0000000..a53c8b5 --- /dev/null +++ b/quakec_rogue/newplats.qc @@ -0,0 +1,621 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// newplats.qc +// pmack +// september 1996 + +// TYPES + +float DN_N_WAIT = 1; +float PLT_TOGGLE = 2; +float ELEVATOR = 4; +float START_AT_TOP = 8; +float PLAT2 = 16; +float PLAT2_BOTTOM = 32; + +var float elvButnDir = 0; + +// ================================== +// down N and wait code +// ================================== + +void() dn_and_wait_go_up; +void() dn_and_wait_go_down; +void() dn_and_wait_crush; + +void() dn_and_wait_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; +}; + +void() dn_and_wait_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; + self.think = dn_and_wait_go_up; + self.nextthink = self.ltime + self.health; +}; + +void() dn_and_wait_go_down = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_DOWN; + SUB_CalcMove (self.pos2, self.speed, dn_and_wait_hit_bottom); +}; + +void() dn_and_wait_go_up = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos1, self.speed, dn_and_wait_hit_top); +}; + +void() dn_and_wait_crush = +{ + T_Damage (other, self, self, 1); + + if (self.state == STATE_UP) + dn_and_wait_go_down (); + else if (self.state == STATE_DOWN) + dn_and_wait_go_up (); + else + objerror ("plat_new_crush: bad self.state\n"); +}; + +void() dn_and_wait_use = +{ + if (self.state != STATE_TOP) + return; + + dn_and_wait_go_down (); +}; + +// ================================== +// toggle type code +// ================================== + +void() toggle_go_up; +void() toggle_go_down; +void() toggle_crush; + +void() toggle_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; +}; + +void() toggle_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() toggle_go_down = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_DOWN; + SUB_CalcMove (self.pos2, self.speed, toggle_hit_bottom); +}; + +void() toggle_go_up = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos1, self.speed, toggle_hit_top); +}; + +void() toggle_crush = +{ + T_Damage (other, self, self, 1); + + if (self.state == STATE_UP) + toggle_go_down (); + else if (self.state == STATE_DOWN) + toggle_go_up (); + else + objerror ("plat_new_crush: bad self.state\n"); +}; + +void() toggle_use = +{ + if (self.state == STATE_TOP) + toggle_go_down (); + else if(self.state == STATE_BOTTOM) + toggle_go_up (); +}; + +// ================================== +// elvtr type code +// ================================== + +void() elvtr_crush; + +void() elvtr_stop = +{ + self.elevatorOnFloor = self.elevatorToFloor; + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; + self.elevatorLastUse = time; +}; + +void() elvtr_go = +{ + self.elevatorDestination = self.pos2; + self.elevatorDestination_z = self.pos2_z + + (self.height * self.elevatorToFloor); + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.elevatorDestination, self.speed, elvtr_stop); + self.elevatorLastUse = time; +}; + +void() elvtr_crush = +{ +// T_Damage (other, self, self, 1); + self.elevatorToFloor = self.elevatorOnFloor; + + elvtr_go (); +}; + +// =============== +// elevator use function +// self = plat, other = elevator button, other.enemy = player +// =============== +void() elvtr_use = +{ + local float tempDist, elvPos, btnPos; + + if ( (self.elevatorLastUse + 2) > time) + return; + + self.elevatorLastUse = time; + + if (elvButnDir == 0) + return; + + elvPos = (self.absmin_z + self.absmax_z) * 0.5; + btnPos = (other.absmin_z + other.absmax_z) * 0.5; + + if (elvPos > btnPos) + { + tempDist = (elvPos - btnPos) / self.height; + tempDist = ceil ( tempDist); + self.elevatorToFloor = self.elevatorOnFloor - tempDist; + elvtr_go (); + return; + } + else + { + tempDist = btnPos - elvPos; + if (tempDist > self.height) + { + tempDist = tempDist / self.height; + tempDist = floor ( tempDist ); + self.elevatorToFloor = self.elevatorOnFloor + tempDist; + elvtr_go (); + return; + } + } + + if (elvButnDir == -1) + { + if(self.elevatorOnFloor > 0) + { + self.elevatorToFloor = self.elevatorOnFloor - 1; + elvtr_go (); + } + } + else if(elvButnDir == 1) + { + if(self.elevatorOnFloor < (self.cnt - 1)) + { + self.elevatorToFloor = self.elevatorOnFloor + 1; + elvtr_go (); + } + } +}; + +// ================================== +// PLAT2 type code +// ================================== + +void() plat2_center_touch; +void() plat2_go_up; +void() plat2_go_down; +void() plat2_crush; + +void() plat2_spawn_inside_trigger = +{ + local entity trigger; + local vector tmin, tmax; + +// +// middle trigger +// + trigger = spawn(); + trigger.touch = plat2_center_touch; + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.enemy = self; + + tmin = self.mins + '25 25 0'; + tmax = self.maxs - '25 25 -8'; + tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8); + + if (self.spawnflags & PLAT_LOW_TRIGGER) + tmax_z = tmin_z + 8; + + if (self.size_x <= 50) + { + tmin_x = (self.mins_x + self.maxs_x) / 2; + tmax_x = tmin_x + 1; + } + if (self.size_y <= 50) + { + tmin_y = (self.mins_y + self.maxs_y) / 2; + tmax_y = tmin_y + 1; + } + + setsize (trigger, tmin, tmax); +}; + +void() plat2_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + + self.plat2LastMove = time; + if(self.plat2Called == 1) + { + self.think = plat2_go_down; + self.nextthink = self.ltime + 1.5; + self.plat2Called = 0; + self.plat2LastMove = 0; // allow immediate move + } + else if(!(self.spawnflags & START_AT_TOP)) + { + self.think = plat2_go_down; + self.nextthink = self.ltime + self.delay; + self.plat2Called = 0; + } +}; + +void() plat2_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; + + self.plat2LastMove = time; + if(self.plat2Called == 1) + { + self.think = plat2_go_up; + self.nextthink = self.ltime + 1.5; + self.plat2Called = 0; + self.plat2LastMove = 0; // allow immediate move + } + else if(self.spawnflags & START_AT_TOP) + { + self.think = plat2_go_up; + self.nextthink = self.ltime + self.delay; + self.plat2Called = 0; + } +}; + +void() plat2_go_down = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_DOWN; + SUB_CalcMove (self.pos2, self.speed, plat2_hit_bottom); +}; + +void() plat2_go_up = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos1, self.speed, plat2_hit_top); +}; + +void() plat2_use = +{ + if(self.state > 4) + self.state = self.state - 10; + + self.use = SUB_Null; +}; + +void() plat2_center_touch = +{ + local float otherState; + local vector platPosition; + + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + // at this point, self is the trigger. self.enemy is the plat. + // this changes self to be the plat, other is the player. + self = self.enemy; + + if ((self.plat2LastMove + 2) > time) + return; + + if (self.state > 4) // disabled. + return; + + if (self.plat2GoTo > STATE_BOTTOM) + { + if (self.plat2GoTime < time) + { + if (self.plat2GoTo == STATE_UP) + plat2_go_up(); + else + plat2_go_down(); + + self.plat2GoTo = 0; + } + return; + } + + if (self.state > STATE_BOTTOM) // STATE_UP or STATE_DOWN + return; + + platPosition = (self.absmax + self.absmin) * 0.5; + + if (self.state == STATE_TOP) + { + otherState = STATE_TOP; + if ( platPosition_z > other.origin_z ) + otherState = STATE_BOTTOM; + } + else + { + otherState = STATE_BOTTOM; + if ( (other.origin_z - platPosition_z) > self.height) + otherState = STATE_TOP; + } + + if (self.state == otherState) + { + self.plat2Called = 0; + self.plat2GoTime = time + 0.5; + } + else + { + self.plat2GoTime = time + 0.1; + self.plat2Called = 1; + } + + if (self.state == STATE_BOTTOM) + self.plat2GoTo = STATE_UP; + else if(self.state == STATE_TOP) + self.plat2GoTo = STATE_DOWN; +}; + +void() plat2_crush = +{ + T_Damage (other, self, self, 1); + + if (self.state == STATE_UP) + plat2_go_down (); + else if (self.state == STATE_DOWN) + plat2_go_up (); + else + objerror ("plat2_crush: bad self.state\n"); +}; + +// ================================== +// Common Plat Code +// ================================== + +/*QUAKED func_new_plat (0 .5 .8) ? DN_N_WAIT PLT_TOGGLE ELEVATOR START_AT_TOP PLAT2 P2_BOTTOM + +-------------- +DN_N_WAIT is a plat that starts at the top and when triggered, goes down, waits, then comes back up. +health - number of seconds to wait (default 5) + +-------------- +PLT_TOGGLE is a plat that will change between the top and bottom each time it is triggered. + +-------------- +ELEVATOR is an elevator plat. You can have as many levels as you want but they must be all the same distance away. Use elevator button entity as the trigger. + cnt is the number of floors + height is the distance between floors + +START_AT_TOP is an optional flag for elevators. It just tells the elevator that it's position is the top floor. (Default is the bottom floor) USE THIS ONLY WITH ELEVATORS! + +-------------- +PLAT2 is a fixed version of the original plat. If you want the plat to start at the bottom and move to the top on demand, use a negative height. That will tell Quake to lower the plat at spawn time. Always place this plat type in the top position when making the map. This will ensure correct lighting, hopefully. If a plat2 is the target of a trigger, it will be disabled until it has been triggered. Delay is the wait before the plat returns to original position. + +If you don't want to bother figuring out the height, don't put a +value in the height + +delay default 3 +speed default 150 +cnt default 2 + +P2_BOTTOM is an optional switch to have an auto-sized plat2 start at the bottom. +-------------- +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. + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height. +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void() func_new_plat = +{ +//local entity t; +local float negativeHeight; + + negativeHeight = 0; + + if (!self.t_length) + self.t_length = 80; + if (!self.t_width) + self.t_width = 10; + + if (self.sounds == 0) + self.sounds = 2; +// FIX THIS TO LOAD A GENERIC PLAT SOUND + + if (self.sounds == 1) + { + precache_sound ("plats/plat1.wav"); + precache_sound ("plats/plat2.wav"); + self.noise = "plats/plat1.wav"; + self.noise1 = "plats/plat2.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("plats/medplat1.wav"); + precache_sound ("plats/medplat2.wav"); + self.noise = "plats/medplat1.wav"; + self.noise1 = "plats/medplat2.wav"; + } + + + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "plat"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + + if (!self.speed) + self.speed = 150; + +// pos1 is the top position, pos2 is the bottom + self.pos1 = self.origin; + self.pos2 = self.origin; + + if (self.height < 0) + { + negativeHeight = 1; + self.height = 0 - self.height; + } + + if (self.height) + self.pos2_z = self.origin_z - self.height; + else + { + negativeHeight = 1; + self.height = self.size_z - 8; + self.pos2_z = self.origin_z - self.height; + } + + if (self.spawnflags & DN_N_WAIT) + { + self.use = dn_and_wait_use; + self.blocked = dn_and_wait_crush; + + if (negativeHeight == 1) + { + self.state = STATE_BOTTOM; + setorigin (self, self.pos2); + } + else + self.state = STATE_TOP; + + if (!self.health) + self.health = 5; + } + else if (self.spawnflags & PLT_TOGGLE) + { + self.use = toggle_use; + self.blocked = toggle_crush; + if (negativeHeight == 1) + { + setorigin (self, self.pos2); + self.state = STATE_BOTTOM; + } + else + { + self.state = STATE_TOP; + } + } + else if (self.spawnflags & ELEVATOR) + { + self.elevatorOnFloor = 0; + self.elevatorToFloor = 0; + self.elevatorLastUse = 0; + + if (self.spawnflags & START_AT_TOP) + { + self.pos1 = self.origin; + self.pos2 = self.origin; + self.pos2_z = self.origin_z - (self.height * (self.cnt - 1)); + self.elevatorOnFloor = self.cnt - 1; + } + else + { + self.pos1 = self.origin; + self.pos2 = self.origin; + self.pos1_z = self.origin_z + (self.height * (self.cnt - 1)); + self.elevatorOnFloor = 0; + } + + self.use = elvtr_use; + self.blocked = elvtr_crush; + } + else if (self.spawnflags & PLAT2) + { + plat2_spawn_inside_trigger (); // the "start moving" trigger + self.plat2Called = 0; + self.plat2LastMove = 0; + self.plat2GoTo = 0; + self.plat2GoTime = 0; + self.blocked = plat2_crush; + + if (!self.delay) + self.delay = 3; + + if (negativeHeight == 1) + { + self.state = STATE_BOTTOM; + // make sure START_AT_TOP isn't set. We need that... + self.spawnflags = PLAT2; + setorigin (self, self.pos2); + } + else + { + // default position is top. + self.spawnflags = self.spawnflags | START_AT_TOP; + self.state = STATE_TOP; + } + + if (self.targetname) + { + self.use = plat2_use; + self.state = self.state + 10; + } + } + +}; + diff --git a/quakec_rogue/ogre.qc b/quakec_rogue/ogre.qc new file mode 100644 index 0000000..ab861dd --- /dev/null +++ b/quakec_rogue/ogre.qc @@ -0,0 +1,518 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OGRE + +============================================================================== +*/ + +$cd id1/models/ogre_c +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 walk13 walk14 walk15 walk16 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame swing1 swing2 swing3 swing4 swing5 swing6 swing7 +$frame swing8 swing9 swing10 swing11 swing12 swing13 swing14 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 smash13 smash14 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame painb1 painb2 painb3 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 paind14 paind15 paind16 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 death11 death12 +$frame death13 death14 + +$frame bdeath1 bdeath2 bdeath3 bdeath4 bdeath5 bdeath6 +$frame bdeath7 bdeath8 bdeath9 bdeath10 + +$frame pull1 pull2 pull3 pull4 pull5 pull6 pull7 pull8 pull9 pull10 pull11 + +//============================================================================= + +float OGRE_BOSS = 2; +float OGRE_NORMAL = 4; + +void() OgreGrenadeExplode = +{ + T_RadiusDamage (self, self.owner, 40, world); + sound (self, CHAN_VOICE, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +void() OgreGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage == DAMAGE_AIM) + { + OgreGrenadeExplode(); + return; + } + sound (self, CHAN_VOICE, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +/* +================ +OgreFireGrenade +================ +*/ +void() MultiGrenadeTouch; +void() MultiGrenadeExplode; + +void() OgreFireGrenade = +{ + local entity missile, mpuff; + + self.effects = self.effects | EF_MUZZLEFLASH; + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + missile.classname = "ogre_grenade"; + +// set missile speed + + makevectors (self.angles); + missile.velocity = normalize(self.enemy.origin - self.origin); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + missile.avelocity = '300 300 300'; + missile.angles = vectoangles(missile.velocity); + +// set missile duration + if(self.spawnflags & OGRE_BOSS) + { + missile.touch = MultiGrenadeTouch; + missile.nextthink = time + 2.5; + missile.think = MultiGrenadeExplode; + setmodel (missile, "progs/mervup.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); + missile.classname = "MultiGrenade"; + } + else + { + missile.touch = OgreGrenadeTouch; + missile.nextthink = time + 2.5; + missile.think = OgreGrenadeExplode; + setmodel (missile, "progs/grenade.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); + } +}; + + +//============================================================================= + +/* +================ +chainsaw + +FIXME +================ +*/ +void(float side) chainsaw = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 4; + T_Damage (self.enemy, self, self, ldmg); + + if (side) + { + makevectors (self.angles); + if (side == 1) + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + else + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + + +void() ogre_stand1 =[ $stand1, ogre_stand2 ] {ai_stand();}; +void() ogre_stand2 =[ $stand2, ogre_stand3 ] {ai_stand();}; +void() ogre_stand3 =[ $stand3, ogre_stand4 ] {ai_stand();}; +void() ogre_stand4 =[ $stand4, ogre_stand5 ] {ai_stand();}; +void() ogre_stand5 =[ $stand5, ogre_stand6 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); +ai_stand(); +}; +void() ogre_stand6 =[ $stand6, ogre_stand7 ] {ai_stand();}; +void() ogre_stand7 =[ $stand7, ogre_stand8 ] {ai_stand();}; +void() ogre_stand8 =[ $stand8, ogre_stand9 ] {ai_stand();}; +void() ogre_stand9 =[ $stand9, ogre_stand1 ] {ai_stand();}; + +void() ogre_walk1 =[ $walk1, ogre_walk2 ] {ai_walk(3);}; +void() ogre_walk2 =[ $walk2, ogre_walk3 ] {ai_walk(2);}; +void() ogre_walk3 =[ $walk3, ogre_walk4 ] { +ai_walk(2); +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle.wav", 1, ATTN_IDLE); +}; +void() ogre_walk4 =[ $walk4, ogre_walk5 ] {ai_walk(2);}; +void() ogre_walk5 =[ $walk5, ogre_walk6 ] {ai_walk(2);}; +void() ogre_walk6 =[ $walk6, ogre_walk7 ] { +ai_walk(5); +if (random() < 0.1) + sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE); +}; +void() ogre_walk7 =[ $walk7, ogre_walk8 ] {ai_walk(3);}; +void() ogre_walk8 =[ $walk8, ogre_walk9 ] {ai_walk(2);}; +void() ogre_walk9 =[ $walk9, ogre_walk10 ] {ai_walk(3);}; +void() ogre_walk10 =[ $walk10, ogre_walk11 ] {ai_walk(1);}; +void() ogre_walk11 =[ $walk11, ogre_walk12 ] {ai_walk(2);}; +void() ogre_walk12 =[ $walk12, ogre_walk13 ] {ai_walk(3);}; +void() ogre_walk13 =[ $walk13, ogre_walk14 ] {ai_walk(3);}; +void() ogre_walk14 =[ $walk14, ogre_walk15 ] {ai_walk(3);}; +void() ogre_walk15 =[ $walk15, ogre_walk16 ] {ai_walk(3);}; +void() ogre_walk16 =[ $walk16, ogre_walk1 ] {ai_walk(4);}; + +void() ogre_run1 =[ $run1, ogre_run2 ] {ai_run(9); +if (random() < 0.2) + sound (self, CHAN_VOICE, "ogre/ogidle2.wav", 1, ATTN_IDLE); +}; +void() ogre_run2 =[ $run2, ogre_run3 ] {ai_run(12);}; +void() ogre_run3 =[ $run3, ogre_run4 ] {ai_run(8);}; +void() ogre_run4 =[ $run4, ogre_run5 ] {ai_run(22);}; +void() ogre_run5 =[ $run5, ogre_run6 ] {ai_run(16);}; +void() ogre_run6 =[ $run6, ogre_run7 ] {ai_run(4);}; +void() ogre_run7 =[ $run7, ogre_run8 ] {ai_run(13);}; +void() ogre_run8 =[ $run8, ogre_run1 ] {ai_run(24);}; + +void() ogre_swing1 =[ $swing1, ogre_swing2 ] {ai_charge(11); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM); +}; +void() ogre_swing2 =[ $swing2, ogre_swing3 ] {ai_charge(1);}; +void() ogre_swing3 =[ $swing3, ogre_swing4 ] {ai_charge(4);}; +void() ogre_swing4 =[ $swing4, ogre_swing5 ] {ai_charge(13);}; +void() ogre_swing5 =[ $swing5, ogre_swing6 ] {ai_charge(9); chainsaw(0);self.angles_y = self.angles_y + random()*25;}; +void() ogre_swing6 =[ $swing6, ogre_swing7 ] {chainsaw(200);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing7 =[ $swing7, ogre_swing8 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing8 =[ $swing8, ogre_swing9 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing9 =[ $swing9, ogre_swing10 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing10 =[ $swing10, ogre_swing11 ] {chainsaw(-200);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing11 =[ $swing11, ogre_swing12 ] {chainsaw(0);self.angles_y = self.angles_y + random()* 25;}; +void() ogre_swing12 =[ $swing12, ogre_swing13 ] {ai_charge(3);}; +void() ogre_swing13 =[ $swing13, ogre_swing14 ] {ai_charge(8);}; +void() ogre_swing14 =[ $swing14, ogre_run1 ] {ai_charge(9);}; + +void() ogre_smash1 =[ $smash1, ogre_smash2 ] {ai_charge(6); +sound (self, CHAN_WEAPON, "ogre/ogsawatk.wav", 1, ATTN_NORM); +}; +void() ogre_smash2 =[ $smash2, ogre_smash3 ] {ai_charge(0);}; +void() ogre_smash3 =[ $smash3, ogre_smash4 ] {ai_charge(0);}; +void() ogre_smash4 =[ $smash4, ogre_smash5 ] {ai_charge(1);}; +void() ogre_smash5 =[ $smash5, ogre_smash6 ] {ai_charge(4);}; +void() ogre_smash6 =[ $smash6, ogre_smash7 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash7 =[ $smash7, ogre_smash8 ] {ai_charge(4); chainsaw(0);}; +void() ogre_smash8 =[ $smash8, ogre_smash9 ] {ai_charge(10); chainsaw(0);}; +void() ogre_smash9 =[ $smash9, ogre_smash10 ] {ai_charge(13); chainsaw(0);}; +void() ogre_smash10 =[ $smash10, ogre_smash11 ] {chainsaw(1);}; +void() ogre_smash11 =[ $smash11, ogre_smash12 ] {ai_charge(2); chainsaw(0); +self.nextthink = self.nextthink + random()*0.2;}; // slight variation +void() ogre_smash12 =[ $smash12, ogre_smash13 ] {ai_charge();}; +void() ogre_smash13 =[ $smash13, ogre_smash14 ] {ai_charge(4);}; +void() ogre_smash14 =[ $smash14, ogre_run1 ] {ai_charge(12);}; + +void() ogre_nail1 =[ $shoot1, ogre_nail2 ] {ai_face();}; +void() ogre_nail2 =[ $shoot2, ogre_nail3 ] {ai_face();}; +void() ogre_nail3 =[ $shoot2, ogre_nail4 ] {ai_face();}; +void() ogre_nail4 =[ $shoot3, ogre_nail5 ] {ai_face();OgreFireGrenade();}; +void() ogre_nail5 =[ $shoot4, ogre_nail6 ] {ai_face();}; +void() ogre_nail6 =[ $shoot5, ogre_nail7 ] {ai_face();}; +void() ogre_nail7 =[ $shoot6, ogre_run1 ] {ai_face();}; + +void() ogre_pain1 =[ $pain1, ogre_pain2 ] {}; +void() ogre_pain2 =[ $pain2, ogre_pain3 ] {}; +void() ogre_pain3 =[ $pain3, ogre_pain4 ] {}; +void() ogre_pain4 =[ $pain4, ogre_pain5 ] {}; +void() ogre_pain5 =[ $pain5, ogre_run1 ] {}; + + +void() ogre_painb1 =[ $painb1, ogre_painb2 ] {}; +void() ogre_painb2 =[ $painb2, ogre_painb3 ] {}; +void() ogre_painb3 =[ $painb3, ogre_run1 ] {}; + + +void() ogre_painc1 =[ $painc1, ogre_painc2 ] {}; +void() ogre_painc2 =[ $painc2, ogre_painc3 ] {}; +void() ogre_painc3 =[ $painc3, ogre_painc4 ] {}; +void() ogre_painc4 =[ $painc4, ogre_painc5 ] {}; +void() ogre_painc5 =[ $painc5, ogre_painc6 ] {}; +void() ogre_painc6 =[ $painc6, ogre_run1 ] {}; + + +void() ogre_paind1 =[ $paind1, ogre_paind2 ] {}; +void() ogre_paind2 =[ $paind2, ogre_paind3 ] {ai_pain(10);}; +void() ogre_paind3 =[ $paind3, ogre_paind4 ] {ai_pain(9);}; +void() ogre_paind4 =[ $paind4, ogre_paind5 ] {ai_pain(4);}; +void() ogre_paind5 =[ $paind5, ogre_paind6 ] {}; +void() ogre_paind6 =[ $paind6, ogre_paind7 ] {}; +void() ogre_paind7 =[ $paind7, ogre_paind8 ] {}; +void() ogre_paind8 =[ $paind8, ogre_paind9 ] {}; +void() ogre_paind9 =[ $paind9, ogre_paind10 ] {}; +void() ogre_paind10=[ $paind10, ogre_paind11 ] {}; +void() ogre_paind11=[ $paind11, ogre_paind12 ] {}; +void() ogre_paind12=[ $paind12, ogre_paind13 ] {}; +void() ogre_paind13=[ $paind13, ogre_paind14 ] {}; +void() ogre_paind14=[ $paind14, ogre_paind15 ] {}; +void() ogre_paind15=[ $paind15, ogre_paind16 ] {}; +void() ogre_paind16=[ $paind16, ogre_run1 ] {}; + +void() ogre_paine1 =[ $paine1, ogre_paine2 ] {}; +void() ogre_paine2 =[ $paine2, ogre_paine3 ] {ai_pain(10);}; +void() ogre_paine3 =[ $paine3, ogre_paine4 ] {ai_pain(9);}; +void() ogre_paine4 =[ $paine4, ogre_paine5 ] {ai_pain(4);}; +void() ogre_paine5 =[ $paine5, ogre_paine6 ] {}; +void() ogre_paine6 =[ $paine6, ogre_paine7 ] {}; +void() ogre_paine7 =[ $paine7, ogre_paine8 ] {}; +void() ogre_paine8 =[ $paine8, ogre_paine9 ] {}; +void() ogre_paine9 =[ $paine9, ogre_paine10 ] {}; +void() ogre_paine10=[ $paine10, ogre_paine11 ] {}; +void() ogre_paine11=[ $paine11, ogre_paine12 ] {}; +void() ogre_paine12=[ $paine12, ogre_paine13 ] {}; +void() ogre_paine13=[ $paine13, ogre_paine14 ] {}; +void() ogre_paine14=[ $paine14, ogre_paine15 ] {}; +void() ogre_paine15=[ $paine15, ogre_run1 ] {}; + + +void(entity attacker, float damage) ogre_pain = +{ + local float r; + +// don't make multiple pain sounds right after each other + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "ogre/ogpain1.wav", 1, ATTN_NORM); + + r = random(); + + if (r < 0.25) + { + ogre_pain1 (); + self.pain_finished = time + 1; + } + else if (r < 0.5) + { + ogre_painb1 (); + self.pain_finished = time + 1; + } + else if (r < 0.75) + { + ogre_painc1 (); + self.pain_finished = time + 1; + } + else if (r < 0.88) + { + ogre_paind1 (); + self.pain_finished = time + 2; + } + else + { + ogre_paine1 (); + self.pain_finished = time + 2; + } +}; + +void() ogre_die1 =[ $death1, ogre_die2 ] {}; +void() ogre_die2 =[ $death2, ogre_die3 ] {}; +void() ogre_die3 =[ $death3, ogre_die4 ] + { + self.solid = SOLID_NOT; + if (self.spawnflags & OGRE_BOSS) + self.ammo_multi_rockets = 2; + else + self.ammo_rockets = 2; + DropBackpack(); + }; +void() ogre_die4 =[ $death4, ogre_die5 ] {}; +void() ogre_die5 =[ $death5, ogre_die6 ] {}; +void() ogre_die6 =[ $death6, ogre_die7 ] {}; +void() ogre_die7 =[ $death7, ogre_die8 ] {}; +void() ogre_die8 =[ $death8, ogre_die9 ] {}; +void() ogre_die9 =[ $death9, ogre_die10 ] {}; +void() ogre_die10 =[ $death10, ogre_die11 ] {}; +void() ogre_die11 =[ $death11, ogre_die12 ] {}; +void() ogre_die12 =[ $death12, ogre_die13 ] {}; +void() ogre_die13 =[ $death13, ogre_die14 ] {}; +void() ogre_die14 =[ $death14, ogre_die14 ] {}; + +void() ogre_bdie1 =[ $bdeath1, ogre_bdie2 ] {}; +void() ogre_bdie2 =[ $bdeath2, ogre_bdie3 ] {ai_forward(5);}; +void() ogre_bdie3 =[ $bdeath3, ogre_bdie4 ] + { + self.solid = SOLID_NOT; + if (self.spawnflags & OGRE_BOSS) + self.ammo_multi_rockets = 2; + else + self.ammo_rockets = 2; + DropBackpack(); + }; +void() ogre_bdie4 =[ $bdeath4, ogre_bdie5 ] {ai_forward(1);}; +void() ogre_bdie5 =[ $bdeath5, ogre_bdie6 ] {ai_forward(3);}; +void() ogre_bdie6 =[ $bdeath6, ogre_bdie7 ] {ai_forward(7);}; +void() ogre_bdie7 =[ $bdeath7, ogre_bdie8 ] {ai_forward(25);}; +void() ogre_bdie8 =[ $bdeath8, ogre_bdie9 ] {}; +void() ogre_bdie9 =[ $bdeath9, ogre_bdie10 ] {}; +void() ogre_bdie10 =[ $bdeath10, ogre_bdie10 ] {}; + +void() ogre_die = +{ +// check for gib + if (self.health < -80) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_ogre.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "ogre/ogdth.wav", 1, ATTN_NORM); + + if (random() < 0.5) + ogre_die1 (); + else + ogre_bdie1 (); +}; + +void() ogre_melee = +{ + if (random() > 0.5) + ogre_smash1 (); + else + ogre_swing1 (); +}; + + +/*QUAKED monster_ogre (1 0 0) (-32 -32 -24) (32 32 64) Ambush Boss Normal +The Ogre + +Boss: use the modified skin and fire multigrenades. +Normal: use the original skin and fire normal grenades. +*/ +void() monster_ogre = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/ogre.mdl"); + precache_model ("progs/h_ogre.mdl"); + precache_model ("progs/grenade.mdl"); + + precache_sound ("ogre/ogdrag.wav"); + precache_sound ("ogre/ogdth.wav"); + precache_sound ("ogre/ogidle.wav"); + precache_sound ("ogre/ogidle2.wav"); + precache_sound ("ogre/ogpain1.wav"); + precache_sound ("ogre/ogsawatk.wav"); + precache_sound ("ogre/ogwake.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/ogre.mdl"); + + // none specified - choose random. 25% boss on normal, hard, bloodbath + if (self.spawnflags <= 1) + { + if (cvar("skill") > 0) + { + if ( random() <= 0.25 ) + { + self.spawnflags = self.spawnflags | OGRE_BOSS; + } + } + } + + if (self.spawnflags & OGRE_BOSS) + { + self.skin = 1; + } + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 200; + + self.th_stand = ogre_stand1; + self.th_walk = ogre_walk1; + self.th_run = ogre_run1; + self.th_die = ogre_die; + self.th_melee = ogre_melee; + self.th_missile = ogre_nail1; + self.th_pain = ogre_pain; + + walkmonster_start(); +}; diff --git a/quakec_rogue/oldone.qc b/quakec_rogue/oldone.qc new file mode 100644 index 0000000..c46b2e6 --- /dev/null +++ b/quakec_rogue/oldone.qc @@ -0,0 +1,327 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +OLD ONE + +============================================================================== +*/ +$cd id1/models/old_one +$origin 0 0 24 +$base base +$skin skin +$scale 1 + +void() finale_1; +void() finale_2; +void() finale_3; +void() finale_4; +void() finale_5; +void() finale_6; + +entity shub; + +$frame old1 old2 old3 old4 old5 old6 old7 old8 old9 +$frame old10 old11 old12 old13 old14 old15 old16 old17 old18 old19 +$frame old20 old21 old22 old23 old24 old25 old26 old27 old28 old29 +$frame old30 old31 old32 old33 old34 old35 old36 old37 old38 old39 +$frame old40 old41 old42 old43 old44 old45 old46 + +$frame shake1 shake2 shake3 shake4 shake5 shake6 shake7 shake8 +$frame shake9 shake10 shake11 shake12 shake12 shake13 shake14 +$frame shake15 shake16 shake17 shake18 shake19 shake20 + +//void() old_stand =[ $old1, old_stand ] {}; + +void() old_idle1 =[ $old1, old_idle2 ] {}; +void() old_idle2 =[ $old2, old_idle3 ] {}; +void() old_idle3 =[ $old3, old_idle4 ] {}; +void() old_idle4 =[ $old4, old_idle5 ] {}; +void() old_idle5 =[ $old5, old_idle6 ] {}; +void() old_idle6 =[ $old6, old_idle7 ] {}; +void() old_idle7 =[ $old7, old_idle8 ] {}; +void() old_idle8 =[ $old8, old_idle9 ] {}; +void() old_idle9 =[ $old9, old_idle10 ] {}; +void() old_idle10 =[ $old10, old_idle11 ] {}; +void() old_idle11 =[ $old11, old_idle12 ] {}; +void() old_idle12 =[ $old12, old_idle13 ] {}; +void() old_idle13 =[ $old13, old_idle14 ] {}; +void() old_idle14 =[ $old14, old_idle15 ] {}; +void() old_idle15 =[ $old15, old_idle16 ] {}; +void() old_idle16 =[ $old16, old_idle17 ] {}; +void() old_idle17 =[ $old17, old_idle18 ] {}; +void() old_idle18 =[ $old18, old_idle19 ] {}; +void() old_idle19 =[ $old19, old_idle20 ] {}; +void() old_idle20 =[ $old20, old_idle21 ] {}; +void() old_idle21 =[ $old21, old_idle22 ] {}; +void() old_idle22 =[ $old22, old_idle23 ] {}; +void() old_idle23 =[ $old23, old_idle24 ] {}; +void() old_idle24 =[ $old24, old_idle25 ] {}; +void() old_idle25 =[ $old25, old_idle26 ] {}; +void() old_idle26 =[ $old26, old_idle27 ] {}; +void() old_idle27 =[ $old27, old_idle28 ] {}; +void() old_idle28 =[ $old28, old_idle29 ] {}; +void() old_idle29 =[ $old29, old_idle30 ] {}; +void() old_idle30 =[ $old30, old_idle31 ] {}; +void() old_idle31 =[ $old31, old_idle32 ] {}; +void() old_idle32 =[ $old32, old_idle33 ] {}; +void() old_idle33 =[ $old33, old_idle34 ] {}; +void() old_idle34 =[ $old34, old_idle35 ] {}; +void() old_idle35 =[ $old35, old_idle36 ] {}; +void() old_idle36 =[ $old36, old_idle37 ] {}; +void() old_idle37 =[ $old37, old_idle38 ] {}; +void() old_idle38 =[ $old38, old_idle39 ] {}; +void() old_idle39 =[ $old39, old_idle40 ] {}; +void() old_idle40 =[ $old40, old_idle41 ] {}; +void() old_idle41 =[ $old41, old_idle42 ] {}; +void() old_idle42 =[ $old42, old_idle43 ] {}; +void() old_idle43 =[ $old43, old_idle44 ] {}; +void() old_idle44 =[ $old44, old_idle45 ] {}; +void() old_idle45 =[ $old45, old_idle46 ] {}; +void() old_idle46 =[ $old46, old_idle1 ] {}; + + +void() old_thrash1 =[ $shake1, old_thrash2 ] {lightstyle(0, "m");}; +void() old_thrash2 =[ $shake2, old_thrash3 ] {lightstyle(0, "k");}; +void() old_thrash3 =[ $shake3, old_thrash4 ] {lightstyle(0, "k");}; +void() old_thrash4 =[ $shake4, old_thrash5 ] {lightstyle(0, "i");}; +void() old_thrash5 =[ $shake5, old_thrash6 ] {lightstyle(0, "g");}; +void() old_thrash6 =[ $shake6, old_thrash7 ] {lightstyle(0, "e");}; +void() old_thrash7 =[ $shake7, old_thrash8 ] {lightstyle(0, "c");}; +void() old_thrash8 =[ $shake8, old_thrash9 ] {lightstyle(0, "a");}; +void() old_thrash9 =[ $shake9, old_thrash10 ] {lightstyle(0, "c");}; +void() old_thrash10 =[ $shake10, old_thrash11 ] {lightstyle(0, "e");}; +void() old_thrash11 =[ $shake11, old_thrash12 ] {lightstyle(0, "g");}; +void() old_thrash12 =[ $shake12, old_thrash13 ] {lightstyle(0, "i");}; +void() old_thrash13 =[ $shake13, old_thrash14 ] {lightstyle(0, "k");}; +void() old_thrash14 =[ $shake14, old_thrash15 ] {lightstyle(0, "m");}; +void() old_thrash15 =[ $shake15, old_thrash16 ] {lightstyle(0, "m"); +self.cnt = self.cnt + 1; +if (self.cnt != 3) + self.think = old_thrash1; +}; +void() old_thrash16 =[ $shake16, old_thrash17 ] {lightstyle(0, "g");}; +void() old_thrash17 =[ $shake17, old_thrash18 ] {lightstyle(0, "c");}; +void() old_thrash18 =[ $shake18, old_thrash19 ] {lightstyle(0, "b");}; +void() old_thrash19 =[ $shake19, old_thrash20 ] {lightstyle(0, "a");}; +void() old_thrash20 =[ $shake20, old_thrash20 ] {finale_4();}; + +//============================================================================ + +void() finale_1 = +{ + local entity pos, pl; + local entity timer; + + intermission_exittime = time + 10000000; // never allow exit + intermission_running = 1; + + // find the intermission spot + pos = find (world, classname, "info_intermission"); + if (!pos) + error ("no info_intermission"); + pl = find (world, classname, "misc_teleporttrain"); + if (!pl) + error ("no teleporttrain"); + remove (pl); + + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, ""); + + pl = find (world, classname, "player"); + while (pl != world) + { + pl.view_ofs = '0 0 0'; + pl.angles = other.v_angle = pos.mangle; + pl.fixangle = TRUE; // turn this way immediately + pl.map = self.map; + pl.nextthink = time + 0.5; + pl.takedamage = DAMAGE_NO; + pl.solid = SOLID_NOT; + pl.movetype = MOVETYPE_NONE; + pl.modelindex = 0; + setorigin (pl, pos.origin); + pl = find (pl, classname, "player"); + } + + // make fake versions of all players as standins, and move the real + // players to the intermission spot + + // wait for 1 second + timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_2; +}; + +void() finale_2 = +{ + local vector o; + + // start a teleport splash inside shub + + o = shub.origin - '0 100 0'; + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, o_x); + WriteCoord (MSG_BROADCAST, o_y); + WriteCoord (MSG_BROADCAST, o_z); + + sound (shub, CHAN_VOICE, "misc/r_tele1.wav", 1, ATTN_NORM); + + self.nextthink = time + 2; + self.think = finale_3; +}; + +void() finale_3 = +{ + // start shub thrashing wildly + shub.think = old_thrash1; + sound (shub, CHAN_VOICE, "boss2/death.wav", 1, ATTN_NORM); + lightstyle(0, "abcdefghijklmlkjihgfedcb"); +}; + +void() finale_4 = +{ + // throw tons of meat chunks + local vector oldo; + local float x, y, z; + local float r; + local entity n; + + sound (self, CHAN_VOICE, "boss2/pop2.wav", 1, ATTN_NORM); + + oldo = self.origin; + + z = 16; + while (z <= 144) + { + x = -64; + while (x <= 64) + { + y = -64; + while (y <= 64) + { + self.origin_x = oldo_x + x; + self.origin_y = oldo_y + y; + self.origin_z = oldo_z + z; + + r = random(); + if (r < 0.3) + ThrowGib ("progs/gib1.mdl", -999); + else if (r < 0.6) + ThrowGib ("progs/gib2.mdl", -999); + else + ThrowGib ("progs/gib3.mdl", -999); + y = y + 32; + } + x = x + 32; + } + z = z + 96; + } + // start the end text + WriteByte (MSG_ALL, SVC_FINALE); + WriteString (MSG_ALL, "$qc_finale_end"); + +// put a player model down + n = spawn(); + setmodel (n, "progs/player.mdl"); + oldo = oldo - '32 264 0'; + setorigin (n, oldo); + n.angles = '0 290 0'; + n.frame = 1; + + remove (self); + +// switch cd track + WriteByte (MSG_ALL, SVC_CDTRACK); + WriteByte (MSG_ALL, 3); + WriteByte (MSG_ALL, 3); + lightstyle(0, "m"); + + // instead of sitting here forever, run the quake ex credits and send the user back to start + local entity timer = spawn(); + timer.nextthink = time + 1; + timer.think = finale_5; +}; + +void() finale_5 = +{ + if (finaleFinished()) { + self.nextthink = time + 5; + self.think = finale_6; + } else { + self.nextthink = time + 0.1; + } +} + +void() finale_6 = +{ + if (!coop) { + localcmd("menu_credits\n"); + localcmd("disconnect\n"); + } else { + changelevel("start"); + } +} +//============================================================================ + +void () nopain = +{ + self.health = 40000; +}; + +//============================================================================ + + +/*QUAKED monster_oldone (1 0 0) (-16 -16 -24) (16 16 32) +*/ +void() monster_oldone = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model2 ("progs/oldone.mdl"); + + precache_sound2 ("boss2/death.wav"); + precache_sound2 ("boss2/idle.wav"); + precache_sound2 ("boss2/sight.wav"); + precache_sound2 ("boss2/pop2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/oldone.mdl"); + setsize (self, '-160 -128 -24', '160 128 256'); + + self.health = 40000; // kill by telefrag + self.think = old_idle1; + self.nextthink = time + 0.1; + self.takedamage = DAMAGE_YES; + self.th_pain = nopain; + self.th_die = finale_1; + shub = self; + + total_monsters = total_monsters + 1; +}; + diff --git a/quakec_rogue/pendulum.qc b/quakec_rogue/pendulum.qc new file mode 100644 index 0000000..3af75e1 --- /dev/null +++ b/quakec_rogue/pendulum.qc @@ -0,0 +1,410 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// pendulum.qc + +$frame frame1 frame2 frame3 frame4 frame5 frame6 frame7 +$frame frame8 frame9 frame10 frame11 frame12 frame13 + +float impactVelocity; + +//float pend_frame; + +float PEND_X = 1; +float PEND_Y = 2; +float PEND_Z = 4; +float PEND_TRG = 8; + +/* +void() pend_stand =[ $frame4, pend_stand ] {}; +*/ + +void() pend_swing1 =[ $frame1, pend_swing2 ] +{ +//// pend_frame = 1; +// 0 -148 88 (center) W 56, H 80 (28,40) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -176 48', '8 -120 128' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-176 -8 48', '-120 8 128' ); + self.nextthink = time + 0.17; + impactVelocity = 1; +}; +void() pend_swing2 =[ $frame2, pend_swing3 ] +{ +//// pend_frame = pend_frame + 1; +// 0 -142 50 (center) W 60, H 76 (30, 38) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -172 12', '8 -112 88' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-172 -8 12', '-112 8 88' ); + self.nextthink = time + 0.15; +}; +void() pend_swing3 =[ $frame3, pend_swing4 ] +{ +//// pend_frame = pend_frame + 1; +// 0 -128 14 (center) W 64, H 72 (32, 36) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -160 -22', '8 -96 50' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-160 -8 -22', '-96 8 50' ); + self.nextthink = time + 0.13; +}; +void() pend_swing4 =[ $frame4, pend_swing5 ] +{ + sound ( self, CHAN_AUTO, "pendulum/swing.wav", 0.5, ATTN_NORM); + +//// pend_frame = pend_frame + 1; +// 0 -104 -17 (center) W 68, H 68 (34, 34) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -138 -51', '8 -70 17' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-138 -8 -51', '-70 8 17' ); + self.nextthink = time + 0.11; +}; +void() pend_swing5 =[ $frame5, pend_swing6 ] +{ +// pend_frame = pend_frame + 1; +// 0 -74 40 (center) W 72, H 64 (36, 32) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -110 -72', '8 -38 -8' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-110 -8 -72', '-38 8 -8' ); + self.nextthink = time + 0.09; +}; +void() pend_swing6 =[ $frame6, pend_swing7 ] +{ +// pend_frame = pend_frame + 1; +// 0 -38 53 (center) W 76, H 60 (38, 30) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -76 -83', '8 0 -23' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-76 -8 -83', '0 8 -23' ); + self.nextthink = time + 0.07; +}; +void() pend_swing7 =[ $frame7, pend_swing8 ] +{ +// pend_frame = pend_frame + 1; +// 0 0 60 (center) W 80, H 56 (40, 28) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -40 -88', '8 40 -32' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-40 -8 -88', '40 8 -32' ); + self.nextthink = time + 0.05; +}; +void() pend_swing8 =[ $frame8, pend_swing9 ] +{ +// pend_frame = pend_frame + 1; +// 0 38 53 (center) W 76, H 60 (38, 30) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 0 -83', '8 76 -23' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '0 -8 -83', '76 8 -23' ); + self.nextthink = time + 0.07; +}; +void() pend_swing9 =[ $frame9, pend_swing10 ] +{ +// pend_frame = pend_frame + 1; +// 0 74 40 (center) W 72, H 64 (36, 32) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 38 -72', '8 100 -8' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '38 -8 -72', '100 8 -8' ); + self.nextthink = time + 0.09; +}; +void() pend_swing10 =[ $frame10, pend_swing11 ] +{ +// pend_frame = pend_frame + 1; +// 0 104 17 (center) W 68, H 68 (34, 34) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 70 -51', '8 138 17' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '70 -8 -51', '138 8 17' ); + self.nextthink = time + 0.11; +}; +void() pend_swing11 =[ $frame11, pend_swing12 ] +{ +// pend_frame = pend_frame + 1; +// 0 128 -14 (center) W 64, H 72 (32, 36) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 96 -22', '8 160 50' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '96 -8 -22', '160 8 50' ); + self.nextthink = time + 0.13; +}; +void() pend_swing12 =[ $frame12, pend_swing13 ] +{ +// pend_frame = pend_frame + 1; +// 0 142 -50 (center) W 60, H 76 (30, 38) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 112 12', '8 172 88' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '112 -8 12', '172 8 88' ); + self.nextthink = time + 0.15; +}; +void() pend_swing13 =[ $frame13, pend_swing14 ] +{ +// pend_frame = pend_frame + 1; +// 0 148 -88 (center) W 56, H 80 (28, 40) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 120 48', '8 176 128' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '120 -8 48', '176 8 128' ); + self.nextthink = time + 0.17; +}; +void() pend_swing14 =[ $frame13, pend_swing15 ] +{ +// pend_frame = pend_frame + 1; + // same frame, same location. no change to bbox. + self.nextthink = time + 0.17; +}; + +void() pend_swing15 =[ $frame12, pend_swing16 ] +{ +// pend_frame = pend_frame + 1; +// 0 142 -50 (center) W 60, H 76 (30, 38) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 112 12', '8 172 88' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '112 -8 12', '172 8 88' ); + self.nextthink = time + 0.15; + impactVelocity = -1; +}; +void() pend_swing16 =[ $frame11, pend_swing17 ] +{ +// pend_frame = pend_frame + 1; +// 0 128 -14 (center) W 64, H 72 (32, 36) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 96 -22', '8 160 50' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '96 -8 -22', '160 8 50' ); + self.nextthink = time + 0.13; +}; +void() pend_swing17 =[ $frame10, pend_swing18 ] +{ + sound ( self, CHAN_AUTO, "pendulum/swing.wav", 0.5, ATTN_NORM); + +// pend_frame = pend_frame + 1; +// 0 104 17 (center) W 68, H 68 (34, 34) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 70 -51', '8 138 17' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '70 -8 -51', '138 8 17' ); + self.nextthink = time + 0.11; +}; +void() pend_swing18 =[ $frame9, pend_swing19 ] +{ +// pend_frame = pend_frame + 1; +// 0 74 40 (center) W 72, H 64 (36, 32) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 38 -72', '8 100 -8' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '38 -8 -72', '100 8 -8' ); + self.nextthink = time + 0.09; +}; +void() pend_swing19 =[ $frame8, pend_swing20 ] +{ +// pend_frame = pend_frame + 1; +// 0 38 53 (center) W 76, H 60 (38, 30) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 0 -83', '8 76 -23' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '0 -8 -83', '76 8 -23' ); + self.nextthink = time + 0.07; +}; +void() pend_swing20 =[ $frame7, pend_swing21 ] +{ +// pend_frame = pend_frame + 1; +// 0 0 60 (center) W 80, H 56 (40, 28) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -40 -88', '8 40 -32' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-40 -8 -88', '40 8 -32' ); + self.nextthink = time + 0.05; +}; +void() pend_swing21 =[ $frame6, pend_swing22 ] +{ +// pend_frame = pend_frame + 1; +// 0 -38 53 (center) W 76, H 60 (38, 30) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -76 -83', '8 0 -23' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-76 -8 -83', '0 8 -23' ); + self.nextthink = time + 0.07; +}; +void() pend_swing22 =[ $frame5, pend_swing23 ] +{ +// pend_frame = pend_frame + 1; +// 0 -74 40 (center) W 72, H 64 (36, 32) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -110 -72', '8 -28 -8' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-110 -8 -72', '-28 8 -8' ); + self.nextthink = time + 0.09; +}; +void() pend_swing23 =[ $frame4, pend_swing24 ] +{ +// pend_frame = pend_frame + 1; +// 0 -104 17 (center) W 68, H 68 (34, 34) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -172 -51', '8 -70 17' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-172 -8 -51', '-70 8 17' ); + self.nextthink = time + 0.11; +}; +void() pend_swing24 =[ $frame3, pend_swing25 ] +{ +// pend_frame = pend_frame + 1; +// 0 -128 -14 (center) W 64, H 72 (32, 36) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -160 -22', '8 -96 50' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-160 -8 -22', '-96 8 50' ); + self.nextthink = time + 0.13; +}; +void() pend_swing25 =[ $frame2, pend_swing26 ] +{ +// pend_frame = pend_frame + 1; +// 0 -142 -50 (center) W 60, H 76 (30, 38) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -172 12', '8 -112 88' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-172 -8 12', '-112 8 88' ); + self.nextthink = time + 0.15; +}; +void() pend_swing26 =[ $frame1, pend_swing1 ] +{ +// pend_frame = pend_frame + 1; +// 0 -148 -88 (center) W 56, H 80 (28,40) + if( self.spawnflags & PEND_Y) + setsize ( self, '-8 -176 48', '8 -120 128' ); + else if ( self.spawnflags & PEND_X ) + setsize ( self, '-176 -8 48', '-120 8 128' ); + self.nextthink = time + 0.17; +}; + +void() pend_touch = +{ + if (other.health < 1 || other.takedamage == DAMAGE_NO) + return; + + if (self.attack_finished < time) + { + sound ( self, CHAN_VOICE, "pendulum/hit.wav", 1, ATTN_NORM); + self.attack_finished = time + 1; + } + + T_Damage (other, self, self, self.currentammo); + + // Fling the impactee in the direction the blade was swinging. + if (self.spawnflags & PEND_Y) + { +// other.velocity_y = other.velocity_y - impactVelocity * 250; + other.velocity_y = impactVelocity * -250; + } + else if(self.spawnflags & PEND_X) + { +// other.velocity_x = other.velocity_x + impactVelocity * 250; + other.velocity_x = impactVelocity * 250; + } + + // Fling the impactee up a little bit. +// other.velocity_z = other.velocity_z + 200; + other.velocity_z = 200; + + SpawnMeatSpray ( other.origin, other.velocity); +}; + +void() pend_use = +{ + self.nextthink = time + self.delay; + self.think = pend_swing1; +}; + + +/*QUAKED pendulum (0 .5 .8) (-8 -24 -100) (8 24 100) PEND_X PEND_Y PEND_TRG +The Pendulum + +Values: +delay - amount of time (in seconds) before starting swinging. default 1. +currentammo - amount of damage for each contact. default 5. + +Directional Flags: Pendulum swings perpendicular to it's angle. (eg. A pendulum facing east will swing north/south). + +Default: PEND_Y + +The pendulum angle is set automatically depending on the PEND_ choice. + +PEND_X - a pendulum hanging from the ceiling swinging parallel to the X axis. +PEND_Y - a pendulum hanging from the ceiling swinging parallel to the Y axis. + +PEND_TRG - the pendulum must be triggered to start swinging. The delay value still affects the pendulum. +*/ +void() pendulum = +{ + local vector center; + + precache_model ("progs/pendulum.mdl"); + precache_sound ("pendulum/swing.wav"); + precache_sound ("pendulum/hit.wav"); + + setmodel (self, "progs/pendulum.mdl"); + + if (!self.spawnflags) + self.spawnflags = self.spawnflags + PEND_Y; + + if (self.spawnflags & PEND_Y) + { + setsize (self, '-8 -24 -100', '8 24 100'); + self.angles = '0 0 0'; + self.v_angle = '0 0 0'; +// if (self.angles_y != 0 ) +// self.angles_y = 0; + } + else if ( self.spawnflags & PEND_X ) + { + setsize (self, '-24 -8 -100', '24 8 100'); + self.angles = '0 270 0'; + self.v_angle = '0 270 0'; +// if (self.angles_y != 270 ) +// self.angles_y = 270; + } + else + error ( "ERROR: Unimplemented Pendulum Type (pendulum.qc)" ); + + if (!self.currentammo) + self.currentammo = 5; + if (!self.delay) + self.delay = 1; + + impactVelocity = 0; + self.solid = SOLID_TRIGGER; + self.takedamage = DAMAGE_NO; + self.touch = pend_touch; + + if (self.spawnflags & PEND_TRG) + { + self.use = pend_use; + } + else + { + self.nextthink = time + self.delay; + self.think = pend_swing1; + } +}; diff --git a/quakec_rogue/plats.qc b/quakec_rogue/plats.qc new file mode 100644 index 0000000..289112c --- /dev/null +++ b/quakec_rogue/plats.qc @@ -0,0 +1,383 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + + +void() plat_center_touch; +void() plat_outside_touch; +void() plat_trigger_use; +void() plat_go_up; +void() plat_go_down; +void() plat_crush; +float PLAT_LOW_TRIGGER = 1; + +void() plat_spawn_inside_trigger = +{ + local entity trigger; + local vector tmin, tmax; + +// +// middle trigger +// + trigger = spawn(); + trigger.touch = plat_center_touch; + trigger.movetype = MOVETYPE_NONE; + trigger.solid = SOLID_TRIGGER; + trigger.enemy = self; + + tmin = self.mins + '25 25 0'; + tmax = self.maxs - '25 25 -8'; + tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8); + if (self.spawnflags & PLAT_LOW_TRIGGER) + tmax_z = tmin_z + 8; + + if (self.size_x <= 50) + { + tmin_x = (self.mins_x + self.maxs_x) / 2; + tmax_x = tmin_x + 1; + } + if (self.size_y <= 50) + { + tmin_y = (self.mins_y + self.maxs_y) / 2; + tmax_y = tmin_y + 1; + } + + setsize (trigger, tmin, tmax); +}; + +void() plat_hit_top = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_TOP; + self.think = plat_go_down; + self.nextthink = self.ltime + 3; +}; + +void() plat_hit_bottom = +{ + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + self.state = STATE_BOTTOM; +}; + +void() plat_go_down = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_DOWN; + SUB_CalcMove (self.pos2, self.speed, plat_hit_bottom); +}; + +void() plat_go_up = +{ + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + self.state = STATE_UP; + SUB_CalcMove (self.pos1, self.speed, plat_hit_top); +}; + +void() plat_center_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + + self = self.enemy; + if (self.state == STATE_BOTTOM) + plat_go_up (); + else if (self.state == STATE_TOP) + self.nextthink = self.ltime + 1; // delay going down +}; + +void() plat_outside_touch = +{ + if (other.classname != "player") + return; + + if (other.health <= 0) + return; + +//dprint ("plat_outside_touch\n"); + self = self.enemy; + if (self.state == STATE_TOP) + plat_go_down (); +}; + +void() plat_trigger_use = +{ + if (self.think) + return; // allready activated + plat_go_down(); +}; + + +void() plat_crush = +{ +//dprint ("plat_crush\n"); + + T_Damage (other, self, self, 1); + + if (self.state == STATE_UP) + plat_go_down (); + else if (self.state == STATE_DOWN) + plat_go_up (); + else + objerror ("plat_crush: bad self.state\n"); +}; + +void() plat_use = +{ + self.use = SUB_Null; + if (self.state != STATE_UP) + objerror ("plat_use: not in up state"); + plat_go_down(); +}; + + +/*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 triggered, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height. +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + + +void() func_plat = + +{ +local entity t; + + if (!self.t_length) + self.t_length = 80; + if (!self.t_width) + self.t_width = 10; + + if (self.sounds == 0) + self.sounds = 2; +// FIX THIS TO LOAD A GENERIC PLAT SOUND + + if (self.sounds == 1) + { + precache_sound ("plats/plat1.wav"); + precache_sound ("plats/plat2.wav"); + self.noise = "plats/plat1.wav"; + self.noise1 = "plats/plat2.wav"; + } + + if (self.sounds == 2) + { + precache_sound ("plats/medplat1.wav"); + precache_sound ("plats/medplat2.wav"); + self.noise = "plats/medplat1.wav"; + self.noise1 = "plats/medplat2.wav"; + } + + + self.mangle = self.angles; + self.angles = '0 0 0'; + + self.classname = "plat"; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + setorigin (self, self.origin); + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + + self.blocked = plat_crush; + if (!self.speed) + self.speed = 150; + +// pos1 is the top position, pos2 is the bottom + self.pos1 = self.origin; + self.pos2 = self.origin; + if (self.height) + self.pos2_z = self.origin_z - self.height; + else + self.pos2_z = self.origin_z - self.size_z + 8; + + self.use = plat_trigger_use; + + plat_spawn_inside_trigger (); // the "start moving" trigger + + if (self.targetname) + { + self.state = STATE_UP; + self.use = plat_use; + } + else + { + setorigin (self, self.pos2); + self.state = STATE_BOTTOM; + } +}; + +//============================================================================ + +void() train_next; +void() func_train_find; + +void() train_blocked = +{ + if (time < self.attack_finished) + return; + self.attack_finished = time + 0.5; + T_Damage (other, self, self, self.dmg); +}; +void() train_use = +{ + if (self.think != func_train_find) + return; // already activated + train_next(); +}; + +void() train_wait = +{ + if (self.wait) + { + self.nextthink = self.ltime + self.wait; + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + } + else + self.nextthink = self.ltime + 0.1; + + self.think = train_next; +}; + +void() train_next = +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + if (!self.target) + objerror ("train_next: no next target"); + if (targ.wait) + self.wait = targ.wait; + else + self.wait = 0; + sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); + SUB_CalcMove (targ.origin - self.mins, self.speed, train_wait); +}; + +void() func_train_find = + +{ + local entity targ; + + targ = find (world, targetname, self.target); + self.target = targ.target; + setorigin (self, targ.origin - self.mins); + if (!self.targetname) + { // not triggered, so start immediately + self.nextthink = self.ltime + 0.1; + self.think = train_next; + } +}; + +/*QUAKED func_train (0 .5 .8) ? +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 +sounds +1) ratchet metal + +*/ +void() func_train = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("func_train without a target"); + if (!self.dmg) + self.dmg = 2; + + if (self.sounds == 0) + { + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + } + + if (self.sounds == 1) + { + self.noise = ("plats/train2.wav"); + precache_sound ("plats/train2.wav"); + self.noise1 = ("plats/train1.wav"); + precache_sound ("plats/train1.wav"); + } + + self.cnt = 1; + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = train_use; + self.classname = "train"; + + setmodel (self, self.model); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + +/*QUAKED misc_teleporttrain (0 .5 .8) (-8 -8 -8) (8 8 8) +This is used for the final bos +*/ +void() misc_teleporttrain = +{ + if (!self.speed) + self.speed = 100; + if (!self.target) + objerror ("func_train without a target"); + + self.cnt = 1; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_PUSH; + self.blocked = train_blocked; + self.use = train_use; + self.avelocity = '100 200 300'; + + self.noise = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + self.noise1 = ("misc/null.wav"); + precache_sound ("misc/null.wav"); + + precache_model2 ("progs/teleport.mdl"); + setmodel (self, "progs/teleport.mdl"); + setsize (self, self.mins , self.maxs); + setorigin (self, self.origin); + +// start trains on the second frame, to make sure their targets have had +// a chance to spawn + self.nextthink = self.ltime + 0.1; + self.think = func_train_find; +}; + diff --git a/quakec_rogue/player.qc b/quakec_rogue/player.qc new file mode 100644 index 0000000..447a331 --- /dev/null +++ b/quakec_rogue/player.qc @@ -0,0 +1,836 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +void() bubble_bob; + +/* +============================================================================== + +PLAYER + +============================================================================== +*/ + +$cd /raid/quake/id1/models/player_4 +$origin 0 -6 24 +$base base +$skin skin + +// +// running +// +$frame axrun1 axrun2 axrun3 axrun4 axrun5 axrun6 + +$frame rockrun1 rockrun2 rockrun3 rockrun4 rockrun5 rockrun6 + +// +// standing +// +$frame stand1 stand2 stand3 stand4 stand5 + +$frame axstnd1 axstnd2 axstnd3 axstnd4 axstnd5 axstnd6 +$frame axstnd7 axstnd8 axstnd9 axstnd10 axstnd11 axstnd12 + + +// +// pain +// +$frame axpain1 axpain2 axpain3 axpain4 axpain5 axpain6 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + + +// +// death +// + +$frame axdeth1 axdeth2 axdeth3 axdeth4 axdeth5 axdeth6 +$frame axdeth7 axdeth8 axdeth9 + +$frame deatha1 deatha2 deatha3 deatha4 deatha5 deatha6 deatha7 deatha8 +$frame deatha9 deatha10 deatha11 + +$frame deathb1 deathb2 deathb3 deathb4 deathb5 deathb6 deathb7 deathb8 +$frame deathb9 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 deathc12 deathc13 deathc14 deathc15 + +$frame deathd1 deathd2 deathd3 deathd4 deathd5 deathd6 deathd7 +$frame deathd8 deathd9 + +$frame deathe1 deathe2 deathe3 deathe4 deathe5 deathe6 deathe7 +$frame deathe8 deathe9 + +// +// attacks +// +$frame nailatt1 nailatt2 + +$frame light1 light2 + +$frame rockatt1 rockatt2 rockatt3 rockatt4 rockatt5 rockatt6 + +$frame shotatt1 shotatt2 shotatt3 shotatt4 shotatt5 shotatt6 + +$frame axatt1 axatt2 axatt3 axatt4 axatt5 axatt6 + +$frame axattb1 axattb2 axattb3 axattb4 axattb5 axattb6 + +$frame axattc1 axattc2 axattc3 axattc4 axattc5 axattc6 + +$frame axattd1 axattd2 axattd3 axattd4 axattd5 axattd6 + + +/* +============================================================================== +PLAYER +============================================================================== +*/ + +void() player_run; + +void() player_stand1 =[ $axstnd1, player_stand1 ] +{ + self.weaponframe=0; + if (self.velocity_x || self.velocity_y) + { + self.walkframe=0; + player_run(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe >= 12) + self.walkframe = 0; + self.frame = $axstnd1 + self.walkframe; + } + else + { + if (self.walkframe >= 5) + self.walkframe = 0; + self.frame = $stand1 + self.walkframe; + } + self.walkframe = self.walkframe + 1; +}; + +void() player_run =[ $rockrun1, player_run ] +{ + self.weaponframe=0; + if (!self.velocity_x && !self.velocity_y) + { + self.walkframe=0; + player_stand1(); + return; + } + + if (self.weapon == IT_AXE) + { + if (self.walkframe == 6) + self.walkframe = 0; + self.frame = $axrun1 + self.walkframe; + } + else + { + if (self.walkframe == 6) + self.walkframe = 0; + self.frame = self.frame + self.walkframe; + } + self.walkframe = self.walkframe + 1; +}; + + +void() player_shot1 = [$shotatt1, player_shot2 ] {self.weaponframe=1; +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() player_shot2 = [$shotatt2, player_shot3 ] {self.weaponframe=2;}; +void() player_shot3 = [$shotatt3, player_shot4 ] {self.weaponframe=3;}; +void() player_shot4 = [$shotatt4, player_shot5 ] {self.weaponframe=4;}; +void() player_shot5 = [$shotatt5, player_shot6 ] {self.weaponframe=5;}; +void() player_shot6 = [$shotatt6, player_run ] {self.weaponframe=6;}; + +void() player_axe1 = [$axatt1, player_axe2 ] {self.weaponframe=1;}; +void() player_axe2 = [$axatt2, player_axe3 ] {self.weaponframe=2;}; +void() player_axe3 = [$axatt3, player_axe4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axe4 = [$axatt4, player_run ] {self.weaponframe=4;}; + +void() player_axeb1 = [$axattb1, player_axeb2 ] {self.weaponframe=5;}; +void() player_axeb2 = [$axattb2, player_axeb3 ] {self.weaponframe=6;}; +void() player_axeb3 = [$axattb3, player_axeb4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axeb4 = [$axattb4, player_run ] {self.weaponframe=8;}; + +void() player_axec1 = [$axattc1, player_axec2 ] {self.weaponframe=1;}; +void() player_axec2 = [$axattc2, player_axec3 ] {self.weaponframe=2;}; +void() player_axec3 = [$axattc3, player_axec4 ] {self.weaponframe=3;W_FireAxe();}; +void() player_axec4 = [$axattc4, player_run ] {self.weaponframe=4;}; + +void() player_axed1 = [$axattd1, player_axed2 ] {self.weaponframe=5;}; +void() player_axed2 = [$axattd2, player_axed3 ] {self.weaponframe=6;}; +void() player_axed3 = [$axattd3, player_axed4 ] {self.weaponframe=7;W_FireAxe();}; +void() player_axed4 = [$axattd4, player_run ] {self.weaponframe=8;}; + +//============================================================================ +//ZOID-- +void() player_grapple3; +void() player_grapple4; +void() player_grapple5; + +void() player_grapple1 = [$axattd1, player_grapple2 ] + { self.weaponframe=1; W_FireGrapple(); }; +// { self.weaponframe=2; W_FireGrapple(); }; +void() player_grapple2 = [$axattd2, player_grapple3 ] + { self.weaponframe=2; }; +// { self.weaponframe=3; }; +void() player_grapple3 = [$axattd3, player_grapple3 ] + { +// self.weaponframe=3; + self.weaponframe=2; + if (!self.hook_out) { + player_grapple5(); + return; + } + if (vlen(self.hook.origin - self.origin) >= 50) { + if (self.on_hook) { + player_grapple4(); + return; + } + } + }; +void() player_grapple4 = [$deathc4, player_grapple4 ] + { +// self.weaponframe=4; + self.weaponframe=2; + if (!self.hook_out) { + player_grapple5(); + return; + } + if (vlen(self.hook.origin - self.origin) < 50 || !self.on_hook) { + player_grapple3(); + return; + } + }; +void() player_grapple5 = [$axattd4, player_run ] +// {self.weaponframe=5;}; + {self.weaponframe=1;}; +//--ZOID +//============================================================================ + +void() player_nail1 =[$nailatt1, player_nail2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireSpikes (4); + self.attack_finished = time + 0.2; +}; +void() player_nail2 =[$nailatt2, player_nail1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireSpikes (-4); + self.attack_finished = time + 0.2; +}; + +//============================================================================ + +void(float ox) W_FireLavaSpikes; + +void() player_lava_nail1 =[$nailatt1, player_lava_nail2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireLavaSpikes (4); + self.attack_finished = time + 0.2; +}; +void() player_lava_nail2 =[$nailatt2, player_lava_nail1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 9) + self.weaponframe = 1; + SuperDamageSound(); + W_FireLavaSpikes (-4); + self.attack_finished = time + 0.2; +}; + +//============================================================================ +void() W_FirePlasma; + +void() player_light1 =[$light1, player_light2 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) + self.weaponframe = 1; + SuperDamageSound(); + + if (self.weapon == IT_LIGHTNING) + { + W_FireLightning(); + self.attack_finished = time + 0.2; + } + else if (self.weapon == IT_PLASMA_GUN) + { + W_FirePlasma(); + player_run(); + return; + } +}; +void() player_light2 =[$light2, player_light1 ] +{ + self.effects = self.effects | EF_MUZZLEFLASH; + + if (!self.button0) + {player_run ();return;} + self.weaponframe = self.weaponframe + 1; + if (self.weaponframe == 5) + self.weaponframe = 1; + SuperDamageSound(); + + if (self.weapon == IT_LIGHTNING) + { + W_FireLightning(); + self.attack_finished = time + 0.2; + } +}; + +//============================================================================ + + +void() player_rocket1 =[$rockatt1, player_rocket2 ] {self.weaponframe=1; +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() player_rocket2 =[$rockatt2, player_rocket3 ] {self.weaponframe=2;}; +void() player_rocket3 =[$rockatt3, player_rocket4 ] {self.weaponframe=3;}; +void() player_rocket4 =[$rockatt4, player_rocket5 ] {self.weaponframe=4;}; +void() player_rocket5 =[$rockatt5, player_rocket6 ] {self.weaponframe=5;}; +void() player_rocket6 =[$rockatt6, player_run ] {self.weaponframe=6;}; +void(float num_bubbles) DeathBubbles; + +void() PainSound = +{ +local float rs; + + if (self.health < 0) + return; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + +// water pain sounds + if (self.watertype == CONTENT_WATER && self.waterlevel == 3) + { + DeathBubbles(1); + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/drown1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/drown2.wav", 1, ATTN_NORM); + return; + } + +// slime pain sounds + if (self.watertype == CONTENT_SLIME) + { +// FIX ME put in some steam here + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + return; + } + + if (self.watertype == CONTENT_LAVA) + { + if (random() > 0.5) + sound (self, CHAN_VOICE, "player/lburn1.wav", 1, ATTN_NORM); + else + sound (self, CHAN_VOICE, "player/lburn2.wav", 1, ATTN_NORM); + return; + } + + if (self.pain_finished > time) + { + self.axhitme = 0; + return; + } + self.pain_finished = time + 0.5; + +// don't make multiple pain sounds right after each other + +// ax pain sound + if (self.axhitme == 1) + { + self.axhitme = 0; + sound (self, CHAN_VOICE, "player/axhit1.wav", 1, ATTN_NORM); + return; + } + + + rs = rint((random() * 5) + 1); + + self.noise = ""; + if (rs == 1) + self.noise = "player/pain1.wav"; + else if (rs == 2) + self.noise = "player/pain2.wav"; + else if (rs == 3) + self.noise = "player/pain3.wav"; + else if (rs == 4) + self.noise = "player/pain4.wav"; + else if (rs == 5) + self.noise = "player/pain5.wav"; + else + self.noise = "player/pain6.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + return; +}; + +void() player_pain1 = [ $pain1, player_pain2 ] {PainSound();self.weaponframe=0;}; +void() player_pain2 = [ $pain2, player_pain3 ] {}; +void() player_pain3 = [ $pain3, player_pain4 ] {}; +void() player_pain4 = [ $pain4, player_pain5 ] {}; +void() player_pain5 = [ $pain5, player_pain6 ] {}; +void() player_pain6 = [ $pain6, player_run ] {}; + +void() player_axpain1 = [ $axpain1, player_axpain2 ] {PainSound();self.weaponframe=0;}; +void() player_axpain2 = [ $axpain2, player_axpain3 ] {}; +void() player_axpain3 = [ $axpain3, player_axpain4 ] {}; +void() player_axpain4 = [ $axpain4, player_axpain5 ] {}; +void() player_axpain5 = [ $axpain5, player_axpain6 ] {}; +void() player_axpain6 = [ $axpain6, player_run ] {}; + +void() player_pain = +{ + if (self.weaponframe) + return; + + if (self.invisible_finished > time) + return; // eyes don't have pain frames + + if (self.weapon == IT_AXE) + player_axpain1 (); + else + player_pain1 (); +}; + +void() player_diea1; +void() player_dieb1; +void() player_diec1; +void() player_died1; +void() player_diee1; +void() player_die_ax1; + +void() DeathBubblesSpawn = +{ +local entity bubble; + if (self.owner.waterlevel != 3) + return; + bubble = spawn(); + setmodel (bubble, "progs/s_bubble.spr"); + setorigin (bubble, self.owner.origin + '0 0 24'); + bubble.movetype = MOVETYPE_NOCLIP; + bubble.solid = SOLID_NOT; + bubble.velocity = '0 0 15'; + bubble.nextthink = time + 0.5; + bubble.think = bubble_bob; + bubble.classname = "bubble"; + bubble.frame = 0; + bubble.cnt = 0; + setsize (bubble, '-8 -8 -8', '8 8 8'); + self.nextthink = time + 0.1; + self.think = DeathBubblesSpawn; + self.air_finished = self.air_finished + 1; + if (self.air_finished >= self.bubble_count) + remove(self); +}; + +void(float num_bubbles) DeathBubbles = +{ +local entity bubble_spawner; + + bubble_spawner = spawn(); + setorigin (bubble_spawner, self.origin); + bubble_spawner.movetype = MOVETYPE_NONE; + bubble_spawner.solid = SOLID_NOT; + bubble_spawner.nextthink = time + 0.1; + bubble_spawner.think = DeathBubblesSpawn; + bubble_spawner.air_finished = 0; + bubble_spawner.owner = self; + bubble_spawner.bubble_count = num_bubbles; + return; +}; + + +void() DeathSound = +{ +local float rs; + + // water death sounds + if (self.waterlevel == 3) + { + DeathBubbles(20); + sound (self, CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + + rs = rint ((random() * 4) + 1); + if (rs == 1) + self.noise = "player/death1.wav"; + if (rs == 2) + self.noise = "player/death2.wav"; + if (rs == 3) + self.noise = "player/death3.wav"; + if (rs == 4) + self.noise = "player/death4.wav"; + if (rs == 5) + self.noise = "player/death5.wav"; + + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NONE); + return; +}; + + +void() PlayerDead = +{ + self.nextthink = -1; +// allow respawn after a certain time + self.deadflag = DEAD_DEAD; +}; + +vector(float dm) VelocityForDamage = +{ + local vector v; + + v_x = 100 * crandom(); + v_y = 100 * crandom(); + v_z = 200 + 100 * random(); + + if (dm > -50) + { +// dprint ("level 1\n"); + v = v * 0.7; + } + else if (dm > -200) + { +// dprint ("level 3\n"); + v = v * 2; + } + else + v = v * 10; + + return v; +}; + +void(string gibname, float dm) ThrowGib = +{ + local entity new; + + new = spawn(); + new.origin = self.origin; + setmodel (new, gibname); + setsize (new, '0 0 0', '0 0 0'); + new.velocity = VelocityForDamage (dm); + new.movetype = MOVETYPE_BOUNCE; + new.solid = SOLID_NOT; + new.avelocity_x = random()*600; + new.avelocity_y = random()*600; + new.avelocity_z = random()*600; + new.think = SUB_Remove; + new.ltime = time; + new.nextthink = time + 10 + random()*10; + new.frame = 0; + new.flags = 0; +}; + +void(string gibname, float dm) ThrowHead = +{ + setmodel (self, gibname); + self.frame = 0; + self.nextthink = -1; + self.movetype = MOVETYPE_BOUNCE; + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.view_ofs = '0 0 8'; + setsize (self, '-16 -16 0', '16 16 56'); + self.velocity = VelocityForDamage (dm); + self.origin_z = self.origin_z - 24; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.avelocity = crandom() * '0 600 0'; +}; + + +void() GibPlayer = +{ + ThrowHead ("progs/h_player.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + + self.deadflag = DEAD_DEAD; + + if (damage_attacker.classname == "teledeath") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (damage_attacker.classname == "teledeath2") + { + sound (self, CHAN_VOICE, "player/teledth1.wav", 1, ATTN_NONE); + return; + } + + if (random() < 0.5) + sound (self, CHAN_VOICE, "player/gib.wav", 1, ATTN_NONE); + else + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NONE); +}; + +void() PlayerDie = +{ + local float i; + + self.items = self.items - (self.items & IT_INVISIBILITY); + self.invisible_finished = 0; // don't die as eyes + self.invincible_finished = 0; + self.super_damage_finished = 0; + self.radsuit_finished = 0; + self.modelindex = modelindex_player; // don't use eyes + + self.gravity = 1.0; + self.shield_finished = 0; + self.antigrav_finished = 0; + +//ZOID-- + if (deathmatch || coop) + { + DropBackpack(); + TeamDropFlagOfPlayer(self); + DropRune(); + } + + if (self.ctf_killed == 2) + self.ctf_killed = 0; + else + self.ctf_killed = 1; +//--ZOID + + self.weaponmodel=""; + self.view_ofs = '0 0 -8'; + self.deadflag = DEAD_DYING; + self.solid = SOLID_NOT; + self.flags = self.flags - (self.flags & FL_ONGROUND); + self.movetype = MOVETYPE_TOSS; + if (self.velocity_z < 10) + self.velocity_z = self.velocity_z + random()*300; + + if (self.health < -40) + { + GibPlayer (); + return; + } + + DeathSound(); + + self.angles_x = 0; + self.angles_z = 0; + + if (self.weapon == IT_AXE) + { + player_die_ax1 (); + return; + } + + i = cvar("temp1"); + if (!i) + i = 1 + floor(random()*6); + + if (i == 1) + player_diea1(); + else if (i == 2) + player_dieb1(); + else if (i == 3) + player_diec1(); + else if (i == 4) + player_died1(); + else + player_diee1(); + +}; + +void() set_suicide_frame = +{ // used by klill command and diconnect command + if (self.model != "progs/player.mdl") + return; // allready gibbed + self.frame = $deatha11; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_TOSS; + self.deadflag = DEAD_DEAD; + self.nextthink = -1; +}; + + +void() player_diea1 = [ $deatha1, player_diea2 ] {}; +void() player_diea2 = [ $deatha2, player_diea3 ] {}; +void() player_diea3 = [ $deatha3, player_diea4 ] {}; +void() player_diea4 = [ $deatha4, player_diea5 ] {}; +void() player_diea5 = [ $deatha5, player_diea6 ] {}; +void() player_diea6 = [ $deatha6, player_diea7 ] {}; +void() player_diea7 = [ $deatha7, player_diea8 ] {}; +void() player_diea8 = [ $deatha8, player_diea9 ] {}; +void() player_diea9 = [ $deatha9, player_diea10 ] {}; +void() player_diea10 = [ $deatha10, player_diea11 ] {}; +void() player_diea11 = [ $deatha11, player_diea11 ] {PlayerDead();}; + +void() player_dieb1 = [ $deathb1, player_dieb2 ] {}; +void() player_dieb2 = [ $deathb2, player_dieb3 ] {}; +void() player_dieb3 = [ $deathb3, player_dieb4 ] {}; +void() player_dieb4 = [ $deathb4, player_dieb5 ] {}; +void() player_dieb5 = [ $deathb5, player_dieb6 ] {}; +void() player_dieb6 = [ $deathb6, player_dieb7 ] {}; +void() player_dieb7 = [ $deathb7, player_dieb8 ] {}; +void() player_dieb8 = [ $deathb8, player_dieb9 ] {}; +void() player_dieb9 = [ $deathb9, player_dieb9 ] {PlayerDead();}; + +void() player_diec1 = [ $deathc1, player_diec2 ] {}; +void() player_diec2 = [ $deathc2, player_diec3 ] {}; +void() player_diec3 = [ $deathc3, player_diec4 ] {}; +void() player_diec4 = [ $deathc4, player_diec5 ] {}; +void() player_diec5 = [ $deathc5, player_diec6 ] {}; +void() player_diec6 = [ $deathc6, player_diec7 ] {}; +void() player_diec7 = [ $deathc7, player_diec8 ] {}; +void() player_diec8 = [ $deathc8, player_diec9 ] {}; +void() player_diec9 = [ $deathc9, player_diec10 ] {}; +void() player_diec10 = [ $deathc10, player_diec11 ] {}; +void() player_diec11 = [ $deathc11, player_diec12 ] {}; +void() player_diec12 = [ $deathc12, player_diec13 ] {}; +void() player_diec13 = [ $deathc13, player_diec14 ] {}; +void() player_diec14 = [ $deathc14, player_diec15 ] {}; +void() player_diec15 = [ $deathc15, player_diec15 ] {PlayerDead();}; + +void() player_died1 = [ $deathd1, player_died2 ] {}; +void() player_died2 = [ $deathd2, player_died3 ] {}; +void() player_died3 = [ $deathd3, player_died4 ] {}; +void() player_died4 = [ $deathd4, player_died5 ] {}; +void() player_died5 = [ $deathd5, player_died6 ] {}; +void() player_died6 = [ $deathd6, player_died7 ] {}; +void() player_died7 = [ $deathd7, player_died8 ] {}; +void() player_died8 = [ $deathd8, player_died9 ] {}; +void() player_died9 = [ $deathd9, player_died9 ] {PlayerDead();}; + +void() player_diee1 = [ $deathe1, player_diee2 ] {}; +void() player_diee2 = [ $deathe2, player_diee3 ] {}; +void() player_diee3 = [ $deathe3, player_diee4 ] {}; +void() player_diee4 = [ $deathe4, player_diee5 ] {}; +void() player_diee5 = [ $deathe5, player_diee6 ] {}; +void() player_diee6 = [ $deathe6, player_diee7 ] {}; +void() player_diee7 = [ $deathe7, player_diee8 ] {}; +void() player_diee8 = [ $deathe8, player_diee9 ] {}; +void() player_diee9 = [ $deathe9, player_diee9 ] {PlayerDead();}; + +void() player_die_ax1 = [ $axdeth1, player_die_ax2 ] {}; +void() player_die_ax2 = [ $axdeth2, player_die_ax3 ] {}; +void() player_die_ax3 = [ $axdeth3, player_die_ax4 ] {}; +void() player_die_ax4 = [ $axdeth4, player_die_ax5 ] {}; +void() player_die_ax5 = [ $axdeth5, player_die_ax6 ] {}; +void() player_die_ax6 = [ $axdeth6, player_die_ax7 ] {}; +void() player_die_ax7 = [ $axdeth7, player_die_ax8 ] {}; +void() player_die_ax8 = [ $axdeth8, player_die_ax9 ] {}; +void() player_die_ax9 = [ $axdeth9, player_die_ax9 ] {PlayerDead();}; + +void(entity shieldOwner, vector dir) shield_spawn; + +void() player_touch = +{ + local vector vec; + local float dot; + local float doDamage; + + if ( self.items2 & IT2_SHIELD) + { + if ( other.classname == "player") + { + makevectors ( self.angles ); + vec = normalize (other.origin - self.origin); + dot = vec * v_forward; + if (dot < 0.3) + return; + + makevectors ( self.angles ); + other.velocity = v_forward * 500; + other.velocity_z = 250; + + doDamage = FALSE; + if (TeamArmorDam(other, self, self, 15)) + { + if(self.shield_death_time < time) + doDamage = TRUE; + } + + // if shield has timed out, respawn it. + // don't do particles more than twice a second. + if ( self.shield_death_time < time) + { + sound(self,CHAN_WEAPON,"enforcer/enfstop.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, other.origin_x); + WriteCoord (MSG_BROADCAST, other.origin_y); + WriteCoord (MSG_BROADCAST, other.origin_z); + WriteByte (MSG_BROADCAST, 230); + WriteByte (MSG_BROADCAST, 5); + + shield_spawn ( self, v_forward ); + } + + if (doDamage) + T_Damage (other, self.shield_entity, self.shield_entity, 15); + +// self.shield_death_time = time + 0.5; + } + } +}; diff --git a/quakec_rogue/progs.src b/quakec_rogue/progs.src new file mode 100644 index 0000000..4cba30b --- /dev/null +++ b/quakec_rogue/progs.src @@ -0,0 +1,68 @@ +progs.dat + +defs.qc +teamplay.qc // ZOID - teamplay stuff +runes.qc +motd.qc +subs.qc +fight.qc +ai.qc +combat.qc +items.qc +grapple.qc // ZOID - grappling hook +weapons.qc +world.qc +client.qc +player.qc +monsters.qc +doors.qc +buttons.qc +triggers.qc +plats.qc +misc.qc + +ogre.qc +demon.qc +shambler.qc +knight.qc +soldier.qc +wizard.qc +dog.qc +zombie.qc +boss.qc + +tarbaby.qc // registered +hknight.qc // registered +fish.qc // registered +shalrath.qc // registered +enforcer.qc // registered +oldone.qc // registered + +new_ai.qc +eel.qc +invis_sw.qc +newitems.qc +newplats.qc +newmisc.qc +elevatr.qc +pendulum.qc +lightnin.qc +wrath.qc +shield.qc +lava_wpn.qc +mult_wpn.qc +dragon.qc +s_wrath.qc +random.qc +earthq.qc +lavaman.qc +sphere.qc +buzzsaw.qc +dmatch.qc +morph.qc + +ending.qc +mummy.qc +timemach.qc + +bots/bot.qc // bot support diff --git a/quakec_rogue/random.qc b/quakec_rogue/random.qc new file mode 100644 index 0000000..ed3f855 --- /dev/null +++ b/quakec_rogue/random.qc @@ -0,0 +1,142 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Random.qc + +// ======================================================= +// Random Items! +// ======================================================= + +// equal divisions of 1: +// 5: 0.2, 0.4, 0.6, 0.8, 1 +// 6: 0.16, 0.33, 0.48, 0.66, 0.83, 1 +// 7: 0.14, 0.28, 0.43, 0.56, 0.71, 0.86, 1 +// 8: 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1 +void() random_pick_type = +{ + local float randItem; + + randItem = random(); + + self.touch = powerup_touch; + setsize (self, '-16 -16 -24', '16 16 32'); + + if(randItem < 0.2) + { + self.touch = newitems_touch; + self.noise = "shield/pickup.wav"; + setmodel (self, "progs/shield.mdl"); + self.netname = "$qc_power_shield"; + self.items = 0; + self.items2 = IT2_SHIELD; +// if(norandom == 1) +// self.classname = "item_powerup_shield"; + } + else if (randItem < 0.4) + { + self.touch = newitems_touch; + self.noise = "belt/pickup.wav"; + setmodel (self, "progs/beltup.mdl"); + self.netname = "$qc_anti_grav_belt"; + self.items = 0; + self.items2 = IT2_ANTIGRAV; +// if(norandom == 1) +// self.classname = "item_powerup_belt"; + } + else if (randItem < 0.6) + { + self.noise = "items/protect.wav"; + setmodel (self, "progs/invulner.mdl"); + self.netname = "$qc_pentagram_of_protection"; + self.items = IT_INVULNERABILITY; + self.items2 = 0; +// if(norandom == 1) +// self.classname = "item_artifact_invulnerability"; + } + else if (randItem < 0.8) + { + self.noise = "items/inv1.wav"; + setmodel (self, "progs/invisibl.mdl"); + self.netname = "$qc_ring_of_shadows"; + self.items = IT_INVISIBILITY; + self.items2 = 0; +// if(norandom == 1) +// self.classname = "item_artifact_invisibility"; + } + else + { + self.noise = "items/damage.wav"; + setmodel (self, "progs/quaddama.mdl"); + self.netname = "$qc_quad_damage"; + self.items = IT_QUAD; + self.items2 = 0; +// if(norandom == 1) +// self.classname = "item_artifact_super_damage"; + } +}; + +/*QUAKED item_random_powerup (0 .5 .8) (-16 -16 -24) (16 16 32) +The Random Box! + +Contains a random powerup. +*/ +void() item_random_powerup = +{ + if (!deathmatch) + { + remove(self); + return; + } + + // Precache the lot of it.... + precache_model ("progs/shield.mdl"); + precache_model ("progs/p_shield.mdl"); + precache_model ("progs/beltup.mdl"); + precache_model ("progs/invulner.mdl"); + precache_model ("progs/invisibl.mdl"); + precache_model ("progs/quaddama.mdl"); + + precache_sound ("items/inv1.wav"); + precache_sound ("items/inv2.wav"); + precache_sound ("items/inv3.wav"); + precache_sound ("items/protect.wav"); + precache_sound ("items/protect2.wav"); + precache_sound ("items/protect3.wav"); + precache_sound ("items/damage.wav"); + precache_sound ("items/damage2.wav"); + precache_sound ("items/damage3.wav"); + + + precache_sound ("belt/pickup.wav"); + precache_sound ("belt/use.wav"); + precache_sound ("belt/fadeout.wav"); + precache_sound ("shield/pickup.wav"); + precache_sound ("shield/hit.wav"); + precache_sound ("shield/fadeout.wav"); + + random_pick_type(); + StartItem (); +}; + +void() random_regen = +{ + random_pick_type(); + SUB_regen(); +}; + diff --git a/quakec_rogue/runes.qc b/quakec_rogue/runes.qc new file mode 100644 index 0000000..b1a5e4a --- /dev/null +++ b/quakec_rogue/runes.qc @@ -0,0 +1,242 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Rogue Runes +// Jan'97 by ZOID +// Under contract to id software for Rogue Entertainment + +.float rune; // what rune does this guy have? +.float runetime; // last time we told him he's already got a rune + +.float rune1_last_noise_time; +.float rune2_last_noise_time; +.float rune3_last_noise_time; +.float rune4_last_regen_time; + +float runespawn; // have we spawned runes? +entity rune_spawn_spot; // spawn spot for runes + +float ITEM_RUNE1 = 1; // Rune of Earth Magic: Resistance +float ITEM_RUNE2 = 2; // Rune of Black Magic: Strength +float ITEM_RUNE3 = 4; // Rune of Hell Magic: Haste +float ITEM_RUNE4 = 8; // Rune of Edler Magic: Regeneration + +//prototypes + +void() RuneTouch = +{ + if (other.classname != "player" || other.health <= 0) + return; + + if (other.rune) { + if (other.runetime < time) + centerprint(other, "$qc_already_have_rune"); + other.runetime = time + 5; + return; + } + + other.rune = other.rune | self.rune; + + sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); + + if (self.rune & ITEM_RUNE1) + centerprint(other, "$qc_rune_resistance"); + else if (self.rune & ITEM_RUNE2) + centerprint(other, "$qc_rune_strength"); + else if (self.rune & ITEM_RUNE3) + centerprint(other, "$qc_rune_haste"); + else if (self.rune & ITEM_RUNE4) + centerprint(other, "$qc_rune_regeneration"); + remove(self); +}; + +entity() RuneSpawnPoint = +{ + while (1) { + rune_spawn_spot = find(rune_spawn_spot, classname, + "info_player_deathmatch"); + + if (rune_spawn_spot != world) + return rune_spawn_spot; + } +}; + +void() RuneRespawn = +{ + local entity spot; + + spot = RuneSpawnPoint(); + setorigin(self, spot.origin); + + self.velocity_z = 300; + self.velocity_x = -300 + (random() * 600); + self.velocity_y = -300 + (random() * 600); + + self.nextthink = time + 120; +}; + +void(float r, entity spot) SpawnRune = +{ + local entity item; + + if (spot == world) + spot = RuneSpawnPoint(); + + item = spawn(); + + setorigin(item, spot.origin); + item.rune = r; + item.velocity_z = 300; + item.velocity_x = -300 + (random() * 600); + item.velocity_y = -300 + (random() * 600); + + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_TOSS; + + if (r & ITEM_RUNE1) + setmodel (item, "progs/end1.mdl"); + else if (r & ITEM_RUNE2) + setmodel (item, "progs/end2.mdl"); + else if (r & ITEM_RUNE3) + setmodel (item, "progs/end3.mdl"); + else if (r & ITEM_RUNE4) + setmodel (item, "progs/end4.mdl"); + + setsize (item, '-16 -16 0', '16 16 56'); + item.touch = RuneTouch; + + item.nextthink = time + 120; // remove after 2 minutes + item.think = RuneRespawn; +}; + +// self is player +void() DropRune = +{ + if (self.rune & ITEM_RUNE1) + SpawnRune(ITEM_RUNE1, self); + if (self.rune & ITEM_RUNE2) + SpawnRune(ITEM_RUNE2, self); + if (self.rune & ITEM_RUNE3) + SpawnRune(ITEM_RUNE3, self); + if (self.rune & ITEM_RUNE4) + SpawnRune(ITEM_RUNE4, self); + self.rune = 0; +}; + +void() DoSpawnRunes = +{ + remove(self); + + SpawnRune(ITEM_RUNE1, world); + SpawnRune(ITEM_RUNE2, world); + SpawnRune(ITEM_RUNE3, world); + SpawnRune(ITEM_RUNE4, world); +}; + +void() SpawnRunes = +{ + local entity spawner; + + if (!deathmatch || !(cvar("gamecfg") & GAMECFG_ENABLE_RUNES) || runespawn) + return; + runespawn = 1; + spawner = spawn(); + spawner.think = DoSpawnRunes; + spawner.nextthink = time + 0.1; +}; + +float(float damage, entity who) RuneApplyEarth = +{ + if (who.rune & ITEM_RUNE1) { + if (who.rune1_last_noise_time < time) { + sound (who, CHAN_ITEM, "runes/end1.wav", 1, ATTN_NORM); + // tune as needed + who.rune1_last_noise_time = time + 1.0; + } + return damage/2; + } + return damage; +}; + +void(entity who) RuneApplyBlackNoise = +{ + if (who.rune & ITEM_RUNE2) + if (who.rune2_last_noise_time < time) { + sound (who, CHAN_ITEM, "runes/end2.wav", 1, ATTN_NORM); + // tune as needed + who.rune2_last_noise_time = time + 1.0; + } +}; + +float(float damage, entity who) RuneApplyBlack = +{ + if (who.rune & ITEM_RUNE2) + return damage*2; + return damage; +}; + +float(float tvalue, entity who) RuneApplyHell = +{ + if (who.rune & ITEM_RUNE3) { + if (who.rune3_last_noise_time < time) { + sound (who, CHAN_ITEM, "runes/end3.wav", 1, ATTN_NORM); + // tune as needed + who.rune3_last_noise_time = time + 1.0; + } + return (tvalue * 2) / 3; + } + return tvalue; +}; + +void(entity who) RuneApplyElder = +{ + if (who.rune & ITEM_RUNE4) { + if (who.rune4_last_regen_time < time && who.health < 100) { + sound (who, CHAN_ITEM, "runes/end4.wav", 1, ATTN_NORM); + who.health = who.health + 5; + if (who.health > 100) + who.health = 100; + who.rune4_last_regen_time = time + 1.0; + } + } +}; + +float(entity who) RuneHasElder = +{ + if (who.rune & ITEM_RUNE4) + return TRUE; + return FALSE; +}; + +void() RunePrecache = +{ + if (cvar("deathmatch")) + if (cvar("gamecfg") & GAMECFG_ENABLE_RUNES) { + precache_model("progs/end1.mdl"); + precache_model("progs/end2.mdl"); + precache_model("progs/end3.mdl"); + precache_model("progs/end4.mdl"); + + precache_sound("runes/end1.wav"); + precache_sound("runes/end2.wav"); + precache_sound("runes/end3.wav"); + precache_sound("runes/end4.wav"); + } +}; diff --git a/quakec_rogue/s_wrath.qc b/quakec_rogue/s_wrath.qc new file mode 100644 index 0000000..84647ab --- /dev/null +++ b/quakec_rogue/s_wrath.qc @@ -0,0 +1,580 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// overlord.qc + +$skin s_wrtrst +$frame s_wrtfrm + +// walk +$frame s_wtwk01 s_wtwk02 s_wtwk03 s_wtwk04 s_wtwk05 +$frame s_wtwk06 s_wtwk07 s_wtwk08 s_wtwk09 s_wtwk10 +$frame s_wtwk11 s_wtwk12 s_wtwk13 s_wtwk14 s_wtwk15 + +// hand to hand attacks +$frame s_wtaa01 s_wtaa02 s_wtaa03 s_wtaa04 s_wtaa05 +$frame s_wtaa06 s_wtaa07 s_wtaa08 s_wtaa09 s_wtaa10 + +$frame s_wtab01 s_wtab02 s_wtab03 s_wtab04 s_wtab05 +$frame s_wtab06 s_wtab07 s_wtab08 s_wtab09 s_wtab10 +$frame s_wtab11 s_wtab12 s_wtab13 s_wtab14 + +$frame s_wtac01 s_wtac02 s_wtac03 s_wtac04 s_wtac05 +$frame s_wtac06 s_wtac07 s_wtac08 s_wtac09 s_wtac10 +$frame s_wtac11 s_wtac12 s_wtac13 s_wtac14 + +// ranged attack +$frame s_wtba01 s_wtba02 s_wtba03 s_wtba04 s_wtba05 +$frame s_wtba06 s_wtba07 s_wtba08 s_wtba09 s_wtba10 s_wtba11 s_wtba12 + +// pain +$frame s_wtpa01 s_wtpa02 s_wtpa03 s_wtpa04 s_wtpa05 +$frame s_wtpa06 s_wtpa07 s_wtpa08 s_wtpa09 s_wtpa10 +$frame s_wtpa11 s_wtpa12 s_wtpa13 s_wtpa14 + +$frame s_wtpb01 s_wtpb02 s_wtpb03 s_wtpb04 s_wtpb05 +$frame s_wtpb06 s_wtpb07 s_wtpb08 s_wtpb09 s_wtpb10 +$frame s_wtpb11 + +// death +$frame s_wtdt01 s_wtdt02 s_wtdt03 s_wtdt04 s_wtdt05 +$frame s_wtdt06 s_wtdt07 s_wtdt08 s_wtdt09 s_wtdt10 +$frame s_wtdt11 s_wtdt12 s_wtdt13 s_wtdt14 s_wtdt15 +$frame s_wtdt16 s_wtdt17 + +float(entity thePoint) isSpawnPointEmpty = +{ + local entity neighbors; + local entity start; + + neighbors = findradius ( thePoint.origin, 64 ); + if (neighbors == world) + return 1; + + start = neighbors; + while (neighbors) + { + if (neighbors != thePoint) + { + if (neighbors.flags & FL_MONSTER) + { + return 0; + } + else if (neighbors.classname == "player") + { + return 0; + } + else if (neighbors.think) + { + return 0; + } + } + + neighbors = neighbors.chain; + if (neighbors == start) + return 1; + } + return 1; +}; + +entity() overlord_find_dest3 = +{ + local entity furthestDest; + local entity bestDest; + local entity curDest; + local entity thePlayer; + local float theDist; + local float curDist; + local float dot; + local vector curVector; + + furthestDest = world; + bestDest = world; + theDist=0; + thePlayer=find(world, classname, "player"); + curDest=world; + + makevectors (thePlayer.angles); + + while (1) + { + curDest = find ( curDest, classname, "info_overlord_destination"); + if ( curDest == world ) + { + if (bestDest != world) + return bestDest; + + return furthestDest; + } + + if (isSpawnPointEmpty(curDest)) + { + curVector = curDest.origin - thePlayer.origin; + curDist = vlen ( curVector ); + curVector = normalize (curVector); + dot = curVector * v_forward; + if ( dot > 0.6 ) + { + if (curDist > 150) + { + bestDest = curDest; + } + } + + if (curDist > theDist ) + { + furthestDest = curDest; + theDist = curDist; + } + } + } + + // just in case, return the world. + return world; +}; + +// ======================================================= + +entity() overlord_find_dest = +{ + local entity curDest, lastDest; + local float loopCount; + + curDest = find ( world, classname, "info_overlord_destination"); + if (curDest != world) + { + lastDest = curDest; + loopCount = random()*5; + while (loopCount > 1) + { + curDest = nextent ( curDest ); + curDest = find ( curDest, classname, "info_overlord_destination"); + if (curDest != world) + { + if (isSpawnPointEmpty(curDest)) + { + loopCount = loopCount - 1; + lastDest = curDest; + } + else + curDest = world; + } + else + { + return lastDest; + } + } + return curDest; + } + return curDest; +}; + + +void() overlord_teleport = +{ + local float chance; + local entity theDest; + local vector org; + + if (!(self.spawnflags & 2)) + return; + + chance = random(); + if (chance > 0.75) + return; + +// theDest = overlord_find_dest(); + theDest = overlord_find_dest3(); + if (theDest == world) + return; + +// put a tfog where the player was + spawn_tfog (self.origin); + +// spawn a tfog flash in front of the destination + makevectors (self.angles); + org = theDest.origin + 32 * v_forward; + + spawn_tfog (org); + spawn_tdeath(theDest.origin, self); + + setorigin (self, theDest.origin); + self.flags = self.flags - self.flags & FL_ONGROUND; +}; +// ======================================================= + +void() overlord_stand1 =[ $s_wtwk01, overlord_stand1 ] { ai_stand();}; + +void() overlord_walk01 =[ $s_wtwk01, overlord_walk02 ] { ai_walk(8); }; +void() overlord_walk02 =[ $s_wtwk02, overlord_walk03 ] { ai_walk(8); }; +void() overlord_walk03 =[ $s_wtwk03, overlord_walk04 ] { ai_walk(8); }; +void() overlord_walk04 =[ $s_wtwk04, overlord_walk05 ] { ai_walk(8); }; +void() overlord_walk05 =[ $s_wtwk05, overlord_walk06 ] { ai_walk(8); }; +void() overlord_walk06 =[ $s_wtwk06, overlord_walk07 ] { ai_walk(8); }; +void() overlord_walk07 =[ $s_wtwk07, overlord_walk08 ] { ai_walk(8); }; +void() overlord_walk08 =[ $s_wtwk08, overlord_walk09 ] { ai_walk(8); }; +void() overlord_walk09 =[ $s_wtwk09, overlord_walk10 ] { ai_walk(8); }; +void() overlord_walk10 =[ $s_wtwk10, overlord_walk11 ] { ai_walk(8); }; +void() overlord_walk11 =[ $s_wtwk11, overlord_walk12 ] { ai_walk(8); }; +void() overlord_walk12 =[ $s_wtwk12, overlord_walk13 ] { ai_walk(8); }; +void() overlord_walk13 =[ $s_wtwk13, overlord_walk14 ] { ai_walk(8); }; +void() overlord_walk14 =[ $s_wtwk14, overlord_walk15 ] { ai_walk(8); }; +void() overlord_walk15 =[ $s_wtwk15, overlord_walk01 ] { ai_walk(8); }; + +void() overlord_run01 =[ $s_wtwk01, overlord_run02 ] { ai_run(12); }; +void() overlord_run02 =[ $s_wtwk02, overlord_run03 ] { ai_run(12); }; +void() overlord_run03 =[ $s_wtwk03, overlord_run04 ] { ai_run(12); }; +void() overlord_run04 =[ $s_wtwk04, overlord_run05 ] { ai_run(12); }; +void() overlord_run05 =[ $s_wtwk05, overlord_run06 ] { ai_run(12); }; +void() overlord_run06 =[ $s_wtwk06, overlord_run07 ] { ai_run(12); }; +void() overlord_run07 =[ $s_wtwk07, overlord_run08 ] { ai_run(12); }; +void() overlord_run08 =[ $s_wtwk08, overlord_run09 ] { ai_run(12); }; +void() overlord_run09 =[ $s_wtwk09, overlord_run10 ] { ai_run(12); }; +void() overlord_run10 =[ $s_wtwk10, overlord_run11 ] { ai_run(12); }; +void() overlord_run11 =[ $s_wtwk11, overlord_run12 ] { ai_run(12); }; +void() overlord_run12 =[ $s_wtwk12, overlord_run13 ] { ai_run(12); }; +void() overlord_run13 =[ $s_wtwk13, overlord_run14 ] { ai_run(12); }; +void() overlord_run14 =[ $s_wtwk14, overlord_run15 ] { ai_run(12); }; +void() overlord_run15 =[ $s_wtwk15, overlord_run01 ] { ai_run(12); }; + +// ================================================= +// overlord hand-to-hand attack +// ================================================= +void() overlord_smash; + +void() overlord_at_a01 =[ $s_wtaa01, overlord_at_a02 ] { ai_charge(12);}; +void() overlord_at_a02 =[ $s_wtaa02, overlord_at_a03 ] { ai_charge(12);}; +void() overlord_at_a03 =[ $s_wtaa03, overlord_at_a04 ] { ai_charge(12);}; +void() overlord_at_a04 =[ $s_wtaa04, overlord_at_a05 ] { ai_charge(12);}; +void() overlord_at_a05 =[ $s_wtaa05, overlord_at_a06 ] { ai_charge(12);}; +void() overlord_at_a06 =[ $s_wtaa06, overlord_at_a07 ] { ai_charge(12);}; +void() overlord_at_a07 =[ $s_wtaa07, overlord_at_a08 ] {overlord_smash();}; +void() overlord_at_a08 =[ $s_wtaa08, overlord_at_a09 ] { ai_charge(12);}; +void() overlord_at_a09 =[ $s_wtaa09, overlord_at_a10 ] { ai_charge(12);}; +void() overlord_at_a10 =[ $s_wtaa10, overlord_run01 ] { ai_charge(12);}; + +void() overlord_at_b01 =[ $s_wtab01, overlord_at_b02 ] { ai_charge(12);}; +void() overlord_at_b02 =[ $s_wtab02, overlord_at_b03 ] { ai_charge(12);}; +void() overlord_at_b03 =[ $s_wtab03, overlord_at_b04 ] { ai_charge(12);}; +void() overlord_at_b04 =[ $s_wtab04, overlord_at_b05 ] { ai_charge(12);}; +void() overlord_at_b05 =[ $s_wtab05, overlord_at_b06 ] { ai_charge(12);}; +void() overlord_at_b06 =[ $s_wtab06, overlord_at_b07 ] { ai_charge(12);}; +void() overlord_at_b07 =[ $s_wtab07, overlord_at_b08 ] { ai_charge(12);}; +void() overlord_at_b08 =[ $s_wtab08, overlord_at_b09 ] {overlord_smash();}; +void() overlord_at_b09 =[ $s_wtab09, overlord_at_b10 ] { ai_charge(12);}; +void() overlord_at_b10 =[ $s_wtab10, overlord_at_b11 ] { ai_charge(12);}; +void() overlord_at_b11 =[ $s_wtab11, overlord_at_b12 ] { ai_charge(12);}; +void() overlord_at_b12 =[ $s_wtab12, overlord_at_b13 ] { ai_charge(12);}; +void() overlord_at_b13 =[ $s_wtab13, overlord_at_b14 ] { ai_charge(12);}; +void() overlord_at_b14 =[ $s_wtab14, overlord_run01 ] { ai_charge(12);}; + +void() overlord_at_c01 =[ $s_wtac01, overlord_at_c02 ] { ai_charge(12);}; +void() overlord_at_c02 =[ $s_wtac02, overlord_at_c03 ] { ai_charge(12);}; +void() overlord_at_c03 =[ $s_wtac03, overlord_at_c04 ] { ai_charge(12);}; +void() overlord_at_c04 =[ $s_wtac04, overlord_at_c05 ] { ai_charge(12);}; +void() overlord_at_c05 =[ $s_wtac05, overlord_at_c06 ] { ai_charge(12);}; +void() overlord_at_c06 =[ $s_wtac06, overlord_at_c07 ] {overlord_smash();}; +void() overlord_at_c07 =[ $s_wtac07, overlord_at_c08 ] { ai_charge(12);}; +void() overlord_at_c08 =[ $s_wtac08, overlord_at_c09 ] { ai_charge(12);}; +void() overlord_at_c09 =[ $s_wtac09, overlord_at_c10 ] { ai_charge(12);}; +void() overlord_at_c10 =[ $s_wtac10, overlord_at_c11 ] { ai_charge(12);}; +void() overlord_at_c11 =[ $s_wtac11, overlord_at_c12 ] {overlord_smash();}; +void() overlord_at_c12 =[ $s_wtac12, overlord_at_c13 ] { ai_charge(12);}; +void() overlord_at_c13 =[ $s_wtac13, overlord_at_c14 ] { ai_charge(12);}; +void() overlord_at_c14 =[ $s_wtac14, overlord_run01 ] { ai_charge(12);}; + +void() overlord_smash = +{ + + local vector delta; + local float smashDmg; + + if (!self.enemy) + return; + if (!CanDamage (self.enemy, self)) + return; + + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + smashDmg = 20 + random () * 10; + + sound (self, CHAN_WEAPON, "s_wrath/smash.wav", 1, ATTN_NORM); + T_Damage (self.enemy, self, self, smashDmg); + delta = normalize (self.enemy.origin - self.origin + self.enemy.view_ofs); + SpawnBlood ( self.enemy.origin - delta * 30, delta * -100, smashDmg); +}; + +void() overlord_melee = +{ + local float r; + + r = random(); + + if (r < 0.33) + { + overlord_at_a01 (); + } + else if (r < 0.66) + { + overlord_at_b01 (); + } + else + { + overlord_at_c01 (); + } +}; + +// ================================================== +// missile attacks +// ================================================== +void(float AttackNumber) WrathMissile; + +void() overlord_msl_a01 =[ $s_wtba01, overlord_msl_a02 ] { ai_charge(12);}; +void() overlord_msl_a02 =[ $s_wtba02, overlord_msl_a03 ] { ai_charge(12);}; +void() overlord_msl_a03 =[ $s_wtba03, overlord_msl_a04 ] { ai_charge(12);}; +void() overlord_msl_a04 =[ $s_wtba04, overlord_msl_a05 ] { ai_charge(12);}; +void() overlord_msl_a05 =[ $s_wtba05, overlord_msl_a06 ] { ai_charge(12);}; +void() overlord_msl_a06 =[ $s_wtba06, overlord_msl_a07 ] {WrathMissile(4);}; +void() overlord_msl_a07 =[ $s_wtba07, overlord_msl_a08 ] { ai_charge(12);}; +void() overlord_msl_a08 =[ $s_wtba08, overlord_msl_a09 ] { ai_charge(12);}; +void() overlord_msl_a09 =[ $s_wtba09, overlord_msl_a10 ] { ai_charge(12);}; +void() overlord_msl_a10 =[ $s_wtba10, overlord_msl_a11 ] { ai_charge(12);}; +void() overlord_msl_a11 =[ $s_wtba11, overlord_msl_a12 ] { ai_charge(12);}; +void() overlord_msl_a12 =[ $s_wtba12, overlord_run01 ] {overlord_teleport();}; + +void() overlord_missile = +{ + local float r; + r = random(); + + overlord_msl_a01(); +}; + +// ================================================== +// pain +// ================================================== +void() overlord_pn_a01 =[ $s_wtpa01, overlord_pn_a02 ] {}; +void() overlord_pn_a02 =[ $s_wtpa02, overlord_pn_a03 ] {}; +void() overlord_pn_a03 =[ $s_wtpa03, overlord_pn_a04 ] {overlord_teleport();}; +void() overlord_pn_a04 =[ $s_wtpa04, overlord_pn_a05 ] {}; +void() overlord_pn_a05 =[ $s_wtpa05, overlord_pn_a06 ] {}; +void() overlord_pn_a06 =[ $s_wtpa06, overlord_pn_a07 ] {}; +void() overlord_pn_a07 =[ $s_wtpa07, overlord_run01 ] {}; + +void() overlord_pn_b01 =[ $s_wtpb01, overlord_pn_b02 ] {}; +void() overlord_pn_b02 =[ $s_wtpb02, overlord_pn_b03 ] {}; +void() overlord_pn_b03 =[ $s_wtpb03, overlord_pn_b04 ] {overlord_teleport();}; +void() overlord_pn_b04 =[ $s_wtpb04, overlord_pn_b05 ] {}; +void() overlord_pn_b05 =[ $s_wtpb05, overlord_pn_b06 ] {}; +void() overlord_pn_b06 =[ $s_wtpb06, overlord_pn_b07 ] {}; +void() overlord_pn_b07 =[ $s_wtpb07, overlord_run01 ] {}; + + +void() overlord_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (r > 0.2) + return; + + if (r < 0.15) + overlord_pn_a01 (); + else + overlord_pn_b01 (); + + self.pain_finished = time + 2; + sound (self, CHAN_VOICE, "wrath/wpain.wav", 1, ATTN_NORM); +}; + +void(string gibName) ol_toss = +{ + local vector flingVelocity; + local entity myGib; + + makevectors(self.angles); + flingVelocity = v_forward*250 + v_up*300; + flingVelocity = flingVelocity + v_up * (random() * 100 - 50); + flingVelocity = flingVelocity + v_right * (random() * 200 - 100); + + myGib = StartGib (gibName); + myGib.velocity = flingVelocity; +}; + +// note - death frame 9 is intentionally done twice. +void() overlord_die01 =[ $s_wtdt01, overlord_die02 ] {self.nextthink = time +0.05;}; +void() overlord_die02 =[ $s_wtdt02, overlord_die03 ] + { self.flags = self.flags | FL_FLY; self.nextthink = time +0.05;}; +void() overlord_die03 =[ $s_wtdt03, overlord_die04 ] {self.nextthink = time +0.05;}; +void() overlord_die04 =[ $s_wtdt04, overlord_die05 ] {self.nextthink = time +0.05;}; +void() overlord_die05 =[ $s_wtdt05, overlord_die06 ] {self.nextthink = time +0.05;}; +void() overlord_die06 =[ $s_wtdt06, overlord_die07 ] {self.nextthink = time +0.05;}; +void() overlord_die07 =[ $s_wtdt07, overlord_die08 ] {self.nextthink = time +0.05;}; +void() overlord_die08 =[ $s_wtdt08, overlord_die09 ] {self.nextthink = time +0.05;}; +void() overlord_die09 =[ $s_wtdt09, overlord_die10 ] {self.nextthink = time +0.05;}; +void() overlord_die10 =[ $s_wtdt09, overlord_die11 ] {self.nextthink = time +0.05;}; +void() overlord_die11 =[ $s_wtdt10, overlord_die12 ] {self.nextthink = time +0.05;}; +void() overlord_die12 =[ $s_wtdt11, overlord_die13 ] {self.nextthink = time +0.05;}; +void() overlord_die13 =[ $s_wtdt12, overlord_die14 ] {self.nextthink = time +0.05;}; +void() overlord_die14 =[ $s_wtdt13, overlord_die15 ] {self.nextthink = time +0.05;}; +void() overlord_die15 =[ $s_wtdt14, overlord_die16 ] {self.nextthink = time +0.05;}; +void() overlord_die16 =[ $s_wtdt15, overlord_die17 ] {self.nextthink = time +0.05;}; +void() overlord_die17 =[ $s_wtdt16, overlord_die18 ] +{ + self.model = ""; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 0); + WriteByte (MSG_BROADCAST, 4); + + ol_toss("progs/s_wrtgb2.mdl"); + ol_toss("progs/s_wrtgb3.mdl"); + ol_toss("progs/wrthgib1.mdl"); + ol_toss("progs/wrthgib2.mdl"); + ol_toss("progs/wrthgib3.mdl"); + self.nextthink = time + 0.1; +}; +void() overlord_die18 =[ $s_wtdt17, overlord_die19 ] +{ + local float counter; + + counter = 2; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 0); + WriteByte (MSG_BROADCAST, 4); + + while(counter>0) + { + ol_toss("progs/gib1.mdl"); + ol_toss("progs/gib2.mdl"); + ol_toss("progs/gib3.mdl"); + counter = counter - 1; + } + self.nextthink = time + 0.1; +}; +void() overlord_die19 = [ $s_wtdt17, overlord_die19 ] +{ + local float counter; + + counter = 2; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 0); + WriteByte (MSG_BROADCAST, 4); + + while(counter>0) + { + ol_toss("progs/gib1.mdl"); + ol_toss("progs/gib2.mdl"); + ol_toss("progs/gib3.mdl"); + counter = counter - 1; + } + + remove(self); +}; + +/*QUAKED monster_super_wrath (1 0 0) (-16 -16 -24) (16 16 32) Ambush BossMode +The super wrath (Overlord). + +BossMode enables the superwrath's powershield and the random teleporting. +*/ +void() monster_super_wrath = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/s_wrath.mdl"); +// + precache_model ("progs/s_wrtgb1.mdl"); + + precache_model ("progs/s_wrtgb2.mdl"); + + precache_model ("progs/s_wrtgb3.mdl"); + precache_model ("progs/wrthgib1.mdl"); + precache_model ("progs/wrthgib2.mdl"); + precache_model ("progs/wrthgib3.mdl"); + + precache_model ("progs/w_ball.mdl"); + precache_sound ("wrath/wsee.wav"); + precache_sound ("wrath/watt.wav"); + precache_sound ("wrath/wpain.wav"); + precache_sound ("wrath/wdthc.wav"); + precache_sound ("s_wrath/smash.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/s_wrath.mdl"); + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + + self.health = 1000; + self.pain_finished = 0; + +// if(self.spawnflags & 2) +// self.items2 = IT2_SHIELD; + self.th_stand = overlord_stand1; + self.th_walk = overlord_walk01; + self.th_run = overlord_run01; + self.th_missile = overlord_missile; + self.th_melee = overlord_melee; + self.th_pain = overlord_pain; + self.th_die = overlord_die02; + + flymonster_start (); +}; + +/*QUAKED info_overlord_destination (.5 .5 .5) (-8 -8 -8) (8 8 32) +This is the destination marker for the overlord. + +It does not require any other fields filled in. +*/ +void() info_overlord_destination = +{ +// this does nothing, just serves as a target spot + self.mangle = self.angles; + self.angles = '0 0 0'; + self.model = ""; + self.origin = self.origin + '0 0 27'; +}; + diff --git a/quakec_rogue/shalrath.qc b/quakec_rogue/shalrath.qc new file mode 100644 index 0000000..1348d08 --- /dev/null +++ b/quakec_rogue/shalrath.qc @@ -0,0 +1,255 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAL-RATH + +============================================================================== +*/ +$cd /raid/quake/id1/models/shalrath +$origin 0 0 24 +$base base +$skin skin +$scale 0.7 + +$frame attack1 attack2 attack3 attack4 attack5 attack6 attack7 attack8 +$frame attack9 attack10 attack11 + +$frame pain1 pain2 pain3 pain4 pain5 + +$frame death1 death2 death3 death4 death5 death6 death7 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 + +void() shalrath_pain; +void() ShalMissile; +void() shal_stand =[ $walk1, shal_stand ] {ai_stand();}; + +void() shal_walk1 =[ $walk2, shal_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); +ai_walk(6);}; +void() shal_walk2 =[ $walk3, shal_walk3 ] {ai_walk(4);}; +void() shal_walk3 =[ $walk4, shal_walk4 ] {ai_walk(0);}; +void() shal_walk4 =[ $walk5, shal_walk5 ] {ai_walk(0);}; +void() shal_walk5 =[ $walk6, shal_walk6 ] {ai_walk(0);}; +void() shal_walk6 =[ $walk7, shal_walk7 ] {ai_walk(0);}; +void() shal_walk7 =[ $walk8, shal_walk8 ] {ai_walk(5);}; +void() shal_walk8 =[ $walk9, shal_walk9 ] {ai_walk(6);}; +void() shal_walk9 =[ $walk10, shal_walk10 ] {ai_walk(5);}; +void() shal_walk10 =[ $walk11, shal_walk11 ] {ai_walk(0);}; +void() shal_walk11 =[ $walk12, shal_walk12 ] {ai_walk(4);}; +void() shal_walk12 =[ $walk1, shal_walk1 ] {ai_walk(5);}; + +void() shal_run1 =[ $walk2, shal_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "shalrath/idle.wav", 1, ATTN_IDLE); +ai_run(6);}; +void() shal_run2 =[ $walk3, shal_run3 ] {ai_run(4);}; +void() shal_run3 =[ $walk4, shal_run4 ] {ai_run(0);}; +void() shal_run4 =[ $walk5, shal_run5 ] {ai_run(0);}; +void() shal_run5 =[ $walk6, shal_run6 ] {ai_run(0);}; +void() shal_run6 =[ $walk7, shal_run7 ] {ai_run(0);}; +void() shal_run7 =[ $walk8, shal_run8 ] {ai_run(5);}; +void() shal_run8 =[ $walk9, shal_run9 ] {ai_run(6);}; +void() shal_run9 =[ $walk10, shal_run10 ] {ai_run(5);}; +void() shal_run10 =[ $walk11, shal_run11 ] {ai_run(0);}; +void() shal_run11 =[ $walk12, shal_run12 ] {ai_run(4);}; +void() shal_run12 =[ $walk1, shal_run1 ] {ai_run(5);}; + +void() shal_attack1 =[ $attack1, shal_attack2 ] { +sound (self, CHAN_VOICE, "shalrath/attack.wav", 1, ATTN_NORM); +ai_face(); +}; +void() shal_attack2 =[ $attack2, shal_attack3 ] {ai_face();}; +void() shal_attack3 =[ $attack3, shal_attack4 ] {ai_face();}; +void() shal_attack4 =[ $attack4, shal_attack5 ] {ai_face();}; +void() shal_attack5 =[ $attack5, shal_attack6 ] {ai_face();}; +void() shal_attack6 =[ $attack6, shal_attack7 ] {ai_face();}; +void() shal_attack7 =[ $attack7, shal_attack8 ] {ai_face();}; +void() shal_attack8 =[ $attack8, shal_attack9 ] {ai_face();}; +void() shal_attack9 =[ $attack9, shal_attack10 ] {ShalMissile();}; +void() shal_attack10 =[ $attack10, shal_attack11 ] {ai_face();}; +void() shal_attack11 =[ $attack11, shal_run1 ] {}; + +void() shal_pain1 =[ $pain1, shal_pain2 ] {}; +void() shal_pain2 =[ $pain2, shal_pain3 ] {}; +void() shal_pain3 =[ $pain3, shal_pain4 ] {}; +void() shal_pain4 =[ $pain4, shal_pain5 ] {}; +void() shal_pain5 =[ $pain5, shal_run1 ] {}; + +void() shal_death1 =[ $death1, shal_death2 ] {}; +void() shal_death2 =[ $death2, shal_death3 ] {}; +void() shal_death3 =[ $death3, shal_death4 ] {}; +void() shal_death4 =[ $death4, shal_death5 ] {}; +void() shal_death5 =[ $death5, shal_death6 ] {}; +void() shal_death6 =[ $death6, shal_death7 ] {}; +void() shal_death7 =[ $death7, shal_death7 ] {}; + + +void() shalrath_pain = +{ + if (self.pain_finished > time) + return; + + sound (self, CHAN_VOICE, "shalrath/pain.wav", 1, ATTN_NORM); + shal_pain1(); + self.pain_finished = time + 3; +}; + +void() shalrath_die = +{ +// check for gib + if (self.health < -90) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shal.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + + sound (self, CHAN_VOICE, "shalrath/death.wav", 1, ATTN_NORM); + shal_death1(); + self.solid = SOLID_NOT; + // insert death sounds here +}; + +/* +================ +ShalMissile +================ +*/ +void() ShalMissileTouch; +void() ShalHome; +void() ShalMissile = +{ + local entity missile; + local vector dir; + local float dist, flytime; + + dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + dist = vlen (self.enemy.origin - self.origin); + flytime = dist * 0.002; + if (flytime < 0.1) + flytime = 0.1; + + self.effects = self.effects | EF_MUZZLEFLASH; + sound (self, CHAN_WEAPON, "shalrath/attack2.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + setmodel (missile, "progs/v_spike.mdl"); + + setsize (missile, '0 0 0', '0 0 0'); + + missile.origin = self.origin + '0 0 10'; + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.nextthink = flytime + time; + missile.think = ShalHome; + missile.enemy = self.enemy; + missile.touch = ShalMissileTouch; +}; + +void() ShalHome = +{ + local vector dir, vtemp; + vtemp = self.enemy.origin + '0 0 10'; + if (self.enemy.health < 1) + { + remove(self); + return; + } + dir = normalize(vtemp - self.origin); + self.velocity = dir * 250; + self.nextthink = time + 0.2; + self.think = ShalHome; +}; + +void() ShalMissileTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (other.classname == "monster_zombie") + T_Damage (other, self, self, 110); + T_RadiusDamage (self, self.owner, 40, world); + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +//================================================================= + +/*QUAKED monster_shalrath (1 0 0) (-32 -32 -24) (32 32 48) Ambush +*/ +void() monster_shalrath = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/shalrath.mdl"); + precache_model2 ("progs/h_shal.mdl"); + precache_model2 ("progs/v_spike.mdl"); + + precache_sound2 ("shalrath/attack.wav"); + precache_sound2 ("shalrath/attack2.wav"); + precache_sound2 ("shalrath/death.wav"); + precache_sound2 ("shalrath/idle.wav"); + precache_sound2 ("shalrath/pain.wav"); + precache_sound2 ("shalrath/sight.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/shalrath.mdl"); + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 400; + + self.th_stand = shal_stand; + self.th_walk = shal_walk1; + self.th_run = shal_run1; + self.th_die = shalrath_die; + self.th_pain = shalrath_pain; + self.th_missile = shal_attack1; + + self.think = walkmonster_start; + self.nextthink = time + 0.1 + random ()*0.1; + +}; diff --git a/quakec_rogue/shambler.qc b/quakec_rogue/shambler.qc new file mode 100644 index 0000000..383f8bd --- /dev/null +++ b/quakec_rogue/shambler.qc @@ -0,0 +1,403 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SHAMBLER + +============================================================================== +*/ + +$cd /raid/quake/id1/models/shams +$origin 0 0 24 +$base base +$skin base + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 stand9 +$frame stand10 stand11 stand12 stand13 stand14 stand15 stand16 stand17 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 +$frame walk8 walk9 walk10 walk11 walk12 + +$frame run1 run2 run3 run4 run5 run6 + +$frame smash1 smash2 smash3 smash4 smash5 smash6 smash7 +$frame smash8 smash9 smash10 smash11 smash12 + +$frame swingr1 swingr2 swingr3 swingr4 swingr5 +$frame swingr6 swingr7 swingr8 swingr9 + +$frame swingl1 swingl2 swingl3 swingl4 swingl5 +$frame swingl6 swingl7 swingl8 swingl9 + +$frame magic1 magic2 magic3 magic4 magic5 +$frame magic6 magic7 magic8 magic9 magic10 magic11 magic12 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame death1 death2 death3 death4 death5 death6 +$frame death7 death8 death9 death10 death11 + +void() sham_stand1 =[ $stand1, sham_stand2 ] {ai_stand();}; +void() sham_stand2 =[ $stand2, sham_stand3 ] {ai_stand();}; +void() sham_stand3 =[ $stand3, sham_stand4 ] {ai_stand();}; +void() sham_stand4 =[ $stand4, sham_stand5 ] {ai_stand();}; +void() sham_stand5 =[ $stand5, sham_stand6 ] {ai_stand();}; +void() sham_stand6 =[ $stand6, sham_stand7 ] {ai_stand();}; +void() sham_stand7 =[ $stand7, sham_stand8 ] {ai_stand();}; +void() sham_stand8 =[ $stand8, sham_stand9 ] {ai_stand();}; +void() sham_stand9 =[ $stand9, sham_stand10] {ai_stand();}; +void() sham_stand10 =[ $stand10, sham_stand11] {ai_stand();}; +void() sham_stand11 =[ $stand11, sham_stand12] {ai_stand();}; +void() sham_stand12 =[ $stand12, sham_stand13] {ai_stand();}; +void() sham_stand13 =[ $stand13, sham_stand14] {ai_stand();}; +void() sham_stand14 =[ $stand14, sham_stand15] {ai_stand();}; +void() sham_stand15 =[ $stand15, sham_stand16] {ai_stand();}; +void() sham_stand16 =[ $stand16, sham_stand17] {ai_stand();}; +void() sham_stand17 =[ $stand17, sham_stand1 ] {ai_stand();}; + +void() sham_walk1 =[ $walk1, sham_walk2 ] {ai_walk(10);}; +void() sham_walk2 =[ $walk2, sham_walk3 ] {ai_walk(9);}; +void() sham_walk3 =[ $walk3, sham_walk4 ] {ai_walk(9);}; +void() sham_walk4 =[ $walk4, sham_walk5 ] {ai_walk(5);}; +void() sham_walk5 =[ $walk5, sham_walk6 ] {ai_walk(6);}; +void() sham_walk6 =[ $walk6, sham_walk7 ] {ai_walk(12);}; +void() sham_walk7 =[ $walk7, sham_walk8 ] {ai_walk(8);}; +void() sham_walk8 =[ $walk8, sham_walk9 ] {ai_walk(3);}; +void() sham_walk9 =[ $walk9, sham_walk10] {ai_walk(13);}; +void() sham_walk10 =[ $walk10, sham_walk11] {ai_walk(9);}; +void() sham_walk11 =[ $walk11, sham_walk12] {ai_walk(7);}; +void() sham_walk12 =[ $walk12, sham_walk1 ] {ai_walk(7); +if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE);}; + +void() sham_run1 =[ $run1, sham_run2 ] {ai_run(20);}; +void() sham_run2 =[ $run2, sham_run3 ] {ai_run(24);}; +void() sham_run3 =[ $run3, sham_run4 ] {ai_run(20);}; +void() sham_run4 =[ $run4, sham_run5 ] {ai_run(20);}; +void() sham_run5 =[ $run5, sham_run6 ] {ai_run(24);}; +void() sham_run6 =[ $run6, sham_run1 ] {ai_run(20); +if (random() > 0.8) + sound (self, CHAN_VOICE, "shambler/sidle.wav", 1, ATTN_IDLE); +}; + +void() sham_smash1 =[ $smash1, sham_smash2 ] { +sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); +ai_charge(2);}; +void() sham_smash2 =[ $smash2, sham_smash3 ] {ai_charge(6);}; +void() sham_smash3 =[ $smash3, sham_smash4 ] {ai_charge(6);}; +void() sham_smash4 =[ $smash4, sham_smash5 ] {ai_charge(5);}; +void() sham_smash5 =[ $smash5, sham_smash6 ] {ai_charge(4);}; +void() sham_smash6 =[ $smash6, sham_smash7 ] {ai_charge(1);}; +void() sham_smash7 =[ $smash7, sham_smash8 ] {ai_charge(0);}; +void() sham_smash8 =[ $smash8, sham_smash9 ] {ai_charge(0);}; +void() sham_smash9 =[ $smash9, sham_smash10 ] {ai_charge(0);}; +void() sham_smash10 =[ $smash10, sham_smash11 ] { +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + ai_charge(0); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + if (!CanDamage (self.enemy, self)) + return; + + ldmg = (random() + random() + random()) * 40; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); + SpawnMeatSpray (self.origin + v_forward*16, crandom() * 100 * v_right); +}; +void() sham_smash11 =[ $smash11, sham_smash12 ] {ai_charge(5);}; +void() sham_smash12 =[ $smash12, sham_run1 ] {ai_charge(4);}; + +void() sham_swingr1; + +void(float side) ShamClaw = +{ +local vector delta; +local float ldmg; + + if (!self.enemy) + return; + ai_charge(10); + + delta = self.enemy.origin - self.origin; + + if (vlen(delta) > 100) + return; + + ldmg = (random() + random() + random()) * 20; + T_Damage (self.enemy, self, self, ldmg); + sound (self, CHAN_VOICE, "shambler/smack.wav", 1, ATTN_NORM); + + if (side) + { + makevectors (self.angles); + SpawnMeatSpray (self.origin + v_forward*16, side * v_right); + } +}; + +void() sham_swingl1 =[ $swingl1, sham_swingl2 ] { +sound (self, CHAN_VOICE, "shambler/melee2.wav", 1, ATTN_NORM); +ai_charge(5);}; +void() sham_swingl2 =[ $swingl2, sham_swingl3 ] {ai_charge(3);}; +void() sham_swingl3 =[ $swingl3, sham_swingl4 ] {ai_charge(7);}; +void() sham_swingl4 =[ $swingl4, sham_swingl5 ] {ai_charge(3);}; +void() sham_swingl5 =[ $swingl5, sham_swingl6 ] {ai_charge(7);}; +void() sham_swingl6 =[ $swingl6, sham_swingl7 ] {ai_charge(9);}; +void() sham_swingl7 =[ $swingl7, sham_swingl8 ] {ai_charge(5); ShamClaw(250);}; +void() sham_swingl8 =[ $swingl8, sham_swingl9 ] {ai_charge(4);}; +void() sham_swingl9 =[ $swingl9, sham_run1 ] { +ai_charge(8); +if (random()<0.5) + self.think = sham_swingr1; +}; + +void() sham_swingr1 =[ $swingr1, sham_swingr2 ] { +sound (self, CHAN_VOICE, "shambler/melee1.wav", 1, ATTN_NORM); +ai_charge(1);}; +void() sham_swingr2 =[ $swingr2, sham_swingr3 ] {ai_charge(8);}; +void() sham_swingr3 =[ $swingr3, sham_swingr4 ] {ai_charge(14);}; +void() sham_swingr4 =[ $swingr4, sham_swingr5 ] {ai_charge(7);}; +void() sham_swingr5 =[ $swingr5, sham_swingr6 ] {ai_charge(3);}; +void() sham_swingr6 =[ $swingr6, sham_swingr7 ] {ai_charge(6);}; +void() sham_swingr7 =[ $swingr7, sham_swingr8 ] {ai_charge(6); ShamClaw(-250);}; +void() sham_swingr8 =[ $swingr8, sham_swingr9 ] {ai_charge(3);}; +void() sham_swingr9 =[ $swingr9, sham_run1 ] {ai_charge(1); +ai_charge(10); +if (random()<0.5) + self.think = sham_swingl1; +}; + +void() sham_melee = +{ + local float chance; + + chance = random(); + if (chance > 0.6 || self.health == 600) + sham_smash1 (); + else if (chance > 0.3) + sham_swingr1 (); + else + sham_swingl1 (); +}; + + +//============================================================================ + +void() CastLightning = +{ + local vector org, dir; + + self.effects = self.effects | EF_MUZZLEFLASH; + + ai_face (); + + org = self.origin + '0 0 40'; + + dir = self.enemy.origin + '0 0 16' - org; + dir = normalize (dir); + + traceline (org, self.origin + dir*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING1); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + // overload the frags field which is unused on enemies + self.frags += 1; + + LightningDamage (org, trace_endpos, self, 10); +}; + +void() sham_magic1 =[ $magic1, sham_magic2 ] {ai_face(); + sound (self, CHAN_WEAPON, "shambler/sattck1.wav", 1, ATTN_NORM); +}; +void() sham_magic2 =[ $magic2, sham_magic3 ] {ai_face();}; +void() sham_magic3 =[ $magic3, sham_magic4 ] {ai_face();self.nextthink = self.nextthink + 0.2; +local entity o; + +self.effects = self.effects | EF_MUZZLEFLASH; +ai_face(); +self.owner = spawn(); +o = self.owner; +setmodel (o, "progs/s_light.mdl"); +setorigin (o, self.origin); +o.angles = self.angles; +o.nextthink = time + 0.7; +o.think = SUB_Remove; +}; +void() sham_magic4 =[ $magic4, sham_magic5 ] +{ +self.effects = self.effects | EF_MUZZLEFLASH; +self.owner.frame = 1; +}; +void() sham_magic5 =[ $magic5, sham_magic6 ] +{ +self.effects = self.effects | EF_MUZZLEFLASH; +self.owner.frame = 2; +}; +void() sham_magic6 =[ $magic6, sham_magic9 ] +{ +remove (self.owner); +CastLightning(); +sound (self, CHAN_WEAPON, "shambler/sboom.wav", 1, ATTN_NORM); +}; +void() sham_magic9 =[ $magic9, sham_magic10 ] +{CastLightning();}; +void() sham_magic10 =[ $magic10, sham_magic11 ] +{CastLightning();}; +void() sham_magic11 =[ $magic11, sham_magic12 ] {}; +void() sham_magic12 =[ $magic12, sham_run1 ] {}; + + + +void() sham_pain1 =[ $pain1, sham_pain2 ] {}; +void() sham_pain2 =[ $pain2, sham_pain3 ] {}; +void() sham_pain3 =[ $pain3, sham_pain4 ] {}; +void() sham_pain4 =[ $pain4, sham_pain5 ] {}; +void() sham_pain5 =[ $pain5, sham_pain6 ] {}; +void() sham_pain6 =[ $pain6, sham_run1 ] {}; + +void(entity attacker, float damage) sham_pain = +{ + sound (self, CHAN_VOICE, "shambler/shurt2.wav", 1, ATTN_NORM); + + if (damage >= self.health && attacker.classname == "player" && attacker.weapon == IT_AXE) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_CLOSE_SHAVE"); + } + + // overload frags field for shambler dance achievement + if (damage >= self.health && attacker.classname == "player" && self.frags == 0) + { + msg_entity = attacker; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_SHAMBLER_DANCE"); + } + + if (self.health <= 0) + return; // allready dying, don't go into pain frame + + if (random()*400 > damage) + return; // didn't flinch + + if (self.pain_finished > time) + return; + self.pain_finished = time + 2; + + sham_pain1 (); +}; + + +//============================================================================ + +void() sham_death1 =[ $death1, sham_death2 ] {}; +void() sham_death2 =[ $death2, sham_death3 ] {}; +void() sham_death3 =[ $death3, sham_death4 ] {self.solid = SOLID_NOT;}; +void() sham_death4 =[ $death4, sham_death5 ] {}; +void() sham_death5 =[ $death5, sham_death6 ] {}; +void() sham_death6 =[ $death6, sham_death7 ] {}; +void() sham_death7 =[ $death7, sham_death8 ] {}; +void() sham_death8 =[ $death8, sham_death9 ] {}; +void() sham_death9 =[ $death9, sham_death10 ] {}; +void() sham_death10 =[ $death10, sham_death11 ] {}; +void() sham_death11 =[ $death11, sham_death11 ] {}; + +void() sham_die = +{ +// check for gib + if (self.health < -60) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_shams.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "shambler/sdeath.wav", 1, ATTN_NORM); + sham_death1 (); +}; + +//============================================================================ + + +/*QUAKED monster_shambler (1 0 0) (-32 -32 -24) (32 32 64) Ambush +*/ +void() monster_shambler = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/shambler.mdl"); + precache_model ("progs/s_light.mdl"); + precache_model ("progs/h_shams.mdl"); + precache_model ("progs/bolt.mdl"); + + precache_sound ("shambler/sattck1.wav"); + precache_sound ("shambler/sboom.wav"); + precache_sound ("shambler/sdeath.wav"); + precache_sound ("shambler/shurt2.wav"); + precache_sound ("shambler/sidle.wav"); + precache_sound ("shambler/ssight.wav"); + precache_sound ("shambler/melee1.wav"); + precache_sound ("shambler/melee2.wav"); + precache_sound ("shambler/smack.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + setmodel (self, "progs/shambler.mdl"); + + setsize (self, VEC_HULL2_MIN, VEC_HULL2_MAX); + self.health = 600; + + self.th_stand = sham_stand1; + self.th_walk = sham_walk1; + self.th_run = sham_run1; + self.th_die = sham_die; + self.th_melee = sham_melee; + self.th_missile = sham_magic1; + self.th_pain = sham_pain; + + // overload frags for lightning attack counts + self.frags = 0; + + walkmonster_start(); +}; diff --git a/quakec_rogue/shield.qc b/quakec_rogue/shield.qc new file mode 100644 index 0000000..a0325a5 --- /dev/null +++ b/quakec_rogue/shield.qc @@ -0,0 +1,122 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Shield +// + +// =================== +// shield_think +// =================== +void() shield_think = +{ + if ( self.owner.shield_death_time < time) + { + self.owner.shield_entity = world; + remove (self); + return; + } + + if ( (self.owner.shield_death_time - 0.25) <= time) + { + self.model = string_null; + } + else + { + setorigin ( self, self.owner.origin ); + self.v_angle = self.owner.v_angle; + self.angles = self.owner.angles; + } + + self.think = shield_think; + self.nextthink = time + 0.05; +}; + +// =================== +// shield_spawn +// =================== +void(entity shieldOwner, vector dir) shield_spawn = +{ + local entity newShield; + + if ( shieldOwner.shield_death_time > time) + { + return; + } + + newShield = spawn (); + newShield.owner = shieldOwner; + newShield.solid = SOLID_NOT; + newShield.takedamage = DAMAGE_NO; + newShield.movetype = MOVETYPE_NONE; + newShield.classname = "power_shield"; + + // time for shield to live +// shieldOwner.shield_death_time = time + 0.5; + shieldOwner.shield_death_time = time + 0.3; + shieldOwner.shield_entity = newShield; + + newShield.v_angle = shieldOwner.v_angle; + newShield.angles = shieldOwner.angles; + setorigin (newShield, shieldOwner.origin); + setmodel (newShield, "progs/p_shield.mdl"); + setsize (newShield, '0 0 0', '0 0 0'); + + newShield.nextthink = time + 0.1; + newShield.think = shield_think; +}; + + +// =================== +// shield_hit +// =================== +float(entity targ, entity inflictor, entity attacker, float damage) shield_hit= +{ + local vector dir; + local float save; + local float hitAngle; + + dir = inflictor.origin - targ.origin; + save = vectoyaw ( dir ); + makevectors (targ.angles); + hitAngle = save - vectoyaw ( v_forward ); + + if(hitAngle > 90 && hitAngle < 270) + { + return damage; + } + else if(hitAngle < -90 && hitAngle > -270 ) + { + return damage; + } + + shield_spawn ( targ, dir); + + if (targ.shieldSoundTime < time) + { + sound (targ, CHAN_ITEM, "shield/hit.wav", 1, ATTN_NORM); + targ.shieldSoundTime = time + 0.5; + } + + if (inflictor.classname == "lava_spike") + save = damage * 0.70; + else + save = damage * 0.30; + + return save; +}; diff --git a/quakec_rogue/soldier.qc b/quakec_rogue/soldier.qc new file mode 100644 index 0000000..e6156d0 --- /dev/null +++ b/quakec_rogue/soldier.qc @@ -0,0 +1,303 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +SOLDIER / PLAYER + +============================================================================== +*/ + +$cd /raid/quake/id1/models/soldier3 +$origin 0 -6 24 +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 +$frame death9 death10 + +$frame deathc1 deathc2 deathc3 deathc4 deathc5 deathc6 deathc7 deathc8 +$frame deathc9 deathc10 deathc11 + +$frame load1 load2 load3 load4 load5 load6 load7 load8 load9 load10 load11 + +$frame pain1 pain2 pain3 pain4 pain5 pain6 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 + +$frame shoot1 shoot2 shoot3 shoot4 shoot5 shoot6 shoot7 shoot8 shoot9 + +$frame prowl_1 prowl_2 prowl_3 prowl_4 prowl_5 prowl_6 prowl_7 prowl_8 +$frame prowl_9 prowl_10 prowl_11 prowl_12 prowl_13 prowl_14 prowl_15 prowl_16 +$frame prowl_17 prowl_18 prowl_19 prowl_20 prowl_21 prowl_22 prowl_23 prowl_24 + +/* +============================================================================== +SOLDIER CODE +============================================================================== +*/ + +void() army_fire; + +void() army_stand1 =[ $stand1, army_stand2 ] {ai_stand();}; +void() army_stand2 =[ $stand2, army_stand3 ] {ai_stand();}; +void() army_stand3 =[ $stand3, army_stand4 ] {ai_stand();}; +void() army_stand4 =[ $stand4, army_stand5 ] {ai_stand();}; +void() army_stand5 =[ $stand5, army_stand6 ] {ai_stand();}; +void() army_stand6 =[ $stand6, army_stand7 ] {ai_stand();}; +void() army_stand7 =[ $stand7, army_stand8 ] {ai_stand();}; +void() army_stand8 =[ $stand8, army_stand1 ] {ai_stand();}; + +void() army_walk1 =[ $prowl_1, army_walk2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); +ai_walk(1);}; +void() army_walk2 =[ $prowl_2, army_walk3 ] {ai_walk(1);}; +void() army_walk3 =[ $prowl_3, army_walk4 ] {ai_walk(1);}; +void() army_walk4 =[ $prowl_4, army_walk5 ] {ai_walk(1);}; +void() army_walk5 =[ $prowl_5, army_walk6 ] {ai_walk(2);}; +void() army_walk6 =[ $prowl_6, army_walk7 ] {ai_walk(3);}; +void() army_walk7 =[ $prowl_7, army_walk8 ] {ai_walk(4);}; +void() army_walk8 =[ $prowl_8, army_walk9 ] {ai_walk(4);}; +void() army_walk9 =[ $prowl_9, army_walk10 ] {ai_walk(2);}; +void() army_walk10 =[ $prowl_10, army_walk11 ] {ai_walk(2);}; +void() army_walk11 =[ $prowl_11, army_walk12 ] {ai_walk(2);}; +void() army_walk12 =[ $prowl_12, army_walk13 ] {ai_walk(1);}; +void() army_walk13 =[ $prowl_13, army_walk14 ] {ai_walk(0);}; +void() army_walk14 =[ $prowl_14, army_walk15 ] {ai_walk(1);}; +void() army_walk15 =[ $prowl_15, army_walk16 ] {ai_walk(1);}; +void() army_walk16 =[ $prowl_16, army_walk17 ] {ai_walk(1);}; +void() army_walk17 =[ $prowl_17, army_walk18 ] {ai_walk(3);}; +void() army_walk18 =[ $prowl_18, army_walk19 ] {ai_walk(3);}; +void() army_walk19 =[ $prowl_19, army_walk20 ] {ai_walk(3);}; +void() army_walk20 =[ $prowl_20, army_walk21 ] {ai_walk(3);}; +void() army_walk21 =[ $prowl_21, army_walk22 ] {ai_walk(2);}; +void() army_walk22 =[ $prowl_22, army_walk23 ] {ai_walk(1);}; +void() army_walk23 =[ $prowl_23, army_walk24 ] {ai_walk(1);}; +void() army_walk24 =[ $prowl_24, army_walk1 ] {ai_walk(1);}; + +void() army_run1 =[ $run1, army_run2 ] { +if (random() < 0.2) + sound (self, CHAN_VOICE, "soldier/idle.wav", 1, ATTN_IDLE); +ai_run(11);}; +void() army_run2 =[ $run2, army_run3 ] {ai_run(15);}; +void() army_run3 =[ $run3, army_run4 ] {ai_run(10);}; +void() army_run4 =[ $run4, army_run5 ] {ai_run(10);}; +void() army_run5 =[ $run5, army_run6 ] {ai_run(8);}; +void() army_run6 =[ $run6, army_run7 ] {ai_run(15);}; +void() army_run7 =[ $run7, army_run8 ] {ai_run(10);}; +void() army_run8 =[ $run8, army_run1 ] {ai_run(8);}; + +void() army_atk1 =[ $shoot1, army_atk2 ] {ai_face();}; +void() army_atk2 =[ $shoot2, army_atk3 ] {ai_face();}; +void() army_atk3 =[ $shoot3, army_atk4 ] {ai_face();}; +void() army_atk4 =[ $shoot4, army_atk5 ] {ai_face();}; +void() army_atk5 =[ $shoot5, army_atk6 ] {ai_face();army_fire(); +self.effects = self.effects | EF_MUZZLEFLASH;}; +void() army_atk6 =[ $shoot6, army_atk7 ] {ai_face();}; +void() army_atk7 =[ $shoot7, army_atk8 ] {ai_face();SUB_CheckRefire (army_atk1);}; +void() army_atk8 =[ $shoot8, army_atk9 ] {ai_face();}; +void() army_atk9 =[ $shoot9, army_run1 ] {ai_face();}; + + +void() army_pain1 =[ $pain1, army_pain2 ] {}; +void() army_pain2 =[ $pain2, army_pain3 ] {}; +void() army_pain3 =[ $pain3, army_pain4 ] {}; +void() army_pain4 =[ $pain4, army_pain5 ] {}; +void() army_pain5 =[ $pain5, army_pain6 ] {}; +void() army_pain6 =[ $pain6, army_run1 ] {ai_pain(1);}; + +void() army_painb1 =[ $painb1, army_painb2 ] {}; +void() army_painb2 =[ $painb2, army_painb3 ] {ai_painforward(13);}; +void() army_painb3 =[ $painb3, army_painb4 ] {ai_painforward(9);}; +void() army_painb4 =[ $painb4, army_painb5 ] {}; +void() army_painb5 =[ $painb5, army_painb6 ] {}; +void() army_painb6 =[ $painb6, army_painb7 ] {}; +void() army_painb7 =[ $painb7, army_painb8 ] {}; +void() army_painb8 =[ $painb8, army_painb9 ] {}; +void() army_painb9 =[ $painb9, army_painb10] {}; +void() army_painb10=[ $painb10, army_painb11] {}; +void() army_painb11=[ $painb11, army_painb12] {}; +void() army_painb12=[ $painb12, army_painb13] {ai_pain(2);}; +void() army_painb13=[ $painb13, army_painb14] {}; +void() army_painb14=[ $painb14, army_run1 ] {}; + +void() army_painc1 =[ $painc1, army_painc2 ] {}; +void() army_painc2 =[ $painc2, army_painc3 ] {ai_pain(1);}; +void() army_painc3 =[ $painc3, army_painc4 ] {}; +void() army_painc4 =[ $painc4, army_painc5 ] {}; +void() army_painc5 =[ $painc5, army_painc6 ] {ai_painforward(1);}; +void() army_painc6 =[ $painc6, army_painc7 ] {ai_painforward(1);}; +void() army_painc7 =[ $painc7, army_painc8 ] {}; +void() army_painc8 =[ $painc8, army_painc9 ] {ai_pain(1);}; +void() army_painc9 =[ $painc9, army_painc10] {ai_painforward(4);}; +void() army_painc10=[ $painc10, army_painc11] {ai_painforward(3);}; +void() army_painc11=[ $painc11, army_painc12] {ai_painforward(6);}; +void() army_painc12=[ $painc12, army_painc13] {ai_painforward(8);}; +void() army_painc13=[ $painc13, army_run1] {}; + +void(entity attacker, float damage) army_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (r < 0.2) + { + self.pain_finished = time + 0.6; + army_pain1 (); + sound (self, CHAN_VOICE, "soldier/pain1.wav", 1, ATTN_NORM); + } + else if (r < 0.6) + { + self.pain_finished = time + 1.1; + army_painb1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } + else + { + self.pain_finished = time + 1.1; + army_painc1 (); + sound (self, CHAN_VOICE, "soldier/pain2.wav", 1, ATTN_NORM); + } +}; + + +void() army_fire = +{ + local vector dir; + local entity en; + + ai_face(); + + sound (self, CHAN_WEAPON, "soldier/sattck1.wav", 1, ATTN_NORM); + +// fire somewhat behind the player, so a dodging player is harder to hit + en = self.enemy; + + dir = en.origin - en.velocity*0.2; + dir = normalize (dir - self.origin); + + FireBullets (4, dir, '0.1 0.1 0'); +}; + + + +void() army_die1 =[ $death1, army_die2 ] {}; +void() army_die2 =[ $death2, army_die3 ] {}; +void() army_die3 =[ $death3, army_die4 ] +{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();}; +void() army_die4 =[ $death4, army_die5 ] {}; +void() army_die5 =[ $death5, army_die6 ] {}; +void() army_die6 =[ $death6, army_die7 ] {}; +void() army_die7 =[ $death7, army_die8 ] {}; +void() army_die8 =[ $death8, army_die9 ] {}; +void() army_die9 =[ $death9, army_die10 ] {}; +void() army_die10 =[ $death10, army_die10 ] {}; + +void() army_cdie1 =[ $deathc1, army_cdie2 ] {}; +void() army_cdie2 =[ $deathc2, army_cdie3 ] {ai_back(5);}; +void() army_cdie3 =[ $deathc3, army_cdie4 ] +{self.solid = SOLID_NOT;self.ammo_shells = 5;DropBackpack();ai_back(4);}; +void() army_cdie4 =[ $deathc4, army_cdie5 ] {ai_back(13);}; +void() army_cdie5 =[ $deathc5, army_cdie6 ] {ai_back(3);}; +void() army_cdie6 =[ $deathc6, army_cdie7 ] {ai_back(4);}; +void() army_cdie7 =[ $deathc7, army_cdie8 ] {}; +void() army_cdie8 =[ $deathc8, army_cdie9 ] {}; +void() army_cdie9 =[ $deathc9, army_cdie10 ] {}; +void() army_cdie10 =[ $deathc10, army_cdie11 ] {}; +void() army_cdie11 =[ $deathc11, army_cdie11 ] {}; + + +void() army_die = +{ +// check for gib + if (self.health < -35) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_guard.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); + return; + } + +// regular death + sound (self, CHAN_VOICE, "soldier/death1.wav", 1, ATTN_NORM); + if (random() < 0.5) + army_die1 (); + else + army_cdie1 (); +}; + + +/*QUAKED monster_army (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_army = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/soldier.mdl"); + precache_model ("progs/h_guard.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_sound ("soldier/death1.wav"); + precache_sound ("soldier/idle.wav"); + precache_sound ("soldier/pain1.wav"); + precache_sound ("soldier/pain2.wav"); + precache_sound ("soldier/sattck1.wav"); + precache_sound ("soldier/sight1.wav"); + + precache_sound ("player/udeath.wav"); // gib death + + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/soldier.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 30; + + self.th_stand = army_stand1; + self.th_walk = army_walk1; + self.th_run = army_run1; + self.th_missile = army_atk1; + self.th_pain = army_pain; + self.th_die = army_die; + + walkmonster_start (); +}; diff --git a/quakec_rogue/sphere.qc b/quakec_rogue/sphere.qc new file mode 100644 index 0000000..aa8c8ec --- /dev/null +++ b/quakec_rogue/sphere.qc @@ -0,0 +1,177 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// vengeance sphere + +// ============================== +// sphere_impact +// ============================== +void() sphere_impact = +{ + if (other.health) + { + T_Damage (other, self, self, 1000 ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact + T_RadiusDamage (self, self, 300, other); + +// sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +void(entity ownerEntity) sphere_remove = +{ + local entity theSphere; + + theSphere = find(world, classname, "Vengeance"); + while(theSphere != world) + { + if(theSphere.owner == self) + { + bprint("$qc_death_denied_vengance", theSphere.owner.netname); + remove(theSphere); + theSphere = find(world, classname, "Vengeance"); + } + else + theSphere = find(theSphere, classname, "Vengeance"); + } +}; + +// ============================== +// sphere_attack +// ============================== +void() sphere_attack = +{ + self.solid = SOLID_TRIGGER; + self.touch = sphere_impact; + + if (self.enemy.health < 1) + { + sprint ( self.owner, "$qc_you_are_denied_vengeance"); + remove(self); + return; + } + + ai_track(self.enemy, 650); + self.nextthink = time + 0.1; + self.think = sphere_attack; +}; + +// ============================== +// sphere_think +// ============================== +void() sphere_think = +{ + if (self.shieldSoundTime < time) + { + sound ( self, CHAN_VOICE, "sphere/sphere.wav", 1, ATTN_NORM); + self.shieldSoundTime = time + 4; + } + + if (time > self.delay) + { + if (self.owner.items2 & IT2_V_SPHERE) + self.owner.items2 = self.owner.items2 - IT2_V_SPHERE; + sprint ( self.owner, "$qc_vengeance_lost"); + remove (self); + return; + } + + if (self.owner.health < 1) + { + if (self.owner.items2 & IT2_V_SPHERE) + self.owner.items2 = self.owner.items2 - IT2_V_SPHERE; + if ( self.owner.enemy.classname == "player" ) + { + self.enemy = self.owner.enemy; + sphere_attack(); + return; + } + if ( self.owner.enemy.owner.classname == "player") + { + self.enemy = self.owner.enemy.owner; + sphere_attack(); + return; + } + remove (self); + return; + } + + ai_orbit(self.owner, 16, '0 0 48'); + + self.think = sphere_think; + self.nextthink = time + 0.1; +}; + +// ============================== +// sphere_spawn +// ============================== +void() sphere_spawn = +{ + local entity missile; + + missile = spawn(); + missile.movetype = MOVETYPE_FLYMISSILE; +// missile.solid = SOLID_BBOX; + missile.solid = SOLID_NOT; + missile.classname = "Vengeance"; + missile.owner = other; + missile.weapon = 0; + missile.delay = time + 30; + setmodel (missile, "progs/sphere.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin ); + missile.avelocity = '40 40 40'; + missile.think = sphere_think; + missile.nextthink = time + 0.1; +}; + +/*QUAKED item_sphere (0 0 1) (-8 -8 -8) (8 8 8) ? +The $qc_vengeance_sphere. DEATHMATCH ONLY. +*/ +void() item_sphere = +{ + if (!deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/sphere.mdl"); + precache_sound ("sphere/sphere.wav"); + + self.noise = "sphere/sphere.wav"; + self.netname = "$qc_vengeance_sphere"; + self.touch = newitems_touch; + + setmodel (self, "progs/sphere.mdl"); + setsize (self, '-8 -8 -8', '8 8 8'); + self.avelocity = '40 40 40'; + StartItem (); +}; diff --git a/quakec_rogue/sprites.qc b/quakec_rogue/sprites.qc new file mode 100644 index 0000000..e90b66d --- /dev/null +++ b/quakec_rogue/sprites.qc @@ -0,0 +1,45 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +// these are the only sprites still in the game... + +$spritename s_explod +$type vp_parallel +$load /raid/quake/id1/gfx/sprites/explod03.lbm +$frame 24 24 56 56 +$frame 120 24 56 56 +$frame 216 24 56 56 +$frame 24 88 56 56 +$frame 120 88 56 56 +$frame 216 88 56 56 + + +$spritename s_bubble +$type vp_parallel +$load /raid/quake/id1/gfx/sprites/bubble.lbm +$frame 16 16 16 16 +$frame 40 16 16 16 + + +$spritename s_light +$type vp_parallel +$load /raid/quake/id1/gfx/sprites/light.lbm +$frame 104 32 32 32 + diff --git a/quakec_rogue/subs.qc b/quakec_rogue/subs.qc new file mode 100644 index 0000000..a2e5f9f --- /dev/null +++ b/quakec_rogue/subs.qc @@ -0,0 +1,335 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + + +void() SUB_Null = {}; + +void() SUB_Remove = {remove(self);}; + + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +vector() SetMovedir = +{ + if (self.angles == '0 -1 0') + self.movedir = '0 0 1'; + else if (self.angles == '0 -2 0') + self.movedir = '0 0 -1'; + else + { + makevectors (self.angles); + self.movedir = v_forward; + } + + self.angles = '0 0 0'; +}; + +/* +================ +InitTrigger +================ +*/ +void() InitTrigger = +{ +// trigger angles are used for one-way touches. An angle of 0 is assumed +// to mean no restrictions, so use a yaw of 360 instead. + if (self.angles != '0 0 0') + SetMovedir (); + self.solid = SOLID_TRIGGER; + setmodel (self, self.model); // set size and link into world + self.movetype = MOVETYPE_NONE; + self.modelindex = 0; + self.model = ""; +}; + +/* +============= +SUB_CalcMove + +calculate self.velocity and self.nextthink to reach dest from +self.origin traveling at speed +=============== +*/ +void(entity ent, vector tdest, float tspeed, void() func) SUB_CalcMoveEnt = +{ +local entity stemp; + stemp = self; + self = ent; + + SUB_CalcMove (tdest, tspeed, func); + self = stemp; +}; + +void(vector tdest, float tspeed, void() func) SUB_CalcMove = +{ +local vector vdestdelta; +local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + + self.think1 = func; + self.finaldest = tdest; + self.think = SUB_CalcMoveDone; + + if (tdest == self.origin) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + +// set destdelta to the vector needed to move + vdestdelta = tdest - self.origin; + +// calculate length of vector + len = vlen (vdestdelta); + +// divide by speed to get time to reach dest + traveltime = len / tspeed; + + if (traveltime < 0.1) + { + self.velocity = '0 0 0'; + self.nextthink = self.ltime + 0.1; + return; + } + +// set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + +// scale the destdelta vector by the time spent traveling to get velocity + self.velocity = vdestdelta * (1/traveltime); // qcc won't take vec/float +}; + +/* +============ +After moving, set origin to exact final destination +============ +*/ +void() SUB_CalcMoveDone = +{ + setorigin(self, self.finaldest); + self.velocity = '0 0 0'; + self.nextthink = -1; + if (self.think1) + self.think1(); +}; + + +/* +============= +SUB_CalcAngleMove + +calculate self.avelocity and self.nextthink to reach destangle from +self.angles rotating + +The calling function should make sure self.think is valid +=============== +*/ +void(entity ent, vector destangle, float tspeed, void() func) SUB_CalcAngleMoveEnt = +{ +local entity stemp; + stemp = self; + self = ent; + SUB_CalcAngleMove (destangle, tspeed, func); + self = stemp; +}; + +void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove = +{ +local vector destdelta; +local float len, traveltime; + + if (!tspeed) + objerror("No speed is defined!"); + +// set destdelta to the vector needed to move + destdelta = destangle - self.angles; + +// calculate length of vector + len = vlen (destdelta); + +// divide by speed to get time to reach dest + traveltime = len / tspeed; + +// set nextthink to trigger a think when dest is reached + self.nextthink = self.ltime + traveltime; + +// scale the destdelta vector by the time spent traveling to get velocity + self.avelocity = destdelta * (1 / traveltime); + + self.think1 = func; + self.finalangle = destangle; + self.think = SUB_CalcAngleMoveDone; +}; + +/* +============ +After rotating, set angle to exact final angle +============ +*/ +void() SUB_CalcAngleMoveDone = +{ + self.angles = self.finalangle; + self.avelocity = '0 0 0'; + self.nextthink = -1; + if (self.think1) + self.think1(); +}; + + +//============================================================================= + +void() DelayThink = +{ + activator = self.enemy; + SUB_UseTargets (); + remove(self); +}; + +/* +============================== +SUB_UseTargets + +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. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void() SUB_UseTargets = +{ + local entity t, stemp, otemp, act; + +// +// check for a delay +// + if (self.delay) + { + // create a temp object to fire at a later time + t = spawn(); + t.classname = "DelayedUse"; + t.nextthink = time + self.delay; + t.think = DelayThink; + t.enemy = activator; + t.message = self.message; + t.killtarget = self.killtarget; + t.target = self.target; + return; + } + + +// +// print the message +// + if (activator.classname == "player" && self.message != "") + { + centerprint (activator, self.message); + if (!self.noise) + sound (activator, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM); + } + +// +// kill the killtagets +// + if (self.killtarget) + { + t = world; + do + { + t = find (t, targetname, self.killtarget); + if (!t) + return; + remove (t); + } while ( 1 ); + } + +// +// fire targets +// + if (self.target) + { + act = activator; + t = world; + do + { + t = find (t, targetname, self.target); + if (!t) + { + return; + } + stemp = self; + otemp = other; + self = t; + other = stemp; + if (self.use != SUB_Null) + { + if (self.use) + self.use (); + } + self = stemp; + other = otemp; + activator = act; + } while ( 1 ); + } + + +}; + + +/* + +in nightmare mode, all attack_finished times become 0 +some monsters refire twice automatically +update: not anymore! it makes nightmare too easy +*/ + +void(float normal) SUB_AttackFinished = +{ + self.cnt = 0; // refire count for nightmare + //if (skill != 3) + self.attack_finished = time + normal; +}; + +float (entity targ) visible; + +void (void() thinkst) SUB_CheckRefire = +{ + if (skill != 3) + return; + if (self.cnt == 1) + return; + if (!visible (self.enemy)) + return; + self.cnt = 1; + self.think = thinkst; +}; diff --git a/quakec_rogue/tarbaby.qc b/quakec_rogue/tarbaby.qc new file mode 100644 index 0000000..1f3710e --- /dev/null +++ b/quakec_rogue/tarbaby.qc @@ -0,0 +1,367 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +BLOB + +============================================================================== +*/ + +$cd /raid/quake/id1/models/tarbaby +$origin 0 0 24 +$base base + +//$skin skin +$skin base +$skin skin2 +$skin skin + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 +$frame walk11 walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 +$frame walk20 walk21 walk22 walk23 walk24 walk25 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 run13 +$frame run14 run15 run16 run17 run18 run19 run20 run21 run22 run23 +$frame run24 run25 + +$frame jump1 jump2 jump3 jump4 jump5 jump6 + +$frame fly1 fly2 fly3 fly4 + +$frame exp + +void() tbaby_stand1 =[ $walk1, tbaby_stand1 ] {ai_stand();}; + +void() tbaby_hang1 =[ $walk1, tbaby_hang1 ] {ai_stand();}; + +void() tbaby_walk1 =[ $walk1, tbaby_walk2 ] {ai_turn();}; +void() tbaby_walk2 =[ $walk2, tbaby_walk3 ] {ai_turn();}; +void() tbaby_walk3 =[ $walk3, tbaby_walk4 ] {ai_turn();}; +void() tbaby_walk4 =[ $walk4, tbaby_walk5 ] {ai_turn();}; +void() tbaby_walk5 =[ $walk5, tbaby_walk6 ] {ai_turn();}; +void() tbaby_walk6 =[ $walk6, tbaby_walk7 ] {ai_turn();}; +void() tbaby_walk7 =[ $walk7, tbaby_walk8 ] {ai_turn();}; +void() tbaby_walk8 =[ $walk8, tbaby_walk9 ] {ai_turn();}; +void() tbaby_walk9 =[ $walk9, tbaby_walk10 ] {ai_turn();}; +void() tbaby_walk10 =[ $walk10, tbaby_walk11 ] {ai_turn();}; +void() tbaby_walk11 =[ $walk11, tbaby_walk12 ] {ai_walk(2);}; +void() tbaby_walk12 =[ $walk12, tbaby_walk13 ] {ai_walk(2);}; +void() tbaby_walk13 =[ $walk13, tbaby_walk14 ] {ai_walk(2);}; +void() tbaby_walk14 =[ $walk14, tbaby_walk15 ] {ai_walk(2);}; +void() tbaby_walk15 =[ $walk15, tbaby_walk16 ] {ai_walk(2);}; +void() tbaby_walk16 =[ $walk16, tbaby_walk17 ] {ai_walk(2);}; +void() tbaby_walk17 =[ $walk17, tbaby_walk18 ] {ai_walk(2);}; +void() tbaby_walk18 =[ $walk18, tbaby_walk19 ] {ai_walk(2);}; +void() tbaby_walk19 =[ $walk19, tbaby_walk20 ] {ai_walk(2);}; +void() tbaby_walk20 =[ $walk20, tbaby_walk21 ] {ai_walk(2);}; +void() tbaby_walk21 =[ $walk21, tbaby_walk22 ] {ai_walk(2);}; +void() tbaby_walk22 =[ $walk22, tbaby_walk23 ] {ai_walk(2);}; +void() tbaby_walk23 =[ $walk23, tbaby_walk24 ] {ai_walk(2);}; +void() tbaby_walk24 =[ $walk24, tbaby_walk25 ] {ai_walk(2);}; +void() tbaby_walk25 =[ $walk25, tbaby_walk1 ] {ai_walk(2);}; + +void() tbaby_run1 =[ $run1, tbaby_run2 ] {ai_face();}; +void() tbaby_run2 =[ $run2, tbaby_run3 ] {ai_face();}; +void() tbaby_run3 =[ $run3, tbaby_run4 ] {ai_face();}; +void() tbaby_run4 =[ $run4, tbaby_run5 ] {ai_face();}; +void() tbaby_run5 =[ $run5, tbaby_run6 ] {ai_face();}; +void() tbaby_run6 =[ $run6, tbaby_run7 ] {ai_face();}; +void() tbaby_run7 =[ $run7, tbaby_run8 ] {ai_face();}; +void() tbaby_run8 =[ $run8, tbaby_run9 ] {ai_face();}; +void() tbaby_run9 =[ $run9, tbaby_run10 ] {ai_face();}; +void() tbaby_run10 =[ $run10, tbaby_run11 ] {ai_face();}; +void() tbaby_run11 =[ $run11, tbaby_run12 ] {ai_run(2);}; +void() tbaby_run12 =[ $run12, tbaby_run13 ] {ai_run(2);}; +void() tbaby_run13 =[ $run13, tbaby_run14 ] {ai_run(2);}; +void() tbaby_run14 =[ $run14, tbaby_run15 ] {ai_run(2);}; +void() tbaby_run15 =[ $run15, tbaby_run16 ] {ai_run(2);}; +void() tbaby_run16 =[ $run16, tbaby_run17 ] {ai_run(2);}; +void() tbaby_run17 =[ $run17, tbaby_run18 ] {ai_run(2);}; +void() tbaby_run18 =[ $run18, tbaby_run19 ] {ai_run(2);}; +void() tbaby_run19 =[ $run19, tbaby_run20 ] {ai_run(2);}; +void() tbaby_run20 =[ $run20, tbaby_run21 ] {ai_run(2);}; +void() tbaby_run21 =[ $run21, tbaby_run22 ] {ai_run(2);}; +void() tbaby_run22 =[ $run22, tbaby_run23 ] {ai_run(2);}; +void() tbaby_run23 =[ $run23, tbaby_run24 ] {ai_run(2);}; +void() tbaby_run24 =[ $run24, tbaby_run25 ] {ai_run(2);}; +void() tbaby_run25 =[ $run25, tbaby_run1 ] {ai_run(2);}; + + +//============================================================================ + + +void() tbaby_jump1; + +void() Tar_JumpTouch = +{ + local float ldmg; + + if (other.takedamage && other.classname != self.classname) + { + if ( vlen(self.velocity) > 400 ) + { + ldmg = 10 + 10*random(); + T_Damage (other, self, self, ldmg); + sound (self, CHAN_WEAPON, "blob/hit1.wav", 1, ATTN_NORM); + } + } + else + sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM); + + + if (!checkbottom(self)) + { + if (self.flags & FL_ONGROUND) + { // jump randomly to not get hung up +//dprint ("popjump\n"); + self.touch = SUB_Null; + self.think = tbaby_run1; + self.movetype = MOVETYPE_STEP; + self.nextthink = time + 0.1; + +// self.velocity_x = (random() - 0.5) * 600; +// self.velocity_y = (random() - 0.5) * 600; +// self.velocity_z = 200; +// self.flags = self.flags - FL_ONGROUND; + } + return; // not on ground yet + } + + self.touch = SUB_Null; + self.think = tbaby_jump1; + self.nextthink = time + 0.1; +}; + +void() tbaby_jump5; + +void() tbaby_fly1 =[ $fly1, tbaby_fly2 ] {}; +void() tbaby_fly2 =[ $fly2, tbaby_fly3 ] {}; +void() tbaby_fly3 =[ $fly3, tbaby_fly4 ] {}; +void() tbaby_fly4 =[ $fly4, tbaby_fly1 ] { +self.cnt = self.cnt + 1; +if (self.cnt == 4) +{ +//dprint ("spawn hop\n"); +tbaby_jump5 (); +} +}; + +void() tbaby_jump1 =[ $jump1, tbaby_jump2 ] {ai_face();}; +void() tbaby_jump2 =[ $jump2, tbaby_jump3 ] {ai_face();}; +void() tbaby_jump3 =[ $jump3, tbaby_jump4 ] {ai_face();}; +void() tbaby_jump4 =[ $jump4, tbaby_jump5 ] {ai_face();}; +void() tbaby_jump5 =[ $jump5, tbaby_jump6 ] +{ + self.movetype = MOVETYPE_BOUNCE; + self.touch = Tar_JumpTouch; + makevectors (self.angles); + self.origin_z = self.origin_z + 1; + self.velocity = v_forward * 600 + '0 0 200'; + self.velocity_z = self.velocity_z + random()*150; + if (self.flags & FL_ONGROUND) + self.flags = self.flags - FL_ONGROUND; + self.cnt = 0; +}; +void() tbaby_jump6 =[ $jump6,tbaby_fly1 ] {}; + + + +//============================================================================= + +void() tbaby_die1 =[ $exp, tbaby_die2 ] { +self.takedamage = DAMAGE_NO; +}; +void() tbaby_die2 =[ $exp, tbaby_run1 ] +{ + T_RadiusDamage (self, self, 120, world); + + sound (self, CHAN_VOICE, "blob/death1.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TAREXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +//============================================================================= +void() tbaby_mitosis; + +void() tbaby_checknew = +{ + if (!walkmove(0,0)) + { + remove(self); + return; + } + + self.enemy = self.enemy; + self.th_stand = tbaby_stand1; + self.th_walk = tbaby_walk1; + self.th_run = tbaby_run1; + self.th_missile = tbaby_jump1; + self.th_melee = tbaby_jump1; + self.th_die = tbaby_die1; + self.origin_z = self.origin_z + 1; // raise off floor a bit + droptofloor(); + + if (!walkmove(0,0)) + { + self.owner.th_pain = tbaby_mitosis; + remove(self); + return; + } + + self.takedamage = DAMAGE_AIM; + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 20; + self.view_ofs = '0 0 25'; + self.use = monster_use; + self.flags = self.flags | FL_MONSTER; + self.pausetime = 99999999; + self.th_stand (); + self.nextthink = self.nextthink + random()*0.5; + + total_monsters = total_monsters + 1; + + + WriteByte (MSG_BROADCAST, SVC_UPDATESTAT); + WriteByte (MSG_BROADCAST, STAT_TOTALMONSTERS); + WriteLong (MSG_BROADCAST, total_monsters); +}; + +void() tbaby_mitosis = +{ + local entity newSpawn; + + if ( random() < 0.35 ) + { + newSpawn = spawn(); + newSpawn.classname = self.classname; + newSpawn.movetype = MOVETYPE_STEP; + setsize (newSpawn, '-16 -16 -24', '16 16 40'); + newSpawn.health = 80; + if (self.skin == 1) + newSpawn.health = 60; + else if (self.skin == 2) + newSpawn.health = 100; + + newSpawn.owner = self; + self.th_pain = SUB_Null; + + setmodel (newSpawn, "progs/tarbaby.mdl"); + newSpawn.solid = SOLID_SLIDEBOX; + newSpawn.skin = self.skin; + + setorigin (newSpawn, self.origin); + newSpawn.think = tbaby_checknew; + newSpawn.nextthink = time + 0.5; + + sound (newSpawn, CHAN_VOICE, "blob/mytosis.wav", 1, ATTN_NORM); + } +}; + +/*QUAKED monster_tarbaby (1 0 0) (-16 -16 -24) (16 16 24) Ambush Blue Green Spots +The Spawn (and hellspawn) + +The spawn type is random by default. +If you want a specific type, select "Blue", "Green", or "Spots". +*/ +void() monster_tarbaby = +{ + local float myRand; + local float skillLevel; + + if (deathmatch) + { + remove(self); + return; + } + precache_model2 ("progs/tarbaby.mdl"); + + precache_sound2 ("blob/death1.wav"); + precache_sound2 ("blob/hit1.wav"); + precache_sound2 ("blob/land1.wav"); + precache_sound2 ("blob/sight1.wav"); + precache_sound2 ("blob/mytosis.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/tarbaby.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + + self.th_stand = tbaby_stand1; + self.th_walk = tbaby_walk1; + self.th_run = tbaby_run1; + self.th_missile = tbaby_jump1; + self.th_melee = tbaby_jump1; + self.th_die = tbaby_die1; + + if (self.spawnflags & 8) + { + self.th_pain = tbaby_mitosis; + self.skin = 2; + self.health = 100; + } + else if (self.spawnflags & 4) + { + self.th_pain = tbaby_mitosis; + self.skin = 1; + self.health = 60; + } + else if (self.spawnflags & 2) + { + self.skin = 0; + } + else + { + skillLevel = cvar ( "skill" ); + myRand = skillLevel * 0.2; + myRand = myRand + random(); + + if (myRand > 0.8 && skillLevel > 1) + { + self.th_pain = tbaby_mitosis; + self.skin = 2; + self.health = 100; + } + else if (myRand > 0.5) + { + self.th_pain = tbaby_mitosis; + self.skin = 1; + self.health = 60; + } + else + { + self.skin = 0; + } + } + + walkmonster_start (); +}; + diff --git a/quakec_rogue/teamplay.qc b/quakec_rogue/teamplay.qc new file mode 100644 index 0000000..c1cf905 --- /dev/null +++ b/quakec_rogue/teamplay.qc @@ -0,0 +1,1886 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// Rogue Teamplay Variants +// Jan'97 by ZOID +// Under contract to id software for Rogue Entertainment + + +// New entity fields + +.float steam; // selected team +.float suicide_count; // how many times has this player done something silly? +.float ctf_flags; // various flags for CTF +.float ctf_killed; // used for base spawning +.float ctf_flagsince; // when we picked up the flag +.float ctf_lasthurtcarrier; // when we last hurt the carrier +.float ctf_lastreturnedflag; // when we returned the flag +.float ctf_lastfraggedcarrier; // when we last fragged the carrier + +float TEAM1 = 5; // color of team1 (red), note that this is self.team which + //is one more than the color setting, so 'color 4' is red. +float TEAM2 = 14; // color of team2 (blue), note that this is self.team which + //is one more than the color setting, so 'color 13' is blue. +float TEAM3 = 1; // color of team3 (grey) in alt CTF + +entity team1_lastspawn; // last spawn spot for team1 +entity team2_lastspawn; // last spawn spot for team2 +float nextteamupdtime; // next time for a broadcast update + +// TEAMPLAY VALUES + +float TEAM_NORMAL_NODAMAGE = 1; // id's normal teamplay, can't hurt self +float TEAM_NORMAL_DAMAGE = 2; // id's alt teamplay, can hurt self/team +float TEAM_DMATCH_TAG = 3; // deathmatch tag, no 'team' stuff +float TEAM_CTF = 4; // regular CTF, locked colors +float TEAM_CTF_ONEFLAG = 5; // one flag CTF +float TEAM_CTF_ALT = 6; // alternate CTF (three teams) + +// gamecfg cvar fields + +float GAMECFG_ENABLE_RUNES = 1; // enable runes (default is disabled) +float GAMECFG_TEAM_ARMOR_DMG = 2; // 2 team damage affects armor (default is not) +float GAMECFG_TEAM_HEALTH_DMG = 4; // 4 team damage affects health (default is not) +float GAMECFG_USE_COLOR = 8; // use color player joins as (if you join and start with blue, you will go +// to blue team, same with red/green. If you are an illegal color, you'll +// get assigned). +float GAMECFG_ALLOW_CHG = 16; // allow people to change teams + +float CTF_CAPTURE_BONUS = 15; // what you get for capture +float CTF_TEAM_BONUS = 10; // what your team gets for capture +float CTF_ALT_CAPTURE_BONUS = 8; // what you get for capture +float CTF_ALT_TEAM_BONUS = 4; // what your team gets for capture +float CTF_RECOVERY_BONUS = 1; // what you get for recovery +float CTF_FLAG_BONUS = 0; // what you get for picking up enemy flag +float CTF_FRAG_CARRIER_BONUS = 2; // what you get for fragging enemy flag carrier +float CTF_FLAG_RETURN_TIME = 40; // seconds until auto return +float CTF_CARRIER_DANGER_PROTECT_BONUS = 2; // bonus for fraggin someone + // who has recently hurt your flag carrier +float CTF_CARRIER_PROTECT_BONUS = 1; // bonus for fraggin someone while + // either you or your target are near your flag carrier +float CTF_FLAG_DEFENSE_BONUS = 1; // bonus for fraggin someone while + // either you or your target are near your flag +float CTF_RETURN_FLAG_ASSIST_BONUS = 1; // awarded for returning a flag that causes a + // capture to happen almost immediately +float CTF_FRAG_CARRIER_ASSIST_BONUS = 2; // award for fragging a flag carrier if a + // capture happens almost immediately +float CTF_TARGET_PROTECT_RADIUS = 400; // the radius around an object being + // defended where a target will be worth extra frags +float CTF_ATTACKER_PROTECT_RADIUS = 400; // the radius around an object being + // defended where an attacker will get extra frags when making kills +float CTF_CARRIER_DANGER_PROTECT_TIMEOUT = 4; +float CTF_CARRIER_FLAG_SINCE_TIMEOUT = 2; +float CTF_FRAG_CARRIER_ASSIST_TIMEOUT = 6; +float CTF_RETURN_FLAG_ASSIST_TIMEOUT = 4; +float CTF_UPDATE_TIME = 120; + +// CTF flags +float CTF_FLAG_FLAG = 1; // palyer has flag in one flag mode +float CTF_FLAG_TEAM1 = 1; // player has team1's flag +float CTF_FLAG_TEAM2 = 2; // player has team2's flag +float CTF_FLAG_STUFF_COLOR = 4; // gotta stuff his color + +// flag status used in cnt field of flag +float FLAG_AT_BASE = 0; +float FLAG_CARRIED = 1; +float FLAG_DROPPED = 2; + +// Prototypes +float() W_BestWeapon; +void() W_SetCurrentAmmo; +void() bound_other_ammo; +void(float o, float n) Deathmatch_Weapon; +void() BackpackTouch; +void(entity comboOwner) EnableComboWeapons; + +// Return a name for the color of a team +string(float Team) GetTeamColor = +{ + if(Team == 1) return("White"); + else if(Team == 2) return("Brown"); + else if(Team == 3) return("Light blue"); + else if(Team == 4) return("Green"); + else if(Team == 5) return("Red"); + else if(Team == 6) return("Olive"); + else if(Team == 7) return("Orange"); + else if(Team == 8) return("Peech"); + else if(Team == 9) return("Purple"); + else if(Team == 10) return("Majenta"); + else if(Team == 11) return("Grey"); + else if(Team == 12) return("Aqua"); + else if(Team == 13) return("Yellow"); + else if(Team == 14) return("Blue"); + return "Unknown"; +}; + +string(float Team) GetCTFTeam = +{ + if (Team == TEAM1) return "���"; + else if (Team == TEAM2) return "����"; + else if (Team == TEAM3) return "����"; + return "UNKNOWN"; + +}; + +/* +================ +TeamArmorDam + +Return TRUE if the target's armor can take damage from this attacker. +================ +*/ + +float(entity targ, entity inflictor, entity attacker, float damage) TeamArmorDam = +{ + if (teamplay <= 0) + return TRUE; // PGM bug? fix + + // teamplay modes 4,5,6 protect armor + if ((teamplay == TEAM_CTF || + teamplay == TEAM_CTF_ONEFLAG || + teamplay == TEAM_CTF_ALT) && + attacker.steam == targ.steam && + attacker != targ && + !(cvar("gamecfg") & GAMECFG_TEAM_ARMOR_DMG)) { + // Armor is protected + return FALSE; + } + return TRUE; +}; + +/* +================ +TeamHealthDam + +Return TRUE if the target can take health damage from this attacker. +================ +*/ + +float(entity targ, entity inflictor, entity attacker, float damage) TeamHealthDam = +{ + if (teamplay <= 0) + return TRUE; + + if (teamplay == TEAM_NORMAL_NODAMAGE && attacker.steam == targ.steam) + return FALSE; + + // teamplay modes 4,5,6 protect health + if ((teamplay == TEAM_CTF || + teamplay == TEAM_CTF_ONEFLAG || + teamplay == TEAM_CTF_ALT) && + attacker.steam == targ.steam && + attacker != targ && + !(cvar("gamecfg") & GAMECFG_TEAM_HEALTH_DMG)) { + // Health is protected + return FALSE; + } + return TRUE; +}; + +void(entity who) TeamResetCarrier = +{ + local entity head; + + // When the flag carrier dies, reset the last_hurt_carrier field in + // all players on the opposite team from the flag carrier. The carrier + // has been killed, so there is no longer a reason to award points for + // killing off his assailants + if (teamplay >= TEAM_CTF && + (who.ctf_flags & (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2))) { + + head = find(world, classname, "player"); + + while (head != world) { + if (teamplay == TEAM_CTF_ONEFLAG || + ((who.ctf_flags & CTF_FLAG_TEAM1) && head.steam == TEAM1) || + ((who.ctf_flags & CTF_FLAG_TEAM2) && head.steam == TEAM2)) + head.ctf_lasthurtcarrier = -10; + head = find(head, classname, "player"); + } + } +}; + +void(entity targ, entity attacker) TeamAssists = +{ + local float flag_radius; + local float flag_carrier_radius; + local string s; + local entity head; + + if ((targ.ctf_flags & (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2)) && + targ.steam != attacker.steam) { + //ZOID: one team fragged the other team's flag carrier + + // Mark the attacker with the time at which he killed the flag + // carrier, for awarding assist points + attacker.ctf_lastfraggedcarrier = time; + + // give player only the normal amount of frags + // if the carrier has only had the flag for a few seconds, to + // prevent ppl intentionally allowing enemies to grab the flag, + // then immediately fragging them + if (targ.ctf_flagsince + CTF_CARRIER_FLAG_SINCE_TIMEOUT > time) { + sprint(attacker, "$qc_enemy_killed_no_bonus"); + } else { + attacker.frags = attacker.frags + CTF_FRAG_CARRIER_BONUS; + s = ftos(CTF_FRAG_CARRIER_BONUS); + sprint(attacker, "$qc_enemy_killed_bonus", s); + } + } + + // This code checks for all game-critical kills OTHER THAN fragging the enemy + // flag carrier, like killing players who are trying to kill your flag carrier + // or trying to grab your flag, and hands out bonus frags. + + // The two variables below track whether special bonus frags have already + // been awarded for the attacker or target being near the flag or flag carrier. + + flag_radius = 0; + flag_carrier_radius = 0; + + // get a string for the attacker's team now, for later announcements + s = GetCTFTeam(attacker.steam); + + if ((targ.ctf_lasthurtcarrier + CTF_CARRIER_DANGER_PROTECT_TIMEOUT > time) && + !(attacker.ctf_flags & (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2))) { + // a player on the same team as the flag carrier killed + // someone who recently shot the flag carrier + attacker.frags = attacker.frags + CTF_CARRIER_DANGER_PROTECT_BONUS; + flag_carrier_radius = 1; + // NOTE: getting CARRIER_DANGER_PROTECT_BONUS precludes getting + // other kinds of bonuses for defending the flag carrier, since + // it's worth more points + bprint("$qc_defend_flag_carrier_aggressive", attacker.netname, s); + } + + // Bonusus for defending the flag carrier or the flag itself. + // Extra frags are awarded if either the attacker or the target are + // 1. within 40 feet of a flag carrier on the same team as the attacker + // 2. within 40 feet of the attacker's flag + // These bonuses are cumulative with respect to defending both the + // flag and the flag carrier at the same time, but not cumulative with + // respect to both the target and attacker being near the object being defended + + // find flags or flag carriers within a radius of the attacker + head = findradius(attacker.origin, CTF_ATTACKER_PROTECT_RADIUS); + + while (head) { + if (head.classname == "player") { + if ( (head.steam == attacker.steam) && + (head.ctf_flags & (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2)) && + (head != attacker) && // self defense + (!flag_carrier_radius) ) { + // attacker was near his own flag carrier + attacker.frags = attacker.frags + CTF_CARRIER_PROTECT_BONUS; + flag_carrier_radius = 1; + bprint("$qc_defend_flag_carrier", attacker.netname, s); + } + } + if ((attacker.steam == TEAM1 && head.classname == "item_flag_team1") || + (attacker.steam == TEAM2 && head.classname == "item_flag_team2") || + head.classname == "item_flag") { // one flag mode + // attacker was near his own flag + attacker.frags = attacker.frags + CTF_FLAG_DEFENSE_BONUS; + flag_radius = 1; + if (teamplay != TEAM_CTF_ONEFLAG) { + bprint("$qc_defend_team_flag", attacker.netname, s); + } else { + bprint("$qc_defends_flag", attacker.netname); + } + } + head = head.chain; + } + + // find flags or flag carriers within a radius from the target + head = findradius(targ.origin, CTF_TARGET_PROTECT_RADIUS); + while (head) { + if (head.classname == "player") { + if ( (head.steam == attacker.steam) && + (head.ctf_flags & (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2)) && + (head != attacker) && + (!flag_carrier_radius)) { // prevents redundant points awarded + // target was near attacker's flag carrier + attacker.frags = attacker.frags + CTF_CARRIER_PROTECT_BONUS; + flag_carrier_radius = 1; + bprint("$qc_defend_flag_carrier", attacker.netname, s); + } + } + if ((attacker.steam == TEAM1 && head.classname == "item_flag_team1") || + (attacker.steam == TEAM2 && head.classname == "item_flag_team2") || + head.classname == "item_flag" && // one flag mode + (!flag_radius)) { // prevents redundant points awarded + // target was near attacker's flag + attacker.frags = attacker.frags + CTF_FLAG_DEFENSE_BONUS; + flag_radius = 1; + if (teamplay != TEAM_CTF_ONEFLAG) { + bprint("$qc_defend_team_flag", attacker.netname, s); + } else { + bprint("$qc_defends_flag", attacker.netname); + } + } + head = head.chain; + } +}; + +/* +================== +TeamColorIsLegal + +Return TRUE if the indicated color is legal +================== +*/ +float(float color) TeamColorIsLegal = +{ + // All colors are legal if teamplay is not CTF + if( teamplay < TEAM_CTF) { + if (color > 0) + return TRUE; + return FALSE; + } + + // In regular CTF and CTF_ONEFLAG, only two colors are legal + if (teamplay == TEAM_CTF || teamplay == TEAM_CTF_ONEFLAG) + if (color == TEAM1 || color == TEAM2) + return TRUE; + else + return FALSE; + + // In ALT CTF, three colors are legal + if (teamplay == TEAM_CTF_ALT) + if (color == TEAM1 || color == TEAM2 || color == TEAM3) + return TRUE; + else + return FALSE; + + // dunno what teamplay we're in, let'em all be ok + return TRUE; +}; + +/* TeamSetSkin - set the skin of the player model to the + apropriate skin based on team and teamplay settings. +*/ +void() TeamSetSkin = +{ + self.skin = 0; + if (deathmatch) + { + if (teamplay >= TEAM_CTF) + self.skin = 1; + } +/* + if ( deathmatch ) + { + if ( teamplay >= TEAM_CTF ) + { +// if (self.team == TEAM3) +// self.skin = 2; +// else + self.skin = 1; + } + if ( teamplay < TEAM_CTF ) + self.skin = 1; + else + { + if (self.team == TEAM1) + self.skin = 2; + else if (self.team == TEAM2) + self.skin = 3; + else if (self.team == TEAM3) + self.skin = 4; + } + } +*/ +}; + +/* +================== +TeamCheckTeam + +Check if the team self is on is legal, and put self in a legal team if not. +================== +*/ +void() TeamCheckTeam = +{ + local float team1count; + local float team2count; + local float team3count; + local float newcolor; + local float t; + local entity p; + local string n; + + if (self.steam >= 0 || teamplay < TEAM_CTF) { + if(TeamColorIsLegal(self.team)) { + self.steam = self.team; + TeamSetSkin(); + return; + } + } + + // Assign the player to a team. + + // Sum the players on all the teams. + team1count = 0; + team2count = 0; + team3count = 0; + + p = find (world, classname, "player"); + + while(p) { + if (p != self) { + if (p.steam == TEAM1) + team1count = team1count + 1; + else if (p.steam == TEAM2) + team2count = team2count + 1; + else if (p.steam == TEAM3) + team3count = team3count + 1; + } + p = find(p, classname, "player"); + } + + // Find the team with the least players. + newcolor = TEAM1; + t = team1count; + + if (team2count < t || (team2count == t && random() < 0.5)) { + newcolor = TEAM2; + t = team2count; + } + + // in CTF_ALT, there's three teams + team3count = team3count * 2; // grey team only gets half as many players + if (teamplay == TEAM_CTF_ALT && team3count < t) { + newcolor = TEAM3; + t = team3count; + } + + // Put the player on a the new team. + self.ctf_flags = self.ctf_flags | CTF_FLAG_STUFF_COLOR; + + n = GetCTFTeam(newcolor); + sprint(self, "$qc_assigned_team", n); + + self.steam = newcolor; // Remember what team we're on + self.team = newcolor; + TeamSetSkin(); +}; + +/* Check for team changing and perform whatever actions are neccessary. */ +void() TeamCheckLock = +{ + local float n; + local string s; + + if (!deathmatch || teamplay < TEAM_CTF) { + // all colors are legal, no force + self.steam = self.team; + return; + } + + if (self.ctf_flags & CTF_FLAG_STUFF_COLOR) { + self.ctf_flags = self.ctf_flags - CTF_FLAG_STUFF_COLOR; + stuffcmd(self, "color "); + n = self.steam - 1; + s = ftos(n); + stuffcmd(self, s); + stuffcmd(self, "\n"); + TeamSetSkin(); + return; + } + + if (!TeamColorIsLegal(self.team) && self.team == self.steam) + self.steam = -1; // full reset + + // Check to see if the player has changed colors + if (self.team != self.steam) { + if (self.steam >= 0) { + if (TeamColorIsLegal(self.steam)) { + + if (!(cvar("gamecfg") & GAMECFG_ALLOW_CHG)) { + // changing teams sucks, kill him + + // if he has tried to change teams several + // times, kick him off the server. + if (self.suicide_count > 3) { + sprint(self, "$qc_color_games"); + stuffcmd(self, "disconnect\n"); + bprint("$qc_death_color_sense", self.netname); + } + // case base respawn + if (self.ctf_killed != 1) + self.ctf_killed = 2; + T_Damage(self,self,self,1000); // Kill the player + // trying to change teams counts as a suicide + self.suicide_count = self.suicide_count + 1; + + sprint(self, "$qc_cannot_change_teams"); + stuffcmd(self, "color "); + n = self.steam - 1; + s = ftos(n); + stuffcmd(self, s); + stuffcmd(self, "\n"); + self.team = self.steam; + return; + } + } else { + // If we're on an illegal team, force a change. + self.steam = -50; + } + } + + if (self.steam > 0) { + // case base respawn + if (self.ctf_killed != 1) + self.ctf_killed = 2; + T_Damage(self,self,self,1000); // Kill the player + } + self.frags = 0; // Zero out frags + TeamCheckTeam(); // re-assignment + } +}; + +/* Toss out a backpack containing some ammo from your current weapon, +and any weapons you don't have. +*/ +void() TossBackpack = +{ + local entity item; + + // If we don't have any ammo, return + if(self.currentammo <= 0) + return; + + // only valid in teamplay modes + if (teamplay < 1) + return; + + item = spawn(); + + // See if you have the Shotgun or Super Shotgun on + if ((self.weapon == IT_SHOTGUN) || (self.weapon == IT_SUPER_SHOTGUN)) { + if( self.ammo_shells1 >= 20 ) { + item.ammo_shells1 = 20; + self.ammo_shells1 = self.ammo_shells1 - 20; + } + else + { + item.ammo_shells1 = self.ammo_shells1; + self.ammo_shells1 = 0; + } + } + + // See if you have neither the Shotgun or Super Shotgun + if ( !(self.items & IT_SHOTGUN) && !(self.items & IT_SUPER_SHOTGUN)) { + if( self.ammo_shells1 >= 20 ) { + item.ammo_shells1 = 20; + self.ammo_shells1 = self.ammo_shells1 - 20; + } else { + item.ammo_shells1 = self.ammo_shells1; + self.ammo_shells1 = 0; + } + } + + // See if we are using a nailgun + if ((self.weapon == IT_NAILGUN) || (self.weapon == IT_SUPER_NAILGUN) ) { + if( self.ammo_nails1 >= 20 ) { + item.ammo_nails1 = 20; + self.ammo_nails1 = self.ammo_nails1 - 20; + } else { + item.ammo_nails1 = self.ammo_nails1; + self.ammo_nails1 = 0; + } + } + + // See if we are using the lava nailguns + if ((self.weapon == IT_LAVA_NAILGUN) || (self.weapon == IT_LAVA_SUPER_NAILGUN) ) { + if( self.ammo_lava_nails >= 20 ) { + item.ammo_lava_nails = 20; + self.ammo_lava_nails = self.ammo_lava_nails - 20; + } else { + item.ammo_lava_nails = self.ammo_lava_nails; + self.ammo_lava_nails = 0; + } + } + + // Check to see if we have neither nailgun + if (!(self.items & IT_NAILGUN) && !(self.items & IT_SUPER_NAILGUN)) { + // put both regular and lava nails in + if (self.ammo_nails1 >= 20) { + item.ammo_nails1 = 20; + self.ammo_nails1 = self.ammo_nails1 - 20; + } else { + item.ammo_nails1 = self.ammo_nails1; + self.ammo_nails1 = 0; + } + if( self.ammo_lava_nails >= 20 ) { + item.ammo_lava_nails = 20; + self.ammo_lava_nails = self.ammo_lava_nails - 20; + } else { + item.ammo_lava_nails = self.ammo_lava_nails; + self.ammo_lava_nails = 0; + } + } + + // See if we are using a grenade or rocket launcher + if ((self.weapon == IT_GRENADE_LAUNCHER) || (self.weapon == IT_ROCKET_LAUNCHER)) { + if( self.ammo_rockets1 >= 10 ) { + item.ammo_rockets1 = 10; + self.ammo_rockets1 = self.ammo_rockets1 - 10; + } else { + item.ammo_rockets1 = self.ammo_rockets1; + self.ammo_rockets1 = 0; + } + } + + // See if we are using the multi grenade or rocket launcher + if ((self.weapon == IT_MULTI_GRENADE) || (self.weapon == IT_MULTI_ROCKET)) { + if (self.ammo_multi_rockets >= 10 ) { + item.ammo_multi_rockets = 10; + self.ammo_multi_rockets = self.ammo_multi_rockets - 10; + } else { + item.ammo_multi_rockets = self.ammo_multi_rockets; + self.ammo_multi_rockets = 0; + } + } + + // See if we have neither the Grenade or rocket launcher + if ( !(self.items & IT_GRENADE_LAUNCHER) && !(self.items & IT_ROCKET_LAUNCHER) ) { + if( self.ammo_rockets1 >= 10 ) { + item.ammo_rockets1 = 10; + self.ammo_rockets1 = self.ammo_rockets1 - 10; + } else { + item.ammo_rockets1 = self.ammo_rockets1; + self.ammo_rockets1 = 0; + } + if (self.ammo_multi_rockets >= 10 ) { + item.ammo_multi_rockets = 10; + self.ammo_multi_rockets = self.ammo_multi_rockets - 10; + } else { + item.ammo_multi_rockets = self.ammo_multi_rockets; + self.ammo_multi_rockets = 0; + } + } + + // See if we're using the lightning gun + if (self.weapon == IT_LIGHTNING) { + if( self.ammo_cells1 >= 20 ) { + item.ammo_cells1 = 20; + self.ammo_cells1 = self.ammo_cells1 - 20; + } else { + item.ammo_cells1 = self.ammo_cells1; + self.ammo_cells1 = 0; + } + } + + // see if we are using the plasma gun + if (self.weapon == IT_PLASMA_GUN) { + if( self.ammo_plasma >= 10 ) { + item.ammo_plasma = 10; + self.ammo_plasma = self.ammo_plasma - 10; + } else { + item.ammo_plasma = self.ammo_plasma; + self.ammo_plasma = 0; + } + } + + // see if we don't have the lightning gun + if (!(self.items & IT_LIGHTNING)) { + if( self.ammo_cells1 >= 20 ) { + item.ammo_cells1 = 20; + self.ammo_cells1 = self.ammo_cells1 - 20; + } else { + item.ammo_cells1 = self.ammo_cells1; + self.ammo_cells1 = 0; + } + if( self.ammo_plasma >= 10 ) { + item.ammo_plasma = 10; + self.ammo_plasma = self.ammo_plasma - 10; + } else { + item.ammo_plasma = self.ammo_plasma; + self.ammo_plasma = 0; + } + } + + if (item.ammo_shells1 + item.ammo_nails1 + item.ammo_lava_nails + + item.ammo_rockets1 + item.ammo_multi_rockets + item.ammo_cells1 + + item.ammo_plasma == 0) { + sprint(self, "$qc_no_ammo_available"); + remove(item); + return; + } + + item.owner = self; + makevectors(self.v_angle); + + setorigin(item, self.origin + '0 0 16'); + item.velocity = aim(self, 1000); + item.velocity = item.velocity * 500; + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_BOUNCE; + + setmodel (item, "progs/backpack.mdl"); + setsize(item, '-16 -16 0', '16 16 56'); + item.touch = BackpackTouch; + item.nextthink = time + 120; // remove after 2 minutes + item.think = SUB_Remove; + + W_SetCurrentAmmo(); +}; + +void() Team_weapon_touch = +{ + local float hadammo, best, new, old; + local entity stemp; + + if (!(other.flags & FL_CLIENT)) + return; + // Don't let the owner pick up his own weapon for a second. + if ((other == self.owner) && ((self.nextthink - time) > 119)) + return; + +// if the player was using his best weapon, change up to the new one if better + stemp = self; + self = other; + best = W_BestWeapon(); + self = stemp; + + if (self.classname == "weapon_nailgun") + new = IT_NAILGUN; + else if (self.classname == "weapon_supernailgun") + new = IT_SUPER_NAILGUN; + else if (self.classname == "weapon_supershotgun") + new = IT_SUPER_SHOTGUN; + else if (self.classname == "weapon_rocketlauncher") + new = IT_ROCKET_LAUNCHER; + else if (self.classname == "weapon_grenadelauncher") + new = IT_GRENADE_LAUNCHER; + else if (self.classname == "weapon_lightning") + new = IT_LIGHTNING; + else + objerror ("Team_weapon_touch: unknown classname"); + + sprint(other, "$qc_got_item", self.netname); +// weapon touch sound + sound (other, CHAN_ITEM, "weapons/pkup.wav", 1, ATTN_NORM); + stuffcmd (other, "bf\n"); + + bound_other_ammo (); + +// change to the weapon + old = other.items; + other.items = other.items | new; + + remove(self); + self = other; + + if (!deathmatch) + self.weapon = new; + else + Deathmatch_Weapon (old, new); + + EnableComboWeapons(self); + UpdateAmmoCounts(self); + + W_SetCurrentAmmo(); + + // dropped weapon doesn't target, so no need to call UseTargets +}; + +void() TossWeapon = +{ + local entity item; + + if (deathmatch != 1) + return; // only in deathmatch 1 + + // only valid in teamplay modes + if (teamplay < 1) + return; + + if((self.weapon == IT_AXE) || (self.weapon == IT_SHOTGUN) || + (self.weapon == IT_GRAPPLE)) + return; + + item = spawn(); + item.owner = self; + makevectors(self.v_angle); + item.weapon = 0; + + setorigin(item, self.origin + '0 0 16'); + item.velocity = aim(self, 1000); + item.velocity = item.velocity * 500; + item.flags = FL_ITEM; + item.solid = SOLID_TRIGGER; + item.movetype = MOVETYPE_BOUNCE; + + if(self.weapon == IT_SUPER_SHOTGUN) + { + setmodel (item, "progs/g_shot.mdl"); + item.weapon = IT_SUPER_SHOTGUN; + item.netname = "$qc_double_shotgun"; + item.classname = "weapon_supershotgun"; + self.items = self.items - IT_SUPER_SHOTGUN; + } + + if( self.weapon == IT_NAILGUN || self.weapon == IT_LAVA_NAILGUN ) + { + setmodel (item, "progs/g_nail.mdl"); + item.weapon = IT_NAILGUN; + item.netname = "$qc_nailgun"; + item.classname = "weapon_nailgun"; + self.items = self.items - (self.items & (IT_NAILGUN | IT_LAVA_NAILGUN)); + } + + if( self.weapon == IT_SUPER_NAILGUN || self.weapon == IT_LAVA_SUPER_NAILGUN) + { + setmodel (item, "progs/g_nail2.mdl"); + item.weapon = IT_SUPER_NAILGUN; + item.netname = "$qc_super_nailgun"; + item.classname = "weapon_supernailgun"; + self.items = self.items - (self.items & (IT_SUPER_NAILGUN | IT_LAVA_SUPER_NAILGUN)); + } + + if( self.weapon == IT_GRENADE_LAUNCHER || self.weapon == IT_MULTI_GRENADE) + { + setmodel (item, "progs/g_rock.mdl"); + item.weapon = IT_GRENADE_LAUNCHER; + item.netname = "$qc_grenade_launcher"; + item.classname = "weapon_grenadelauncher"; + self.items = self.items - (self.items & (IT_GRENADE_LAUNCHER | IT_MULTI_GRENADE)); + } + + if( self.weapon == IT_ROCKET_LAUNCHER || self.weapon == IT_MULTI_ROCKET) + { + setmodel (item, "progs/g_rock2.mdl"); + item.weapon = IT_ROCKET_LAUNCHER; + item.netname = "$qc_rocket_launcher"; + item.classname = "weapon_rocketlauncher"; + self.items = self.items - (self.items & (IT_ROCKET_LAUNCHER | IT_MULTI_ROCKET)); + } + + if( self.weapon == IT_LIGHTNING || self.weapon == IT_PLASMA_GUN) + { + setmodel (item, "progs/g_light.mdl"); + item.weapon = IT_LIGHTNING; + item.netname = "$qc_thunderbolt"; + item.classname = "weapon_lightning"; + self.items = self.items - (self.items & (IT_LIGHTNING | IT_PLASMA_GUN)); + } + setsize(item, '-16 -16 0', '16 16 56'); + item.touch = Team_weapon_touch; + item.think = SUB_Remove; + item.nextthink = time + 120; + + self.weapon = W_BestWeapon(); + W_SetCurrentAmmo(); +}; + +void(entity flg) RegenFlag = +{ + flg.movetype = MOVETYPE_TOSS; + flg.solid = SOLID_TRIGGER; + sound (flg, CHAN_VOICE, "items/itembk2.wav", 1, ATTN_NORM); // play respawn sound + setorigin(flg, flg.oldorigin); + flg.angles = flg.mangle; + flg.cnt = FLAG_AT_BASE; // it's at home base + flg.owner = world; +}; + +void(entity flg) TeamReturnFlag = +{ + local entity p; + local string n; + + RegenFlag(flg); + + p = find(world, classname, "player"); + while (p != world) { + if (teamplay == TEAM_CTF_ONEFLAG) // one flag mode? + centerprint(p, "$qc_flag_returned"); + else { + if (teamplay == TEAM_CTF_ALT) { + if (flg.team == TEAM1) + centerprint(p, "��� flag has been returned to base!\n"); + else if (flg.team == TEAM2) + centerprint(p, "���� flag has been returned to base!\n"); + else + centerprint(p, "$qc_some_flag_returned_base"); + } else { + if (p.steam != flg.team) + centerprint(p, "$qc_enemy_flag_returned_base"); + else if (p.steam == flg.team) + centerprint(p, "$qc_your_flag_returned_base"); + } + } + p = find(p, classname, "player"); + } +}; + +void () TeamRegenFlags = +{ + local entity f; + + if (teamplay == TEAM_CTF_ONEFLAG) { + f = find(world, classname, "item_flag"); + if (f != world) + RegenFlag(f); + return; + } + + f = find(world, classname, "item_flag_team1"); + if (f != world) + RegenFlag(f); + f = find(world, classname, "item_flag_team2"); + if (f != world) + RegenFlag(f); +}; + +void(entity flg) TeamDropFlag = +{ + local entity item, f, oself; + local entity p; + + p = flg.owner; + + bprint(p.netname); + if (teamplay == TEAM_CTF_ONEFLAG) + bprint(" ���� the flag!\n"); + else { + if (flg.team == TEAM1) + bprint(" ���� the ��� flag!\n"); // red + else + bprint(" ���� the ���� flag!\n"); // blue + } + + setorigin(flg, p.origin - '0 0 24'); + flg.cnt = FLAG_DROPPED; + flg.velocity_z = 300; + flg.velocity_x = 0; + flg.velocity_y = 0; + flg.flags = FL_ITEM | FL_OBJECTIVE; + flg.solid = SOLID_TRIGGER; + flg.movetype = MOVETYPE_TOSS; + setsize(flg, '-16 -16 0', '16 16 74'); + // return it after so long + flg.super_time = time + CTF_FLAG_RETURN_TIME; +}; + +void(entity player) TeamDropFlagOfPlayer = +{ + local string kn; + local entity e; + + if (teamplay == TEAM_CTF_ONEFLAG && (player.ctf_flags & CTF_FLAG_FLAG)) + kn = "item_flag"; + else if (player.ctf_flags & CTF_FLAG_TEAM1) + kn = "item_flag_team1"; + else if (player.ctf_flags & CTF_FLAG_TEAM2) + kn = "item_flag_team2"; + else + return; // doesn't have a flg + player.ctf_flags = player.ctf_flags - + (player.ctf_flags & (CTF_FLAG_FLAG | CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2)); + e = find(world, classname, kn); + if (e != world) + TeamDropFlag(e); +}; + +// A flag was touched. In one flag mode, the player always just picks it up +// (the flagbase models at each base will cause the score). In two flag mode, +// it could be the guy returning his flag, or the guy getting the enemy flag +// Three team mode gets more complicated as the third team touches the +// flagbase to score +void() TeamFlagTouch = +{ + local entity p, oself; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + + if (other.team != other.steam) + return; // something is fishy, somebody is playing with colors + + if (self.cnt == FLAG_CARRIED) + return; // huh? + + // Ok, first up, let's do it for one flag mode + if (teamplay == TEAM_CTF_ONEFLAG) { + // in one flag mode, we always pick up the flag. The touch of the + // flagbase entities does the scoring + bprint(other.netname); + bprint(" ��� the flag!\n"); + if (CTF_FLAG_BONUS) + other.frags = other.frags + CTF_FLAG_BONUS; + //centerprint(other, "$qc_got_flag_enemy_base"); + centerprint(other, "��� ��� ��� ����\n\n���� �� �� ����� ����\n"); + + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + + other.ctf_flags = other.ctf_flags | CTF_FLAG_FLAG; + other.items = other.items | self.items; + other.ctf_flagsince = time; + + // pick up the flag + self.cnt = FLAG_CARRIED; + self.movetype = MOVETYPE_NOCLIP; + self.solid = SOLID_NOT; + self.owner = other; + + p = find(world, classname, "player"); + while (p != world) { + if (p != other) + centerprint(p, "$qc_flag_taken"); + p = find(p, classname, "player"); + } + return; + } + + // Regular and Alt CTF mode + if (teamplay != TEAM_CTF && teamplay != TEAM_CTF_ALT) + return; // odd, but ignore it + + if (self.team == other.steam) { + // same team, if the flag is *not* at the base, return + // it to base. we overload the 'cnt' field for this + if (self.cnt == FLAG_AT_BASE) { + // the flag is at home base. if the player has the enemy + // flag, he's just won! + if ((self.team == TEAM1 && (other.ctf_flags & CTF_FLAG_TEAM2)) || + (self.team == TEAM2 && (other.ctf_flags & CTF_FLAG_TEAM1))) { + bprint(other.netname); + if (other.team == TEAM1) + bprint(" �������� the ���� flag!\n"); // blue + else + bprint(" �������� the ��� flag!\n"); // red + other.items = other.items - (other.items & (IT_KEY1 | IT_KEY2)); + + sound (other, CHAN_VOICE, "misc/flagcap.wav", 1, ATTN_NONE); + + // other gets another 10 frag bonus + other.frags = other.frags + CTF_CAPTURE_BONUS; + + // Ok, let's do the player loop, hand out the bonuses + p = find(world, classname, "player"); + while (p != world) { + self = p; + if (self.team == other.team && self != other) + self.frags = self.frags + CTF_TEAM_BONUS; + if (self.team != other.team) { + if (self.team == TEAM3) + if (other.team == TEAM1) + centerprint(self, "���� flag was captured!\n"); + else + centerprint(self, "��� flag was captured!\n"); + else + centerprint(self, "$qc_your_flag_captured"); + // reset the last_hurt_carrier variable in all enemy players, so that you don't get + // bonuses for defending the flag carrier if the flag carrier has already + // completed a capture + self.ctf_lasthurtcarrier = -5; + } else if (self.team == other.team) { + // done to all players on the capturing team + centerprint(self, "$qc_your_team_captured"); + // award extra points for capture assists + if (self.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > time) { + bprint(self.netname); + if (self.team == TEAM1) + bprint(" gets an assist for returning the ��� flag!\n"); + else + bprint(" gets an assist for returning the ���� flag!\n"); + self.frags = self.frags + CTF_RETURN_FLAG_ASSIST_BONUS; + } + if (self.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > time) { + bprint(self.netname); + bprint(" gets an assist for fragging the flag carrier!\n"); + self.frags = self.frags + CTF_FRAG_CARRIER_ASSIST_BONUS; + } + } + self.ctf_flags = self.ctf_flags - (self.ctf_flags & + (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2)); + p = find(p, classname, "player"); + } + // respawn flags + TeamRegenFlags(); + return; + } + return; // its at home base already + } + // hey, its not home. return it by teleporting it back + bprint(other.netname); + if (other.team == TEAM1) + bprint(" �������� the ��� flag!\n"); // red + else + bprint(" �������� the ���� flag!\n"); // blue + other.frags = other.frags + CTF_RECOVERY_BONUS; + other.ctf_lastreturnedflag = time; + sound (other, CHAN_ITEM, self.noise1, 1, ATTN_NORM); + TeamReturnFlag(self); + return; + } + + // if we have any flags, leave now + if (other.ctf_flags & (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2)) + return; + + // hey, its not our flag, pick it up + bprint(other.netname); + if (self.team == TEAM1) + bprint(" ��� the ��� flag!\n"); // red + else + bprint(" ��� the ���� flag!\n"); // blue + other.frags = other.frags + CTF_FLAG_BONUS; + + // if in three team, messages are a little different + if (other.team == TEAM3) { +// centerprint(other, "$qc_got_flag_other"); + centerprint(other, "��� ��� ��� ����� ����\n\n������ �� �� ��� ����� ����\n"); + } else { +// centerprint(other, "$qc_got_flag_return_base"); + centerprint(other, "��� ��� ��� ����� ����\n\n������ �� ����\n"); + } + sound (other, CHAN_ITEM, self.noise, 1, ATTN_NORM); + + if (self.team == TEAM1) + other.ctf_flags = other.ctf_flags | CTF_FLAG_TEAM1; + else + other.ctf_flags = other.ctf_flags | CTF_FLAG_TEAM2; + other.items = other.items | self.items; + + other.ctf_flagsince = time; + + // pick up the flag + self.cnt = FLAG_CARRIED; + self.movetype = MOVETYPE_NOCLIP; + self.solid = SOLID_NOT; + self.owner = other; + + // PGM Fix - 03/06/97 Made it work right in three team. + p = find(world, classname, "player"); + while (p != world) + { + if (p != other) + { + if (p.steam == self.team) + { + centerprint(p, "$qc_your_flag_taken"); + } + else if (p.steam == other.team) + { + if (self.team == TEAM1) + centerprint(p, "Your team has the ��� flag!\n"); // Red Flag + else + centerprint(p, "Your team has the ���� flag!\n"); // Blue Flag + } + else + { + if (self.team == TEAM1) + if (other.steam == TEAM2) + centerprint(p, "���� team has the ��� flag!\n"); // Blue has red + else // must be team3 + centerprint(p, "���� team has the ��� flag!\n"); // grey has red + else + if (other.steam == TEAM1) + centerprint(p, "��� team has the ���� flag!\n"); // red has blue + else // must be team3 + centerprint(p, "���� team has the ���� flag!\n");// grey has blue + } + } + p = find(p, classname, "player"); + } +}; + +// A flagbase was touched. In one flag mode, this is how a capture is made. +// in three team mode, only team3 touches this. In regular CTF, this is +// ignored +void() TeamFlagBaseTouch = +{ + local entity p, oself, f; + + if (other.classname != "player") + return; + if (other.health <= 0) + return; + if (other.team != other.steam) + return; // something is fishy, somebody is playing with colors + + // Ok, first up, let's do it for one flag mode + if (teamplay == TEAM_CTF_ONEFLAG) { + // ok, if they guy touching it has the flag and this is his + // base, capture! + if (((self.team == TEAM1 && other.steam == TEAM2) || + (self.team == TEAM2 && other.steam == TEAM1)) && + (other.ctf_flags & CTF_FLAG_FLAG)) { + // he just touched enemy base, capture it + bprint(other.netname); + bprint(" �������� the flag!\n"); // blue + other.items = other.items - (other.items & (IT_KEY1 | IT_KEY2)); + sound (other, CHAN_VOICE, "misc/flagcap.wav", 1, ATTN_NONE); + + // other gets another 10 frag bonus + other.frags = other.frags + CTF_CAPTURE_BONUS; + + // Ok, let's do the player loop, hand out the bonuses + p = find(world, classname, "player"); + while (p != world) { + self = p; + if (self.team == other.team && self != other) + self.frags = self.frags + CTF_TEAM_BONUS; + if (self.team != other.team) { + self.ctf_lasthurtcarrier = -5; + } else if (self.team == other.team) { + // award extra points for capture assists + if (self.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > time) { + bprint(self.netname); + bprint(" gets an assist for fragging the flag carrier!\n"); + self.frags = self.frags + CTF_FRAG_CARRIER_ASSIST_BONUS; + } + } + self.ctf_flags = self.ctf_flags - (self.ctf_flags & CTF_FLAG_FLAG); + p = find(p, classname, "player"); + } + // respawn flags + TeamRegenFlags(); + return; + } + } + + // in three team mode (TEAM_CTF_ALT) and we're on team3, see if we capture + if (teamplay != TEAM_CTF_ALT || other.steam != TEAM3) + return; + + if (((other.ctf_flags & CTF_FLAG_TEAM1) && (self.team == TEAM2)) || + ((other.ctf_flags & CTF_FLAG_TEAM2) && (self.team == TEAM1))) { + // third team captured + + bprint(other.netname); + if (self.team == TEAM1) + bprint(" �������� the ���� flag!\n"); // blue + else + bprint(" �������� the ��� flag!\n"); // red + other.items = other.items - (other.items & (IT_KEY1 | IT_KEY2)); + + sound (other, CHAN_VOICE, "misc/flagcap.wav", 1, ATTN_NONE); + + // other gets another 10 frag bonus + other.frags = other.frags + CTF_ALT_CAPTURE_BONUS; + + // Ok, let's do the player loop, hand out the bonuses + p = find(world, classname, "player"); + while (p != world) { + self = p; + if (self.team == other.team && self != other) + self.frags = self.frags + CTF_ALT_TEAM_BONUS; + if (self.team != other.team) { + if ((other.ctf_flags & CTF_FLAG_TEAM1) && self.team == TEAM1) + centerprint(self, "$qc_your_flag_captured"); + else if ((other.ctf_flags & CTF_FLAG_TEAM2) && self.team == TEAM2) + centerprint(self, "$qc_enemy_flag_captured"); + // reset the last_hurt_carrier variable in all enemy players, so that you don't get + // bonuses for defending the flag carrier if the flag carrier has already + // completed a capture + self.ctf_lasthurtcarrier = -5; + } else if (self.team == other.team) { + // done to all players on the capturing team + centerprint(self, "$qc_your_team_captured"); + } + p = find(p, classname, "player"); + } + // respawn flags + if (other.ctf_flags & CTF_FLAG_TEAM1) { + f = find(world, classname, "item_flag_team1"); + if (f != world) + RegenFlag(f); + } else { // must be flag2 + f = find(world, classname, "item_flag_team2"); + if (f != world) + RegenFlag(f); + } + other.ctf_flags = other.ctf_flags - (other.ctf_flags & + (CTF_FLAG_TEAM1 | CTF_FLAG_TEAM2)); + return; + } +}; + + +void() TeamFlagThink = +{ + local entity e; + local vector v; + local float f; + local string s; + + self.nextthink = time + 0.1; + + if (self.cnt == FLAG_AT_BASE) + return; // just sitting around waiting to be picked up + + if (self.cnt == FLAG_DROPPED) { + if (time - self.super_time > CTF_FLAG_RETURN_TIME) + TeamReturnFlag(self); + return; + } + + if (self.cnt != FLAG_CARRIED) + objerror("Flag in invalid state\n"); + + e = self.owner; + if (e.classname != "player" || e.deadflag) { + TeamDropFlag(self); + return; + } + + if (teamplay == TEAM_CTF_ONEFLAG && !(e.ctf_flags & CTF_FLAG_FLAG)) { + TeamDropFlag(self); + return; + } + // must be TEAM_CTF or TEAM_CTF_ALT + if ((!(e.ctf_flags & CTF_FLAG_TEAM1) && self.team == TEAM1) || + (!(e.ctf_flags & CTF_FLAG_TEAM2) && self.team == TEAM2)) { + TeamDropFlag(self); + return; + } + + makevectors (e.angles); + v = v_forward; + v_z = (-1) * v_z; // reverse z component + + f = 14; + if (self.owner.frame >= 29 && self.owner.frame <= 40) { + if (self.owner.frame >= 29 && self.owner.frame <= 34) { //axpain + if (self.owner.frame == 29) f = f + 2; + else if (self.owner.frame == 30) f = f + 8; + else if (self.owner.frame == 31) f = f + 12; + else if (self.owner.frame == 32) f = f + 11; + else if (self.owner.frame == 33) f = f + 10; + else if (self.owner.frame == 34) f = f + 4; + } else if (self.owner.frame >= 35 && self.owner.frame <= 40) { // pain + if (self.owner.frame == 35) f = f + 2; + else if (self.owner.frame == 36) f = f + 10; + else if (self.owner.frame == 37) f = f + 10; + else if (self.owner.frame == 38) f = f + 8; + else if (self.owner.frame == 39) f = f + 4; + else if (self.owner.frame == 40) f = f + 2; + } + } else if (self.owner.frame >= 103 && self.owner.frame <= 118) { + if (self.owner.frame >= 103 && self.owner.frame <= 104) f = f + 6; //nailattack + else if (self.owner.frame >= 105 && self.owner.frame <= 106) f = f + 6; //light + else if (self.owner.frame >= 107 && self.owner.frame <= 112) f = f + 7; //rocketattack + else if (self.owner.frame >= 112 && self.owner.frame <= 118) f = f + 7; //shotattack + } + self.origin = e.origin + '0 0 -16' - f*v + v_right * 22; + self.angles = e.angles + '0 0 -45'; + setorigin (self, self.origin); + self.nextthink = time + 0.01; +}; + +void() TeamFlagStatusReport = +{ + local entity flag1, flag2, p; + local string n; + + if (!deathmatch) + return; + + if (teamplay != TEAM_CTF && teamplay != TEAM_CTF_ONEFLAG && + teamplay != TEAM_CTF_ALT) { + sprint(self, "$qc_ctf_disabled"); + return; + } + + if (teamplay == TEAM_CTF_ONEFLAG) { + flag1 = find (world, classname, "item_flag"); + if (flag1 == world) + sprint(self, "$qc_flag_missing"); + else if (flag1.cnt == FLAG_AT_BASE) + sprint(self, "$qc_flag_at_base"); + else if (flag1.cnt == FLAG_DROPPED) + sprint(self, "$qc_flag_lying_about"); + else if (flag1.cnt == FLAG_CARRIED) { + if (flag1.owner == self) + sprint(self, "$qc_you_have_flag"); + else { + sprint(self, flag1.owner.netname); + n = GetCTFTeam(flag1.owner.steam); + sprint(self, " of the "); + sprint(self, n); + sprint(self, " team has the flag!\n"); + } + } else + sprint(self, "$qc_flag_screwed_up"); + return; + } + + // normal CTF + if (teamplay == TEAM_CTF) { + // Find the flags at home base + flag1 = find (world,classname, "item_flag_team1"); + flag2 = find (world,classname, "item_flag_team2"); + + // If on team 2 switch meanings of flags + if (self.team != TEAM1) { + p = flag1; + flag1 = flag2; + flag2 = p; + } + + if (flag1 != world && flag1.cnt == FLAG_CARRIED) { + sprint(self, flag1.owner.netname); + sprint(self, " has your flag. "); + } else { + sprint(self, "Your flag is "); + if (flag1 == world) + sprint(self, "missing!\n"); + if (flag1.cnt == FLAG_AT_BASE) + sprint(self, "in your base.\n"); + else if (flag1.cnt == FLAG_DROPPED) + sprint(self, "lying about.\n"); + else + sprint(self, " corrupt.\n"); + } + + if (flag2 != world && flag2.cnt == FLAG_CARRIED) { + if (self == flag2.owner) + sprint(self, "$qc_you_have_enemy_flag"); + else { + sprint(self, flag2.owner.netname); + sprint(self, " has the enemy flag.\n"); + } + } else { + sprint(self, "The enemy flag is "); + if (flag2 == world) + sprint(self, "missing!\n"); + if (flag2.cnt == FLAG_AT_BASE) + sprint(self, "in their base.\n"); + else if (flag2.cnt == FLAG_DROPPED) + sprint(self, "lying about.\n"); + else + sprint(self, " corrupt.\n"); + } + } + + // three team CTF + if (teamplay == TEAM_CTF_ALT) { + // Find the flags at home base + flag1 = find (world,classname, "item_flag_team1"); + flag2 = find (world,classname, "item_flag_team2"); + + if (flag1 != world && flag1.cnt == FLAG_CARRIED) { + if (flag1.owner == self) + sprint(self, "You have the ��� flag!\n"); + else { + sprint(self, flag1.owner.netname); + n = GetCTFTeam(flag1.owner.steam); + sprint(self, " of the "); + sprint(self, n); + sprint(self, " team has the ��� flag.\n"); + } + } else { + if (self.steam == flag1.team) + sprint(self, "Your flag is "); + else + sprint(self, "��� flag is "); + if (flag1 == world) + sprint(self, "missing!\n"); + if (flag1.cnt == FLAG_AT_BASE) + sprint(self, "at base.\n"); + else if (flag1.cnt == FLAG_DROPPED) + sprint(self, "lying about.\n"); + else + sprint(self, " corrupt.\n"); + } + + if (flag2 != world && flag2.cnt == FLAG_CARRIED) { + if (flag2.owner == self) + sprint(self, "You have the ���� flag.\n"); + else { + sprint(self, flag2.owner.netname); + n = GetCTFTeam(flag2.owner.steam); + sprint(self, " of the "); + sprint(self, n); + sprint(self, " team has the ���� flag.\n"); + } + } else { + if (self.steam == flag1.team) + sprint(self, "Your flag is "); + else + sprint(self, "���� flag is "); + if (flag2 == world) + sprint(self, "missing!\n"); + if (flag2.cnt == FLAG_AT_BASE) + sprint(self, "at base.\n"); + else if (flag2.cnt == FLAG_DROPPED) + sprint(self, "lying about.\n"); + else + sprint(self, " corrupt.\n"); + } + } +}; + +///////////////////////////////////////////////////////////////////////// + +$cd id1/models/flag +$base base +$skin skin + +void() place_flag = { + self.mdl = self.model; // so it can be restored on respawn + self.flags = FL_ITEM | FL_OBJECTIVE; // make extra wide + self.solid = SOLID_TRIGGER; + self.movetype = MOVETYPE_TOSS; + self.velocity = '0 0 0'; + self.origin_z = self.origin_z + 6; + self.think = TeamFlagThink; + self.touch = TeamFlagTouch; + self.nextthink = time + 0.1; + self.cnt = FLAG_AT_BASE; + self.mangle = self.angles; + self.effects = self.effects | EF_DIMLIGHT; + if (!droptofloor()) { + dprint ("Flag fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + return; + } + self.oldorigin = self.origin; // save for flag return +}; + +void(entity flg, string cname) place_flagbase = +{ + local entity oself; + + teamplay = cvar("teamplay"); + + oself = self; + self = spawn(); + self.classname = cname; + setorigin(self, flg.origin); + self.angles = flg.angles; + precache_model ("progs/ctfbase.mdl"); + setmodel (self, "progs/ctfbase.mdl"); + self.skin = flg.skin; + self.team = flg.team; +// setsize(self, '-8 -8 -4', '8 8 4'); + setsize(self, '-8 -8 0', '8 8 8'); + self.flags = FL_ITEM; // make extra wide + self.movetype = MOVETYPE_TOSS; + self.velocity = '0 0 0'; + self.origin_z = self.origin_z + 6; + if (teamplay == TEAM_CTF_ONEFLAG || teamplay == TEAM_CTF_ALT) { + self.solid = SOLID_TRIGGER; + self.touch = TeamFlagBaseTouch; + } else + self.solid = SOLID_NOT; + if (!droptofloor()) { + dprint ("Flagbase fell out of level at "); + dprint (vtos(self.origin)); + dprint ("\n"); + remove(self); + self = oself; + return; + } + self = oself; +}; + +// ZOID Capture the flag +/*QUAKED item_flag_team1 (0 .5 .8) (-8 -8 -32) (8 8 24) +red team flag +Only appears in CTF teamplay + +The flag waves 90 degrees off from the entity angle. If +you want the flag to point at 180, set then entity angle +to 90. +*/ +void() item_flag_team1 = +{ + if (!deathmatch) { + remove(self); + return; + } + + teamplay = cvar("teamplay"); + + if (teamplay == TEAM_CTF_ONEFLAG) { + precache_sound ("misc/flagtk.wav"); // flag taken + precache_sound ("misc/flagcap.wav"); // flag capture + self.skin = 0; + self.team = TEAM1; + place_flagbase(self, "item_flagbase_team1"); + remove(self); + return; + } + + if (teamplay == TEAM_CTF || teamplay == TEAM_CTF_ALT) { + self.team = TEAM1; + self.items = IT_KEY2; + precache_model ("progs/ctfmodel.mdl"); + setmodel (self, "progs/ctfmodel.mdl"); + self.skin = 0; + precache_sound ("misc/flagtk.wav"); // flag taken + precache_sound ("misc/flagcap.wav"); // flag capture + precache_sound ("misc/flagret.wav"); // flag return + self.noise = "misc/flagtk.wav"; + self.noise1 = "misc/flagret.wav"; + setsize(self, '-16 -16 0', '16 16 74'); + self.nextthink = time + 0.2; // items start after other solids + self.think = place_flag; + place_flagbase(self, "item_flagbase_team1"); + } else { + remove(self); // not teamplay or deathmatch + } +}; + +/*QUAKED item_flag_team2 (0 .5 .8) (-8 -8 -32) (8 8 24) +blue team flag +Only appears in CTF teamplay + +The flag waves 90 degrees off from the entity angle. If +you want the flag to point at 180, set then entity angle +to 90. +*/ +void() item_flag_team2 = +{ + if (!deathmatch) { + remove(self); + return; + } + + teamplay = cvar("teamplay"); + + if (teamplay == TEAM_CTF_ONEFLAG) { + precache_sound ("misc/flagtk.wav"); // flag taken + precache_sound ("misc/flagcap.wav"); // flag capture + self.team = TEAM2; + self.skin = 1; + place_flagbase(self, "item_flagbase_team2"); + remove(self); + return; + } + + if (teamplay == TEAM_CTF || teamplay == TEAM_CTF_ALT) { + self.team = TEAM2; + self.items = IT_KEY1; + precache_model ("progs/ctfmodel.mdl"); + setmodel (self, "progs/ctfmodel.mdl"); + self.skin = 1; + precache_sound ("misc/flagtk.wav"); // flag taken + precache_sound ("misc/flagcap.wav"); // flag capture + precache_sound ("misc/flagret.wav"); // flag return + self.noise = "misc/flagtk.wav"; + self.noise1 = "misc/flagret.wav"; + setsize(self, '-16 -16 0', '16 16 74'); + self.nextthink = time + 0.2; // items start after other solids + self.think = place_flag; + place_flagbase(self, "item_flagbase_team2"); + } else { + remove(self); // not teamplay or deathmatch + } +}; + +/*QUAKED item_flag (0 .5 .8) (-8 -8 -32) (8 8 24) +flag for OneTeam play. +Only appears in CTF teamplay + +The flag waves 90 degrees off from the entity angle. If +you want the flag to point at 180, set then entity angle +to 90. +*/ +void() item_flag = +{ + if (cvar("teamplay") != TEAM_CTF_ONEFLAG) { + remove(self); + return; + } + self.team = 0; // no team + self.items = IT_KEY1 | IT_KEY2; +// precache_model ("progs/flag.mdl"); +// setmodel (self, "progs/flag.mdl"); + precache_model ("progs/ctfmodel.mdl"); // PGM 01/18/97 + setmodel (self, "progs/ctfmodel.mdl"); // PGM 01/18/97 + self.skin = 2; + precache_sound ("misc/flagtk.wav"); // flag taken + precache_sound ("misc/flagcap.wav"); // flag capture + precache_sound ("misc/flagret.wav"); // flag return + self.noise = "misc/flagtk.wav"; + self.noise1 = "misc/flagret.wav"; + setsize(self, '-16 -16 0', '16 16 74'); + self.nextthink = time + 0.2; // items start after other solids + self.think = place_flag; + place_flagbase(self, "item_flagbase"); +}; + +/*QUAKED info_player_team1 (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for CTF games team 1. +*/ +void() info_player_team1 = +{ +}; + +/*QUAKED info_player_team2 (1 0 1) (-16 -16 -24) (16 16 24) +potential spawning position for CTF games team 2. +*/ +void() info_player_team2 = +{ +}; + +void() TeamSetUpdate = +{ + nextteamupdtime = time + CTF_UPDATE_TIME; +}; + +void() TeamCheckUpdate = +{ + local float total1, total2, total3; + local entity p; + local string s; + local string ts1, ts2, ts3; + + if (nextteamupdtime > time || teamplay < 1 || !deathmatch) + return; + + TeamSetUpdate(); + + // count up total + total1 = total2 = total3 = 0; + + ts1 = GetCTFTeam(TEAM1); + ts2 = GetCTFTeam(TEAM2); + ts3 = GetCTFTeam(TEAM3); + + if (teamplay == TEAM_CTF || teamplay == TEAM_CTF_ALT || teamplay == TEAM_CTF_ONEFLAG) { + p = find(world, classname, "player"); + while (p != world) { + if (p.steam == TEAM1) + total1 = total1 + p.frags; + else if (p.steam == TEAM2) + total2 = total2 + p.frags; + else if (p.steam == TEAM3) + total3 = total3 + p.frags; + p = find(p, classname, "player"); + } + + if (teamplay == TEAM_CTF || teamplay == TEAM_CTF_ONEFLAG) { + if (total1 > total2) { + bprint(ts1); + bprint(" team is leading by "); + s = ftos(total1 - total2); + bprint(s); + bprint(" points!\n"); + } else if (total1 < total2) { + bprint(ts2); + bprint(" team is leading by "); + s = ftos(total2 - total1); + bprint(s); + bprint(" points!\n"); + } else { + bprint(ts1); + bprint(" and "); + bprint(ts2); + bprint(" teams are tied with "); + s = ftos(total1); + bprint(s); + bprint(" points!\n"); + } + } else if (teamplay == TEAM_CTF_ALT) { + if (total1 > total2 && total1 > total3) { + bprint(ts1); + bprint(" team is leading by "); + if (total2 > total3) + s = ftos(total1 - total2); + else + s = ftos(total1 - total3); + bprint(s); + bprint(" points!\n"); + } else if (total2 > total1 && total2 > total3) { + bprint(ts2); + bprint(" team is leading by "); + if (total1 > total3) + s = ftos(total2 - total1); + else + s = ftos(total2 - total3); + bprint(s); + bprint(" points!\n"); + } else if (total3 > total1 && total3 > total2) { + bprint(ts3); + bprint(" team is leading by "); + if (total1 > total2) + s = ftos(total3 - total1); + else + s = ftos(total3 - total2); + bprint(s); + bprint(" points!\n"); + } else if (total1 == total2) { + bprint(ts1); + bprint(" and "); + bprint(ts2); + bprint(" teams are tied with "); + s = ftos(total1); + bprint(s); + bprint(" points!\n"); + } else if (total3 == total2) { + bprint(ts2); + bprint(" and "); + bprint(ts3); + bprint(" teams are tied with "); + s = ftos(total3); + bprint(s); + bprint(" points!\n"); + } else if (total3 == total1) { + bprint(ts1); + bprint(" and "); + bprint(ts3); + bprint(" teams are tied with "); + s = ftos(total1); + bprint(s); + bprint(" points!\n"); + } else { + bprint(ts1); + bprint(", "); + bprint(ts2); + bprint(" and "); + bprint(ts3); + bprint(" teams are tied with "); + s = ftos(total1); + bprint(s); + bprint(" points!\n"); + } + } + } +}; + +/*QUAKED func_ctf_wall (0 .5 .8) ? +This is just a solid wall if not inhibitted +Only appears in CTF teamplay +*/ +void() func_ctf_wall = +{ + teamplay = cvar("teamplay"); + if (teamplay == TEAM_CTF || teamplay == TEAM_CTF_ONEFLAG || + teamplay == TEAM_CTF_ALT) { + self.angles = '0 0 0'; + self.movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + self.solid = SOLID_BSP; + setmodel (self, self.model); + } else + remove(self); +}; + diff --git a/quakec_rogue/timemach.qc b/quakec_rogue/timemach.qc new file mode 100644 index 0000000..806e148 --- /dev/null +++ b/quakec_rogue/timemach.qc @@ -0,0 +1,242 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// timemach.qc +// pmack +// jan97 + +// ------------------------------------------------ +// time machine and related parts +// ------------------------------------------------ +void() time_chunk = +{ + local entity timeChunk; + + timeChunk = spawn(); + setmodel(timeChunk, "progs/timegib.mdl"); + timeChunk.solid = SOLID_NOT; + timeChunk.movetype = MOVETYPE_TOSS; + makevectors (self.owner.angles); + setorigin ( timeChunk, self.owner.origin + v_forward*84 - v_up * 136); + timeChunk.velocity = v_up * -50; + timeChunk.avelocity = '300 300 300'; + timeChunk.angles = self.owner.angles; + + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NONE); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, timeChunk.origin_x); + WriteCoord (MSG_BROADCAST, timeChunk.origin_y); + WriteCoord (MSG_BROADCAST, timeChunk.origin_z); + + self.owner.frame = 1; + + timeChunk.think = SUB_Remove; + timeChunk.nextthink = time + 5; +}; + +void() time_stop_shake = +{ + SUB_UseTargets(); + remove (self); +}; + +void() time_boom = +{ + local entity stopShakeEnt; + + SUB_UseTargets(); + + if(self.owner.health < 1250 && self.owner.frame > 0) + { + if ( self.owner.skin < 2) + { + self.owner.frame = 2; + self.owner.skin = 2; + } + } + else if(self.owner.health < 1500) + { + if ( self.owner.frame == 0 ) + { + time_chunk(); + self.owner.frame = 1; + self.owner.skin = 1; + } + } + + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NONE); + + if (random() < 0.5) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 244); + WriteByte (MSG_BROADCAST, 3); + } + + BecomeExplosion(); + stopShakeEnt = spawn(); + stopShakeEnt.target = self.target; + stopShakeEnt.think = time_stop_shake; + stopShakeEnt.nextthink = time + 0.7; +}; + +void() time_fall = +{ + if (!self.pain_finished) + { + if ( self.origin_z < -20 ) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LAVASPLASH); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z - 80); + self.pain_finished = 1; + } + else if (random() < 0.3) + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + } + + self.velocity_z = self.velocity_z - 5; + self.nextthink = time + 0.1; + self.think = time_fall; +}; + +void() time_crash = +{ + self.takedamage = DAMAGE_NO; + self.movetype = MOVETYPE_FLY; + self.solid = SOLID_NOT; + self.velocity = '0 0 -50'; + self.avelocity = '15 0 5'; + setsize (self, '0 0 0', '0 0 0'); + self.think = time_fall; + self.nextthink = time + 0.1; + + self.target = "timeramp"; + SUB_UseTargets(); +}; + +void(entity attacker, float take) time_pain = +{ + local entity painEnt; + local vector painOrg; + local float painRnd; + + if ( self.health > 1100 ) + if ( self.pain_finished > time ) + return; + + painRnd = random(); + if(painRnd < 0.4) + { + self.pain_finished = time + 2; + painRnd = random(); + makevectors ( self.angles ); + painEnt = spawn(); + if ( painRnd < 0.33 ) + painEnt.origin = self.origin + v_forward*80 - v_up*64; + else if (painRnd < 0.66) + painEnt.origin = self.origin + v_right*80 - v_up*24; + else + painEnt.origin = self.origin + v_forward*64 - v_up*48 - v_right*48; + painEnt.target = self.target; + painEnt.owner = self; + painEnt.think = time_boom; + painEnt.nextthink = time + 0.2 + random() * 0.3; + } + if ( self.health < 1000 ) + { + self.pain_finished = 0; + self.th_pain = SUB_Null; + self.th_die = SUB_Null; + cutscene_running = 1; + return; + } +}; + +//(-16 -16 -32) (16 16 0) +/*QUAKED item_time_machine (1 1 0) (-64 -64 -144) (64 64 0) +*/ +void() item_time_machine = +{ + if (deathmatch) + { + remove (self); + return; + } + + precache_model("progs/timemach.mdl"); + precache_model("progs/timegib.mdl"); + setmodel(self, "progs/timemach.mdl"); + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + self.health = 1600; + self.takedamage = DAMAGE_YES; + self.view_ofs = '0 0 25'; + self.movetype = MOVETYPE_FLY; + setsize(self, '-64 -64 -144', '64 64 0'); + self.avelocity = '0 60 0'; + self.flags = self.flags | FL_MONSTER; + + self.th_stand = SUB_Null; + self.th_walk = SUB_Null; + self.th_run = SUB_Null; + self.th_die = time_pain; + self.th_missile = SUB_Null; + self.th_pain = time_pain; + + theMachine = self; +}; + +/*QUAKED item_time_core (1 0 0) (-8 -8 -8) (8 8 8) +*/ +void() item_time_core = +{ + if (deathmatch) + { + remove (self); + return; + } + + precache_model ("progs/timecore.mdl"); + setmodel(self, "progs/timecore.mdl"); + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_FLY; + self.avelocity = '60 60 60'; +}; \ No newline at end of file diff --git a/quakec_rogue/triggers.qc b/quakec_rogue/triggers.qc new file mode 100644 index 0000000..c0a808a --- /dev/null +++ b/quakec_rogue/triggers.qc @@ -0,0 +1,698 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +entity stemp, otemp, s, old; + + +void() trigger_reactivate = +{ + self.solid = SOLID_TRIGGER; +}; + +//============================================================================= + +float SPAWNFLAG_NOMESSAGE = 1; +float SPAWNFLAG_NOTOUCH = 1; + +// the wait time has passed, so set back up for another activation +void() multi_wait = +{ + if (self.max_health) + { + self.health = self.max_health; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + } +}; + + +// the trigger was just touched/killed/used +// self.enemy 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 = +{ + if (self.nextthink > time) + { + return; // allready been triggered + } + + if (self.classname == "trigger_secret") + { + if (self.enemy.classname != "player") + return; + found_secrets = found_secrets + 1; + WriteByte (MSG_ALL, SVC_FOUNDSECRET); + + msg_entity = self.enemy; + WriteByte (MSG_ONE, SVC_ACHIEVEMENT); + WriteString(MSG_ONE, "ACH_FIND_SECRET"); + } + + if (self.noise) + sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM); + +// don't trigger again until reset + self.takedamage = DAMAGE_NO; + + activator = self.enemy; + + SUB_UseTargets(); + + if (self.wait > 0) + { + self.think = multi_wait; + self.nextthink = time + self.wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called wheil C code is looping through area links... + self.touch = SUB_Null; + self.nextthink = time + 0.1; + self.think = SUB_Remove; + } +}; + +void() multi_killed = +{ + self.enemy = damage_attacker; + multi_trigger(); +}; + +void() multi_use = +{ + self.enemy = activator; + multi_trigger(); +}; + +void() multi_touch = +{ + if (other.classname != "player") + return; + +// if the trigger has an angles field, check player's facing direction + if (self.movedir != '0 0 0') + { + makevectors (other.angles); + if (v_forward * self.movedir < 0) + return; // not facing the right way + } + + self.enemy = other; + multi_trigger (); +}; + +/*QUAKED trigger_multiple (.5 .5 .5) ? notouch +Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +If notouch is set, the trigger is only fired by other entities, not by touching. +NOTOUCH has been obsoleted by trigger_relay! +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_multiple = +{ + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + else if (self.sounds == 3) + { + precache_sound ("misc/trigger1.wav"); + self.noise = "misc/trigger1.wav"; + } + + if (!self.wait) + self.wait = 0.2; + self.use = multi_use; + + InitTrigger (); + + if (self.health) + { + if (self.spawnflags & SPAWNFLAG_NOTOUCH) + objerror ("health and notouch don't make sense\n"); + self.max_health = self.health; + self.th_die = multi_killed; + self.takedamage = DAMAGE_YES; + self.solid = SOLID_BBOX; + setorigin (self, self.origin); // make sure it links into the world + } + else + { + if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) ) + { + self.touch = multi_touch; + } + } +}; + + +/*QUAKED trigger_once (.5 .5 .5) ? notouch +Variable sized trigger. 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 "health" is set, the trigger must be killed to activate. +If notouch is set, the trigger is only fired by other entities, not by touching. +if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired. +if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0. +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void() trigger_once = +{ + self.wait = -1; + trigger_multiple(); +}; + +//============================================================================= + +/*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. It can contain killtargets, targets, delays, and messages. +*/ +void() trigger_relay = +{ + self.use = SUB_UseTargets; +}; + + +//============================================================================= + +/*QUAKED trigger_secret (.5 .5 .5) ? +secret counter trigger +sounds +1) secret +2) beep beep +3) +4) +set "message" to text string +*/ +void() trigger_secret = +{ + total_secrets = total_secrets + 1; + self.wait = -1; + if (!self.message) + self.message = "$qc_found_secret"; + if (!self.sounds) + self.sounds = 1; + + if (self.sounds == 1) + { + precache_sound ("misc/secret.wav"); + self.noise = "misc/secret.wav"; + } + else if (self.sounds == 2) + { + precache_sound ("misc/talk.wav"); + self.noise = "misc/talk.wav"; + } + + trigger_multiple (); +}; + +//============================================================================= + + +void() counter_use = +{ + local string junk; + + self.count = self.count - 1; + if (self.count < 0) + return; + + if (self.count != 0) + { + if (activator.classname == "player" + && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + { + if (self.count >= 4) + centerprint (activator, "$qc_more_go"); + else if (self.count == 3) + centerprint (activator, "$qc_three_more"); + else if (self.count == 2) + centerprint (activator, "$qc_two_more"); + else + centerprint (activator, "$qc_one_more"); + } + return; + } + + if (activator.classname == "player" + && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0) + centerprint(activator, "$qc_sequence_completed"); + self.enemy = activator; + multi_trigger (); +}; + +/*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 = +{ + self.wait = -1; + if (!self.count) + self.count = 2; + + self.use = counter_use; +}; + + +/* +============================================================================== + +TELEPORT TRIGGERS + +============================================================================== +*/ + +float PLAYER_ONLY = 1; +float SILENT = 2; +float CTF_ONLY = 4; + +void() play_teleport = +{ + local float v; + local string tmpstr; + + v = random() * 5; + if (v < 1) + tmpstr = "misc/r_tele1.wav"; + else if (v < 2) + tmpstr = "misc/r_tele2.wav"; + else if (v < 3) + tmpstr = "misc/r_tele3.wav"; + else if (v < 4) + tmpstr = "misc/r_tele4.wav"; + else + tmpstr = "misc/r_tele5.wav"; + + sound (self, CHAN_VOICE, tmpstr, 1, ATTN_NORM); + remove (self); +}; + +void(vector org) spawn_tfog = +{ + s = spawn (); + s.origin = org; + s.nextthink = time + 0.2; + s.think = play_teleport; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_TELEPORT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); +}; + + +void() tdeath_touch = +{ + if (other == self.owner) + return; + +// frag anyone who teleports in on top of an invincible player + if (other.classname == "player") + { + if (other.invincible_finished > time) + self.classname = "teledeath2"; + if (self.owner.classname != "player") + { // other monsters explode themselves + T_Damage (self.owner, self, self, 50000); + return; + } + + } + + if (other.health) + { + T_Damage (other, self, self, 50000); + } +}; + + +void(vector org, entity death_owner) spawn_tdeath = +{ +local entity death; + + death = spawn(); + death.classname = "teledeath"; + death.movetype = MOVETYPE_NONE; + death.solid = SOLID_TRIGGER; + death.angles = '0 0 0'; + setsize (death, death_owner.mins - '1 1 1', death_owner.maxs + '1 1 1'); + setorigin (death, org); + death.touch = tdeath_touch; + death.nextthink = time + 0.2; + death.think = SUB_Remove; + death.owner = death_owner; + + force_retouch = 2; // make sure even still objects get hit +}; + +void() teleport_touch = +{ +local entity t; +local vector org; + + if (self.targetname) + { + if (self.nextthink < time) + { + return; // not fired yet + } + } + + if (self.spawnflags & PLAYER_ONLY) + { + if (other.classname != "player") + return; + } + +// only teleport living creatures + if (other.health <= 0 || other.solid != SOLID_SLIDEBOX) + return; + + SUB_UseTargets (); + +// put a tfog where the player was + spawn_tfog (other.origin); + + t = find (world, targetname, self.target); + if (!t) + objerror ("couldn't find target"); + +// spawn a tfog flash in front of the destination + makevectors (t.mangle); + org = t.origin + 32 * v_forward; + + spawn_tfog (org); + spawn_tdeath(t.origin, other); + +// move the player and lock him down for a little while + if (!other.health) + { + other.origin = t.origin; + other.velocity = (v_forward * other.velocity_x) + (v_forward * other.velocity_y); + return; + } + + setorigin (other, t.origin); + other.angles = t.mangle; + if (other.classname == "player") + { + other.fixangle = 1; // turn this way immediately + other.teleport_time = time + 0.7; + if (other.flags & FL_ONGROUND) + other.flags = other.flags - FL_ONGROUND; + other.velocity = v_forward * 300; + } + other.flags = other.flags - other.flags & FL_ONGROUND; +}; + +/*QUAKED info_teleport_destination (.5 .5 .5) (-8 -8 -8) (8 8 32) +This is the destination marker for a teleporter. It should have a "targetname" field with the same value as a teleporter's "target" field. +*/ +void() info_teleport_destination = +{ +// this does nothing, just serves as a target spot + self.mangle = self.angles; + self.angles = '0 0 0'; + self.model = ""; + self.origin = self.origin + '0 0 27'; + if (!self.targetname) + objerror ("no targetname"); +}; + +void() teleport_use = +{ + self.nextthink = time + 0.2; + force_retouch = 2; // make sure even still objects get hit + self.think = SUB_Null; +}; + +/*QUAKED trigger_teleport (.5 .5 .5) ? PLAYER_ONLY SILENT CTF_ONLY +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. +*/ +void() trigger_teleport = +{ + local vector o; + + if (self.spawnflags & CTF_ONLY) + { + if (teamplay < TEAM_CTF || teamplay > TEAM_CTF_ALT) + { + remove (self); + return; + } + } + + InitTrigger (); + self.touch = teleport_touch; + // find the destination + if (!self.target) + objerror ("no target"); + self.use = teleport_use; + + if (!(self.spawnflags & SILENT)) + { + precache_sound ("ambience/hum1.wav"); + o = (self.mins + self.maxs)*0.5; + ambientsound (o, "ambience/hum1.wav",0.5 , ATTN_STATIC); + } +}; + +/* +============================================================================== + +trigger_setskill + +============================================================================== +*/ + +void() trigger_skill_touch = +{ + if (other.classname != "player") + return; + + cvar_set ("skill", self.message); +}; + +/*QUAKED trigger_setskill (.5 .5 .5) ? +sets skill level to the value of "message". +Only used on start map. +*/ +void() trigger_setskill = +{ + InitTrigger (); + self.touch = trigger_skill_touch; +}; + + +/* +============================================================================== + +ONLY REGISTERED TRIGGERS + +============================================================================== +*/ + +void() trigger_onlyregistered_touch = +{ + if (other.classname != "player") + return; + if (self.attack_finished > time) + return; + + self.attack_finished = time + 2; + if (cvar("registered")) + { + self.message = ""; + SUB_UseTargets (); + remove (self); + } + else + { + if (self.message != "") + { + centerprint (other, self.message); + sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM); + } + } +}; + +/*QUAKED trigger_onlyregistered (.5 .5 .5) ? +Only fires if playing the registered version, otherwise prints the message +*/ +void() trigger_onlyregistered = +{ + precache_sound ("misc/talk.wav"); + InitTrigger (); + self.touch = trigger_onlyregistered_touch; +}; + +//============================================================================ + +void() hurt_on = +{ + self.solid = SOLID_TRIGGER; + self.nextthink = -1; +}; + +void() hurt_touch = +{ + if (other.takedamage) + { + self.solid = SOLID_NOT; + T_Damage (other, self, self, self.dmg); + self.think = hurt_on; + self.nextthink = time + 1; + } + + return; +}; + +/*QUAKED trigger_hurt (.5 .5 .5) ? +Any object touching this will be hurt +set dmg to damage amount +defalt dmg = 5 +*/ +void() trigger_hurt = +{ + InitTrigger (); + self.touch = hurt_touch; + if (!self.dmg) + self.dmg = 5; +}; + +//============================================================================ + +float PUSH_ONCE = 1; +float PUSH_TOGL = 2; +float PUSH_ACTIVE = 4; + +void() trigger_push_touch = +{ + if (!(self.spawnflags & PUSH_ACTIVE)) + return; + + if (other.classname == "grenade") + other.velocity = self.speed * self.movedir * 10; + else if (other.classname == "MiniGrenade") + other.velocity = self.speed * self.movedir * 10; + else if (other.classname == "MultiGrenade") + other.velocity = self.speed * self.movedir * 10; + else if (other.health > 0) + { + other.velocity = self.speed * self.movedir * 10; + if (other.classname == "player") + { + if (other.fly_sound < time) + { + other.fly_sound = time + 1.5; + sound (other, CHAN_AUTO, "ambience/windfly.wav", 1, ATTN_NORM); + } + } + } + if (self.spawnflags & PUSH_ONCE) + remove(self); +}; + +void() trigger_push_use = +{ + if (self.spawnflags & PUSH_ACTIVE) + self.spawnflags = self.spawnflags - PUSH_ACTIVE; + else + self.spawnflags = self.spawnflags + PUSH_ACTIVE; +}; + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE PUSH_TOGL PUSH_ACTIVE +Pushes the player. + +speed: velocity of push. (default 1000) +*/ +void() trigger_push = +{ + if (self.spawnflags & PUSH_TOGL) + self.use = trigger_push_use; + else + self.spawnflags = self.spawnflags + PUSH_ACTIVE; + + InitTrigger (); + precache_sound ("ambience/windfly.wav"); + self.touch = trigger_push_touch; + if (!self.speed) + self.speed = 1000; +}; + +//============================================================================ + +void() trigger_monsterjump_touch = +{ + if ( other.flags & (FL_MONSTER | FL_FLY | FL_SWIM) != FL_MONSTER ) + return; + +// set XY even if not on ground, so the jump will clear lips + other.velocity_x = self.movedir_x * self.speed; + other.velocity_y = self.movedir_y * self.speed; + + if ( !(other.flags & FL_ONGROUND) ) + return; + + other.flags = other.flags - FL_ONGROUND; + + other.velocity_z = self.height; +}; + +/*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 = +{ + if (!self.speed) + self.speed = 200; + if (!self.height) + self.height = 200; + if (self.angles == '0 0 0') + self.angles = '0 360 0'; + InitTrigger (); + self.touch = trigger_monsterjump_touch; +}; + diff --git a/quakec_rogue/weapons.qc b/quakec_rogue/weapons.qc new file mode 100644 index 0000000..93f704b --- /dev/null +++ b/quakec_rogue/weapons.qc @@ -0,0 +1,1758 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +*/ + +float lavaGunFired; + +void (entity targ, entity inflictor, entity attacker, float damage) T_Damage; +void () player_run; +void(entity bomb, entity attacker, float rad, entity ignore) T_RadiusDamage; +void(vector org, vector vel, float damage) SpawnBlood; +void() SuperDamageSound; + + +// called by worldspawn +void() W_Precache = +{ + precache_sound ("weapons/r_exp3.wav"); // new rocket explosion + precache_sound ("weapons/rocket1i.wav"); // spike gun + precache_sound ("weapons/sgun1.wav"); + precache_sound ("weapons/guncock.wav"); // player shotgun + precache_sound ("weapons/ric1.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric2.wav"); // ricochet (used in c code) + precache_sound ("weapons/ric3.wav"); // ricochet (used in c code) + precache_sound ("weapons/spike2.wav"); // super spikes + precache_sound ("weapons/tink1.wav"); // spikes tink (used in c code) + precache_sound ("weapons/grenade.wav"); // grenade launcher + precache_sound ("weapons/bounce.wav"); // grenade bounce + precache_sound ("weapons/shotgn2.wav"); // super shotgun + +//ZOID-- + // grapple + precache_sound("weapons/chain1.wav"); +// precache_sound("weapons/chain2.wav"); +// precache_sound("weapons/chain3.wav"); +// precache_sound("weapons/bounce2.wav"); +// precache_sound("blob/land1.wav"); + precache_sound("pendulum/hit.wav"); +//--ZOID + + precache_sound ("lavagun/snail.wav"); // lava nail gun cooldown +}; + +float() crandom = +{ + return 2*(random() - 0.5); +}; + +/* +================ +W_FireAxe +================ +*/ +void() W_FireAxe = +{ + local vector source; + local vector org; + + makevectors (self.v_angle); + source = self.origin + '0 0 16'; + traceline (source, source + v_forward*64, FALSE, self); + if (trace_fraction == 1.0) + return; + + org = trace_endpos - v_forward*4; + + if (trace_ent.takedamage) + { + trace_ent.axhitme = 1; + SpawnBlood (org, '0 0 0', 20); + T_Damage (trace_ent, self, self, 20); + } + else + { // hit wall + sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM); + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + + +//============================================================================ + + +vector() wall_velocity = +{ + local vector vel; + + vel = normalize (self.velocity); + vel = normalize(vel + v_up*(random()- 0.5) + v_right*(random()- 0.5)); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + return vel; +}; + + +/* +================ +SpawnMeatSpray +================ +*/ +void(vector org, vector vel) SpawnMeatSpray = +{ + local entity missile, mpuff; + local vector org; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_NOT; + + makevectors (self.angles); + + missile.velocity = vel; + missile.velocity_z = missile.velocity_z + 250 + 50*random(); + + missile.avelocity = '3000 1000 2000'; + +// set missile duration + missile.nextthink = time + 1; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + +/* +================ +SpawnBlood +================ +*/ +void(vector org, vector vel, float damage) SpawnBlood = +{ + particle (org, vel*0.1, 73, damage*2); +}; + +/* +================ +spawn_touchblood +================ +*/ +void(float damage) spawn_touchblood = +{ + local vector vel; + + vel = wall_velocity () * 0.2; + SpawnBlood (self.origin + vel*0.01, vel, damage); +}; + + +/* +================ +SpawnChunk +================ +*/ +void(vector org, vector vel) SpawnChunk = +{ + particle (org, vel*0.02, 0, 10); +}; + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +entity multi_ent; +float multi_damage; + +void() ClearMultiDamage = +{ + multi_ent = world; + multi_damage = 0; +}; + +void() ApplyMultiDamage = +{ + if (!multi_ent) + return; + T_Damage (multi_ent, self, self, multi_damage); +}; + +void(entity hit, float damage) AddMultiDamage = +{ + if (!hit) + return; + + if (hit != multi_ent) + { + ApplyMultiDamage (); + multi_damage = damage; + multi_ent = hit; + } + else + multi_damage = multi_damage + damage; +}; + +/* +============================================================================== + +BULLETS + +============================================================================== +*/ + +/* +================ +TraceAttack +================ +*/ +void(float damage, vector dir) TraceAttack = +{ + local vector vel, org; + + vel = normalize(dir + v_up*crandom() + v_right*crandom()); + vel = vel + 2*trace_plane_normal; + vel = vel * 200; + + org = trace_endpos - dir*4; + + if (trace_ent.takedamage) + { + SpawnBlood (org, vel*0.2, damage); + AddMultiDamage (trace_ent, damage); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_GUNSHOT); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + } +}; + +/* +================ +FireBullets + +Used by shotgun, super shotgun, and enemy soldier firing +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void(float shotcount, vector dir, vector spread) FireBullets = +{ + local vector direction; + local vector src; + + makevectors(self.v_angle); + + src = self.origin + v_forward*10; + src_z = self.absmin_z + self.size_z * 0.7; + + ClearMultiDamage (); + while (shotcount > 0) + { + direction = dir + crandom()*spread_x*v_right + crandom()*spread_y*v_up; + + traceline (src, src + direction*2048, FALSE, self); + if (trace_fraction != 1.0) + TraceAttack (4, direction); + + shotcount = shotcount - 1; + } + ApplyMultiDamage (); +}; + +/* +================ +W_FireShotgun +================ +*/ +void() W_FireShotgun = +{ + local vector dir; + + sound (self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + self.currentammo = self.ammo_shells1 = self.ammo_shells1 - 1; + UpdateAmmoCounts(self); + dir = aim (self, 100000); + FireBullets (6, dir, '0.04 0.04 0'); +}; + + +/* +================ +W_FireSuperShotgun +================ +*/ +void() W_FireSuperShotgun = +{ + local vector dir; + + if (self.currentammo == 1) + { + W_FireShotgun (); + return; + } + + sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM); + + self.punchangle_x = -4; + + self.currentammo = self.ammo_shells1 = self.ammo_shells1 - 2; + UpdateAmmoCounts(self); + dir = aim (self, 100000); + FireBullets (14, dir, '0.14 0.08 0'); +}; + + +/* +============================================================================== + +ROCKETS + +============================================================================== +*/ + +void() s_explode1 = [0, s_explode2] {}; +void() s_explode2 = [1, s_explode3] {}; +void() s_explode3 = [2, s_explode4] {}; +void() s_explode4 = [3, s_explode5] {}; +void() s_explode5 = [4, s_explode6] {}; +void() s_explode6 = [5, SUB_Remove] {}; + +void() BecomeExplosion = +{ + self.movetype = MOVETYPE_NONE; + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; + +void() T_MissileTouch = +{ + local float damg; + + if (other == self.owner) + return; // don't explode on owner + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + + damg = 100 + random()*20; + + if (other.health) + { + if (other.classname == "monster_shambler") + damg = damg * 0.5; // mostly immune + T_Damage (other, self, self.owner, damg ); + } + + // don't do radius damage to the other, because all the damage + // was done in the impact + T_RadiusDamage (self, self.owner, 120, other); + +// sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + self.origin = self.origin - 8*normalize(self.velocity); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + + + +/* +================ +W_FireRocket +================ +*/ +void() W_FireRocket = +{ + local entity missile, mpuff; + + self.currentammo = self.ammo_rockets1 = self.ammo_rockets1 - 1; + UpdateAmmoCounts(self); + + sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.solid = SOLID_BBOX; + missile.classname = "missile"; + +// set missile speed + + makevectors (self.v_angle); + missile.velocity = aim(self, 1000); + missile.velocity = missile.velocity * 1000; + missile.angles = vectoangles(missile.velocity); + + missile.touch = T_MissileTouch; + +// set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/missile.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + v_forward*8 + '0 0 16'); +}; + +/* +=============================================================================== + +LIGHTNING + +=============================================================================== +*/ + +/* +================= +LightningDamage +================= +*/ +void(vector p1, vector p2, entity from, float damage) LightningDamage = +{ + local entity e1, e2; + local vector f; + + f = p2 - p1; + normalize (f); + f_x = 0 - f_y; + f_y = f_x; + f_z = 0; + f = f*16; + + e1 = e2 = world; + + traceline (p1, p2, FALSE, self); + if (trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + if (self.classname == "player") + { + if (other.classname == "player") + trace_ent.velocity_z = trace_ent.velocity_z + 400; + } + } + e1 = trace_ent; + + traceline (p1 + f, p2 + f, FALSE, self); + if (trace_ent != e1 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } + e2 = trace_ent; + + traceline (p1 - f, p2 - f, FALSE, self); + if (trace_ent != e1 && trace_ent != e2 && trace_ent.takedamage) + { + particle (trace_endpos, '0 0 100', 225, damage*4); + T_Damage (trace_ent, from, from, damage); + } +}; + + +void() W_FireLightning = +{ + local vector org; + local float cells; + + if (self.ammo_cells1 < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + +// explode if under water + if (self.waterlevel > 1) + { + cells = self.ammo_cells1; + self.ammo_cells1 = 0; + W_SetCurrentAmmo (); + T_RadiusDamage (self, self, 35*cells, world); + return; + } + + if (self.t_width < time) + { + sound (self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM); + self.t_width = time + 0.6; + } + self.punchangle_x = -2; + + self.currentammo = self.ammo_cells1 = self.ammo_cells1 - 1; + UpdateAmmoCounts(self); + + org = self.origin + '0 0 16'; + + makevectors (self.v_angle); + traceline (org, org + v_forward*600, TRUE, self); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_LIGHTNING2); + WriteEntity (MSG_BROADCAST, self); + WriteCoord (MSG_BROADCAST, org_x); + WriteCoord (MSG_BROADCAST, org_y); + WriteCoord (MSG_BROADCAST, org_z); + WriteCoord (MSG_BROADCAST, trace_endpos_x); + WriteCoord (MSG_BROADCAST, trace_endpos_y); + WriteCoord (MSG_BROADCAST, trace_endpos_z); + + LightningDamage (self.origin + '0 0 16', trace_endpos + v_forward*4, self, 30); +}; + + +//============================================================================= + + +void() GrenadeExplode = +{ + T_RadiusDamage (self, self.owner, 120, world); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + BecomeExplosion (); +}; + +void() GrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + + if (other.takedamage == DAMAGE_AIM) + { + GrenadeExplode(); + return; + } + sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound + if (self.velocity == '0 0 0') + self.avelocity = '0 0 0'; +}; + +/* +================ +W_FireGrenade +================ +*/ +void() W_FireGrenade = +{ + local entity missile, mpuff; + + self.currentammo = self.ammo_rockets1 = self.ammo_rockets1 - 1; + UpdateAmmoCounts(self); + + sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM); + + self.punchangle_x = -2; + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + missile.classname = "grenade"; + +// set missile speed + + makevectors (self.v_angle); + + if (self.v_angle_x) + missile.velocity = v_forward*600 + v_up * 200 + crandom()*v_right*10 + crandom()*v_up*10; + else + { + missile.velocity = aim(self, 10000); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + } + + missile.avelocity = '300 300 300'; + + missile.angles = vectoangles(missile.velocity); + + missile.touch = GrenadeTouch; + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = GrenadeExplode; + + setmodel (missile, "progs/grenade.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin); +}; + + +//============================================================================= + +void() spike_touch; +void() superspike_touch; +void() lavaspike_touch; +void() superlavaspike_touch; + +/* +=============== +launch_spike + +Used for both the player and the ogre +=============== +*/ +void(vector org, vector dir) launch_spike = +{ + newmis = spawn (); + newmis.owner = self; + newmis.movetype = MOVETYPE_FLYMISSILE; + newmis.solid = SOLID_BBOX; + + newmis.angles = vectoangles(dir); + + newmis.touch = spike_touch; + newmis.classname = "spike"; + newmis.think = SUB_Remove; + newmis.nextthink = time + 6; + setmodel (newmis, "progs/spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + setorigin (newmis, org); + + newmis.velocity = dir * 1000; +}; + +void() W_FireSuperSpikes = +{ + local vector dir; + local entity old; + + sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + + self.currentammo = self.ammo_nails1 = self.ammo_nails1 - 2; + UpdateAmmoCounts(self); + + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16', dir); + newmis.touch = superspike_touch; + setmodel (newmis, "progs/s_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + self.punchangle_x = -2; +}; + +void(float ox) W_FireSpikes = +{ + local vector dir; + local entity old; + + makevectors (self.v_angle); + + if (self.ammo_nails1 >= 2 && self.weapon == IT_SUPER_NAILGUN) + { + W_FireSuperSpikes (); + return; + } + + if (self.ammo_nails1 < 1) + { + self.weapon = W_BestWeapon (); + W_SetCurrentAmmo (); + return; + } + + sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM); + self.attack_finished = time + 0.2; + self.currentammo = self.ammo_nails1 = self.ammo_nails1 - 1; + UpdateAmmoCounts(self); + + dir = aim (self, 1000); + launch_spike (self.origin + '0 0 16' + v_right*ox, dir); + + self.punchangle_x = -2; +}; + +.float hit_z; +void() spike_touch = +{ +local float rand; + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (9); + T_Damage (other, self, self.owner, 9); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + + if (self.classname == "wizspike") + WriteByte (MSG_BROADCAST, TE_WIZSPIKE); + else if (self.classname == "knightspike") + WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE); + else + WriteByte (MSG_BROADCAST, TE_SPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + +void() superspike_touch = +{ +local float rand; + if (other == self.owner) + return; + + if (other.solid == SOLID_TRIGGER) + return; // trigger field, do nothing + + if (pointcontents(self.origin) == CONTENT_SKY) + { + remove(self); + return; + } + +// hit something that bleeds + if (other.takedamage) + { + spawn_touchblood (18); + T_Damage (other, self, self.owner, 18); + } + else + { + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_SUPERSPIKE); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + } + + remove(self); + +}; + +/* +=============================================================================== + +PLAYER WEAPON USE + +=============================================================================== +*/ + +void() W_SetCurrentAmmo = +{ + player_run (); // get out of any weapon firing states + + self.items = self.items - + ( self.items & (IT_SHELLS | IT_NAILS | IT_ROCKETS | IT_CELLS)); + self.items2 = self.items2 - + (self.items2 & (IT2_LAVA_NAILS|IT2_PLASMA_AMMO|IT2_MULTI_ROCKETS)); + + if (self.weapon == IT_AXE) + { + self.currentammo = 0; + self.weaponmodel = "progs/v_axe.mdl"; + self.weaponframe = 0; + } +//ZOID-- + else if (self.weapon == IT_GRAPPLE) + { + self.currentammo = 0; + self.weaponmodel = "progs/v_grpple.mdl"; + self.weaponframe = 0; + } +//--ZOID + else if (self.weapon == IT_SHOTGUN) + { + self.currentammo = self.ammo_shells1; + self.weaponmodel = "progs/v_shot.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.currentammo = self.ammo_shells1; + self.weaponmodel = "progs/v_shot2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_SHELLS; + } + else if (self.weapon == IT_LAVA_NAILGUN) + { + self.currentammo = self.ammo_lava_nails; + self.weaponmodel = "progs/v_lava.mdl"; + self.weaponframe = 0; + self.items2 = self.items2 | IT2_LAVA_NAILS; + } + else if (self.weapon == IT_LAVA_SUPER_NAILGUN) + { + self.currentammo = self.ammo_lava_nails; + self.weaponmodel = "progs/v_lava2.mdl"; + self.weaponframe = 0; + self.items2 = self.items2 | IT2_LAVA_NAILS; + } + else if (self.weapon == IT_MULTI_GRENADE) + { + self.currentammo = self.ammo_multi_rockets; + self.weaponmodel = "progs/v_multi.mdl"; + self.weaponframe = 0; + self.items2 = self.items2 | IT2_MULTI_ROCKETS; + } + else if (self.weapon == IT_MULTI_ROCKET) + { + + self.currentammo = self.ammo_multi_rockets; + self.weaponmodel = "progs/v_multi2.mdl"; + self.weaponframe = 0; + self.items2 = self.items2 | IT2_MULTI_ROCKETS; + } + else if (self.weapon == IT_PLASMA_GUN) + { + self.currentammo = self.ammo_plasma; + self.weaponmodel = "progs/v_plasma.mdl"; + self.weaponframe = 0; + self.items2 = self.items2 | IT2_PLASMA_AMMO; + } + else if (self.weapon == IT_NAILGUN) + { + self.currentammo = self.ammo_nails1; + self.weaponmodel = "progs/v_nail.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_NAILS; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.currentammo = self.ammo_nails1; + self.weaponmodel = "progs/v_nail2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_NAILS; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.currentammo = self.ammo_rockets1; + self.weaponmodel = "progs/v_rock.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.currentammo = self.ammo_rockets1; + self.weaponmodel = "progs/v_rock2.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_ROCKETS; + } + else if (self.weapon == IT_LIGHTNING) + { + self.currentammo = self.ammo_cells1; + self.weaponmodel = "progs/v_light.mdl"; + self.weaponframe = 0; + self.items = self.items | IT_CELLS; + } + else + { + self.currentammo = 0; + self.weaponmodel = ""; + self.weaponframe = 0; + } + + UpdateAmmoCounts(self); +}; + +float() W_BestWeapon = +{ + local float it; + + it = self.items; + +// pgm - fix for sandy. will not change to plasma gun + +// if(self.waterlevel <= 1 && self.ammo_plasma >= 1 && (it & IT_PLASMA_GUN)) +// return IT_PLASMA_GUN; +// else + if(self.waterlevel<=1 && self.ammo_cells1>=1 && (it & IT_LIGHTNING)) + return IT_LIGHTNING; + else if(self.ammo_lava_nails >= 2 && (it & IT_LAVA_SUPER_NAILGUN) ) + return IT_LAVA_SUPER_NAILGUN; + else if(self.ammo_nails1 >= 2 && (it & IT_SUPER_NAILGUN) ) + return IT_SUPER_NAILGUN; + else if(self.ammo_lava_nails >= 1 && (it & IT_LAVA_NAILGUN) ) + return IT_LAVA_NAILGUN; + else if(self.ammo_nails1 >= 1 && (it & IT_NAILGUN) ) + return IT_NAILGUN; + else if(self.ammo_shells1 >= 2 && (it & IT_SUPER_SHOTGUN) ) + return IT_SUPER_SHOTGUN; + else if(self.ammo_shells1 >= 1 && (it & IT_SHOTGUN) ) + return IT_SHOTGUN; + + return IT_AXE; +}; + +float(entity playerEnt, float old, float new) W_WantsToChangeWeapon = +{ + float playerFlags = CheckPlayerEXFlags(playerEnt); + if((playerFlags & PEF_CHANGENEVER) != 0) + { + return 0; + } + + if(((playerFlags & PEF_CHANGEONLYNEW) != 0) && old == new) + { + return 0; + } + + return 1; +}; + +float() W_CheckNoAmmo = +{ + if (self.currentammo > 0) + return TRUE; + +//ZOID-- + if (self.weapon == IT_AXE || self.weapon == IT_GRAPPLE) + return TRUE; +//--ZOID + + self.weapon = W_BestWeapon (); + + W_SetCurrentAmmo (); + +// drop the weapon down + return FALSE; +}; + +/* +============ +W_Attack + +An attack impulse can be triggered now +============ +*/ +void() player_axe1; +void() player_axeb1; +void() player_axec1; +void() player_axed1; +void() player_shot1; +void() player_nail1; +void() player_light1; +void() player_rocket1; +void() player_lava_nail1; +//ZOID-- +void() player_grapple1; +void() player_grapple3; +//--ZOID + +void() W_FireMultiGrenade; +void() W_FireMultiRocket; + +void() W_Attack = +{ + local float r; + + if (!W_CheckNoAmmo ()) + return; + + makevectors (self.v_angle); // calculate forward angle for velocity + self.show_hostile = time + 1; // wake monsters up + +// ZOID-- + RuneApplyBlackNoise(self); // make rune noise +//--ZOID + + if (self.weapon == IT_AXE) + { + sound (self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM); + r = random(); + if (r < 0.25) + player_axe1 (); + else if (r<0.5) + player_axeb1 (); + else if (r<0.75) + player_axec1 (); + else + player_axed1 (); + self.attack_finished = time + RuneApplyHell(0.5, self); + } +//ZOID-- + else if (self.weapon == IT_GRAPPLE) + { + if (!self.hook_out) + player_grapple1(); + else + player_grapple3(); + self.attack_finished = time + 0.1; + } +//--ZOID + else if (self.weapon == IT_SHOTGUN) + { + player_shot1 (); + W_FireShotgun (); + self.attack_finished = time + RuneApplyHell(0.5, self); + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + player_shot1 (); + W_FireSuperShotgun (); + self.attack_finished = time + RuneApplyHell(0.7, self); + } + else if (self.weapon == IT_LAVA_NAILGUN) + { + lavaGunFired = 1; + player_lava_nail1 (); + } + else if (self.weapon == IT_LAVA_SUPER_NAILGUN) + { + lavaGunFired = 1; + player_lava_nail1 (); + } + else if (self.weapon == IT_MULTI_GRENADE) + { + player_rocket1(); + W_FireMultiGrenade(); + self.attack_finished = time + RuneApplyHell(0.6, self); + } + else if (self.weapon == IT_MULTI_ROCKET) + { + player_rocket1(); + W_FireMultiRocket(); + self.attack_finished = time + RuneApplyHell(0.8, self); + } + else if (self.weapon == IT_PLASMA_GUN) + { + // player_light1 determines choice of W_FireLightning + // or W_FirePlasma, but same lighting and frames... + self.attack_finished = time + RuneApplyHell(1.0, self); + player_light1 (); + } + else if (self.weapon == IT_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + player_nail1 (); + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + player_rocket1(); + W_FireGrenade(); + self.attack_finished = time + RuneApplyHell(0.6, self); + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + player_rocket1(); + W_FireRocket(); + self.attack_finished = time + RuneApplyHell(0.8, self); + } + else if (self.weapon == IT_LIGHTNING) + { + player_light1(); + self.attack_finished = time + 0.1; + sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM); + } +}; + +/* +============ +W_ChangeWeapon + +============ +*/ +void() W_ChangeWeapon = +{ + local float it, am, fl; + + EnableComboWeapons ( self ); + UpdateAmmoCounts ( self ); + + it = self.items; + am = 0; + + if (self.impulse == 1) + { +//ZOID-- + if (self.weapon == IT_AXE && deathmatch && teamplay >= TEAM_CTF) + fl = IT_GRAPPLE; + else + fl = IT_AXE; + } + else if (self.impulse == 22) + { + if(deathmatch && teamplay >= TEAM_CTF) //pgm optimization + fl = IT_GRAPPLE; + } +//--ZOID + else if (self.impulse == 2) + { + fl = IT_SHOTGUN; + if (self.ammo_shells1 < 1) + am = 1; + } + else if (self.impulse == 3) + { + fl = IT_SUPER_SHOTGUN; + if (self.ammo_shells1 < 2) + am = 1; + } + else if (self.impulse == 4) + { + if ((self.items & IT_LAVA_NAILGUN) && + (self.weapon == IT_NAILGUN || self.ammo_nails1 < 1)) + { + fl = IT_LAVA_NAILGUN; + if (self.ammo_lava_nails < 1) + am = 1; + } + else + { + fl = IT_NAILGUN; + if (self.ammo_nails1 < 1) + am = 1; + } + } + else if (self.impulse == 5) + { + if ((self.items & IT_LAVA_SUPER_NAILGUN) && + (self.weapon == IT_SUPER_NAILGUN || self.ammo_nails1 < 2)) + { + fl = IT_LAVA_SUPER_NAILGUN; + if (self.ammo_lava_nails < 2) + am = 1; + } + else + { + fl = IT_SUPER_NAILGUN; + if (self.ammo_nails1 < 2) + am = 1; + } + } + else if (self.impulse == 6) + { + if ((self.items & IT_MULTI_GRENADE) && + (self.weapon == IT_GRENADE_LAUNCHER || self.ammo_rockets1 < 1)) + { + fl = IT_MULTI_GRENADE; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else + { + fl = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets1 < 1) + am = 1; + } + } + else if (self.impulse == 7) + { + if ((self.items & IT_MULTI_ROCKET) && + (self.weapon == IT_ROCKET_LAUNCHER || self.ammo_rockets1 < 1)) + { + fl = IT_MULTI_ROCKET; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else + { + fl = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets1 < 1) + am = 1; + } + } + else if (self.impulse == 8) + { + if ((self.items & IT_PLASMA_GUN) && + (self.weapon == IT_LIGHTNING || self.ammo_cells1 < 1)) + { + fl = IT_PLASMA_GUN; + if (self.ammo_plasma < 1) + am = 1; + } + else + { + fl = IT_LIGHTNING; + if (self.ammo_cells1 < 1) + am = 1; + } + } + else if (self.impulse == 60) + { + fl = IT_LAVA_NAILGUN; + if (self.ammo_lava_nails < 1) + am = 1; + } + else if (self.impulse == 61) + { + fl = IT_LAVA_SUPER_NAILGUN; + if (self.ammo_lava_nails < 1) + am = 1; + } + else if (self.impulse == 62) + { + fl = IT_MULTI_GRENADE; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else if (self.impulse == 63) + { + fl = IT_MULTI_ROCKET; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else if (self.impulse == 64) + { + fl = IT_PLASMA_GUN; + if (self.ammo_plasma < 1) + am = 1; + } + else if (self.impulse == 65) + { + fl = IT_NAILGUN; + + if (self.ammo_nails < 1) + am = 1; + } + else if (self.impulse == 66) + { + fl = IT_SUPER_NAILGUN; + + if (self.ammo_nails < 2) + am = 1; + } + else if (self.impulse == 67) + { + fl = IT_GRENADE_LAUNCHER; + + if (self.ammo_rockets < 1) + am = 1; + } + else if (self.impulse == 68) + { + fl = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets < 1) + am = 1; + } + + self.impulse = 0; + + if (!(self.items & fl)) + { // don't have the weapon or the ammo + sprint (self, "$qc_no_weapon"); + return; + } + + if (am) + { // don't have the ammo + sprint (self, "$qc_not_enough_ammo"); + return; + } + + + if (self.weapon != fl) + { + if (self.weapon == IT_LAVA_NAILGUN || + self.weapon == IT_LAVA_SUPER_NAILGUN) + { + if (fl == IT_NAILGUN || fl == IT_SUPER_NAILGUN) + sprint (self, "$qc_normal_nails"); + } + else if (self.weapon == IT_MULTI_GRENADE) + { + if (fl == IT_GRENADE_LAUNCHER) + sprint (self, "$qc_normal_grenades"); + } + else if (self.weapon == IT_MULTI_ROCKET) + { + if (fl == IT_ROCKET_LAUNCHER) + sprint (self, "$qc_normal_rockets"); + } + else if (self.weapon == IT_PLASMA_GUN) + { + if (fl == IT_LIGHTNING) + sprint (self, "$qc_lightning_gun"); + } + else if (fl == IT_LAVA_NAILGUN || fl == IT_LAVA_SUPER_NAILGUN) + { + sprint (self, "$qc_lava_nails"); + } + else if(fl == IT_MULTI_GRENADE) + { + sprint (self, "$qc_multi_gl"); + } + else if(fl == IT_MULTI_ROCKET) + { + sprint (self, "$qc_multi_rl"); + } + else if(fl == IT_PLASMA_GUN) + { + sprint (self, "$qc_plasma_gun"); + } + } + +// +// set weapon, set ammo +// + self.weapon = fl; + W_SetCurrentAmmo (); +}; + +/* +============ +CheatCommand +============ +*/ +void() CheatCommand = +{ + if ( ( deathmatch || coop ) && cheats_allowed == 0 ) { // mal: allow impulse 9 IF cheats allowed + return; + } + + self.ammo_rockets1 = 100; + self.ammo_nails1 = 200; + self.ammo_shells1 = 100; + + self.items = self.items | + IT_AXE | + IT_SHOTGUN | + IT_SUPER_SHOTGUN | + IT_NAILGUN | + IT_SUPER_NAILGUN | + IT_GRENADE_LAUNCHER | + IT_ROCKET_LAUNCHER | + + IT_LAVA_NAILGUN | + IT_LAVA_SUPER_NAILGUN | + IT_MULTI_GRENADE | + IT_MULTI_ROCKET | + IT_PLASMA_GUN | + IT_LIGHTNING; + + self.items = self.items | IT_KEY1 | IT_KEY2; + + self.ammo_lava_nails = 200; + self.ammo_multi_rockets = 100; + self.ammo_plasma = 100; + self.ammo_cells1 = 200; + + self.weapon = IT_ROCKET_LAUNCHER; + self.impulse = 0; + W_SetCurrentAmmo (); +}; + +/* +============ +CycleWeaponCommand + +Go to the next weapon with ammo +============ +*/ +void() CycleWeaponCommand = +{ + local float it, am; + + EnableComboWeapons ( self ); + UpdateAmmoCounts ( self ); + + it = self.items; + self.impulse = 0; + + while (1) + { + am = 0; + + if (self.weapon == IT_PLASMA_GUN) + { + self.weapon = IT_AXE; + } + else if (self.weapon == IT_AXE) + { +//ZOID-- + if (deathmatch && teamplay >= TEAM_CTF) + self.weapon = IT_GRAPPLE; + else + { + self.weapon = IT_SHOTGUN; + if (self.ammo_shells1 < 1) + am = 1; + } +//--ZOID + } + else if (self.weapon == IT_GRAPPLE) + { + self.weapon = IT_SHOTGUN; + if (self.ammo_shells1 < 1) + am = 1; + } +//--ZOID + else if (self.weapon == IT_SHOTGUN) + { + self.weapon = IT_SUPER_SHOTGUN; + if (self.ammo_shells1 < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.weapon = IT_NAILGUN; + if (self.ammo_nails1 < 1) + am = 1; + } + else if (self.weapon == IT_NAILGUN) + { + self.weapon = IT_LAVA_NAILGUN; + if (self.ammo_lava_nails < 1) + am = 1; + } + else if (self.weapon == IT_LAVA_NAILGUN) + { + self.weapon = IT_SUPER_NAILGUN; + if (self.ammo_nails1 < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.weapon = IT_LAVA_SUPER_NAILGUN; + if (self.ammo_lava_nails < 2) + am = 1; + } + else if (self.weapon == IT_LAVA_SUPER_NAILGUN) + { + self.weapon = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets1 < 1) + am = 1; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.weapon = IT_MULTI_GRENADE; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else if (self.weapon == IT_MULTI_GRENADE) + { + self.weapon = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets1 < 1) + am = 1; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.weapon = IT_MULTI_ROCKET; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else if (self.weapon == IT_MULTI_ROCKET) + { + self.weapon = IT_LIGHTNING; + if (self.ammo_cells1 < 1) + am = 1; + } + else if (self.weapon == IT_LIGHTNING) + { + self.weapon = IT_PLASMA_GUN; + if (self.ammo_plasma < 1) + am = 1; + } + + if ( (it & self.weapon) && am == 0) + { + W_SetCurrentAmmo (); + return; + } + } + +}; + +/* +============ +CycleWeaponReverseCommand + +Go to the prev weapon with ammo +============ +*/ +void() CycleWeaponReverseCommand = +{ + local float it, am; + + EnableComboWeapons ( self ); + UpdateAmmoCounts ( self ); + + it = self.items; + self.impulse = 0; + + while (1) + { + am = 0; + + if (self.weapon == IT_PLASMA_GUN) + { + self.weapon = IT_LIGHTNING; + if (self.ammo_cells1 < 1) + am = 1; + } + else if (self.weapon == IT_LIGHTNING) + { + self.weapon = IT_MULTI_ROCKET; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else if (self.weapon == IT_MULTI_ROCKET) + { + self.weapon = IT_ROCKET_LAUNCHER; + if (self.ammo_rockets1 < 1) + am = 1; + } + else if (self.weapon == IT_ROCKET_LAUNCHER) + { + self.weapon = IT_MULTI_GRENADE; + if (self.ammo_multi_rockets < 1) + am = 1; + } + else if (self.weapon == IT_MULTI_GRENADE) + { + self.weapon = IT_GRENADE_LAUNCHER; + if (self.ammo_rockets1 < 1) + am = 1; + } + else if (self.weapon == IT_GRENADE_LAUNCHER) + { + self.weapon = IT_LAVA_SUPER_NAILGUN; + if (self.ammo_lava_nails < 2) + am = 1; + } + else if (self.weapon == IT_LAVA_SUPER_NAILGUN) + { + self.weapon = IT_SUPER_NAILGUN; + if (self.ammo_nails1 < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_NAILGUN) + { + self.weapon = IT_LAVA_NAILGUN; + if (self.ammo_lava_nails < 2) + am = 1; + } + else if (self.weapon == IT_LAVA_NAILGUN) + { + self.weapon = IT_NAILGUN; + if (self.ammo_nails1 < 1) + am = 1; + } + else if (self.weapon == IT_NAILGUN) + { + self.weapon = IT_SUPER_SHOTGUN; + if (self.ammo_shells1 < 2) + am = 1; + } + else if (self.weapon == IT_SUPER_SHOTGUN) + { + self.weapon = IT_SHOTGUN; + if (self.ammo_shells1 < 1) + am = 1; + } + else if (self.weapon == IT_SHOTGUN) + { +//PGM + if (deathmatch && teamplay >= TEAM_CTF) + self.weapon = IT_GRAPPLE; + else + self.weapon = IT_AXE; +//PGM + } +//ZOID-- + else if (self.weapon == IT_GRAPPLE) + { + self.weapon = IT_AXE; + } +//--ZOID + else if (self.weapon == IT_AXE) + { + self.weapon = IT_PLASMA_GUN; + if (self.ammo_plasma < 1) + am = 1; + } + + if ( (it & self.weapon) && am == 0) + { + W_SetCurrentAmmo (); + return; + } + } + +}; + +/* +============ +ServerflagsCommand + +Just for development +============ +*/ +void() ServerflagsCommand = +{ + serverflags = serverflags * 2 + 1; +}; + +void() QuadCheat = +{ + if (deathmatch || coop) + return; + self.super_time = 1; + self.super_damage_finished = time + 30; + self.items = self.items | IT_QUAD; + dprint ("$qc_quad_cheat"); +}; + + +/* +============ +ImpulseCommands + +============ +*/ +void() ImpulseCommands = +{ + local entity takeThisOut; + + if (self.impulse >= 1 && self.impulse <= 8) + W_ChangeWeapon (); + else if (self.impulse >= 60 && self.impulse <= 68) + W_ChangeWeapon (); + else if (self.impulse == 9) + CheatCommand (); + else if (self.impulse == 10) + CycleWeaponCommand (); + else if (self.impulse == 11) + ServerflagsCommand (); + else if (self.impulse == 12) + CycleWeaponReverseCommand (); +//ZOID-- +//teamplay stuff + else if (self.impulse == 20) + TossBackpack(); + else if (self.impulse == 21) + TossWeapon(); + else if (self.impulse == 22) + { + if (deathmatch && teamplay >= TEAM_CTF) + W_ChangeWeapon(); + } + else if (self.impulse == 23) + TeamFlagStatusReport(); +//--ZOID + + else if (self.impulse == 255) + QuadCheat (); + + self.impulse = 0; +}; + +/* +============ +W_WeaponFrame + +Called every frame so impulse events can be handled as well as possible +============ +*/ +void() W_WeaponFrame = +{ + if (time < self.attack_finished) + return; + + if (lavaGunFired) + { + if (self.weapon == IT_LAVA_NAILGUN || + self.weapon == IT_LAVA_SUPER_NAILGUN) + { + sound (self, CHAN_WEAPON, "lavagun/snail.wav", 1, ATTN_NORM); + } + lavaGunFired = 0; + } + + if (self.impulse) + ImpulseCommands (); + +// check for attack + if (self.button0) + { + SuperDamageSound (); + W_Attack (); + } +}; + +/* +======== +SuperDamageSound + +Plays sound if needed +======== +*/ +void() SuperDamageSound = +{ + if (self.super_damage_finished > time) + { + if (self.super_sound < time) + { + self.super_sound = time + 1; + sound (self, CHAN_BODY, "items/damage3.wav", 1, ATTN_NORM); + } + } + return; +}; + + diff --git a/quakec_rogue/wizard.qc b/quakec_rogue/wizard.qc new file mode 100644 index 0000000..2c2bfa6 --- /dev/null +++ b/quakec_rogue/wizard.qc @@ -0,0 +1,429 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +WIZARD + +============================================================================== +*/ + +$cd /raid/quake/id1/models/a_wizard +$origin 0 0 24 +$base wizbase +$skin wizbase + +$frame hover1 hover2 hover3 hover4 hover5 hover6 hover7 hover8 +$frame hover9 hover10 hover11 hover12 hover13 hover14 hover15 + +$frame fly1 fly2 fly3 fly4 fly5 fly6 fly7 fly8 fly9 fly10 +$frame fly11 fly12 fly13 fly14 + +$frame magatt1 magatt2 magatt3 magatt4 magatt5 magatt6 magatt7 +$frame magatt8 magatt9 magatt10 magatt11 magatt12 magatt13 + +$frame pain1 pain2 pain3 pain4 + +$frame death1 death2 death3 death4 death5 death6 death7 death8 + +/* +============================================================================== + +WIZARD + +If the player moves behind cover before the missile is launched, launch it +at the last visible spot with no velocity leading, in hopes that the player +will duck back out and catch it. +============================================================================== +*/ + +/* +============= +LaunchMissile + +Sets the given entities velocity and angles so that it will hit self.enemy +if self.enemy maintains it's current velocity +0.1 is moderately accurate, 0.0 is totally accurate +============= +*/ +void(entity missile, float mspeed, float accuracy) LaunchMissile = +{ + local vector vec, move; + local float fly; + + makevectors (self.angles); + +// set missile speed + vec = self.enemy.origin + self.enemy.mins + self.enemy.size * 0.7 - missile.origin; + +// calc aproximate time for missile to reach vec + fly = vlen (vec) / mspeed; + +// get the entities xy velocity + move = self.enemy.velocity; + move_z = 0; + +// project the target forward in time + vec = vec + move * fly; + + vec = normalize(vec); + vec = vec + accuracy*v_up*(random()- 0.5) + accuracy*v_right*(random()- 0.5); + + missile.velocity = vec * mspeed; + + missile.angles = '0 0 0'; + missile.angles_y = vectoyaw(missile.velocity); + +// set missile duration + missile.nextthink = time + 5; + missile.think = SUB_Remove; +}; + + +void() wiz_run1; +void() wiz_side1; + +/* +================= +WizardCheckAttack +================= +*/ +float() WizardCheckAttack = +{ + local vector spot1, spot2; + local entity targ; + local float chance; + + if (time < self.attack_finished) + return FALSE; + if (!enemy_vis) + return FALSE; + + if (enemy_range == RANGE_FAR) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + targ = self.enemy; + +// see if any entities are in the way of the shot + spot1 = self.origin + self.view_ofs; + spot2 = targ.origin + targ.view_ofs; + + traceline (spot1, spot2, FALSE, self); + + if (trace_ent != targ) + { // don't have a clear shot, so move to a side + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + return FALSE; + } + + if (enemy_range == RANGE_MELEE) + chance = 0.9; + else if (enemy_range == RANGE_NEAR) + chance = 0.6; + else if (enemy_range == RANGE_MID) + chance = 0.2; + else + chance = 0; + + if (random () < chance) + { + self.attack_state = AS_MISSILE; + return TRUE; + } + + if (enemy_range == RANGE_MID) + { + if (self.attack_state != AS_STRAIGHT) + { + self.attack_state = AS_STRAIGHT; + wiz_run1 (); + } + } + else + { + if (self.attack_state != AS_SLIDING) + { + self.attack_state = AS_SLIDING; + wiz_side1 (); + } + } + + return FALSE; +}; + +/* +================= +WizardAttackFinished +================= +*/ +float() WizardAttackFinished = +{ + if (enemy_range >= RANGE_MID || !enemy_vis) + { + self.attack_state = AS_STRAIGHT; + self.think = wiz_run1; + } + else + { + self.attack_state = AS_SLIDING; + self.think = wiz_side1; + } +}; + +/* +============================================================================== + +FAST ATTACKS + +============================================================================== +*/ + +void() Wiz_FastFire = +{ + local vector vec; + local vector dst; + + if (self.owner.health > 0) + { + self.owner.effects = self.owner.effects | EF_MUZZLEFLASH; + + makevectors (self.enemy.angles); + dst = self.enemy.origin - 13*self.movedir; + + vec = normalize(dst - self.origin); + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + launch_spike (self.origin, vec); + newmis.velocity = vec*600; + newmis.owner = self.owner; + newmis.classname = "wizspike"; + setmodel (newmis, "progs/w_spike.mdl"); + setsize (newmis, VEC_ORIGIN, VEC_ORIGIN); + } + + remove (self); +}; + + +void() Wiz_StartFast = +{ + local entity missile; + + sound (self, CHAN_WEAPON, "wizard/wattack.wav", 1, ATTN_NORM); + self.v_angle = self.angles; + makevectors (self.angles); + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 0.6; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right*14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.8; + missile.think = Wiz_FastFire; + missile.movedir = v_right; + + missile = spawn (); + missile.owner = self; + missile.nextthink = time + 1; + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, self.origin + '0 0 30' + v_forward*14 + v_right* -14); + missile.enemy = self.enemy; + missile.nextthink = time + 0.3; + missile.think = Wiz_FastFire; + missile.movedir = VEC_ORIGIN - v_right; +}; + + + +void() Wiz_idlesound = +{ +local float wr; + wr = random() * 5; + + if (self.waitmin < time) + { + self.waitmin = time + 2; + if (wr > 4.5) + sound (self, CHAN_VOICE, "wizard/widle1.wav", 1, ATTN_IDLE); + if (wr < 1.5) + sound (self, CHAN_VOICE, "wizard/widle2.wav", 1, ATTN_IDLE); + } + return; +}; + +void() wiz_stand1 =[ $hover1, wiz_stand2 ] {ai_stand();}; +void() wiz_stand2 =[ $hover2, wiz_stand3 ] {ai_stand();}; +void() wiz_stand3 =[ $hover3, wiz_stand4 ] {ai_stand();}; +void() wiz_stand4 =[ $hover4, wiz_stand5 ] {ai_stand();}; +void() wiz_stand5 =[ $hover5, wiz_stand6 ] {ai_stand();}; +void() wiz_stand6 =[ $hover6, wiz_stand7 ] {ai_stand();}; +void() wiz_stand7 =[ $hover7, wiz_stand8 ] {ai_stand();}; +void() wiz_stand8 =[ $hover8, wiz_stand1 ] {ai_stand();}; + +void() wiz_walk1 =[ $hover1, wiz_walk2 ] {ai_walk(8); +Wiz_idlesound();}; +void() wiz_walk2 =[ $hover2, wiz_walk3 ] {ai_walk(8);}; +void() wiz_walk3 =[ $hover3, wiz_walk4 ] {ai_walk(8);}; +void() wiz_walk4 =[ $hover4, wiz_walk5 ] {ai_walk(8);}; +void() wiz_walk5 =[ $hover5, wiz_walk6 ] {ai_walk(8);}; +void() wiz_walk6 =[ $hover6, wiz_walk7 ] {ai_walk(8);}; +void() wiz_walk7 =[ $hover7, wiz_walk8 ] {ai_walk(8);}; +void() wiz_walk8 =[ $hover8, wiz_walk1 ] {ai_walk(8);}; + +void() wiz_side1 =[ $hover1, wiz_side2 ] {ai_run(8); +Wiz_idlesound();}; +void() wiz_side2 =[ $hover2, wiz_side3 ] {ai_run(8);}; +void() wiz_side3 =[ $hover3, wiz_side4 ] {ai_run(8);}; +void() wiz_side4 =[ $hover4, wiz_side5 ] {ai_run(8);}; +void() wiz_side5 =[ $hover5, wiz_side6 ] {ai_run(8);}; +void() wiz_side6 =[ $hover6, wiz_side7 ] {ai_run(8);}; +void() wiz_side7 =[ $hover7, wiz_side8 ] {ai_run(8);}; +void() wiz_side8 =[ $hover8, wiz_side1 ] {ai_run(8);}; + +void() wiz_run1 =[ $fly1, wiz_run2 ] {ai_run(16); +Wiz_idlesound(); +}; +void() wiz_run2 =[ $fly2, wiz_run3 ] {ai_run(16);}; +void() wiz_run3 =[ $fly3, wiz_run4 ] {ai_run(16);}; +void() wiz_run4 =[ $fly4, wiz_run5 ] {ai_run(16);}; +void() wiz_run5 =[ $fly5, wiz_run6 ] {ai_run(16);}; +void() wiz_run6 =[ $fly6, wiz_run7 ] {ai_run(16);}; +void() wiz_run7 =[ $fly7, wiz_run8 ] {ai_run(16);}; +void() wiz_run8 =[ $fly8, wiz_run9 ] {ai_run(16);}; +void() wiz_run9 =[ $fly9, wiz_run10 ] {ai_run(16);}; +void() wiz_run10 =[ $fly10, wiz_run11 ] {ai_run(16);}; +void() wiz_run11 =[ $fly11, wiz_run12 ] {ai_run(16);}; +void() wiz_run12 =[ $fly12, wiz_run13 ] {ai_run(16);}; +void() wiz_run13 =[ $fly13, wiz_run14 ] {ai_run(16);}; +void() wiz_run14 =[ $fly14, wiz_run1 ] {ai_run(16);}; + +void() wiz_fast1 =[ $magatt1, wiz_fast2 ] {ai_face();Wiz_StartFast();}; +void() wiz_fast2 =[ $magatt2, wiz_fast3 ] {ai_face();}; +void() wiz_fast3 =[ $magatt3, wiz_fast4 ] {ai_face();}; +void() wiz_fast4 =[ $magatt4, wiz_fast5 ] {ai_face();}; +void() wiz_fast5 =[ $magatt5, wiz_fast6 ] {ai_face();}; +void() wiz_fast6 =[ $magatt6, wiz_fast7 ] {ai_face();}; +void() wiz_fast7 =[ $magatt5, wiz_fast8 ] {ai_face();}; +void() wiz_fast8 =[ $magatt4, wiz_fast9 ] {ai_face();}; +void() wiz_fast9 =[ $magatt3, wiz_fast10 ] {ai_face();}; +void() wiz_fast10 =[ $magatt2, wiz_run1 ] {ai_face();SUB_AttackFinished(2);WizardAttackFinished ();}; + +void() wiz_pain1 =[ $pain1, wiz_pain2 ] {}; +void() wiz_pain2 =[ $pain2, wiz_pain3 ] {}; +void() wiz_pain3 =[ $pain3, wiz_pain4 ] {}; +void() wiz_pain4 =[ $pain4, wiz_run1 ] {}; + +void() wiz_death1 =[ $death1, wiz_death2 ] { + +self.velocity_x = -200 + 400*random(); +self.velocity_y = -200 + 400*random(); +self.velocity_z = 100 + 100*random(); +self.flags = self.flags - (self.flags & FL_ONGROUND); +sound (self, CHAN_VOICE, "wizard/wdeath.wav", 1, ATTN_NORM); +}; +void() wiz_death2 =[ $death2, wiz_death3 ] {}; +void() wiz_death3 =[ $death3, wiz_death4 ]{self.solid = SOLID_NOT;}; +void() wiz_death4 =[ $death4, wiz_death5 ] {}; +void() wiz_death5 =[ $death5, wiz_death6 ] {}; +void() wiz_death6 =[ $death6, wiz_death7 ] {}; +void() wiz_death7 =[ $death7, wiz_death8 ] {}; +void() wiz_death8 =[ $death8, wiz_death8 ] {}; + +void() wiz_die = +{ +// check for gib + if (self.health < -40) + { + sound (self, CHAN_VOICE, "player/udeath.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_wizard.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + return; + } + + wiz_death1 (); +}; + + +void(entity attacker, float damage) Wiz_Pain = +{ + sound (self, CHAN_VOICE, "wizard/wpain.wav", 1, ATTN_NORM); + if (random()*70 > damage) + return; // didn't flinch + + wiz_pain1 (); +}; + + +void() Wiz_Missile = +{ + wiz_fast1(); +}; + +/*QUAKED monster_wizard (1 0 0) (-16 -16 -24) (16 16 40) Ambush +*/ +void() monster_wizard = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/wizard.mdl"); + precache_model ("progs/h_wizard.mdl"); + precache_model ("progs/w_spike.mdl"); + + precache_sound ("wizard/hit.wav"); // used by c code + precache_sound ("wizard/wattack.wav"); + precache_sound ("wizard/wdeath.wav"); + precache_sound ("wizard/widle1.wav"); + precache_sound ("wizard/widle2.wav"); + precache_sound ("wizard/wpain.wav"); + precache_sound ("wizard/wsight.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/wizard.mdl"); + + setsize (self, '-16 -16 -24', '16 16 40'); + self.health = 80; + + self.th_stand = wiz_stand1; + self.th_walk = wiz_walk1; + self.th_run = wiz_run1; + self.th_missile = Wiz_Missile; + self.th_pain = Wiz_Pain; + self.th_die = wiz_die; + + flymonster_start (); +}; diff --git a/quakec_rogue/world.qc b/quakec_rogue/world.qc new file mode 100644 index 0000000..bc6d151 --- /dev/null +++ b/quakec_rogue/world.qc @@ -0,0 +1,458 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + + +void() InitBodyQue; + + +void() main = +{ + dprint ("main function\n"); + +// these are just commands the the prog compiler to copy these files + + precache_file ("progs.dat"); + precache_file ("gfx.wad"); + precache_file ("quake.rc"); + precache_file ("default.cfg"); + + precache_file ("end1.bin"); + precache_file2 ("end2.bin"); + + precache_file ("demo1.dem"); + precache_file ("demo2.dem"); + precache_file ("demo3.dem"); + +// +// these are all of the lumps from the cached.ls files +// + precache_file ("gfx/palette.lmp"); + precache_file ("gfx/colormap.lmp"); + + precache_file2 ("gfx/pop.lmp"); + + precache_file ("gfx/complete.lmp"); + precache_file ("gfx/inter.lmp"); + + precache_file ("gfx/ranking.lmp"); + precache_file ("gfx/vidmodes.lmp"); + precache_file ("gfx/finale.lmp"); + precache_file ("gfx/conback.lmp"); + precache_file ("gfx/qplaque.lmp"); + + precache_file ("gfx/menudot1.lmp"); + precache_file ("gfx/menudot2.lmp"); + precache_file ("gfx/menudot3.lmp"); + precache_file ("gfx/menudot4.lmp"); + precache_file ("gfx/menudot5.lmp"); + precache_file ("gfx/menudot6.lmp"); + + precache_file ("gfx/menuplyr.lmp"); + precache_file ("gfx/bigbox.lmp"); + precache_file ("gfx/dim_modm.lmp"); + precache_file ("gfx/dim_drct.lmp"); + precache_file ("gfx/dim_ipx.lmp"); + precache_file ("gfx/dim_tcp.lmp"); + precache_file ("gfx/dim_mult.lmp"); + precache_file ("gfx/mainmenu.lmp"); + + precache_file ("gfx/box_tl.lmp"); + precache_file ("gfx/box_tm.lmp"); + precache_file ("gfx/box_tr.lmp"); + + precache_file ("gfx/box_ml.lmp"); + precache_file ("gfx/box_mm.lmp"); + precache_file ("gfx/box_mm2.lmp"); + precache_file ("gfx/box_mr.lmp"); + + precache_file ("gfx/box_bl.lmp"); + precache_file ("gfx/box_bm.lmp"); + precache_file ("gfx/box_br.lmp"); + + precache_file ("gfx/sp_menu.lmp"); + precache_file ("gfx/ttl_sgl.lmp"); + precache_file ("gfx/ttl_main.lmp"); + precache_file ("gfx/ttl_cstm.lmp"); + + precache_file ("gfx/mp_menu.lmp"); + + precache_file ("gfx/netmen1.lmp"); + precache_file ("gfx/netmen2.lmp"); + precache_file ("gfx/netmen3.lmp"); + precache_file ("gfx/netmen4.lmp"); + precache_file ("gfx/netmen5.lmp"); + + precache_file ("gfx/sell.lmp"); + + precache_file ("gfx/help0.lmp"); + precache_file ("gfx/help1.lmp"); + precache_file ("gfx/help2.lmp"); + precache_file ("gfx/help3.lmp"); + precache_file ("gfx/help4.lmp"); + precache_file ("gfx/help5.lmp"); + + precache_file ("gfx/pause.lmp"); + precache_file ("gfx/loading.lmp"); + + precache_file ("gfx/p_option.lmp"); + precache_file ("gfx/p_load.lmp"); + precache_file ("gfx/p_save.lmp"); + precache_file ("gfx/p_multi.lmp"); + +// sounds loaded by C code + precache_sound ("misc/menu1.wav"); + precache_sound ("misc/menu2.wav"); + precache_sound ("misc/menu3.wav"); + + precache_sound ("ambience/water1.wav"); + precache_sound ("ambience/wind2.wav"); + +// shareware + precache_file ("maps/start.bsp"); + + precache_file ("maps/e1m1.bsp"); + precache_file ("maps/e1m2.bsp"); + precache_file ("maps/e1m3.bsp"); + precache_file ("maps/e1m4.bsp"); + precache_file ("maps/e1m5.bsp"); + precache_file ("maps/e1m6.bsp"); + precache_file ("maps/e1m7.bsp"); + precache_file ("maps/e1m8.bsp"); + +// registered + precache_file2 ("gfx/pop.lmp"); + + precache_file2 ("maps/e2m1.bsp"); + precache_file2 ("maps/e2m2.bsp"); + precache_file2 ("maps/e2m3.bsp"); + precache_file2 ("maps/e2m4.bsp"); + precache_file2 ("maps/e2m5.bsp"); + precache_file2 ("maps/e2m6.bsp"); + precache_file2 ("maps/e2m7.bsp"); + + precache_file2 ("maps/e3m1.bsp"); + precache_file2 ("maps/e3m2.bsp"); + precache_file2 ("maps/e3m3.bsp"); + precache_file2 ("maps/e3m4.bsp"); + precache_file2 ("maps/e3m5.bsp"); + precache_file2 ("maps/e3m6.bsp"); + precache_file2 ("maps/e3m7.bsp"); + + precache_file2 ("maps/e4m1.bsp"); + precache_file2 ("maps/e4m2.bsp"); + precache_file2 ("maps/e4m3.bsp"); + precache_file2 ("maps/e4m4.bsp"); + precache_file2 ("maps/e4m5.bsp"); + precache_file2 ("maps/e4m6.bsp"); + precache_file2 ("maps/e4m7.bsp"); + precache_file2 ("maps/e4m8.bsp"); + + precache_file2 ("maps/end.bsp"); + + precache_file2 ("maps/dm1.bsp"); + precache_file2 ("maps/dm2.bsp"); + precache_file2 ("maps/dm3.bsp"); + precache_file2 ("maps/dm4.bsp"); + precache_file2 ("maps/dm5.bsp"); + precache_file2 ("maps/dm6.bsp"); +}; + + +entity lastspawn; + +//======================= +/*QUAKED worldspawn (0 0 0) ? +Only used for the world entity. +Set message to the level name. +Set sounds to the cd track to play. + +World Types: +0: medieval +1: metal +2: base +*/ +//======================= +void() worldspawn = +{ + lastspawn = world; + InitBodyQue (); + +// custom map attributes + if (self.model == "maps/e1m8.bsp") + cvar_set ("sv_gravity", "100"); + else + cvar_set ("sv_gravity", "800"); + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + RunePrecache(); // precache the runes + +// sounds used from C physics code + precache_sound ("demon/dland2.wav"); // landing thud + precache_sound ("misc/h2ohit1.wav"); // landing splash + +// setup precaches allways needed + precache_sound ("items/itembk2.wav"); // item respawn sound + precache_sound ("player/plyrjmp8.wav"); // player jump + precache_sound ("player/land.wav"); // player landing + precache_sound ("player/land2.wav"); // player hurt landing + precache_sound ("player/drown1.wav"); // drowning pain + precache_sound ("player/drown2.wav"); // drowning pain + precache_sound ("player/gasp1.wav"); // gasping for air + precache_sound ("player/gasp2.wav"); // taking breath + precache_sound ("player/h2odeath.wav"); // drowning death + + precache_sound ("misc/talk.wav"); // talk + precache_sound ("player/teledth1.wav"); // telefrag + precache_sound ("misc/r_tele1.wav"); // teleport sounds + precache_sound ("misc/r_tele2.wav"); + precache_sound ("misc/r_tele3.wav"); + precache_sound ("misc/r_tele4.wav"); + precache_sound ("misc/r_tele5.wav"); + precache_sound ("weapons/lock4.wav"); // ammo pick up + precache_sound ("weapons/pkup.wav"); // weapon up + precache_sound ("items/armor1.wav"); // armor up + precache_sound ("weapons/lhit.wav"); //lightning + precache_sound ("weapons/lstart.wav"); //lightning start + precache_sound ("items/damage3.wav"); + + precache_sound ("misc/power.wav"); //lightning for boss + +// player gib sounds + precache_sound ("player/gib.wav"); // player gib sound + precache_sound ("player/udeath.wav"); // player gib sound + precache_sound ("player/tornoff2.wav"); // gib sound + +// player pain sounds + + precache_sound ("player/pain1.wav"); + precache_sound ("player/pain2.wav"); + precache_sound ("player/pain3.wav"); + precache_sound ("player/pain4.wav"); + precache_sound ("player/pain5.wav"); + precache_sound ("player/pain6.wav"); + +// player death sounds + precache_sound ("player/death1.wav"); + precache_sound ("player/death2.wav"); + precache_sound ("player/death3.wav"); + precache_sound ("player/death4.wav"); + precache_sound ("player/death5.wav"); + +// ax sounds + precache_sound ("weapons/ax1.wav"); // ax swoosh + precache_sound ("player/axhit1.wav"); // ax hit meat + precache_sound ("player/axhit2.wav"); // ax hit world + + precache_sound ("player/h2ojump.wav"); // player jumping into water + precache_sound ("player/slimbrn2.wav"); // player enter slime + precache_sound ("player/inh2o.wav"); // player enter water + precache_sound ("player/inlava.wav"); // player enter lava + precache_sound ("misc/outwater.wav"); // leaving water sound + + precache_sound ("player/lburn1.wav"); // lava burn + precache_sound ("player/lburn2.wav"); // lava burn + + precache_sound ("misc/water1.wav"); // swimming + precache_sound ("misc/water2.wav"); // swimming + +// PGM Addition Sounds + precache_sound ("enforcer/enfstop.wav"); // shield impact + precache_sound ("plasma/explode.wav"); + precache_sound ("plasma/flight.wav"); + precache_sound ("plasma/fire.wav"); + precache_sound ("shield/hit.wav"); + precache_sound ("belt/use.wav"); + precache_sound ("items/damage2.wav"); + + precache_model ("progs/player.mdl"); + precache_model ("progs/eyes.mdl"); + precache_model ("progs/h_player.mdl"); + precache_model ("progs/gib1.mdl"); + precache_model ("progs/gib2.mdl"); + precache_model ("progs/gib3.mdl"); + + precache_model ("progs/s_bubble.spr"); // drowning bubbles + precache_model ("progs/s_explod.spr"); // sprite explosion + + precache_model ("progs/v_axe.mdl"); + precache_model ("progs/v_shot.mdl"); + precache_model ("progs/v_nail.mdl"); + precache_model ("progs/v_rock.mdl"); + precache_model ("progs/v_shot2.mdl"); + precache_model ("progs/v_nail2.mdl"); + precache_model ("progs/v_rock2.mdl"); + + // Lava Gun models - pgm change + precache_model ("progs/v_lava.mdl"); + precache_model ("progs/v_lava2.mdl"); + precache_model ("progs/v_multi.mdl"); + precache_model ("progs/v_multi2.mdl"); + + precache_model ("progs/bolt.mdl"); // for lightning gun + precache_model ("progs/bolt2.mdl"); // for lightning gun + precache_model ("progs/bolt3.mdl"); // for boss shock + precache_model ("progs/lavaball.mdl"); // for testing + + precache_model ("progs/missile.mdl"); + precache_model ("progs/grenade.mdl"); + precache_model ("progs/spike.mdl"); + precache_model ("progs/s_spike.mdl"); + + // PGM addition + precache_model ("progs/mervup.mdl"); + precache_model ("progs/rockup.mdl"); + precache_model ("progs/rockup_d.mdl"); + precache_model ("progs/lspike.mdl"); + precache_model ("progs/shield.mdl"); + precache_model ("progs/p_shield.mdl"); + + precache_model ("progs/backpack.mdl"); + + precache_model ("progs/zom_gib.mdl"); + + precache_model ("progs/v_light.mdl"); + + precache_model ("progs/plasma.mdl"); + precache_model ("progs/v_plasma.mdl"); + +//ZOID-- + // hook for CTF + precache_model ("progs/hook.mdl"); + precache_model ("progs/v_grpple.mdl"); +// precache_model ("progs/bit.mdl"); +//--ZOID + +// +// Setup light animation tables. 'a' is total darkness, 'z' is maxbright. +// + + // 0 normal + lightstyle(0, "m"); + + // 1 FLICKER (first variety) + lightstyle(1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + lightstyle(2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + lightstyle(3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + lightstyle(4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + lightstyle(5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + lightstyle(6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + lightstyle(7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + lightstyle(8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + lightstyle(9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + lightstyle(10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + lightstyle(11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + lightstyle(63, "a"); +}; + +void() StartFrame = +{ + teamplay = cvar("teamplay"); + skill = cvar("skill"); + cheats_allowed = cvar("sv_cheats"); + + // save off a global here so it gets included in savegames + if (!campaign_valid) { + campaign_valid = TRUE; + campaign = cvar("campaign"); + } else { + cvar_set("campaign", ftos(campaign)); + } + + framecount = framecount + 1; +}; + +/* +============================================================================== + +BODY QUE + +============================================================================== +*/ + +entity bodyque_head; + +void() bodyque = +{ // just here so spawn functions don't complain after the world + // creates bodyques +}; + +void() InitBodyQue = +{ + local entity e; + + bodyque_head = spawn(); + bodyque_head.classname = "bodyque"; + bodyque_head.owner = spawn(); + bodyque_head.owner.classname = "bodyque"; + bodyque_head.owner.owner = spawn(); + bodyque_head.owner.owner.classname = "bodyque"; + bodyque_head.owner.owner.owner = spawn(); + bodyque_head.owner.owner.owner.classname = "bodyque"; + bodyque_head.owner.owner.owner.owner = bodyque_head; +}; + + +// make a body que entry for the given ent so the ent can be +// respawned elsewhere +void(entity ent) CopyToBodyQue = +{ + // PGM - support for new deathmatch skins on corpses too! + bodyque_head.skin = ent.skin; + + bodyque_head.angles = ent.angles; + bodyque_head.model = ent.model; + bodyque_head.modelindex = ent.modelindex; + bodyque_head.frame = ent.frame; + bodyque_head.colormap = ent.colormap; + bodyque_head.movetype = ent.movetype; + bodyque_head.velocity = ent.velocity; + bodyque_head.flags = 0; + setorigin (bodyque_head, ent.origin); + setsize (bodyque_head, ent.mins, ent.maxs); + bodyque_head = bodyque_head.owner; +}; + + diff --git a/quakec_rogue/wrath.qc b/quakec_rogue/wrath.qc new file mode 100644 index 0000000..a1ceb87 --- /dev/null +++ b/quakec_rogue/wrath.qc @@ -0,0 +1,362 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +// wrath.qc + +$skin wrthskin + +$frame wrthfrme + +$frame wrthwk01 wrthwk02 wrthwk03 wrthwk04 wrthwk05 wrthwk06 +$frame wrthwk07 wrthwk08 wrthwk09 wrthwk10 wrthwk11 wrthwk12 + +$frame wrthaa01 wrthaa02 wrthaa03 wrthaa04 wrthaa05 wrthaa06 wrthaa07 +$frame wrthaa08 wrthaa09 wrthaa10 wrthaa11 wrthaa12 wrthaa13 wrthaa14 + +$frame wrthab01 wrthab02 wrthab03 wrthab04 wrthab05 wrthab06 wrthab07 +$frame wrthab08 wrthab09 wrthab10 wrthab11 wrthab12 wrthab13 + +$frame wrthac01 wrthac02 wrthac03 wrthac04 wrthac05 +$frame wrthac06 wrthac07 wrthac08 wrthac09 wrthac10 +$frame wrthac11 wrthac12 wrthac13 wrthac14 wrthac15 + +$frame wrthpa01 wrthpa02 wrthpa03 wrthpa04 wrthpa05 wrthpa06 + +$frame wrthpb01 wrthpb02 wrthpb03 wrthpb04 wrthpb05 wrthpb06 +$frame wrthpb07 wrthpb08 wrthpb09 wrthpb10 wrthpb11 + +$frame wrthdt01 wrthdt02 wrthdt03 wrthdt04 wrthdt05 +$frame wrthdt06 wrthdt07 wrthdt08 wrthdt09 wrthdt10 +$frame wrthdt11 wrthdt12 wrthdt13 wrthdt14 wrthdt15 + +void() WrathMissileTouch; +void() WrathHome; +void(float AttackNumber) WrathMissile; + +void() wrath_stand1 =[ $wrthwk01, wrath_stand1 ] { ai_stand();}; + +void() wrath_walk01 =[ $wrthwk01, wrath_walk02 ] { ai_walk(8); }; +void() wrath_walk02 =[ $wrthwk02, wrath_walk03 ] { ai_walk(8); }; +void() wrath_walk03 =[ $wrthwk03, wrath_walk04 ] { ai_walk(8); }; +void() wrath_walk04 =[ $wrthwk04, wrath_walk05 ] { ai_walk(8); }; +void() wrath_walk05 =[ $wrthwk05, wrath_walk06 ] { ai_walk(8); }; +void() wrath_walk06 =[ $wrthwk06, wrath_walk07 ] { ai_walk(8); }; +void() wrath_walk07 =[ $wrthwk07, wrath_walk08 ] { ai_walk(8); }; +void() wrath_walk08 =[ $wrthwk08, wrath_walk09 ] { ai_walk(8); }; +void() wrath_walk09 =[ $wrthwk09, wrath_walk10 ] { ai_walk(8); }; +void() wrath_walk10 =[ $wrthwk10, wrath_walk11 ] { ai_walk(8); }; +void() wrath_walk11 =[ $wrthwk11, wrath_walk12 ] { ai_walk(8); }; +void() wrath_walk12 =[ $wrthwk12, wrath_walk01 ] { ai_walk(8); }; + +void() wrath_run01 =[ $wrthwk01, wrath_run02 ] { ai_run(12); }; +void() wrath_run02 =[ $wrthwk02, wrath_run03 ] { ai_run(12); }; +void() wrath_run03 =[ $wrthwk03, wrath_run04 ] { ai_run(12); }; +void() wrath_run04 =[ $wrthwk04, wrath_run05 ] { ai_run(12); }; +void() wrath_run05 =[ $wrthwk05, wrath_run06 ] { ai_run(12); }; +void() wrath_run06 =[ $wrthwk06, wrath_run07 ] { ai_run(12); }; +void() wrath_run07 =[ $wrthwk07, wrath_run08 ] { ai_run(12); }; +void() wrath_run08 =[ $wrthwk08, wrath_run09 ] { ai_run(12); }; +void() wrath_run09 =[ $wrthwk09, wrath_run10 ] { ai_run(12); }; +void() wrath_run10 =[ $wrthwk10, wrath_run11 ] { ai_run(12); }; +void() wrath_run11 =[ $wrthwk11, wrath_run12 ] { ai_run(12); }; +void() wrath_run12 =[ $wrthwk12, wrath_run01 ] { ai_run(12); }; + +void() wrath_at_a01 =[ $wrthaa01, wrath_at_a02 ] {ai_charge(12);}; +void() wrath_at_a02 =[ $wrthaa02, wrath_at_a03 ] {ai_charge(12);}; +void() wrath_at_a03 =[ $wrthaa03, wrath_at_a04 ] {ai_charge(12);}; +void() wrath_at_a04 =[ $wrthaa04, wrath_at_a05 ] {ai_charge(12);}; +void() wrath_at_a05 =[ $wrthaa05, wrath_at_a06 ] {ai_charge(12);}; +void() wrath_at_a06 =[ $wrthaa06, wrath_at_a07 ] {ai_charge(12);}; +void() wrath_at_a07 =[ $wrthaa07, wrath_at_a08 ] {ai_charge(12);}; +void() wrath_at_a08 =[ $wrthaa08, wrath_at_a09 ] {ai_charge(12);}; +void() wrath_at_a09 =[ $wrthaa09, wrath_at_a10 ] {ai_charge(12);}; +void() wrath_at_a10 =[ $wrthaa10, wrath_at_a11 ] {ai_charge(12);}; +void() wrath_at_a11 =[ $wrthaa11, wrath_at_a12 ] {WrathMissile(1);}; +void() wrath_at_a12 =[ $wrthaa12, wrath_at_a13 ] {ai_charge(12);}; +void() wrath_at_a13 =[ $wrthaa13, wrath_at_a14 ] {ai_charge(12);}; +void() wrath_at_a14 =[ $wrthaa14, wrath_run01 ] {ai_charge(12);}; + +void() wrath_at_b01 =[ $wrthab01, wrath_at_b02 ] {ai_charge(12);}; +void() wrath_at_b02 =[ $wrthab02, wrath_at_b03 ] {ai_charge(12);}; +void() wrath_at_b03 =[ $wrthab03, wrath_at_b04 ] {ai_charge(12);}; +void() wrath_at_b04 =[ $wrthab04, wrath_at_b05 ] {ai_charge(12);}; +void() wrath_at_b05 =[ $wrthab05, wrath_at_b06 ] {ai_charge(12);}; +void() wrath_at_b06 =[ $wrthab06, wrath_at_b07 ] {WrathMissile(2);}; +void() wrath_at_b07 =[ $wrthab07, wrath_at_b08 ] {ai_charge(12);}; +void() wrath_at_b08 =[ $wrthab08, wrath_at_b09 ] {ai_charge(12);}; +void() wrath_at_b09 =[ $wrthab09, wrath_at_b10 ] {ai_charge(12);}; +void() wrath_at_b10 =[ $wrthab10, wrath_at_b11 ] {ai_charge(12);}; +void() wrath_at_b11 =[ $wrthab11, wrath_at_b12 ] {ai_charge(12);}; +void() wrath_at_b12 =[ $wrthab12, wrath_at_b13 ] {ai_charge(12);}; +void() wrath_at_b13 =[ $wrthab13, wrath_run01 ] {ai_charge(12);}; + +void() wrath_at_c01 =[ $wrthac01, wrath_at_c02 ] {ai_charge(12);}; +void() wrath_at_c02 =[ $wrthac02, wrath_at_c03 ] {ai_charge(12);}; +void() wrath_at_c03 =[ $wrthac03, wrath_at_c04 ] {ai_charge(12);}; +void() wrath_at_c04 =[ $wrthac04, wrath_at_c05 ] {ai_charge(12);}; +void() wrath_at_c05 =[ $wrthac05, wrath_at_c06 ] {ai_charge(12);}; +void() wrath_at_c06 =[ $wrthac06, wrath_at_c07 ] {ai_charge(12);}; +void() wrath_at_c07 =[ $wrthac07, wrath_at_c08 ] {WrathMissile(3);}; +void() wrath_at_c08 =[ $wrthac08, wrath_at_c09 ] {ai_charge(12);}; +void() wrath_at_c09 =[ $wrthac09, wrath_at_c10 ] {ai_charge(12);}; +void() wrath_at_c10 =[ $wrthac10, wrath_at_c11 ] {ai_charge(12);}; +void() wrath_at_c11 =[ $wrthac11, wrath_at_c12 ] {ai_charge(12);}; +void() wrath_at_c12 =[ $wrthac12, wrath_at_c13 ] {ai_charge(12);}; +void() wrath_at_c13 =[ $wrthac13, wrath_at_c14 ] {ai_charge(12);}; +void() wrath_at_c14 =[ $wrthac14, wrath_at_c15 ] {ai_charge(12);}; +void() wrath_at_c15 =[ $wrthac15, wrath_run01 ] {ai_charge(12);}; + +void() wrath_attack = +{ + local float r; + + r = random(); + + if (r < 0.25) + { + wrath_at_a01 (); + } + else if (r < 0.65) + { + wrath_at_b01 (); + } + else + { + wrath_at_c01 (); + } + sound (self, CHAN_VOICE, "wrath/watt.wav", 1, ATTN_NORM); +}; + +void() wrath_pn_a01 =[ $wrthpa01, wrath_pn_a02 ] {}; +void() wrath_pn_a02 =[ $wrthpa02, wrath_pn_a03 ] {}; +void() wrath_pn_a03 =[ $wrthpa03, wrath_pn_a04 ] {}; +void() wrath_pn_a04 =[ $wrthpa04, wrath_pn_a05 ] {}; +void() wrath_pn_a05 =[ $wrthpa05, wrath_pn_a06 ] {}; +void() wrath_pn_a06 =[ $wrthpa06, wrath_run01 ] {}; + +void() wrath_pn_b01 =[ $wrthpb01, wrath_pn_b02 ] {}; +void() wrath_pn_b02 =[ $wrthpb02, wrath_pn_b03 ] {}; +void() wrath_pn_b03 =[ $wrthpb03, wrath_pn_b04 ] {}; +void() wrath_pn_b04 =[ $wrthpb04, wrath_pn_b05 ] {}; +void() wrath_pn_b05 =[ $wrthpb05, wrath_pn_b06 ] {}; +void() wrath_pn_b06 =[ $wrthpb06, wrath_pn_b07 ] {}; +void() wrath_pn_b07 =[ $wrthpb07, wrath_pn_b08 ] {}; +void() wrath_pn_b08 =[ $wrthpb08, wrath_pn_b09 ] {}; +void() wrath_pn_b09 =[ $wrthpb09, wrath_pn_b10 ] {}; +void() wrath_pn_b10 =[ $wrthpb10, wrath_pn_b11 ] {}; +void() wrath_pn_b11 =[ $wrthpb11, wrath_run01 ] {}; + + +void() wrath_pain = +{ + local float r; + + if (self.pain_finished > time) + return; + + r = random(); + + if (r > 0.1) + { + self.pain_finished = time + 0.5; + return; + } + + if (r < 0.07) + wrath_pn_a01 (); + else + wrath_pn_b01 (); + + self.pain_finished = time + 3; + sound (self, CHAN_VOICE, "wrath/wpain.wav", 1, ATTN_NORM); +}; + +void() wrath_die02 =[ $wrthdt02, wrath_die03 ] + { sound (self, CHAN_VOICE, "wrath/wdthc.wav", 1, ATTN_NORM); }; +void() wrath_die03 =[ $wrthdt03, wrath_die04 ] {}; +void() wrath_die04 =[ $wrthdt04, wrath_die05 ] {}; +void() wrath_die05 =[ $wrthdt05, wrath_die07 ] {}; +void() wrath_die07 =[ $wrthdt07, wrath_die09 ] {}; +void() wrath_die09 =[ $wrthdt09, wrath_die11 ] {}; +void() wrath_die11 =[ $wrthdt11, wrath_die13 ] {}; +void() wrath_die13 =[ $wrthdt13, wrath_die15 ] {}; +void() wrath_die15 =[ $wrthdt15, wrath_die15 ] +{ + ThrowGib ("progs/wrthgib1.mdl", self.health); + ThrowGib ("progs/wrthgib2.mdl", self.health); + ThrowGib ("progs/wrthgib3.mdl", self.health); + T_RadiusDamage (self, self, 80, world); + + self.origin = self.origin + '0 0 24'; + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION2); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + WriteByte (MSG_BROADCAST, 0); + WriteByte (MSG_BROADCAST, 4); + + remove (self); +}; + +/*QUAKED monster_wrath (1 0 0) (-16 -16 -24) (16 16 32) Ambush +*/ +void() monster_wrath = +{ + if (deathmatch) + { + remove(self); + return; + } + precache_model ("progs/wrath.mdl"); + precache_model ("progs/w_ball.mdl"); + precache_model ("progs/wrthgib1.mdl"); + precache_model ("progs/wrthgib2.mdl"); + precache_model ("progs/wrthgib3.mdl"); + + precache_sound ("wrath/wsee.wav"); + precache_sound ("wrath/watt.wav"); + precache_sound ("wrath/wpain.wav"); + precache_sound ("wrath/wdthc.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/wrath.mdl"); + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + + self.health = 400; + self.yaw_speed = 35; + + self.th_stand = wrath_stand1; + self.th_walk = wrath_walk01; + self.th_run = wrath_run01; + self.th_missile = wrath_attack; + self.th_pain = wrath_pain; + self.th_die = wrath_die02; + + flymonster_start (); +}; + +/* +================ +WrathMissile +================ +*/ +void(float AttackNumber) WrathMissile = +{ + local entity missile; + local vector dir, org; + local float dist, flytime; + + dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + dist = vlen (self.enemy.origin - self.origin); + flytime = dist * 0.002; + if (flytime < 0.1) + flytime = 0.1; + + self.effects = self.effects | EF_MUZZLEFLASH; + + makevectors (self.angles); + + missile = spawn (); + missile.owner = self; + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + setmodel (missile, "progs/w_ball.mdl"); + + setsize (missile, '0 0 0', '0 0 0'); + + if ( AttackNumber == 1) + { + org = self.origin + v_forward*20 + v_up*12; // 20 // 44; + } + else if ( AttackNumber == 2) + { + org = self.origin + v_forward*18 + v_up*10; // 18 // 42; + } + else if ( AttackNumber == 3) + { + org = self.origin + v_forward*12 + v_up*12 + v_right*20; +// up20 + } + else if ( AttackNumber == 4) + { + org = self.origin + v_forward*20 + v_up*16; + } + + setorigin ( missile, org ); + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.enemy = self.enemy; + missile.touch = WrathMissileTouch; + missile.nextthink = time + 0.1; + missile.think = WrathHome; + + self.attack_finished = time + 2; +}; + +void() WrathHome = +{ + if (self.enemy.health < 1) + { + remove(self); + return; + } + + if (skill == 3) + ai_track (self.enemy, 550); + else + ai_track (self.enemy, 400); + + self.nextthink = time + 0.1; + self.think = WrathHome; +}; + +void() WrathMissileTouch = +{ + if (other == self.owner || + other.classname == "monster_wrath" || + other.classname == "monster_super_wrath") + { + remove(self); + return; // don't explode on any wraths + + } + + if (other.classname == "monster_zombie") + T_Damage (other, self, self, 110); + + T_RadiusDamage (self, self.owner, 20, world); + sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); + + WriteByte (MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte (MSG_BROADCAST, TE_EXPLOSION); + WriteCoord (MSG_BROADCAST, self.origin_x); + WriteCoord (MSG_BROADCAST, self.origin_y); + WriteCoord (MSG_BROADCAST, self.origin_z); + + self.velocity = '0 0 0'; + self.touch = SUB_Null; + setmodel (self, "progs/s_explod.spr"); + self.solid = SOLID_NOT; + s_explode1 (); +}; diff --git a/quakec_rogue/zombie.qc b/quakec_rogue/zombie.qc new file mode 100644 index 0000000..3fb611f --- /dev/null +++ b/quakec_rogue/zombie.qc @@ -0,0 +1,700 @@ +/* Copyright (C) 1996-2022 id Software LLC + + 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 + + See file, 'COPYING', for details. +*/ + +/* +============================================================================== + +ZOMBIE + +============================================================================== +*/ +$cd /raid/quake/id1/models/zombie + +$origin 0 0 24 + +$base base +$skin skin + +$frame stand1 stand2 stand3 stand4 stand5 stand6 stand7 stand8 +$frame stand9 stand10 stand11 stand12 stand13 stand14 stand15 + +$frame walk1 walk2 walk3 walk4 walk5 walk6 walk7 walk8 walk9 walk10 walk11 +$frame walk12 walk13 walk14 walk15 walk16 walk17 walk18 walk19 + +$frame run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 +$frame run13 run14 run15 run16 run17 run18 + +$frame atta1 atta2 atta3 atta4 atta5 atta6 atta7 atta8 atta9 atta10 atta11 +$frame atta12 atta13 + +$frame attb1 attb2 attb3 attb4 attb5 attb6 attb7 attb8 attb9 attb10 attb11 +$frame attb12 attb13 attb14 + +$frame attc1 attc2 attc3 attc4 attc5 attc6 attc7 attc8 attc9 attc10 attc11 +$frame attc12 + +$frame paina1 paina2 paina3 paina4 paina5 paina6 paina7 paina8 paina9 paina10 +$frame paina11 paina12 + +$frame painb1 painb2 painb3 painb4 painb5 painb6 painb7 painb8 painb9 painb10 +$frame painb11 painb12 painb13 painb14 painb15 painb16 painb17 painb18 painb19 +$frame painb20 painb21 painb22 painb23 painb24 painb25 painb26 painb27 painb28 + +$frame painc1 painc2 painc3 painc4 painc5 painc6 painc7 painc8 painc9 painc10 +$frame painc11 painc12 painc13 painc14 painc15 painc16 painc17 painc18 + +$frame paind1 paind2 paind3 paind4 paind5 paind6 paind7 paind8 paind9 paind10 +$frame paind11 paind12 paind13 + +$frame paine1 paine2 paine3 paine4 paine5 paine6 paine7 paine8 paine9 paine10 +$frame paine11 paine12 paine13 paine14 paine15 paine16 paine17 paine18 paine19 +$frame paine20 paine21 paine22 paine23 paine24 paine25 paine26 paine27 paine28 +$frame paine29 paine30 + +$frame cruc_1 cruc_2 cruc_3 cruc_4 cruc_5 cruc_6 cruc_7 +$frame cruc_8 cruc_9 cruc_10 cruc_11 cruc_12 cruc_13 +$frame cruc_14 cruc_15 cruc_16 + +float SPAWN_CRUCIFIED = 1; +float SPAWN_LYING = 2; +float SPAWN_AMBUSH = 4; +float SPAWN_SNEAKY = 8; + +//============================================================================= + +.float inpain; +void() zombie_escape1; + +void() zombie_stand1 =[ $stand1, zombie_stand2 ] {ai_stand();}; +void() zombie_stand2 =[ $stand2, zombie_stand3 ] {ai_stand();}; +void() zombie_stand3 =[ $stand3, zombie_stand4 ] {ai_stand();}; +void() zombie_stand4 =[ $stand4, zombie_stand5 ] {ai_stand();}; +void() zombie_stand5 =[ $stand5, zombie_stand6 ] {ai_stand();}; +void() zombie_stand6 =[ $stand6, zombie_stand7 ] {ai_stand();}; +void() zombie_stand7 =[ $stand7, zombie_stand8 ] {ai_stand();}; +void() zombie_stand8 =[ $stand8, zombie_stand9 ] {ai_stand();}; +void() zombie_stand9 =[ $stand9, zombie_stand10 ] {ai_stand();}; +void() zombie_stand10 =[ $stand10, zombie_stand11 ] {ai_stand();}; +void() zombie_stand11 =[ $stand11, zombie_stand12 ] {ai_stand();}; +void() zombie_stand12 =[ $stand12, zombie_stand13 ] {ai_stand();}; +void() zombie_stand13 =[ $stand13, zombie_stand14 ] {ai_stand();}; +void() zombie_stand14 =[ $stand14, zombie_stand15 ] {ai_stand();}; +void() zombie_stand15 =[ $stand15, zombie_stand1 ] {ai_stand();}; + +// zombie_lurk is an improved version of what zombie did in zombie_crucXX +// with an added check for enemies if they're sneaky types... +void() zombie_lurk = +{ + if (self.spawnflags & SPAWN_SNEAKY) + { + // not pausing before attack, but looking for player. + if (!self.pain_finished) + { + if(FindTarget ()) + return; + } + else + { + // if we've waited long enough, pounce! + if ( self.pain_finished < time ) + { + self.think = zombie_escape1; + self.nextthink = time + 0.1; + return; + } + } + } + self.nextthink = time + 0.1 + random()*0.1; +}; + +void() zombie_cruc1 = [ $cruc_1, zombie_cruc2 ] + { + zombie_lurk(); + if (random() < 0.1) + sound (self, CHAN_VOICE, "zombie/idle_w2.wav", 1, ATTN_STATIC); + }; +void() zombie_cruc2 = [ $cruc_2, zombie_cruc3 ] { zombie_lurk(); }; +void() zombie_cruc3 = [ $cruc_3, zombie_cruc4 ] { zombie_lurk(); }; +void() zombie_cruc4 = [ $cruc_4, zombie_cruc5 ] { zombie_lurk(); }; +void() zombie_cruc5 = [ $cruc_5, zombie_cruc6 ] { zombie_lurk(); }; +void() zombie_cruc6 = [ $cruc_6, zombie_cruc1 ] { zombie_lurk(); }; + +void() zombie_walk1 =[ $walk1, zombie_walk2 ] {ai_walk(0);}; +void() zombie_walk2 =[ $walk2, zombie_walk3 ] {ai_walk(2);}; +void() zombie_walk3 =[ $walk3, zombie_walk4 ] {ai_walk(3);}; +void() zombie_walk4 =[ $walk4, zombie_walk5 ] {ai_walk(2);}; +void() zombie_walk5 =[ $walk5, zombie_walk6 ] {ai_walk(1);}; +void() zombie_walk6 =[ $walk6, zombie_walk7 ] {ai_walk(0);}; +void() zombie_walk7 =[ $walk7, zombie_walk8 ] {ai_walk(0);}; +void() zombie_walk8 =[ $walk8, zombie_walk9 ] {ai_walk(0);}; +void() zombie_walk9 =[ $walk9, zombie_walk10 ] {ai_walk(0);}; +void() zombie_walk10 =[ $walk10, zombie_walk11 ] {ai_walk(0);}; +void() zombie_walk11 =[ $walk11, zombie_walk12 ] {ai_walk(2);}; +void() zombie_walk12 =[ $walk12, zombie_walk13 ] {ai_walk(2);}; +void() zombie_walk13 =[ $walk13, zombie_walk14 ] {ai_walk(1);}; +void() zombie_walk14 =[ $walk14, zombie_walk15 ] {ai_walk(0);}; +void() zombie_walk15 =[ $walk15, zombie_walk16 ] {ai_walk(0);}; +void() zombie_walk16 =[ $walk16, zombie_walk17 ] {ai_walk(0);}; +void() zombie_walk17 =[ $walk17, zombie_walk18 ] {ai_walk(0);}; +void() zombie_walk18 =[ $walk18, zombie_walk19 ] {ai_walk(0);}; +void() zombie_walk19 =[ $walk19, zombie_walk1 ] { +ai_walk(0); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE);}; + +void() zombie_run1 =[ $run1, zombie_run2 ] {ai_run(1);self.inpain = 0;}; +void() zombie_run2 =[ $run2, zombie_run3 ] {ai_run(1);}; +void() zombie_run3 =[ $run3, zombie_run4 ] {ai_run(0);}; +void() zombie_run4 =[ $run4, zombie_run5 ] {ai_run(1);}; +void() zombie_run5 =[ $run5, zombie_run6 ] {ai_run(2);}; +void() zombie_run6 =[ $run6, zombie_run7 ] {ai_run(3);}; +void() zombie_run7 =[ $run7, zombie_run8 ] {ai_run(4);}; +void() zombie_run8 =[ $run8, zombie_run9 ] {ai_run(4);}; +void() zombie_run9 =[ $run9, zombie_run10 ] {ai_run(2);}; +void() zombie_run10 =[ $run10, zombie_run11 ] {ai_run(0);}; +void() zombie_run11 =[ $run11, zombie_run12 ] {ai_run(0);}; +void() zombie_run12 =[ $run12, zombie_run13 ] {ai_run(0);}; +void() zombie_run13 =[ $run13, zombie_run14 ] {ai_run(2);}; +void() zombie_run14 =[ $run14, zombie_run15 ] {ai_run(4);}; +void() zombie_run15 =[ $run15, zombie_run16 ] {ai_run(6);}; +void() zombie_run16 =[ $run16, zombie_run17 ] {ai_run(7);}; +void() zombie_run17 =[ $run17, zombie_run18 ] {ai_run(3);}; +void() zombie_run18 =[ $run18, zombie_run1 ] { +ai_run(8); +if (random() < 0.2) + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); +if (random() > 0.8) + sound (self, CHAN_VOICE, "zombie/z_idle1.wav", 1, ATTN_IDLE); +}; + +/* +============================================================================= + +ATTACKS + +============================================================================= +*/ + +void() ZombieGrenadeTouch = +{ + if (other == self.owner) + return; // don't explode on owner + if (other.takedamage) + { + T_Damage (other, self, self.owner, 10 ); + sound (self, CHAN_WEAPON, "zombie/z_hit.wav", 1, ATTN_NORM); + remove (self); + return; + } + sound (self, CHAN_WEAPON, "zombie/z_miss.wav", 1, ATTN_NORM); // bounce sound + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.touch = SUB_Remove; +}; + +/* +================ +ZombieFireGrenade +================ +*/ +void(vector st) ZombieFireGrenade = +{ + local entity missile, mpuff; + local vector org; + + sound (self, CHAN_WEAPON, "zombie/z_shot1.wav", 1, ATTN_NORM); + + missile = spawn (); + missile.owner = self; + missile.movetype = MOVETYPE_BOUNCE; + missile.solid = SOLID_BBOX; + +// calc org + org = self.origin + st_x * v_forward + st_y * v_right + (st_z - 24) * v_up; + +// set missile speed + + makevectors (self.angles); + + missile.velocity = normalize(self.enemy.origin - org); + missile.velocity = missile.velocity * 600; + missile.velocity_z = 200; + + missile.avelocity = '3000 1000 2000'; + + missile.touch = ZombieGrenadeTouch; + +// set missile duration + missile.nextthink = time + 2.5; + missile.think = SUB_Remove; + + setmodel (missile, "progs/zom_gib.mdl"); + setsize (missile, '0 0 0', '0 0 0'); + setorigin (missile, org); +}; + + +void() zombie_atta1 =[ $atta1, zombie_atta2 ] {ai_face();}; +void() zombie_atta2 =[ $atta2, zombie_atta3 ] {ai_face();}; +void() zombie_atta3 =[ $atta3, zombie_atta4 ] {ai_face();}; +void() zombie_atta4 =[ $atta4, zombie_atta5 ] {ai_face();}; +void() zombie_atta5 =[ $atta5, zombie_atta6 ] {ai_face();}; +void() zombie_atta6 =[ $atta6, zombie_atta7 ] {ai_face();}; +void() zombie_atta7 =[ $atta7, zombie_atta8 ] {ai_face();}; +void() zombie_atta8 =[ $atta8, zombie_atta9 ] {ai_face();}; +void() zombie_atta9 =[ $atta9, zombie_atta10 ] {ai_face();}; +void() zombie_atta10 =[ $atta10, zombie_atta11 ] {ai_face();}; +void() zombie_atta11 =[ $atta11, zombie_atta12 ] {ai_face();}; +void() zombie_atta12 =[ $atta12, zombie_atta13 ] {ai_face();}; +void() zombie_atta13 =[ $atta13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -22 30');}; + +void() zombie_attb1 =[ $attb1, zombie_attb2 ] {ai_face();}; +void() zombie_attb2 =[ $attb2, zombie_attb3 ] {ai_face();}; +void() zombie_attb3 =[ $attb3, zombie_attb4 ] {ai_face();}; +void() zombie_attb4 =[ $attb4, zombie_attb5 ] {ai_face();}; +void() zombie_attb5 =[ $attb5, zombie_attb6 ] {ai_face();}; +void() zombie_attb6 =[ $attb6, zombie_attb7 ] {ai_face();}; +void() zombie_attb7 =[ $attb7, zombie_attb8 ] {ai_face();}; +void() zombie_attb8 =[ $attb8, zombie_attb9 ] {ai_face();}; +void() zombie_attb9 =[ $attb9, zombie_attb10 ] {ai_face();}; +void() zombie_attb10 =[ $attb10, zombie_attb11 ] {ai_face();}; +void() zombie_attb11 =[ $attb11, zombie_attb12 ] {ai_face();}; +void() zombie_attb12 =[ $attb12, zombie_attb13 ] {ai_face();}; +void() zombie_attb13 =[ $attb13, zombie_attb14 ] {ai_face();}; +void() zombie_attb14 =[ $attb13, zombie_run1 ] {ai_face();ZombieFireGrenade('-10 -24 29');}; + +void() zombie_attc1 =[ $attc1, zombie_attc2 ] {ai_face();}; +void() zombie_attc2 =[ $attc2, zombie_attc3 ] {ai_face();}; +void() zombie_attc3 =[ $attc3, zombie_attc4 ] {ai_face();}; +void() zombie_attc4 =[ $attc4, zombie_attc5 ] {ai_face();}; +void() zombie_attc5 =[ $attc5, zombie_attc6 ] {ai_face();}; +void() zombie_attc6 =[ $attc6, zombie_attc7 ] {ai_face();}; +void() zombie_attc7 =[ $attc7, zombie_attc8 ] {ai_face();}; +void() zombie_attc8 =[ $attc8, zombie_attc9 ] {ai_face();}; +void() zombie_attc9 =[ $attc9, zombie_attc10 ] {ai_face();}; +void() zombie_attc10 =[ $attc10, zombie_attc11 ] {ai_face();}; +void() zombie_attc11 =[ $attc11, zombie_attc12 ] {ai_face();}; +void() zombie_attc12 =[ $attc12, zombie_run1 ] {ai_face();ZombieFireGrenade('-12 -19 29');}; + +void() zombie_missile = +{ + local float r; + + droptofloor(); + r = random(); + + if (r < 0.3) + zombie_atta1 (); + else if (r < 0.6) + zombie_attb1 (); + else + zombie_attc1 (); +}; + + +/* +============================================================================= + +PAIN + +============================================================================= +*/ + +void() zombie_paina1 =[ $paina1, zombie_paina2 ] {sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM);}; +void() zombie_paina2 =[ $paina2, zombie_paina3 ] {ai_painforward(3);}; +void() zombie_paina3 =[ $paina3, zombie_paina4 ] {ai_painforward(1);}; +void() zombie_paina4 =[ $paina4, zombie_paina5 ] {ai_pain(1);}; +void() zombie_paina5 =[ $paina5, zombie_paina6 ] {ai_pain(3);}; +void() zombie_paina6 =[ $paina6, zombie_paina7 ] {ai_pain(1);}; +void() zombie_paina7 =[ $paina7, zombie_paina8 ] {}; +void() zombie_paina8 =[ $paina8, zombie_paina9 ] {}; +void() zombie_paina9 =[ $paina9, zombie_paina10 ] {}; +void() zombie_paina10 =[ $paina10, zombie_paina11 ] {}; +void() zombie_paina11 =[ $paina11, zombie_paina12 ] {}; +void() zombie_paina12 =[ $paina12, zombie_run1 ] {}; + +void() zombie_painb1 =[ $painb1, zombie_painb2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painb2 =[ $painb2, zombie_painb3 ] {ai_pain(2);}; +void() zombie_painb3 =[ $painb3, zombie_painb4 ] {ai_pain(8);}; +void() zombie_painb4 =[ $painb4, zombie_painb5 ] {ai_pain(6);}; +void() zombie_painb5 =[ $painb5, zombie_painb6 ] {ai_pain(2);}; +void() zombie_painb6 =[ $painb6, zombie_painb7 ] {}; +void() zombie_painb7 =[ $painb7, zombie_painb8 ] {}; +void() zombie_painb8 =[ $painb8, zombie_painb9 ] {}; +void() zombie_painb9 =[ $painb9, zombie_painb10 ] {sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM);}; +void() zombie_painb10 =[ $painb10, zombie_painb11 ] {}; +void() zombie_painb11 =[ $painb11, zombie_painb12 ] {}; +void() zombie_painb12 =[ $painb12, zombie_painb13 ] {}; +void() zombie_painb13 =[ $painb13, zombie_painb14 ] {}; +void() zombie_painb14 =[ $painb14, zombie_painb15 ] {}; +void() zombie_painb15 =[ $painb15, zombie_painb16 ] {}; +void() zombie_painb16 =[ $painb16, zombie_painb17 ] {}; +void() zombie_painb17 =[ $painb17, zombie_painb18 ] {}; +void() zombie_painb18 =[ $painb18, zombie_painb19 ] {}; +void() zombie_painb19 =[ $painb19, zombie_painb20 ] {}; +void() zombie_painb20 =[ $painb20, zombie_painb21 ] {}; +void() zombie_painb21 =[ $painb21, zombie_painb22 ] {}; +void() zombie_painb22 =[ $painb22, zombie_painb23 ] {}; +void() zombie_painb23 =[ $painb23, zombie_painb24 ] {}; +void() zombie_painb24 =[ $painb24, zombie_painb25 ] {}; +void() zombie_painb25 =[ $painb25, zombie_painb26 ] {ai_painforward(1);}; +void() zombie_painb26 =[ $painb26, zombie_painb27 ] {}; +void() zombie_painb27 =[ $painb27, zombie_painb28 ] {}; +void() zombie_painb28 =[ $painb28, zombie_run1 ] {}; + +void() zombie_painc1 =[ $painc1, zombie_painc2 ] {sound (self, CHAN_VOICE, "zombie/z_pain1.wav", 1, ATTN_NORM);}; +void() zombie_painc2 =[ $painc2, zombie_painc3 ] {}; +void() zombie_painc3 =[ $painc3, zombie_painc4 ] {ai_pain(3);}; +void() zombie_painc4 =[ $painc4, zombie_painc5 ] {ai_pain(1);}; +void() zombie_painc5 =[ $painc5, zombie_painc6 ] {}; +void() zombie_painc6 =[ $painc6, zombie_painc7 ] {}; +void() zombie_painc7 =[ $painc7, zombie_painc8 ] {}; +void() zombie_painc8 =[ $painc8, zombie_painc9 ] {}; +void() zombie_painc9 =[ $painc9, zombie_painc10 ] {}; +void() zombie_painc10 =[ $painc10, zombie_painc11 ] {}; +void() zombie_painc11 =[ $painc11, zombie_painc12 ] {ai_painforward(1);}; +void() zombie_painc12 =[ $painc12, zombie_painc13 ] {ai_painforward(1);}; +void() zombie_painc13 =[ $painc13, zombie_painc14 ] {}; +void() zombie_painc14 =[ $painc14, zombie_painc15 ] {}; +void() zombie_painc15 =[ $painc15, zombie_painc16 ] {}; +void() zombie_painc16 =[ $painc16, zombie_painc17 ] {}; +void() zombie_painc17 =[ $painc17, zombie_painc18 ] {}; +void() zombie_painc18 =[ $painc18, zombie_run1 ] {}; + +void() zombie_paind1 =[ $paind1, zombie_paind2 ] + { + sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM); + droptofloor(); + }; +void() zombie_paind2 =[ $paind2, zombie_paind3 ] {}; +void() zombie_paind3 =[ $paind3, zombie_paind4 ] {}; +void() zombie_paind4 =[ $paind4, zombie_paind5 ] {}; +void() zombie_paind5 =[ $paind5, zombie_paind6 ] {}; +void() zombie_paind6 =[ $paind6, zombie_paind7 ] {}; +void() zombie_paind7 =[ $paind7, zombie_paind8 ] {}; +void() zombie_paind8 =[ $paind8, zombie_paind9 ] {}; +void() zombie_paind9 =[ $paind9, zombie_paind10 ] {ai_pain(1);}; +void() zombie_paind10 =[ $paind10, zombie_paind11 ] {}; +void() zombie_paind11 =[ $paind11, zombie_paind12 ] {}; +void() zombie_paind12 =[ $paind12, zombie_paind13 ] {}; +void() zombie_paind13 =[ $paind13, zombie_run1 ] {}; + +void() zombie_paine1 =[ $paine1, zombie_paine2 ] + { + sound (self, CHAN_VOICE, "zombie/z_pain.wav", 1, ATTN_NORM); + self.health = 60; + }; +void() zombie_paine2 =[ $paine2, zombie_paine3 ] {ai_pain(8);}; +void() zombie_paine3 =[ $paine3, zombie_paine4 ] {ai_pain(5);}; +void() zombie_paine4 =[ $paine4, zombie_paine5 ] {ai_pain(3);}; +void() zombie_paine5 =[ $paine5, zombie_paine6 ] {ai_pain(1);}; +void() zombie_paine6 =[ $paine6, zombie_paine7 ] {ai_pain(2);}; +void() zombie_paine7 =[ $paine7, zombie_paine8 ] {ai_pain(1);}; +void() zombie_paine8 =[ $paine8, zombie_paine9 ] {ai_pain(1);}; +void() zombie_paine9 =[ $paine9, zombie_paine10 ] {ai_pain(2);}; +void() zombie_paine10 =[ $paine10, zombie_paine11 ] + { + sound (self, CHAN_BODY, "zombie/z_fall.wav", 1, ATTN_NORM); + self.solid = SOLID_NOT; + }; +void() zombie_paine11 =[ $paine11, zombie_paine12 ] + { + droptofloor(); + self.nextthink = self.nextthink + 5; + self.health = 60; + }; +void() zombie_paine12 =[ $paine12, zombie_paine13 ] + { + // see if ok to stand up + droptofloor(); + self.health = 60; + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + self.solid = SOLID_SLIDEBOX; + if (!walkmove (0, 0)) + { + self.think = zombie_paine11; + self.solid = SOLID_NOT; + return; + } + }; +void() zombie_paine13 =[ $paine13, zombie_paine14 ] {}; +void() zombie_paine14 =[ $paine14, zombie_paine15 ] {}; +void() zombie_paine15 =[ $paine15, zombie_paine16 ] {}; +void() zombie_paine16 =[ $paine16, zombie_paine17 ] {}; +void() zombie_paine17 =[ $paine17, zombie_paine18 ] {}; +void() zombie_paine18 =[ $paine18, zombie_paine19 ] {}; +void() zombie_paine19 =[ $paine19, zombie_paine20 ] {}; +void() zombie_paine20 =[ $paine20, zombie_paine21 ] {}; +void() zombie_paine21 =[ $paine21, zombie_paine22 ] {}; +void() zombie_paine22 =[ $paine22, zombie_paine23 ] {}; +void() zombie_paine23 =[ $paine23, zombie_paine24 ] {}; +void() zombie_paine24 =[ $paine24, zombie_paine25 ] {}; +void() zombie_paine25 =[ $paine25, zombie_paine26 ] {ai_painforward(5);}; +void() zombie_paine26 =[ $paine26, zombie_paine27 ] {ai_painforward(3);}; +void() zombie_paine27 =[ $paine27, zombie_paine28 ] {ai_painforward(1);}; +void() zombie_paine28 =[ $paine28, zombie_paine29 ] {ai_pain(1);}; +void() zombie_paine29 =[ $paine29, zombie_paine30 ] {}; +void() zombie_paine30 =[ $paine30, zombie_run1 ] + { }; + +void() zombie_die = +{ + sound (self, CHAN_VOICE, "zombie/z_gib.wav", 1, ATTN_NORM); + ThrowHead ("progs/h_zombie.mdl", self.health); + ThrowGib ("progs/gib1.mdl", self.health); + ThrowGib ("progs/gib2.mdl", self.health); + ThrowGib ("progs/gib3.mdl", self.health); +}; + +/* +================= +zombie_pain + +Zombies can only be killed (gibbed) by doing 60 hit points of damage +in a single frame (rockets, grenades, quad shotgun, quad nailgun). + +A hit of 25 points or more (super shotgun, quad nailgun) will allways put it +down to the ground. + +A hit of from 10 to 40 points in one frame will cause it to go down if it +has been twice in two seconds, otherwise it goes into one of the four +fast pain frames. + +A hit of less than 10 points of damage (winged by a shotgun) will be ignored. + +FIXME: don't use pain_finished because of nightmare hack +================= +*/ +void(entity attacker, float take) zombie_pain = +{ + local float r; + + self.health = 60; // allways reset health + + if (take < 9) + return; // totally ignore + + if (self.inpain == 2) + return; // down on ground, so don't reset any counters + +// go down immediately if a big enough hit + if (take >= 25) + { + self.inpain = 2; + zombie_paine1 (); + return; + } + + if (self.inpain) + { +// if hit again in next gre seconds while not in pain frames, definately drop + self.pain_finished = time + 3; + return; // currently going through an animation, don't change + } + + if (self.pain_finished > time) + { +// hit again, so drop down + self.inpain = 2; + zombie_paine1 (); + return; + } + +// gp into one of the fast pain animations + self.inpain = 1; + + r = random(); + if (r < 0.25) + zombie_paina1 (); + else if (r < 0.5) + zombie_painb1 (); + else if (r < 0.75) + zombie_painc1 (); + else + zombie_paind1 (); +}; + +void() zombie_sleep = [ $paine11, zombie_sleep ] { ai_stand(); }; + +void() zombie_wake = +{ + self.th_stand = zombie_stand1; + self.th_walk = zombie_walk1; + self.th_pain = zombie_pain; + self.th_run = zombie_run1; + self.th_missile = zombie_missile; + +// paine12 will handle setting solid, just make the size box correct. +// self.solid = SOLID_SLIDEBOX; +// setsize (self, '-16 -16 -24', '16 16 40'); + + zombie_paine12(); +}; + +void() zombie_escape1=[ $cruc_1, zombie_escape2 ] {self.nextthink=time+0.20;}; +void() zombie_escape2=[ $cruc_2, zombie_escape3 ] {self.nextthink=time+0.20;}; +void() zombie_escape3=[ $cruc_3, zombie_escape4 ] {self.nextthink=time+0.20;}; +void() zombie_escape4=[ $cruc_4, zombie_escape5 ] {self.nextthink=time+0.20;}; +void() zombie_escape5=[ $cruc_5, zombie_escape6 ] {self.nextthink=time+0.15;}; +void() zombie_escape6=[ $cruc_6, zombie_escape7 ] {self.nextthink=time+0.15;}; +void() zombie_escape7=[ $cruc_7, zombie_escape8 ] {self.nextthink=time+0.15;}; +void() zombie_escape8=[ $cruc_8, zombie_escape9 ] {self.nextthink=time+0.15;}; +void() zombie_escape9=[ $cruc_9, zombie_escape10 ] {self.nextthink=time+0.15;}; +void() zombie_escape10=[$cruc_10, zombie_escape11] {self.nextthink=time+0.15;}; +void() zombie_escape11=[$cruc_11, zombie_escape12] {self.nextthink=time+0.10;}; +void() zombie_escape12=[$cruc_12, zombie_escape13] {self.nextthink=time+0.10;}; +void() zombie_escape13=[$cruc_13, zombie_escape14] {self.nextthink=time+0.10;}; +void() zombie_escape14=[$cruc_14, zombie_escape15] +{ + makevectors (self.angles); + setorigin ( self, self.origin + v_forward * 8); + self.nextthink=time+0.15; +}; +void() zombie_escape15=[ $cruc_15, zombie_escape16] +{ + makevectors (self.angles); + setorigin ( self, self.origin + v_forward * 8); + self.nextthink=time+0.15; +}; +void() zombie_escape16 = [ $cruc_16, zombie_run1 ] +{ + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + self.takedamage = DAMAGE_AIM; + self.th_stand = zombie_stand1; + self.th_walk = zombie_walk1; + self.th_pain = zombie_pain; + self.th_run = zombie_run1; + self.th_missile = zombie_missile; + self.health = 60; + sound (self, CHAN_VOICE, "zombie/z_idle.wav", 1, ATTN_IDLE); + + droptofloor(); +}; + +void() zombie_escape = +{ + if (!self.pain_finished) + { + self.pain_finished = time + self.delay; + } + self.think = zombie_cruc1; + self.nextthink = time + 0.1; +}; + +//============================================================================ + +/*QUAKED monster_zombie (1 0 0) (-16 -16 -24) (16 16 32) Crucified Lying ambush Sneaky + +Crucified - Zombie stays stuck in the wall, moans and writhes. +Lying - Zombie will start lying down, and stand when player spotted. +Ambush - Just like all the other ambush flags. +Sneaky - Zombie stays stuck in the wall, but breaks his chains, hops off the wall, and attacks after seeing the player. + +If crucified, stick the bounding box 12 pixels back into a wall to look right. +*/ +void() monster_zombie = +{ + if (deathmatch) + { + remove(self); + return; + } + + precache_model ("progs/zombie.mdl"); + precache_model ("progs/h_zombie.mdl"); + precache_model ("progs/zom_gib.mdl"); + + precache_sound ("zombie/z_idle.wav"); + precache_sound ("zombie/z_idle1.wav"); + precache_sound ("zombie/z_shot1.wav"); + precache_sound ("zombie/z_gib.wav"); + precache_sound ("zombie/z_pain.wav"); + precache_sound ("zombie/z_pain1.wav"); + precache_sound ("zombie/z_fall.wav"); + precache_sound ("zombie/z_miss.wav"); + precache_sound ("zombie/z_hit.wav"); + precache_sound ("zombie/idle_w2.wav"); + + self.solid = SOLID_SLIDEBOX; + self.movetype = MOVETYPE_STEP; + + setmodel (self, "progs/zombie.mdl"); + + setsize (self, VEC_HULL_MIN, VEC_HULL_MAX); + self.health = 60; + + self.th_stand = zombie_stand1; + self.th_walk = zombie_walk1; + self.th_run = zombie_run1; + self.th_pain = zombie_pain; + self.th_die = zombie_die; + self.th_missile = zombie_missile; + + if (self.spawnflags & SPAWN_CRUCIFIED) + { + self.movetype = MOVETYPE_NONE; + zombie_cruc1 (); + } + else if (self.spawnflags & SPAWN_LYING) + { + self.solid = SOLID_NOT; + self.th_stand = zombie_sleep; + self.th_run = zombie_wake; + self.th_walk = zombie_wake; + self.th_pain = zombie_wake; + self.th_missile = zombie_wake; + setsize (self, '-16 -16 -24', '16 16 -16'); + + // HOLY HACK, BATMAN! - Don't blame me. Check out FindTarget in ai.qc! + if ( self.spawnflags & SPAWN_AMBUSH) + self.spawnflags = SPAWN_LYING; + + walkmonster_start(); + } + else if (self.spawnflags & SPAWN_SNEAKY) + { + self.movetype = MOVETYPE_NONE; + self.solid = SOLID_NOT; + self.th_stand = zombie_cruc1; + self.th_run = zombie_escape; + self.th_walk = zombie_escape; + self.th_pain = zombie_escape; + self.th_missile = zombie_escape; + + if (!self.delay) + self.delay = 3; + + self.ideal_yaw = self.angles * '0 1 0'; + if (!self.yaw_speed) + self.yaw_speed = 20; + self.view_ofs = '0 0 25'; + self.flags = self.flags | FL_MONSTER; + self.th_stand (); + self.nextthink = self.nextthink + random()*0.5; + total_monsters = total_monsters + 1; + } + else + { + if (self.spawnflags & SPAWN_AMBUSH) + self.spawnflags = SPAWN_LYING; + walkmonster_start(); + } +};